Joh*_*ode 52
让我们首先得到重要的东西:数组不是指针.数组类型和指针类型是完全不同的东西,编译器会对它们进行不同的处理.
出现混淆的地方是C如何处理数组表达式.N1570:
6.3.2.1左值,数组和函数指示符
...
3除了当它是的操作数sizeof
运算符,_Alignof
操作者,或一元&
运算符,或者是用于初始化数组文本的字符串,其具有输入""的阵列的表达类型 ""被转换为表达式用类型""指针键入指向阵列对象的初始元素,不是左值"".如果数组对象具有寄存器存储类,则行为未定义.
我们来看看以下声明:
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *parr = arr;
Run Code Online (Sandbox Code Playgroud)
arr
是一个10元素的数组int
; 它指的是一个足以存储10个int
值的连续内存块.第二个声明中的表达式 arr
是数组类型,但由于它不是&
或的操作数sizeof
而且它不是字符串文字,表达式的类型变为"指向int
",而值是第一个元素的地址或者&arr[0]
.
parr
是指向int的指针; 它指的是一块足以容纳单个int
对象地址的内存块.arr
如上所述,它被初始化为指向第一个元素.
这是一个假设的存储器映射,显示了两者之间的关系(假设16位整数和32位地址):
Object Address 0x00 0x01 0x02 0x03 ------ ------- ---------------------- arr 0x10008000 0x00 0x00 0x00 0x01 0x10008004 0x00 0x02 0x00 0x03 0x10008008 0x00 0x04 0x00 0x05 0x1000800c 0x00 0x06 0x00 0x07 0x10008010 0x00 0x08 0x00 0x09 parr 0x10008014 0x10 0x00 0x80 0x00
这些类型对于像sizeof
和的事物很重要&
; sizeof arr == 10 * sizeof (int)
在这种情况下,它是20,而sizeof parr == sizeof (int *)
在这种情况下,它是4.类似地,表达式的类型&arr
是int (*)[10]
,或指向10元素数组的指针int
,而&parr
is 的类型int **
或指向指针的指针int
.
请注意,表达式arr
和&arr
将产生相同的值(第一个元素的地址arr
),但表达式的类型是不同的(int *
和int (*)[10]
分别).这在使用指针运算时有所不同.例如,给定:
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr;
int (*ap)[10] = &arr;
printf("before: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);
p++;
ap++;
printf("after: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);
Run Code Online (Sandbox Code Playgroud)
"之前"行应该为所有三个表达式打印相同的值(在我们的假设图中0x10008000
)."本经"行应该表现出三个不同的值:0x10008000
, 0x10008002
(基地+ sizeof (int)
)和0x10008014
(基地+ sizeof (int [10])
).
现在让我们回到上面的第二段:在大多数情况下,数组表达式转换为指针类型.我们来看下标表达式arr[i]
.由于表达式arr
不是作为任何一个sizeof
或的操作数出现&
,并且因为它不是用于初始化另一个数组的字符串文字,所以它的类型从"10元素数组int
"转换为"指向int
",以及下标操作正在应用于此指针值.实际上,当您查看C语言定义时,您会看到以下语言:
6.5.2.1数组下标
...
2后缀表达式后跟方括号[]中的表达式是数组对象元素的下标名称.下标运算符[]的定义是E1 [E2]与(*((E1)+(E2)))相同.由于适用于binary +运算符的转换规则,如果E1是数组对象(等效地,指向数组对象的初始元素的指针)并且E2是整数,则E1 [E2]指定E2的第2个元素.E1(从零开始计数).
实际上,这意味着您可以将下标运算符应用于指针对象,就像它是一个数组一样.这就像代码一样
int foo(int *p, size_t size)
{
int sum = 0;
int i;
for (i = 0; i < size; i++)
{
sum += p[i];
}
return sum;
}
int main(void)
{
int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int result = foo(arr, sizeof arr / sizeof arr[0]);
...
}
Run Code Online (Sandbox Code Playgroud)
它的工作方式. main
正在处理一个数组int
,而foo
正在处理一个指针int
,但两个都能够使用下标运算符,就好像它们都处理数组类型一样.
它还意味着数组下标是可交换的:假设a
是一个数组表达式并且i
是一个整数表达式,a[i]
并且i[a]
都是有效的表达式,并且两者都将产生相同的值.
不,它们的实现方式不同.两者都找到具有相同计算的元素:a[i]
是地址a + i*sizeof(a[0])
,也是p[i]
地址p + i*sizeof(p[0])
.
但是,类型系统对它们的处理方式不同.C++在数组上有输入信息,可以通过sizeof operator
(如C),模板推理,函数重载,RTTI等看到.基本上语言中使用类型信息的任何地方,指针和数组都可能表现不同.
C++中有许多例子,其中两种不同的语言概念具有相同的实现.只是几个:数组与指针,指针与引用,虚函数与函数指针,迭代器与指针,for循环与while循环,异常vs longjmp
在每种情况下,都有不同的语法和不同的思考方式,但最终会产生相同的机器代码.
在C++中(我认为在C中),数组不是指针,可以通过以下方式证明.
#include <iostream>
int main()
{
char arr[1000];
std::cout << sizeof arr;
}
Run Code Online (Sandbox Code Playgroud)
如果arr是一个指针,这个程序将打印sizeof(char*),通常为4.但它打印1000.
另一个证据:
template <class T>
void f(T& obj)
{
T x = obj; //this will fail to compile if T is an array type
}
int main()
{
int a[30] = {};
int* p = 0;
f(p); //OK
f(a); //results in compile error. Remember f takes by ref therefore needs lvalue and no conversion applies
}
Run Code Online (Sandbox Code Playgroud)
形式上,一个数组被转换为指向lvalue-to-rvalue转换中第一个元素的指针,也就是说,当一个rvalue被预期时,在一个上下文中给出一个数组类型的左值,该数组被转换为指向它的第一个元件.
此外,声明为按值获取数组的函数等同于获取指针的函数,即
void f(int a[]);
void f(int a[10]);
void f(int* a);
Run Code Online (Sandbox Code Playgroud)
是三个等同的声明.HTH