← Lập trình C cơ bản

Bài 11 · Vận dụng · 22 phút· Cập nhật 11/06/2026

Hàm

Biên soạn bởi Nguyễn Anh Tuấn

Tách chương trình thành hàm: parameter/argument, return/void, prototype, scope và pass by value.

Hàm (function) gói một việc thành khối có tên, viết một lần rồi gọi nhiều nơi. Một hàm gồm bốn phần: kiểu trả về · tên · danh sách tham số · thân. Trong thân, return gửi kết quả về cho nơi gọi:

ham-co-ban.c

#include <stdio.h>

int cong(int a, int b) {   // tra ve int; ten 'cong'; 2 tham so
    return a + b;          // gui ket qua ve
}

int main() {
    int tong = cong(3, 4); // goi ham, doi so 3 va 4
    printf("%d\n", tong);
    return 0;
}

Kết quả khi chạy

7
  • Cú pháp: kiểu_trả_về tên(tham_số) { thân; return …; }
  • Tham số = biến trong định nghĩa; đối số = giá trị thực khi gọi.
  • main cũng là một hàm - chương trình bắt đầu chạy từ đó.

Hãy hình dung hàm như một chiếc máy: truyền đối số vào, nó tính toán rồi return kết quả ra. Chọn hàm và đổi đối số để xem từng đối số được gán vào tham số nào và hàm trả về gì:

int cong(int a,
			int b) {
    return a + b;
}
Lời gọi
cong(2, 2)
Ràng buộc
a = 2b = 2
Trả về
4

Hàm nhận đối số vào các tham số, tính toán, rồi return một giá trị về cho nơi gọi - như một chiếc máy: bỏ nguyên liệu vào, nhận sản phẩm ra.

binh-phuong.c

#include <stdio.h>

int binhPhuong(int x) {
    return x * x;
}

int main() {
    printf("%d\n", binhPhuong(5));
    return 0;
}

Kết quả khi chạy

25

Không phải hàm nào cũng cần trả giá trị. void nghĩa là “rỗng” - không trả về gì; hàm chỉ thực hiện một việc (vd in ra màn hình):

ham-void.c

#include <stdio.h>

void chao(char ten[]) {            // void: khong tra ve
    printf("Xin chao, %s!\n", ten);
}

int main() {
    chao("Lan");
    chao("Nam");
    return 0;
}

Kết quả khi chạy

Xin chao, Lan!
Xin chao, Nam!
  • void = không trả về giá trị; chỉ tạo “tác dụng phụ” (side effect) như in ra.
  • Trong hàm void có thể dùng return; (không kèm giá trị) để thoát sớm.
  • Viết một việc thành hàm rồi gọi nhiều lần → tránh lặp code.

Biến khai báo trong hàm là cục bộ - chỉ sống trong hàm đó. Và C truyền theo giá trị: hàm nhận một bản sao của đối số, nên sửa tham số bên trong không đổi biến gốc ở nơi gọi:

truyen-gia-tri.c

#include <stdio.h>

void thuTangGap(int n) {
    n = n * 2;                  // chi doi BAN SAO
    printf("Trong ham: %d\n", n);
}

int main() {
    int x = 5;
    thuTangGap(x);
    printf("Sau khi goi: %d\n", x);  // x VAN la 5
    return 0;
}

Kết quả khi chạy

Trong ham: 10
Sau khi goi: 5

Muốn hàm sửa được biến gốc?

Phải truyền địa chỉ của biến qua con trỏ (pointer) - đó là chủ đề bài Con trỏ & hàm của khoá C nâng cao. Ở mức cơ bản, hãy nhớ: hàm trả kết quả ra bằng return, không “sửa ngược” biến của nơi gọi.

Lỗi phổ biến nhất với người mới: gọi hàm trước khi trình biên dịch biết nó. Trình biên dịch đọc từ trên xuống - nếu hàm định nghĩa BÊN DƯỚI main, tới chỗ gọi nó chưa biết hàm là gì:

thieu-prototype.c

#include <stdio.h>

