Lập trình C: Bài 8 – Hàm trong C

1. Mở đầu về hàm

Hãy nhập và chạy 2 ví dụ sau, quan sát kết quả và nhận xét.

Ví dụ 1:

// e.g 1 about function in C - code by nguyenvanquan7826

#include <stdio.h>

void loiKhuyen()
{
    printf("Neu hoc nghiem tuc va cham chi thi ban se thay:\n");
    printf("Khong co viec gi kho\n");
    printf("Chi so long khong ben\n");
    printf("Dao nui va lap bien\n");
    printf("Quyet chi at lam nen\n");
    printf("\n");
}

int main()
{
    printf("Hoc bai nay kho qua!\n");
    loiKhuyen();

    printf("Hoc C kho qua!\n");
    loiKhuyen();

    return 0;
}

Kết quả:

Hoc bai nay kho qua!
Neu hoc nghiem tuc va cham chi thi ban se thay:
Khong co viec gi kho
Chi so long khong ben
Dao nui va lap bien
Quyet chi at lam nen

Hoc C kho qua!
Neu hoc nghiem tuc va cham chi thi ban se thay:
Khong co viec gi kho
Chi so long khong ben
Dao nui va lap bien
Quyet chi at lam nen

Ví dụ 2:

// e.g 2 about function in C - code by nguyenvanquan7826

#include <stdio.h>

void vechu(char chu, int soluong)
{
    int i;
    for (i = 0; i < soluong; i++) {
        printf("%c", chu);
    }
    printf("\n");
}

int main()
{
    vechu('h', 20);
    vechu('a', 30);
    vechu('h', 12);
    vechu('a', 5);
    vechu('p', 10);

    return 0;
}

Kết quả:

hhhhhhhhhhhhhhhhhhhh
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
hhhhhhhhhhhh
aaaaa
pppppppppp

Nếu như trong chương trình có một số lệnh, hoặc công việc nào đó có dạng giống hoặc tương tự nhau cần lặp đi lặp lại nhiều lần ở các vị trí khác nhau, để khỏi mất thời gian và công sức sao chép những dãy lệnh đó ta nên tổ chức và xây dựng nó thành một đoạn lệnh, chỗ nào trong chương trình cần thực hiện công việc đó thì chỉ cần gọi đoạn lệnh đó mà thôi. Các đoạn lệnh đó gọi là hàm.

Hàm là một đoạn lệnh thực hiện một khối công việc được lặp đi lặp lại nhiều lần trong khi chạy chương trình hoặc dùng tách một khối công việc cụ thể để chương trình đỡ phức tạp.

Trong ví dụ 1: Có 2 chương trình, 1 chương trình chính: main và 1 chương trình phụ: loiKhuyen không có tham số và không trả về giá trị nào nên kiểu dữ liệu của hàm được khai báo là void. Cả main và loiKhuyen đều gọi là hàm. Hàm main là hàm chính thực thi và chạy toàn bộ chương trình của ta. Hàm loiKhuyen thực hiện một số công việc (xuất ra 5 dòng chữ như trên). Trong hàm main ta có 2 chỗ gọi hàm loiKhuyen();

Trong ví dụ 2: Cũng tương tự như ví dụ 1, nó gồm 2 hàm, 1 hàm chính là main và 1 hàm phụ, nhưng hàm phụ là vechu(char chu, int soluong); có 2 tham số hình thức là chu thuộc kiểu char và soluong thuộc kiểu int. Hàm vechu cũng không có giá trị trả về nên kiểu dữ liệu của hàm được khai báo là void. Trong hàm main ta có 3 lần gọi hàm vechu với các tham số thực lần lượt như trong chương trình trên.

2. Nguyên tắc hoạt động của hàm

  • Khi máy gặp lời gọi hàm ở bất kỳ chỗ nào đó thì hàm bắt đầu được thực hiện, tức sẽ rời chỗ đó để đi đến hàm được gọi.
  • Nếu là hàm có tham số thì máy sẽ thực hiện truyền tham số thực cho các tham số hình thức tương ứng trong hàm.
  • Máy bắt đầu thực hiện lần lượt các câu lệnh trong thân hàm đến khi nào lệnh reuturn hoặc dấu } của hàm thì dừng lại thoát khỏi hàm trở về chương trình đã gọi nó và thực hiện tiếp các câu lệnh của chương trình này.
  • Nếu hàm trả về giá trị thì giá trị của biểu thức return trong hàm sẽ là giá trị của hàm.

