Hướng dẫn khai báo hàm con trong C. Ưu điểm của hàm trong lập trình C

By 03/07/2021Tháng Bảy 6th, 2021C/C++, KIẾN THỨC LẬP TRÌNH

1. Các hàm trong C

Trong hướng dẫn này, bạn sẽ được giới thiệu về các hàm trong C (cả hàm do người dùng định nghĩa và hàm thư viện chuẩn). Ngoài ra, bạn cũng sẽ biết được lý do tại sao các hàm lại được sử dụng trong lập trình.

ham trong c

Các hàm trong C

Hàm trong C (ngôn ngữ lập trình) là một khối mã thực hiện một nhiệm vụ cụ thể.

Giả sử, bạn cần viết một lập trình để tạo một hình tròn và tô màu cho nó. Bạn cần chọn bán kính và màu sắc. Bạn có thể tạo 2 hàm dưới đây để thực hiện nhiệm vụ trên:

  • Tạo một hàm vẽ hình tròn: createCircle() function
  • Tạo một hàm tô màu: color() function

Việc phân chia các vấn đề phức tạp thành các vấn đề nhỏ hơn giúp lập trình của bạn dễ hiểu và dễ sử dụng lại hơn.

Có hai loại hàm trong C:

  • Các hàm thư viện tiêu chuẩn
  • Các hàm do người dùng định nghĩa

1.1 Các hàm thư viện tiêu chuẩn

Các hàm thư viện tiêu chuẩn chính là các hàm sẵn có trong lập trình C.

Các hàm này được xác định trong các header files (tệp có phần mở rộng .h)

  • Printf() là một hàm thư viện chuẩn có chức năng xuất kết quả đã được định dạng sẵn ra màn hình (hiển thị kết quả trên màn hình). Hàm này được xác định trong tệp tiêu đề stdio.h. Do đó, để sử dụng hàm printf (), chúng ta cần bao gồm tệp tiêu đề stdio.h bằng cách sử dụng #include <stdio.h>.
  • Hàm sqrt() có chức năng tính căn bậc hai của một số. Hàm được xác định trong tệp tiêu đề math.h.

Truy cập vào các hàm thư viện tiêu chuẩn trong lập trình C để tìm hiểu thêm.

1.2 Các hàm do người dùng xác định

Bạn cũng có thể tạo các hàm trong C phù hợp với nhu cầu của mình. Các hàm do người dùng tạo ra được gọi là hàm do người dùng xác định.

Hàm do người dùng xác định hoạt động như thế nào?

#include <stdio.h>

void functionName()

{

… .. …

… .. …

}

int main()

{

… .. …

… .. …

functionName();

… .. …

… .. …

}

Lập trình này sẽ thực thi các mã sau hàm main()

Khi trình biên dịch gặp hàm functionName():, điều khiển của lập trình sẽ nhảy tới

void functionName()

Và, trình biên dịch bắt đầu thực thi các mã bên trong functionName ().

Điều khiển lập trình sẽ quay lại hàm main() sau khi mã bên trong hàm này được thực thi.

Lưu ý, tên hàm là định danh và phải là duy nhất.

Ưu điểm của các hàm trong C do người dùng xác định

  • Lập trình này sẽ dễ hiểu, dễ bảo trì và dễ sửa lỗi hơn.
  • Các mã tái sử dụng có thể được dùng trong những lập trình khác
  • Một lập trình lớn có thể được chia thành các mô-đun nhỏ hơn. Do đó, có thể chia một dự án lớn thành nhiều phần và mỗi lập trình viên đảm nhận một phần.

2. Các hàm trong C do người dùng xác định

2.1 Ví dụ: Hàm trong C do người dùng xác định

Đây là ví dụ về việc thêm 2 số nguyên. Để thực hiện nhiệm vụ này, bạn cần tạo hàm addNumbers do người dùng xác định.

#include <stdio.h>

int addNumbers(int a, int b); // function prototype

int main()

