Con trỏ trỏ đến con trỏ
Vì con trỏ cũng là một biến (được lưu ở một địa chỉ nào đó), hoàn toàn có thể tạo một con trỏ trỏ đến một con trỏ khác. Đây là khái niệm nâng cao hơn nhưng quan trọng để hiểu sâu về cách bộ nhớ hoạt động.
Khai báo con trỏ đến con trỏ
Phần tiêu đề “Khai báo con trỏ đến con trỏ”#include <iostream>
int main() { int x = 10; int* ptr = &x; // ptr trỏ đến x int** ptr_to_ptr = &ptr; // ptr_to_ptr trỏ đến ptr (con trỏ trỏ đến con trỏ)
std::cout << x << std::endl; // 10 std::cout << *ptr << std::endl; // 10 (đi qua 1 cấp con trỏ) std::cout << **ptr_to_ptr << std::endl; // 10 (đi qua 2 cấp con trỏ)
return 0;}Hai dấu ** trong khai báo nghĩa là “con trỏ trỏ đến một con trỏ, mà con trỏ đó trỏ đến int”.
Hình dung bằng sơ đồ
Phần tiêu đề “Hình dung bằng sơ đồ”ptr_to_ptr ---> ptr ---> x (con trỏ) (con trỏ) (giá trị 10)ptr_to_ptrlưu địa chỉ củaptrptrlưu địa chỉ củaxxlưu giá trị10
Mỗi dấu * khi dereference là một bước “đi xuống” một cấp trong chuỗi này.
Thay đổi giá trị qua nhiều cấp con trỏ
Phần tiêu đề “Thay đổi giá trị qua nhiều cấp con trỏ”#include <iostream>
int main() { int x = 10; int* ptr = &x; int** ptr_to_ptr = &ptr;
**ptr_to_ptr = 100; // Đi qua 2 cấp, thay đổi giá trị của x
std::cout << x << std::endl; // 100
return 0;}Thay đổi con trỏ (không phải giá trị) thông qua cấp cao hơn
Phần tiêu đề “Thay đổi con trỏ (không phải giá trị) thông qua cấp cao hơn”#include <iostream>
int main() { int x = 10; int y = 20; int* ptr = &x; int** ptr_to_ptr = &ptr;
*ptr_to_ptr = &y; // Làm cho ptr trỏ sang y (thay đổi CHÍNH ptr, không phải giá trị nó trỏ đến)
std::cout << *ptr << std::endl; // 20 (ptr giờ trỏ đến y)
return 0;}Ứng dụng thực tế
Phần tiêu đề “Ứng dụng thực tế”Con trỏ đến con trỏ ít gặp trong code hàng ngày, nhưng xuất hiện trong một số trường hợp cụ thể:
- Mảng động 2 chiều (mảng của các con trỏ, mỗi con trỏ trỏ đến một mảng con)
- Một số hàm cần thay đổi chính con trỏ được truyền vào (vì C++ truyền con trỏ theo giá trị mặc định, muốn sửa đổi con trỏ gốc cần con trỏ đến con trỏ, hoặc dùng tham chiếu đến con trỏ)
#include <iostream>
void allocate(int** ptr) { *ptr = new int(42); // Thay đổi con trỏ GỐC (bên ngoài hàm) để trỏ đến vùng nhớ mới}
int main() { int* ptr = nullptr; allocate(&ptr); // Truyền địa chỉ của ptr, để hàm có thể thay đổi chính nó
std::cout << *ptr << std::endl; // 42
delete ptr; return 0;}Trong ví dụ trên, nếu chỉ truyền int* ptr (không phải int** ptr) vào hàm, hàm sẽ chỉ nhận được một bản sao của con trỏ, và không thể thay đổi con trỏ gốc ở main().
Tổng kết
Phần tiêu đề “Tổng kết”int** ptrlà một con trỏ trỏ đến một con trỏ khác, dùng**để truy cập giá trị gốc qua hai cấp- Mỗi dấu
*khi dereference tương ứng với việc “đi xuống” một cấp con trỏ - Ứng dụng thực tế phổ biến nhất: cho phép một hàm thay đổi chính con trỏ được truyền vào (không chỉ giá trị nó trỏ đến)