Con trỏ và mảng
Mảng và con trỏ có mối quan hệ rất chặt chẽ trong C++ - hiểu mối quan hệ này giúp giải thích nhiều hành vi của mảng mà bạn đã thấy ở các bài trước.
Tên mảng “phân rã” thành con trỏ
Phần tiêu đề “Tên mảng “phân rã” thành con trỏ”Khi dùng tên của một mảng trong hầu hết ngữ cảnh, nó tự động chuyển thành con trỏ trỏ đến phần tử đầu tiên của mảng:
#include <iostream>
int main() { int numbers[5] = {10, 20, 30, 40, 50};
std::cout << numbers << std::endl; // Địa chỉ của phần tử đầu tiên std::cout << &numbers[0] << std::endl; // Cũng chính là địa chỉ đó
int* ptr = numbers; // Không cần dấu &, vì numbers đã "là" một địa chỉ std::cout << *ptr << std::endl; // 10 (giá trị của phần tử đầu tiên)
return 0;}Truy cập phần tử mảng bằng con trỏ
Phần tiêu đề “Truy cập phần tử mảng bằng con trỏ”int numbers[5] = {10, 20, 30, 40, 50};int* ptr = numbers;
std::cout << *ptr << std::endl; // 10 (numbers[0])std::cout << *(ptr + 1) << std::endl; // 20 (numbers[1])std::cout << *(ptr + 2) << std::endl; // 30 (numbers[2])Phép cộng trên con trỏ (pointer arithmetic)
Phần tiêu đề “Phép cộng trên con trỏ (pointer arithmetic)”Khi cộng một số nguyên vào con trỏ, C++ tự động “nhảy” theo đúng kích thước của kiểu dữ liệu, không phải theo byte:
int numbers[3] = {10, 20, 30};int* ptr = numbers;
std::cout << ptr << std::endl; // Địa chỉ, ví dụ 0x1000std::cout << (ptr + 1) << std::endl; // 0x1004 (nhảy 4 byte, vì sizeof(int) == 4)Điều này giải thích vì sao numbers[i] và *(numbers + i) cho kết quả giống hệt nhau - thực chất arr[i] chính là cú pháp rút gọn của *(arr + i):
int numbers[3] = {10, 20, 30};
std::cout << numbers[1] << std::endl; // 20std::cout << *(numbers + 1) << std::endl; // 20 - hoàn toàn tương đươngDuyệt mảng bằng con trỏ
Phần tiêu đề “Duyệt mảng bằng con trỏ”#include <iostream>
int main() { int numbers[5] = {1, 2, 3, 4, 5}; int* ptr = numbers;
for (int i = 0; i < 5; i++) { std::cout << *(ptr + i) << " "; } // 1 2 3 4 5
return 0;}Sự khác biệt quan trọng: mảng không phải là con trỏ
Phần tiêu đề “Sự khác biệt quan trọng: mảng không phải là con trỏ”Dù mảng “phân rã” thành con trỏ trong hầu hết ngữ cảnh, chúng không hoàn toàn giống nhau:
int numbers[5] = {1, 2, 3, 4, 5};int* ptr = numbers;
std::cout << sizeof(numbers) << std::endl; // 20 (5 phần tử x 4 byte = kích thước TOÀN BỘ mảng)std::cout << sizeof(ptr) << std::endl; // 8 (kích thước của MỘT con trỏ trên hệ 64-bit)sizeof trên mảng cho biết kích thước toàn bộ mảng, nhưng sizeof trên con trỏ chỉ cho biết kích thước của bản thân con trỏ (thường là 8 byte trên hệ 64-bit) - đây là lý do tại sao khi mảng “phân rã” thành con trỏ khi truyền vào hàm, hàm đó không thể tự biết kích thước mảng.
Tổng kết
Phần tiêu đề “Tổng kết”- Tên mảng, khi dùng trong biểu thức, tự động chuyển thành con trỏ trỏ đến phần tử đầu tiên
arr[i]và*(arr + i)là hai cách viết tương đương để truy cập phần tử thứi- Phép cộng trên con trỏ tự động “nhảy” theo kích thước của kiểu dữ liệu, không phải theo byte
- Mảng và con trỏ không hoàn toàn giống nhau:
sizeofcho kết quả khác nhau giữa hai loại