{

int n1,n2,sum;

printf(“Enters two numbers: “);

scanf(“%d %d”,&n1,&n2);

sum = addNumbers(n1, n2); // function call

printf(“sum = %d”,sum);

return 0;

}

int addNumbers(int a, int b) // function definition

{

int result;

result = a+b;

return result; // return statement

}

2.2 Nguyên mẫu hàm

Nguyên mẫu hàm chính là sự khai báo hàm: tên hàm, các tham số và kiểu trả về. Nó không phải là phần thân của hàm.

Nguyên mẫu hàm thông báo cho trình biên dịch rằng hàm đó có thể sẽ được sử dụng trong lập trình này.

Cú pháp của nguyên mẫu hàm

returnType functionName(type1 argument1, type2 argument2, …);

note: argument: đối số

Trong ví dụ trên, int addNumbers(int a, int b); chính là nguyên mẫu hàm cung cấp thông tin cho trình biên dịch:

  • tên của hàm là addNumbers ()
  • kiểu trả về của hàm là int
  • hai đối số kiểu int được chuyển cho hàm

Không cần tạo nguyên mẫu hàm nếu hàm do người dùng xác định được đặt trước hàm main().

2.3 Lời gọi hàm

Quyền kiểm soát lập trình được chuyển sang cho hàm do người dùng xác định thông qua lời gọi hàm

Cú pháp của lời gọi hàm

functionName(argument1, argument2, …);

Trong ví dụ trên, lời gọi hàm được thực hiện bằng lệnh addNumbers(n1, n2); nằm trong hàm main().

2.4 Định nghĩa hàm

Định nghĩa hàm chứa khối mã để thực hiện một nhiệm vụ cụ thể. Trong ví dụ trên, định nghĩa hàm chính là thêm 2 số và trả lại nó.

Cú pháp định nghĩa hàm

returnType functionName(type1 argument1, type2 argument2, …)

{

//body of the function

}

Khi một hàm được gọi, quyền điều khiển của lập trình sẽ được chuyển sang định nghĩa hàm. Và trình biên dịch bắt đầu thực thi các mã bên trong phần thân của hàm.

2.5 Truyền đối số cho một hàm

Trong lập trình, đối số ám chỉ một biến được truyền cho hàm. Trong ví dụ trên, 2 biến số n1 và n2 được truyền thông qua quá trình gọi hàm.

Các tham số a và b chấp nhận các đối số được truyền vào trong định nghĩa hàm. Các đối số này được gọi là tham số chính thức của hàm.

Loại đối số được truyền cho một hàm và các tham số chính thức phải khớp nhau. Nếu không, trình biên dịch sẽ báo lỗi.

Nếu n1 thuộc kiểu char thì a cũng phải thuộc kiểu char. Nếu n2 thuộc kiểu float thì biến b cũng phải thuộc kiểu float.

Cũng có thể gọi một hàm mà không cần truyền đối số.

2.6 Câu lệnh return (trả về)

Câu lệnh return kết thúc việc thực thi một hàm và trả về giá trị cho hàm đang gọi. Điều khiển của lập trình sẽ được chuyển cho hàm đang gọi nằm sau câu lệnh return.

Trong ví dụ trên, giá trị của biến result được trả về cho hàm main. Biến sum trong hàm main() được gán giá trị này.

Cú pháp của câu lệnh return

return (expression);

Ví dụ,

return a;

return (a+b);

Kiểu giá trị được trả về từ hàm, kiểu trả về được chỉ định trong nguyên mẫu hàm và định nghĩa hàm phải khớp nhau.

Truy cập vào đây để biết thêm thông tin chi tiết về cách truyền đối số và giá trị trả về từ một hàm.

3. Khai báo hàm trong C

Trong phần này, bạn sẽ được tìm hiểu về các phương pháp tiếp cận khác nhau để giải quyết cùng một vấn đề bằng cách sử dụng, khai báo hàm trong C.

4 lập trình dưới đây sẽ kiểm tra xem số nguyên mà người dùng nhập vào có phải là số nguyên tố không.

