C语言的复合类型、内存管理、综合案例

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 枚举

  • 枚举:将变量的值一一列举出来,变量的值只限于列举出来的值的范围内

  • 语法格式:

    c 复制代码
    enum  枚举名 { 枚举值表 };
  • 在枚举值表中应列出所有可用值,也称为枚举元素

    • 枚举值是常量,不能在程序中用赋值语句再对它赋值
    • 枚举元素本身由系统定义了一个表示序号的数值从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;
}
相关推荐
stars2 分钟前
数字人开发02--前端服务配置
前端·人工智能
懋学的前端攻城狮35 分钟前
Next.js + TypeScript + Shadcn UI:构建高性能懒加载与无限滚动系统
前端·react.js·前端框架
hqxstudying37 分钟前
SpringBoot启动项目详解
java·spring boot·后端
宋辰月1 小时前
Vue2的进阶Vue3
前端·javascript·vue.js
你我约定有三1 小时前
分布式微服务--Nacos作为配置中心(补)关于bosststrap.yml与@RefreshScope
java·分布式·spring cloud·微服务·架构
keepDXRcuriosity2 小时前
IDEA识别lombok注解问题
java·ide·intellij-idea
海上Bruce3 小时前
C primer plus (第六版)第十章 编程练习第7,8,9,10,11题
c语言
仟濹3 小时前
【C/C++】整数超过多少位时用「高精度」
c语言·c++·算法
夏影孤灯3 小时前
C 语言问题
c语言·开发语言