九、复合类型(自定义类型):结构体、共用体(联合体)、枚举、typedef
结构体

//结构体的定义与使用 //结构体一般卸载.h文件中,不卸载.c文件内 struct student { int age; char name[20]; char grade[20]; }; struct student1 { int age; char name[20]; char grade[20]; }std1, std2, std3; //在定义结构体时,可以声明结构体变量 struct student2 { int age; char name[20]; char grade[20]; }std22 = { .name = "allen3",.age = 20, };//在定义结构体时,可以初始化结构体变量 int main() { //结构体使用 struct student s1, s2; //###初始化方式1 s1.age = 18; strcpy(s1.name, "allen"); strcpy(s1.grade, "3年1班"); //我说这里为什么要用strcpy()函数,因为定义name为数组,而"allen"是字符串数组。不能用赋值语句将一个字符数组直接给一个字符数组赋值 //###初始化方式2 (&s2)->age = 19; strcpy((&s2)->name, "allen2"); strcpy((&s2)->grade, "3年2班"); //这个不就是指针的方式嘛。。。 //###初始化方式3 struct student s3 = { .name = "allen3",.age = 20, }; //可以不全部赋值,可以不按顺序 //###初始化方式4 struct student s4 = { 21,"allen4","3年4班" }; printf("%d\t%s\t%s\n", (&s1)->age, (&s1)->name, (&s1)->grade); //18 allen 3年1班 printf("%d\t%s\t%s\n", s2.age, s2.name, s2.grade); //19 allen2 3年2班 printf("%d\t%s\t%s\n", s3.age, s3.name, s3.grade); //20 allen3 printf("%d\t%s\t%s\n", s4.age, s4.name, s4.grade); //21 allen4 3年4班 }

我的结构体 "."用法、"->"用法 总结: 其实就是看结构体变量的变量类型 若结构体变量是普通变量,那么直接用"."进行操作;or 将普通变量用"&"转为地址后,使用"->"进行操作 若结构体变量是指针变量,那么直接用"->"进行操作;or 将指针变量用"*"转为普通变量后,用"."进行操作 再看结构体成员的变量类型 都可以用strcpy或"="方式进行赋值,区别在于赋值两边的类型要相同 若结构体成员为普通变量,那么右边也是普通变量 若结构体成员为指针变量,那么右边也是指针变量or地址 若结构体成员为指针变量,赋值过程: struct Device* DD = malloc(sizeof(struct Device*)); DD->name = malloc(sizeof(char) * 20); (*DD).name = malloc(sizeof(char) * 20); sprintf(DD->name, "str_names"); //向DD->name地址空间存放内容 sprintf(( * DD).name, "str_names"); //向DD->name地址空间存放内容 //device001->name 这种用法在不同的语境中,功能看起来是不一样的嘛。。。 //1.获取对应的内容 printf("device设备名1:%s\n",device001->name); //2.这个才是往内存空间中储存内容 DD->name = malloc(sizeof(char)*20); //3.这个是将"DD->name对应的地址"进行解引用,然后再往这个内存空间储存内容 sprintf(DD->name, names); ------------------------------------------------------------------------------------------------ 结构体变量是普通变量+结构体成员是普通变量: 使用"."进行操作: p.name, p.age 使用"->"进行操作: strcpy((&p)->name, "test"); (&p)->age = 22; 结构体变量是指针变量+结构体成员是普通变量: 需要通过"->"进行操作: strcpy(p->name, "test"); p->age = 22; 使用"."进行操作: (*p).name (*p).age

//结构体内存对齐规则 //看起来是根据占用内存最长字节的类型进行调整,例如此处最大类型为int,占用4字节; struct student1 { char name; //浪费3字节 int age; char grade[15]; //浪费1字节 }std1; struct student2 { int age; //定义的内容一样,只是内内容进行了优化排序 char name; char grade[15]; }std2; int main() { printf("%d\n", sizeof(std1)); //24字节 printf("%d\n", sizeof(std2)); //20字节 }

//结构体数组使用 struct student { int age; char name[20]; char grade[20]; }; int main() { //###初始化方式1 struct student boys[2] = { {18,"allen3","4年1班"}, {19,"allen4","4年2班"} }; printf("%d\t%s\t%s\n", (&boys[0])->age, (&boys[0])->name, (&boys[0])->grade); //18 allen3 4年1班 printf("%d\t%s\t%s\n", boys[1].age, boys[1].name, boys[1].grade); //19 allen4 4年2班 struct student girls[2]; //girls[0] = { 18,"lili","4年1班" } ; //这种方式不行? //###初始化方式2 girls[0].age = 18, strcpy(girls[0].name, "lili"), strcpy(girls[0].grade, "5年1班"); girls[1].age = 19, strcpy(girls[1].name, "lili2"), strcpy(girls[1].grade, "5年2班"); printf("%d\t%s\t%s\n", (&girls[0])->age, (&girls[0])->name, (&girls[0])->grade); //18 lili 5年1班 printf("%d\t%s\t%s\n", girls[1].age, girls[1].name, girls[1].grade); //19 lili2 5年2班 }

