【C语言】结构体新的理解
【C语言】结构体新的理解
- 一、引言
- 1 介绍
- 2 分析
- 二、怎么定义结构体?
- 1 直接定义结构体变量
- 2 定义一个结构体“类型”
- 3 定义结构体“类型”,且typedef指定别名
- 三、typedef的用法
- 1 最基本的用法
- 2 与define的区别
- 2.1 原理不同
- 2.2 功能不同
- 2.3 作用域不同
- 2.4 对指针的操作不同
- 3 typedef为基本数据类型取“别名”
- 4 typedef为“自定义数据类型”取“别名”
- 5 为数组起别名
- 6 typedef为指针取“别名”
- 四、结构体指针
- 五、回顾最开始的问题
一、引言
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;
可以使用Point
和Color
这两个类型,定义该类型下的变量。
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;
该语句的含义是定义结构体类型的指针变量。