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

Vir*_*dya 39 c arrays

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

int A[5][5];
int B[5][5];
int*** C = {&A, &B};
Run Code Online (Sandbox Code Playgroud)

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

警告:从不兼容的指针类型初始化[默认启用]

如果我运行代码,它将引发分段错误.不过,如果我动态分配AB,它工作得很好.为什么是这样?

dbu*_*ush 25

如果你想的声明C适合的现有的宣言AB你需要做的是这样的:

int A[5][5];
int B[5][5];
int (*C[])[5][5] = {&A, &B};
Run Code Online (Sandbox Code Playgroud)

类型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};
Run Code Online (Sandbox Code Playgroud)

你也可以使用Vlad来自莫斯科的语法来做到这一点:

int A[5][5];
int B[5][5];
int (*C[])[5] = {A, B};
Run Code Online (Sandbox Code Playgroud)

这个声明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};
Run Code Online (Sandbox Code Playgroud)

  • 对 !但是如果可能的话,我不会用10英尺的杆子触碰它;) (5认同)

Lun*_*din 19

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

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

另外,作为经验法则:每当您发现自己使用超过2级间接时,很可能意味着您的程序设计存在根本缺陷,需要从头开始重新制作.


要正确执行此操作,您必须执行以下操作:

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

int A[5][5];
int (*ptr)[5][5] = &A;
Run Code Online (Sandbox Code Playgroud)

如果你想要一个数组指针数组,它将是类型int(*[])[5][5].例:

int A[5][5];
int B[5][5];
int (*arr[2])[5][5] = {&A, &B};
Run Code Online (Sandbox Code Playgroud)

正如你所知,这段代码看起来不必要复杂 - 而且确实如此.访问单个项目会很麻烦,因为您必须输入(*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;
Run Code Online (Sandbox Code Playgroud)

但是,这仍然是一些复杂的代码.完全考虑不同的设计!例子:

  • 制作3D阵列.
  • 创建包含2D数组的结构,然后创建此类结构的数组.


Joh*_*ode 14

这条线路有很多问题

int*** C = {&A, &B};
Run Code Online (Sandbox Code Playgroud)

你宣布一个单一的指针C,但是你告诉它指向多个对象; 这是行不通的.您需要做的是声明C为指向这些数组的指针数组.

类型两者的&A&Bint (*)[5][5],或"指针5元素的5个元素的数组的数组int"; 因此,C的类型需要是"指向5元素阵列的5元素数组的指针数组int",或者

int (*C[2])[5][5] = { &A, &B };
Run Code Online (Sandbox Code Playgroud)

读作为

      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
Run Code Online (Sandbox Code Playgroud)

呸.这真是太可恶了.如果你想访问其中一个元素AB通过C以下元素,它会变得更加丑陋:

int x = (*C[0])[i][j]; // x = A[i][j]
int y = (*C[1])[i][j]; // y = B[i][j]
Run Code Online (Sandbox Code Playgroud)

C[i]在我们可以索引到它所指向的数组之前,我们必须显式地取消引用,并且因为下标运算符的[]优先级高于一元运算*符,所以我们需要*C[0]在parens中进行分组.

我们可以稍微清理一下.除非它是sizeof或一元运算&符的操作数(或者是用于初始化声明中的另一个数组的字符串文字),否则类型为" -element array of " 的表达式将被转换("decay")为表达式输入"指向",表达式的值将是数组第一个元素的地址. NTT

表达式AB类型int [5][5],或"5元素数组的5元素数组int".根据上面的规则,两个表达式"衰减"到"指向5元素数组的指针"类型的表达式int,或int (*)[5].如果我们用AB而不是&A和初始化数组&B,那么我们需要一个指向5元素数组的指针数组int,或者

int (*C[2])[5] = { A, B };
Run Code Online (Sandbox Code Playgroud)

好吧,那仍然是非常刺眼,但这很干净,因为没有typedef会得到它.

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

请记住,数组下标操作a[i]定义*(a + i); 也就是说,给定一个基地址a,从该地址偏移i 元素(而不是字节)1并取消引用结果.这意味着

*a == *(a + 0) == a[0]
Run Code Online (Sandbox Code Playgroud)

从而,

*C[i] == *(C[i] + 0) == C[i][0]
Run Code Online (Sandbox Code Playgroud)

把这一切放在一起:

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
Run Code Online (Sandbox Code Playgroud)

我们可以索引C 它好像是一个3D数组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]   
Run Code Online (Sandbox Code Playgroud)