Đầu ra của tất cả các lập trình dưới đây đều giống nhau, và tôi chỉ tạo một hàm do người dùng xác định trong mỗi ví dụ. Tuy nhiên, cách tiếp cận mà tôi thực hiện trong mỗi ví dụ là khác nhau.

Ví dụ 1: Không có đối số nào được truyền và không có giá trị nào trả về

#include <stdio.h>

void checkPrimeNumber();

int main()

{

checkPrimeNumber(); // argument is not passed

return 0;

}

// return type is void meaning doesn’t return any value

void checkPrimeNumber()

{

int n, i, flag = 0;

printf(“Enter a positive integer: “);

scanf(“%d”,&n);

for(i=2; i <= n/2; ++i)

{

if(n%i == 0)

{

flag = 1;

}

}

if (flag == 1)

printf(“%d is not a prime number.”, n);

else

printf(“%d is a prime number.”, n);

}

Hàm checkPrimeNumber() nhận đầu vào từ người dùng, kiểm tra xem nó có phải là số nguyên tố hay không và hiển thị nó lên màn hình.

Các dấu ngoặc đơn trống trong câu lệnh checkPrimeNumber (); bên trong hàm main() có nghĩa là không có đối số nào được truyền vào hàm.

Kiểu trả về của hàm là void. Do đó, không có giá trị nào được trả về từ hàm này.

Ví dụ 2: Không có đối số nào được truyền nhưng có giá trị trả về

#include <stdio.h>

int getInteger();

int main()

{

int n, i, flag = 0;

// no argument is passed

n = getInteger();

for(i=2; i<=n/2; ++i)

{

if(n%i==0){

flag = 1;

break;

}

}

if (flag == 1)

printf(“%d is not a prime number.”, n);

else

printf(“%d is a prime number.”, n);

return 0;

}

// returns integer entered by the user

int getInteger()

{

int n;

printf(“Enter a positive integer: “);

scanf(“%d”,&n);

return n;

}

Các dấu ngoặc đơn trống trong câu lệnh n = getInteger(); đồng nghĩa với việc không có đối số nào được truyền cho hàm này. Và giá trị được trả về từ hàm được gán cho n.

Hàm getInteger() nhận đầu vào từ người dùng và trả về nó. Mã nằm trong hàm main() sẽ kiểm tra xem số nhập vào có phải là số nguyên tố hay không

Ví dụ 3: Đối số được truyền nhưng không có giá trị trả về

#include <stdio.h>

void checkPrimeAndDisplay(int n);

int main()

{

int n;

printf(“Enter a positive integer: “);

scanf(“%d”,&n);

// n is passed to the function

checkPrimeAndDisplay(n);

return 0;

}

// return type is void meaning doesn’t return any value

void checkPrimeAndDisplay(int n)

{

int i, flag = 0;

for(i=2; i <= n/2; ++i)

{

if(n%i == 0){

flag = 1;

break;

}

}

if(flag == 1)

printf(“%d is not a prime number.”,n);

else

printf(“%d is a prime number.”, n);

}

Giá trị số nguyên do người dùng nhập vào sẽ được truyền vào hàm checkPrimeAndDisplay()

Hàm checkPrimeAndDisplay() sẽ kiểm tra xem đối số được truyền vào có phải là số nguyên tố hay không và hiển thị thông báo thích hợp.

Ví dụ 4: Đối số được truyền và có giá trị trả về

#include <stdio.h>

int checkPrimeNumber(int n);

int main()

{

int n, flag;

printf(“Enter a positive integer: “);

scanf(“%d”,&n);

// n is passed to the checkPrimeNumber() function

// the returned value is assigned to the flag variable

flag = checkPrimeNumber(n);

if(flag == 1)

printf(“%d is not a prime number”,n);

else

printf(“%d is a prime number”,n);

return 0;

}

// int is returned from the function

int checkPrimeNumber(int n)

{

int i;

for(i=2; i <= n/2; ++i)

{

if(n%i == 0)

return 1;

}

return 0;

}

