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;
}
- 建議:開始的時候可以把思路捋清楚,然後對着學,對着敲一遍,後面熟悉了,再自己敲一遍