动态

详情 返回 返回

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;
}
  • 建議:開始的時候可以把思路捋清楚,然後對着學,對着敲一遍,後面熟悉了,再自己敲一遍
user avatar xinggandemuer_b5u1v2 头像 u_16231477 头像 mstech 头像 zhuifengdekaomianbao 头像 josie_68d213f999ae8 头像 kukudejiqimao_bns3pe 头像 cryptorzz 头像 infodator 头像 guishangguandao 头像 f702 头像 gmicloud 头像 georgegcs 头像
点赞 24 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.