引言:C 語言的魅力與挑戰
從操作系統內核到嵌入式系統,從高性能計算到網絡編程,C 語言高效、靈活和貼近硬件的特性,始終占據著不可替代的地位。然而,C 語言的強大也伴隨著較高的學習曲線,尤其是指針、內存管理和復雜數據結構的操作? 經常搞得人暈.....
哥們這次特地基于 700 余行實戰代碼,系統梳理 C 語言的核心知識點,從基礎語法到高級應用,從內存模型到算法實現,幫助讀者建立完整的 C 語言知識體系。
第一部分:筆者搜集的?csdn牛客里扣github博客園開源中國等頭部技術論壇的大廠考點實戰:
一、基礎概念與內存模型
1. 指針與數組的本質區別
題目:以下關于指針與數組的說法錯誤的是( )
A. 數組名在多數情況下會衰退為指針
B.?sizeof(arr)
返回數組總字節數,sizeof(ptr)
返回指針字節數
C. 指針可以進行算術運算,數組名不能
D. 數組元素存儲在堆上,指針變量存儲在棧上
解析:
答案 D。數組元素存儲位置取決于定義方式:局部數組在棧上,全局 / 靜態數組在數據段,動態分配的數組在堆上。指針變量本身是變量,存儲位置由定義位置決定(局部指針在棧,全局指針在數據段)。
關鍵點:
- 數組名衰退規則:除
sizeof(arr)
和&arr
外,數組名會衰退為指向首元素的指針 - 指針算術運算本質是地址偏移,步長由指向類型決定
2. 指針大小與平臺相關性
題目:在 64 位 Linux 系統中,以下指針類型的大小分別是( )
int *p1;
void **p2;
char (*p3)[10];
int (*p4)(int, int);
A. 8,8,8,8 B. 4,8,8,4 C. 8,16,8,8 D. 4,4,4,4
解析:
答案 A。在 64 位系統中,所有指針類型(包括函數指針、多級指針、數組指針)的大小均為 8 字節,與指向類型無關。
關鍵點:
- 指針大小僅由操作系統位數決定:32 位 4 字節,64 位 8 字節
- 函數指針、數組指針本質仍是指針,遵循相同大小規則
3. 野指針成因與危害
題目:以下代碼會產生野指針的是( )
A. int *p; *p = 10;
B. int *p = (int*)malloc(sizeof(int)); free(p); p = NULL;
C. int arr[5], *p = arr; p += 5;
D. int *p = &(int){10};
解析:
答案 A、C、D。
- A:未初始化的指針直接解引用,是典型野指針
- C:指針超出數組邊界,指向無效內存
- D:臨時變量地址在表達式結束后失效,形成野指針
關鍵點:
野指針常見成因:
- 未初始化的指針
- 釋放后未置 NULL 的指針
- 越界訪問的指針
- 指向臨時變量的指針
二、指針與數組高級操作
4. 二維數組與指針運算
題目:對于二維數組int arr[3][4]
,以下表達式值為arr[1][2]
的是( )
A. *(arr + 1 + 2)
B. *(arr [1] + 2)
C.?((arr + 2) + 1)
D. arr[1] + 2
解析:
答案 B。
arr
是指向int[4]
的指針,arr[1]
等價于*(arr+1)
,類型為int*
arr[1]+2
是指向arr[1][2]
的指針,解引用后得到值
關鍵點:
二維數組在內存中按行存儲,arr[i][j]
等價于*(*(arr+i)+j)
5. 指針數組與數組指針辨析
題目:定義int (*p)[5]
和int *p[5]
,以下說法正確的是( )
A. 兩者都是指針數組,存儲 5 個 int * 指針
B. 前者是數組指針,后者是指針數組
C. 前者指針指向 5 個 int,后者數組存儲 5 個指針
D. 兩者沒有區別
解析:
答案 B。
int (*p)[5]
:數組指針,指向包含 5 個 int 的數組int *p[5]
:指針數組,包含 5 個 int * 指針
關鍵點:
- 括號優先級:
*p[5]
中[]
優先級高于*
,先成數組 (int (*)[5])
中括號改變優先級,先成指針
6. 字符串指針與數組的陷阱
題目:分析以下代碼的輸出:
void string_trap() {char str[] = "hello";char *ptr = "world";str[0] = 'H';ptr[0] = 'W';
}
A. 編譯錯誤
B. 運行時錯誤(段錯誤)
C. 正常運行,str 變為 "Hello",ptr 變為 "World"
D. str 變為 "Hello",ptr 指向的字符串不變
解析:
答案 B。
str
是數組,存儲在棧上,可以修改ptr
指向字符串常量,存儲在代碼段,修改會導致段錯誤
關鍵點:
- 字符串字面量存儲在只讀區,不能修改
- 數組名作為左值時可修改元素
三、內存管理與指針操作
7. 動態內存分配與指針
題目:以下代碼存在的問題是( )
c
運行
void memory_bug() {int *p = (int*)malloc(10 * sizeof(int));for (int i=0; i<10; i++) p[i] = i;int *q = (int*)realloc(p, 20 * sizeof(int));free(p);
}
A. 沒有問題
B. realloc 后未檢查返回值
C. free (p) 釋放了已重新分配的內存
D. 內存泄漏
解析:
答案 B、C。
- realloc 可能失敗,需檢查返回值
- realloc 成功時,p 的地址可能改變,原 p 被 q 覆蓋后釋放 q 才正確
關鍵點:
realloc 使用規范:
void *new_ptr = realloc(old_ptr, new_size);
if (new_ptr) {old_ptr = new_ptr;
}
8. 指針與結構體對齊
題目:已知結構體:
struct Data {char c;int i;double d;
};
在 64 位系統中,sizeof(struct Data)
的結果是( )
A. 13 B. 16 C. 24 D. 32
解析:
答案 B。
- 64 位系統默認對齊為 8 字節
char c
占 1,補 3 到 4 字節int i
占 4,累計 8 字節double d
占 8,累計 16 字節
關鍵點:
結構體對齊規則:
- 每個成員按自身大小和對齊參數取最小對齊
- 整體大小為最大對齊參數的整數倍
9. 多級指針操作
題目:執行以下代碼后,**pp
的值是( )
int a = 10, b = 20;
int *p = &a, **pp = &p;
p = &b;
A. 10 B. 20 C. 編譯錯誤 D. 運行時錯誤
解析:
答案 B。
pp
是指向p
的指針,p
先指向a
,后指向b
**pp
等價于*p
,即b
的值
關鍵點:
多級指針本質是指針的指針,解引用次數等于級別數
四、函數指針與回調
10. 函數指針數組實現計算器
題目:用函數指針數組實現四則運算計算器,要求支持+
、-
、*
、/
,并處理除零錯誤。
解析:
#include <stdio.h>
#include <stdlib.h>// 函數指針類型
typedef int (*OpFunc)(int, int);// 四則運算函數
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) {if (b == 0) {printf("錯誤:除數不能為零\n");exit(1);}return a / b;
}int main() {// 函數指針數組OpFunc ops[4] = {add, subtract, multiply, divide};char op;int a, b;printf("輸入運算(+ - * /): ");scanf(" %c", &op);printf("輸入兩個操作數: ");scanf("%d %d", &a, &b);// 根據操作符選擇函數int idx = -1;switch (op) {case '+': idx = 0; break;case '-': idx = 1; break;case '*': idx = 2; break;case '/': idx = 3; break;default: printf("不支持的操作符\n"); return 1;}printf("結果: %d\n", ops[idx](a, b));return 0;
}
關鍵點:
- 函數指針數組實現多態操作
- 類型安全檢查與錯誤處理
五、算法與數據結構中的指針應用
11. 鏈表逆序(指針操作)
題目:用指針操作實現單鏈表的逆序,要求時間復雜度 O (n),空間復雜度 O (1)。
解析:
struct Node {int data;struct Node *next;
};struct Node* reverseList(struct Node* head) {struct Node *prev = NULL;struct Node *current = head;struct Node *next = NULL;while (current != NULL) {next = current->next; // 保存下一個節點current->next = prev; // 反轉指針prev = current; // 移動prevcurrent = next; // 移動current}return prev; // 新的頭節點
}
關鍵點:
三指針法:prev
、current
、next
配合實現指針反轉
12. 快速排序中的指針應用
題目:用指針操作實現快速排序,要求使用void*
指針實現通用排序。
解析:
#include <stdio.h>
#include <stdlib.h>// 交換函數
void swap(void *a, void *b, size_t size) {char temp[size];memcpy(temp, a, size);memcpy(a, b, size);memcpy(b, temp, size);
}// 分區函數
int partition(void *base, int low, int high, size_t size, int (*cmp)(const void*, const void*)) {void *pivot = (char*)base + high * size;int i = low - 1;for (int j = low; j < high; j++) {void *elem = (char*)base + j * size;if (cmp(elem, pivot) <= 0) {i++;swap((char*)base + i * size, elem, size);}}swap((char*)base + (i+1) * size, pivot, size);return i + 1;
}// 快速排序
void quickSort(void *base, int n, size_t size, int (*cmp)(const void*, const void*)) {if (n > 1) {int pi = partition(base, 0, n-1, size, cmp);quickSort(base, pi, size, cmp);quickSort((char*)base + (pi+1)*size, n - pi - 1, size, cmp);}
}// 測試
int intCmp(const void *a, const void *b) {return *(int*)a - *(int*)b;
}int main() {int arr[] = {3, 1, 4, 1, 5, 9, 2};int n = sizeof(arr)/sizeof(arr[0]);quickSort(arr, n, sizeof(int), intCmp);for (int i=0; i<n; i++) {printf("%d ", arr[i]);}return 0;
}
關鍵點:
void*
指針實現通用排序- 內存操作函數
memcpy
實現任意類型交換
六、綜合應用與陷阱
13. 指針與數組傳參陷阱
題目:以下函數中sizeof(arr)
的結果是( )
void func(int arr[10]) {printf("%zu\n", sizeof(arr));
}int main() {int arr[5];func(arr);return 0;
}
A. 20 B. 8 C. 40 D. 編譯錯誤
解析:
答案 B。數組作為函數參數時衰退為指針,sizeof(arr)
返回指針大小(64 位系統 8 字節)
關鍵點:
數組傳參本質是傳遞指針,無法在函數內獲取原始數組大小
14. 指針運算與類型轉換
題目:計算以下表達式的值(64 位系統):
char *p = "hello";
int *q = (int*)p;
q += 1;
q - p
的值是( )
A. 1 B. 4 C. 8 D. 編譯錯誤
解析:
答案 B。int*
指針 + 1 偏移 4 字節(int 大小),char*
指針偏移 1 字節,差值為 4
關鍵點:
指針算術運算的步長由指向類型決定
15. 內存泄漏檢測
題目:找出以下代碼中的內存泄漏:
void leak_demo() {int *p1 = (int*)malloc(10*sizeof(int));int *p2 = (int*)realloc(p1, 20*sizeof(int));if (p2 == NULL) return;free(p1);
}
解析:
realloc
成功時,p1
指向的內存已被重新分配,原p1
地址可能改變,直接free(p1)
會釋放新分配的內存,導致原內存泄漏。應改為:
int *p2 = realloc(p1, 20*sizeof(int));
if (p2) {p1 = p2; // 更新指針
}
七、編程題(大廠算法題)
16. 指針實現字符串拷貝(模擬 strcpy)
題目:用指針操作實現strcpy
函數,要求處理邊界情況。
解析:
char* my_strcpy(char* dest, const char* src) {char* res = dest;if (dest == NULL || src == NULL) return NULL;while (*src) {*dest++ = *src++;}*dest = '\0';return res;
}
17. 指針實現鏈表環檢測(Floyd 判圈算法)
題目:用指針操作實現鏈表環檢測,要求時間復雜度 O (n),空間復雜度 O (1)。
解析:
int hasCycle(struct Node *head) {if (head == NULL || head->next == NULL) return 0;struct Node *slow = head;struct Node *fast = head->next;while (slow != fast) {if (fast == NULL || fast->next == NULL) return 0;slow = slow->next;fast = fast->next->next;}return 1;
}
18. 指針與內存池設計
題目:設計一個簡單內存池,要求:
- 預分配一塊大內存
- 實現內存分配與釋放
- 避免碎片
解析:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>#define POOL_SIZE 1024*1024 // 1MB內存池typedef struct {char* memory; // 內存池起始地址char* current; // 當前分配位置int size; // 內存池大小
} MemoryPool;// 初始化內存池
MemoryPool* initMemoryPool(int size) {MemoryPool* pool = (MemoryPool*)malloc(sizeof(MemoryPool));if (pool == NULL) return NULL;pool->memory = (char*)malloc(size);if (pool->memory == NULL) {free(pool);return NULL;}pool->current = pool->memory;pool->size = size;return pool;
}// 從內存池分配內存
void* allocFromPool(MemoryPool* pool, int size) {if (pool == NULL || size <= 0) return NULL;if (pool->current + size > pool->memory + pool->size) {printf("內存池不足\n");return NULL;}void* res = pool->current;pool->current += size;return res;
}// 釋放內存池
void freeMemoryPool(MemoryPool* pool) {if (pool == NULL) return;free(pool->memory);free(pool);
}
八、系統級指針操作
19. 指針與類型轉換(內存復用)
題目:用指針操作實現 int 與 float 的內存互轉,要求不使用聯合。
解析:
20. 指針與位操作(內存映射)
題目:用指針操作實現將整數的第 3 位和第 7 位取反。
解析:
int flipBits(int num) {// 第3位和第7位掩碼int mask = (1 << 3) | (1 << 7);return num ^ mask;
}// 指針版本
void flipBitsPtr(int *num) {int mask = (1 << 3) | (1 << 7);*num ^= mask;
}
面試題解析方法論
大廠指針題核心考點歸納:
- 指針本質:地址操作、類型系統、衰退規則
- 內存模型:棧堆數據段、對齊規則、生命周期
- 算法應用:鏈表 / 數組操作、排序 / 搜索中的指針技巧
- 系統編程:內存管理、函數指針、類型轉換
- 安全問題:野指針、內存泄漏、越界訪問
解題思路:
- 畫圖分析:指針操作時畫出內存布局
- 類型推導:從定義推導指針類型(如
int (*)[5]
是數組指針) - 邊界測試:空指針、越界、類型轉換等邊界情況
- 內存跟蹤:動態分配時跟蹤指針變化
這些題目覆蓋了騰訊、阿里等大廠面試中指針相關的核心考點,從基礎概念到系統級編程,結合算法與數據結構,適合進階學習和面試準備。建議在理解原理的基礎上,動手實現并調試代碼,加深對指針本質的理解。
第二部分:相關知識點詳解
一、C 語言基礎數據結構與內存模型
1.1 結構體:數據組織的基石
結構體是 C 語言中組織復雜數據的核心機制,它允許我們將不同類型的數據組合成一個有機整體。在實際項目中,結構體的設計直接影響程序的效率和可維護性。
// 多用途節點結構體,適用于鏈表和樹結構
struct Node {int value; // 節點存儲的值struct Node *next; // 鏈表中的下一個節點struct Node **children; // 樹結構中的子節點數組int child_count; // 子節點數量
};// 人員信息結構體
struct Person {char name[10]; // 姓名int age; // 年齡
};// 棧數據結構實現
struct stack {int data[100]; // 棧存儲數組int top; // 棧頂指針
};
上述代碼定義了三種常用結構體:多用途節點結構體Node
、人員信息結構體Person
和棧結構體stack
。其中Node
結構體的設計體現了 C 語言的靈活性 —— 通過next
指針可以構建鏈表,通過children
指針數組可以構建樹結構,這種 "一結構多用途" 的設計在實際開發中非常常見。
結構體使用實戰:棧的實現與應用
棧是計算機科學中最基礎的數據結構之一,下面我們通過stack
結構體實現一個完整的棧,并演示其基本操作:
#define maxL 99 // 棧的最大容量// 初始化棧
void initStack(struct stack *s) {s->top = -1;
}// 判斷棧是否為空
int isEmpty(struct stack *s) {return s->top == -1 ? 1 : -100;
}// 判斷棧是否已滿
int isFull(struct stack *s) {return s->top == maxL ? 1 : 0;
}// 入棧操作
void pushStack(struct stack *s, int value) {if (!isFull(s)) {s->data[++(s->top)] = value;}
}// 出棧操作
int popStack(struct stack *s) {if (!isEmpty(s)) {return s->data[(s->top)--];}return -1;
}// 獲取棧頂元素
int peekTop(struct stack *s) {return s->data[s->top];
}// 棧操作測試
void stackTest() {struct stack s;initStack(&s);printf("初始化后,棧是否為空: %d\n", isEmpty(&s));pushStack(&s, 1);printf("壓入 1 后,棧是否為空: %d\n", isEmpty(&s));pushStack(&s, 2);pushStack(&s, 3);printf("棧頂元素: %d\n", peekTop(&s));pushStack(&s, 4);printf("棧頂元素: %d\n", peekTop(&s));popStack(&s);printf("彈出元素后,棧頂元素: %d\n", peekTop(&s));
}
這段代碼完整實現了棧的基本操作:初始化、判空、判滿、入棧、出棧和獲取棧頂元素。在stackTest
函數中,我們演示了棧的基本使用流程。需要注意的是,這里使用數組實現棧,屬于順序棧,其優點是訪問效率高,缺點是容量固定。在實際項目中,當需要動態調整棧大小時,可以考慮使用鏈表實現棧結構。
1.2 指針:C 語言的靈魂
指針是 C 語言的核心特性,也是讓許多初學者望而生畏的難點。理解指針,本質上是理解計算機內存的工作原理。
// 野指針成因演示
void wild_pointer_cause() {int a = 10;int *new_ptr = &a; // 合法指針,指向變量aint *heap_ptr = (int *)malloc(sizeof(int)); // 在堆上分配內存free(heap_ptr); // 釋放內存,但指針未置為NULL// heap_ptr現在成為野指針int arr[5] = {1, 2, 3, 4, 5};int *arr_ptr = arr;arr_ptr = arr_ptr + 10; // 指針越界,成為野指針
}// 指針關系運算演示
void pointer_relation() {int arr[5] = {1, 2, 3, 4, 5};int *p1 = arr + 1; // 指向arr[1]int *p2 = arr + 3; // 指向arr[3]printf("p1 < p2: %s\n", (p1 < p2) ? "true" : "false"); // 指針地址比較printf("p1 == arr+1: %s\n", (p1 == arr + 1) ? "true" : "false"); // 指針相等比較
}// 指針算術運算演示
void pointer_arithmetic_application() {int arr[5] = {1, 2, 3, 4, 5};int *p = arr;for (int i = 0; i < 5; i++) {printf("arr[%d] = %d\n", i, *(p + i)); // 通過指針算術訪問數組元素}
}
指針與數組的愛恨情仇
在 C 語言中,指針與數組的關系極為密切,但又有著本質區別。許多初學者容易混淆兩者,導致程序出現難以試的錯誤。
// 數組與指針區別演示
void array_pointer_question() {int arr[5] = {1, 2, 3, 4, 5};printf("sizeof(arr) = %zu\n", sizeof(arr)); // 輸出數組總字節數:5*4=20printf("sizeof(arr+0) = %zu\n", sizeof(arr + 0)); // 輸出指針字節數:8(64位系統)
}// 數組指針應用:二維數組操作
void array_ptr_application() {int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};int (*p)[3] = arr; // 定義指向包含3個int的數組的指針for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {// 兩種等價的訪問方式printf("1 the num: %d-%d:%d\n", i, j, *(p + i)[j]);printf("2 the num: %d-%d:%d\n", i, j, p[i][j]);}}
}
1.3 C 語言內存模型:從棧到堆的全景圖
理解 C 語言的內存分布是寫出高效、穩定程序的關鍵。C 程序的內存通常分為以下幾個區域:
// 內存分布演示
void memory_distribution_demo() {// 棧區:存儲局部變量int localVar = 10;char str[20] = "hello";// 堆區:動態分配的內存int *heapPtr = (int *)malloc(sizeof(int));if (heapPtr == NULL) {printf("內存分配失敗\n");return;}*heapPtr = 20;// 數據段:存儲全局變量和靜態變量static int staticVar = 30;// 代碼段:存儲可執行代碼和字符串常量const char *strConst = "world";// 釋放堆內存free(heapPtr);
}
二、字符串處理:從基礎函數到自定義實現
字符串是 C 語言中最常用的數據類型之一,熟練掌握字符串處理是 C 程序員的基本素養。標準庫提供了豐富的字符串函數,但了解其實現原理能幫助我們更好地使用它們。
2.1 字符串處理函數的自定義實現
c
// 自定義strstr函數:查找子字符串
char *my_strstr(const char *haystack, const char *needle) {assert(haystack != NULL && needle != NULL);// 空字符串特殊處理if (*needle == '\0') {return NULL;}while (*haystack) {const char *h = haystack;const char *n = needle;// 逐個字符比較while (*h && *n && *h == *n) {h++;n++;}// 找到匹配的子字符串if (*n == '\0') {return (char *)haystack;}haystack++;}return NULL;
}// 自定義strncpy函數:復制指定長度的字符串
char *my_strncpy(char *dest, const char *src, size_t n) {assert(dest != NULL && src != NULL);size_t i = 0;char *tempPtr = dest;// 復制src中的字符,直到n個或遇到'\0'while (i < n && src[i]) {dest[i] = src[i];i++;}// 填充剩余位置為'\0'while (i < n) {dest[i] = '\0';i++;}return tempPtr;
}// 自定義strncat函數:連接指定長度的字符串
char *my_strncat(char *dest, const char *src, size_t n) {assert(dest != NULL && src != NULL);size_t len = strlen(dest);char *res = dest;size_t i;// 連接src中的字符,直到n個或遇到'\0'for (i = 0; i < n && src[i] != '\0'; i++) {dest[len + i] = src[i];}dest[i + len] = '\0';return res;
}// 自定義strncmp函數:比較指定長度的字符串
int my_strncmp(const char *s1, const char *s2, size_t n) {assert(s1 != NULL && s2 != NULL);for (size_t i = 0; i < n; i++) {char c1 = s1[i] ? s1[i] : '\0';char c2 = s2[i] ? s2[i] : '\0';if (c1 != c2) {return c1 - c2;}}return 0;
}// 自定義strchr函數:查找字符
char *my_strchr(const char *str, int c) {assert(str != NULL);const char *p = str;while (*p) {if (*p == (char)c) {return (char *)p;}p++;}// 處理查找'\0'的情況return (char)c == '\0' ? (char *)p : NULL;
}
2.2 字符串處理實戰:從復制到拼接
c
// 字符串復制函數
char *str_copy(const char *src) {// 分配內存,+1用于存儲'\0'char *dest = (char *)malloc(strlen(src) + 1);assert(dest != NULL);char *p = dest;while (*src) {*p++ = *src++;}*p = '\0';return dest;
}// 字符串拼接演示
void char_ptr_application() {char str1[20] = "Hello";char *str2 = ", World!";char *p1 = str1 + strlen(str1);char *p2 = str2;// 手動拼接字符串while (*p2) {*p1++ = *p2++;}*p1 = '\0';printf("拼接后的字符串: %s\n", str1);
}// 字符串處理函數測試
void string_functions_test() {printf("字符串復制測試:\n");char *copy = str_copy("test");printf("復制后的字符串: %s\n", copy);free(copy);printf("\nstrncpy測試:\n");char dest[10] = {0};my_strncpy(dest, "hello", 3);printf("復制結果: %s\n", dest);printf("\nstrncat測試:\n");char dest_cat[10] = {'1', '2', '\0'};char src_cat[] = {'3', '4', 'a', '\0'};my_strncat(dest_cat, src_cat, 3);printf("拼接結果: %s\n", dest_cat);printf("\nstrncmp測試:\n");char s1[] = {'1', '2', '3', '5'};char s2[] = {'1', '2', '3'};printf("比較結果: %d\n", my_strncmp(s1, s2, 4));printf("\nstrchr測試:\n");char test[] = {'1', '2', 'a', 'b', 'c', 'f', 'l', '6', '\0'};char *res = my_strchr(test, 'a');if (res) {printf("找到字符 'a',位置: %ld\n", res - test);} else {printf("未找到字符\n");}printf("\nstrstr測試:\n");char haystack[] = {'1', 'a', '2', 'b', 'c', '\0'};char needle[] = {'b', 'c', '\0'};char *strstr_res = my_strstr(haystack, needle);if (strstr_res) {printf("找到子字符串,位置: %ld\n", strstr_res - haystack);} else {printf("未找到子字符串\n");}
}
三、算法與數據結構:從排序到遞歸
3.1 排序算法:快速排序的實現與優化
快速排序是實踐中常用的高效排序算法,其平均時間復雜度為 O (n log n)。
c
// 交換函數
void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}// 快速排序的分區函數
int partition(int arr[], int low, int high) {int pi = arr[high]; // 選擇最后一個元素作為基準int i = low - 1; // 小于基準的元素的邊界for (int j = low; j <= high - 1; j++) {// 如果當前元素小于等于基準if (arr[j] <= pi) {i++;swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[high]);return i + 1;
}// 快速排序主函數
void quick_Sort(int arr[], int low, int high) {if (low < high) {// 分區操作,返回基準的正確位置int pi = partition(arr, low, high);// 遞歸排序基準左側和右側quick_Sort(arr, low, pi - 1);quick_Sort(arr, pi + 1, high);}
}// 快速排序測試
void quick_sort_test() {int arr[] = {4, 5, 6, 7, 15, 234, 46, 698, 238, 258, 45, 2, 36, 26, 123, 77, 5, 48, 45, 2, 5, 325, 32, 1, 6};int len = sizeof(arr) / sizeof(arr[0]);printf("排序前數組:\n");for (int i = 0; i < len; i++) {printf("%d ", arr[i]);}printf("\n");quick_Sort(arr, 0, len - 1);printf("排序后數組:\n");for (int i = 0; i < len; i++) {printf("%d ", arr[i]);}printf("\n");
}
3.2 遞歸算法:斐波那契數列的實現
遞歸是一種強大的算法思想,常用于解決可以分解為相似子問題的場景。
c
// 斐波那契數列遞歸實現
int fibonacci(int n) {if (n <= 1) {return n;}return fibonacci(n - 1) + fibonacci(n - 2);
}// 斐波那契數列測試
void fibonacci_test() {printf("fib(5) = %d\n", fibonacci(5)); // 輸出5printf("fib(10) = %d\n", fibonacci(10)); // 輸出55
}// 遞歸優化思路:記憶化搜索
int fibonacci_memo(int n, int *memo) {if (n <= 1) {return n;}// 如果已經計算過,直接返回結果if (memo[n] != -1) {return memo[n];}// 計算并存儲結果memo[n] = fibonacci_memo(n - 1, memo) + fibonacci_memo(n - 2, memo);return memo[n];
}// 記憶化搜索測試
void fibonacci_memo_test() {int n = 20;int *memo = (int *)calloc(n + 1, sizeof(int));for (int i = 0; i <= n; i++) {memo[i] = -1;}printf("fib_memo(20) = %d\n", fibonacci_memo(20, memo));free(memo);
}
四、高級主題:從函數指針到內存管理
4.1 函數指針:C 語言的回調機制
函數指針是 C 語言中實現回調機制的基礎,也是許多高級特性的基石。
c
// 加法函數
int add(int a, int b) {return a + b;
}// 減法函數
int subtract(int a, int b) {return a - b;
}// 函數指針數組演示
void func_ptr_array_demo() {// 定義函數指針類型typedef int (*OpFunc)(int, int);// 創建函數指針數組OpFunc ops[] = {add, subtract};printf("5+3=%d\n", ops[0](5, 3));printf("5-3=%d\n", ops[1](5, 3));
}// 回調函數示例:排序比較函數
int compare_asc(const void *a, const void *b) {return *(int *)a - *(int *)b;
}// qsort函數使用演示
void qsort_demo() {int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};int n = sizeof(arr) / sizeof(arr[0]);printf("排序前:\n");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");// 使用qsort排序,傳入比較函數qsort(arr, n, sizeof(int), compare_asc);printf("排序后:\n");for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}printf("\n");
}
4.2 內存管理:從 malloc 到野指針防護
c
// 動態內存分配演示
int *dy_alloc(int n) {int *res = (int *)malloc(n * sizeof(int));if (res == NULL) {printf("內存分配失敗\n");exit(1);}// 初始化分配的內存for (int i = 0; i < n; i++) {res[i] = i;}return res;
}// 內存管理最佳實踐
void memory_management_best_practices() {// 分配內存int *ptr1 = (int *)malloc(10 * sizeof(int));if (ptr1 == NULL) {printf("內存分配失敗\n");return;}// 使用內存for (int i = 0; i < 10; i++) {ptr1[i] = i;}// 重新分配內存int *ptr2 = (int *)realloc(ptr1, 20 * sizeof(int));if (ptr2 == NULL) {free(ptr1);printf("內存重新分配失敗\n");return;}ptr1 = ptr2; // 更新指針// 繼續使用內存for (int i = 10; i < 20; i++) {ptr1[i] = i;}// 釋放內存free(ptr1);ptr1 = NULL; // 置為NULL,避免野指針
}// 野指針防護策略
void wild_pointer_prevention() {int *ptr = NULL; // 初始化為NULL// 分配內存ptr = (int *)malloc(sizeof(int));if (ptr == NULL) {printf("內存分配失敗\n");return;}// 使用內存*ptr = 10;// 釋放內存free(ptr);ptr = NULL; // 釋放后立即置為NULL// 安全檢查if (ptr != NULL) {// 不會執行到這里}
}
五、綜合實戰:從鏈表到矩陣操作
5.1 鏈表操作:從創建到遍歷
c
// 在鏈表頭部插入節點
struct Node *insert_atFirst(struct Node *head, int data) {struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));if (n_node == NULL) {printf("內存分配失敗\n");return head;}n_node->value = data;n_node->next = head;n_node->children = NULL;n_node->child_count = 0;return n_node;
}// 打印鏈表
void print_List(struct Node *node) {struct Node *l = node;while (l != NULL) {printf("鏈表節點值: %d\n", l->value);l = l->next;}
}// 鏈表操作測試
void linked_list_test() {struct Node *head_node = NULL;head_node = insert_atFirst(head_node, 123);printf("第一個節點值: %d\n", head_node->value);head_node = insert_atFirst(head_node, 4);printf("第二個節點值: %d\n", head_node->value);print_List(head_node);
}
5.2 矩陣操作:轉置與拼接
c
// 矩陣轉置
void zhuanzhi(int arr[3][3]) {for (int i = 0; i < 3; i++) {for (int j = i + 1; j < 3; j++) {int temp = arr[i][j];arr[i][j] = arr[j][i];arr[j][i] = temp;}}
}// 矩陣轉置測試
void matrix_transpose_test() {int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};printf("轉置前矩陣:\n");for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {printf("%d ", arr[i][j]);}printf("\n");}zhuanzhi(arr);printf("轉置后矩陣:\n");for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {printf("%d ", arr[i][j]);}printf("\n");}
}// 字符串拼接
void connect_char(char *a, char *b) {char *p1 = a + strlen(a);while (*b) {*p1 = *b;p1++;b++;}*p1 = '\0';
}// 字符串拼接測試
void string_concatenation_test() {char char1[] = {'1', '2', '3', '4', '5', 'a', '\0'};char char2[] = {'b', '9', '\0'};printf("拼接前char1: %s, char2: %s\n", char1, char2);connect_char(char1, char2);printf("拼接后char1: %s\n", char1);
}
六、C 語言編程最佳實踐與常見陷阱
6.1 編程規范與最佳實踐
c
// 命名規范演示
#define MAX_STACK_SIZE 100 // 宏定義使用全大寫
typedef struct {int data[MAX_STACK_SIZE];int top;
} Stack; // 結構體類型使用大寫開頭// 函數命名使用小寫加下劃線
Stack* stack_create() {Stack *s = (Stack *)malloc(sizeof(Stack));if (s == NULL) {return NULL;}s->top = -1;return s;
}// 注釋規范
int calculate_sum(int a, int b) {// 計算兩個整數的和return a + b;
}// 代碼縮進與格式
if (n > 0) {for (int i = 0; i < n; i++) {if (arr[i] > 0) {process(arr[i]);}}
} else {printf("n 必須為正數\n");
}
6.2 常見陷阱與解決方案
c
// 數組越界陷阱
void array_overflow_trap() {int arr[5] = {1, 2, 3, 4, 5};int *ptr = arr;// 危險操作:越界訪問for (int i = 0; i < 10; i++) {printf("%d ", ptr[i]); // 訪問越界,行為未定義}
}// 內存泄漏陷阱
void memory_leak_trap() {while (1) {int *ptr = (int *)malloc(sizeof(int));// 忘記調用free(ptr)}
}// 空指針解引用陷阱
void null_pointer_dereference() {int *ptr = NULL;*ptr = 10; // 空指針解引用,程序崩潰
}// 解決方案:防御性編程
void defensive_programming() {int *ptr = (int *)malloc(sizeof(int));if (ptr == NULL) {printf("內存分配失敗\n");return;}*ptr = 10;// 使用指針前檢查是否為NULLif (ptr != NULL) {printf("ptr的值: %d\n", *ptr);}free(ptr);ptr = NULL; // 釋放后置為NULL
}
七、總結+本人vscode本地編輯的源碼:
?
1 附錄1 指針知識代碼源碼:
2? 附錄代碼2 :指針知識點總結
?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>// 結構體定義
// 定義 Node 結構體,用于多級指針操作和鏈表
struct Node
{int value; // 節點值struct Node *next; // 指向下一個節點的指針(用于鏈表)struct Node **children; // 子節點數組(用于多級指針)int child_count; // 子節點數量
};// 定義 Person 結構體,包含姓名和年齡
struct Person
{char name[10];int age;
};// 定義棧結構體
struct stack
{int data[100]; // 假設棧最大容量為 100int top;
};#define maxL 99 // 棧的最大容量// 函數聲明
// 棧操作函數
void initStack(struct stack *s);
int isEmpty(struct stack *s);
int isFull(struct stack *s);
void pushStack(struct stack *s, int value);
int popStack(struct stack *s);
int peekTop(struct stack *s);// 鏈表操作函數
struct Node *insert_atFirst(struct Node *head, int data);
void print_List(struct Node *node);// 排序函數
void quick_Sort(int arr[], int low, int high);
int partition(int arr[], int low, int high);
void swap(int *a, int *b);// 字符串處理函數
char *my_strstr(const char *haystack, const char *needle);
char *my_strncpy(char *dest, const char *src, size_t n);
char *my_strncat(char *dest, const char *src, size_t n);
int my_strncmp(const char *s1, const char *s2, size_t n);
char *my_strchr(const char *str, int c);
char *str_copy(const char *src);// 其他函數
void wild_pointer_cause();
void pointer_relation();
void pointer_arithmetic_application();
void array_pointer_question();
void array_ptr_application();
void char_ptr_application();
void const_pointer();
void value_pass_application();
void pass_row_ptr(int arr[][3], int rows);
void qsort_demo();
void func_ptr_array();
void testFuncPtr();
int fibonacci(int n);
void test_fibonacci();
void typedef_usage();
void dachangmianshi3();
void test_strncpy();
void test_str_cat();
void test_strncmp();
void test_strchar();
void test_strstr();
void arrSort(char *arr[], int n);
int *dy_alloc(int n);
void add_child(struct Node *parent, struct Node *child);
void *arrtoPtrInt(void *x);
int getStrLen(char *a);
char *strCpyFn(char *dest, char *src);
void zhuanzhi(int arr[3][3]);
void connect_char(char *a, char *b);// 加法函數
int add(int a, int b);
// 減法函數
int subtract(int a, int b);
// 測試字符串復制函數
void test_str_copy();// 棧操作函數實現
// 初始化棧
void initStack(struct stack *s)
{s->top = -1;
}// 判斷棧是否為空
int isEmpty(struct stack *s)
{return s->top == -1 ? 1 : -100;
}// 判斷棧是否已滿
int isFull(struct stack *s)
{return s->top == maxL ? 1 : 0;
}// 入棧操作
void pushStack(struct stack *s, int value)
{if (!isFull(s)){s->data[++(s->top)] = value;}
}// 出棧操作
int popStack(struct stack *s)
{if (!isEmpty(s)){return s->data[(s->top)--];}return -1;
}// 獲取棧頂元素
int peekTop(struct stack *s)
{return s->data[s->top];
}// 鏈表操作函數實現
// 在鏈表頭部插入節點
struct Node *insert_atFirst(struct Node *head, int data)
{struct Node *n_node = (struct Node *)malloc(sizeof(struct Node));n_node->value = data; // 修改為正確的成員名 valuen_node->next = head; // 使用正確的成員名 nextn_node->children = NULL; // 初始化子節點數組n_node->child_count = 0; // 初始化子節點數量return n_node;
}// 打印鏈表節點數據
void print_List(struct Node *node)
{struct Node *l = node;while (l != NULL){printf("當前打印的節點:數據為 %d \n", l->value); // 修改為正確的成員名 valuel = l->next; // 使用正確的成員名 next}
}// 排序函數實現
// 快速排序
void quick_Sort(int arr[], int low, int high)
{if (low < high){int pi = partition(arr, low, high);quick_Sort(arr, low, pi - 1);quick_Sort(arr, pi + 1, high);}
}// 分區函數
int partition(int arr[], int low, int high)
{int pi = arr[high];int i = low - 1;for (int j = low; j <= high - 1; j++){if (arr[j] <= pi){i++;swap(&arr[i], &arr[j]);}}swap(&arr[i + 1], &arr[high]);return i + 1;
}// 交換兩個整數的值
void swap(int *a, int *b)
{int temp = *a;*a = *b;*b = temp;
}// 字符串處理函數實現
// 自定義 strstr 函數,查找子字符串
char *my_strstr(const char *haystack, const char *needle)
{assert(haystack != NULL && needle != NULL);if (*needle == '\0'){return NULL;}while (*haystack){const char *h = haystack;const char *n = needle;while (*h && *n && *h == *n){h++;n++;}if (*n == '\0'){return (char *)haystack;}haystack++;}return NULL;
}// 自定義 strncpy 函數,復制指定長度的字符串
char *my_strncpy(char *dest, const char *src, size_t n)
{assert(dest != NULL && src != NULL);size_t i = 0;char *tempPtr = dest;while (i < n && src[i]){dest[i] = src[i];i++;}while (i < n){dest[i] = '\0';i++;}return tempPtr;
}// 自定義 strncat 函數,連接指定長度的字符串
char *my_strncat(char *dest, const char *src, size_t n)
{assert(dest != NULL && src != NULL);size_t len = strlen(dest);char *res = dest;size_t i;for (i = 0; i < n && src[i] != '\0'; i++){dest[len + i] = src[i];}dest[i + len] = '\0';return res;
}// 自定義 strncmp 函數,比較指定長度的字符串
int my_strncmp(const char *s1, const char *s2, size_t n)
{assert(s1 != NULL && s2 != NULL);for (size_t i = 0; i < n; i++){char c1 = s1[i] ? s1[i] : '\0';char c2 = s2[i] ? s2[i] : '\0';if (c1 != c2){return c1 - c2;}}return 0;
}// 自定義 strchr 函數,查找字符
char *my_strchr(const char *str, int c)
{printf("\n___>>>>> in the strchar func!!\n");assert(str != NULL);const char *p = str;while (*p){if (*p == (char)c){return (char *)p;}p++;}return (char)c == '\0' ? (char *)p : NULL;
}// 自定義字符串復制函數
char *str_copy(const char *src)
{char *dest = (char *)malloc(strlen(src) + 1);assert(dest != NULL);char *p = dest;while (*src){*p++ = *src++;}*p = '\0';return dest;
}// 其他函數實現
// 演示野指針的成因
void wild_pointer_cause()
{int a = 10;int *new_ptr = &a;int *heap_ptr = (int *)malloc(sizeof(int));free(heap_ptr);int arr[5] = {1, 2, 3, 4, 5};
}// 演示指針的關系運算
void pointer_relation()
{int arr[5] = {1, 2, 3, 4, 5};int *p1 = arr + 1;int *p2 = arr + 3;printf("p1 < p2: %s\n", (p1 < p2) ? "true" : "false");printf("p1 == arr+1: %s\n", (p1 == arr + 1) ? "true" : "false");
}// 演示指針的算術運算
void pointer_arithmetic_application()
{int arr[5] = {1, 2, 3, 4, 5};int *p = arr;for (int i = 0; i < 5; i++){printf("arr[%d] = %d\n", i, *(p + i));}
}// 演示數組指針相關問題
void array_pointer_question()
{int arr[5] = {1, 2, 3, 4, 5};printf("sizeof(arr) = %zu\n", sizeof(arr));printf("sizeof(arr+0) = %zu\n", sizeof(arr + 0));
}// 演示數組指針的應用
void array_ptr_application()
{int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};int (*p)[3] = arr;for (int i = 0; i < 3; i++){for (int j = 0; j < 3; j++){printf("1 the numn :%d-%d:%d\n", i, j, *(p + i)[j]);printf("2 the num: %d -%d : %d\n", i, j, p[i][j]);}}printf("\n");
}// 演示字符指針的應用
void char_ptr_application()
{char str1[20] = "Hello";char *str2 = ", World!";char *p1 = str1 + strlen(str1);char *p2 = str2;while (*p2){*p1++ = *p2++;}*p1 = '\0';printf("拼接后的字符串: %s\n", str1);
}// 演示 const 指針的使用
void const_pointer()
{int val = 10;const int *cp1 = &val;int *const cp2 = &val;
}// 演示值傳遞的應用
void value_pass_application()
{printf("-->>\nin hte 21 value-pass-func:\n---->>\n");int x = 5;void value_pass(int *);value_pass(&x);printf("x的值不變: %d\n", x);
}// 傳遞二維數組的行指針
void pass_row_ptr(int arr[][3], int rows)
{for (int i = 0; i < rows; i++){for (int j = 0; j < 3; j++){printf("%d ", arr[i][j]);}printf("\n");}
}// 比較函數,用于 qsort
int compare_asc(const void *a, const void *b)
{return *(int *)a - *(int *)b;
}// 演示 qsort 函數的使用
void qsort_demo()
{int arr[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5};int n = sizeof(arr) / sizeof(arr[0]);qsort(arr, n, sizeof(int), compare_asc);for (int i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}// 演示函數指針數組的使用
int add(int a, int b);
int subtract(int a, int b); // 修正函數聲明void func_ptr_array()
{typedef int (*OpFunc)(int, int);OpFunc ops[] = {add, subtract};printf("5+3=%d\n", ops[0](5, 3));printf("5-3=%d\n", ops[1](5, 3));
}// 測試函數指針
void testFuncPtr()
{// 示例代碼可根據實際需求添加
}// 斐波那契數列遞歸實現
int fibonacci(int n)
{if (n <= 1)return n;return fibonacci(n - 1) + fibonacci(n - 2);
}// 測試斐波那契數列
void test_fibonacci()
{printf("fib(5) = %d\n", fibonacci(5));
}// 演示 typedef 的使用
void typedef_usage()
{typedef int IntArray[5];IntArray arr;arr[0] = 10;printf("typedef 使用示例: %d\n", arr[0]);
}// 大廠面試相關代碼
void dachangmianshi3()
{
#define PTR_INT int *typedef int *ptr_int;PTR_INT a, b;ptr_int x, y;printf("--->>> %s %s \n", "int*", "int");printf("--->>> %s %s \n", "int*", "int*");
}// 測試 strncpy 函數
void test_strncpy()
{char dest[10] = {0};my_strncpy(dest, "hello", 3);printf("strncpy---復制結果: %s\n", dest);
}// 測試 strncat 函數
void test_str_cat()
{printf("\n---->>>>>\n strcat 函數測試\n");char dest[10] = {'1', '2', '\0'};char src[] = {'3', '4', 'a', '\0'};char *res = my_strncat(dest, src, 3);printf("--->>> \n str cat func --->>> \n%s", res);
}// 測試 strncmp 函數
void test_strncmp()
{char s1[] = {'1', '2', '3', '5'};char s2[] = {'1', '2', '3'};printf("\n--->>>\nstrnCmp 函數測試結果:\n%d", my_strncmp(s1, s2, 4));
}// 測試 strchr 函數
void test_strchar()
{char test[] = {'1', '2', 'a', 'b', 'c', 'f', 'l', '6', '\0'};char *res = my_strchr(test, '7');if (!res){printf("not found!!\n");}else{printf("founded !!!! res is %c \n\n", *res);}
}// 測試 strstr 函數
void test_strstr()
{char haystack[] = {'1', 'a', '2', 'b', 'c', '\0'};char needle[] = {'b', 'c', '\0'};char *res = my_strstr(haystack, needle); // 使用自定義的 my_strstr 函數if (res){printf("-->>>>>>\n now in strstr func, res is :%c \n", *res);}else{printf("-->>>>>>\n now in strstr func, not found \n");}
}// 對字符串數組進行排序
void arrSort(char *arr[], int n)
{for (int i = 0; i < n - 1; i++)for (int j = 0; j < n - 1 - i; j++)if (strcmp(arr[j], arr[j + 1]) > 0){char *temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}
}// 動態分配數組
int *dy_alloc(int n)
{int *res = (int *)malloc(n * sizeof(int));for (int i = 0; i < n; i++)res[i] = i;return res;
}// 添加子節點
void add_child(struct Node *parent, struct Node *child)
{parent->children = realloc(parent->children, (parent->child_count + 1) * sizeof(struct Node *));parent->children[parent->child_count++] = child;
}// void 指針通用類型轉換
void *arrtoPtrInt(void *x)
{return (char *)(x) + 2;
}// 獲取字符串長度
int getStrLen(char *a)
{char *p = a;while (*p)p++;return p - a;
}// 字符串拷貝函數
char *strCpyFn(char *dest, char *src)
{char *result = dest;while (*src)*dest++ = *src++;*dest = '\0';return result;
}// 矩陣轉置
void zhuanzhi(int arr[3][3])
{for (int i = 0; i < 3; i++){for (int j = i + 1; j < 3; j++){int temp = arr[i][j];arr[i][j] = arr[j][i];arr[j][i] = temp;}}
}// 連接兩個字符串
void connect_char(char *a, char *b)
{char *p1 = a + strlen(a);while (*b){*p1 = *b;p1++;b++;}*p1 = '\0';
}// 加法函數
int add(int a, int b)
{return a + b;
}// 減法函數
int subtract(int a, int b)
{return a - b;
}// 測試字符串復制函數
void test_str_copy()
{char *copy = str_copy("test");printf("復制后的字符串: %s\n", copy);free(copy);
}
void value_pass(int *p)
{printf("在 value_pass 函數中:原值 = %d\n", *p);*p = *p + 10;printf("在 value_pass 函數中:新值 = %d\n", *p);
}// 主函數
int main()
{printf("=== C 語言知識點綜合測試 ===\n\n");// 野指針相關測試printf("野指針相關測試:\n");wild_pointer_cause();printf("\n");// 指針關系和算術運算測試printf("指針關系和算術運算測試:\n");pointer_relation();pointer_arithmetic_application();printf("\n");// 數組指針測試printf("數組指針測試:\n");array_pointer_question();array_ptr_application();printf("\n");// 字符指針測試printf("字符指針測試:\n");char_ptr_application();printf("\n");// const 指針測試printf("const 指針測試:\n");const_pointer();printf("\n");// 值傳遞測試printf("值傳遞測試:\n");value_pass_application();printf("\n");// 二維數組行指針傳遞測試printf("二維數組行指針傳遞測試:\n");int arr2d[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};pass_row_ptr(arr2d, 3);printf("\n");// 字符串復制測試printf("字符串復制測試:\n");test_str_copy(); // 確保函數已聲明和定義printf("\n");// qsort 函數測試printf("qsort 函數測試:\n");qsort_demo();func_ptr_array();testFuncPtr();printf("\n");// 斐波那契數列測試printf("斐波那契數列測試:\n");test_fibonacci();printf("\n");// typedef 使用測試printf("typedef 使用測試:\n");typedef_usage();printf("\n");// 大廠面試相關代碼測試printf("大廠面試相關代碼測試:\n");dachangmianshi3();printf("\n");// 字符串處理函數測試printf("字符串處理函數測試:\n");test_strncpy();test_str_cat();test_strncmp();test_strchar();test_strstr();printf("\n");// 棧操作測試printf("棧操作測試:\n");struct stack s;initStack(&s);printf("初始化后,棧是否為空: %d\n", isEmpty(&s));pushStack(&s, 1);printf("壓入 1 后,棧是否為空: %d\n", isEmpty(&s));pushStack(&s, 2);pushStack(&s, 3);printf("棧頂元素: %d\n", peekTop(&s));pushStack(&s, 4);printf("棧頂元素: %d\n", peekTop(&s));popStack(&s);printf("彈出元素后,棧頂元素: %d\n", peekTop(&s));printf("\n");// 鏈表操作測試printf("鏈表操作測試:\n");struct Node *head_node = NULL;head_node = insert_atFirst(head_node, 123);printf("第一個節點: %d\n", head_node->value); // 修改為正確的成員名 valuehead_node = insert_atFirst(head_node, 4);printf("第二個節點: %d\n", head_node->value); // 修改為正確的成員名 valueprint_List(head_node);printf("\n");// 快速排序測試printf("快速排序測試:\n");int arr11[] = {4, 5, 6, 7, 15, 234, 46, 698, 238, 258, 45, 2, 36, 26, 123, 77, 5, 48, 45, 2, 5, 325, 32, 1, 6};int len = sizeof(arr11) / sizeof(arr11[0]);quick_Sort(arr11, 0, len - 1);for (int i = 0; i < len; i++){printf("排序后輸出的結果: %d\n", arr11[i]);}printf("\n");printf("=== 測試結束 ===\n");return 0;
}
第三部分:本人結合AI所總結的相關c語言知識點總結
?
知識點總結表格:
考點分類 | 具體知識點 | 常見題型 | 解題關鍵點 | 易錯點 |
---|---|---|---|---|
基礎概念 | 指針與數組的本質區別 | 選擇題(如數組名衰退規則) | 數組名在sizeof 和& 操作外衰退為指針,數組存儲位置由定義方式決定 | 混淆數組與指針的本質,認為數組名始終是指針 |
指針大小與平臺相關性 | 選擇題(64 位系統指針大小) | 指針大小僅由操作系統位數決定(32 位 4 字節,64 位 8 字節) | 誤以為指針大小與指向類型有關 | |
野指針成因與危害 | 代碼找錯題 | 未初始化、釋放未置 NULL、越界訪問、指向臨時變量是野指針主因 | 忽視臨時變量地址失效問題 | |
數組與指針 | 二維數組與指針運算 | 表達式求值題(如arr[1][2] 的指針表示) | 二維數組按行存儲,arr[i][j] 等價于*(*(arr+i)+j) | 錯誤計算指針偏移量 |
指針數組與數組指針辨析 | 定義辨析題 | int (*p)[5] 是數組指針,int *p[5] 是指針數組 | 混淆括號優先級導致類型判斷錯誤 | |
字符串指針與數組陷阱 | 代碼運行結果題 | 字符串字面量存儲在只讀區,數組可修改,指針指向不可修改 | 修改字符串常量導致段錯誤 | |
內存管理 | 動態內存分配與 realloc 陷阱 | 代碼找錯題 | realloc 需檢查返回值,成功時原指針可能改變 | 直接釋放原指針導致內存泄漏 |
指針與結構體對齊 | sizeof 計算題 | 結構體對齊規則:成員按自身大小對齊,整體為最大對齊參數整數倍 | 忽視對齊導致結構體大小計算錯誤 | |
多級指針操作 | 指針解引用題 | 多級指針解引用次數等于級別數,注意指針指向的指針層級 | 解引用次數不足或過多導致訪問錯誤 | |
函數指針 | 函數指針數組實現計算器 | 編程題 | 定義函數指針類型,通過數組索引調用對應函數 | 函數指針類型定義錯誤 |
通用排序中的 void * 指針 | 算法實現題 | 使用void* 和memcpy 實現任意類型排序,需傳入比較函數 | 類型轉換時未考慮內存對齊問題 | |
算法應用 | 鏈表逆序(指針操作) | 數據結構題 | 三指針法:prev 、current 、next 配合反轉指針 | 指針更新順序錯誤導致鏈表斷裂 |
快速排序中的指針應用 | 算法題 | 用指針偏移實現任意類型分區,void* 配合memcpy 交換元素 | 分區時偏移量計算錯誤 | |
系統級操作 | 指針與數組傳參陷阱 | sizeof 計算題 | 數組作為參數衰退為指針,無法獲取原始大小 | 在函數內用sizeof(arr) 獲取數組大小 |
指針運算與類型轉換 | 表達式求值題 | 指針算術步長由指向類型決定,不同類型指針相減得到元素個數 | 忽視指針類型不同導致偏移量錯誤 | |
內存泄漏檢測與預防 | 代碼分析題 | 動態內存分配后需正確釋放,realloc 后更新指針 | 遺漏realloc 后的指針更新導致內存泄漏 | |
指針實現字符串拷貝 | 編程題 | 指針遍歷源字符串,逐個字符復制,處理NULL 輸入 | 未處理源字符串或目標字符串為NULL 的情況 | |
鏈表環檢測(Floyd 判圈算法) | 算法題 | 快慢指針法:慢指針每次走 1 步,快指針每次走 2 步,相遇則有環 | 邊界條件處理不當(空鏈表或單節點鏈表) | |
指針與內存池設計 | 系統編程題 | 預分配大內存,用指針跟蹤分配位置,避免碎片 | 內存池邊界檢查缺失導致越界 | |
指針與類型轉換(內存復用) | 底層編程題 | 通過強制類型轉換實現不同類型內存復用,如int 與float 互轉 | 違反類型安全原則導致未定義行為 | |
指針與位操作(內存映射) | 位運算題 | 用指針定位內存 |
邏輯關系圖:
指針知識體系
│
├── 基礎概念
│ ├── 指針本質(地址操作、類型系統)
│ ├── 指針與數組的關系
│ │ ├── 數組名衰退規則
│ │ ├── 二維數組內存布局
│ │ └── 指針數組 vs 數組指針
│ ├── 指針大小(平臺相關性)
│ └── 野指針
│ ├── 成因(未初始化、釋放未置NULL、越界、臨時變量)
│ └── 危害(段錯誤、數據破壞)
│
├── 內存管理
│ ├── 動態分配(malloc/realloc/free)
│ │ ├── realloc陷阱(返回值檢查、指針更新)
│ │ └── 內存泄漏檢測
│ ├── 結構體對齊
│ │ ├── 對齊規則(成員對齊、整體對齊)
│ │ └── sizeof計算
│ └── 多級指針操作(解引用層級)
│
├── 函數指針
│ ├── 函數指針定義與調用
│ ├── 函數指針數組(回調機制)
│ └── 通用編程(void*指針+比較函數)
│
├── 算法與數據結構
│ ├── 鏈表操作
│ │ ├── 鏈表逆序(三指針法)
│ │ └── 環檢測(Floyd算法)
│ ├── 排序算法
│ │ ├── 快速排序(指針分區)
│ │ └── 通用排序實現
│ └── 字符串處理(指針實現strcpy等)
│
└── 系統級編程├── 指針傳參與數組衰退├── 指針運算(類型轉換、偏移量)├── 內存池設計(預分配與指針跟蹤)└── 底層操作(位運算、內存復用)
最后:? ?
? ? ? ? ? ? ? ? ? ??覺得我寫得不錯的,還請各位給一個點贊收藏關注!
?