需要注意的是A,&A,A[0],&A[0],和&A[0][0]所有产生相同的(数组的地址和数组的第一个元件的地址总是相同的),但类型是不同的,如表中所示的上方.


  1. 指针算法考虑指向类型的大小; 如果p包含一个int对象的地址,则p+1产生下一个int对象的地址,该地址可能是2到4个字节.


hac*_*cks 9

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.
Run Code Online (Sandbox Code Playgroud)

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

int a2[][5] = {{0}};
int **p2 = a2;
Run Code Online (Sandbox Code Playgroud)

然后会出现一个关于不兼容指针分配的警告,然后他们会想:"我的天啊!为什么这个阵列变成了Harvey Dent?".

有些人甚至领先一步

int a3[][5][10] = {{{0}}};
int ***p3 = a3;             // "?"
Run Code Online (Sandbox Code Playgroud)

然后Riddler来到他们的数组指针等效的噩梦.

在此输入图像描述

永远记住数组不是指针,反之亦然.数组是数据类型,指针是另一种数据类型(不是数组类型).几年前在C-FAQ中已经解决了这个问题:

说数组和指针是"等价的"意味着它们既不相同也不可互换.这意味着定义了数组和指针算法,以便可以方便地使用指针来访问数组或模拟数组.换句话说,正如Wayne Throop所说,它是"指针算术和数组索引[在] C中相同,指针和数组是不同的." )

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

  • 数组不是指针.指针不是数组.
  • 当在表达式中使用时,数组被转换为指向其第一个元素的指针,除非是sizeof&操作符的操作数.
  • 这是指针算术数组索引是相同的.
  • 指针和数组是不同的.
  • 我说过"指针不是数组,反之亦然".

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

int a1[] = {1,2,3,4,5};
int *p1 = a1;
Run Code Online (Sandbox Code Playgroud)

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

int a2[][5] = {{0}};
int **p2 = a2;
Run Code Online (Sandbox Code Playgroud)

a2是一个数组,并在int **p2 = a2;其中衰减指向其第一个元素.它的元素是类型int[5](2D数组是1D数组的数组),因此指向其第一个元素的指针将是类型int(*)[5](指向数组的指针),它与类型不兼容int **.它应该是

int (*p2)[5] = a2;
Run Code Online (Sandbox Code Playgroud)

同样的

int a3[][5][10] = {{{0}}};
int ***p3 = a3;
Run Code Online (Sandbox Code Playgroud)

元素a3的类型是int [5][10]和指针的第一个元素是类型的int (*)[5][10],但p3就是int ***类型,所以让他们兼容的,它应该是

int (*p3)[5][10] = a3;
Run Code Online (Sandbox Code Playgroud)

现在来看你的片段

int A[5][5];
int B[5][5];
int*** C = {&A, &B};
Run Code Online (Sandbox Code Playgroud)

&A并且&B是类型int(*)[5][5].C是类型int***,它不是一个数组.既然你想C保留两个数组的地址AB,你需要声明C两个数组int(*)[5][5]类型的元素.这应该是作为

int (*C[2])[5][5] = {&A, &B};
Run Code Online (Sandbox Code Playgroud)

但是,如果我动态分配A和B,它就可以正常工作.为什么是这样?

在这种情况下,你必须声明ABas int **.在这种情况下,两者都是指针,而不是数组.C是类型的int ***,因此它可以保存int**类型数据的地址.请注意,在这种情况下,声明int*** C = {&A, &B};应该是

  int*** C = &A;
Run Code Online (Sandbox Code Playgroud)

在这种情况下int*** C = {&A, &B};,程序的行为可能是未定义的或实现定义的.

