Là một tên của một mảng một con trỏ trong C? Nếu không, sự khác biệt giữa tên của một mảng và một biến con trỏ là gì?
&array[0]
mang lại một con trỏ, không phải là một mảng;)
Là một tên của một mảng một con trỏ trong C? Nếu không, sự khác biệt giữa tên của một mảng và một biến con trỏ là gì?
&array[0]
mang lại một con trỏ, không phải là một mảng;)
Câu trả lời:
Một mảng là một mảng và một con trỏ là một con trỏ, nhưng trong hầu hết các trường hợp, tên mảng được chuyển đổi thành con trỏ. Một thuật ngữ thường được sử dụng là chúng phân rã thành con trỏ.
Đây là một mảng:
int a[7];
a
chứa không gian cho bảy số nguyên và bạn có thể đặt một giá trị vào một trong số chúng bằng một phép gán, như sau:
a[3] = 9;
Đây là một con trỏ:
int *p;
p
không chứa bất kỳ khoảng trắng nào cho số nguyên, nhưng nó có thể trỏ đến khoảng trắng cho một số nguyên. Ví dụ, chúng ta có thể đặt nó để trỏ đến một trong những vị trí trong mảng a
, chẳng hạn như vị trí đầu tiên:
p = &a[0];
Điều có thể gây nhầm lẫn là bạn cũng có thể viết điều này:
p = a;
Điều này không sao chép nội dung của mảng a
vào con trỏ p
(bất cứ điều gì có nghĩa). Thay vào đó, tên mảng a
được chuyển đổi thành một con trỏ thành phần tử đầu tiên của nó. Vì vậy, nhiệm vụ đó làm giống như trước đó.
Bây giờ bạn có thể sử dụng p
theo cách tương tự như một mảng:
p[3] = 17;
Lý do mà điều này hoạt động là toán tử hội nghị mảng trong C [ ]
, được định nghĩa theo các con trỏ. x[y]
có nghĩa là: bắt đầu với con trỏ x
, bước y
các phần tử chuyển tiếp sau những gì con trỏ trỏ tới, và sau đó lấy bất cứ thứ gì ở đó. Sử dụng cú pháp số học con trỏ, x[y]
cũng có thể được viết là *(x+y)
.
Đối với điều này để làm việc với một mảng bình thường, chẳng hạn như chúng tôi a
, tên a
ở a[3]
trước tiên phải được chuyển đổi sang một con trỏ (tới phần tử đầu tiên trong a
). Sau đó, chúng tôi bước 3 yếu tố về phía trước, và lấy bất cứ thứ gì ở đó. Nói cách khác: lấy phần tử ở vị trí 3 trong mảng. (Đây là phần tử thứ tư trong mảng, vì phần tử đầu tiên được đánh số 0.)
Vì vậy, tóm lại, tên mảng trong chương trình C (trong hầu hết các trường hợp) được chuyển đổi thành con trỏ. Một ngoại lệ là khi chúng ta sử dụng sizeof
toán tử trên một mảng. Nếu a
được chuyển đổi thành một con trỏ trong ngữ cảnh này, sizeof a
sẽ cho kích thước của một con trỏ chứ không phải của mảng thực tế, điều này sẽ khá vô dụng, vì vậy trong trường hợp đó a
có nghĩa là chính mảng đó.
functionpointer()
và (*functionpointer)()
có nghĩa là cùng một điều, đủ kỳ lạ.
sizeof()
bối cảnh khác trong đó không có mảng -> phân rã con trỏ là toán tử &
- trong ví dụ của bạn ở trên, &a
sẽ là một con trỏ tới một mảng 7 int
, không phải là một con trỏ tới một mảng int
; nghĩa là, kiểu của nó sẽ là int(*)[7]
, không hoàn toàn có thể chuyển đổi thành int*
. Theo cách này, các hàm thực sự có thể đưa con trỏ tới các mảng có kích thước cụ thể và thực thi các hạn chế thông qua hệ thống loại.
Khi một mảng được sử dụng làm giá trị, tên của nó đại diện cho địa chỉ của phần tử đầu tiên.
Khi một mảng không được sử dụng làm giá trị, tên của nó đại diện cho toàn bộ mảng.
int arr[7];
/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */
/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */
Nếu một biểu hiện của kiểu mảng (chẳng hạn như tên mảng) xuất hiện trong một biểu thức lớn hơn và nó không phải là toán hạng của một trong hai &
hoặc sizeof
khai thác, sau đó loại của biểu thức mảng được chuyển đổi từ "mảng N phần tử của T" để "Con trỏ tới T" và giá trị của biểu thức là địa chỉ của phần tử đầu tiên trong mảng.
Nói tóm lại, tên mảng không phải là một con trỏ, nhưng trong hầu hết các bối cảnh, nó được xử lý như thể nó là một con trỏ.
Biên tập
Trả lời câu hỏi trong bình luận:
Nếu tôi sử dụng sizeof, tôi có đếm kích thước chỉ các phần tử của mảng không? Sau đó, mảng Head head cũng chiếm không gian với thông tin về chiều dài và con trỏ (và điều này có nghĩa là nó chiếm nhiều không gian hơn so với một con trỏ bình thường)?
Khi bạn tạo một mảng, không gian duy nhất được phân bổ là không gian cho chính các phần tử; không lưu trữ được cụ thể hóa cho một con trỏ riêng biệt hoặc bất kỳ siêu dữ liệu nào. Được
char a[10];
những gì bạn nhận được trong bộ nhớ là
+---+
a: | | a[0]
+---+
| | a[1]
+---+
| | a[2]
+---+
...
+---+
| | a[9]
+---+
Các biểu a
đề cập đến toàn bộ mảng, nhưng không có đối tượng a
riêng biệt từ các phần tử mảng mình. Do đó, sizeof a
cung cấp cho bạn kích thước (tính bằng byte) của toàn bộ mảng. Biểu thức &a
cung cấp cho bạn địa chỉ của mảng, giống như địa chỉ của phần tử đầu tiên . Sự khác biệt giữa &a
và &a[0]
là loại kết quả 1 - char (*)[10]
trong trường hợp đầu tiên và char *
trong trường hợp thứ hai.
Trường hợp mọi thứ trở nên kỳ lạ là khi bạn muốn truy cập các phần tử riêng lẻ - biểu thức a[i]
được xác định là kết quả của *(a + i)
- được đưa ra một giá trị địa chỉ a
, i
phần tử bù ( không phải byte ) từ địa chỉ đó và kết quả lại.
Vấn đề a
không phải là con trỏ hay địa chỉ - đó là toàn bộ đối tượng mảng. Do đó, quy tắc trong C là bất cứ khi nào trình biên dịch nhìn thấy một biểu thức của kiểu mảng (chẳng hạn như a
có kiểu char [10]
) và biểu thức đó không phải là toán hạng của sizeof
toán tử hoặc toán tử đơn nguyên &
, loại biểu thức đó được chuyển đổi ("decays") đến một kiểu con trỏ ( char *
) và giá trị của biểu thức là địa chỉ của phần tử đầu tiên của mảng. Do đó, biểu thức a
có cùng loại và giá trị với biểu thức &a[0]
(và bằng phần mở rộng, biểu thức *a
có cùng loại và giá trị với biểu thức a[0]
).
C được bắt nguồn từ một ngôn ngữ trước đó được gọi là B, và trong B a
là một đối tượng con trỏ tách biệt với các phần tử mảng a[0]
, a[1]
vv Ritchie muốn giữ ngữ nghĩa mảng B, nhưng ông không muốn gây rối với lưu trữ các đối tượng con trỏ riêng biệt. Vì vậy, anh đã thoát khỏi nó. Thay vào đó, trình biên dịch sẽ chuyển đổi các biểu thức mảng thành các biểu thức con trỏ trong quá trình dịch khi cần thiết.
Hãy nhớ rằng tôi đã nói các mảng không lưu trữ bất kỳ siêu dữ liệu nào về kích thước của chúng. Ngay khi biểu thức mảng đó "phân rã" thành một con trỏ, tất cả những gì bạn có là một con trỏ tới một phần tử. Phần tử đó có thể là phần đầu tiên của chuỗi các phần tử hoặc nó có thể là một đối tượng. Không có cách nào để biết dựa trên chính con trỏ.
Khi bạn chuyển một biểu thức mảng cho một hàm, tất cả các hàm nhận được là một con trỏ tới phần tử đầu tiên - nó không biết mảng đó lớn đến mức nào (đây là lý do tại sao gets
hàm đó là một mối đe dọa như vậy và cuối cùng đã bị xóa khỏi thư viện). Để hàm biết có bao nhiêu phần tử của mảng, bạn phải sử dụng giá trị sentinel (chẳng hạn như dấu kết thúc 0 trong chuỗi C) hoặc bạn phải truyền số lượng phần tử làm tham số riêng.
sizeof
là một toán tử và nó ước tính số byte trong toán hạng (biểu thức biểu thị một đối tượng hoặc tên kiểu trong ngoặc đơn). Vì vậy, đối với một mảng, sizeof
ước tính số lượng phần tử nhân với số byte trong một phần tử. Nếu an int
rộng 4 byte thì mảng 5 phần tử int
chiếm 20 byte.
[ ]
cũng không đặc biệt? Ví dụ, int a[2][3];
sau đó x = a[1][2];
, mặc dù nó có thể được viết lại thành x = *( *(a+1) + 2 );
, ở đây a
không được chuyển đổi thành một loại con trỏ int*
(mặc dù nếu a
là một đối số của hàm thì nó nên được chuyển đổi thành int*
).
a
có kiểu int [2][3]
, "phân rã" thành kiểu int (*)[3]
. Biểu thức *(a + 1)
có kiểu int [3]
, "phân rã" thành int *
. Như vậy, *(*(a + 1) + 2)
sẽ có loại int
. a
điểm đến các mảng 3 phần tử đầu tiên của int
, a + 1
điểm đến các mảng 3 phần tử thứ hai của int
, *(a + 1)
là mảng 3 phần tử thứ hai của int
, *(a + 1) + 2
điểm đến yếu tố thứ ba của mảng thứ hai của int
, do đó *(*(a + 1) + 2)
là yếu tố thứ ba của mảng thứ hai của int
. Làm thế nào mà được ánh xạ tới mã máy hoàn toàn phụ thuộc vào trình biên dịch.
Một mảng được khai báo như thế này
int a[10];
cấp phát bộ nhớ trong 10 int
giây. Bạn không thể sửa đổi a
nhưng bạn có thể thực hiện số học con trỏ với a
.
Một con trỏ như thế này phân bổ bộ nhớ cho con trỏ p
:
int *p;
Nó không phân bổ bất kỳ int
s. Bạn có thể sửa đổi nó:
p = a;
và sử dụng các mục con mảng như bạn có thể với:
p[2] = 5;
a[2] = 5; // same
*(p+2) = 5; // same effect
*(a+2) = 5; // same effect
int
giây với thời gian lưu trữ tự động '.
Tên mảng tự nó mang lại một vị trí bộ nhớ, vì vậy bạn có thể coi tên mảng như một con trỏ:
int a[7];
a[0] = 1976;
a[1] = 1984;
printf("memory location of a: %p", a);
printf("value at memory location %p is %d", a, *a);
Và những thứ tiện lợi khác mà bạn có thể làm với con trỏ (ví dụ: thêm / trừ phần bù), bạn cũng có thể thực hiện với một mảng:
printf("value at memory location %p is %d", a + 1, *(a + 1));
Về mặt ngôn ngữ, nếu C không phơi bày mảng chỉ là một loại "con trỏ" (về mặt giáo dục thì đó chỉ là một vị trí bộ nhớ. Nó không thể chỉ ra vị trí tùy ý trong bộ nhớ, cũng không thể được lập trình viên kiểm soát). Chúng ta luôn cần mã này:
printf("value at memory location %p is %d", &a[1], a[1]);
Tôi nghĩ ví dụ này làm sáng tỏ vấn đề:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
int **b = &a;
printf("a == &a: %d\n", a == b);
return 0;
}
Nó biên dịch tốt (với 2 cảnh báo) trong gcc 4.9.2 và in như sau:
a == &a: 1
Giáo sư :-)
Vì vậy, kết luận là không, mảng không phải là con trỏ, nó không được lưu trong bộ nhớ (thậm chí không chỉ đọc) như một con trỏ, mặc dù có vẻ như vậy, vì bạn có thể lấy địa chỉ của nó bằng toán tử & . Nhưng - rất tiếc - toán tử đó không hoạt động :-)), dù sao đi nữa, bạn đã được cảnh báo:
p.c: In function ‘main’:
pp.c:6:12: warning: initialization from incompatible pointer type
int **b = &a;
^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
printf("a == &a: %d\n", a == b);
C ++ từ chối mọi nỗ lực như vậy với lỗi trong thời gian biên dịch.
Biên tập:
Đây là những gì tôi muốn chứng minh:
#include <stdio.h>
int main()
{
int a[3] = {9, 10, 11};
void *c = a;
void *b = &a;
void *d = &c;
printf("a == &a: %d\n", a == b);
printf("c == &c: %d\n", c == d);
return 0;
}
Mặc dù c
và a
"trỏ" vào cùng một bộ nhớ, bạn có thể lấy địa chỉ của c
con trỏ, nhưng bạn không thể lấy địa chỉ của a
con trỏ.
-std=c11 -pedantic-errors
, bạn sẽ gặp lỗi trình biên dịch khi viết mã C không hợp lệ. Lý do là vì bạn cố gắng gán a int (*)[3]
cho một biến int**
, đó là hai loại hoàn toàn không liên quan gì đến nhau. Vì vậy, những gì ví dụ này được cho là để chứng minh, tôi không có ý tưởng.
int **
loại không phải là điểm đó, ta nên sử dụng tốt hơn void *
cho việc này.
Tên mảng hoạt động giống như một con trỏ và trỏ đến phần tử đầu tiên của mảng. Thí dụ:
int a[]={1,2,3};
printf("%p\n",a); //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0
Cả hai câu lệnh in sẽ cung cấp chính xác cùng một đầu ra cho một máy. Trong hệ thống của tôi, nó đã cho:
0x7fff6fe40bc0
Một mảng là một tập hợp các yếu tố liên tục và liên tục trong bộ nhớ. Trong C, tên của mảng là chỉ mục cho phần tử đầu tiên và áp dụng phần bù bạn có thể truy cập phần còn lại của các phần tử. Một "chỉ mục cho phần tử đầu tiên" thực sự là một con trỏ tới hướng bộ nhớ.
Sự khác biệt với các biến con trỏ là bạn không thể thay đổi vị trí mà tên của mảng đang trỏ tới, tương tự như con trỏ const (tương tự, không giống nhau. Xem bình luận của Mark). Nhưng bạn cũng không cần phải hủy đăng ký tên mảng để nhận giá trị nếu bạn sử dụng số học con trỏ:
char array = "hello wordl";
char* ptr = array;
char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'
Vì vậy, câu trả lời là "có".
Tên mảng là địa chỉ của phần tử thứ 1 của một mảng. Vì vậy, có tên mảng là một con trỏ const.