Để hiểu đầy đủ điều này, bạn phải nắm được các khái niệm sau:
Mảng không phải là con trỏ!
Trước hết (Và nó đã được giảng đủ), mảng không phải là con trỏ . Thay vào đó, trong hầu hết các trường hợp sử dụng, chúng 'phân rã' thành địa chỉ cho phần tử đầu tiên của chúng, có thể được gán cho một con trỏ:
int a[] = {1, 2, 3};
int *p = a; // p now points to a[0]
Tôi cho rằng nó hoạt động theo cách này để nội dung của mảng có thể được truy cập mà không cần sao chép tất cả chúng. Đó chỉ là một hành vi của các kiểu mảng và không có nghĩa là chúng giống nhau.
Mảng đa chiều
Mảng đa chiều chỉ là một cách để 'phân vùng' bộ nhớ theo cách mà trình biên dịch / máy có thể hiểu và vận hành.
Ví dụ, int a[4][3][5]
= một mảng chứa 4 * 3 * 5 (60) 'khối' bộ nhớ có kích thước số nguyên.
Lợi thế so với sử dụng int a[4][3][5]
đồng bằngint b[60]
là giờ đây chúng đã được 'phân vùng' (Dễ dàng làm việc hơn với 'các khối' của chúng, nếu cần) và chương trình hiện có thể thực hiện kiểm tra giới hạn.
Trên thực tế, int a[4][3][5]
được lưu trữ giống hệt như int b[60]
trong bộ nhớ - Sự khác biệt duy nhất là chương trình hiện quản lý nó như thể chúng là các thực thể riêng biệt có kích thước nhất định (Cụ thể là bốn nhóm ba nhóm năm).
Lưu ý: Cả hai int a[4][3][5]
và int b[60]
đều giống nhau trong bộ nhớ và sự khác biệt duy nhất là cách chúng được ứng dụng / trình biên dịch xử lý
{
{1, 2, 3, 4, 5}
{6, 7, 8, 9, 10}
{11, 12, 13, 14, 15}
}
{
{16, 17, 18, 19, 20}
{21, 22, 23, 24, 25}
{26, 27, 28, 29, 30}
}
{
{31, 32, 33, 34, 35}
{36, 37, 38, 39, 40}
{41, 42, 43, 44, 45}
}
{
{46, 47, 48, 49, 50}
{51, 52, 53, 54, 55}
{56, 57, 58, 59, 60}
}
Từ đó, bạn có thể thấy rõ ràng rằng mỗi "phân vùng" chỉ là một mảng mà chương trình theo dõi.
Cú pháp
Bây giờ, mảng khác với con trỏ về mặt cú pháp . Cụ thể, điều này có nghĩa là trình biên dịch / máy sẽ xử lý chúng theo cách khác nhau. Điều này có vẻ không có trí tuệ, nhưng hãy xem điều này:
int a[3][3];
printf("%p %p", a, a[0]);
Ví dụ trên in cùng một địa chỉ bộ nhớ hai lần, như sau:
0x7eb5a3b4 0x7eb5a3b4
Tuy nhiên, chỉ một có thể được gán trực tiếp cho một con trỏ :
int *p1 = a[0]; // RIGHT !
int *p2 = a; // WRONG !
Tại sao không a
thể gán cho một con trỏ nhưng a[0]
có thể?
Điều này, đơn giản, là hệ quả của các mảng nhiều chiều và tôi sẽ giải thích tại sao:
Ở cấp độ ' a
', chúng ta vẫn thấy rằng chúng ta có một 'không gian' khác để mong đợi. Ở cấp độ của 'a[0]
Tuy nhiên, ', chúng ta đã ở trong kích thước hàng đầu, theo như chương trình có liên quan, chúng ta chỉ đang xem xét một mảng bình thường.
Bạn có thể hỏi:
Tại sao lại có vấn đề nếu mảng là nhiều chiều liên quan đến việc tạo một con trỏ cho nó?
Tốt nhất hãy nghĩ theo cách này:
Một 'phân rã' từ một mảng nhiều chiều không chỉ là một địa chỉ mà còn là một địa chỉ với dữ liệu phân vùng (AKA nó vẫn hiểu rằng dữ liệu cơ bản của nó được tạo từ các mảng khác), bao gồm các ranh giới được thiết lập bởi mảng bên ngoài kích thước đầu tiên.
Logic 'phân vùng' này không thể tồn tại trong một con trỏ trừ khi chúng tôi chỉ định nó:
int a[4][5][95][8];
int (*p)[5][95][8];
p = a; // p = *a[0] // p = a+0
Nếu không, ý nghĩa của các thuộc tính sắp xếp của mảng sẽ bị mất.
Cũng lưu ý việc sử dụng dấu ngoặc đơn xung quanh *p
: int (*p)[5][95][8]
- Điều đó để xác định rằng chúng tôi đang tạo một con trỏ với các giới hạn này, không phải một mảng con trỏ với các giới hạn này:int *p[5][95][8]
Phần kết luận
Hãy xem lại:
- Mảng phân rã thành địa chỉ nếu chúng không có mục đích khác trong ngữ cảnh đã sử dụng
- Mảng đa chiều chỉ là mảng của mảng - Do đó, địa chỉ 'bị phân rã' sẽ mang gánh nặng "Tôi có thứ nguyên phụ"
- Dữ liệu thứ nguyên không thể tồn tại trong một con trỏ trừ khi bạn cung cấp cho nó .
Tóm lại: mảng đa chiều phân rã thành các địa chỉ có khả năng hiểu nội dung của chúng.