Kiểu Tham Chiếu Trong C++ – Reference — Modern C++

Nhắc lại khai báo biến

dataType variableName;

Với cú pháp như trên ta gọi là các cách khai báo tham trị (chủ yếu biến sẽ lưu giá trị), dù là con trỏ vẫn lưu giá trị (địa chỉ vùng nhớ cũng là 1 con số).

int age = 9;
int * pAge = &age;
int * matrix = new int[4 * 4];

Ta có age, pAge, matrix là các biến được khai báo tham trị.

Vì sao C++ ra đời thêm tham chiếu

Với C không có khai báo dạng tham chiếu, như vậy để tương tác “qua trung gian” (thông qua địa chỉ) cần truyền địa chỉ. Giả sử hàm thay đổi giá trị 2 biến cần thông qua địa chỉ.

void Swap(int *addressParam1, int *addessParam2);

Khảo sát addressParam1 cho thấy thông tin từ biến này quá nhiều bao gồm:

  • Biến này có thể là 1 cấp phát động.
  • Biến này có thể cấp phát động.
  • Có thể lấy được vùng nhớ của addressParam1 tường minh trong khi có thể không có nhu cầu.
  • Bất tiện cho trường hợp nếu addressParam1 là 1 đối tượng, vậy sẽ có các cú pháp dạng:
    • addressParam1->age
    • addressParam1->get()

Từ các thông tin trên, cần thiết 1 phương pháp mới sao cho:

  • Tiện lợi khi thao tác thông qua địa chỉ.
  • Vẫn giữ được khả năng truyền địa chỉ.
  • Trừu tượng hơn để không can thiệp trực tiếp vào địa chỉ.
  • Ràng buộc không cho phép cấp phát động.

Cú pháp khai báo tham chiếu

Tham chiếu về cách hiểu đơn thuần có thể nghĩ đây là bí danh của một biến hoặc tên gọi khác của một biến đã có sẵn.

Về mặt kỹ thuật, tham chiếu là 1 cách tương tác vào vùng nhớ của biến khác nhưng lại không thể tương tác (lấy địa chỉ, gán địa chỉ tham chiếu mới) vào vùng nhớ trung gian này như trong trường hợp của con trỏ.

Cú pháp:

<Tên kiểu tham chiếu>& <Tên biến> = <Tên biến khác>

Ví dụ:

int age = 8;
int & rAge = age; // age == 8

int * matrix = new int[4 * 5];
int * & rMatrix[3] = 97; // matrix[3] = 97;

Phụ lục – hàm swap thay đổi giá trị 2 biến kinh điển

Khi đề cập đến tham chiếu trong C++, hàm swap thường được nhắc đến như 1 ví dụ điển hình.

#include <iostream>
using namespace std;

void swap1(int param1, int param2)
{
	int temp = param1;
	param1 = param2;
	param2 = temp;
}

void swap2(int& param1, int& param2)
{
	int temp = param1;
	param1 = param2;
	param2 = temp;
}

int main()
{
	int a = 1024;
	int b = 2048;

	cout << "a = " << a << ", b = " << b << endl;

	cout << "swap1: ";
	swap1(a, b);
	cout << "a = " << a << ", b = " << b << endl;

	cout << "swap2: ";
	swap2(a, b);
	cout << "a = " << a << ", b = " << b << endl;

	return 0;
}

// Difference between value parameter and reference parameter
// a = 1024, b = 2048
// Swap1: a = 1024, b = 2048
// Swap2: a = 2048, b = 1024

swap1: Truyền tham trị – param1param2 chỉ là 2 bản sao chép của ab

Truyền tham trị trong hàm swap.

 

swap2: Truyền tham chiếu – param1param2 cũng chính là ab

Truyền tham chiếu trong hàm swap.

So sánh tham chiếu và con trỏ

Giữa con trỏ và tham chiếu có điểm tương đồng, tuy nhiên cũng có một số điểm khác biệt cơ bản về mặt ý nghĩa như sau:

  • Một con trỏ có thể nhận giá trị null trong khi một tham chiếu thì không thể.
  • Một con trỏ có thể khởi tạo bất cứ lúc nào, trong khi một tham chiếu phải được khởi tạo ngay khi nó được khai báo. Nói cách khác, câu lệnh sau trình biên dịch sẽ báo lỗi:

    int &k; // error

  • Một con trỏ có thể trỏ đến nhiều đối tượng, trong khi một tham chiếu chỉ được tham chiếu tới một đối tượng duy nhất.
  • Con trỏ có thể lưu trữ vùng nhớ cấp phát động, tham chiếu thì không thể.