day05:复合类型、内存管理、综合案例
一、复合类型(自定义类型)
1.1 共用体(联合体)
共用体和结构体区别
特性 | 结构体 (struct) | 共用体 (union) |
---|---|---|
存储方式 | 各成员顺序存储,拥有独立的内存空间。 | 所有成员共享同一块起始内存空间。 |
内存占用 | 所有成员大小之和(需考虑内存对齐)。 | 最大成员的大小。 |
成员访问 | 所有成员同时有效,可随时访问,互不影响。 | 同一时间只有一个成员有效,对一个成员赋值会覆盖其他成员。 |
#include <stdio.h>
#include <stdint.h>
// 类型定义 union 共用体(联合体)名字
// union Test 合在一起,才是类型
union Test {
uint8_t a;
uint16_t b;
uint32_t c;
};
int main() {
// 类型 变量
union Test temp;
// 1. 所有成员地址都一样
printf("%p, %p, %p\n", &temp.a, &temp.b, &temp.c);
// 2. 类型大小 取决于 最大成员大小
printf("sizeof(temp) = %d\n", sizeof(temp));
// 3. 改一个成员,别的成员受影响
temp.c = 0x44332211;
printf("%#x, %#x, %#x\n", temp.a, temp.b, temp.c);
temp.a = 0xff;
printf("%#x, %#x, %#x\n", temp.a, temp.b, temp.c);
return 0;
}
1.2 枚举
-
枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内
-
语法格式:
enum 枚举名 { 枚举值表 };
-
在枚举值表中应列出所有可用值,也称为枚举元素
- 枚举值是常量,不能在程序中用赋值语句再对它赋值
- 枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …
#include <stdio.h>
// 枚举类型: 给标志位常量,起别名
// 第一个成员默认是0,后面按顺序+1
// 只要定义了枚举类型,里面的成员可以直接使用,它是常量
enum Color {
// 0 1 2 3 4 5 6
red, black, white, yellow, blue, pink, green
};
int main() {
printf("%d\n", black);
// 颜色判断
// 类型 变量
enum Color flag = yellow;
switch (flag){
case red: printf("红色\n"); break;
case black: printf("黑色\n"); break;
case white: printf("白色\n"); break;
case yellow: printf("黄色\n"); break;
default:
break;
}
return 0;
}
1.3 typedef
- typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字,不能创建新类型。
#include <stdio.h>
// 枚举类型: 给标志位常量,起别名
// 第一个成员默认是0,后面按顺序+1
// 只要定义了枚举类型,里面的成员可以直接使用,它是常量
enum Color {
// 0 1 2 3 4 5 6
red, black, white, yellow, blue, pink, green
};
int main() {
printf("%d\n", black);
// 颜色判断
// 类型 变量
enum Color flag = yellow;
switch (flag){
case red: printf("红色\n"); break;
case black: printf("黑色\n"); break;
case white: printf("白色\n"); break;
case yellow: printf("黄色\n"); break;
default:
break;
}
return 0;
}
二、内存管理
2.1 C代码编译过程(了解)
-
预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
-
编译:检查语法,将预处理后文件编译生成汇编文件
-
汇编:将汇编文件生成目标文件(二进制文件)
-
链接:将目标文件链接为可执行程序
2.2 进程的内存分布
-
程序运行起来(没有结束前)就是一个进程
- windows打开任务管理器:ctrl+shfit+esc
-
程序内存区域划分
内存区域 存储内容 生存周期 管理方式 主要特点 代码区 (Text) 程序的可执行代码 整个程序运行期间 系统 只读 数据区 (Data) 已初始化的全局/静态变量、常量 整个程序运行期间 系统 程序结束时释放 BSS区 (BSS) 未初始化的全局/静态变量 整个程序运行期间 系统 程序启动时清零 栈区 (Stack) 函数参数、局部变量、返回值 函数调用期间 编译器自动管理 先进后出 (FILO),空间有限 堆区 (Heap) 动态分配的内存 (malloc) 从分配到释放 程序员手动管理 空间较大,需手动释放,易产生碎片
运用typede起别名
#include <stdio.h>
// 定义类型同时给类型起别名
// 给 struct Student 起别名 叫 Student
// 给 struct Student * 起别名 叫 PStudent
typedef struct Student{
char name[30];
int age;
char sex;
}Student, *PStudent;
typedef enum Color {
// 0 1 2 3 4 5 6
red, black, white, yellow, blue, pink, green
}Color;
// C51的类型
// 旧名 新名
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
int main() {
u8 ch; // unsigned char ch;
Student s;
PStudent p = &s;
p->age = 18;
printf("%d\n", s.age);
Color flag;
return 0;
}
2.3 堆区内存的使用
- 手动管理
- 不手动释放,如果程序没有结束,堆区内存一直在
#include <stdio.h>
#include <stdlib.h> // malloc() free()
int main() {
int * p = NULL; // p 局部变量,放栈区
// p 指向的空间在堆区 int * 指向 int
// 申请的空间大小 sizeof(int)
// 申请分配空间 参数为: 申请空间大小
// 返回值:成功返回,堆区的地址 失败返回NULL
p = malloc(sizeof(int));
printf("p = %p\n", p); // 打印地址
*p = 123; // 操作指针所指向的内存,堆区空间
printf("*p = %d\n", *p);
if (p != NULL) {
// 1. 不是释放p的空间,释放的是p所指向的空间
// 2. 只能释放1次
free(p);
p = NULL;
}
return 0;
}
int main01() {
int a = 10; // 局部变量,放栈区
int * p; // 局部变量,放栈区
p = &a; // 指针p 指向 栈区空间
return 0;
}
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; // 操作野指针指向的内存,err
printf("11111111111111111\n"); // 这句话可能执行不到,因为上一句话报错
return 0;
}
2.4.2 data区代码分析
#include <stdio.h>
int a = 10; // 全局变量
int *func() {
return &a; // 函数调用完毕,因为a是局部变量,a释放
}
int main() {
int *p = NULL;
p = func();
*p = 100; // 操作野指针指向的内存,err
printf("11111111111111111\n"); // 这句话可能执行不到,因为上一句话报错
printf("a = %d\n", a);
return 0;
}
2.4.2.1 返回data区地址
- 在函数内部使用static修饰的变量称为静态局部变量
- 它在程序运行期间只被初始化一次,并且在函数调用结束后也不会被销毁
#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.2.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.3 返回堆区地址
#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> // strcmp()
// 帮助菜单显示函数定义
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");
}
int main() {
// 1. 死循环
while (1) {
// 2. 调用菜单
help_menu();
// 3. 输入指令
int cmd;
printf("请输入指令数字:");
scanf("%d", &cmd);
// 4. 判断
if (cmd == 1) {
add_stu(); //1. 添加
} else if (cmd == 2) {
show_all(); //2. 显示
} else if (cmd == 3) {
find_someone(); //3. 查询
} else if (cmd == 4) {
modify_someone();//4. 修改
} else if (cmd == 5) {
delete_someone();//5. 删除
} else if (cmd == 6) {
printf("退出\n");//6. 退出
break;
} else {
printf("指令错误,请重新输入\n");
}
}
return 0;
}
- 添加
#define MAX 50 // 数组的元素个数
typedef struct Student{
char name[30];
int age;
char sex;
}Student;
// 默认有几个学生 s[0] s[1] s[2] s[3]
Student s[MAX] = {
{"mike", 18, 'm'},
{"lily", 19, 'f'},
{"jerry", 20, 'm'},
{"yoyo", 21, 'f'},
};
int n = 4; // 全局变量,标志学生个数
void add_stu() { // 添加
printf("添加\n");
// ======================= 增
if (n >= MAX) {
printf("空间不足\n");
return;
}
printf("增加第 %d 个学生的信息\n", n+1);
printf("请输入姓名:");
scanf("%s", s[n].name);
printf("请输入年龄:");
scanf("%d", &s[n].age);
printf("请输入性别(m或f):");
// " %c" 前面有一个空格,吃掉上一步的'\n'
scanf(" %c", &s[n].sex);
n++; // 学生人数+1
printf("新增成功\n");
}
- 显示
void show_all() { // 显示所有学生
printf("显示\n");
// ======================= 查所有
printf("姓名\t年龄\t性别\n");
for (int i = 0; i < n; i++) { // s[i]
printf("%s\t%d\t%c\n", s[i].name, s[i].age, s[i].sex);
}
}
- 查询
/**********************************************************
* @功能:通过姓名找位置
* @参数:temp: 姓名
* @return 找到返回对应的下标,找不到返回 -1
**********************************************************/
int find_pos_by_name(char * temp) { // temp = "jerry"
for (int i = 0; i < n; i++) { // s[i]
// 结构体里面name和参数的temp是否相等
if (strcmp(s[i].name, temp) == 0) {
return i;
}
}
// 执行到这里,说明上面没有找到相等的
return -1;
}
void find_someone() { // 查询
printf("查询\n");
char temp[30];
printf("请输入姓名:");
scanf("%s", temp);
int pos = find_pos_by_name(temp);
// printf("pos = %d\n", pos);
if (pos != -1) {
// ======================= 查
printf("%s 的信息如下:\n", temp);
printf("%s\t%d\t%c\n", s[pos].name, s[pos].age, s[pos].sex);
} else {
printf("%s 不存在\n", temp);
}
}
- 修改
void modify_someone() { // 修改
printf("修改\n");
char temp[30];
printf("请输入姓名:");
scanf("%s", temp);
int pos = find_pos_by_name(temp);
// printf("pos = %d\n", pos);
if (pos != -1) {
// ======================= 改
printf("请输入新的姓名:");
scanf("%s", s[pos].name);
printf("请输入新的年龄:");
scanf("%d", &s[pos].age);
printf("请输入新的性别(m或f):");
// " %c" 前面有一个空格,吃掉上一步的'\n'
scanf(" %c", &s[pos].sex);
printf("修改成功\n");
} else {
printf("%s 不存在\n", temp);
}
}
- 删除
void delete_someone() { // 删除
printf("删除\n");
char temp[30];
printf("请输入姓名:");
scanf("%s", temp);
int pos = find_pos_by_name(temp);
// printf("pos = %d\n", pos);
if (pos != -1) {
// ======================= 删
s[pos] = s[n-1];
n--;
printf("%s 删除成功\n", temp);
} else {
printf("%s 不存在\n", temp);
}
}
- 功能实现(整个)
#include <stdio.h>
#include <string.h> // strcmp()
#define MAX 50 // 数组的元素个数
typedef struct Student{
char name[30];
int age;
char sex;
}Student;
// 默认有几个学生 s[0] s[1] s[2] s[3]
Student s[MAX] = {
{"mike", 18, 'm'},
{"lily", 19, 'f'},
{"jerry", 20, 'm'},
{"yoyo", 21, 'f'},
};
int n = 4; // 全局变量,标志学生个数
/**********************************************************
* @功能:通过姓名找位置
* @参数:temp: 姓名
* @return 找到返回对应的下标,找不到返回 -1
**********************************************************/
int find_pos_by_name(char * temp) { // temp = "jerry"
for (int i = 0; i < n; i++) { // s[i]
// 结构体里面name和参数的temp是否相等
if (strcmp(s[i].name, temp) == 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_stu() { // 添加
printf("添加\n");
// ======================= 增
if (n >= MAX) {
printf("空间不足\n");
return;
}
printf("增加第 %d 个学生的信息\n", n+1);
printf("请输入姓名:");
scanf("%s", s[n].name);
printf("请输入年龄:");
scanf("%d", &s[n].age);
printf("请输入性别(m或f):");
// " %c" 前面有一个空格,吃掉上一步的'\n'
scanf(" %c", &s[n].sex);
n++; // 学生人数+1
printf("新增成功\n");
}
void show_all() { // 显示所有学生
printf("显示\n");
// ======================= 查所有
printf("姓名\t年龄\t性别\n");
for (int i = 0; i < n; i++) { // s[i]
printf("%s\t%d\t%c\n", s[i].name, s[i].age, s[i].sex);
}
}
void find_someone() { // 查询
printf("查询\n");
char temp[30];
printf("请输入姓名:");
scanf("%s", temp);
int pos = find_pos_by_name(temp);
// printf("pos = %d\n", pos);
if (pos != -1) {
// ======================= 查
printf("%s 的信息如下:\n", temp);
printf("%s\t%d\t%c\n", s[pos].name, s[pos].age, s[pos].sex);
} else {
printf("%s 不存在\n", temp);
}
}
void modify_someone() { // 修改
printf("修改\n");
char temp[30];
printf("请输入姓名:");
scanf("%s", temp);
int pos = find_pos_by_name(temp);
// printf("pos = %d\n", pos);
if (pos != -1) {
// ======================= 改
printf("请输入新的姓名:");
scanf("%s", s[pos].name);
printf("请输入新的年龄:");
scanf("%d", &s[pos].age);
printf("请输入新的性别(m或f):");
// " %c" 前面有一个空格,吃掉上一步的'\n'
scanf(" %c", &s[pos].sex);
printf("修改成功\n");
} else {
printf("%s 不存在\n", temp);
}
}
void delete_someone() { // 删除
printf("删除\n");
char temp[30];
printf("请输入姓名:");
scanf("%s", temp);
int pos = find_pos_by_name(temp);
// printf("pos = %d\n", pos);
if (pos != -1) {
// ======================= 删
s[pos] = s[n-1];
n--;
printf("%s 删除成功\n", temp);
} else {
printf("%s 不存在\n", temp);
}
}
int main() {
// 1. 死循环
while (1) {
// 2. 调用菜单
help_menu();
// 3. 输入指令
int cmd;
printf("请输入指令数字:");
scanf("%d", &cmd);
// 4. 判断
if (cmd == 1) {
add_stu();
} else if (cmd == 2) {
show_all();
} else if (cmd == 3) {
find_someone();
} else if (cmd == 4) {
modify_someone();
} else if (cmd == 5) {
delete_someone();
} else if (cmd == 6) {
printf("退出\n");
break;
} else {
printf("指令错误,请重新输入\n");
}
}
return 0;
}
- 建议:开始的时候可以把思路捋清楚,然后对着学,对着敲一遍,后面熟悉了,再自己敲一遍
点击查看更多内容
为 TA 点赞
评论
共同学习,写下你的评论
评论加载中...
作者其他优质文章
正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