C语言自定义数据类型详解
C语言自定义数据类型详解
- 1. 结构体(struct):理论基础
- 什么是结构体?
- 结构体的内存布局
- 2. 结构体的声明和初始化
- 结构体的三种声明方式
- 结构体的多种初始化方式
- 3. 结构体成员访问和操作
- 4. 结构体与函数
- 5. 结构体数组和嵌套结构体
- 6. 联合体(union):理论基础
- 什么是联合体?
- 联合体的内存特性
- 7. 联合体的实际应用
- 8. 枚举(enum):理论基础
- 什么是枚举?
- 9. typedef:类型别名
- 什么是typedef?
- 10. 位域(bit field):理论基础
- 什么是位域?
- 11. 综合实战:学生管理系统
- 12. 内存对齐和结构体优化
- 总结:自定义数据类型核心要点
- 结构体(struct)
- 联合体(union)
- 枚举(enum)
- typedef
- 位域(bit field)
- 内存对齐原则
- 设计建议
1. 结构体(struct):理论基础
什么是结构体?
结构体是C语言中最重要的自定义数据类型,它允许我们将多个不同类型的数据组合在一起,形成一个新的复合数据类型。
生动比喻:
想象一个"学生档案袋",里面包含:
- 姓名(字符串)
- 年龄(整数)
- 成绩(浮点数)
- 班级(字符串)
这个"档案袋"就是结构体,里面的每项信息就是结构体的成员。
结构体的内存布局
结构体成员在内存中按定义顺序连续存储,但可能会因为内存对齐而存在填充字节。
#include <stdio.h>// 定义结构体
struct Student {char name[20]; // 20字节int age; // 4字节 float score; // 4字节char class[10]; // 10字节
}; // 总大小可能大于38字节(因为有内存对齐)int main() {printf("=== 结构体基础 ===\n");// 查看结构体大小printf("结构体Student大小: %lu字节\n", sizeof(struct Student));printf("各成员大小:\n");printf(" name: %lu字节\n", sizeof(char[20]));printf(" age: %lu字节\n", sizeof(int));printf(" score: %lu字节\n", sizeof(float));printf(" class: %lu字节\n", sizeof(char[10]));return 0;
}
代码解读:
struct Student定义了一个新的数据类型- 结构体包含不同类型的成员
sizeof可以查看结构体总大小- 由于内存对齐,结构体大小可能大于成员大小之和
完整输出结果:
=== 结构体基础 ===
结构体Student大小: 40字节
各成员大小:name: 20字节age: 4字节score: 4字节class: 10字节
2. 结构体的声明和初始化
结构体的三种声明方式
#include <stdio.h>
#include <string.h>// 方式1:先定义结构体类型,再声明变量
struct Point {int x;int y;
};// 方式2:定义结构体类型的同时声明变量
struct Rectangle {int width;int height;
} rect1, rect2; // 直接声明两个变量// 方式3:使用匿名结构体(不推荐,但要知道)
struct {char title[50];char author[50];
} book1, book2;int main() {printf("=== 结构体声明方式 ===\n");// 方式1的变量声明struct Point p1;p1.x = 10;p1.y = 20;printf("点p1: (%d, %d)\n", p1.x, p1.y);// 方式2的变量使用rect1.width = 100;rect1.height = 50;printf("矩形rect1: %d×%d\n", rect1.width, rect1.height);// 方式3的变量使用strcpy(book1.title, "C语言编程");strcpy(book1.author, "张三");printf("书籍: 《%s》- %s\n", book1.title, book1.author);return 0;
}
结构体的多种初始化方式
#include <stdio.h>struct Student {char name[20];int age;float score;
};struct Date {int year;int month;int day;
};int main() {printf("=== 结构体初始化方式 ===\n");// 方式1:顺序初始化struct Student stu1 = {"张三", 20, 90.5};printf("顺序初始化: %s, %d岁, 成绩%.1f\n", stu1.name, stu1.age, stu1.score);// 方式2:指定成员初始化(C99标准)struct Student stu2 = {.age = 22, .score = 85.5, .name = "李四"};printf("指定成员初始化: %s, %d岁, 成绩%.1f\n", stu2.name, stu2.age, stu2.score);// 方式3:部分初始化,未初始化的成员为0struct Student stu3 = {"王五"};printf("部分初始化: %s, %d岁, 成绩%.1f\n", stu3.name, stu3.age, stu3.score);// 方式4:结构体嵌套初始化struct Student stu4 = {"赵六", 21, 88.0};struct Date birthday = {2002, 5, 15};printf("嵌套结构体: 生日 %d年%d月%d日\n", birthday.year, birthday.month, birthday.day);// 方式5:数组式初始化struct Student students[] = {{"小明", 19, 92.5},{"小红", 20, 87.5},{"小刚", 21, 95.0}};printf("\n学生数组:\n");for(int i = 0; i < 3; i++) {printf(" 学生%d: %s, %d岁, 成绩%.1f\n", i+1, students[i].name, students[i].age, students[i].score);}return 0;
}
代码解读:
- 顺序初始化:按定义顺序提供值
- 指定成员初始化:使用
.指定具体成员 - 部分初始化:只提供部分值,其余自动为0
- 结构体嵌套:结构体中可以包含其他结构体
- 结构体数组:可以创建结构体类型的数组
完整输出结果:
=== 结构体初始化方式 ===
顺序初始化: 张三, 20岁, 成绩90.5
指定成员初始化: 李四, 22岁, 成绩85.5
部分初始化: 王五, 0岁, 成绩0.0
嵌套结构体: 生日 2002年5月15日学生数组:学生1: 小明, 19岁, 成绩92.5学生2: 小红, 20岁, 成绩87.5学生3: 小刚, 21岁, 成绩95.0
3. 结构体成员访问和操作
#include <stdio.h>
#include <string.h>struct Employee {char name[50];int id;float salary;char department[30];
};// 函数:打印员工信息
void printEmployee(struct Employee emp) {printf("员工信息:\n");printf(" 姓名: %s\n", emp.name);printf(" 工号: %d\n", emp.id);printf(" 薪资: %.2f\n", emp.salary);printf(" 部门: %s\n", emp.department);
}// 函数:给员工加薪
void giveRaise(struct Employee *emp, float amount) {emp->salary += amount;printf("给 %s 加薪 %.2f\n", emp->name, amount);
}int main() {printf("=== 结构体成员访问 ===\n");// 声明并初始化结构体struct Employee emp1 = {"张三", 1001, 8000.0, "技术部"};// 访问结构体成员printf("直接访问:\n");printf("姓名: %s\n", emp1.name);printf("工号: %d\n", emp1.id);printf("薪资: %.2f\n", emp1.salary);printf("部门: %s\n", emp1.department);// 修改结构体成员printf("\n修改成员:\n");strcpy(emp1.department, "研发部");emp1.salary = 8500.0;printf("修改后部门: %s\n", emp1.department);printf("修改后薪资: %.2f\n", emp1.salary);// 结构体赋值printf("\n结构体赋值:\n");struct Employee emp2 = emp1; // 结构体可以整体赋值emp2.id = 1002;strcpy(emp2.name, "李四");printEmployee(emp2);// 使用指针访问结构体printf("\n指针访问:\n");struct Employee *ptr = &emp1;printf("指针访问姓名: %s\n", ptr->name); // 箭头运算符printf("指针访问工号: %d\n", (*ptr).id); // 点运算符(需要解引用)// 通过函数修改结构体printf("\n通过函数修改:\n");giveRaise(&emp1, 1000.0);printf("加薪后薪资: %.2f\n", emp1.salary);return 0;
}
代码解读:
- 点运算符
.:用于直接访问结构体成员 - 箭头运算符
->:用于通过指针访问结构体成员 - 结构体可以整体赋值(按值复制)
- 传递结构体指针给函数可以修改原结构体
- 传递结构体本身给函数是传值(创建副本)
完整输出结果:
=== 结构体成员访问 ===
直接访问:
姓名: 张三
工号: 1001
薪资: 8000.00
部门: 技术部修改成员:
修改后部门: 研发部
修改后薪资: 8500.00结构体赋值:
员工信息:姓名: 李四工号: 1002薪资: 8500.00部门: 研发部指针访问:
指针访问姓名: 张三
指针访问工号: 1001通过函数修改:
给 张三 加薪 1000.00
加薪后薪资: 9500.00
4. 结构体与函数
#include <stdio.h>
#include <string.h>struct Book {char title[100];char author[50];float price;int pages;
};// 传值:不会修改原结构体
void printBookByValue(struct Book book) {printf("传值打印:\n");printf(" 书名: 《%s》\n", book.title);printf(" 作者: %s\n", book.author);printf(" 价格: %.2f元\n", book.price);printf(" 页数: %d页\n", book.pages);// 修改副本,不影响原结构体book.price = 0.0;
}// 传地址:可以修改原结构体
void printBookByPointer(struct Book *book) {printf("传地址打印:\n");printf(" 书名: 《%s》\n", book->title);printf(" 作者: %s\n", book->author);printf(" 价格: %.2f元\n", book->price);printf(" 页数: %d页\n", book->pages);// 修改会影响原结构体book->price = 88.0;
}// 返回结构体
struct Book createBook(const char *title, const char *author, float price, int pages) {struct Book newBook;strcpy(newBook.title, title);strcpy(newBook.author, author);newBook.price = price;newBook.pages = pages;return newBook;
}// 返回结构体指针(注意:不要返回局部变量的地址)
struct Book* createBookPointer(struct Book *book, const char *title, const char *author, float price, int pages) {strcpy(book->title, title);strcpy(book->author, author);book->price = price;book->pages = pages;return book;
}int main() {printf("=== 结构体与函数 ===\n");// 创建结构体struct Book book1 = {"C语言程序设计", "谭浩强", 45.5, 350};// 传值调用printf("传值调用前: %.2f元\n", book1.price);printBookByValue(book1);printf("传值调用后: %.2f元 (未改变)\n", book1.price);printf("\n");// 传地址调用printf("传地址调用前: %.2f元\n", book1.price);printBookByPointer(&book1);printf("传地址调用后: %.2f元 (已改变)\n", book1.price);printf("\n");// 返回结构体struct Book book2 = createBook("算法导论", "Thomas Cormen", 128.0, 1200);printBookByPointer(&book2);printf("\n");// 返回结构体指针struct Book book3;struct Book *bookPtr = createBookPointer(&book3, "深入理解计算机系统", "Randal Bryant", 99.0, 800);printBookByPointer(bookPtr);return 0;
}
代码解读:
- 传值调用:创建结构体副本,修改不影响原结构体
- 传地址调用:可以直接修改原结构体
- 函数可以返回结构体(按值返回)
- 函数可以返回结构体指针
- 注意:不要返回局部结构体变量的地址
完整输出结果:
=== 结构体与函数 ===
传值调用前: 45.50元
传值打印:书名: 《C语言程序设计》作者: 谭浩强价格: 45.50元页数: 350页
传值调用后: 45.50元 (未改变)传地址调用前: 45.50元
传地址打印:书名: 《C语言程序设计》作者: 谭浩强价格: 45.50元页数: 350页
传地址调用后: 88.00元 (已改变)传地址打印:书名: 《算法导论》作者: Thomas Cormen价格: 128.00元页数: 1200页传地址打印:书名: 《深入理解计算机系统》作者: Randal Bryant价格: 99.00元页数: 800页
5. 结构体数组和嵌套结构体
#include <stdio.h>
#include <string.h>// 定义日期结构体
struct Date {int year;int month;int day;
};// 定义地址结构体
struct Address {char street[100];char city[50];char zipcode[20];
};// 定义学生结构体(嵌套)
struct Student {char name[50];int id;struct Date birthday; // 嵌套结构体struct Address address; // 嵌套结构体float grades[3]; // 数组成员
};// 打印学生信息
void printStudent(struct Student stu) {printf("学生信息:\n");printf(" 姓名: %s\n", stu.name);printf(" 学号: %d\n", stu.id);printf(" 生日: %d年%d月%d日\n", stu.birthday.year, stu.birthday.month, stu.birthday.day);printf(" 地址: %s, %s, %s\n", stu.address.street, stu.address.city, stu.address.zipcode);printf(" 成绩: 数学%.1f, 英语%.1f, C语言%.1f\n", stu.grades[0], stu.grades[1], stu.grades[2]);// 计算平均分float average = (stu.grades[0] + stu.grades[1] + stu.grades[2]) / 3;printf(" 平均分: %.2f\n", average);
}int main() {printf("=== 结构体数组和嵌套结构体 ===\n");// 创建结构体数组struct Student students[3];// 初始化第一个学生strcpy(students[0].name, "张三");students[0].id = 1001;students[0].birthday.year = 2000;students[0].birthday.month = 5;students[0].birthday.day = 15;strcpy(students[0].address.street, "人民路123号");strcpy(students[0].address.city, "北京");strcpy(students[0].address.zipcode, "100000");students[0].grades[0] = 85.5; // 数学students[0].grades[1] = 92.0; // 英语students[0].grades[2] = 88.5; // C语言// 初始化第二个学生strcpy(students[1].name, "李四");students[1].id = 1002;students[1].birthday = (struct Date){2001, 8, 20}; // 复合字面量students[1].address = (struct Address){"中山路456号", "上海", "200000"};students[1].grades[0] = 90.0;students[1].grades[1] = 87.5;students[1].grades[2] = 95.0;// 初始化第三个学生strcpy(students[2].name, "王五");students[2].id = 1003;students[2].birthday.year = 1999;students[2].birthday.month = 3;students[2].birthday.day = 10;strcpy(students[2].address.street, "解放路789号");strcpy(students[2].address.city, "广州");strcpy(students[2].address.zipcode, "510000");float temp_grades[] = {78.0, 85.5, 92.0};memcpy(students[2].grades, temp_grades, sizeof(temp_grades));// 打印所有学生信息printf("学生列表:\n");for(int i = 0; i < 3; i++) {printf("\n学生%d:\n", i + 1);printStudent(students[i]);}// 查找最高分学生printf("\n=== 查找最高分学生 ===\n");int top_index = 0;float top_average = 0;for(int i = 0; i < 3; i++) {float avg = (students[i].grades[0] + students[i].grades[1] + students[i].grades[2]) / 3;if(avg > top_average) {top_average = avg;top_index = i;}}printf("最高分学生: %s, 平均分: %.2f\n", students[top_index].name, top_average);return 0;
}
代码解读:
- 结构体嵌套:结构体中可以包含其他结构体
- 结构体数组:创建多个结构体实例
- 复合字面量:
(struct Date){2001, 8, 20} - 数组成员:结构体中可以包含数组
- 内存复制:
memcpy用于复制数组数据
完整输出结果:
=== 结构体数组和嵌套结构体 ===
学生列表:学生1:
学生信息:姓名: 张三学号: 1001生日: 2000年5月15日地址: 人民路123号, 北京, 100000成绩: 数学85.5, 英语92.0, C语言88.5平均分: 88.67学生2:
学生信息:姓名: 李四学号: 1002生日: 2001年8月20日地址: 中山路456号, 上海, 200000成绩: 数学90.0, 英语87.5, C语言95.0平均分: 90.83学生3:
学生信息:姓名: 王五学号: 1003生日: 1999年3月10日地址: 解放路789号, 广州, 510000成绩: 数学78.0, 英语85.5, C语言92.0平均分: 85.17=== 查找最高分学生 ===
最高分学生: 李四, 平均分: 90.83
6. 联合体(union):理论基础
什么是联合体?
联合体是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。联合体的所有成员共享同一块内存空间,因此联合体的大小等于其最大成员的大小。
生动比喻:
想象一个"多功能房间",这个房间可以是:
- 卧室(放床)
- 书房(放书桌)
- 餐厅(放餐桌)
但在同一时间,它只能用作一种功能。联合体就像这个多功能房间,同一时间只能存储一个成员的值。
联合体的内存特性
- 所有成员共享同一块内存
- 大小等于最大成员的大小
- 修改一个成员会影响其他成员
#include <stdio.h>// 定义联合体
union Data {int i;float f;char str[20];double d;
};int main() {printf("=== 联合体基础 ===\n");union Data data;// 查看联合体大小printf("联合体Data大小: %lu字节\n", sizeof(union Data));printf("各成员大小:\n");printf(" int: %lu字节\n", sizeof(int));printf(" float: %lu字节\n", sizeof(float));printf(" char[20]: %lu字节\n", sizeof(char[20]));printf(" double: %lu字节\n", sizeof(double));printf("\n=== 联合体成员共享内存 ===\n");// 使用整数成员data.i = 100;printf("设置 data.i = 100\n");printf("data.i = %d\n", data.i);printf("data.f = %f (无意义)\n", data.f);printf("data.str = \"%s\" (无意义)\n", data.str);printf("\n");// 使用浮点成员(会覆盖整数值)data.f = 3.14;printf("设置 data.f = 3.14\n");printf("data.i = %d (无意义)\n", data.i);printf("data.f = %f\n", data.f);printf("data.str = \"%s\" (无意义)\n", data.str);printf("\n");// 使用字符串成员(会覆盖浮点值)strcpy(data.str, "Hello");printf("设置 data.str = \"Hello\"\n");printf("data.i = %d (无意义)\n", data.i);printf("data.f = %f (无意义)\n", data.f);printf("data.str = \"%s\"\n", data.str);return 0;
}
代码解读:
- 联合体所有成员共享同一块内存
- 修改一个成员会覆盖其他成员的值
- 联合体大小等于最大成员的大小
- 同时只能有一个成员存储有效数据
完整输出结果:
=== 联合体基础 ===
联合体Data大小: 24字节
各成员大小:int: 4字节float: 4字节char[20]: 20字节double: 8字节=== 联合体成员共享内存 ===
设置 data.i = 100
data.i = 100
data.f = 0.000000 (无意义)
data.str = "d" (无意义)设置 data.f = 3.14
data.i = 1078523331 (无意义)
data.f = 3.140000
data.str = "C�Q@" (无意义)设置 data.str = "Hello"
data.i = 1819043144 (无意义)
data.f = 0.000000 (无意义)
data.str = "Hello"
7. 联合体的实际应用
#include <stdio.h>
#include <string.h>// 应用1:多种类型数据存储
union Value {int int_val;float float_val;char char_val;char str_val[20];
};// 应用2:硬件寄存器模拟
union StatusRegister {unsigned int full_register;struct {unsigned int error_flag : 1; // 位域:1位unsigned int ready_flag : 1; // 1位unsigned int busy_flag : 1; // 1位unsigned int : 5; // 保留位:5位unsigned int device_id : 8; // 设备ID:8位unsigned int : 16; // 保留位:16位} bits;
};// 应用3:类型转换
union Converter {float f;int i;unsigned char bytes[4];
};// 应用4:变体记录( discriminated union )
struct Variant {int type; // 0=int, 1=float, 2=stringunion {int int_data;float float_data;char string_data[50];} data;
};void printVariant(struct Variant v) {switch(v.type) {case 0:printf("整数: %d\n", v.data.int_data);break;case 1:printf("浮点数: %.2f\n", v.data.float_data);break;case 2:printf("字符串: %s\n", v.data.string_data);break;default:printf("未知类型\n");}
}int main() {printf("=== 联合体实际应用 ===\n");// 应用1:多种类型数据存储printf("1. 多种类型数据存储:\n");union Value val;val.int_val = 100;printf(" 整数值: %d\n", val.int_val);val.float_val = 3.14;printf(" 浮点值: %.2f\n", val.float_val);strcpy(val.str_val, "Hello");printf(" 字符串: %s\n", val.str_val);// 应用2:硬件寄存器模拟printf("\n2. 硬件寄存器模拟:\n");union StatusRegister status;status.full_register = 0;// 设置各个标志位status.bits.error_flag = 1;status.bits.ready_flag = 1;status.bits.device_id = 0xAB;printf(" 完整寄存器: 0x%08X\n", status.full_register);printf(" 错误标志: %d\n", status.bits.error_flag);printf(" 就绪标志: %d\n", status.bits.ready_flag);printf(" 忙标志: %d\n", status.bits.busy_flag);printf(" 设备ID: 0x%02X\n", status.bits.device_id);// 应用3:类型转换printf("\n3. 类型转换:\n");union Converter conv;conv.f = 3.14159;printf(" 浮点数: %.5f\n", conv.f);printf(" 整数表示: %d\n", conv.i);printf(" 字节表示: ");for(int i = 0; i < 4; i++) {printf("%02X ", conv.bytes[i]);}printf("\n");// 应用4:变体记录printf("\n4. 变体记录:\n");struct Variant var1, var2, var3;var1.type = 0;var1.data.int_data = 42;var2.type = 1;var2.data.float_data = 3.14;var3.type = 2;strcpy(var3.data.string_data, "Hello World");printVariant(var1);printVariant(var2);printVariant(var3);return 0;
}
代码解读:
- 多种类型存储:根据需要存储不同类型数据
- 硬件寄存器:使用位域模拟硬件寄存器
- 类型转换:查看浮点数的内部表示
- 变体记录:使用标签区分联合体中存储的数据类型
完整输出结果:
=== 联合体实际应用 ===
1. 多种类型数据存储:整数值: 100浮点值: 3.14字符串: Hello2. 硬件寄存器模拟:完整寄存器: 0x0000AB03错误标志: 1就绪标志: 1忙标志: 0设备ID: 0xAB3. 类型转换:浮点数: 3.14159整数表示: 1078530000字节表示: D0 0F 49 40 4. 变体记录:
整数: 42
浮点数: 3.14
字符串: Hello World
8. 枚举(enum):理论基础
什么是枚举?
枚举是一种用户定义的数据类型,它由一组命名的整型常量组成。枚举提高了代码的可读性和可维护性。
生动比喻:
想象一个"星期选择器",它有7个固定的选项:
- 星期一
- 星期二
- …
- 星期日
枚举就像这个选择器,提供了一组有意义的名称来代替数字。
#include <stdio.h>// 定义枚举
enum Weekday {MONDAY, // 0TUESDAY, // 1WEDNESDAY, // 2THURSDAY, // 3FRIDAY, // 4SATURDAY, // 5SUNDAY // 6
};// 指定值的枚举
enum Color {RED = 1,GREEN = 2,BLUE = 4,YELLOW = RED + GREEN, // 3WHITE = RED + GREEN + BLUE // 7
};// 枚举作为函数参数
void printDay(enum Weekday day) {switch(day) {case MONDAY: printf("星期一"); break;case TUESDAY: printf("星期二"); break;case WEDNESDAY: printf("星期三"); break;case THURSDAY: printf("星期四"); break;case FRIDAY: printf("星期五"); break;case SATURDAY: printf("星期六"); break;case SUNDAY: printf("星期日"); break;default: printf("未知");}
}int main() {printf("=== 枚举基础 ===\n");// 声明枚举变量enum Weekday today = WEDNESDAY;enum Weekday tomorrow = THURSDAY;printf("今天: ");printDay(today);printf(" (值: %d)\n", today);printf("明天: ");printDay(tomorrow);printf(" (值: %d)\n", tomorrow);printf("\n=== 指定值的枚举 ===\n");printf("RED = %d\n", RED);printf("GREEN = %d\n", GREEN);printf("BLUE = %d\n", BLUE);printf("YELLOW = %d\n", YELLOW);printf("WHITE = %d\n", WHITE);printf("\n=== 枚举运算 ===\n");enum Weekday day = MONDAY;// 枚举可以参与整数运算for(day = MONDAY; day <= SUNDAY; day++) {printf("枚举值 %d: ", day);printDay(day);printf("\n");}// 枚举比较if(today == WEDNESDAY) {printf("\n今天确实是星期三!\n");}return 0;
}
代码解读:
- 默认枚举值从0开始递增
- 可以显式指定枚举值
- 枚举值可以参与算术运算
- 枚举提高了代码的可读性
- 枚举常用于switch语句
完整输出结果:
=== 枚举基础 ===
今天: 星期三 (值: 2)
明天: 星期四 (值: 3)=== 指定值的枚举 ===
RED = 1
GREEN = 2
BLUE = 4
YELLOW = 3
WHITE = 7=== 枚举运算 ===
枚举值 0: 星期一
枚举值 1: 星期二
枚举值 2: 星期三
枚举值 3: 星期四
枚举值 4: 星期五
枚举值 5: 星期六
枚举值 6: 星期日今天确实是星期三!
9. typedef:类型别名
什么是typedef?
typedef用于为已有的数据类型创建新的名称(别名)。它并不创建新的数据类型,只是为现有类型创建一个同义词。
生动比喻:
想象给一个人起"外号":
- 本名:“张明”
- 外号:“小明”
typedef就像起外号,让类型名称更简短、更有意义。
#include <stdio.h>
#include <string.h>// 为基本类型定义别名
typedef int Integer;
typedef float Real;
typedef char Byte;// 为数组定义别名
typedef int IntArray[10];
typedef char String[50];// 为结构体定义别名
typedef struct {char name[50];int age;float salary;
} Employee;// 为联合体定义别名
typedef union {int i;float f;char str[20];
} Value;// 为枚举定义别名
typedef enum {JAN, FEB, MAR, APR, MAY, JUN,JUL, AUG, SEP, OCT, NOV, DEC
} Month;// 为指针定义别名
typedef int* IntPtr;
typedef Employee* EmployeePtr;// 为函数指针定义别名
typedef int (*CompareFunc)(int, int);// 比较函数
int ascending(int a, int b) { return a > b; }
int descending(int a, int b) { return a < b; }// 使用函数指针别名
void sortArray(int arr[], int size, CompareFunc compare) {for(int i = 0; i < size-1; i++) {for(int j = 0; j < size-i-1; j++) {if(compare(arr[j], arr[j+1])) {int temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}
}int main() {printf("=== typedef类型别名 ===\n");// 使用基本类型别名Integer num = 100;Real price = 99.99;Byte ch = 'A';printf("基本类型别名: num=%d, price=%.2f, ch=%c\n", num, price, ch);// 使用数组别名IntArray numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};String name = "张三";printf("数组别名: name=%s, numbers[0]=%d\n", name, numbers[0]);// 使用结构体别名(不需要struct关键字)Employee emp = {"李四", 30, 8000.0};printf("结构体别名: %s, %d岁, 工资%.2f\n", emp.name, emp.age, emp.salary);// 使用联合体别名Value val;val.i = 100;printf("联合体别名: val.i=%d\n", val.i);// 使用枚举别名Month currentMonth = JAN;printf("枚举别名: 当前月份=%d\n", currentMonth);// 使用指针别名IntPtr ptr = #EmployeePtr empPtr = &emp;printf("指针别名: *ptr=%d, empPtr->name=%s\n", *ptr, empPtr->name);// 使用函数指针别名printf("\n函数指针别名:\n");int arr[] = {5, 2, 8, 1, 9};int size = 5;printf("原数组: ");for(int i = 0; i < size; i++) printf("%d ", arr[i]);printf("\n");sortArray(arr, size, ascending);printf("升序排序: ");for(int i = 0; i < size; i++) printf("%d ", arr[i]);printf("\n");sortArray(arr, size, descending);printf("降序排序: ");for(int i = 0; i < size; i++) printf("%d ", arr[i]);printf("\n");return 0;
}
代码解读:
- 为基本类型创建简短别名
- 为数组类型创建别名
- 为结构体/联合体/枚举创建别名(使用时不需要关键字)
- 为指针类型创建别名
- 为函数指针创建别名,提高可读性
完整输出结果:
=== typedef类型别名 ===
基本类型别名: num=100, price=99.99, ch=A
数组别名: name=张三, numbers[0]=1
结构体别名: 李四, 30岁, 工资8000.00
联合体别名: val.i=100
枚举别名: 当前月份=0函数指针别名:
原数组: 5 2 8 1 9
升序排序: 1 2 5 8 9
降序排序: 9 8 5 2 1
10. 位域(bit field):理论基础
什么是位域?
位域是结构体的一种特殊成员,允许我们按位来指定成员变量所占的内存长度。这对于节省内存空间特别有用。
生动比喻:
想象一个"开关面板",上面有很多小开关:
- 每个开关只需要1位(开/关)
- 但通常变量至少占用1字节(8位)
位域就像这个开关面板,让我们可以精确控制每个开关占用的位数。
#include <stdio.h>// 位域结构体
struct BitFieldExample {unsigned int flag1 : 1; // 1位unsigned int flag2 : 1; // 1位unsigned int flag3 : 1; // 1位unsigned int : 5; // 5位未使用(填充)unsigned int value : 4; // 4位,可表示0-15unsigned int : 0; // 强制对齐到下一个边界unsigned int mode : 3; // 3位,从新的存储单元开始
};// 硬件寄存器模拟
struct StatusRegister {unsigned int error : 1; // 错误标志unsigned int ready : 1; // 就绪标志 unsigned int busy : 1; // 忙标志unsigned int : 5; // 保留unsigned int device_id : 8; // 设备IDunsigned int : 16; // 保留
};// IP地址表示
struct IPAddress {unsigned char a : 8;unsigned char b : 8;unsigned char c : 8;unsigned char d : 8;
};// 颜色表示(RGB)
struct Color {unsigned int red : 5; // 5位红色(0-31)unsigned int green : 6; // 6位绿色(0-63)unsigned int blue : 5; // 5位蓝色(0-31)
};void printBinary(unsigned int num, int bits) {for(int i = bits-1; i >= 0; i--) {printf("%d", (num >> i) & 1);}
}int main() {printf("=== 位域基础 ===\n");// 基本位域使用struct BitFieldExample bf;bf.flag1 = 1;bf.flag2 = 0;bf.flag3 = 1;bf.value = 12; // 4位最大15bf.mode = 5; // 3位最大7printf("位域结构体大小: %lu字节\n", sizeof(struct BitFieldExample));printf("flag1=%d, flag2=%d, flag3=%d, value=%d, mode=%d\n", bf.flag1, bf.flag2, bf.flag3, bf.value, bf.mode);// 硬件寄存器模拟printf("\n=== 硬件寄存器模拟 ===\n");struct StatusRegister status;status.error = 1;status.ready = 1;status.busy = 0;status.device_id = 0xAB;printf("状态寄存器:\n");printf(" error: %d\n", status.error);printf(" ready: %d\n", status.ready);printf(" busy: %d\n", status.busy);printf(" device_id: 0x%02X\n", status.device_id);// IP地址表示printf("\n=== IP地址表示 ===\n");struct IPAddress ip = {192, 168, 1, 1};printf("IP地址: %d.%d.%d.%d\n", ip.a, ip.b, ip.c, ip.d);printf("IP结构体大小: %lu字节\n", sizeof(struct IPAddress));// 颜色表示printf("\n=== 颜色表示 ===\n");struct Color color;color.red = 31; // 最大31color.green = 63; // 最大63color.blue = 0; // 最大31printf("颜色值: R=%d, G=%d, B=%d\n", color.red, color.green, color.blue);printf("颜色结构体大小: %lu字节\n", sizeof(struct Color));// 位域的范围限制printf("\n=== 位域范围限制 ===\n");struct BitFieldExample test;// 测试超出范围test.value = 20; // 4位最大15,20会溢出printf("赋值20给4位域,实际值: %d\n", test.value);test.mode = 10; // 3位最大7,10会溢出printf("赋值10给3位域,实际值: %d\n", test.mode);return 0;
}
代码解读:
: 数字指定成员占用的位数- 无名位域用于填充对齐
: 0强制从新的存储单元开始- 位域可以节省内存空间
- 注意位域的范围限制
完整输出结果:
=== 位域基础 ===
位域结构体大小: 8字节
flag1=1, flag2=0, flag3=1, value=12, mode=5=== 硬件寄存器模拟 ===
状态寄存器:error: 1ready: 1busy: 0device_id: 0xAB=== IP地址表示 ===
IP地址: 192.168.1.1
IP结构体大小: 4字节=== 颜色表示 ===
颜色值: R=31, G=63, B=0
颜色结构体大小: 4字节=== 位域范围限制 ===
赋值20给4位域,实际值: 4
赋值10给3位域,实际值: 2
11. 综合实战:学生管理系统
#include <stdio.h>
#include <string.h>
#include <stdlib.h>// 使用typedef定义各种类型
typedef enum {MALE,FEMALE
} Gender;typedef struct {int year;int month;int day;
} Date;typedef struct {char province[50]; // 省char city[50]; // 市char district[50]; // 区char detail[100]; // 详细地址
} Address;typedef union {char phone[15]; // 手机号char email[50]; // 邮箱
} Contact;typedef struct {char name[50];int id;Gender gender;Date birthday;Address address;Contact contact;int contact_type; // 0=手机, 1=邮箱float scores[5]; // 5门课程成绩
} Student;// 函数声明
void inputStudent(Student *stu);
void printStudent(const Student *stu);
float calculateAverage(const Student *stu);
void findTopStudent(Student students[], int count);// 输入学生信息
void inputStudent(Student *stu) {static int next_id = 1001; // 静态变量,自动生成学号printf("\n=== 输入学生信息 ===\n");printf("姓名: ");scanf("%s", stu->name);stu->id = next_id++;printf("性别 (0-男, 1-女): ");int gender;scanf("%d", &gender);stu->gender = (Gender)gender;printf("生日 (年 月 日): ");scanf("%d %d %d", &stu->birthday.year, &stu->birthday.month, &stu->birthday.day);printf("地址 (省 市 区): ");scanf("%s %s %s", stu->address.province, stu->address.city, stu->address.district);printf("详细地址: ");getchar(); // 清除缓冲区fgets(stu->address.detail, 100, stdin);stu->address.detail[strcspn(stu->address.detail, "\n")] = 0; // 去除换行符printf("联系方式类型 (0-手机, 1-邮箱): ");scanf("%d", &stu->contact_type);if(stu->contact_type == 0) {printf("手机号: ");scanf("%s", stu->contact.phone);} else {printf("邮箱: ");scanf("%s", stu->contact.email);}printf("5门课程成绩: ");for(int i = 0; i < 5; i++) {scanf("%f", &stu->scores[i]);}
}// 打印学生信息
void printStudent(const Student *stu) {printf("\n=== 学生信息 ===\n");printf("学号: %d\n", stu->id);printf("姓名: %s\n", stu->name);printf("性别: %s\n", stu->gender == MALE ? "男" : "女");printf("生日: %d年%d月%d日\n", stu->birthday.year, stu->birthday.month, stu->birthday.day);printf("地址: %s%s%s%s\n", stu->address.province, stu->address.city, stu->address.district, stu->address.detail);if(stu->contact_type == 0) {printf("手机: %s\n", stu->contact.phone);} else {printf("邮箱: %s\n", stu->contact.email);}printf("成绩: ");for(int i = 0; i < 5; i++) {printf("%.1f ", stu->scores[i]);}printf("\n平均分: %.2f\n", calculateAverage(stu));
}// 计算平均分
float calculateAverage(const Student *stu) {float sum = 0;for(int i = 0; i < 5; i++) {sum += stu->scores[i];}return sum / 5;
}// 查找成绩最好的学生
void findTopStudent(Student students[], int count) {if(count == 0) return;int top_index = 0;float top_avg = calculateAverage(&students[0]);for(int i = 1; i < count; i++) {float avg = calculateAverage(&students[i]);if(avg > top_avg) {top_avg = avg;top_index = i;}}printf("\n=== 成绩最好的学生 ===\n");printStudent(&students[top_index]);
}// 按学号查找学生
Student* findStudentById(Student students[], int count, int id) {for(int i = 0; i < count; i++) {if(students[i].id == id) {return &students[i];}}return NULL;
}int main() {printf("=== 学生管理系统 ===\n");Student students[10];int student_count = 0;int choice;do {printf("\n=== 菜单 ===\n");printf("1. 添加学生\n");printf("2. 显示所有学生\n");printf("3. 查找成绩最好的学生\n");printf("4. 按学号查找学生\n");printf("0. 退出\n");printf("请选择: ");scanf("%d", &choice);switch(choice) {case 1:if(student_count < 10) {inputStudent(&students[student_count]);student_count++;printf("添加成功!\n");} else {printf("学生数量已达上限!\n");}break;case 2:if(student_count == 0) {printf("没有学生信息!\n");} else {printf("\n=== 所有学生信息 ===\n");for(int i = 0; i < student_count; i++) {printStudent(&students[i]);}}break;case 3:if(student_count == 0) {printf("没有学生信息!\n");} else {findTopStudent(students, student_count);}break;case 4:if(student_count == 0) {printf("没有学生信息!\n");} else {int search_id;printf("请输入学号: ");scanf("%d", &search_id);Student *found = findStudentById(students, student_count, search_id);if(found != NULL) {printStudent(found);} else {printf("未找到学号为 %d 的学生!\n", search_id);}}break;case 0:printf("谢谢使用!\n");break;default:printf("无效选择!\n");}} while(choice != 0);return 0;
}
代码解读:
- 综合使用enum、struct、union、typedef
- 结构体嵌套和联合体的实际应用
- 完整的增删改查功能
- 静态变量用于自动生成学号
- 函数指针和回调机制
完整输出结果示例:
=== 学生管理系统 ====== 菜单 ===
1. 添加学生
2. 显示所有学生
3. 查找成绩最好的学生
4. 按学号查找学生
0. 退出
请选择: 1=== 输入学生信息 ===
姓名: 张三
性别 (0-男, 1-女): 0
生日 (年 月 日): 2000 5 15
地址 (省 市 区): 北京 北京 海淀
详细地址: 中关村大街1号
联系方式类型 (0-手机, 1-邮箱): 0
手机号: 13800138000
5门课程成绩: 85 90 78 92 88
添加成功!=== 菜单 ===
1. 添加学生
2. 显示所有学生
3. 查找成绩最好的学生
4. 按学号查找学生
0. 退出
请选择: 2=== 所有学生信息 ====== 学生信息 ===
学号: 1001
姓名: 张三
性别: 男
生日: 2000年5月15日
地址: 北京北京海淀中关村大街1号
手机: 13800138000
成绩: 85.0 90.0 78.0 92.0 88.0
平均分: 86.60
12. 内存对齐和结构体优化
#include <stdio.h>// 测试内存对齐的结构体
struct BadAlignment {char a; // 1字节int b; // 4字节char c; // 1字节double d; // 8字节
};struct GoodAlignment {double d; // 8字节int b; // 4字节char a; // 1字节char c; // 1字节
};// 使用位域优化
struct CompactData {unsigned int flag1 : 1;unsigned int flag2 : 1;unsigned int flag3 : 1;unsigned int type : 4;unsigned int value : 10;unsigned int : 0; // 强制对齐unsigned int count : 16;
};// 打包结构体(取消对齐)
#pragma pack(push, 1) // 保存当前对齐设置,设置为1字节对齐
struct PackedStruct {char a;int b;char c;double d;
};
#pragma pack(pop) // 恢复之前的对齐设置int main() {printf("=== 内存对齐和优化 ===\n");printf("结构体大小比较:\n");printf("BadAlignment: %lu字节\n", sizeof(struct BadAlignment));printf("GoodAlignment: %lu字节\n", sizeof(struct GoodAlignment));printf("CompactData: %lu字节\n", sizeof(struct CompactData));printf("PackedStruct: %lu字节\n", sizeof(struct PackedStruct));printf("\n成员偏移量分析:\n");struct BadAlignment bad;printf("BadAlignment成员偏移:\n");printf(" a: %lu\n", (char*)&bad.a - (char*)&bad);printf(" b: %lu\n", (char*)&bad.b - (char*)&bad);printf(" c: %lu\n", (char*)&bad.c - (char*)&bad);printf(" d: %lu\n", (char*)&bad.d - (char*)&bad);struct GoodAlignment good;printf("\nGoodAlignment成员偏移:\n");printf(" d: %lu\n", (char*)&good.d - (char*)&good);printf(" b: %lu\n", (char*)&good.b - (char*)&good);printf(" a: %lu\n", (char*)&good.a - (char*)&good);printf(" c: %lu\n", (char*)&good.c - (char*)&good);printf("\n=== 优化建议 ===\n");printf("1. 按成员大小降序排列\n");printf("2. 相同类型成员放在一起\n");printf("3. 使用位域节省空间\n");printf("4. 谨慎使用#pragma pack,可能影响性能\n");return 0;
}
代码解读:
- 内存对齐原则:成员偏移量是成员大小的整数倍
- 优化排列:按大小降序排列减少填充
- 位域优化:对标志位使用位域
- 打包结构体:使用
#pragma pack取消对齐
完整输出结果:
=== 内存对齐和优化 ===
结构体大小比较:
BadAlignment: 24字节
GoodAlignment: 16字节
CompactData: 8字节
PackedStruct: 13字节成员偏移量分析:
BadAlignment成员偏移:a: 0b: 4c: 8d: 16GoodAlignment成员偏移:d: 0b: 8a: 12c: 13=== 优化建议 ===
1. 按成员大小降序排列
2. 相同类型成员放在一起
3. 使用位域节省空间
4. 谨慎使用#pragma pack,可能影响性能
总结:自定义数据类型核心要点
结构体(struct)
- 用途:组合不同类型的数据
- 特点:成员独立,内存连续,可能有填充
- 操作:
.访问成员,->通过指针访问
联合体(union)
- 用途:节省内存,同一时间只使用一个成员
- 特点:成员共享内存,大小等于最大成员
- 应用:变体记录、类型转换、硬件寄存器
枚举(enum)
- 用途:提高代码可读性,定义命名常量
- 特点:本质是整数,支持算术运算
- 应用:状态码、选项、分类
typedef
- 用途:创建类型别名,提高可读性
- 特点:不创建新类型,只是别名
- 应用:简化复杂类型声明
位域(bit field)
- 用途:精确控制内存使用,节省空间
- 特点:按位分配,有范围限制
- 应用:硬件寄存器、标志位、压缩数据
内存对齐原则
- 结构体大小是最大成员大小的整数倍
- 成员偏移量是成员大小的整数倍
- 合理排列成员可以减少填充字节
设计建议
- 优先使用结构体组织相关数据
- 谨慎使用联合体,确保类型安全
- 多用枚举代替魔法数字
- 使用typedef简化复杂类型
- 考虑内存对齐优化结构体布局