int main() {
    printf("%d\n", cong(2, 3));   // LOI: 'cong' chua duoc khai bao
    return 0;
}

int cong(int a, int b) { return a + b; }

Kết quả khi chạy

Loi bien dich: 'cong' undeclared

Cách sửa & lỗi quên return

Sửa bằng cách đặt định nghĩa hàm trước main, hoặc khai báo nguyên mẫu int cong(int, int); ở đầu file. Một bẫy khác: quên return trong hàm khác void ⇒ giá trị trả về là rác (không xác định). Hàm hứa trả int thì phải return một int.

Câu hỏi thường gặp

Hàm giúp (1) tái sử dụng: viết một lần, gọi nhiều nơi; (2) đặt tên cho một việc → code dễ đọc; (3) chia nhỏ bài toán để dễ kiểm thử. Chương trình lớn mà nhồi hết vào main thì rối và lặp lại rất nhiều.

Tham số là tên biến ghi trong định nghĩa hàm - int cong(int a, int b) thì a, b là tham số. Đối số là giá trị thực truyền vào khi gọi - cong(3, 4) thì 3, 4 là đối số. Khi gọi, đối số được “gán” vào tham số.

return trả một giá trị về cho nơi gọi và kết thúc hàm ngay. Hàm void không trả gì - nó chỉ làm việc (vd in ra màn hình); có thể viết một lệnh return; không kèm giá trị để thoát sớm.

C truyền theo GIÁ TRỊ (pass by value): hàm nhận một BẢN SAO của đối số. Sửa bản sao không động tới bản gốc. Muốn hàm sửa được biến gốc phải truyền địa chỉ (con trỏ) - chủ đề của khoá C nâng cao.

Trình biên dịch đọc từ trên xuống; tới chỗ gọi mà chưa thấy hàm thì không biết nó nhận/trả kiểu gì. Cách sửa: đặt định nghĩa hàm TRƯỚC main, hoặc khai báo “nguyên mẫu” (prototype) int cong(int, int); ở đầu file.

Không. Biến khai báo trong một hàm là biến CỤC BỘ (local) - chỉ sống trong hàm đó, biến mất khi hàm kết thúc. Hai hàm có thể có biến cùng tên mà không đụng nhau. Đây gọi là phạm vi (scope).

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Phân biệt tham số (parameter) và đối số (argument) theo bài?

  1. 1

    Hàm cộng

    Viết hàm int cong(int a, int b) trả về tổng, rồi gọi cong(15, 27) trong main và in kết quả.

    Hoàn thành khi: int cong(int a, int b) { return a + b; } → in 42.

  2. 2

    Bình phương

    Dùng công cụ Bước 2: chọn hàm binhPhuong, đặt x = 7. Nó trả về bao nhiêu?

    Hoàn thành khi: 49 (7 × 7). Thân hàm là return x * x;

  3. 3

    Chu vi hình chữ nhật

    Dùng công cụ: chọn chuViHCN với dai = 5, rong = 3. Kết quả? Viết lại công thức trong thân hàm.

    Hoàn thành khi: 16 - return (dai + rong) * 2; với (5+3)*2 = 16.

  4. 4

    Truyền theo giá trị

    Hàm void thuTangGap(int n) đặt n = n*2. Sau khi gọi thuTangGap(x) với x = 5, main in x ra bao nhiêu?

    Hoàn thành khi: 5 - hàm chỉ đổi bản sao; biến xmain không đổi.

  5. 5

    Hàm void chào

    Viết hàm void chao(char ten[]) in “Xin chao, <ten>!”. Gọi với "Lan""Nam".

    Hoàn thành khi: void chao(char ten[]) { printf("Xin chao, %s!\n", ten); } - không có return giá trị.

  6. 6

    Số chẵn?

    Viết hàm int laChan(int n) trả về 1 nếu n chẵn, 0 nếu lẻ (dùng %). laChan(8) ra gì?

    Hoàn thành khi: return n % 2 == 0;laChan(8) = 1 (chẵn).