【转】typedef的用法

内容与参考资料基本一致,稍微修改了一点参考资料上存在的错误

一、typedef的四种用法

1. 定义类型别名

char *a, *p;

typedef char* char_pointer;
char_pointer pa, pb;

通过定义类型别名在声明多个指针变量时就方便了很多,也减少了漏写星号*的风险。


2. typedef struct

这个实际上在C语言中比较常用到,在C语言中如果要声明一个struct对象必须使用struct [结构名][对象名]的语法来实现:

struct A {
    int i;
    int j;
};
struct A a;

使用typedef可以简化这一步骤直接使用[结构名][对象名]来声明:

typedef struct B {
    int i;
    int j;
}B;
B b;

不过在C++中不需要typedef也能直接使用结构名来声明对象。

在没有typedef的情况下末尾定义的是变量,有情况下则是类型别名。

struct Human{
    int age;
} bob; // bob是一个变量

typedef struct student{
    int age;
} Stu; // Stu是结构体student的别名

3. 定义和平台无关的数据类型

比如定义一个叫REAL的浮点类型,表示为目标平台上最高精度的类型:

typedef long double REAL;
// 如果不支持long double 改为:
typedef double REAL;
// 如果不支持 double 改为:
typedef float REAL;

也就是说在跨平台时遇到不支持的类型情况只需要修改typedef一处即可,不需要大量修改源码的其他地方。

标准库中广泛使用了这一技巧,比如size_t

4. 为复杂的声明顶一个简单别名

例如函数指针相关的

int *(*a[5])(int, char*); // 原声明

typedef int *(*fun_ptr)(int, char*);
fun_ptr a[5];

在原来的声明里逐步用别名替换一部分复杂声明,如此循环,
把带变量名的部分留到最后替换,得到的就是原声明的最简化版。

void (*b[10])(void(*)()); // 原声明

// 先替换右边括号
typedef void (*fun_ptr_param)();
// 再替换左边括号
typedef void (*fun_ptr)(fun_ptr_param);

// 原声明简化后
fun_ptr p[10];

参考的文章中在第四行的代码为:

typedef void (*pFunParam);

image

这实际上是有问题的,它不是函数别名而是void *类型的别名

二、如何理解复杂声明和定义

参考资料给了一个中的例子,但我其实感觉例子不是很好,而且在我自己电脑上尝试的时候会报错。需要使用::Q,所以这部分代码就不贴了。有兴趣的看参考资料原文吧。

在阅读Linux的内核代码时经常会遇到一些复杂的声明和定义,例如:

void * (* (*fp1) (int)) [10];
​
float (* (*fp2) (int, int, float)) (int);
​
typedef double (* (* (*fp3) ()) [10]) ();
fp3 a;
​
int (* (*fp4()) [10]) ();

要理解这些复杂的声明和定义,应该由浅而深,逐步突破。

下面先看一些简单的定义:

int a; //定义一个整型数
int *p; //定义一个指向整型数的指针
int **pp; //定义一个指向指针的指针,它指向的指针指向一个整型数

p = &a;   // p指向整数a所在的地址
pp = &p;  // pp指向指针p

如下所示,定义整数型数组的指针指向整数型数组。

int arr[10]; //定义一个包含10个整型数的数组
int (*pArr) [10]; //定义一个指向包含10个整型数数组的指针

pArr = &arr;

如下所示,包含指向函数的指针的数组,这些函数有整型参数和整型返回值。

int (*pfunc) (int); //定义一个指向函数的指针,被指向的函数有一个整型参数并返回整型值
int (*arr[10]) (int); //定义一个包含10个指针的数组,其中包含的指针指向函数,
					  //这些函数有一个整型参数并返回整型值

arr[0] = pfunc;

三、右左法则

当声明和定义逐渐复杂时,需要使用用于理解复杂定义的右左法则:

从变量名看起,先往右,再往左,碰到圆括号就调转阅读的方向;括号内分析完就跳出括号,还是先右后左的顺序。如此循环,直到分析完整个定义。

用分析int (*pfunc) (int)为例

  • 找到变量名pfunc,先往右是圆括号,调转方向,左边是一个*号,这说明pfunc是一个指针;
  • 然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*pfunc)是一个函数,所以pfunc是一个指向这类函数的指针,即函数指针,
  • 这类函数具有一个int类型的参数,返回值类型是int

同样的,对于int (*arr[10]) (int)

  • 找到变量名arr,先往右是[]运算符,说明arr是一个数组;
  • 再往左是一个*号,说明arr数组的元素是指针(注意:这里的*修饰的不是arr,而是arr[10]。原因是[]运算符的优先级比*要高,arr先与[]结合。);
  • 跳出圆括号,先往右又遇到圆括号,说明arr数组的元素是指向函数的指针,它指向的函数有一个int类型的参数,返回值类型是int