//结构体数组排序 struct student { char name[20]; int scroes[3]; }; int main() { struct student std[6]={ {"allen1",89,100,35}, {"allen2",67,78,56}, {"allen3",99,33,55}, {"allen4",56,34,98}, {"allen5",23,55,99}, {"allen6",66,99,54}, }; for (size_t i = 0; i < 6; i++) { printf("%s %d %d %d %d\n", std[i].name, std[i].scroes[0], std[i].scroes[1], std[i].scroes[2], std[i].scroes[0]+ std[i].scroes[1] + std[i].scroes[2]); } printf("================================================================\n"); //冒泡排序 for (size_t i = 0,len = 5; i < 5; i++,len--) { for (size_t j = 0; j < len; j++) { if (std[j].scroes[0] + std[j].scroes[1] + std[j].scroes[2] < std[j+1].scroes[0] + std[j+1].scroes[1] + std[j+1].scroes[2]) { char tmp[20]; strcpy(tmp, std[j].name); int scroe0 = std[j].scroes[0]; int scroe1 = std[j].scroes[1]; int scroe2 = std[j].scroes[2]; strcpy(std[j].name, std[j + 1].name); std[j].scroes[0] = std[j + 1].scroes[0]; std[j].scroes[1] = std[j + 1].scroes[1]; std[j].scroes[2] = std[j + 1].scroes[2]; strcpy(std[j+1].name, tmp); std[j + 1].scroes[0] = scroe0; std[j + 1].scroes[1] = scroe1; std[j + 1].scroes[2] = scroe2; } } } for (size_t i = 0; i < 6; i++) {printf("%s %d %d %d %d\n", std[i].name, std[i].scroes[0], std[i].scroes[1], std[i].scroes[2], std[i].scroes[0] + std[i].scroes[1] + std[i].scroes[2]);} printf("================================================================\n"); //排序优化,结构体变量交换 for (size_t i = 0, len = 5; i < 5; i++, len--) { for (size_t j = 0; j < len; j++) { if (std[j].scroes[0] < std[j + 1].scroes[0]) { struct student tmp; tmp = std[j]; std[j] = std[j + 1]; std[j + 1] = tmp; } } } for (size_t i = 0; i < 6; i++) { printf("%s %d %d %d %d\n", std[i].name, std[i].scroes[0], std[i].scroes[1], std[i].scroes[2], std[i].scroes[0] + std[i].scroes[1] + std[i].scroes[2]); } }

结构体成员为指针;堆空间结构体【常用】 ========================================================================================== //结构体成员为指针 //过渡状态,不常用 struct student { char *name; int age; }; int main() { struct student s[5] = { {(char*)malloc(sizeof(char) * 20),33}, {(char*)malloc(sizeof(char) * 20),77}, {(char*)malloc(sizeof(char) * 20),99}, {(char*)malloc(sizeof(char) * 20),56}, {(char*)malloc(sizeof(char) * 20),21}, }; strcpy(s[0].name, "allen0"); strcpy((&s[1])->name, "allen1"); strcpy(s[2].name, "allen2"); strcpy(s[3].name, "allen3"); strcpy(s[4].name, "allen4"); for (size_t i = 0; i < 5; i++) { printf("%s %d\n", s[i].name, s[i].age); //打印数组,给地址就可以了。。。。 } free(s[0].name); free(( &s[1])->name); free(s[2].name); free(s[3].name); free(s[4].name); } --------------------------------------------------------------------------------------- //堆空间结构体【常用】 struct student { char *name; int *age; }; int main() { struct student* s = (struct student*)malloc(sizeof(struct student) * 5); //堆空间申请了5个struct结构体的指针空间 printf("%d\n", sizeof(struct student)); //16 定义的student结构体占用16字节 printf("%d\n", sizeof(s)); //8 64位系统,一个指针占用8字节 for (size_t i = 0; i < 5; i++) { (s + i)->name = (char*)malloc(sizeof(char) * 20); (s + i)->age = (int *)malloc(sizeof(int)); } strcpy((s + 0)->name, "allen0"); *((s + 0)->age) = 11; strcpy((s + 1)->name, "allen1"); *((s + 1)->age) = 22; strcpy((s + 2)->name, "allen2"); *((s + 2)->age) = 33; strcpy((s + 3)->name, "allen3"); *((s + 3)->age) = 44; strcpy((s + 4)->name, "allen4"); *((s + 4)->age) = 55; for (size_t i = 0; i < 5; i++) { printf("%s %d\n", (s + i)->name , *((s + i)->age)); } for (size_t i = 0; i < 5; i++) { free((s + i)->name); free((s + i)->age); } free(s); }

