【C语言】结构体新的理解

一、引言

1 介绍

最近在看ESP32的I2C程序时,看到一条语句,不太理解,于是记录一下。

/**
 * @brief Type of I2C master bus handle
 */
typedef struct i2c_master_bus_t *i2c_master_bus_handle_t;
struct i2c_master_bus_t {
    i2c_bus_t *base;                 // bus base class
    SemaphoreHandle_t bus_lock_mux;  // semaphore to lock bus process
    int cmd_idx;                     //record current command index, for master mode
    _Atomic i2c_master_status_t status;    // record current command status, for master mode
.
.
.
.
    i2c_transaction_t i2c_trans_pool[];    // I2C transaction pool.
};

应用的时候,配置I2C总线。

#include "driver/i2c_master.h"

i2c_master_bus_config_t i2c_mst_config = {
    .clk_source = I2C_CLK_SRC_DEFAULT,
    .i2c_port = TEST_I2C_PORT,
    .scl_io_num = I2C_MASTER_SCL_IO,
    .sda_io_num = I2C_MASTER_SDA_IO,
    .glitch_ignore_cnt = 7,
    .flags.enable_internal_pullup = true,
};

i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));

2 分析

结合上面的程序,i2c_master_bus_handle_t是一个i2c_master_bus_t类型的结构体指针
看下面这个语句

i2c_master_bus_handle_t bus_handle;

应该是用结构体指针,定义一个i2c_master_bus_t类型的结构体变量,结构体变量名是bus_handle。
指针只能等于同类型变量的地址(指针 = &变量)。
问题是:
用了typedef之后,指针可以定义变量吗?
下面梳理一下。

二、怎么定义结构体?

1 直接定义结构体变量

struct{
    char no[20];        //学号
    char name[20];      //姓名
    char sex[5];      //性别
    int age;          //年龄
}stu1,stu2;    

上面stu1;stu2是两个结构体变量,如果想要再定义一个相同的结构体变量,还需要重新做完整的定义,如下所示。

struct{
    char no[20];        //学号
    char name[20];      //姓名
    char sex[5];      //性别
    int age;          //年龄
}stu3;    

显而易见,如果每次都这样写,比较麻烦。

2 定义一个结构体“类型”

struct student{
    char no[20];        //学号
    char name[20];      //姓名
    char sex[5];      //性别
    int age;          //年龄
};    

上面的struct student就是一个类型,相当于int,char
可以用这个结构体类型,定义结构体变量,如下:

struct student stu1;

int,char可以在定义变量的同时,为变量赋初值。
相对应的
可以在定义结构体类型的同时,定义结构体变量(可不可以同时赋初值,待定)
如下面定义一个结构体类型的同时,定义一个stu1的结构体变量:

struct student{
   char no[20];        //学号
   char name[20];      //姓名
   char sex[5];      //性别
   int age;          //年龄
}stu1;    

后面可以再用struct student这个结构体类型定义其他的变量。

这样看,每次定义变量都需要写struct student这个结构体类型,也比较麻烦。
所以有下面这种方法,用typedef为struct student指定一个别名。

3 定义结构体“类型”,且typedef指定别名

typedef struct student{
    char no[20];       //学号
    char name[20];    //姓名
    char sex[5];    //性别
    int age;          //年龄
}STUDENT;
STUDENT stu1;

上面STUDENT就等于struct student
可以用STUDENT定义struct student的结构体变量。

STUDENT stu1;

总结:
也就是说:
struct student是一个结构体类型,通过typedef为结构体类型指定别名STUDENT之后。
STUDENT可以定义变量。
那么如果使用typedef为int 或者char指定别名之后,是否可以通过这个“别名”定义变量呢??

三、typedef的用法

typedef最核心的思想就是
为已有的类型起一个别名
自己定义的结构体类型,也可以用typedef起别名。
既然是“为类型起别名”,那么也可以用别名定义该类型的变量

1 最基本的用法

最开始是和define一块认识的,所以最基本的用法就是起别名。

#define unsigned int uint
typedef uint unsigned int

2 与define的区别

在C语言中,typedef和define都是用来起别名的关键字,但它们的应用方式和效果却存在明显差异。typedef用于为已有的数据类型创建新的名称,而define则用于定义预处理宏,在编译时会被替换为指定的文本。

2.1 原理不同

#define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。

typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。
用typedef定义数组、指针、结构体等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。

2.2 功能不同

typedef用来定义类型的别名,起到类型易于记忆的功能。
另一个功能是定义机器无关的类型,也就是前面在“定义结构体”时说的,使用typedef定义一个结构体的类型。

这么看,typedef确实有“定义类型”的作用。
使用typedef定义完类型,就可以用这个类型定义变量。

