C语言声明的优先级规则

  • 声明从它的名字开始读取,然后按照优先级顺序依次读取

  • 优先级从高到底依次是:

    • 声明中被括号括起来的那部分
    • 前缀操作符:星号 * 表示“指向…的指针”
    • 后缀操作符:括号()表示这是一个函数,而方括号[]表示这是一个数组
  • 如果const和 volatile 关键字的后面紧跟类型说明符(int、long等)它作用域是类型说明符,其他情况下,const 和 volatile关键字作用于它左面紧邻的指针星后。

通过上述规则分析下面C语言声明:

1
2
3
char * const *(*next)();
//next是一个指针,它指向一个函数,该函数返回一个另一个指针,
//该指针指向一个类型为char 类型的常量指针

typedef作用

在C语言中,typedef关键字用于定义新的数据类型别名。它的作用有以下几个方面:

  1. 简化复杂的数据类型名称:typedef可以用来为复杂的数据类型定义一个简单的别名,使得代码更易读、易懂。例如,可以使用typedef为指针类型定义一个简单的别名,并没有创建新的类型,使得代码中使用该指针类型的地方更加清晰明了。

  2. 提高代码的可维护性:通过typedef定义别名,可以使得代码中使用的数据类型更加具有可读性和可维护性。如果需要修改某个数据类型的名称,只需要修改typedef的定义,而不需要修改所有使用该数据类型的地方。

  3. 提高代码的可移植性:通过typedef定义别名,可以使得代码更加具有可移植性。在不同的平台或编译器上,可能存在不同的数据类型名称,使用typedef定义别名可以屏蔽这种差异,使得代码在不同的平台上都能够正常编译和运行。

  4. 增加代码的可读性:通过typedef定义别名,可以使得代码更加易读。通过为数据类型定义一个有意义的别名,可以增加代码的可读性,使得代码更加易于理解和维护。

典型的例子

 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
40
41
42
43
44
//signal()原型的声明,signal是一个函数,它返回一个函数指针,
//这个函数指针指向的函数接受一个int参数并返回void。
void (*signal(int sig, void (*func)(int)))(int)

//可通过typedef做如下改变
typedef void (*sighandler_t)(int);   //可以使用sighandler_t直接代替void (*)(int)
sighandler_t signal(int signum, sighandler_t handler);

//定义结构体别名:
typedef struct {
    int x;
    int y;
} Point;

// 可以使用Point直接代替struct Point
Point p;
p.x = 10;
p.y = 20;
定义指针类型别名:
typedef int* IntPtr;

// 可以使用IntPtr直接代替int*
IntPtr p;
int x = 10;
p = &x;

//定义函数指针类型别名:
typedef int (*MathFunc)(int, int);

// 可以使用MathFunc直接代替int (*)(int, int)
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

MathFunc func;
func = add;
int result = func(5, 3); // 调用add函数,结果为8

func = subtract;
result = func(5, 3); // 调用subtract函数,结果为2

C语言存在多种名字空间:

  • 标签名(label name)

  • 标签(tag):这个名字空间用于所有的结构体、枚举和联合

  • 成员名:每个结构或联合都有自身的名字空间

  • 其他

在同一个名字空间里,任何名字必须具有唯一性,但在不同的名字空间里可以存在相同的名字。由于每个结构或者联合具有自己的名字空间,所以同一个名字可以出现在许多不同的结构内。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
typdedef struct my_tag{int i;}mytype;
struct my_tag var1;
mytype var2;
//这个typedef声明引入了mytype这个名字作为"struct my_tag{int i;}"的简写方式。
//但它同时引入了结构标签my_tag,在他前面加个关键字struct可以表示同样的意思。

typedef struct fruit {int weight;}fruit; //语句1
struct veg{int weight;}veg;    //语句2
//语句1声明了结构标签fruit和typedef声明的结构类型fruit;实际效果如下
struct fruit mandarin;
fruit mandarin;
//语句2声明了结构标签veg和变量veg,只有结构标签能够在以后的声明中使用,如
struct veg potato;
//如果试图使用veg cabbage这样的声明,将是一个错误。这有点类似下面的写法:
int i;
i j;

typedef、宏定义替换之间的区别

可以把typedef看成是一种“封装”类型—-在声明之后不能再往里面增加别的东西。它和宏的区别体现在两个方面

  • 可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
1
2
3
4
5
#define peach int 
unsigned peach i;  //没问题

typedef int peach;
unsigned peach i;  //语法错误
  • 连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型无法保证
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#define int_ptr int *
int_ptr chalk, cheese;
//经过宏扩展

int *chalk, cheese;
chalk是一个指针,cheese是一个整型。

typedef int* int_ptr;
int_ptr chalk,cheese;
chalk,cheese都为整型指针

枚举类型

enum sizes { small = 7, medium, large = 10, humungous};

缺省情况下,整型值是从零开始。如果对列表中的某个标识符进行了赋值,那么紧接其后的那个标识符的值就比所赋的值大1,然后类推

枚举中的成员可以当做宏一样直接使用,相比宏定义枚举具有一个优点:#define定义的名字一般在编译时被丢弃,而枚举名字则通常一直在调试器中可见,可以在调试代码时使用它们。