Lưu ý: Khi khai báo hàm void vechu(char chu, int soluong) thì chu và soluong là các tham số hình thức, còn khi gọi hàm vechu(‘h’, 20); thì ‘h’ và 20 là các tham số thực sự.

3. Cấu trúc của hàm

Chúng ta có cấu trúc khai báo hàm như sau:

KieuTraVe TenHam(Cac tham so)
{
    ThanHam;
}

Tên hàm được đặt theo quy tắc đặt tên biến.

4. Các ví dụ về hàm

Ví dụ 1: Hàm không trả về giá trị

Ở trên chúng ta đã có 2 ví dụ mở đầu đều là hàm không trả về giá trị và được khai báo có kiểu trả về là void.

Ví dụ 2: Hàm có giá trị trả về

Xây dựng hàm tính n! và tính vài biểu thức giai thừa. Biết n! = 1.2.3…n

// e.g about function in C - code by nguyenvanquan7826

#include <stdio.h>

long giaiThua(int n) 
{
    long gt = 1;
    int i;
    for (i = 2; i <= n; i++)
    {
        gt *= i; // <=> gt = gt * i
    }
    return gt;
}

int main()
{
    printf("5! = %ld \n", giaiThua(5));
    printf("6! + 1 = %ld \n", giaiThua(6) + 1);
    return 0;
}

Kết quả:

5! = 120
6! + 1 = 721

Do n! có thể là một số nguyên khá lớn nên hàm trả về giá trị là kiểu long (lớn hơn kiểu int – Xem lại Các kiểu dữ liệu trong C). Lệnh return gt nghĩa là trả về biến gt, gt phải có cùng kiểu dữ liệu với kiểu dữ liệu của hàm (cùng là long).

Trong VD này hàm giaiThua trả về giá trị là giai thừa của số n được truyền vào vì vậy nó có thể được coi như 1 biến và được sử dụng trong các biểu thức, sử dụng để in ra luôn kết quả. Khi gặp hàm ở bất ký đâu thì nó sẽ truyền 1 tham số tương ứng cho hàm và trả về giá trị qua lệnh return.

Ví dụ 3: Hàm kiểm tra

Xây dựng hàm kiểm tra một số có là số đẹp không? Số đẹp là số chia hết cho 2 và chia hết cho 5.

Để làm bài này, chúng ta cần xác định là hàm này kiểm tra một số là số gì đó, do vậy ta cần trả về giá trị đúng (1) hoặc sai (0). Vậy nên hàm sẽ trả về kiểu số nguyên.

// e.g about function in C - code by nguyenvanquan7826
#include <stdio.h>

int kiemTraChiaHet(int n)
{
    if(n % 2 == 0 && n % 5 == 0)
    {
        return 1;
    }
    return 0;
}

int main()
{
    int x;

    printf("Nhap x = ");
    scanf("%d", &x);

    if( kiemTraChiaHet(x) == 0)
    {
        printf("day khong phai so dep @@\n");
    } else {
        printf("day la so dep !!!\n");
    }

    return 0;
}

Ở hàm kiemTraChiaHet, khi n chia hết cho 2 và cho 5, thì sẽ thực hiện lệnh return 1, khi thực hiện lệnh này tức là kết thúc hàm, không làm gì trong hàm nãy nữa dù đằng sau còn lệnh hay không. Nếu số n không chia hết cho 2 và cho 5 thì sẽ thực hiện lệnh return 0.

Ví dụ 4: Các hàm gọi nhau

// e.g about function in C - code by nguyenvanquan7826

#include <stdio.h>

int max2(int a, int b) 
{
    return a > b ? a : b;
}

int max3(int a, int b, int c) 
{
    return max2( max2(a, b), c);
}

int main()
{
    int a = 7, b = 13, c = 4;
    printf("So lon nhat la %d \n", max3(a, b, c));
    return 0;
}