那么,怎么判断定义的是函数指针,还是数组指针,或是数组呢?可以抽象出几个模式:

typedef (*var)(...); // 变量名var与*结合,被圆括号括起来,右边是参数列表。表明这是函数指针
typedef (*var)[];  //变量名var与*结合,被圆括号括起来,右边是[]运算符。表示这是数组指针
typedef (*var[])...;// 变量名var先与[]结合,说明这是一个数组(至于数组包含的是什么,由旁边的修饰决定) 

下面可以利用右左法则去分析复杂的声明和定义:

void * (* (*fp1)(int)) [10];
  • 找到变量名fp1,往右看是圆括号,调转方向往左看到*号,说明fp1是一个指针;
  • 跳出内层圆括号,往右看是参数列表,说明fp1是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
  • 再跳出外层圆括号,往右看是[]运算符,说明函数返回的是一个数组指针,往左看是void *,说明数组包含的类型是void *

简言之 ,fp1是一个指向函数的指针,该函数接受一个整型参数并返回一个指向含有10个void指针数组的指针。

float (* (*fp2) (int, int, float)) (int);
  • 找到变量名fp2,往右看是圆括号,调转方向往左看到*号,说明fp2是一个指针;
  • 跳出内层圆括号,往右看是参数列表,说明fp2是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
  • 再跳出外层圆括号,往右看还是参数列表,说明返回的指针是一个函数指针,该函数有一个int类型的参数,返回值类型是float

简言之,fp2是一个指向函数的指针,该函数接受三个参数(int, int, float),且返回一个指向函数的指针,该函数接受一个int参数并返回一个float

typedef double (* (* (*fp3) ()) [10]) ();
fp3 a;

如果创建许多复杂的定义,可以使用typedef。这一条显示typedef是如何缩短复杂的定义的。

  • 跟前面一样,先找到变量名fp3(这里fp3其实是新类型名),往右看是圆括号,调转方向往左是*,说明fp3是一个指针;
  • 跳出圆括号,往右看是空参数列表,说明fp3是一个函数指针,接着往左是*号,说明该函数的返回值是一个指针;
  • 跳出第二层圆括号,往右是[]运算符,说明函数的返回值是一个数组指针,接着往左是*号,说明数组中包含的是指针;
  • 跳出第三层圆括号,往右是参数列表,说明数组中包含的是函数指针,这些函数没有参数,返回值类型是double

简言之,fp3是一个指向函数的指针,该函数无参数,且返回一个含有10个指向函数指针的数组的指针,这些函数不接受参数且返回double值。

这二行接着说明:a是fp3类型中的一个。

int (* (*fp4()) [10]) ();

这里fp4不是变量定义,而是一个函数声明。

  • 找到变量名fp4,往右是一个无参参数列表,说明fp4是一个函数,接着往左是*号,说明函数返回值是一个指针;
  • 跳出里层圆括号,往右是[]运算符,说明fp4的函数返回值是一个指向数组的指针,往左是*号,说明数组中包含的元素是指针;
  • 跳出外层圆括号,往右是一个无参参数列表,说明数组中包含的元素是函数指针,这些函数没有参数,返回值的类型是int

简言之,fp4是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数不接受参数且返回整型值。

四、用typedef简化复杂的声明和定义

  1. int *(*a[10]) (int, char*);

    用前面的“右左法则”,可以知道

    • a是一个包含10个函数指针的数组,这些函数的参数列表是(int, char*),返回值类型是int *
    • 如果要定义相同类型的变量b,都得重复书写:int *(*b[10]) (int, char*);
    • 为了避免重复复杂的定义,用typedef来简化复杂的声明和定义。 typedef可以给现有的类型起个别名。这里用typedef给以上ab的类型起个别名:
    typedef int *(*A[10])(int, char*);// 在之前定义的前面加入typedef,然后将变量名a替换成类型名A
    

    现在要再定义相同类型的变量c,只需要: A c;

  2. void (*b[10]) (void (*)());

    • 先替换右边括号里面的参数,将void (*)()的类型起个别名pParam

      typedef void (*pParam) (); 
      
    • 再替换左边的变量b,为b的类型起个别名B

      typedef void (*B)(pParam);
      
    • 原声明的简化版:

      B b[10];
      

参考资料:

C++ typedef的详细用法 - 知乎 (zhihu.com)

posted @   Larcvz  阅读(620)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示