Đầu vào được người dùng nhập vào sẽ được truyền đến hàm checkPrimeNumber ().

Hàm checkPrimeNumber() kiểm tra xem đối số được truyền vào có phải là số nguyên tố hay không.

Nếu đối số được truyền vào là số nguyên tố, hàm trả về 0. Nếu đối số được truyền vào không phải là số nguyên tố, hàm trả về 1. Giá trị trả về được gán cho biến flag.

Tùy thuộc vào việc biến flag là 0 hay 1 thì đầu ra sẽ hiển thị thông báo thích hợp từ hàm main()

Cách tiếp cận nào tốt hơn?

Để đánh giá cách tiếp cận nào tốt hơn thì phải xem xét đến vấn đề mà bạn đang muốn giải quyết. Nhưng trong trường hợp này, truyền đối số và trả về giá trị từ một hàm (ví dụ 4) là tốt nhất.

Một chức năng nên thực hiện một nhiệm vụ cụ thể. Hàm checkPrimeNumber() không thể lấy đầu vào từ người dùng cũng như không thể hiển thị thông báo thích hợp được. Nó chi có chức năng kiểm tra một số có phải là số nguyên tố hay không.

4. Đệ quy trong lập trình C

Trong phần này, bạn sẽ được học cách khai báo hàm đệ quy trong lập trình C thông qua ví dụ.

Một hàm có khả năng tự gọi chính nó thì được xem là hàm đệ quy. Và kỹ thuật này được gọi là đệ quy.

Đệ quy hoạt động như thế nào?

void recurse()

{

… .. …

recurse();

… .. …

}

int main()

{

… .. …

recurse();

… .. …

}

Đệ quy diễn ra liên tục cho đến khi nó bị ngăn lại bởi một vài điều kiện nhất định.

Để ngăn chặn đệ quy lặp vô hạn, bạn sử dụng câu lệnh if … else (hoặc cách tiếp cận tương tự) tại vị trí mà một nhánh thực hiện lệnh gọi đệ quy và nhánh còn lại thì không.

Ví dụ: Tổng các số tự nhiên sử dụng đệ quy

#include <stdio.h>

int sum(int n);

 

int main() {

int number, result;

 

printf(“Enter a positive integer: “);

scanf(“%d”, &number);

 

result = sum(number);

 

printf(“sum = %d”, result);

return 0;

}

 

int sum(int n) {

if (n != 0)

// sum() function calls itself

return n + sum(n-1);

else

return n;

}

Đầu ra

Enter a positive integer:3

sum = 6

Đầu tiên, hàm sum() được gọi từ hàm main() với number được truyền dưới dạng đối số.

Giả sử ban đầu giá trị của n bên trong sum() là 3. Trong lần gọi hàm tiếp theo, 2 được chuyển cho hàm sum(). Quá trình này tiếp tục cho đến khi n bằng 0.

Khi n bằng 0, điều kiện if không thành công và phần else được thực thi, trả về tổng số cuối cùng của các số nguyên cho hàm main ().

Ưu điểm và nhược điểm của đệ quy

Đệ quy giúp lập trình trở nên ngắn gọn hơn. Tuy nhiên, dùng vòng lặp thay vì đệ quy sẽ khiến lập trình diễn ra chậm hơn nhiều.

Tóm lại, đệ quy là một khái niệm quan trọng. Nó thường được sử dụng trong cấu trúc dữ liệu và các thuật toán. Ví dụ, đệ quy thường được sử dụng để giải quyết những vấn đề như duyệt cây.

5. Lớp lưu trữ trong lập trình C

Trong phần này, bạn sẽ được tìm hiểu về phạm vi và thời gian tồn tại của các biến cục bộ và các biến toàn cục. Bên cạnh đó, bạn cũng sẽ biết thêm thông tin về biến tĩnh và biến register.

Tất cả các biến trong lập trình C đều có 2 tính năng: loại và lớp lưu trữ.

Loại đề cập đến kiểu dữ liệu của một biến. Và lớp lưu trữ xác định phạm vi, khả năng hiển thị và thời gian tồn tại của một biến.

