文档版本 | 更新时间 | 更新内容 |
---|---|---|
v1.0 | 2020-09-14 | 初稿完成 |
typedef用来给数据类型起别名,用法如下:
typedef <已有数据类型> <新名称>;
比如:
typedef unsingned char uint8_t;
typedef unsingned int uint16_t;
为了表示一些复杂的事物,普通数据类型无法满足要求。
把一些基本数据类型组合在一起而形成的一个新的数据类型,叫做结构体。
定义结构体有四种方法:
① 只定义数据类型,不定义变量:
struct student_st {
char name[20]; /* 学生名称 */
char sex; /* 学生性别 */
float score; /* 学生成绩 */
};
② 定义数据类型,同时定义一个变量:
struct student_st {
char name[20]; /* 学生名称 */
char sex; /* 学生性别 */
float score; /* 学生成绩 */
} st1;
③ 定义数据类型,同时定义一个变量,但结构体类型匿名:
struct {
char name[20]; /* 学生名称 */
char sex; /* 学生性别 */
float score; /* 学生成绩 */
} st1;
④ 定义数据类型,不定义变量,同时为新的类型起个别名(多用):
typedef struct student_st {
char name[20]; /* 学生名称 */
char sex; /* 学生性别 */
float score; /* 学生成绩 */
} student_t;
结构体变量只能在定义的同时赋初值:
student_t st1 = {
"mculover666", 'M', 66.6};
一旦定义完成,将无法整体赋值,只能单个赋值。
访问结构体中的成员有两种方法:
① 通过结构体变量访问:
printf("name:%s sex:%c score:%.1f\r\n", st1.name, st1.sex, st1.score);
② 通过指向结构体变量的指针访问:
student_t* st_ptr = &st1;
printf("name:%s sex:%c score:%.1f\r\n", st_ptr->name, st_ptr->sex, st_ptr->score);
结构体变量之间不能相加、相减、也不能相互乘除,但是结构体变量之间可以相互赋值。
student_t st1 = {
"mculover666", 'M', 66.6};
student_t st2;
st2 = st1;
printf("name:%s sex:%c score:%.1f\r\n", st2.name, st2.sex, st2.score);
如果要将结构体作为函数参数传递,两种方法如下:
执行如下代码:
printf("%d + %d + %d = %d", sizeof(st1.name), sizeof(st1.sex), sizeof(st1.score), sizeof(st1));
运行结果为:
20 + 1 + 4 = 28
很神奇,结构体不应该占用25个字节吗?为什么会是28个字节?
将st1每个成员的地址打印出来便知:
printf("st1: %p\nst1.name: %p\nst1.sex: %p\nst1.score:%p\n", &st1, &st1.name, &st1.sex, &st1.score);
打印结果为:
st1: 000000000061FDF0
st1.name: 000000000061FDF0
st1.sex: 000000000061FE04
st1.score:000000000061FE08
不难发现,sex变量是char类型,本来应该占用 1 个字节,实际却占用了 4 个字节!
编译器给结构体中的变量分配空间时有内存对齐规则:
① 结构体变量的起始地址能够被其最宽的成员大小整除;
② 结构体每个成员相对于起始地址的偏移,能够被其自身大小整除,如果不能,则在前一个成员后面补充空白字节;
③ 结构体总体大小能够被最宽的成员的大小整除,否则将在后面补充空白字节。
第2个成员sex是char类型,只占用一个字节,所以当后面的成员score定义时,其自身大小是4,结果发现000000000061FE04+1
不能满足对齐规则,所以补充空白字节,使前面有20+4=24个字节才行,地址变为000000000061FE08
。
如果我们手动补充上这些空白字节,则整个结构体大小还会是28个字节。
typedef struct student_st {
char name[20]; /* 学生名称 */
char sex; /* 学生性别 */
char reserve_bytes[3]; /* 保留字节 */
float score; /* 学生成绩 */
} student_t;
student_t st[100];
#define STUDENT_NUM 100
/* 申请动态内存 */
student_t* st_ptr = (student_t *)malloc(STUDENT_NUM * sizeof(student_t));
/* 使用... */
/* 使用完毕之后释放 */
free(st_ptr);
st_ptr = NULL;
枚举体是将一个类型的变量所有可能的值都罗列出来,并且此类型变量不能赋其它值。
上述结构体中有一个成员是sex(性别),适合使用枚举体。
定义枚举体方法有二。
① 只定义枚举类型:
enum student_sex_en {
MALE = 'M',
FAMALE = 'F',
};
② 定义枚举类型的同时,起个新名字,方便使用:
typedef enum student_sex_en {
MALE = 'M',
FAMALE = 'F',
} student_sex_t;
首先优化之前我们定义的结构体:
typedef struct student_st {
char name[20]; /* 学生名称 */
student_sex_t sex; /* 学生性别 */
char reserve_bytes[3]; /* 保留字节 */
float score; /* 学生成绩 */
} student_t;
接着在给sex变量赋值的时候使用:
student_t st1 = {
"mculover666", MALE, 66.6};
共用体就是多个变量共用同一段内存空间,共用体的大小是最大变量占用的内存空间。
最典型的一个用法就是:结构体无差异化遍历。
比如现在有一个结构体:
typedef struct num_st {
int a;
int b;
int c;
int d;
} num_t;
使用情况如下:
显然,第一个需求适合使用数组,第二个需求适合使用结构体,所以就使用共用体:让数组和结构体共用同一个内存空间。
按照上述需求定义共用体,并起个新名字,这个共用体占用的空间为4个int的大小:
typedef union num_un {
num_t num;
int n[4];
} num_u;
在赋值时按照数组来用,直接循环遍历,满足第一个需求:
num_u num1;
for (int i = 0; i < 4; i++) {
num1.n[i] = i;
}
在访问数据时按照结构体来用,按照名字取值,满足第二个需求:
printf("a = %d, b = %d, c = %d, d = %d\r\n", num1.num.a, num1.num.b, num1.num.c, num1.num.d);
最终运行结果为:
a = 0, b = 1, c = 2, d = 3