为了账号安全,请及时绑定邮箱和手机立即绑定

C语言:复合类型,内存管理,综合案例

标签:
C++ C# 嵌入式

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 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

举报

0/150
提交
取消