Có 4 loại lớp lưu trữ:

  1. Tự động
  2. bên ngoài
  3. tĩnh
  4. register

5.1 Biến cục bộ

Các biến được khai báo bên trong một khối chính là biến tự động hoặc biến cục bộ. Cá biến cục bộ chỉ tồn tạo bên trong một khối mà nó được khai báo.

Ví dụ:

#include <stdio.h>

int main(void) {

for (int i = 0; i < 5; ++i) {

printf(“C programming”);

}

// Error: i is not declared at this point

printf(“%d”, i);

return 0;

}

Khi chạy, lập trình trên sẽ báo lỗi là undeclared identifier i. Bởi vì i được khai báo bên trong khối vòng lặp for. Còn bên ngoài khối đó thì nó không được khai báo.

Xem thêm ví dụ dưới đây:

int main() {

int n1; // n1 is a local variable to main()

}

 

void func() {

int n2; // n2 is a local variable to func()

}

Trong ví dụ này, n1 là biến cục bộ của hàm main() và n2 là biến cục bộ của hàm func().

Điều này có nghĩa là bạn không thể truy cập vào biến n1 bên trong hàm func() bởi nó chỉ tồn tại bên trong hàm main(). Tương tự, bạn cũng không thể truy cập vào biến n2 trong hàm main() vì nó chỉ tồn tại bên trong hàm func().

5.2 Biến toàn cục

Các biến được khai báo bên ngoài tất cả các hàm được gọi là biến bên ngoài hoặc biến toàn cục. Chúng có thể được truy cập từ bất kỳ hàm nào trong lập trình.

Ví dụ 1: Biến toàn cục

#include <stdio.h>

void display();

int n = 5; // global variable

int main()

{

++n;

display();

return 0;

}

void display()

{

++n;

printf(“n = %d”, n);

}

Đầu ra

n = 7

Giả sử, một biến toàn cục được khai báo trong file1. Nếu bạn cố gắng sử dụng biến đó trong tệp file2, trình biên dịch sẽ khiếu nại. Để giải quyết vấn đề này, bạn nên sử dụng từ khóa extern trong file2 để ám chỉ rằng rằng biến bên ngoài đã được khai báo trong một tệp khác.

5.3 Biến register

Từ khóa register dùng để khai báo biến register. Biến register sẽ chạy nhanh hơn biến cục bộ.

Tuy nhiên, các trình biên dịch hiện đại có ưu điểm là tối ưu hóa mã nhanh chóng. Và sử dụng biến register cũng không giúp lập trình của bạn chạy nhanh hơn.

Trừ khi bạn làm việc trên hệ thống nhúng và bạn biết cách tối ưu hóa mã cho những ứng dụng nhất định thì không cần sử dụng các biến register.

5.4 Biến tĩnh

Biến tĩnh được khai báo bằng cách sử dụng từ khóa static. Ví dụ;

static int i;

Giá trị của biến tĩnh vẫn tồn tại cho đến khi lập trình kết thúc.

Ví dụ 2: Biến tĩnh

#include <stdio.h>

void display();

int main()

{

display();

display();

}

void display()

{

static int c = 1;

c += 5;

printf(“%d “,c);

}

Đầu ra

6 11

Trong lần gọi hàm đầu tiên, giá trị của c được khởi tạo bằng 1. Giá trị của nó tăng thêm 5. Lúc này, giá trị của c sẽ là 6 và số 6 được in lên màn hình

Trong lần gọi thứ 2, c không được khởi tạo từ 1 nữa vì c là một biến tĩnh. Lúc này, giá trị của c tăng thêm 5. Và giá trị của c sẽ là 11, số 11 được in lên màn hình.

Trên đây là tất cả các kiến thức về các hàm trong C, ưu điểm của việc sử dụng hàm. Hy vọng bài viết sẽ giúp bạn có thêm kiến thức về (hàm) function trong C. Chúc bạn thành công!

Để lại một câu trả lời