Tham chiếu và hằng số trong C++


Bài 10: Tham chiếu và hằng số trong C++

Trong C++, tham chiếu (reference) là một tính năng ngôn ngữ giúp tạo ra một bí danh (alias) cho một biến đã tồn tại. Khác với con trỏ, tham chiếu không phải là một biến riêng biệt trỏ đến vùng nhớ, mà là một cách khác để gọi cùng một vùng nhớ đã có. Một tham chiếu phải được gán ngay khi khai báo và không thể đổi chỗ trỏ về sau.

int a = 5;
int& b = a; // b là tham chiếu tới a

Khi b được dùng, mọi thao tác trên b đều ảnh hưởng trực tiếp tới a. Không có khả năng tách biệt b khỏi a, vì cả hai chia sẻ cùng một vị trí bộ nhớ. Khác với con trỏ, không có thao tác như dereference * hay truy cập địa chỉ & để dùng với b, bởi vì b đã là một cách gọi khác của a.

b = 10;  // tương đương a = 10
cout << a; // in 10

Tham chiếu thường được sử dụng trong truyền tham số vào hàm, đặc biệt là khi ta muốn tránh sao chép hoặc muốn hàm thay đổi trực tiếp dữ liệu gốc:

void tang(int& x) {
    x++;
}
int y = 3;
tang(y); // y giờ là 4

So với truyền con trỏ, tham chiếu an toàn hơn vì không có khả năng “null reference”, và không cần dereference – điều này làm mã rõ ràng hơn và ít lỗi hơn. Vì vậy, trong lập trình hiện đại, tham chiếu thường được ưu tiên nếu không cần phải thao tác động với địa chỉ.

Một đặc điểm quan trọng là tham chiếu không thể đổi đích sau khi được khởi tạo. Trong ví dụ sau, r không thể được gán lại để trỏ đến biến b:

int a = 1, b = 2;
int& r = a;
r = b; // gán giá trị b cho a, không đổi tham chiếu

Trong C++, một dạng tham chiếu đặc biệt là const reference – dùng để nhận đối tượng dưới dạng tham chiếu nhưng đảm bảo không bị thay đổi:

void inThongTin(const string& s) {
    cout << s << endl;
}

Khi dùng const reference, ta có thể truyền cả biến tạm hoặc biểu thức mà không cần sao chép toàn bộ. Đây là công cụ quan trọng trong việc tối ưu hiệu suất, nhất là khi truyền các đối tượng lớn như vectormap, hay các lớp tự định nghĩa.

Bên cạnh đó, từ khóa const có thể được dùng ở nhiều vị trí để biểu thị dữ liệu không thể thay đổi. Một biến const không thể bị gán lại sau khi khởi tạo:

const int x = 10;
x = 20; // lỗi

Khi kết hợp với con trỏ, const có thể có nhiều nghĩa tùy vào vị trí:

const int* p;     // con trỏ tới hằng số (không được thay đổi *p)
int* const p;     // hằng con trỏ (p không đổi, nhưng *p có thể)
const int* const p; // cả hai đều không thay đổi

const cũng áp dụng được cho tham chiếu, nhưng bản thân tham chiếu đã không thể đổi nên const int& có nghĩa là tham chiếu tới hằng số.

Khi khai báo hàm thành viên trong lớp, từ khóa const giúp bảo đảm rằng hàm không thay đổi trạng thái nội tại của đối tượng:

class SinhVien {
    string ten;
public:
    string layTen() const {
        return ten;
    }
};

Một điểm kỹ thuật quan trọng là: C++ không cho phép tạo ra tham chiếu null, vì thế mọi tham chiếu luôn phải gắn với một vùng nhớ hợp lệ. Tuy nhiên, thông qua ép kiểu nguy hiểm hoặc tương tác với con trỏ, ta có thể tạo điều kiện xảy ra lỗi. Vì thế, tham chiếu giúp an toàn hơn, nhưng không tuyệt đối loại trừ khả năng lỗi runtime nếu dùng sai cách.

Trong lập trình hiện đại với C++, việc kết hợp giữa tham chiếu và hằng số (const&) là cách phổ biến để truyền dữ liệu hiệu quả, đồng thời vẫn bảo vệ dữ liệu khỏi sự thay đổi ngẫu nhiên.