Các cách truyền đối số vào hàm
C++ có 3 cách truyền đối số vào hàm: theo giá trị, theo tham chiếu, và theo con trỏ - mỗi cách có ảnh hưởng khác nhau đến việc biến gốc có bị thay đổi hay không. Bài này giới thiệu tổng quan; chi tiết về con trỏ và tham chiếu sẽ có các bài riêng.
1. Truyền theo giá trị (Pass by Value) - mặc định
Phần tiêu đề “1. Truyền theo giá trị (Pass by Value) - mặc định”Hàm nhận một bản sao (copy) của đối số. Mọi thay đổi bên trong hàm không ảnh hưởng đến biến gốc bên ngoài:
#include <iostream>
void increase(int x) { x = x + 1; // Chỉ thay đổi bản sao cục bộ std::cout << "Ben trong ham: " << x << std::endl;}
int main() { int number = 5; increase(number); std::cout << "Ben ngoai ham: " << number << std::endl; // Ben trong ham: 6 // Ben ngoai ham: 5 (KHÔNG đổi!) return 0;}2. Truyền theo tham chiếu (Pass by Reference) - dùng &
Phần tiêu đề “2. Truyền theo tham chiếu (Pass by Reference) - dùng &”Hàm nhận một “bí danh” (alias) của biến gốc, không phải bản sao. Mọi thay đổi bên trong hàm sẽ ảnh hưởng đến biến gốc:
#include <iostream>
void increase(int& x) { // Lưu ý dấu & sau kiểu dữ liệu x = x + 1; // Thay đổi TRỰC TIẾP biến gốc std::cout << "Ben trong ham: " << x << std::endl;}
int main() { int number = 5; increase(number); std::cout << "Ben ngoai ham: " << number << std::endl; // Ben trong ham: 6 // Ben ngoai ham: 6 (ĐÃ đổi!) return 0;}Đây là khả năng mà truyền theo giá trị không thể làm được.
3. Truyền theo con trỏ (Pass by Pointer)
Phần tiêu đề “3. Truyền theo con trỏ (Pass by Pointer)”Hàm nhận địa chỉ bộ nhớ của biến gốc, và dùng toán tử * để truy cập/thay đổi giá trị tại địa chỉ đó:
#include <iostream>
void increase(int* x) { // Nhận con trỏ (địa chỉ) *x = *x + 1; // "Đi tới" địa chỉ đó và thay đổi giá trị}
int main() { int number = 5; increase(&number); // Truyền ĐỊA CHỈ của number, không phải giá trị std::cout << number << std::endl; // 6
return 0;}Về hiệu quả, truyền theo con trỏ và tham chiếu tương tự nhau (đều tránh sao chép, đều có thể thay đổi giá trị gốc) - chi tiết về sự khác biệt sẽ được trình bày ở bài “Con trỏ vs Tham chiếu”.
Khi nào dùng cách nào?
Phần tiêu đề “Khi nào dùng cách nào?”| Cách truyền | Khi nào dùng |
|---|---|
| Theo giá trị | Kiểu dữ liệu nhỏ (int, char, bool…) và không muốn hàm thay đổi giá trị gốc |
Theo tham chiếu & |
Muốn hàm thay đổi giá trị gốc, hoặc tránh sao chép dữ liệu lớn (std::string, std::vector…) |
Theo tham chiếu hằng const& |
Muốn tránh sao chép dữ liệu lớn NHƯNG không muốn hàm thay đổi nó (phổ biến nhất cho tham số kiểu lớn) |
Theo con trỏ * |
Khi giá trị truyền vào có thể là “không có gì” (nullptr), hoặc làm việc với mảng C-style |
// Cách phổ biến nhất khi truyền dữ liệu lớn chỉ để đọc: const&void print_message(const std::string& message) { std::cout << message << std::endl;}Tổng kết
Phần tiêu đề “Tổng kết”- Theo giá trị: sao chép dữ liệu, an toàn nhưng tốn bộ nhớ với dữ liệu lớn
- Theo tham chiếu (
&): không sao chép, có thể thay đổi biến gốc - Theo con trỏ (
*): tương tự tham chiếu nhưng tường minh hơn về địa chỉ bộ nhớ, cho phép giá trịnullptr - Quy tắc thường dùng: dùng
const&cho tham số kiểu lớn chỉ đọc, dùng&khi cần thay đổi giá trị gốc