结构体、联合体、枚举、链表
结构体
用计算机去解决现实世界的一些问题的时候,需要把现实世界的物体,抽象成一个计算机语言中的模型 -> 建模
学生信息:
姓名:张三 ---->字符数组
学号:20260309 ---->整形
性别:b ---->字符
年龄:23 ---->整形
身高:185 ---->整形
自定义类型:是一种组合的数据类型,是一组不同的数据类型的集合。
- 定义
-
语法
struct 结构体名 { 成员类型1 成员变量名1; 成员类型2 成员变量名2; ... 成员类型n 成员变量名n; };- 示例:
struct student { char name[50]; int id; char sex; int age; int height; };注意:这一句执行完,并不会分配空间。只是单纯的定义了一个新的数据类型(结构体类型的声明),没有进行空间分配
-
- 使用:struct 结构体名字 结构体变量名{=初始化值};
struct student { char name[50]; int id; char sex; int age; int height; }; struct student zhangsan = {"张三", 20260309, 'b', 23, 180};- 取结构体的值:结构体变量名+域操作符+成员名
成员变量的使用和普通变量是一模一样的struct student { char name[50]; int id; char sex; int age; int height; }; struct student zhangsan = {"张三", 20260309, 'b', 23, 180}; printf("name:%s\n", zhangsan.name); - 完整示例:
#include <stdio.h> struct student { char name[40]; int age; }; int main(){ struct student zhangsan; printf("请输入名字和年龄:"); scanf("%s %d", zhangsan.name, &zhangsan.age); printf("zhangsan.name:%s, zhangsan.age:%d\n", zhangsan.name, zhangsan.age); struct student lisi = {"李四", 22}; printf("lisi.name:%s, lisi.age:%d\n", lisi.name, lisi.age); return 0; } 结果: C:\ccode\class6> .\struct.exe 请输入名字和年龄:张三 18 zhangsan.name:张三, zhangsan.age:18 lisi.name:李四, lisi.age:22
- 取结构体的值:结构体变量名+域操作符+成员名
- 结构体指针变量的引用
- 语法:结构体指针变量名+指向操作符+成员名:
zhangsanp->name;#include <stdio.h> struct student { char name[20]; int age; }; int main(){ struct student zhangsan; struct student *zhangsanp; zhangsanp = &zhangsan; printf("请输入name和age:"); scanf("%s %d", zhangsanp->name, &(zhangsanp->age)); printf("zhangsan.name:%s, zhangsan.age:%d\n", zhangsan.name, zhangsan.age); printf("(*zhangsanp).name: %s\n", (*zhangsanp).name); printf("(*zhangsanp).age: %d\n", (*zhangsanp).age); return 0; } 结果: PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class6> .\struct2.exe 请输入name和age:zhangsan 222 zhangsan.name:zhangsan, zhangsan.age:222 (*zhangsanp).name: zhangsan (*zhangsanp).age: 222
- 语法:结构体指针变量名+指向操作符+成员名:
结构体成员的内存分布
- 结构体类型所占的空间大小是各个成员变量的所占空间之和(可能会被pack填充)
- 结构体内各个成员变量按他们定义时出现的次序,依次保存。
sizeof(结构体)===> ??? 并非简单的成员空间大小相加,而是根据对齐规则有略微的超出
结构体的字节对齐规则
-
字节对齐:
- CPU为了访问效率,一般要求数据按字节对齐来存放。32位CPU数据总线长度为32位,按4字节对齐效率更高
- 64位CPU,按8字节对齐的效率更高
如果结构体数据的成员所占内存不是对齐pack值的整倍数,就会按他的整数倍字节来算,32位CPU中:char 类型为1字节,实际应该是4字节
#include <stdio.h> struct student { char name[36]; char sex; int age; }; int main(){ struct student zhangsan; printf("%d\n", sizeof(zhangsan)); return 0; } 结果: 44示例中,char[36], char, int 总和应该41才对,实际是44
- 手动指定字节对齐pack:
#pragma pack(2)
#include <stdio.h> #pragma pack(2) struct student { char name[36]; char sex; int age; }; int main(){ struct student zhangsan; printf("%d\n", sizeof(zhangsan)); return 0; } 结果: 42
共用体
共用体也叫联合体,各个成员共用一段存储空间。
语法:
union 共用体名
{
成员类型1 成员变量名1;
成员类型2 成员变量名2;
...
成员类型n 成员变量名n;
};
共用体与结构体的区别:
- 结构体所占空间是各个成员之和,共用体所占空间是各个成员中最大的那个
注意:共用体使用一个成员的时候,不能使用另一个成员(多线程并发可能会使用多个成员,一定要注意)
共用体最早的作用就是解决嵌入式中内存不够的情况,现在空间已经够大了,用的会很少
大端模式和小端模式
早期在各个处理器厂商,在处理把CPU的寄存器中的数据存放到内存中时,处理方式并没有统一
- 大端模式:内存的低地址,存放寄存器中的高字节数据
- 小端模式:内存的地地址,存放寄存器中的低字节数据
#include <stdio.h> union demo { int a; char b; }; int main(){ union demo d; d.a = 0xAE86; printf("%x\n", d.b); return 0; } 结果: ffffff86示例中取出的是86说明是小端模式,因为数据都是存放在低地址的,只是数据顺序可能会反,通过结果可以看出,数据并没有存反
枚举
把该类型变量的所有值都列举出来 有的时候,我们需要列举一些整数值,而且这些整数值在一定的范围内:
语法:
enum 枚举类型名
{
成员标识符1{=整数值},
成员标识符2{=整数值},
...
成员标识符n{=整数值},
}
成员标识符
- 用一些合法的C标识符来命名一些数值,默认从零开始
#include <stdio.h>
enum weekday
{
sun, // 0
mon, // 1
tue, // 2
wed, // 3
thu, // 4
fri, // 5
sat // 6
};
int main(){
enum weekday w = sun;
printf("%d %d %d\n", w, fri, sizeof(w));
return 0;
}
结果:
0 5 4
// 可以指定值
enum weekday
{
sun = 10, // 10
mon, // 11
tue, // 12
};
注意:枚举完全可以当做一个常量列表来使用
typedef
用来将一个已存在的类型声明为一个新的数据类型
语法
typedef 旧类型名(已经存在的) 新类型名;
// 比如:
typedef int ACK; // 将int类型重新声明成ACK类型
ACK a; // 定义一个ACK类型的变量 a
与宏 #define 的区别
typedef int ACK_T;
#define ACK_D int
ACK_T a1; // 通过ACK_T定义变量: ACK_T a1 !=> int a1; 不等价
ACK_D a2; // 通过ACK_D定义变量: ACK_D a2 ==> int a2; 等价
练习
使用 struct 指针做一个链表
#include <stdio.h>
#include <stdlib.h>
struct Node {
int val;
struct Node * next;
};
int main(){
struct Node *head = NULL;
// 构建链表
struct Node *temp = NULL;
for (int i = 0; i<10; i++){
// 这里要使用malloc手动申请内存,否则在循环结束,会将内存释放,导致存储失败
struct Node *n = (struct Node*)malloc(sizeof(struct Node));
n->val = rand();
n->next = NULL;
if (head==NULL){
n->val = 1;
head = n;
temp = n;
} else {
if (temp==NULL){
temp = head;
}
temp->next = n;
temp = temp->next;
}
printf("n->val:%d\n", n->val);
}
// 打印链表
printf("================================ 打印链表 =====================================\n");
temp = head;
while (temp!=NULL){
printf("%d\n", temp->val);
temp = temp->next;
}
// 释放链表内存
printf("================================ 释放内存 =====================================\n");
temp = head;
while(temp !=NULL){
struct Node *next = temp->next;
printf("释放 val:%d\n", temp->val);
free(temp);
temp = next;
}
return 0;
}
结果:
PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class6> .\node2.exe
n->val:1
n->val:18467
n->val:6334
n->val:26500
n->val:19169
n->val:15724
n->val:11478
n->val:29358
n->val:26962
n->val:24464
================================ 打印链表 =====================================
1
18467
6334
26500
19169
15724
11478
29358
26962
24464
================================ 释放内存 =====================================
释放 val:1
释放 val:18467
释放 val:6334
释放 val:26500
释放 val:19169
释放 val:15724
释放 val:11478
释放 val:29358
释放 val:26962
释放 val:24464
PS C:\Users\86176\Desktop\嵌入式\c语言\ccode\class6>