结构体
一般结构体写在单独的 .h 文件中。
struct 结构体名称
{
// 结构体成员列表
};
结构体赋值方法
- 按照结构体顺序赋值
-
打乱顺序赋值 (不需要遵循结构体顺序)
-
逐个赋值
-
定义结构体时添加别名,利用别名赋值
-
在定义结构体时赋值
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 成员列表 /* struct students { char name[20]; unsigned int age; char tel[16]; float scores[3]; char sex; }; */ /* struct students { char name[20]; unsigned int age; char tel[16]; float scores[3]; char sex; } stu; // 可以使用别名 */ // 在定义结构体时赋值 struct students { char name[20]; unsigned int age; char tel[16]; float scores[3]; char sex; } stu = { "aa", 10, "123456", 30.1, 40.1, 50.1, 'M' }; int main(void) { // 按结构体顺序赋值 //struct students stu = { "aa", 10, "123456", 30.1, 40.1, 50.1, 'M' }; // 打乱顺序赋值 //struct students stu = { .sex = 'M', .name = "aa", .age = 10, .tel = "123456", .scores[0] = 30.1, .scores[1] = 40.1, .scores[2] = 50.1 }; // 逐个赋值 //struct students stu; /* strcpy(stu.name, "aa"); // 字符串需要通过 strcpy 函数赋值 stu.age = 10; strcpy(stu.tel, "123456"); stu.scores[0] = 30.1; stu.scores[1] = 40.1; stu.scores[2] = 50.1; stu.sex = 'M'; */ // 输出 printf("name: %s, age: %u, tel: %s, scores: %.2f %.2f %.2f, sex: %c\n", stu.name, stu.age, stu.tel, stu.scores[0], stu.scores[1], stu.scores[2], stu.sex); system("pause"); return 0; }
结构体的基本使用
- 使用 typedef 可以为结构体取别名
-
不加 typedef 可以直接创建一个结构体变量
-
结构体声明可以是匿名的
-
结构体的创建 (栈上/堆上)
-
结构体变量数组的创建 (栈上/堆上)
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include // typedef 取别名 typedef struct Person1 { char name[64]; int age; } myPerson1; // 不加 typedef 直接创建一个结构体变量 struct Person2 { char name[64]; int age; } myPerson2 = { "bbb", 24 }; // 结构体变量 // 匿名结构体 struct { char name[64]; int age; } myPerson3 = { "bbb", 24 }; // 结构体变量 void test01() { struct Person2 p1 = { "aaa", 14 }; struct Person2 p2 = { "ccc", 18 }; printf("name: %s, age: %d\n", p1.name, p1.age); printf("name: %s, age: %d\n", myPerson2.name, myPerson2.age); } // 结构体的创建 void test02() { // 创建在栈上 struct Person2 p1 = { "aaa", 14 }; printf("name: %s, age: %d\n", p1.name, p1.age); // name: aaa, age: 14 // 创建在堆上 struct Person2* p2 = malloc(sizeof(struct Person2)); strcpy(p2->name, "bbb"); p2->age = 20; printf("name: %s, age: %d\n", p2->name, p2->age); // name: bbb, age: 20 if (p2 != NULL) { free(p2); p2 = NULL; } } // 打印结构体数组 void printfStruct(struct Person2* persons, int len) { for (int i = 0; i < len; i++) { printf("name: %s, age: %d\n", persons[i].name, persons[i].age); } } // 结构体变量数组的创建 void test03() { // 在栈上分配内存 struct Person2 persons[] = { {"aaa", 10}, {"bbb", 20}, {"ccc", 30} }; int len = sizeof(persons) / sizeof(struct Person2); // 3 行数 printf("%d %d\n", sizeof(persons), sizeof(struct Person2)); // 204 68 printfStruct(persons, len); // 在堆上分配内存 struct Person2* p2 = malloc(sizeof(struct Person2) * 4); for (int i = 0; i < 4; i++) { sprintf(p2[i].name, "name_%d", i + 1); p2[i].age = i + 20; } printfStruct(p2, 4); if (p2 != NULL) { free(p2); p2 = NULL; } } int main(void) { test01(); printf("-------------------\n"); test02(); printf("-------------------\n"); test03(); system("pause"); return 0; }
结构体大小和内存结构
- 结构体大小
结构体需要根据数据类型做内存对齐。
- 所有数据类型的大小在内存中存储的地址,一定是它的类型的倍数
-
数据类型从上到下,按从大到小排列
-
示意图
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 成员列表 struct students { char name[20]; unsigned int age; char tel[16]; float scores[3]; char sex; } stu; // 可以使用别名 int main(void) { // 逐个赋值 strcpy(stu.name, "aa"); // 字符串需要通过 strcpy 函数赋值 stu.age = 10; strcpy(stu.tel, "123456"); stu.scores[0] = 30.1; stu.scores[1] = 40.1; stu.scores[2] = 50.1; stu.sex = 'M'; printf("结构体大小为 %d\n", sizeof(stu)); // 结构体大小为 56 //printf("name: %s, age: %u, tel: %s, scores: %.2f %.2f %.2f, sex: %c\n", stu.name, stu.age, stu.tel, stu.scores[0], stu.scores[1], stu.scores[2], stu.sex); system("pause"); return 0; }
- 内存对齐
- 内存对齐原因
- cpu 将内存当成多个块,每次从内存中读取一个块,这个块的大小可能是 2、4、8、16 (2 的倍数),如果没有内存对齐,获取数据时,需要做二次访问
- 内存对齐的优缺点
- 优点
- 提高访问效率
- 缺点
- 以空间换时间
- 优点
- 对齐模数
默认是 8,与平台有关,如果选择 x64,默认是 16。
#pragma pack(show) // warning C4810: pragma pack(show) 的值 == 8
- 计算内存对齐
- 对于标准数据类型,它的地址只要是它长度的整数倍
- 对于非标准数据类型
- 普通结构体
- 第一个属性开始,从 0 开始偏移
- 第二个属性开始,要放在该类型的大小和对齐模数比,取小的值的整倍数
- 所有属性都计算完毕后,再整体做二次偏移。将整体计算的结果放在结构体最大类型与对齐模数比,取小的值的整倍数上
- 结构体嵌套结构体
- 子结构体放在该结构体中最大类型和对齐模数比的,整数倍上即可
- 普通结构体
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #pragma pack(show) // warning C4810: pragma pack(show) 的值 == 8 (如果选择x64,结果是16) typedef struct _STUDENT1 { int a; // 0-3 char b; // 4-7 double c; // 8-15 float d; //16-23 } student1; typedef struct _STUDENT2 { char a; // 0-7 student1 b; // 8-31 double c; // 32-39 } student2; int main(void) { printf("sizeof(student1) = %d\n", sizeof(student1)); // sizeof(student1) = 24 printf("sizeof(student2) = %d\n", sizeof(student2)); // sizeof(student2) = 40 system("pause"); return 0; }
- 内存对齐原因
结构体数组
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include // 成员列表 struct students { char name[3]; unsigned int age; float scores[3]; }; int main(void) { // 结构体数组 struct students stu[2] = {0}; strcpy(stu[0].name, "aa"); // 字符串需要通过 strcpy 函数赋值 stu[0].age = 10; stu[0].scores[0] = 30.1; stu[0].scores[1] = 40.1; stu[0].scores[2] = 50.1; strcpy(stu[1].name, "aa"); // 字符串需要通过 strcpy 函数赋值 stu[1].age = 11; stu[1].scores[0] = 31.1; stu[1].scores[1] = 41.1; stu[1].scores[2] = 51.1; // 输出 for (size_t i = 0; i < 2; i++) { printf("name: %s, age: %u, scores: %.2f %.2f %.2f\n", stu[i].name, stu[i].age, stu[i].scores[0], stu[i].scores[1], stu[i].scores[2]); } system("pause"); return 0; }
结构体排序
- 结构体成员交换
-
结构体变量交换
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct stu { char name[3]; int score[2]; } s = { 0 }; int main(void) { struct stu s[2] = { 0 }; // 结构体赋值 strcpy(s[0].name, "aa"); s[0].score[0] = 10; s[0].score[1] = 30; strcpy(s[1].name, "bb"); s[1].score[0] = 11; s[1].score[1] = 21; int n = 2; int sum[2] = { 0 }; // 求分数和 for (size_t i = 0; i < n; i++) { for (size_t j = 0; j < n; j++) { sum[i] += s[i].score[j]; } } //printf("%d %d\n", sum[0], sum[1]); // 查看求和情况 for (size_t i = 0; i < n; i++) { for (size_t j = 0; j sum[j + 1]) { // 1. 交换变量 /* struct stu temp = s[j]; s[j] = s[j + 1]; s[j + 1] = temp;*/ // 2. 交换成员 // 交换 name char temp1[20] = { 0 }; strcpy(temp1, s[j].name); strcpy(s[j].name, s[j + 1].name); strcpy(s[j + 1].name, temp1); // 交换 score int temp2[20] = {0}; strcpy(temp2, s[j].score); strcpy(s[j].score, s[j + 1].score); strcpy(s[j + 1].score, temp2); } } } for (size_t i = 0; i < 2; i++) { printf("%s: %d %d\n", s[i].name, s[i].score[0], s[i].score[1]); } system("pause"); return 0; }
结构体和指针
- 结构体成员为指针
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct stu { char* name; int age; } s; int main(void) { //struct stu s; s.name = (char*)malloc(sizeof(char) * 3); // 结构体成员为指针 if (s.name == NULL) { printf("malloc error:"); return -1; } strcpy(s.name, "aa"); s.age = 10; printf("%s %d\n", s.name, s.age); free(s.name); system("pause"); return 0; }
- 结构体指针
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct student { char* name; int age; } stu; int main(void) { struct student *s = &stu; s->name = (char*)malloc(sizeof(char) * 3); // 结构体为指针 strcpy(s->name, "aa"); s->age = 10; printf("%s %d\n", s->name, s->age); free(s->name); system("pause"); return 0; }
- 在堆中开辟结构体
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct student { char* name; int age; } stu; int main(void) { struct student *p = (struct student*)malloc(sizeof(stu)); // 在堆中开辟结构体 p->name = (char*)malloc(sizeof(char) * 3); strcpy(p->name, "aa"); p->age = 10; printf("%s %d\n", p->name, p->age); free(p->name); free(p); system("pause"); return 0; }
- 在堆中开辟结构体数组
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct student { char* name; unsigned int *score; } stu; int main(void) { // 开辟 heap struct student* p = (struct student*)malloc(sizeof(stu) * 2); for (size_t i = 0; i<2; i++) { p[i].name = (char *)malloc(sizeof(char) * 2); p[i].score = (int *)malloc(sizeof(unsigned int) * 3); // 赋值 strcpy(p[i].name, "a"); p[i].score[0] = 10 + i; p[i].score[1] = 10 + i; p[i].score[2] = 10 + i; // 输出 printf("%s %d %d %d\n", p[i].name, p[i].score[0], p[i].score[1], p[i].score[2]); } // 释放 heap for (size_t i = 0; i < 2; i++) { free(p[i].name); free(p[i].score); } free(p); system("pause"); return 0; }
结构体和函数
- 错误用法
- 结构体做形参
- 结构体地址做返回值
- 正确用法
- 结构体值做形参
- 结构体作为返回值
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct student { char name[3]; int age; }; // 结构体做形参 void func1(struct student stu) { strcpy(stu.name, "aa"); stu.age = 10; } // 结构体值做形参 void func2(struct student *p) { strcpy(p->name, "ab"); p->age = 12; } // 结构体做返回值 struct student func3(void) { struct student stu; strcpy(stu.name, "ac"); stu.age = 13; return stu; } // 结构体地址做返回值 struct student * func4(void) { struct student stu; strcpy(stu.name, "ad"); stu.age = 14; return &stu; } int main(void) { struct student stu; // struct student stu= { .name = "bb", .age = 20 }; strcpy(stu.name, "bb"); stu.age = 20; //func1(stu); //bb 20 func2(&stu); //ab 12 struct student s = func3(); struct student *p = func4(); printf("%s %d\n", stu.name, stu.age); printf("%s %d\n", s.name, s.age); // ac 13 printf("%s %d\n", p->name, p->age); // 乱码,原因是地址记录在栈帧里的,函数调用结束,栈帧释放了 system("pause"); return 0; }
结构体的深/浅拷贝
- 浅拷贝 (系统默认的逐字节拷贝)
- 系统提供的赋值操作是简单的浅拷贝 (逐字节拷贝)
- 在栈上没有问题
- 如果结构体中有属性创建在堆区,就会出现问题,在释放期间,一段内存重复释放,一段内存泄露 -> 分别释放就会造成崩溃
- 解决方法
- 深拷贝
- 系统提供的赋值操作是简单的浅拷贝 (逐字节拷贝)
- 深拷贝
- 自己提供赋值操作 (深拷贝),而不是用系统提供的赋值
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct Person { char name[64]; int age; }; // 在栈上的浅拷贝,没有影响 void test01() { struct Person p1 = { "aaa", 10 }; struct Person p2 = { "bbb", 20 }; p1 = p2; printf("[p1] name: %s, age: %d\n", p1.name, p1.age); // [p1] name: bbb, age: 20 printf("[p2] name: %s, age: %d\n", p2.name, p2.age); // [p2] name: bbb, age: 20 } // 在堆上的浅拷贝,造成内存泄露和重复释放 void test02() { struct Person* p1 = malloc(sizeof(struct Person)); strcpy(p1->name, "aaa"); p1->age = 10; struct Person* p2 = malloc(sizeof(struct Person)); strcpy(p2->name, "bbb"); p2->age = 20; p1 = p2; // 内存泄露+内存重复 printf("[p1] name: %s, age: %d\n", p1->name, p1->age); // [p1] name: bbb, age: 20 printf("[p2] name: %s, age: %d\n", p2->name, p2->age); // [p2] name: bbb, age: 20 free(p1); //free(p2); // Error! 由于内存重复,释放时会崩溃 } // 解决方案:深拷贝,手动赋值 void test03() { struct Person* p1 = malloc(sizeof(struct Person)); strcpy(p1->name, "aaa"); p1->age = 10; struct Person* p2 = malloc(sizeof(struct Person)); strcpy(p2->name, "bbb"); p2->age = 20; // 先释放原来堆区的内容 free(p1); p1 = NULL; //p1 = p2; // Error! // 手动赋值 p1 = malloc(sizeof(struct Person)); // 原来的内存已经释放了,必须重新申请 strcpy(p1->name, p2->name); p1->age = p2->age; printf("[p1] name: %s, age: %d\n", p1->name, p1->age); // [p1] name: bbb, age: 20 printf("[p2] name: %s, age: %d\n", p2->name, p2->age); // [p2] name: bbb, age: 20 // 释放内存 free(p1); p1 = NULL; free(p2); p2 = NULL; } int main(void) { test01(); printf("-------------------\n"); test02(); printf("-------------------\n"); test03(); system("pause"); return 0; }
结构体嵌套
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct str1 {
int a; // 4
char b; // 1
} s1; // 共 8
struct str2 {
int aa; // 4
char bb; // 1 int 和 char 共 8,加上 s1 就是 16
struct str1 s1; // 结构体嵌套
} s2;
int main(void)
{
struct str2 s2;
s2.aa = 22;
s2.bb = 'b';
s2.s1.a = 11;
s2.s1.b = 'd';
printf("%d\n", s2.s1.a); // 11
printf("%d\n", sizeof(s2)); // 16
system("pause");
return 0;
}
- 示意图:内存对齐
-
结构体的自身引用
- 结构体可以嵌套另外一个结构体的任何类型变量
- 结构体不可以嵌套本结构体普通变量
- 本结构体的类型大小无法确定
- 类型的本质是固定大小内存块的别名
- 结构体可以嵌套本结构体指针变量
- 指针变量的空间能确定,4 字节、8 字节、16 字节、32 字节、64 字节…
- 结构体嵌套一级指针
- 示意图:注意创建和释放顺序
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct Person { char* name; int age; }; // 分配内存 struct Person** allocatesSpace() { // 结构体指针数组 struct Person** temp = malloc(sizeof(struct Person*) * 3); for (int i = 0; i name = malloc(sizeof(char) * 64); // 将结构体姓名创建在堆区 sprintf(temp[i]->name, "name_%d", i + 1); // 给姓名赋值 temp[i]->age = i + 20; } return temp; } // 打印结构体内容 struct Person** printPerson(struct Person** pArray, int len) { for (int i = 0; i name, pArray[i]->age); } } // 释放内存 struct Person** freeSpace(struct Person** pArray, int len) { for (int i = 0; i name != NULL) { free(pArray[i]->name); pArray[i]->name = NULL; printf("pArray[%d]->name 已释放\n", i); } free(pArray[i]); pArray[i] = NULL; printf("pArray[%d] 已释放\n", i); } free(pArray); pArray = NULL; printf("pArray 已释放\n"); } int main(void) { int len = 3; struct Person** pArray = NULL; pArray = allocatesSpace(); // 分配堆区内存 printPerson(pArray, len); // 打印结构体 freeSpace(pArray, len); // 释放内存 system("pause"); return 0; }
- 示意图:注意创建和释放顺序
- 结构体嵌套二级指针
- 示意图:层次解析
-
函数拆分
- 分配内存
- 给老师数组分配内存
- 给每个老师分配内存
- 给老师姓名分配内存
- 给学生数组分配内存
- 给学生姓名分配内存
- 打印数组
-
释放内存
- 老师姓名和学生数组是同级,不分先后。
- 分配内存
- 示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct Teacher { char* name; char** students; }; // 分配内存 void allocateSpace(struct Teacher*** teachers) { if (teachers == NULL) { return; } struct Teacher** ts = malloc(sizeof(struct Teacher*) * 3); for (int i = 0; i name = malloc(sizeof(char) * 64); sprintf(ts[i]->name, "teacher_%d\n", i + 1); printf("%s", ts[i]->name); // 给学生数组分配内存 ts[i]->students = malloc(sizeof(char*) * 4); // 给每个学生姓名分配内存 for (int j = 0; j students[j] = malloc(sizeof(char) * 64); sprintf(ts[i]->students[j], "teacher_%d-student_%d\n", i + 1, j + 1); printf("%s", ts[i]->students[j]); } } // 传出参数 *teachers = ts; } // 打印结构体 void printTeacher(struct Teacher** ts) { for (int i = 0; i name); for (int j = 0; j students[j]); } } } // 释放内存 void freeTeacher(struct Teacher** ts) { if (ts == NULL) { return; } for (int i = 0; i < 3; i++) { // 释放学生姓名 for (int j = 0; j students[j]); } // 释放学生数组 free(ts[i]->students); ts[i]->students = NULL; // 释放老师姓名 free(ts[i]->name); ts[i]->name = NULL; // 释放老师 free(ts[i]); ts[i] = NULL; } // 释放结构体指针 free(ts); ts = NULL; } int main(void) { struct Teacher** pArray = NULL; allocateSpace(&pArray); // 开辟内存 printf("--------------------------\n"); printTeacher(pArray); // 打印数组 freeTeacher(pArray); // 释放内存 system("pause"); return 0; }
- 示意图:层次解析
结构体偏移量
通过偏移量可以操作内存。
- 求偏移量
- offsetof 函数
- 对于嵌套结构体
-
示例
#define _CRT_SECURE_NO_WARNINGS #include #include #include struct Teacher { char a; int b; }; // 求偏移量 void test01() { struct Teacher t; struct Teacher* p = &t; printf("a 的偏移量 = %d\n", &(p->b) - p); // a 的偏移量 = 1 printf("b 的偏移量 = %d\n", (int)&(p->b) - (int)p); // b 的偏移量 = 4 (offsetof 的底层实现方式) printf("结构体 Teacher 的偏移量 = %d\n", offsetof(struct Teacher, b)); // 结构体 Teacher 的偏移量 = 4 } // 通过偏移量操作内存 void test02() { struct Teacher t = { 'a', 10 }; printf("t.b = %d\n", *((char*)&t + offsetof(struct Teacher, b))); // t.b = 10 printf("t.b = %d\n", *(int*)((char*)&t + offsetof(struct Teacher, b))); // t.b = 10 printf("t.b = %d\n", *(int*)((int*)&t + 1)); // t.b = 10 } struct TeacherNest // 结构体嵌套结构体 { char a; int b; struct Teacher c; }; void test03() { struct TeacherNest t = { 'a', 10, 'b', 20 }; printf("sizeof(struct TeacherNest) = %d\n", sizeof(struct TeacherNest)); // sizeof(struct TeacherNest) = 16 int offset1 = offsetof(struct TeacherNest, c); int offset2 = offsetof(struct Teacher, b); printf("offset1 = %d, offset2 = %d\n", offset1, offset2); // offset1 = 8, offset2 = 4 printf("t.c.b = %d\n", *(int*)((char*)&t + offset1 + offset2)); // t.c.b = 20 printf("t.c.b = %d\n", t.c.b); // t.c.b = 20 printf("b = %c\n", *((char*)&t + offset1)); // b = b printf("%d\n", ((struct Teacher*)((char*)&t + offset1))->b); // 20 } int main(void) { test01(); printf("------------------\n"); test02(); printf("------------------\n"); test03(); system("pause"); return 0; }
共用体 (联合体)
成员地址
- 内部所有成员变量地址一致,等同于整个联合体的地址
- 修改其中任意一个成员变量的值,其他成员变量也会随之修改
联合体的大小
- 是内部成员变量中,最大的那个成员变量的大小
- 对齐
示例
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef union test {
char ch;
short sh;
int a;
} test_t;
int main(void)
{
test_t obj;
obj.a = 0x123413241; // 更改后,所有地址都变为 009FF88C
printf("&obj = %p\n", &obj); // 更改前,所有地址都是 00BDF834
printf("&obj.ch = %p\n", &obj.ch); // & obj.ch = 00BDF834
printf("&obj.sh = %p\n", &obj.sh); // & obj.sh = 00BDF834
printf("&obj.a = %p\n", &obj.a); // & obj.sh = 00BDF834
system("pause");
return 0;
}
枚举
枚举常量
// enum color { 枚举常量 };
enum color { red, green, blue, pink, yellow};
- 是整型常量,不能是浮点数,可以是负数
- 默认初值从 0 开始,后续常量较前一个常量 +1
- 可以给任意一个常量赋任意一个初值 (后续常量较前一个常量 +1)
示例
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
enum color1 {red, green, blue, pink, yellow};
// 0 1 2 3 4 // 原始位置
enum color2 { big, small = 10, medium };
int main(void)
{
printf("%d %d %d %d %d\n", red, green, blue, pink, yellow); // 0 1 2 3 4
int blue = 10;
printf("%d %d %d %d %d\n", red, green, blue, pink, yellow); // 0 1 10 3 4
printf("%d %d %d\n", big, small, medium); // 0 10 11
system("pause");
return 0;
}