- 1 1. Cơ bản về tham số trong ngôn ngữ C
- 2 2. Sự khác biệt giữa tham số thực và tham số hình thức
- 3 3. Cách truyền tham số
- 4 4. Kết hợp số lượng tham số và giá trị trả về
- 5 5. Đệ quy và tham số
- 6 6. Macro dạng hàm và tham số
- 7 7. Hàm thư viện chuẩn C và tham số
- 8 8. Tổng kết
- 9 9. Kỹ thuật nâng cao liên quan đến tham số
- 10 10. Tham số hàm và quản lý bộ nhớ
1. Cơ bản về tham số trong ngôn ngữ C
Tham số là gì?
Tham số là dữ liệu được truyền từ bên ngoài vào hàm khi hàm được gọi. Việc sử dụng tham số giúp hàm nhận các giá trị đầu vào khác nhau và xử lý dựa trên những giá trị đó. Việc thành thạo cách sử dụng tham số trong C là điều cần thiết để tăng khả năng tái sử dụng và linh hoạt cho chương trình.
Tham số thực và tham số hình thức
Giá trị được cung cấp ở phía gọi hàm gọi là tham số thực, còn giá trị được nhận trong định nghĩa hàm gọi là tham số hình thức. Ví dụ, trong PrintScore(score);
thì score
là tham số thực, còn score
trong void PrintScore(int score)
là tham số hình thức. Để sử dụng hàm đúng, việc hiểu sự khác biệt này là rất quan trọng.
2. Sự khác biệt giữa tham số thực và tham số hình thức
Tham số thực
Tham số thực là giá trị thực tế được truyền vào khi gọi hàm. Ví dụ, trong PrintScore(100);
, 100
là tham số thực. Tham số thực được chuyển vào hàm và sử dụng trong hàm đó.
Tham số hình thức
Tham số hình thức là tên tạm thời cho dữ liệu nhận trong phần định nghĩa hàm. Tham số hình thức tham chiếu đến giá trị của tham số thực bên trong hàm, nhưng không thể thay đổi giá trị đó bên ngoài hàm. Ví dụ, score
trong void PrintScore(int score)
là tham số hình thức.
3. Cách truyền tham số
Truyền theo giá trị
Truyền theo giá trị là phương pháp sao chép giá trị của tham số thực sang tham số hình thức. Khi đó, việc thay đổi giá trị tham số hình thức trong hàm sẽ không ảnh hưởng tới tham số thực bên ngoài. Xem ví dụ sau:
void LevelUp(int lv) {
lv++;
}
int main() {
int level = 1;
LevelUp(level);
printf("Level: %dn", level); // Kết quả: Level: 1
}
Trong ví dụ này, lv
trong hàm LevelUp
được tăng lên, nhưng giá trị của level
trong hàm main
không thay đổi. Ưu điểm của truyền theo giá trị là bảo vệ dữ liệu gốc, tuy nhiên khi truyền dữ liệu lớn, việc này có thể tốn bộ nhớ hơn.
Truyền theo con trỏ
Truyền theo con trỏ là cách truyền địa chỉ của tham số thực vào tham số hình thức. Phương pháp này cho phép thay đổi trực tiếp giá trị của tham số thực trong hàm.
void LevelUp(int *plv) {
(*plv)++;
}
int main() {
int level = 1;
LevelUp(&level);
printf("Level: %dn", level); // Kết quả: Level: 2
}
Ở ví dụ này, giá trị level
được thay đổi trực tiếp trong hàm LevelUp
. Ưu điểm của truyền con trỏ là có thể thay đổi nhiều giá trị hoặc trả về kết quả từ hàm. Tuy nhiên, thao tác sai với con trỏ dễ gây lỗi hoặc rò rỉ bộ nhớ, cần sử dụng cẩn thận.
4. Kết hợp số lượng tham số và giá trị trả về
Có tham số, không trả về giá trị
Đây là ví dụ hàm nhận tham số nhưng không trả về giá trị. Ví dụ: void PrintScore(int score)
nhận tham số score để hiển thị nhưng không trả về gì cả.
Không có tham số, có giá trị trả về
Ví dụ về hàm không nhận tham số nhưng trả về giá trị xử lý. int GetCurrentScore()
là hàm tính toán và trả về điểm hiện tại.
Có tham số, có giá trị trả về
Đây là kiểu hàm nhận tham số và trả về kết quả. Ví dụ: int Add(int a, int b)
nhận hai số và trả về tổng của chúng. Loại hàm này rất linh hoạt và thường được sử dụng trong nhiều tình huống khác nhau.
5. Đệ quy và tham số
Đệ quy là gì?
Đệ quy là kỹ thuật một hàm tự gọi lại chính nó. Đệ quy rất hiệu quả khi giải quyết các vấn đề bằng cách chia nhỏ ra, nhưng nếu không kiểm soát đúng sẽ dễ gây tràn bộ nhớ (stack overflow).
Ví dụ về đệ quy
Dưới đây là ví dụ đệ quy sử dụng tham số để chia số cho 2 liên tục:
int funcA(int num) {
if(num % 2 != 0) {
return num;
}
return funcA(num / 2);
}
int main() {
int result = funcA(20);
printf("Result: %dn", result); // Kết quả: Result: 5
}
Ở ví dụ này, hàm funcA
tự gọi lại chính nó và lặp lại xử lý với tham số truyền vào. Khi sử dụng đệ quy, phải chú ý đặt điều kiện dừng để tránh lặp vô hạn.
6. Macro dạng hàm và tham số
Macro dạng hàm là gì?
Macro dạng hàm là macro có tham số, mã sẽ được thay thế ngay tại thời điểm biên dịch. Nhờ đó, hiệu năng chương trình được cải thiện.
Ví dụ macro dạng hàm
Dưới đây là ví dụ macro dạng hàm dùng để lấy số phần tử của mảng:
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
int main() {
int arr[10];
printf("Array size: %dn", ARRAY_SIZE(arr)); // Kết quả: Array size: 10
}
Macro dạng hàm thay thế mã nguồn trước khi chương trình chạy, nên không có overhead thực thi. Tuy nhiên, do không kiểm tra kiểu dữ liệu, nên dùng sai dễ gây lỗi bất ngờ.
7. Hàm thư viện chuẩn C và tham số
Sử dụng hàm thư viện chuẩn
Ngôn ngữ C có rất nhiều hàm thư viện chuẩn, hầu hết đều sử dụng tham số để xử lý. Ví dụ, hàm printf
nhận tham số với số lượng thay đổi để in dữ liệu theo định dạng chỉ định.
Ví dụ về hàm thư viện chuẩn
Dưới đây là ví dụ sử dụng hàm printf
:
printf("Name: %s, Age: %dn", "Alice", 30); // Kết quả: Name: Alice, Age: 30
Ở ví dụ này, hàm printf
dùng tham số để in chuỗi và số. Sử dụng hàm thư viện giúp code dễ đọc, dễ bảo trì và hiệu quả hơn.
8. Tổng kết
Sử dụng tham số biến đổi (variadic arguments)
Trong C, có thể định nghĩa hàm nhận số lượng tham số thay đổi nhờ tham số biến đổi (variadic). Dùng dấu ba chấm ...
trong khai báo hàm. Điều này cho phép hàm nhận số lượng tham số không cố định. Ví dụ tiêu biểu là printf
có thể nhận số tham số khác nhau tùy định dạng.
Ví dụ tham số biến đổi
Dưới đây là ví dụ hàm nhận nhiều số nguyên và tính tổng:
#include <stdarg.h>
#include <stdio.h>
int sum(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main() {
printf("Sum: %dn", sum(4, 1, 2, 3, 4)); // Kết quả: Sum: 10
}
Ở ví dụ này, hàm sum
nhận nhiều số và trả về tổng. Có thể dùng macro như va_list
, va_start
, va_arg
, va_end
để xử lý tham số biến đổi.
Lưu ý khi dùng tham số biến đổi
Khi dùng tham số biến đổi, cần cẩn trọng với kiểu và số lượng tham số được truyền. Nếu không khớp giữa phía gọi và phía định nghĩa hàm, có thể gây lỗi hoặc chương trình dừng bất ngờ.
Ứng dụng thực tế của tham số
Cách dùng tham số hiệu quả
Khai thác tham số hợp lý giúp code dễ đọc và dễ tái sử dụng. Khi nhiều hàm xử lý chung một dữ liệu, nên truyền qua tham số thay vì dùng biến toàn cục để đảm bảo tính độc lập và hạn chế ảnh hưởng lên phần còn lại của chương trình.
Tối ưu bộ nhớ và hiệu năng
Khi truyền dữ liệu lớn vào hàm, sử dụng con trỏ sẽ tiết kiệm bộ nhớ hơn. Nếu truyền mảng hoặc struct lớn theo giá trị, toàn bộ dữ liệu sẽ bị sao chép; còn truyền theo con trỏ chỉ truyền địa chỉ, tiết kiệm tài nguyên.
Best practice khi viết hàm
Khi viết hàm, nên xác định số lượng và kiểu tham số thật hợp lý. Truyền dư tham số làm phức tạp hàm và dễ gây lỗi; ngược lại, truyền đủ thông tin cần thiết giúp code rõ ràng, dễ bảo trì.
9. Kỹ thuật nâng cao liên quan đến tham số
Hàm callback
Hàm callback là kỹ thuật truyền một hàm như tham số cho hàm khác và gọi hàm đó trong hàm nhận. Cách này giúp xử lý linh hoạt, đặc biệt phù hợp với lập trình hướng sự kiện hoặc xử lý bất đồng bộ.
#include <stdio.h>
void executeCallback(void (*callback)(int)) {
callback(10);
}
void printValue(int val) {
printf("Value: %dn", val);
}
int main() {
executeCallback(printValue); // Kết quả: Value: 10
}
Ví dụ này truyền hàm printValue
làm callback và thực thi trong executeCallback
.
Con trỏ hàm
Con trỏ hàm cho phép xử lý hàm như một biến. Bạn có thể truyền hàm qua tham số hoặc gọi các hàm khác nhau tại runtime. Cách này rất linh hoạt cho các tình huống động.
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int (*operation)(int, int) = add;
printf("Result: %dn", operation(2, 3)); // Kết quả: Result: 5
}
Ở đây, hàm add
được gán cho biến con trỏ hàm operation
và gọi như một hàm thông thường.
10. Tham số hàm và quản lý bộ nhớ
Bộ nhớ động và tham số
Trong C, bạn có thể cấp phát động bộ nhớ bằng malloc
và giải phóng bằng free
. Khi truyền con trỏ bộ nhớ động vào hàm, cần chú ý quản lý bộ nhớ để tránh rò rỉ.
#include <stdlib.h>
#include <stdio.h>
void allocateMemory(int **ptr, int size) {
*ptr = (int *)malloc(size * sizeof(int));
}
int main() {
int *arr;
allocateMemory(&arr, 5);
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]); // Kết quả: 1 2 3 4 5
}
free(arr); // Giải phóng bộ nhớ
}
Ví dụ này cấp phát động mảng và truyền qua tham số cho hàm. Nếu không quản lý tốt sẽ gây rò rỉ bộ nhớ.