Tài trợ bài viết này và giới thiệu dịch vụ, sản phẩm, thương hiệu, nhu cầu tuyển dụng của doanh nghiệp đến với cộng đồng.
La Kiến Vinh Một trong những nỗi đau nhức nhói khá lớn cho những người mới bắt đầu với C++ đó là khó phân biệt được khái niệm tham chiếu và con trỏ. Trong bài viết này, tôi sẽ diễn đạt sự khác biệt theo 1 hướng tiếp cận cực kỳ dễ hiểu. Nên lưu ý rằng, khi bàn về tham chiếu thì nó chỉ tồn tại trong C++.
Nội dung bài viết

Giới thiệu

Một trong những nỗi đau không nhỏ là khái niệm và phân biệt khái niệm. Với C++, phân biệt Tham chiếu và Con trỏ cũng là vấn đề cần làm rõ. Bài viết này nhằm phân biệt 2 khái niệm này theo lối suy nghĩ bình dị nhất.

Dẫn dắt

Nhắc lại một thói quen

Khi trình bày về tham chiếu (tham biến), chúng ta thường nhắc đến 1 hàm tiêu biểu đó là thay đổi giá trị giữa 2 biến.

void swap(int & number1, int & number2)
{
	int numberTemp = number1;
	number1 = number2;
	number2 = numberTemp;
}

Với prototype (khuôn mẫu) là void swap(int &, int &)

Khi chưa có nhiều kinh nghiệm, ta chỉ mới tiếp cận được việc sử dụng cú pháp dataType & X = Y cho mỗi trường hợp trên, điều này cũng dẫn đến ta việc ta chưa dùng hết khả năng của nó và hiểu tường tận hơn về loại khai báo này.

Các bạn theo dõi từng bước bên dưới để hiểu đầy đủ hơn.

Nhắc lại các phương pháp khai báo biến

Các cách khai báo biến

int integerNumber;
int integerArray[10];
int * integerPointer;

Ngoài các cách khai báo thông thường như trên, ta còn có 1 cách khá đặc biệt. Không chỉ C++ tạo ra cách này mà 1 số ngôn ngữ khác cũng có những điều tương tự đó là đặt bí danh khác (alias) cho biến đó là 1 biến có thể có nhiều tên.

Đặt tên khác cho biến đã có

Giả sử ta muốn đặt tên khác cho biến đã tồn tại ta dùng cú pháp: dataType & newName = originalName (Với dataType là kiểu dữ liệu của originalName). Xem ví dụ thông qua đoạn code sau.

int age;
int & tuoi = age;

short RAM;
short & HDD = RAM:

Doctor doctor;
Doctor & bacSi = doctor;

TrainingObject* STDIO;
TrainingObject* & STDIOTraining = STDIO;

Lưu ý rằng age và tuoi là 1, RAM và HDD là 1, ... do đó age và tuoi dùng chung vùng nhớ, không có sự cấp phát thêm vùng nhớ (khi không đề cập đến cách hiện thực của trình biên dịch) ở dòng số 2, 5, 8, 11.

Hướng dẫn chi tiết

Giả sử ta đã có 1 biến như sau

int integerNumber;

Ta sử dụng biến đó như 1 cách thông thường như ví dụ

#include <iostream>

int main()
{
	int integerNumber;
	integerNumber = 5;

	std::cout << integerNumber; // Ket qua: 5

	return 0;
}

Đặt tên mới cho biến integerNumber như trên theo các bước

  • Bước 1: xác định kiểu của integerNumber (trong trường hợp này là kiểu int).
  • Bước 2: lưu ý toán tử & trong khai báo biến mới.
int integerNumber = 5;

int & newIntegerNumberName = integerNumber;

newIntegerNumberName lúc này là cái tên mới cho biến integerNumber → 1 vùng nhớ có 2 tên. Lúc ta thay đổi giá trị ở tên biến nào thì khi truy cập biến còn lại cũng là giá trị đã thay đổi đó. Ví dụ

#include <iostream>

int main()
{
	int integerNumber;
	integerNumber = 5;

	std::cout << integerNumber; // Ket qua: 5
	
	int & newIntegerNumberName = integerNumber;
	
	newIntegerNumberName = 10;
	
	std::cout << integerNumber; // Ket qua: 10

	return 0;
}

Thảo luận thêm cho vấn đề trên

Trong thuật ngữ thì người ta gọi là khai báo thêm 1 tham chiếu, tuy nhiên hiểu nó theo 1 nghĩa đơn giản đó là ĐẶT THÊM TÊN MỚI CHO BIẾN CŨ (khi đã loại trừ việc khảo sát cách thức hiện thực của trình biên dịch). Điều này sẽ giảm được việc hiểu lầm với con trỏ.

Để hiểu thêm ta thử nghiệm với 1 con trỏ

int* p;

Để đặt tên khác cho p ta cũng chú tâm vào 2 bước sau

  • Bước 1: xác định kiểu của p (trong trường hợp này là kiểu int*).
  • Bước 2: lưu ý toán tử & trong khai báo biến mới.

Vậy công thức đặt thêm tên khác cho vùng nhớ của p đó là

int* & p2 = p;

Rút ra nhận xét và kết luận

  • Vì là đặt lại tên khác do đó, sẽ không tốn kém thêm bộ nhớ khi thao tác.
  • Vì là đặt lại tên khác do đó, sẽ không có bộ nhớ khi định nghĩa, dẫn đến khai báo sau là sai: int & a = 5; Bạn bắt buộc phải chỉ định ngay a phải là tên mới của biến nào, tức là sau dấu bằng phải là 1 biến kiểu int (int &a = b).

Nhìn lại vấn đề khai báo biến với việc truyền tham số vào hàm

XÉT HÀM SAU

#include <iostream>

void swap(int a, int b) // (1)
{
	int c = a;
	a = b;
	b = c;
}

int main()
{
	int x = 5, y = 10;
	
	swap(x, y); // (2)
	
	std::cout << x << " " y; // Ket qua: 5 10
}

GIẢI THÍCH

Tại (1)(2) sẽ làm cho điều sau xảy ra

int a = xint b = y.

Mà khai báo này tức là ta đang định nghĩa 2 vùng nhớ mới và sao chép giá trị x, y vào ab.

Như vậy, nếu như hàm swap có prototype như sau

void swap(int & a, int & b) thì điều như sau sẽ xảy ra

int & a = xint & b = y.

Hiểu đơn giản có nghĩa là ta đang đặt thêm tên mới cho xayb.

Đó là lý do vì sao 2 giá trị của x, y được thay đổi nếu ta "truyền tham chiếu".

Dành cho bạn

So sánh sự khác biệt giữa 2 hàm

Dựa vào các kiến thức trên, bạn hãy so sánh sự khác biệt của 2 hàm sau

void swap(int & a, int & b)
{
	int c = a;
	a = b;
	b = c;
}

void swap(int * a, int * b)
{
	int c = *a;
	*a = *b;
	*b = c;
}

Phân tích lỗi

Giải thích lý do vì sao biên dịch chương trình sau sẽ ra lỗi

#include <iostream>

void func(int & a)
{
	std::cout << "STDIO: " << a;
}

int main()
{
	func(3);
}

Nếu bạn giải quyết cặn kẽ được 2 bài tập trên, sẽ giúp ích được rất nhiều.

THẢO LUẬN
ĐÓNG