让人眼花缭乱的`C`语言声明

只有编译器才会喜欢的语法

Kemighan 和 Richie 承认,“C 语言声明的语法有时会带来严重的问题。”(K&R,第二版,第122页)。C语言声明的语法对于编译器(或编译器设计者)的处理来说并不是什么大不了的事。但对于一般的程序员,它却会成为障碍。语言的设计者也是人,他们也会犯错误。例如,Ada 的语言参考手册在最后的附录中所附的Ada 语法手册中,有一处存在歧义。对于编程语言的语法来说,歧义是非常忌讳的,因为它使编译器设计者的工作严重复杂化。但C语言声明的语法确实非常可怕,渗透于整个语言使用的方方面面。毫不夸张地说,正是由于在组合类型方面的笨拙行为,C语言被显著且毫无必要地复杂化了。

C语言的声明模型之所以如此晦涩,这里有几个原因。六十年代晚期,人们在设计C语言的这部分内容时,“类型模型(type model)”这个概念对于当时的编程语言理论而言尚属陌生。BCPL 语言(C语言的祖先)几乎没有类型,它把二进制字作为惟一的数据类型,所以C语言先天有缺。然后出现了一种C语言设计哲学,要求对象的声明形式与它的使用形式尽可能相似。

“声明的形式和使用的形式相似”这种用法可能是C语言的独创,其他语言并没有采用这种方法。而且,”声明的形式和使用的形式相似”即使在当时也不像是一个特别好的主意。把两种截然不同的东西做成一个样子真的有什么重要意义吗?贝尔实验室的学究们也承认此批评有理,但他们坚决死扛原来的决定,至今依然。

什么是C语言声明?

C 语言的声明是描述变量或函数的类型的语句。在 C 语言中,声明可以包括基本类型、指针、数组、结构体、联合体和函数等。声明的复杂性取决于它所描述的实体的复杂性,以及使用的修饰符和符号。

以下是一些常见的 C 语言声明元素和一些复杂声明的解释:

1. 基本类型声明:

1
int x;

这是一个简单的声明,表示 x 是一个整数。

2. 指针声明:

1
int *ptr;

这表示 ptr 是一个指向整数的指针。

3. 数组声明:

1
int arr[5];

这表示 arr 是一个包含 5 个整数的数组。

4. 结构体声明:

1
2
3
4
struct Point {
int x;
int y;
};

这声明了一个名为 Point 的结构体,其中包含两个整数成员 xy

5. 复杂结构体声明:

1
2
3
4
5
struct Complex {
int a;
float b;
struct Point p;
};

这声明了一个名为 Complex 的结构体,包含整数 a、浮点数 b 和另一个结构体 Point

6. 联合体声明:

1
2
3
4
5
union Data {
int i;
float f;
char c;
};

这声明了一个名为 Data 的联合体,可以存储整数、浮点数或字符,但只能同时存储其中一种类型。

7. 函数声明:

1
int add(int a, int b);

这声明了一个名为 add 的函数,接受两个整数参数并返回一个整数。

8. 指向函数的指针声明:

1
int (*funcPtr)(int, float);

这表示 funcPtr 是一个指向接受一个整数和一个浮点数作为参数并返回整数的函数的指针。

9. 复杂的数组声明:

1
int (*arrPtr)[5];

这表示 arrPtr 是一个指针,指向包含 5 个整数的数组。

10. 指向数组的指针声明:

1
int (*ptrToArray)[10];

这表示 ptrToArray 是一个指针,指向包含 10 个整数的数组。

11. 复杂的指针声明:

1
int (*(*ptrToFunc)(int))[5];

这表示 ptrToFunc 是一个指针,指向一个函数,该函数接受一个整数作为参数并返回一个指针,该指针指向包含 5 个整数的数组。

12. 指向结构体的指针声明:

1
struct Point *ptrToStruct;

这表示 ptrToStruct 是一个指针,指向结构体 Point

13. 指向指针的指针声明:

1
int **ptrToPtr;

这表示 ptrToPtr 是一个指针,指向一个指针,该指针指向整数。

14. 函数指针数组声明:

1
int (*funcPtrArr[3])(char, double);

