Khái niệm
C không có truyền tham chiếu gốc như C++ (void f(int &x)). Thay vào đó, truyền địa chỉ biến (con trỏ) để hàm có thể truy cập và sửa đổi biến gốc.
Cơ chế
void ham(int *x) { // Nhận địa chỉ
*x = 20; // Sửa giá trị tại địa chỉ
}
int main() {
int a = 10;
ham(&a); // Truyền địa chỉ của a
// a giờ là 20
}
Chương trình: Hoán đổi 2 số (THÀNH CÔNG)
#include <stdio.h>
void hoanDoi(int *a, int *b) {
int tam = *a;
*a = *b;
*b = tam;
}
int main() {
int x = 10, y = 20;
printf("Truoc hoan doi: x=%d, y=%d\n", x, y);
hoanDoi(&x, &y); // Truyền địa chỉ
printf("Sau hoan doi: x=%d, y=%d\n", x, y);
return 0;
}
Kết quả
Truoc hoan doi: x=10, y=20
Sau hoan doi: x=20, y=10 // THÀNH CÔNG!
Sơ đồ bộ nhớ
main(): hoanDoi():
┌─────────┐ ┌──────────────┐
│ x = 10 │◄──────│ *a (trỏ đến x)│
│ (0x100) │ │ a = 0x100 │
├─────────┤ ├──────────────┤
│ y = 20 │◄──────│ *b (trỏ đến y)│
│ (0x104) │ │ b = 0x104 │
└─────────┘ └──────────────┘
Cùng địa chỉ - cùng ô nhớ
Các trường hợp bắt buộc dùng Call by Reference
1. Hàm cần sửa đổi biến gốc
void tangLen(int *x) { (*x)++; }
void nhapSo(int *x) { scanf("%d", x); } // scanf cần địa chỉ
2. Truyền struct/mảng lớn (tránh copy tốn kém)
struct SinhVien { char ten[50]; int diem[10]; };
// Sai - copy cả struct lớn
void inSV(struct SinhVien sv) { ... }
// Đúng - truyền con trỏ
void inSV(const struct SinhVien *sv) { ... }
3. Hàm cần trả về nhiều giá trị
void tachSo(int n, int *hangChuc, int *hangDonVi) {
*hangChuc = n / 10;
*hangDonVi = n % 10;
}
int main() {
int a, b;
tachSo(45, &a, &b); // a=4, b=5
}
4. Cấp phát bộ nhớ động trong hàm
int taoMang(int **arr, int n) {
*arr = malloc(n * sizeof(int));
return *arr != NULL;
}
int main() {
int *a;
if (taoMang(&a, 10)) { ... }
free(a);
}
Const correctness với con trỏ
| Khai báo | Ý nghĩa |
|---|---|
void f(int *p) | Có thể sửa *p, có thể đổi p |
void f(const int *p) | KHÔNG được sửa *p (read-only) |
void f(int *const p) | KHÔNG được đổi p (con trỏ const) |
void f(const int *const p) | Cả hai đều không được sửa |
Best Practice
// 1. Dùng const nếu không sửa đổi
void inMang(const int *arr, int n) { ... }
// 2. Kiểm tra NULL
void anToan(int *p) {
if (!p) return; // Hoặc assert(p)
*p = 10;
}
// 3. Restrict (C99) - compiler optimize tốt hơn
void copy(int *restrict dest, const int *restrict src, int n);
Tóm tắt
- Call by Value: Truyền bản sao, an toàn, mặc định cho kiểu cơ bản
- Call by Reference (C style): Truyền
&bien, hàm nhận*con_tro, có thể sửa gốc - Mảng: Luôn truyền như con trỏ (địa chỉ phần tử 0)
- Luôn dùng const cho tham số chỉ đọc