day05:复合类型、内存管理、综合案例
文章目录
- day05:复合类型、内存管理、综合案例
-
- 一、复合类型(自定义类型)
-
- [1.1 共用体(联合体)](#1.1 共用体(联合体))
- [1.2 枚举](#1.2 枚举)
- [1.3 typedef](#1.3 typedef)
- 二、内存管理
-
- [2.1 C代码编译过程(了解)](#2.1 C代码编译过程(了解))
- [2.2 进程的内存分布](#2.2 进程的内存分布)
- [2.3 堆区内存的使用](#2.3 堆区内存的使用)
- [2.4 内存分布代码分析](#2.4 内存分布代码分析)
-
- [2.4.1 返回栈区地址](#2.4.1 返回栈区地址)
-
- [2.4.1.1 返回data区地址](#2.4.1.1 返回data区地址)
- [2.4.1.1 普通和静态局部变量区别](#2.4.1.1 普通和静态局部变量区别)
- [2.4.2 返回堆区地址](#2.4.2 返回堆区地址)
- 三、学生信息管理系统
一、复合类型(自定义类型)
1.1 共用体(联合体)
共用体和结构体区别
特性 | 结构体 (struct) | 共用体 (union) |
---|---|---|
存储方式 | 各成员顺序存储,拥有独立的内存空间。 | 所有成员共享同一块起始内存空间。 |
内存占用 | 所有成员大小之和(需考虑内存对齐)。 | 最大成员的大小。 |
成员访问 | 所有成员同时有效,可随时访问,互不影响。 | 同一时间只有一个成员有效,对一个成员赋值会覆盖其他成员。 |
c
#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字节,即0x44332211
printf("%#x %#x %#x\n",text.a,text.b,text.c);
return 0;
}
1.2 枚举
-
枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
-
语法格式:
cenum 枚举名 { 枚举值表 };
-
在枚举值表中应列出所有可用值,也称为枚举元素
- 枚举值是常量,不能在程序中用赋值语句再对它赋值
- 枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 ...
c
#include <stdio.h>
// 定义一个枚举类型color,用于表示颜色
// 枚举类型是一种用户定义的数据类型,它包含一组命名的整型常量
// 默认情况下,第一个枚举值被赋值为0,后续的枚举值依次递增1
enum color{
// red 被赋值为 0
red,
// whild 被赋值为 1
whild,
// green 被赋值为 2
green,
// blue 被赋值为 3
blue,
// yellow 被赋值为 4
yellow
};
int main() {
// 打印red的值,应该是0
printf("red = %d\n",red);
// 使用switch语句根据枚举值进行分支处理
// 这里使用blue作为switch的条件,blue的值为3
switch (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语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。
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 = 0
whild, // whild = 1
green, // green = 2
blue, // blue = 3
yellow // 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别名声明变量a
u8 a;
// 使用Student别名声明结构体变量s
Student s;
// 使用PStudent别名声明结构体指针变量p,并指向s
PStudent p = &s;
// 通过指针p修改结构体s的age成员
p->age = 12;
// 打印结构体s的age成员,验证修改成功
printf("%d\n",s.age);
// 使用color别名声明枚举变量c
color 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 返回栈区地址
c
#include <stdio.h>
int *func() {
int a = 10;
return &a; // 函数调用完毕,因为a是局部变量,a释放
}
int main() {
int *p = NULL;
p = func();
*p = 100; // 操作野指针指向的内存,err
printf("11111111111111111\n"); // 这句话可能执行不到,因为上一句话报错
return 0;
}
2.4.1.1 返回data区地址
- 在函数内部使用static修饰的变量称为静态局部变量
- 它在程序运行期间只被初始化一次,并且在函数调用结束后也不会被销毁
c
#include <stdio.h>
int *func() {
// 静态局部变量,只会初始化一次
static int a = 10;
return &a; // 函数调用完毕,a不释放
}
int main() {
int *p = NULL;
p = func();
*p = 100; // ok
printf("*p = %d\n", *p);
return 0;
}
2.4.1.1 普通和静态局部变量区别
特性 | 普通局部变量 | 静态局部变量 |
---|---|---|
存储位置 | 栈区 (Stack) | 静态数据区 (Data/BSS) |
生命周期 | 函数调用开始 到 函数返回结束 | 整个程序运行期间 |
作用域 | 仅限于声明所在的 函数内部 | 仅限于声明所在的 函数内部 |
初始化时机 | 每次 进入函数时都初始化 | 仅在第一次 进入函数时初始化一次 |
默认初始值 | 不确定值(随机值) | 0 或 NULL(由系统自动初始化) |
函数调用间的值 | 每次调用都是新的,不保留 上次的值 | 会保留 上次调用结束时的值 |
c
#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 返回堆区地址
c
#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;
}
三、学生信息管理系统
- 函数版学生信息管理系统
c
#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比较字符串,相等时返回0
if(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;
}