Trong bài viết này sẽ giới thiệu cơ bản cho các bạn về con trỏ trong C/C++. Đây là 1 trong những đặc trưng của ngôn ngữ lập trình này mà ở các ngôn ngữ khác như Java, C#… bạn sẽ không thấy.
Mình cũng chưa có nhiều kinh nghiệm về C++, tuy vậy kiến thức dưới đây là do tìm hiểu và tổng hợp lại trong quá trình nghiên cứu cho công việc. Nếu có gì chưa chính xác mọi người hỗ trợ giúp mình nhé!
Biến trong C++ (variable)
Biên là 1 ô nhớ đơn lẻ, hoặc một vùng nhớ được hệ điều hành cấp phát nhằm lưu trữ giá trị trong vùng nhớ đó. Thông thường khi thao tác chúng ta cũng không quan tâm đến vùng nhớ của biến. Các thao tác liên quan sẽ thông qua tên định danh của biến đó – hay còn gọi là tên biến.
Khi thao tác với các biến thông thường, chúng ta không cần quan tâm đến địa chỉ vùng nhớ của biến. Khi cần truy xuất giá trị của biến, chúng ta chỉ cần gọi định danh (hay thường gọi là tên biến).
int a = 1000; cout << "Gia Tri Cua a: " << a << endl; // Giá trị của a
Tham chiếu trong C++
Tham chiếu dùng để tạo ra biến mới nhưng có cùng địa chỉ với biến được tham chiếu đến.
Bất kì sự thay đổi nào ở vùng địa chỉ nhớ này đều tác động tới các biến tham chiếu tới vùng nhớ đó
Việc sử dụng tham chiếu này bạn có thể thấy trong lập trình PHP. Tuy vậy trong các Framework mới bạn sẽ ít thấy điều này hơn!
int v1 = 999; int &v2 = v1; cout << "v1: " << v1 << endl; // Trả về 999 cout << "&v1: " << &v1 << endl; // Trả về vùng nhớ của biến v1 cout << "v2: " << v2 << endl; // Trả về 999 cout << "&v2: " << &v2 << endl;// Trả về vùng nhớ của v2, đây cũng là vùng nhớ biến v1
Dưới đây là kết quả:
Và dưới đây là ví dụ về việc thay đổi giá trị của biến
int v1 = 999; int &v2 = v1; cout << "v1 = " << v1 << " - v2 ="<< v2 << endl; v1 = 888; cout << "v1 = " << v1 << " - v2 =" << v2 << endl; v2 = 777; cout << "v1 = " << v1 << " - v2 =" << v2 << endl;
Kết quả là
Dereference operator
Toán tử Dereference operator (trỏ đến) hay còn gọi là indirection operator (toán tử điều hành gián tiếp) sử dụng trong C++ với ký tự ” * “. Toán tử này sử dụng với mục đích lấy giá trị của 1 địa chỉ nhớ.
Khác với việc tham chiếu, Dereference operator không tạo ra biến mới mà thao tác trực tiếp với vùng nhớ đã xác định đó.
Ví dụ:
int v1 = 999; cout << "v1: " << v1 << endl; // Trả về 999 cout << "&v1: " << &v1 << endl; // Trả về vùng nhớ của biến v1 cout << "*&v1 " << *&v1 << " = *(&v1) " << *(&v1) << endl; // Trả về giá trị lưu tại 1 địa chỉ nhớ *&v1 = 888; cout << "v1: " << v1 << endl; cout << "&v1: " << &v1 << endl; // Vùng nhớ không thay đổi, chỉ có giá trị thay đổi
Kết quả tương ứng như sau
v1: 999 &v1: 00F3FC60 *&v1 999 = *(&v1) 999 v1: 888 &v1: 00F3FC60 Press any key to continue . . .
Như ta thấy
Có thể gán trực tiệp giá trị tương ứng với vùng nhớ đã xác định dưỡi dạng *&v1 = 888; Vùng nhớ sẽ không bị thay đổi, chỉ có giá trị là thay đổi.
Con trỏ (Pointer)
Một con trỏ ( apointer) là một biến được dùng để lưu trữ địa chỉ của biến khác.
Khác với tham chiếu, con trỏ là một biến có địa chỉ độc lập so với vùng nhớ mà nó trỏ đến, nhưng giá trị của vùng nhớ của con trỏ chính là địa chỉ của biến mà nó trỏ tới.
int v1 = 999; // Khai báo biến v1 có giá trị 999; int *v2 = &v1; // Biến v2 sẽ có giá trị là địa chỉ của biến v1 cout << "v1: " << v1 << " - &v1: " << &v1 << endl; cout << "v2: " << v2 << " - &v2: " << &v2 << endl;
Khi đó kết quả là
v1: 999 - &v1: 00B9FE88 v2: 00B9FE88 - &v2: 00B9FE7C
Như ta thấy
Giá trị của biến v2 là địa chỉ vùng nhớ của v1 và bằng: 00B9FE88
Còn địa chỉ vùng nhớ của v2 là 00B9FE7C
Kiểu dữ liệu của v2 khi này là int * thay vì là int
Chúng ta có thể dùng lệnh dưới để kiểm tra
cout << "Kieu du lieu cua v2: " << typeid(v2).name() << endl;
Kết quả là
Kieu du lieu cua v2: int *
Để gán giá trị chúng ta dùng toán tử Dereference Operator:
int v1 = 999; // Khai báo biến v1 có giá trị 999; int *v2 = &v1; // Biến v2 sẽ có giá trị là địa chỉ của biến v1 cout << "v1: " << v1 << " - v2: " << v2 << endl; *v2 = 888; cout << "v1: " << v1 << " - v2: " << v2 << endl;
Kết quả là
v1: 999 - v2: 010FFDB4 v1: 888 - v2: 010FFDB4
Ví dụ tổng quát lại
int a = 1000; // Giá giá trị 1000 cho a cout << "Gia Tri Cua a: " << a << endl; // Giá trị của a = 1000 int* p_a = &a; // lấy địa chỉ của a gán vào biến p_a cout << "Dia chi cua bien a " << &a << " = p_a = "<<p_a << endl; int** p_p_a = &p_a; // Lấy địa chỉ của p_a gán vào p_p_a cout << "Dia chi cua &p_a " << &p_a << " = p_p_a ="<<p_p_a << endl; cout << "Gia tri cua *p_p_a " << *p_p_a << " = p_a "<< p_a << endl; cout << "Gia tri a =" << a << " = gia tri *p_a = "<<*p_a << " = gia tri cua gia tri p_p_a = " << **p_p_a << " = "<< *(*p_p_a) << endl;
Kết quả là
Gia Tri Cua a: 1000 Dia chi cua bien a 010FFEB8 = p_a = 010FFEB8 Dia chi cua &p_a 010FFEAC = p_p_a =010FFEAC Gia tri cua *p_p_a 010FFEB8 = p_a 010FFEB8 Gia tri a =1000 = gia tri *p_a = 1000 = gia tri cua gia tri p_p_a = 1000 = 1000
Khi đó kiểu dữ liệu của p_p_a là
cout << " p_p_a co kieu gia tri: "<<typeid(p_p_a).name() << endl;
Kiểu dữ liệu của p_p_a là int * *
p_p_a co kieu gia tri: int * *
Từ ví dụ trên ta cũng có thể để gán giá trị lại cho biến a ban đầu thong qua p_p_a sẽ như sau
int a = 1000; // Giá giá trị 1000 cho a int* p_a = &a; // lấy địa chỉ của a gán vào biến p_a int** p_p_a = &p_a; // Lấy địa chỉ của p_a gán vào p_p_a cout << "a = " << a <<endl; *p_a = 999; cout << "a = " << a << endl; **p_p_a = 888; cout << "a = " << a << endl;
Kết quả như sau
a = 1000 a = 999 a = 888
Từ những ví dụ trên đây ta có 1 kiểu viết khá hài hước và cũng khó hiểu trong C++
int a = 1000; cout << *&*&*&*&*&*&*&*&a << endl;
Theo bạn thì kết quả được in ra là bao nhiêu???
Nếu bạn nói 1000, thì chính xác là như vậy rồi đấy ạ!
Con trỏ và mảng
Phần này mình sẽ nói về việc con trỏ với kiểu dữ liệu mảng, vì có 1 chút sự khác biệt so với các kiến thức bên trên. Bạn có thể đọc nhiều hơn về mảng tại bài viết Cấu trúc dữ liệu mảng.
Xem ví dụ dưới đây
int arr[5] = { 1,3,4,5,6 }; for (int i = 0; i < 5; i++) { cout << "arr[" << i << "] = " << arr[i] << " - dia chi "<< &arr[i] << endl; } cout << arr << endl;
Kết quả như dưới đây
arr[0] = 1 - dia chi 00D6F848 arr[1] = 3 - dia chi 00D6F84C arr[2] = 4 - dia chi 00D6F850 arr[3] = 5 - dia chi 00D6F854 arr[4] = 6 - dia chi 00D6F858 00D6F848
Các bạn để ý sẽ thấy
+ Mảng là sự liên tục các vùng nhớ: 00D6F848 + 4 = 00D6F84C . 4 là kích thươc vùng nhớ của kiểu int
+ Khi cout arr thì kết quả trả về là 1 địa chỉ vùng nhớ, và địa chỉ này là địa chỉ của phần tử đầu tiên của mảng
Kết luận
Trên đây là toàn bộ các thông tin mà mình muốn gửi tới các bạn trong bài viết về con trỏ trong C++. Hi vọng với bài viết này bạn sẽ tự tin hơn khi học và tìm hiểu về C++. Nếu có góp ý hoặc thắc mắc gì đừng ngại ngần mà đặt câu hỏi và gửi thông tin cho mình nhé!