Kết quả:

So lon nhat la 13

Ở ví dụ này chúng ta xây dựng 2 hàm để tìm số lớn nhất của 2 số và 3 số. Trong hàm max2 chúng ta sử dụng toán tử diều kiện để tìm số lớn nhất trong 2 số. Trong hàm max3 ta đã gọi hàm max2 đến 2 lần để tìm số lớn nhất trong a, b rồi lấy số tìm được só sánh với c tìm ra số lớn nhất.

Chú ý: Hàm khai báo sau được gọi các hàm đã khai báo trước nó nhưng các hàm khai báo trước không được gọi hàm khai báo sau. Tức là trong VD trên nếu ta xây dựng hàm max2 ở sau hàm max3 thì máy sẽ báo lỗi. Để khắc phục điều này chúng ta thường khai báo các hàm ở đầu chương trình sau đó chúng ta định nghĩa các hàm ở bất kỳ đâu trong chương trình đều được ta xét ví dụ tiếp:

Ví dụ 5: Khai báo hàm trước khi định nghĩa hàm

// e.g about function in C - code by nguyenvanquan7826
#include <stdio.h>

// khai bao cac ham max2, max3, max4
int max2(int a, int b);
int max3(int a, int b, int c);
int max4(int a, int b, int c, int d);

int max3(int a, int b, int c) {
    return max2( max2(a, b), c);
}

int max2(int a, int b) {
    return a > b ? a : b;
}

int main() 
{
    int a = 7, b = 13, c = 4, d = 16;
    printf("So lon nhat trong a, b, c la %d\n", max3(a, b, c));
    printf("So lon nhat trong a, b, c, d la %d\n", max4(a, b, c, d));
    return 0;
}

int max4(int a, int b, int c, int d) 
{
    return max2( max2(a, b), max2(c, d) );
}

Kết quả:

So lon nhat trong a, b, c la 13
So lon nhat trong a, b, c, d la 16

Khi làm việc chúng ta nên khai báo các hàm trước như thế này và định nghĩa sau. Như vậy chúng ta sẽ tránh được một số lỗi khi mà chúng ta muốn dùng các hàm trong nhau.

Ta đã biết các hàm có thể gọi lẫn nhau và một hàm cũng có thể gọi chính nó, đó là ta sử dụng hàm đệ quy.

Ví dụ 6: Hàm đệ quy

Ta đã biết công thức tính n! = 1.2.3…n. Tuy nhiên ta cũng có thể viết n! = (n-1)!.n. Trong cách viết này để tính n! chúng ta lại cần tính (n-1)!. Do vậy đây có thể gọi là biểu thức đệ quy và hàm cũng vậy, hàm gọi lại chính nó thì gọi là hàm đệ quy.

// e.g about function in C - code by nguyenvanquan7826
#include <stdio.h>

int giaiThua(int n) 
{
    if(n == 0) return 1; // dieu kien dung
    return giaiThua(n-1) * n; // loi goi chinh no
}

int main() {
    int n = 5;
    printf("%d! = %d",n, giaiThua(n));
    return 0;
}

Lưu ý trong hàm đệ quy luôn luôn có 2 yếu tố là điều kiện dừng và lời gọi chính nó.

Ví dụ 7: Dùng dẫn hướng #define để định nghĩa hàm đơn giản

#include <stdio.h>

#define tong(x, y) x + y

int main() 
{
    int a = 5, b = 8;
    printf("%d + %d = %d",a, b, tong(a, b));
    return 0;
}

Đến đây chúng ta đã cơ bản nắm được về hàm, nhưng còn rất nhiều điều cần nói về hàm nữa, đặc biệt là hàm liên quan đến mảng và con trỏ. Chúng ta sẽ tiếp tục tìm hiều chúng ở các bài sau cùng với mảng và con trỏ.

Bài tập

  1. Viết hàm tính tổng S = 1+2+….+n.
  2. Viết các hàm kiểm tra số nguyên tố, số hoàn hảo.
  3. Nhập vào một dãy n số và thông báo có phải số nguyên tố, số hoàn hảo hay không.