这表示 funcPtrArr 是一个包含 3 个函数指针的数组,每个函数接受一个字符和一个双精度浮点数作为参数,并返回整数。

15. 指向数组的指针数组声明:

1
int (*ptrToArrArr[2])[3];

这表示 ptrToArrArr 是一个包含 2 个指针的数组,每个指针指向包含 3 个整数的数组。

16. 结构体数组声明:

1
struct Point myStructArr[5];

这声明了一个包含 5 个结构体 Point 的数组。

17. 指向结构体数组的指针声明:

1
struct Point (*ptrToStructArr)[5];

这表示 ptrToStructArr 是一个指针,指向包含 5 个结构体 Point 的数组。

这些例子展示了 C 语言声明的一些基本和复杂的方面。理解 C 语言声明的关键在于从内到外阅读,逐步解释每个声明中的符号和修饰符。

复(sao)杂(qi)的声明

当涉及复杂的 C 语言声明时,很容易使人感到迷惑。以下是 20 个以上可能令人迷惑的 C 语言声明的例子:

  1. 指向函数的指针数组:

    1
    int (*funcPtrArr[5])(float);
  2. 指向包含函数指针的数组的指针:

    1
    int (*(*arrPtr)[5])(float);
  3. 函数指针的数组:

    1
    int (*arrFuncPtr[3])(char, double);
  4. 包含指向数组的指针的结构体:

    1
    2
    3
    struct {
    int (*ptrToArray)[10];
    } myStruct;
  5. 指向包含指向函数的指针的结构体的指针:

    1
    2
    3
    struct {
    int (*(*funcPtrStruct)[3])(int);
    } *ptrToStruct;
  6. 函数指针数组的指针:

    1
    int (*(*ptrToFuncPtrArr)[5])(float);
  7. 包含指向函数指针数组的结构体:

    1
    2
    3
    struct {
    int (*funcPtrArr[4])(char);
    } myStruct;
  8. 指向包含指向函数指针数组的结构体的指针:

    1
    2
    3
    struct {
    int (*(*ptrToFuncPtrArrStruct))[4](char);
    } *ptrToStruct;
  9. 函数指针的指针数组:

    1
    int (*(*ptrToFuncPtrArr)[2])(float);
  10. 函数指针数组的指针数组:

    1
    int (*(*ptrToFuncPtrArr)[2])[3](float);
  11. 多维数组的指针:

    1
    int (*ptrToMultiDimArr)[3][4];
  12. 指向包含函数指针数组的结构体数组的指针:

    1
    2
    3
    struct {
    int (*funcPtrArr[2])(char);
    } myStructArr[3], (*ptrToStructArr)[3];
  13. 指向函数的指针的数组的指针:

    1
    int (*(*ptrToFuncPtrArr)[2])(float);
  14. 包含指向数组的指针的联合:

    1
    2
    3
    union {
    int (*ptrToArray)[5];
    } myUnion;
  15. 指向包含函数指针的结构体的数组的指针:

    1
    2
    3
    struct {
    int (*funcPtr)(double);
    } myStructArr[4], (*ptrToStructArr)[4];
  16. 指向包含指向函数指针的结构体的数组的指针:

    1
    2
    3
    struct {
    int (*(*ptrToFuncPtrStruct))[2](float);
    } myStructArr[3], (*ptrToStructArr)[3];
  17. 函数指针数组的指针的数组:

    1
    int (*(*ptrToFuncPtrArrArr)[2])[3](float);
  18. 指向包含函数指针数组的结构体数组的指针的数组的指针:

    1
    2
    3
    struct {
    int (*funcPtrArr[2])(char);
    } myStructArr[3], (*ptrToStructArrArr)[3];
  19. 包含指向指针数组的结构体数组:

    1
    2
    3
    struct {
    int (*ptrArr[3])[5];
    } myStructArr[2];
  20. 指向包含指向指针数组的结构体数组的指针:

    1
    2
    3
    struct {
    int (*(*ptrToPtrArrStruct))[3][5];
    } myStructArr[2], (*ptrToStructArr)[2];

这些声明可能看起来复杂,但通过仔细分析可以理解它们。重要的是逐步解读每个声明中的部分,理解声明的嵌套结构。