Con trỏ vs Tham chiếu
Con trỏ và tham chiếu đều cho phép làm việc gián tiếp với một biến khác mà không sao chép nó, nhưng chúng có những khác biệt quan trọng cần nắm rõ để chọn đúng công cụ cho từng tình huống.
Bảng so sánh
Phần tiêu đề “Bảng so sánh”| Con trỏ | Tham chiếu | |
|---|---|---|
| Cú pháp khai báo | int* ptr |
int& ref |
| Có thể null? | Có (nullptr) |
Không - luôn phải trỏ đến một biến hợp lệ |
| Bắt buộc khởi tạo ngay? | Không | Có |
| Có thể đổi hướng sau đó? | Có (gán con trỏ khác) | Không - gắn cố định với biến ban đầu |
| Cần dereference để lấy giá trị? | Có (*ptr) |
Không - dùng trực tiếp như biến gốc |
| Hỗ trợ phép toán số học? | Có (ptr + 1) |
Không |
Ví dụ minh họa từng điểm khác biệt
Phần tiêu đề “Ví dụ minh họa từng điểm khác biệt”Con trỏ có thể là null, tham chiếu thì không
Phần tiêu đề “Con trỏ có thể là null, tham chiếu thì không”int* ptr = nullptr; // Hợp lệ - "chưa trỏ đến đâu"// int& ref = nullptr; // LỖI BIÊN DỊCH! Tham chiếu phải gắn với biến thậtCon trỏ có thể đổi hướng, tham chiếu thì không
Phần tiêu đề “Con trỏ có thể đổi hướng, tham chiếu thì không”int x = 10, y = 20;
int* ptr = &x;ptr = &y; // OK - ptr giờ trỏ sang ystd::cout << *ptr << std::endl; // 20
int& ref = x;ref = y; // KHÔNG đổi hướng! Đây là gán giá trị của y CHO xstd::cout << x << std::endl; // 20 (x đã bị đổi giá trị, ref vẫn gắn với x)Tham chiếu dùng trực tiếp, không cần dereference
Phần tiêu đề “Tham chiếu dùng trực tiếp, không cần dereference”int x = 10;
int* ptr = &x;std::cout << *ptr << std::endl; // Cần dấu * để lấy giá trị
int& ref = x;std::cout << ref << std::endl; // Dùng trực tiếp, không cần ký hiệu đặc biệtKhi nào dùng con trỏ, khi nào dùng tham chiếu?
Phần tiêu đề “Khi nào dùng con trỏ, khi nào dùng tham chiếu?”Dùng tham chiếu khi:
- Bạn chắc chắn luôn có một biến hợp lệ để trỏ đến (không cần trạng thái “rỗng”)
- Không cần đổi hướng trỏ sau khi khởi tạo
- Muốn cú pháp đơn giản, dễ đọc hơn (không cần
*/&liên tục) - Trường hợp phổ biến nhất: tham số hàm (
const T&để tránh sao chép,T&để sửa đổi)
Dùng con trỏ khi:
- Giá trị có thể “không tồn tại” (cần biểu diễn bằng
nullptr) - Cần đổi hướng trỏ sang đối tượng khác trong quá trình chạy
- Làm việc với mảng động, cấp phát bộ nhớ (
new/delete) - Làm việc với các API cấp thấp hoặc thư viện C
Ví dụ thực tế: cùng một mục đích, hai cách viết
Phần tiêu đề “Ví dụ thực tế: cùng một mục đích, hai cách viết”#include <iostream>
// Dùng con trỏvoid increase_ptr(int* x) { if (x != nullptr) { // Cần kiểm tra null *x = *x + 1; // Cần dereference }}
// Dùng tham chiếu - ngắn gọn và an toàn hơn ở đâyvoid increase_ref(int& x) { x = x + 1; // Không cần kiểm tra null, không cần dereference}
int main() { int a = 5, b = 5; increase_ptr(&a); increase_ref(b); std::cout << a << " " << b << std::endl; // 6 6 return 0;}Trong trường hợp này, tham chiếu là lựa chọn rõ ràng hơn vì biến chắc chắn tồn tại và không cần đổi hướng.
Tổng kết
Phần tiêu đề “Tổng kết”- Con trỏ có thể null và đổi hướng; tham chiếu luôn gắn cố định với một biến hợp lệ ngay từ khi khởi tạo
- Tham chiếu có cú pháp đơn giản hơn (không cần
*/&khi dùng), nên được ưu tiên khi không cần các khả năng đặc biệt của con trỏ - Con trỏ vẫn cần thiết cho cấp phát động, cấu trúc dữ liệu phức tạp, và khi giá trị có thể “không tồn tại”