C语言的复合类型、内存管理、综合案例
day05:复合类型、内存管理、综合案例
文章目录
- day05:复合类型、内存管理、综合案例
- 一、复合类型(自定义类型)
- 1.1 共用体(联合体)
- 1.2 枚举
- 1.3 typedef
- 二、内存管理
- 2.1 C代码编译过程(了解)
- 2.2 进程的内存分布
- 2.3 堆区内存的使用
- 2.4 内存分布代码分析
- 2.4.1 返回栈区地址
- 2.4.1.1 返回data区地址
- 2.4.1.1 普通和静态局部变量区别
- 2.4.2 返回堆区地址
- 三、学生信息管理系统
一、复合类型(自定义类型)
1.1 共用体(联合体)
共用体和结构体区别
特性 | 结构体 (struct) | 共用体 (union) |
---|---|---|
存储方式 | 各成员顺序存储,拥有独立的内存空间。 | 所有成员共享同一块起始内存空间。 |
内存占用 | 所有成员大小之和(需考虑内存对齐)。 | 最大成员的大小。 |
成员访问 | 所有成员同时有效,可随时访问,互不影响。 | 同一时间只有一个成员有效,对一个成员赋值会覆盖其他成员。 |
#include <stdio.h>
#include<stdint.h>// 定义一个联合体(共同体)Text,它包含三个不同类型的成员
// 联合体的特点是所有成员共享同一块内存空间,其大小等于最大成员的大小
union Text{uint8_t a; // 1字节uint16_t b; // 2字节uint32_t c; // 4字节// 因此,这个联合体的大小为4字节(即uint32_t的大小)
};int main() {union Text text; // 声明一个联合体变量text// 打印各个成员的地址,可以看到它们的地址是相同的// 这是因为联合体的所有成员都共享同一块内存空间printf("%p, %p, %p \n",&text.a,&text.b,&text.c);//地址// 打印联合体变量的大小,结果为4字节(最大成员uint32_t的大小)printf("%d\n",sizeof(text));//最大的数据类型就是共用体的内存// 给成员c赋值,由于联合体共享内存,这个值会同时影响其他成员text.c = 0x44332211;//赋值// 打印各个成员的值:// text.a 只能访问最低的1字节,即0x11// text.b 只能访问最低的2字节,即0x2211// text.c 访问全部4字节,即0x44332211printf("%#x %#x %#x\n",text.a,text.b,text.c);return 0;
}
1.2 枚举
-
枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
-
语法格式:
enum 枚举名 { 枚举值表 };
-
在枚举值表中应列出所有可用值,也称为枚举元素
- 枚举值是常量,不能在程序中用赋值语句再对它赋值
- 枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …
#include <stdio.h>// 定义一个枚举类型color,用于表示颜色
// 枚举类型是一种用户定义的数据类型,它包含一组命名的整型常量
// 默认情况下,第一个枚举值被赋值为0,后续的枚举值依次递增1
enum color{// red 被赋值为 0red,// whild 被赋值为 1whild,// green 被赋值为 2green,// blue 被赋值为 3blue,// yellow 被赋值为 4yellow
};int main() {// 打印red的值,应该是0printf("red = %d\n",red);// 使用switch语句根据枚举值进行分支处理// 这里使用blue作为switch的条件,blue的值为3switch (blue){// 当值为red(0)时,打印"红色"case red:printf("红色\n");break;// 当值为green(2)时,打印"绿色"case green:printf("绿色\n");break;// 当值为blue(3)时,打印"蓝色"case blue:printf("蓝色\n");break;// 默认情况,当没有匹配的case时执行default:break;}return 0;
}
1.3 typedef
- typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。
#include <stdio.h>// 使用typedef为unsigned char类型起一个别名u8
// 这样在后续代码中可以直接使用u8来声明变量,提高代码可读性
typedef unsigned char u8;// 使用typedef为枚举类型起别名
// 定义一个枚举类型color,包含几种颜色
// 然后使用typedef将enum color命名为color,这样可以直接使用color来声明变量
typedef enum color{// 默认从0开始计数red, // red = 0whild, // whild = 1green, // green = 2blue, // blue = 3yellow // yellow = 4
}color; // 为enum color起别名color// 使用typedef为结构体类型起别名
// 定义一个Student结构体,包含姓名、年龄和性别
// 然后使用typedef将struct Student命名为Student,并将struct Student*命名为PStudent
typedef struct Student{char name[20]; // 姓名,字符数组int age ; // 年龄,整型char sex ; // 性别,字符型
}Student,*PStudent; // Student是struct Student的别名,PStudent是struct Student*的别名int main() {// 使用u8别名声明变量au8 a;// 使用Student别名声明结构体变量sStudent s;// 使用PStudent别名声明结构体指针变量p,并指向sPStudent p = &s;// 通过指针p修改结构体s的age成员p->age = 12;// 打印结构体s的age成员,验证修改成功printf("%d\n",s.age);// 使用color别名声明枚举变量ccolor c;return 0;
}
二、内存管理
2.1 C代码编译过程(了解)
-
预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
-
编译:检查语法,将预处理后文件编译生成汇编文件
-
汇编:将汇编文件生成目标文件(二进制文件)
-
链接:将目标文件链接为可执行程序
2.2 进程的内存分布
-
程序运行起来(没有结束前)就是一个进程
- windows打开任务管理器:ctrl+shfit+esc
-
程序内存区域划分
内存区域 存储内容 生存周期 管理方式 主要特点 代码区 (Text) 程序的可执行代码 整个程序运行期间 系统 只读 数据区 (Data) 已初始化的全局/静态变量、常量 整个程序运行期间 系统 程序结束时释放 BSS区 (BSS) 未初始化的全局/静态变量 整个程序运行期间 系统 程序启动时清零 栈区 (Stack) 函数参数、局部变量、返回值 函数调用期间 编译器自动管理 先进后出 (FILO),空间有限 堆区 (Heap) 动态分配的内存 (malloc) 从分配到释放 程序员手动管理 空间较大,需手动释放,易产生碎片
2.3 堆区内存的使用
- 手动管理
- 不手动释放,如果程序没有结束,堆区内存一直在
2.4 内存分布代码分析
2.4.1 返回栈区地址
#include <stdio.h>int *func() {int a = 10;return &a; // 函数调用完毕,因为a是局部变量,a释放
}int main() {int *p = NULL;p = func();*p = 100; // 操作野指针指向的内存,errprintf("11111111111111111\n"); // 这句话可能执行不到,因为上一句话报错return 0;
}
2.4.1.1 返回data区地址
- 在函数内部使用static修饰的变量称为静态局部变量
- 它在程序运行期间只被初始化一次,并且在函数调用结束后也不会被销毁
#include <stdio.h>int *func() {// 静态局部变量,只会初始化一次static int a = 10;return &a; // 函数调用完毕,a不释放
}int main() {int *p = NULL;p = func();*p = 100; // okprintf("*p = %d\n", *p);return 0;
}
2.4.1.1 普通和静态局部变量区别
特性 | 普通局部变量 | 静态局部变量 |
---|---|---|
存储位置 | 栈区 (Stack) | 静态数据区 (Data/BSS) |
生命周期 | 函数调用开始 到 函数返回结束 | 整个程序运行期间 |
作用域 | 仅限于声明所在的 函数内部 | 仅限于声明所在的 函数内部 |
初始化时机 | 每次 进入函数时都初始化 | 仅在第一次 进入函数时初始化一次 |
默认初始值 | 不确定值(随机值) | 0 或 NULL(由系统自动初始化) |
函数调用间的值 | 每次调用都是新的,不保留 上次的值 | 会保留 上次调用结束时的值 |
#include <stdio.h>void normal_func() {int i = 0;i++;printf("局部变量 i = %d\n", i);
}void static_func() {static int j = 0;j++;printf("static局部变量 j = %d\n", j);
}int main() {// 调用3次normal_func()normal_func();normal_func();normal_func();// 调用3次static_func()static_func();static_func();static_func();return 0;
}
2.4.2 返回堆区地址
#include <stdio.h>
#include <stdlib.h>int *func() {int *tmp = NULL;// 堆区申请空间tmp = (int *)malloc(sizeof(int));*tmp = 100;return tmp; // 返回堆区地址,函数调用完毕,不释放
}int main() {int *p = NULL;p = func();printf("*p = %d\n", *p); // ok// 堆区空间,使用完毕,手动释放if (p != NULL) {free(p);p = NULL;}return 0;
}
三、学生信息管理系统
- 函数版学生信息管理系统
#include <stdio.h>
#include<string.h>// 定义学生数组的最大容量
#define MAX 50// 定义学生结构体,包含姓名、年龄和性别
// 使用typedef为struct {...}起别名为Student
typedef struct {char name[20]; // 学生姓名,最多19个字符+1个结束符int age ; // 学生年龄char sex ; // 学生性别,'m'表示男性,'f'表示女性
}Student;// 初始化学生数组,包含4个学生的信息
Student stu [MAX]={{"feifei",18,'m'},{"mamafei",18,'f'},{"koko",18,'f'},{"yoyo",20,'f'},
};// 当前学生数组中的实际学生数量
int len = 4 ;// 根据姓名查找学生在数组中的索引
// 参数:p - 要查找的学生姓名
// 返回值:找到则返回索引,未找到返回-1
int find_by_name(char * p){for (int i = 0; i < len; i++){// 使用strcmp比较字符串,相等时返回0if(strcmp(stu[i].name,p)==0){return i ; }}return -1;
}// 显示帮助菜单
void help_menu() {printf("\n");printf(" 欢迎使用本学生信息管理系统\n");printf("* ================================ *\n");printf("* 1. 添加 *\n");printf("* 2. 显示 *\n");printf("* 3. 查询 *\n");printf("* 4. 修改 *\n");printf("* 5. 删除 *\n");printf("* 6. 退出 *\n");printf("* ================================ *\n");
}// 添加学生信息
void add(){printf("增加第%d的学生的信息\n",len+1);printf("请输入新的学生的名字\n");scanf("%s",stu[len].name); // 读取学生姓名printf("请输入新的学生的年龄\n");scanf("%d",&stu[len].age); // 读取学生年龄printf("请输入新的学生的性别(m,f)\n");scanf(" %c",&stu[len].sex); // 读取学生性别,注意前面有空格用于跳过换行符len++; // 学生数量增加printf("添加成功\n");}// 显示所有学生信息
void show(){printf("姓名\t年龄\t性别\n");for (int i = 0; i < len; i++){printf("%s\t%d\t%c\n",stu[i].name,stu[i].age,stu[i].sex);}// 检查是否达到最大容量if (len >MAX){printf("不能在存储数据了\n");}}// 查询单个学生信息
void show_one(){char tamp [20]; // 临时存储输入的姓名printf("请输入姓名\n");scanf("%s",tamp);int sop = find_by_name(tamp); // 查找学生if (sop != -1){// 如果找到学生,显示其信息printf("%s\t%d\t%c\n",stu[sop].name,stu[sop].age,stu[sop].sex);}else{printf("请重新输入名字,%s不存在\n",tamp);}}// 修改学生信息
void updata(){char tamp [20]; // 临时存储输入的姓名printf("请输入姓名\n");scanf("%s",tamp);int sop = find_by_name(tamp); // 查找学生if (sop != -1){//printf("%s\t%d\t%c\n",stu[sop].name,stu[sop].age,stu[sop].sex);//=======================修改printf("请输入需要修改学生的名字\n");scanf("%s",stu[sop].name); // 修改姓名printf("请输入学生修改后的年龄\n");scanf("%d",&stu[sop].age); // 修改年龄printf("请输入新的学生修改后的性别(m,f)\n");scanf(" %c",&stu[sop].sex); // 修改性别printf("修改成功\n");}else{printf("请重新输入名字,%s不存在\n",tamp);}}// 删除学生信息
void delete(){char tamp [20]; // 临时存储输入的姓名printf("请输入姓名\n");scanf("%s",tamp);int sop = find_by_name(tamp); // 查找学生if (sop != -1){//=======================删除// 将数组最后一个元素复制到要删除的位置,然后减少数组长度// 这是一种简单的删除方法,避免了数组元素的移动stu[sop] = stu[len-1];len--;printf("%s删除成功\n",tamp);}else{printf("请重新输入名字,%s不存在\n",tamp);}}// 主函数,程序入口
int main() {// 无限循环,直到用户选择退出while (1){help_menu(); // 显示菜单int a ;printf("请输入一个数字 ");scanf("%d",&a); // 读取用户选择// 根据用户输入执行相应操作if (a==1){printf("添加\n");add(); // 添加学生}else if (a==2){printf("显示\n");show(); // 显示所有学生}else if (a==3){printf("查询\n");show_one(); // 查询单个学生}else if (a==4){printf("修改\n");updata(); // 修改学生信息}else if (a==5){printf("删除\n");delete(); // 删除学生信息}else if (a==6){printf("退出\n");break; // 退出循环,结束程序}else{printf("请重新输入数字\n"); // 输入无效时提示}
} return 0;
}