指向数组/指针数组的C指针消歧

C pointer to array/array of pointers disambiguation

以下声明之间的区别是什么?

1
2
3
int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

理解更复杂声明的一般规则是什么?


1
2
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

第三个和第一个一样。

一般规则是运算符优先级。当函数指针出现在图片中时,它会变得更加复杂。


按照K&R的建议,使用CDECL程序。

1
2
3
4
5
6
7
8
9
$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

它的工作方式也是相反的。

1
2
cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )


我不知道它是否有正式的名字,但我称它为左右thingy(tm)。

从变量开始,然后向右,向左,向右…等等。

1
int* arr1[8];

arr1是指向整数的8个指针的数组。

1
int (*arr2)[8];

arr2是一个指向8个整数数组的指针(括号阻塞了左右)。

1
int *(arr3[8]);

arr3是指向整数的8个指针的数组。

这可以帮助您处理复杂的声明。


1
2
3
4
5
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5


最后两个问题的答案也可以从C中的黄金法则中扣除:

Declaration follows use.

int (*arr2)[8];

如果你不引用arr2怎么办?你得到一个8个整数的数组。

int *(arr3[8]);

如果你从arr3中取一个元素会发生什么?你得到一个指向整数的指针。

这也有助于处理指向函数的指针。以Sigjuice为例:

float *(*x)(void )

当你取消引用x时会发生什么?您得到了一个可以不带参数调用的函数。当你叫它时会发生什么?它将返回一个指向浮点的指针。

不过,运算符优先级总是很棘手的。但是,使用括号实际上也会令人困惑,因为声明后面跟着使用。至少在我看来,arr2看起来像一个8个指向int的指针数组,但实际上却是相反的。只是需要一些习惯。如果你问我的话,有足够的理由总是在这些声明中添加注释:)

编辑:实例

顺便说一下,我刚刚偶然发现了以下情况:一个具有静态矩阵的函数,它使用指针算法来查看行指针是否越界。例子:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i < 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]
"
, i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

输出:

1
2
3
4
5
6
0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

注意border的值永远不会改变,所以编译器可以把它优化掉。这与您最初可能想要使用的不同:const int (*border)[3]:它声明border作为指向3个整数数组的指针,只要变量存在,该数组就不会改变值。但是,该指针可以随时指向任何其他此类数组。相反,我们希望对参数使用这种行为(因为这个函数不会改变这些整数中的任何一个)。使用后声明。

(P.S.:请随时改进此样品!)


1
2
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];


根据经验,右一元运算符(如[]()等)优先于左一元运算符。因此,int *(*ptr)()[];将是一个指针,指向一个返回指向int的指针数组的函数(从括号中取出后,尽快获得正确的运算符)。


下面是一个有趣的网站,它解释了如何阅读C语言中的复杂类型:网址:http://www.unixwiz.net/techttips/reading-cdecl.html


以下是我的解释方法:

1
int *something[n];

note on precedence: array subscript operator ('[ ]') has higher priority than
dereference operator ('*').

因此,这里我们将把"*"前面的"[]"应用于,使语句等效于:

1
int *(something[i]);

note on how a declaration makes sense: int num means (num) is an (int), int *ptr or int (*ptr) means, (value at ptr) is
an (int), which makes ptr a pointer to int.

这可以理解为,((某物的第i个索引处的值)是一个整数。所以,(某物的第i个索引处的值)是一个(整型指针),它使某物成为整型指针数组。

在第二个阶段,

1
int (*something)[n];

要理解这一说法,您必须熟悉这一事实:

note on pointer representation of array: somethingElse[i] is equivalent to *(somethingElse + i)

所以,用(*something)替换somethingle,我们得到*(*something+i),根据声明,它是一个整数。所以,(*某物)给了我们一个数组,它使得某物等价于(指向数组的指针)。


我想我们可以用简单的规则……

1
2
example int * (*ptr)()[];
start from ptr

"ptr是指向的指针"往右边走,它的","现在往左边走,它的a"("出来,走右边"()"所以指向不带参数的函数"go left"并返回指针"go right"to整数的"左移"数组


我想第二个声明对很多人来说都很困惑。这是一个很容易理解的方法。

让我们有一个整数数组,即int B[8]

我们还有一个变量a指向b,现在a的值是b,即(*A) == B。因此,指向一个整数数组。在你的问题中,arr类似于a。

同样,在int* (*C) [8]中,c是指向指向整数指针数组的指针。


在指向整数的指针中,如果指针递增,则转到下一个整数。

在指针数组中,如果指针递增,则跳到下一个数组