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++.
Tóm Tắt
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:
- Đặ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ỏ.
- 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;
age
vàtuoi
là 1, … do đóage
vàtuoi
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 a
là 00CFF778
và p
là 00CFF76C
. 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 a
là 00CFF778
và r
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 p
và r
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 p
và r
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) và (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 a
và b
.
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;
và 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.