#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。

typedef在创建新类型时,只是为已有类型赋予别名。它只在编译器阶段有效,不会引起任何代码替换。使用typedef给基本类型或复杂类型取别名,可以集中管理多个相似的类型,提高代码的可读性和可维护性。

相比之下,define是在预处理阶段进行文本替换。预处理器会根据宏定义中指定的文本,将代码中所有的宏调用替换为对应的文本。这种替换是简单的文本替换,没有类型检查和语法分析。

2.3 作用域不同

#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。
typedef有自己的作用域。

可以理解为
define的作用域是全局
typedef有限
但具体怎么有限,暂时不清楚。

2.4 对指针的操作不同

这里简单写一个不同。
define和普通用类型定义定义指针一样,只对第一个变量起作用。

int *a,b;

a为int指针变量,b为int变量

#define INT int*
INT a,b;

a为int指针变量,b为int变量
typedef对后面多个变量起作用。

typedef int* INT
INT a,b;

a和b均为int类型的指针。

3 typedef为基本数据类型取“别名”

typedef unsigned int uint;

使用uint就是unsigned int

4 typedef为“自定义数据类型”取“别名”

typedef struct {
    int x;
    int y;
} ;

typedef enum {
    RED,
    GREEN,
    BLUE
} Color;

可以使用PointColor这两个类型,定义该类型下的变量。

Point point;
Color color;

5 为数组起别名

typedef char arr_name[20];

上面这个语句,好像没有“别名”
typedef A B也就是没有B。

这是一个 C 语言中的类型定义语句,用于定义一个名为arr_name的数组类型,数组元素类型为 char,数组长度为 20。
具体来说,typedef char arr_name[20]; 定义了一个名为 arr_name数组类型,它包含了 20 个 char 类型的元素。
通过这个类型定义,可以使用 arr_name 来声明一个长度为 20 的字符数组,而不必每次都写出完整的数组声明语句。
例如,使用这个类型定义可以这样声明一个长度为 20 的字符数组:

arr_name my_array;

这样就等同于以下完整的数组声明:

char my_array[20];

这种类型定义可以使代码更加简洁和易读,特别是在多处需要声明相同类型的数组时。

这么看来,使用typedef之后,确实可以用后面的“别名”,去定义一个变量。

6 typedef为指针取“别名”

用着的时候再研究

四、结构体指针

自定义一个结构体类型,也可以定义指向某结构体类型的结构体指针。

//定义结构体类型(struct Person)(Per)
typedef struct Person
{
	char name[10];
	int age;
	char job[];
	int annual_salary;
}Per;

int main(void)
{
	Per Lihua;//用结构体类型定义一个Lihua的结构体
	//struct Person Lihua;//等效
	Lihua.age = 18;//访问结构体成员用`.`。

	Per *Lihua_dad;//定义指向Per类型的结构体指针,名为Lihua_dad
	Lihua_dad = &Lihua;//Lihua_dad和Lihua均为struct Person类型。左边是结构体指针,右边是结构体。将右边取地址,可赋值给左边。
	
	Lihua_dad->age = 20;//通过结构体指针,访问结构体的成员(应该是结构体指针的特殊用法)(用的最多)(通过结构体访问时,用`->`)
	//*(Lihua_dad+1) = 20;//应该是结构体指针的标准用法,符合指针用法(在结构体指针上,很少用)(理论可行)(未验证)
	//*(Lihua_dad).age = 20;//已验证
	//Lihua_dad[1] = 20;//指针类似数组的访问方法(理论可行)(未验证)
}

五、回顾最开始的问题

这么综合看下来,typedef核心思想就是“起别名”。
最开始的语句

/**
 * @brief Type of I2C master bus handle
 */
typedef struct i2c_master_bus_t *i2c_master_bus_handle_t;

如果是

typedef struct i2c_master_bus_t i2c_master_bus_handle_t;

含义就是为struct i2c_master_bus_t起了一个别名为i2c_master_bus_handle_t
可以用i2c_master_bus_handle_t来定义struct i2c_master_bus_t类型的结构体。
所以,

typedef struct i2c_master_bus_t *i2c_master_bus_handle_t;

应该改为下面的形式更好理解

typedef struct i2c_master_bus_t* i2c_master_bus_handle_t;

这么看,可以用struct i2c_master_bus_t*类型定义结构体指针变量
起别名之后,就可以用i2c_master_bus_handle_t别名后的类型,定义结构体指针变量

即,

i2c_master_bus_handle_t bus_handle;

就相当于

struct i2c_master_bus_t* bus_handle;

该语句的含义是定义结构体类型的指针变量。

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值