//结构体和函数 struct student { char name[20]; int age; }; void func1(struct student s) { strcpy(s.name, "李四"); s.age = 20; } void func2(struct student *s) { strcpy(s->name, "李四"); s->age = 20; } struct student func3() { struct student s3 = { "王五",18 }; return s3; } struct student *func4() { struct student s4 = { "王七",20 }; return &s4; } int main() { struct student s1 = { "张三",18 }; printf("%s %d\n", s1.name, s1.age); //张三 18 func1(s1); printf("%s %d\n", s1.name, s1.age); //张三 18 结构体传值形式,函数不会改变原变量内容 func2(&s1); printf("%s %d\n", s1.name, s1.age); //李四 20 传地址,完成了变量内容的修改 struct student s3 = func3(); //别搞混了啊。。。函数的返回值被s3接收了,那么就相当于s3储存了返回值而已,并不是函数栈空间的值还保留着 printf("%s %d\n", s3.name, s3.age); //王五 18 struct student *s4 = func4(); printf("%s %d\n", s4->name, s4->age); //烫烫烫烫烫烫烫烫8鮷橰 20 这里乱码是正常的,因为函数执行结束,数据被销毁了。只是为什么20是显示正常的? }

//结构体嵌套结构体 //这个好像可以搞对象了嘛。。 struct hands_foots { //该结构体保存了四肢详情 int hands; int foots; }; struct food { //该结构体保存了食物及分量 char food[20]; int weight; }; struct animal { char name[20]; struct hands_foots hf; struct food fd; }; int main() { struct animal xingxing; strcpy(xingxing.name, "猩猩"); xingxing.hf.hands = 2; xingxing.hf.foots = 2; strcpy(xingxing.fd.food, "香蕉"); xingxing.fd.weight = 3; printf("动物名称:%s;手:%d;脚:%d;食物:%s;食物分量:%d斤", xingxing.name, xingxing.hf.hands, xingxing.hf.foots, xingxing.fd.food, xingxing.fd.weight = 3); //动物名称:猩猩;手:2;脚:2;食物:香蕉;食物分量:3斤 }
共用体(联合体)

联合union是一个能在同一个存储空间存储不同类型数据的类型; 联合体所占的内存长度等于其最长成员的长度倍数,也有叫做共用体; 同一内存段可以用来存放几种不同类型的成员,但每一瞬时只有一种起作用; 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员的值会被覆盖; 共用体变量的地址和它的各成员的地址都是同一地址。 //共用体(联合体) //使用比较少,因为会显得数据比较乱;优点是节省内存 union MyUnion { int a; float b; char c; double d; char arr[22]; }; int main() { union MyUnion test; printf("%d\n", sizeof(union MyUnion)); //24字节 看来也受到内存对齐规则限制 test.a = 100; printf("%d\n", test.a); //100 //共用体只有最后一次赋值的数据才是准确的。 test.b = 3.14; printf("%d\n", test.a); //1078523331 printf("%f\n", test.b); //3.140000 test.c = 'A'; printf("%d\n", test.a); //1078523201 printf("%f\n", test.b); //3.139969 printf("%c\n", test.c); //A printf("%p\n", &test.a); //000000E16D35FC58 printf("%p\n", &test.b); //000000E16D35FC58 printf("%p\n", &test.c); //000000E16D35FC58 };
枚举

枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。 //枚举 enum colors { red, bule, yellow, black, white, green }clo1; int main() { clo1 = 1; switch (clo1) { case red: break; case bule: printf("当clo1 = 1时,执行这条语句"); //当clo1 = 1时,执行这条语句 break; case yellow: break; case black: break; case white: break; case green: break; default: break; } };
typedef

typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。 与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值 #define发生在预处理,typedef发生在编译阶段 //typedef 就是起别名功能,不能定义新类型 //常用的就是给常用变量、结构体起别名,一般放在头文件.h文件中 typedef unsigned int UI; struct student_info_list { char name[20]; char sex; }; typedef struct student_info_list SIL; //可以给结构体起别名 int main() { unsigned int a = 10; typedef unsigned int UI; UI b = 20; SIL s1; //结构体起别名后,申请结构体变量就简单多了。。。 printf("%d\n", a); printf("%d\n", b); };
打字游戏

打字游戏 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<string.h> #include<stdlib.h> #include <conio.h> #include <time.h> void tips() { printf("==================打字游戏================\n"); printf("================按任意键继续==============\n"); printf("===============按Esc 退出游戏=============\n"); char ch = _getch(); if (ch == 27) { exit(0); } } void rand_ch(char* arr) { srand((unsigned int)time(NULL)); for (int i = 0; i < 50; i++) { arr[i] = rand() % 26 + 'a'; } } void print_ch(char* arr) { //变量 计时器 开始 结束 计数器 val unsigned int start_time; unsigned int end_time; int val = 0; for (int i = 0; i < 50; i++) { char ch = _getch(); if (i == 0) { start_time = time(NULL); } if (ch == arr[i]) { printf("%c", ch); val++; } else { printf("_"); } } end_time = time(NULL); printf("\n用时:%d(秒)\n", end_time - start_time); printf("正确率:%.1f%%\n", val * 1.0 / 50 * 100); if (val * 1.0 / 50 * 100 > 80.0) { printf("打字小能手!\n"); } } int main() { //字库 char arr[51]; memset(arr, 0, 51); while (1) { //1、提示 tips(); //2、随机字符 rand_ch(arr); printf("%s\n", arr); //3、输入字符 print_ch(arr); } return EXIT_SUCCESS; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!