C11:5.1.1.3(P1):

如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合要求的实现应生成至少一条诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实现 - 定义

阅读此帖以获得进一步说明.

  • 在downvote之后评论将被赞赏. (3认同)

Mad*_*ist 8

数组不是一回事多维指针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;
}
Run Code Online (Sandbox Code Playgroud)

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

int **A = malloc(5 * sizeof(int *));
int i;
for(i = 0; i < 5; i++) {
    A[i] = malloc(5 * sizeof(int));
}
Run Code Online (Sandbox Code Playgroud)

  • 数组不是***,而是指针,多维或其他.而且,数组的名称指定数组,而不是任何指针.重要的是不要将数据值*衰减*与大多数(但不是全部)上下文中的指针混淆. (3认同)
  • 另外,给定`int A [5] [5]`,当`A`衰减到指针时,该指针的类型是`int(*)[5]`,而不是`int*`. (3认同)
  • `(实际上是int*[5])`不正确.这是一个指针数组,而不是指向数组的指针.您计算偏移量是不正确的.`A [x + 5*y]`与'A [x] [y]`的结果不相同 (2认同)
  • 这个答案没有回答@JohnBollinger和@ 2501指出的问题和误导.另外:*如果`A`被声明为`int A [5] [5]`,那么`A`通常意味着第一个元素的地址*:不.它总是意味着`A`是一个由*`int`s*组成的5个数组的*数组.没有更多,没有更少. (2认同)

agh*_*ast 7

您对数组和指针的等价性感到困惑.

当你声明一个数组时A[5][5],因为你已经声明了这两个维度,C将连续为25个对象分配内存.也就是说,内存将按如下方式分配:

A00, A01, ... A04, A10, A11, ..., A14, A20, ..., A24, ...
Run Code Online (Sandbox Code Playgroud)

结果对象A是指向此内存块开头的指针.它是类型int *,而不是int **.

如果需要指向数组的指针向量,则需要将变量声明为:

int   *A[5], *B[5];
Run Code Online (Sandbox Code Playgroud)

那会给你:

A0, A1, A2, A3, A4
Run Code Online (Sandbox Code Playgroud)

所有类型int*,你必须填写使用malloc()或任何.

或者,您可以声明Cint **C.

  • 数组和指针之间没有等价.它们是相关联的,但并非完全相同.另外,给定`int A [5] [5]`,`A`衰变的指针类型是`int(*)[5]`,而不是`int*`. (4认同)

Joh*_*ger 7

尽管数组和指针紧密相关,但它们并不完全相同.人们有时会对此感到困惑,因为在大多数情况下,数组值会衰减为指针,并且因为数组表示法可以在函数原型中用于声明实际上是指针的参数.此外,许多人认为数组索引表示法实际上执行指针算法和解除引用的组合,因此它对指针值和数组值同样有效(因为数组值衰减到指针).

鉴于声明

int A[5][5];
Run Code Online (Sandbox Code Playgroud)

变量A指定由五个五个数组组成的数组int.这衰减衰落到一个类型的指针int (*)[5]- 即指向数组5的指针int.另一方面,指向整个多维数组的int (*)[5][5]指针具有类型(指向5个5个数组的数组的指针int),它int ***与指向指向指针的指针完全不同int.如果你想声明一个指向多维数组的指针,那么你可以这样做:

int A[5][5];
int B[5][5];
int (*C)[5][5] = &A;
Run Code Online (Sandbox Code Playgroud)

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

int (*D[2])[5][5] = { &A, &B };
Run Code Online (Sandbox Code Playgroud)

添加:

这些区别以各种方式发挥作用,一些更重要的是数组值不会衰减到指针的上下文,以及与之相关的上下文.其中最重要的一个是当值是运算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)
Run Code Online (Sandbox Code Playgroud)

此外,这些关系表达式可能(但不能保证)评估为1:

sizeof(C)       == sizeof(void *)
sizeof(D)       == 2 * sizeof(void *)
Run Code Online (Sandbox Code Playgroud)

这是数组索引如何工作的基础,也是了解何时分配内存所必需的.