指向固定大小数组的指针数组

我试图分配两个固定大小的数组指向他们的指针数组,但编译器警告我,我不明白为什么。

int A[5][5]; int B[5][5]; int*** C = {&A, &B}; 

此代码编译时出现以下警告:

警告:从不兼容的指针types初始化[缺省情况下启用]

如果我运行代码,会引发分段错误 。 但是,如果我dynamic分配AB ,它工作得很好。 为什么是这样?

如果你想要一个符合AB现有声明的C声明,你需要这样做:

 int A[5][5]; int B[5][5]; int (*C[])[5][5] = {&A, &B}; 

C的types被读为“ C是指向int [5][5]数组的指针数组 ”。 既然你不能分配一个完整的数组,你需要分配一个指向数组的指针。

通过此声明, (*C[0])[1][2]正在访问与A[1][2]相同的内存位置。

如果你想要像C[0][1][2]这样简洁的语法,那么你需要做别人已经说过的话并dynamic地分配内存:

 int **A; int **B; // allocate memory for A and each A[i] // allocate memory for B and each B[i] int **C[] = {A, B}; 

你也可以用莫斯科的Vladbuild议的语法来做这个:

 int A[5][5]; int B[5][5]; int (*C[])[5] = {A, B}; 

C这个声明被读为“ C是指向int [5]数组的指针数组 ”。 在这种情况下, C每个数组元素的types是int (*)[5] ,而int [5][5]数组可以衰减到这种types。

现在,您可以使用C[0][1][2]访问与A[1][2]相同的内存位置。

这个逻辑也可以扩展到更高维度:

 int A[5][5][3]; int B[5][5][3]; int (*C[])[5][3] = {A, B}; 

不幸的是,有很多蹩脚的书籍/教程/老师会教你错误的东西….

忘记指针指针,它们与数组无关。 期。

另外,作为一个经验法则:无论何时你发现自己使用了两个以上的间接级别,这很可能意味着你的程序devise从根本上是有缺陷的,需要从零开始重新编制。


为了做到这一点,你必须这样做:

指向数组int [5][5] 指针称为数组指针 ,声明为int(*)[5][5] 。 例:

 int A[5][5]; int (*ptr)[5][5] = &A; 

如果你想要一个数组指针的数组 ,它将是typesint(*[])[5][5] 。 例:

 int A[5][5]; int B[5][5]; int (*arr[2])[5][5] = {&A, &B}; 

正如你可以告诉这个代码看起来不必要的复杂 – 它是。 访问单个项目会很痛苦,因为您必须键入(*arr[x])[y][z] 。 含义:“在数组指针数组中,取数组指针的数字x,取其指向的内容 – 这是一个二维数组 – 然后把该数组中的索引[y] [z]项。

发明这样的结构只是疯狂,没有什么我会推荐的。 我想通过使用一个普通的数组指针可以简化代码:

 int A[5][5]; int B[5][5]; int (*arr[2])[5][5] = {&A, &B}; int (*ptr)[5][5] = arr[0]; ... ptr[x][y][z] = 0; 

不过,这个代码还是有些复杂的。 完全考虑一个不同的devise! 例子:

  • 制作一个3D数组。
  • 创build一个包含二维数组的结构体,然后创build一个这样的结构体的数组。

这条线有很多错误

 int*** C = {&A, &B}; 

你声明了一个单一的指针C ,但是你要告诉它指向多个对象; 那不行。 你需要做的是将C声明为这些数组的指针数组。

&A&B的types都是int (*)[5][5] ,或者“指向5元素数组的5元素数组的指针”。 因此,C的types需要是“指向5元素数组的5元素数组的指针数组”,或者

 int (*C[2])[5][5] = { &A, &B }; 

其中读作为

  C -- C is a C[2] -- 2-element array of *C[2] -- pointers to (*C[2])[5] -- 5-element arrays of (*C[2])[5][5] -- 5-element arrays of int (*C[2])[5][5] -- int 

呸。 这真是太丑了。 如果你想通过C访问A或者B的元素,它会变得更加丑陋:

 int x = (*C[0])[i][j]; // x = A[i][j] int y = (*C[1])[i][j]; // y = B[i][j] 

我们必须明确地解引用C[i]然后才能指向它所指向的数组,因为下标运算符[]具有比一元运算符更高的优先级,所以我们需要将*C[0]分组到parens中。

我们可以稍微清理一下。 除了sizeof或unary &运算符的操作数(或者是在声明中用于初始化另一个数组的string文字)之外,types“ T N元素数组”的expression式将被转换(“decay”)到“指向T指针”types的expression式,expression式的值将是数组的第一个元素的地址。

expression式AB具有typesint [5][5] ,或者“5元素数组的5元素数组”。 通过上面的规则,这两个expression式“衰减”到“指向5元素数组的指针”types的expression式,或者int (*)[5] 。 如果我们使用AB来初始化数组,而不是&A&B ,那么我们需要一个指向int五元素数组的指针数组,

 int (*C[2])[5] = { A, B }; 

好吧,这仍然是非常晦涩的,但是这样干净,因为这将得到没有typedefs。

那么我们如何通过C访问AB元素呢?

请记住,数组下标操作a[i]定义*(a + i) ; 即给定一个基地址a ,从该地址偏移i 元素不是字节1 ,并对结果进行解引用。 这意味着

 *a == *(a + 0) == a[0] 

从而,

 *C[i] == *(C[i] + 0) == C[i][0] 

把这一切放在一起:

 C[0] == A // int [5][5], decays to int (*)[5] C[1] == B // int [5][5], decays to int (*)[5] *C[0] == C[0][0] == A[0] // int [5], decays to int * *C[1] == C[1][0] == B[0] // int [5], decays to int * C[0][i] == A[i] // int [5], decays to int * C[1][i] == B[i] // int [5], decays to int * C[0][i][j] == A[i][j] // int C[1][i][j] == B[i][j] // int 

我们可以将C索引,就好像它是一个int数组,它比(*C[i)[j][k]更清晰一些。

这个表也可能是有用的:

 Expression Type "Decays" to Value ---------- ---- ----------- ----- A int [5][5] int (*)[5] Address of A[0] &A int (*)[5][5] Address of A *A int [5] int * Value of A[0] (address of A[0][0]) A[i] int [5] int * Value of A[i] (address of A[i][0]) &A[i] int (*)[5] Address of A[i] *A[i] int Value of A[i][0] A[i][j] int Value of A[i][j] 

请注意, A&AA[0]&A[0]&A[0][0]都会产生相同的 (数组的地址和数组的第一个元素的地址总是相同的)但是types是不同的,如上表所示。


  1. 指针算术考虑指向types的大小; 如果p包含一个int对象的地址,那么p+1产生下一个int对象的地址,这个对象可能是2到4个字节。

C初学者的一个常见的误解是,他们只是假设指针和数组是等价的。 这完全错了。

当他们看到类似的代码时,混淆了初学者

 int a1[] = {1,2,3,4,5}; int *p1 = a1; // Beginners intuition: If 'p1' is a pointer and 'a1' can be assigned // to it then arrays are pointers and pointers are arrays. p1[1] = 0; // Oh! I was right a1[3] = 0; // Bruce Wayne is the Batman! Yeah. 

现在,初学者已经证实,数组是指针,指针是数组,所以他们做了这样的实验:

 int a2[][5] = {{0}}; int **p2 = a2; 

然后popup一个关于不兼容的指针分配的警告,然后他们想:“天哪!为什么这个arrays成为哈维·丹特?”

有些甚至超前了一步

 int a3[][5][10] = {{{0}}}; int ***p3 = a3; // "?" 

然后Riddler进入了arrays指针等价的梦魇。

在这里输入图像说明

一定要记住, 数组不是指针 ,反之亦然。 数组是数据types,指针是另一种数据types( 不是数组types )。 这已经在几年前的C-FAQ中得到了解决:

说数组和指针是“等价的”,意味着既不相同也不可互换。 这意味着数组和指针算术的定义使得指针可以方便地用来访问数组或模拟数组。 换句话说,就像Wayne Throop所说的那样, “指针算术和数组索引[在C中是等价的,指针和数组是不同的。

现在总是记住几个重要的规则来避免这种混淆:

  • 数组不是指针。 指针不是数组。
  • 当在expression式中使用时,数组被转换为指向其第一个元素的指针,除了当sizeof& operator的操作数。
  • 这是指针算术数组索引是相同的。
  • 指针和数组是不同的。
  • 我说过“指针不是数组,反之亦然”。

现在你有规则,你可以得出结论

 int a1[] = {1,2,3,4,5}; int *p1 = a1; 

a1是一个数组,在声明中int *p1 = a1; 它转换为指向其第一个元素的指针。 它的元素的types是int那么指向它的第一个元素的指针是int * ,它与p1兼容。

 int a2[][5] = {{0}}; int **p2 = a2; 

a2是一个数组,在int **p2 = a2; 它会衰减指向它的第一个元素。 它的元素types为int[5] (二维数组是一维数组),因此指向其第一个元素的指针将是int(*)[5] (指向数组的指针),它与int **types不兼容int ** 。 它应该是

 int (*p2)[5] = a2; 

同样的

 int a3[][5][10] = {{{0}}}; int ***p3 = a3; 

a3元素types为int [5][10] ,指向其第一个元素的指针的types为int (*)[5][10] ,但是p3的types为int *** ,所以为了使它们兼容,它应该是

 int (*p3)[5][10] = a3; 

现在来到你的片段

 int A[5][5]; int B[5][5]; int*** C = {&A, &B}; 

&A&B的types是int(*)[5][5]C的types是int*** ,它不是一个数组。 既然你想让C保存数组AB的地址,你需要声明C为两个int(*)[5][5]types元素的数组。 这应该做为

 int (*C[2])[5][5] = {&A, &B}; 

但是,如果我dynamic分配A和B它工作得很好。 为什么是这样?

在这种情况下,您必须将AB声明为int ** 。 在这种情况下,都是指针,而不是数组。 C的types是int *** ,所以它可以保存int**types数据的地址。 请注意,在这种情况下,声明int*** C = {&A, &B}; 应该

  int*** C = &A; 

如果是int*** C = {&A, &B}; ,程序的行为将是未定义的或实现定义的。

C11:5.1.1.3(P1):

如果预处理翻译单元或翻译单元包含违反任何语法规则或约束的情况,即使该行为也被明确指定为未定义或实现定义的,则一致性实现应产生至less一个诊断消息(以实现定义的方式标识)定义

阅读这篇文章的进一步解释。

数组与C中的多维指针并不相同。在大多数情况下,数组的名称被解释为包含它的缓冲区的地址,无论如何索引它。 如果A被声明为int A[5][5] ,那么A通常意味着第一个元素的地址,即它被有效地解释为int * (实际上是int *[5] ),而不是int **在所有。 地址的计算恰好需要两个元素: A[x][y] = A + x + 5 * y 。 这是做A[x + 5 * y]一个方便,它不会将A提升为多维缓冲区。

如果你想在C中使用多维指针,你也可以这样做。 语法是非常相似的,但它需要更多的设置。 有几个常见的方法来做到这一点。

使用单个缓冲区:

 int **A = malloc(5 * sizeof(int *)); A[0] = malloc(5 * 5 * sizeof(int)); int i; for(i = 1; i < 5; i++) { A[i] = A[0] + 5 * i; } 

每行有一个单独的缓冲区:

 int **A = malloc(5 * sizeof(int *)); int i; for(i = 0; i < 5; i++) { A[i] = malloc(5 * sizeof(int)); } 

你被数组和指针的等价混淆了。

当你声明一个像A[5][5]这样的数组时,因为你声明了两个维度,C将连续地为25个对象分配内存。 也就是说,内存将被分配像这样:

 A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ... 

结果对象A是一个指向这块内存开始的指针。 它的types是int * ,而不是int **

如果你想要一个指向数组的指针向量,你需要声明你的variables为:

 int *A[5], *B[5]; 

这会给你:

 A0, A1, A2, A3, A4 

所有的typesint* ,你将不得不使用malloc()或任何填充。

或者,你可以声明Cint **C

尽pipe数组和指针紧密相关,但它们并不完全相同。 人们有时会对此感到困惑,因为在大多数情况下,数组的值会衰减到指针,并且因为数组符号可以用在函数原型中来声明实际上是指针的参数。 另外,很多人认为数组索引符号实际上执行了指针运算和取消引用的组合,因此它对于指针值和数组值(因为数组值衰减为指针)同样适用。

鉴于声明

 int A[5][5]; 

variablesA指定一个由5个int组成的数组。 这个衰变会衰减到int (*)[5]types的指针 – 也就是指向一个5 int数组的指针。 另一方面,指向整个multidimensional array的指针的types为int (*)[5][5] (指向由5个int组成的5个数组的数组),这与int *** (指针指向指向int指针)。 如果你想声明一个指向multidimensional array的指针,例如这些,那么你可以这样做:

 int A[5][5]; int B[5][5]; int (*C)[5][5] = &A; 

如果你想声明一个这样的指针数组,那么你可以这样做:

 int (*D[2])[5][5] = { &A, &B }; 

添加:

这些区别以各种方式发挥作用,其中一些更为重要的是arrays价值不会衰退的指向性背景,以及与这些特征相关的背景。 其中最重要的一个是当valueof是sizeof运算符的操作数。 鉴于上述声明,以下所有关系expression式评估为1(真):

 sizeof(A) == 5 * 5 * sizeof(int) sizeof(A[0]) == 5 * sizeof(int) sizeof(A[0][4]) == sizeof(int) sizeof(D[1]) == sizeof(C) sizeof(*C) == sizeof(A) 

此外,这些关系expression式有可能但不能保证1:

 sizeof(C) == sizeof(void *) sizeof(D) == 2 * sizeof(void *) 

这对于数组索引的工作原理是很重要的,而且在分配内存的时候也很重要。

要么你应该声明第三个数组

 int A[5][5]; int B[5][5]; int ( *C[] )[N][N] = { &A, &B }; 

那就是指向二维数组的指针数组。

例如

 #include <stdio.h> #define N 5 void output( int ( *a )[N][N] ) { for ( size_t i = 0; i < N; i++ ) { for ( size_t j = 0; j < N; j++ ) printf( "%2d ", ( *a )[i][j] ); printf( "\n" ); } } int main( void ) { int A[N][N] = { { 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 } }; int B[N][N] = { { 25, 24, 23, 22, 21 }, { 20, 19, 18, 17, 16 }, { 15, 14, 13, 12, 11 }, { 10, 9, 8, 7, 6 }, { 5, 4, 3, 2, 1 } }; /* typedef int ( *T )[N][N]; TC[] = { &A, &B }; */ int ( *C[] )[N][N] = { &A, &B }; output( C[0] ); printf( "\n" ); output( C[1] ); printf( "\n" ); } 

程序输出是

  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 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 

或者像

 int A[5][5]; int B[5][5]; int ( *C[] )[N] = { A, B }; 

也就是指向二维数组的第一个元素的指针数组。

例如

 #include <stdio.h> #define N 5 void output( int ( *a )[N] ) { for ( size_t i = 0; i < N; i++ ) { for ( size_t j = 0; j < N; j++ ) printf( "%2d ", a[i][j] ); printf( "\n" ); } } int main( void ) { int A[N][N] = { { 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 } }; int B[N][N] = { { 25, 24, 23, 22, 21 }, { 20, 19, 18, 17, 16 }, { 15, 14, 13, 12, 11 }, { 10, 9, 8, 7, 6 }, { 5, 4, 3, 2, 1 } }; /* typedef int ( *T )[N]; TC[] = { A, B }; */ int ( *C[] )[N] = { A, B }; output( C[0] ); printf( "\n" ); output( C[1] ); printf( "\n" ); } 

程序输出与上面相同

  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 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 

取决于你将如何使用第三个数组。

使用typedefs(在演示程序中显示为注释)可以简化数组的定义。

至于这个声明

 int*** C = {&A, &B}; 

然后在左边声明一个types为int ***的指针,它是一个标量对象,而在右边则有一个具有不同typesint ( * )[N][N]的初始值设定项列表。

所以编译器发出一个消息。

我非常相信使用typedef

 #define SIZE 5 typedef int OneD[SIZE]; // OneD is a one-dimensional array of ints typedef OneD TwoD[SIZE]; // TwoD is a one-dimensional array of OneD's // So it's a two-dimensional array of ints! TwoD a; TwoD b; TwoD *c[] = { &a, &b, 0 }; // c is a one-dimensional array of pointers to TwoD's // That does NOT make it a three-dimensional array! int main() { for (int i = 0; c[i] != 0; ++i) { // Test contents of c to not go to far! for (int j = 0; j < SIZE; ++j) { for (int k = 0; k < SIZE; ++k) { // c[i][j][k] = 0; // Error! This proves it's not a 3D array! (*c[i])[j][k] = 0; // You need to dereference the entry in c first } // for } // for } // for return 0; } // main()