Search…

Phân Biệt Tham Chiếu và Con Trỏ trong C++

19/09/20206 min read
Phân biệt tham chiếu và con trỏ trong C++ theo phương pháp đơn giản.

Bài viết không đề cập nhiều về cú pháp khai báo tham chiếu hay tham chiếu cho con trỏ, để nắm bắt được các cú pháp phức tạp bạn đọc có thể tham khảo trước bài viết Kiểu Tham Chiếu trong C++.

Nhắc lại hàm swap kinh điển

Khi đề cập tham chiếu (tham biến), hàm swap thay đổi giá trị 2 biến thường được đề cập đến:

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

Với prototype (khuôn mẫu) là void swap(int &, int &), khai báo các tham số như hàm swap trên int & là ứng dụng từ khai báo biến tham chiếu trong C++.

Các phương pháp khai báo biến

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

int i;
int iArr[10];
int * iPtr;

Ngoài các cách khai báo biến như C, C++ hỗ trợ thêm kiểu khai báo tham chiếu - dataType & varName 

int i;
int & rI = i;

Khai báo tham chiếu là gì?

Cú pháp

dataType variable = value;
dataType & refVariable = variable;

dataType & refVariable = variable khai báo 1 tham chiếu là refVariable tham chiếu đến biến variable.

Ví dụ:

int age = 8;

int & rAge = age;
rAge = 15;

cout << age; // OUTPUT: 15

Phân biệt tham chiếu và con trỏ

Để dễ dàng thao tác với biến tham chiếu, tùy vào khả năng có thể hiểu theo 3 cách sau:

  1. Đặt tên khác cho biến đã có: nếu không cần chuyên sâu về kỹ thuật mà về mặt ứng dụng có thể hiểu khai báo tham chiếu là đặt thêm 1 tên mới cho biến đã có.
    • Với cách hiểu này, rất dễ dàng để phân biệt với con trỏ.
  2. Tham chiếu về mặt kỹ thuật: tham chiếu có khả năng quản lý vùng nhớ khác như con trỏ, nhưng:
    • Có cú pháp truy cập vùng nhớ khác với con trỏ, tạo cảm giác đang tương tác trực tiếp lên vùng nhớ đang tham chiếu đến.
    • Bị giới hạn khả năng nhận được 1 cấp phát động.
    • Bị giới hạn khả năng thay đổi vùng nhớ đang tham chiếu đến.

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

Giả sử muốn đặt tên khác cho biến đã tồn tại, sử dụng cú pháp:

dataType & newName = originalName: Với dataType là kiểu dữ liệu của originalName.

Ví dụ 1:

int age;
int & tuoi = age;

short RAM;
short & boNho = RAM:

Doctor doctor;
Doctor & bacSi = doctor;

Stdio* stdio;
Stdio* & tieuChuanNhapXuat = stdio;
  • agetuoi là 1, ... do đó agetuoi có thể thay đổi giá trị tại vùng nhớ age.
  • Tương tự với RAM, doctor, stdio.

Ví dụ 2:

#include <iostream>

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

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

	return 0;
}

2. Tham chiếu và con trỏ

Chuyên sâu vào kỹ thuật tham chiếu được thiết kế gần như con trỏ nhưng khác nhau cách sử dụng:

  • Tham chiếu cũng có vùng nhớ riêng như con trỏ và lưu trữ địa chỉ của vùng nhớ đang tham chiếu đến, nhưng vùng nhớ này được ẩn giấu đi thông qua các ràng buộc về cú pháp lập trình.
  • Tham chiếu được hiện thực để có 1 phần khả năng của con trỏ, tăng tính trừu tượng, giới hạn sự tự do của thao tác vùng nhớ so với con trỏ để giữ cho việc phát triển ứng dụng an toàn hơn.
Con trỏ
#include <iostream>

int main() {
    int a = 8;
    int* p = &a;

    std::cout << std::hex << &a << " " << &p;

    return 0;
}

Giả sử địa chỉ của a00CFF778p00CFF76C. Khi lấy giá trị này ra sẽ được: 00CFF778 00CFF76C.

Tham chiếu
#include <iostream>

int main() {
    int a = 8;
    int& r = a;

    std::cout << std::hex << &r << " " << &a;

    return 0;
}

Giả sử địa chỉ của a00CFF778r là 00CFF76C. Khi lấy giá trị này ra sẽ được: 00CFF778 00CFF778.

Như vậy với cách thiết kế và giới hạn của C++, không thể lấy được địa chỉ của biến tham chiếu bằng cú pháp lập trình.

Assembly của con trỏ và tham chiếu

Sử dụng khả năng Disassembly để quan sát mã nguồn sau khi biên dịch:

int main() {
	// Cap phat
	int a = 8;
	int* p = &a;
	int& r = a;

	// Gan gia tri
	a = 9;
	*p = 9;
	r = 9;
	
	return 0;
}

Mã sinh ra trong thao tác cấp phát với pr tương đồng nhau và khác với a.

    int a = 8;
00853122  mov         dword ptr [a],8  
	
    int* p = &a;
00853129  lea         eax,[a]  
0085312C  mov         dword ptr [p],eax  

    int& r = a;
0085312F  lea         eax,[a]  
00853132  mov         dword ptr [r],eax

Mã sinh ra trong thao tác gán với pr tương đồng nhau và khác với a.

    a = 9;
00B03135  mov         dword ptr [a],9  

    *p = 9;
00B0313C  mov         eax,dword ptr [p]  
00B0313F  mov         dword ptr [eax],9  

    r = 9;
00B03145  mov         eax,dword ptr [r]  
00B03148  mov         dword ptr [eax],9 

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

#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 = x; và int b = y;

Khai báo này tức là đ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 void swap(int & a, int & b) điều như sau sẽ xảy ra:

int & a = x;int & b = y;

Dành cho bạn

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

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);
}

Một trong những nỗi đau không nhỏ là hiểu khái niệm và phân biệt khái niệm: Tham chiếu và Con trỏ là 2 khái niệm gây bối rối cho người mới bắt đầu.

IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024