我试图将两个固定大小的数组分配给一个指向它们的指针数组,但编译器发出警告,我不明白为什么。
int A[5][5];
int B[5][5];
int*** C = {&A, &B};
这段代码编译时出现以下警告:
警告:不兼容的指针类型初始化[默认启用]
如果我运行这段代码,它会引发一个分段错误。但是,如果我动态分配A
和B
,它就可以正常工作。为什么呢?
我试图将两个固定大小的数组分配给一个指向它们的指针数组,但编译器发出警告,我不明白为什么。
int A[5][5];
int B[5][5];
int*** C = {&A, &B};
这段代码编译时出现以下警告:
警告:不兼容的指针类型初始化[默认启用]
如果我运行这段代码,它会引发一个分段错误。但是,如果我动态分配A
和B
,它就可以正常工作。为什么呢?
如果你想声明一个符合 A
和 B
的现有声明的 C
,你需要这样做:
int A[5][5];
int B[5][5];
int (*C[])[5][5] = {&A, &B};
变量C
的类型被称为“C
是一个指向包含int [5][5]
数组的指针数组”。由于不能直接赋整个数组,所以需要分配一个指向该数组的指针。
通过这个声明,(*C[0])[1][2]
访问的是与A[1][2]
相同的内存地址。
如果你想要像C[0][1][2]
这样更清晰的语法,那么你需要像其他人所说的那样动态地分配内存:
int **A;
int **B;
// allocate memory for A and each A[i]
// allocate memory for B and each B[i]
int **C[] = {A, B};
您也可以使用莫斯科的 Vlad 建议的语法来完成这个操作:
int A[5][5];
int B[5][5];
int (*C[])[5] = {A, B};
这个声明中的C
应该被理解为"C
是指向int [5]
数组的指针数组"。在这种情况下,C
的每个数组元素都是类型为int (*)[5]
的数组,而类型为int [5][5]
的数组可以转换为此类型。
现在,您可以使用C[0][1][2]
来访问与A[1][2]
相同的内存位置。
这种逻辑也可以扩展到更高维度:
int A[5][5][3];
int B[5][5][3];
int (*C[])[5][3] = {A, B};
不幸的是,市面上有很多烂书/教程/老师会教你错误的东西......
忘记指向指针,它们与数组没有任何关系。就这样。
还有一个经验法则:每当你发现自己使用超过2级间接引用时,这很可能意味着你的程序设计根本有缺陷,需要从头开始重做。
要正确地做到这一点,你应该这样做:
指向数组 int [5][5]
的指针被称为数组指针,并声明为 int(*)[5][5]
。例如:
int A[5][5];
int (*ptr)[5][5] = &A;
如果你想要一个指向数组的数组指针,那么它的类型应该是int(*[])[5][5]
。例如:
int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};
你可以看出这段代码看起来过于复杂 - 实际上它确实如此。访问单个元素会很麻烦,因为你需要输入 (*arr[x])[y][z]
。意思是:“在数组指针的数组中,取编号为x的数组指针,获取它所指向的内容 - 这是一个2D数组 - 然后再取该数组中索引为[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;
然而,这仍然是相当复杂的代码。考虑完全不同的设计!例如:
这行代码存在很多问题。
int*** C = {&A, &B};
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]
分组放在括号中。sizeof
或一元 &
运算符的操作数(或者是用于声明中初始化另一个数组的字符串字面量),类型为 "N
元素数组 of T
" 的表达式将被转换("衰减")为类型为 "指向 T
的指针" 的表达式,并且表达式的值将是数组的第一个元素的地址。A
和 B
的类型为 int [5][5]
,或者是 "5 元素数组 of 5 元素数组 of int
"。根据上述规则,两个表达式都会 "衰减" 为类型为 "指向 5 元素数组 of int
的指针" 的表达式,即 int (*)[5]
。如果我们使用 A
和 B
来初始化数组,而不是使用 &A
和 &B
,那么我们需要一个指向 5 元素数组 of int
的指针数组。int (*C[2])[5] = { A, B };
*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
元素组成的3D数组,这比使用(*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
、&A
、A[0]
、&A[0]
和&A[0][0]
都产生相同的值(数组的地址和数组第一个元素的地址总是相同的),但它们的类型是不同的,如上表所示。p
包含一个int
对象的地址,则p+1
将产生下一个int
对象的地址,该地址可能相距2到4个字节。C
以便可以像三维数组一样进行索引的建议。 - John Bollingerint 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;
然后出现了一个关于不兼容指针赋值的警告,然后他们想:“哦天啊!为什么这个数组变成了Harvey Dent?”
有些人甚至更进一步。
int a3[][5][10] = {{{0}}};
int ***p3 = a3; // "?"
然后Riddler就会涉及到他们关于数组指针等价性的噩梦。
请记住,数组不是指针,反之亦然。数组是一种数据类型,而指针是另一种数据类型(它不是数组类型)。几年前就在C-FAQ中讨论过这个问题:现在,请始终记住一些重要的数组规则,以避免这种混淆:说数组和指针是“等价的”并不意味着它们是相同的,甚至可以互换。它的意思是数组和指针算术被定义为指针可以方便地用于访问数组或模拟数组。换句话说,正如Wayne Throop所说的那样,在C语言中,“指针算术和数组索引是等价的,指针和数组是不同的。”
sizeof
和&
运算符的操作数外,数组会转换为指向其第一个元素的指针。现在你知道了这些规则,可以得出结论:
int a1[] = {1,2,3,4,5};
int *p1 = a1;
a1
是一个数组,在声明语句int *p1 = a1;
中,它被转换为指向其第一个元素的指针。它的元素类型是int
,那么指向其第一个元素的指针将是int *
类型,与p1
兼容。
在
int a2[][5] = {{0}};
int **p2 = a2;
a2
是一个数组,在int **p2 = a2;
中它会退化为指向其第一个元素的指针。它的元素是int[5]
类型(2D数组是1D数组的数组),所以它的第一个元素的指针类型应该是int(*)[5]
(指向数组的指针),与int **
类型不兼容。正确的写法应该是
int (*p2)[5] = a2;
int a3[][5][10] = {{{0}}};
int ***p3 = a3;
a3
的元素是int [5][10]
类型,指向其第一个元素的指针应该是int (*)[5][10]
类型,但是p3
是int ***
类型,为了使它们兼容,应该
int (*p3)[5][10] = a3;
int A[5][5];
int B[5][5];
int*** C = {&A, &B};
&A
和&B
的类型是int(*)[5][5]
。 C
的类型是int***
,它不是一个数组。由于您想让C
保存A
和B
数组的地址,因此需要将C
声明为包含两个int(*)[5][5]
类型元素的数组。应该这样做:
int (*C[2])[5][5] = {&A, &B};
int*** C = &A;
int*** C = {&A, &B};
的情况下,程序的行为可能是未定义的或实现定义的。A
声明为int A[5][5]
,则A
通常表示第一个元素的地址,即它被有效地解释为int *
(实际上是int *[5]
),而不是int **
。地址的计算恰好需要两个元素:A[x][y] = A + x + 5 * y
。这是一个方便的做法,可以执行A[x + 5 * y]
,但它并不使A
提升为多维缓冲区。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));
}
int A[5][5]
,当A
退化为指针时,该指针的类型是int (*)[5]
,而不是int *
。 - John Bollinger(actually int *[5])
不正确。这是指针数组而不是数组指针。您计算偏移量的方法不正确。A[x + 5 * y]
与 A[x][y]
不相同。 - 2501A
被声明为int A [5] [5]
,那么A
通常会意味着第一个元素的地址:不是的。它总是表示A
是5个数组中每个数组都包含有5个整数的5个数组的数组。 没有更多,也没有更少。 - haccks您可能会被数组和指针的等价性所困惑。
当您声明一个像A[5][5]
这样的数组时,因为您已经声明了两个维度,C语言将会连续地分配25个对象的内存。也就是说,内存将会按照以下方式分配:
A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ...
产生的对象A
是指向这块内存起始位置的指针。它的类型是int *
而不是int **
。
如果你想要一个指向数组的指针向量,你需要将变量声明为:
int *A[5], *B[5];
A0, A1, A2, A3, A4
所有类型为int*
的内容,您需要使用malloc()
或其他方法进行填充。
或者,您可以将C
声明为int **C
。
int **C
的问题在于它丢失了二维索引信息。无论如何,这是一个很好的答案。 - Mad Physicistint A[5][5]
,A
所衰减的指针类型是int (*)[5]
,而不是int *
。 - John BollingerA
是指向这个内存块开头的指针。它的类型是int *
,而不是int **
。” 这完全是错误的。对象A
是一个数组类型。在某些情况下,它将转换为指向其第一个元素的指针。A
的类型是int[5][5]
。转换后,它的类型为int (*)[5]
。再次强调:“如果您想要一个指向数组的指针向量,您需要将变量声明为:int *A [5],*B [5];
”:不是的。这将声明指针数组,而不是指向数组的指针向量。 - haccksint *A[5], *B[5];
为什么你说这些是指向数组的向量(在C语言中没有这个意思,但我们假设你的意思是数组),当它们显然不是? - 2501int A[5][5];
A
表示一个由五个包含五个int
元素的数组组成的数组。它会自动转换为指向由5个int
元素组成的数组的指针,即int (*)[5]
类型的指针。而指向整个多维数组的指针则具有不同的类型:int (*)[5][5]
(指向由5个包含5个int
元素的数组组成的数组的指针),与int***
(指向int
的指针,再指向指针,再指向指针)完全不同。如果你想声明一个指向这种多维数组的指针,可以像这样进行:int A[5][5];
int B[5][5];
int (*C)[5][5] = &A;
如果您想声明一个指向这些指针的数组,则可以这样做:
int (*D[2])[5][5] = { &A, &B };
新增:
这些区别在各种方式中发挥作用,其中一些更重要的是数组值不会在某些情况下衰减为指针,并与这些情况相关的上下文。最显著的一个情况是当一个值是sizeof
运算符的操作数时。根据以上声明,所有以下关系表达式求值结果均为1 (true):
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)
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];
T C[] = { &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];
T C[] = { 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
根据你要如何使用第三个数组,可能需要不同的翻译方式。
使用typedef(在演示程序中以注释形式显示)可以简化数组的定义。
至于这个声明:
int*** C = {&A, &B};
然后在左侧声明了一个类型为int ***
的指针,它是一个标量对象,而右侧有一个不同类型的初始化器列表,其类型为int ( * )[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 too 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()