【发布时间】:2014-06-08 08:49:41
【问题描述】:
int (*arr)[5] 表示 arr 是一个指向 5 个整数的数组的指针。现在这个指针到底是什么?
如果我声明int arr[5] 是否相同,其中arr 是指向第一个元素的指针?
两个示例中的arr 是否相同?如果不是,那究竟什么是指向数组的指针?
【问题讨论】:
int (*arr)[5] 表示 arr 是一个指向 5 个整数的数组的指针。现在这个指针到底是什么?
如果我声明int arr[5] 是否相同,其中arr 是指向第一个元素的指针?
两个示例中的arr 是否相同?如果不是,那究竟什么是指向数组的指针?
【问题讨论】:
首先介绍一些理论(您可以跳到“答案”部分,但我建议您也阅读此部分):
int arr[5]
这是一个数组,“arr”不是指向数组第一个元素的指针。 在特定情况下(例如,将它们作为左值传递给函数)它们会衰减为指针:您将失去对它们调用 sizeof 的能力。
在正常情况下,数组是数组,指针是指针,它们是完全不同的两个东西。
在处理衰减指针和指向您编写的数组的指针时,它们的行为完全相同,但有一点需要注意:T 类型的数组可以衰减为 T 类型的指针,但只能衰减一次(或一级-深的)。新创建的衰减类型不能进一步衰减为其他任何东西。
这意味着像这样的二维数组
int array1[2][2] = {{0, 1}, {2, 3}};
不能传递给
void function1(int **a);
因为它意味着两个级别的衰减并且这是不允许的(您会丢失数组元素的布局方式)。以下将改为工作:
void function1(int a[][2]);
void function1(int a[2][2]);
在将一维数组作为左值传递给函数的情况下,您可以将其衰减为简单的指针,在这种情况下您可以像使用任何其他指针一样使用它。
回答您的问题:
int (*arr)[5]
这是一个指向数组的指针,您可以将“作为一个由 5 个整数组成的数组”视为它的类型,即您不能使用它来指向一个由 3 个整数组成的数组。
int arr[5]
这是一个数组,除非你将它作为左值传递,否则它总是表现得像一个数组
int* ptrToArr = arr;
在这种情况下,数组会衰减(除了上面我引用的所有例外情况),您会得到一个 指针,您可以随意使用它。
并且:不,它们不相等,否则将允许这样的事情
int (*arr)[5]
int* ptrToArr = arr; // NOT ALLOWED
Error cannot convert ‘int (*)[5]’ to ‘int*’ in initialization
它们都是指针,但区别在于它们的类型。
【讨论】:
在运行时,无论它指向什么,指针都是“只是一个指针”,区别是语义上的;与指向元素的指针相比,指向数组的指针传达了不同的含义(对编译器)
在处理指向数组的指针时,您指向的是指定大小的数组 - 编译器将确保您只能指向该大小的数组。
即此代码将编译
int theArray[5];
int (*ptrToArray)[5];
ptrToArray = &theArray; // OK
但这会中断:
int anotherArray[10];
int (*ptrToArray)[5];
ptrToArray = &anotherArray; // ERROR!
在处理指向元素的指针时,您可以指向内存中具有匹配类型的任何对象。 (它甚至不一定需要在一个数组中;编译器不会做任何假设或以任何方式限制你)
即
int theArray[5];
int* ptrToElement = &theArray[0]; // OK - Pointer-to element 0
和..
int anotherArray[10];
int* ptrToElement = &anotherArray[0]; // Also OK!
总而言之,数据类型int* 并不意味着任何数组知识,但是数据类型int (*)[5] 意味着一个数组,它必须包含恰好5 个元素。
【讨论】:
int* anotherPtr = theArray; 应该不起作用。
int* anotherPtr = &theArray[0]; 的简写语法,它们的作用完全相同。
指向数组的指针是指向特定类型数组的指针。类型包括元素的类型以及大小。您不能为其分配不同类型的数组:
int (*arr)[5];
int a[5];
arr = &a; // OK
int b[42];
arr = &b; // ERROR: b is not of type int[5].
指向数组第一个元素的指针可以指向任何具有正确元素类型的数组的开头(实际上它可以指向数组中的任何元素):
int* arr;
int a[5];
arr = &a[0]; // OK
int b[42];
arr = &b[0]; // OK
arr = &b[9]; // OK
请注意,在 C 和 C++ 中,数组在某些上下文中衰减为指向其元素类型的指针。这就是为什么可以这样做:
int* arr;
int a[5];
arr = a; // OK, a decays to int*, points to &a[0]
这里,arr (int*) 的类型与 a (int[5]) 的类型不同,但 a 衰减为指向其第一个元素的 int*,使得转让合法。
【讨论】:
指向数组的指针和指向数组第一个元素的指针是不同的。对于int (*arr)[5],arr 是指向5int 的内存块的指针。取消引用arr 将给出整行。在int arr[5] 的情况下,arr 衰减为指向第一个元素的指针。取消引用 arr 将给出第一个元素。
在这两种情况下,起始地址相同,但两个指针的类型不同。
如果我声明
int arr[5]是否相同,其中arr是指向第一个元素的指针?两个示例中的arr是否相同?如果不是,那么指向数组的指针到底是什么?
没有。要理解这一点,请参阅函数图1:
void f(void) {
int matrix[4][2] = { {0,1}, {2,3}, {4,5}, {6,7} };
char s[] = "abc";
int i = 123;
int *p1 = &matrix[0][0];
int (*p2)[2] = &matrix[0];
int (*p3)[4][2] = &matrix;
/* code goes here */
}
所有三个指针都允许您在matrix[0][0] 中找到0,如果您将这些指针转换为“字节地址”并使用@ 中的%p 指令将它们打印出来987654339@, 这三个很可能产生相同的输出(在典型的现代计算机上)。但是int * 指针p1 只指向一个int,用黑色圈起来。红色指针p2,其类型为int (*)[2],指向两个ints,而蓝色指针——指向整个矩阵的那个——确实指向整个矩阵。
这些差异会影响指针算术和一元 *(间接)运算符的结果。由于p1 指向一个int,p1 + 1 向前移动一个int。黑色圆圈1 仅与int 一样大,而*(p1 + 1) 只是下一个int,其值为1。同样,sizeof *p1 只是sizeof(int)(可能是4)。
由于p2 指向整个“int 数组2”,但是,p2 + 1 将向前移动一个这样的数组。结果将是一个指向围绕{2,3} 对的红色圆圈的指针。由于间接运算符的结果是一个对象,*(p2 + 1) 是整个数组对象,它可能属于规则。如果它确实属于规则,则该对象将改为指向其第一个元素的指针,即当前持有 2 的 int。如果它不属于规则——例如,在sizeof *(p2 + 1) 中,它将对象置于对象上下文中——它将保留整个数组对象。这意味着sizeof *(p2 + 1)(当然还有sizeof *p2)是sizeof(int[2])(可能是8)。
【讨论】:
整个数组的地址和第一个元素的地址被定义为相同的,因为 C++(和 C)中的数组除了组成对象之外没有固有的填充。
但是,这些指针的类型是不同的。在您执行某种类型转换之前,将 int * 与 int (*)[5] 进行比较是苹果与橘子的比较。
如果您声明arr[5],那么arr 不是指向第一个元素的指针。它是数组对象。您可以观察到这一点,因为sizeof( arr ) 将等于5 * sizeof (int)。数组对象隐式转换为指向其第一个元素的指针。
指向数组的指针不会隐式转换为任何内容,这可能是您混淆的另一个原因。
【讨论】:
如果您写int arr[5],您将在堆栈上创建一个包含五个int 的数组。这占用的大小等于五个整数的大小。
如果您写int (*arr)[5],您将创建一个指针,指向堆栈上的五个int 的数组。这占用的大小等于指针的大小。
如果从上面不清楚的话,指针与数组有分开存储,可以指向任何东西,但不能将数组名称分配给其他东西。
查看我的回答here了解更多详情。
【讨论】: