本文最后更新于 2025-03-11,学习久了要注意休息哟

第一章 一维数组

1.1 数组基础

📌 定义与初始化

// 方式1:声明时初始化所有元素  
int arr1[5] = {0, 1, 2, 3, 4};

// 方式2:部分初始化(剩余元素自动补0)  
int arr2[5] = {10, 20};  // → [10, 20, 0, 0, 0]  

// 方式3:自动推断长度
int arr3[] = {2,4,6};     // 长度=3

🖥️ 内存布局

地址     | 值  
0x1000 | arr[0] → 0
0x1004 | arr[1] → 1
0x1008 | arr[2] → 2
0x100C | arr[3] → 3
0x1010 | arr[4] → 4

特性

  • 元素连续存储,地址递增
  • 数组名 arr 是首元素地址的常量指针

📏 计算数组长度

int len = sizeof(arr) / sizeof(arr[0]);  

注意

  • 仅在定义数组的作用域内有效
  • 数组作为函数参数传递时会退化为指针

1.2 基本操作

🔄 遍历数组

for(int i=0; i<5; i++) {  
    printf("arr[%d] = %d\n", i, arr[i]);  
}

✏️ 修改元素

arr[2] = 100;  // 直接通过索引修改  

注意:索引从 0 开始,最大有效索引为 长度-1

🔍 查找最大值

int max = arr[0];  
for(int i=1; i<5; i++){  
    if(arr[i] > max) {  
        max = arr[i];  
    }  
}  

1.3 常见问题

💥 越界访问风险

int arr[5] = {0};  
arr[5] = 10;  // ❌ 访问越界 → 段错误或数据污染  

🧩 数组名本质

printf("%p == %p", arr, &arr[0]);  // 输出相同地址  
// arr = &other; ❌ 数组名是常量指针,不可修改

🔄 数组赋值限制

int a[5], b[5] = {1,2,3,4,5};  
a = b;              // ❌ 错误!  
memcpy(a, b, sizeof(b));  // ✅ 正确做法  

实战案例

案例1:学生成绩统计系统

案例2:斐波那契数列生成器

#include <stdio.h>  

int main() {  
    int fib[20] = {0, 1};  // 初始化前两项  

    // 生成数列  
    for(int i=2; i<20; i++) {  
        fib[i] = fib[i-1] + fib[i-2];  
    }  

    // 打印输出  
    printf("斐波那契数列前20项:\n");  
    for(int i=0; i<20; i++){  
        printf("%d ", fib[i]);  
        if((i+1)%5 == 0) printf("\n");  // 每行5个  
    }  
    return 0;  
}  

输出效果

0 1 1 2 3  5 8 13 21 34  
55 89 144 233 377  
610 987 1597 2584 4181  

🚨 高频错误总结

错误类型错误示例解决方案
越界访问arr[5] = 10;检查循环条件i < 长度
未初始化访问int arr[5]; printf("%d", arr[0]);显式初始化数组
错误计算长度sizeof(arr) 在函数参数中使用传递长度参数

📚 核心要点记忆卡

1. 数组是连续内存块,索引从0开始  
2. sizeof(arr) 仅在定义域有效  
3. 数组名是常量指针(不可修改)  
4. 越界访问会导致未定义行为  
5. 数组间赋值需用memcpy或循环  

第二章 二维数组

2.1 定义与初始化

📌 定义方式

// 方式1:按行优先初始化  
int matrix1[3][4] = {  
    {1, 2, 3, 4},  
    {5, 6, 7, 8},  
    {9, 10, 11, 12}  
};

// 方式2:连续初始化(自动填充)  
int matrix2[][4] = {1,2,3,4,5,6}; // → 2行4列,剩余补0  

// 方式3:动态初始化  
int* matrix3[3];  // 指针数组  
for(int i=0; i<3; i++){  
    matrix3[i] = (int*)malloc(4 * sizeof(int));  
}  

2.2 内存存储原理

🔍 行优先存储图示

地址      | 值  
0x1000 | matrix[0][0] → 1
0x1004 | matrix[0][1] → 2
0x1008 | matrix[0][2] → 3
0x100C | matrix[0][3] → 4
0x1010 | matrix[1][0] → 5
...  

📏 地址计算公式

int* base = &matrix[0][0];
int row = 1, col = 2;  
int* elem = base + (row * 4 + col);  // 4为列数  

2.3 二维数组传参

📌 函数参数传递方式

固定列数传递(推荐)

void printMatrix(int mat[][4], int rows) {  
    for(int i=0; i<rows; i++){  
        for(int j=0; j<4; j++){  
            printf("%d ", mat[i][j]);  
        }  
        printf("\n");  
    }  
}  
// 调用  
printMatrix(matrix1, 3);  

指针形式传递

void processMatrix(int* mat, int rows, int cols) {  
    for(int i=0; i<rows; i++){  
        for(int j=0; j<cols; j++){  
            printf("%d ", *(mat + i*cols + j));  
        }  
    }  
}  
// 调用  
processMatrix(&matrix1[0][0], 3, 4);  

🚨 常见错误

错误用法问题分析解决方案
void func(int** mat)类型不匹配(静态数组)使用固定列数或一维指针
不指定列数:int mat[][]编译器无法计算地址必须明确指定列数
动态数组传参未传递行列数无法确定维度显式传递行数和列数

🚨 高频错误总结

段错误

int mat[3][4];  
mat[3][0] = 5;  // ❌ 行越界  

错误传递动态数组

int** mat = malloc(3*sizeof(int*));  
// 错误调用  
printMatrix(mat, 3);  // ❌ mat类型不匹配  

忽略列数约束

void func(int mat[][]);  // ❌ 未指定列数  

📚 核心要点记忆卡

1. 二维数组内存按行连续存储
2. 函数传参必须指定列数(动态数组除外)
3. 地址计算:base + (i*col + j)*sizeof(type)
4. 动态二维数组需传递行列数
5. GDB使用 x/[数量][格式] 命令查看内存  

第三章 字符数组与字符串

3.1 字符数组特性

📌 字符串存储规则

char str1[] = {'H','e','l','l','o','\0'}; // 显式包含\0  
char str2[] = "World";                    // 隐式包含\0  

内存布局

地址   | 值
0x1000| 'H'
0x1001| 'e'
...
0x1005| '\0'

⚠️ 输入输出风险

函数风险点安全替代方案
scanf缓冲区溢出scanf("%10s", str)
gets无长度限制fgets(str, size, stdin)

3.2 字符串处理函数

📚 扩展函数表

函数功能描述安全替代方案示例
strlen计算字符串长度(不含\0)strnlen (C11)strlen("Hello") → 5
strcpy字符串拷贝strncpystrcpy(dest, src)
strcat字符串拼接strncatstrcat(str, "!")
strcmp字符串比较strncmpstrcmp("a", "b") → 负数
strchr查找字符首次出现位置strchr("abc", 'b') → "bc"
strstr查找子串首次出现位置strstr("hello", "ll") → "llo"
strtok分割字符串strtok_r (线程安全)strtok(str, ",")
strdup字符串动态复制(需free)char* copy = strdup(str)

💡 安全函数用法示例

// 安全拷贝(自动添加\0)  
strncpy(dest, src, sizeof(dest)-1);  
dest[sizeof(dest)-1] = '\0';  

// 安全输入  
fgets(str, sizeof(str), stdin);  
str[strcspn(str, "\n")] = '\0';  // 去除换行符  

3.3 常见漏洞

💥 缓冲区溢出攻击

char buffer[10];  
scanf("%s", buffer);  // 输入超10字符则溢出  

攻击原理:

  • 溢出数据覆盖相邻内存
  • 可能劫持函数返回地址

🛡️ 防御措施

边界检查:

strncpy(dest, src, dest_size-1);  

使用安全函数:

#define _CRT_SECURE_NO_WARNINGS  // Windows需定义  
gets_s(buffer, sizeof(buffer)); // C11安全函数  

实战案例

案例1:密码强度检测工具

int checkPassword(char* pwd) {  
    int strength = 0;  
    if(strlen(pwd) >= 8) strength++;  
    if(strchr(pwd, '!')) strength++;  
    if(strstr(pwd, "123")) {  
        strength--;  // 禁止简单数字序列  
    }  
    return strength;  
}  

// 使用示例  
char pwd[20];  
fgets(pwd, sizeof(pwd), stdin);  
int level = checkPassword(pwd);  

案例2:字符串逆序算法

void reverseString(char* str) {  
    if(!str) return;  
    int len = strlen(str);  
    for(int i=0; i<len/2; i++){  
        char tmp = str[i];  
        str[i] = str[len-1-i];  
        str[len-1-i] = tmp;  
    }  
}  

// 测试  
char str[] = "Hello";  
reverseString(str);  // → "olleH"  

🚨 高频错误总结

错误类型错误示例解决方案
忘记终止符char s[3] = {'A','B'};显式添加\0
未检查空指针strlen(NULL);增加NULL判断
错误使用strtok连续调用未保存指针使用静态变量或strtok_r

📚 核心要点记忆卡

1. 字符串必须以\0结尾
2. 优先使用带n的安全函数(strncpy等)
3. 永远不要使用gets()
4. strtok会修改原字符串
5. fgets会保留换行符,需手动处理

第四章 数组高级应用

4.1 查找与排序

🔍 查找算法

算法时间复杂度适用场景代码实现要点
线性查找O(n)无序数组遍历所有元素
二分查找O(log n)有序数组左右指针动态折半

线性查找示例

#include <stdio.h>

// 线性查找函数
int linearSearch(int arr[], int size, int target) {
    // 从第一个元素开始查找
    for (int i = 0; i < size; i++) {
        // 如果找到目标元素,返回当前索引
        if (arr[i] == target) {
            return i;  // 返回找到的元素索引
        }
    }
    return -1;  // 如果没有找到目标元素,返回 -1
}

int main() {
    int arr[] = {3, 5, 1, 9, 7, 6};  // 测试数组
    int size = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度
    int target = 9;  // 目标值
    
    int index = linearSearch(arr, size, target);  // 调用线性查找函数
    
    if (index != -1) {
        printf("元素 %d 在索引 %d 处找到。\n", target, index);
    } else {
        printf("未找到元素 %d。\n", target);
    }
    
    return 0;
}

二分查找示例

#include <stdio.h>

// 二分查找函数,假设数组是升序排列的
int binarySearch( int arr[], int size, int target ) {
    int left = 0;  // 定义左边界
    int right = size - 1;  // 定义右边界
    
    while (left <= right) {
        int mid = left + (right - left) / 2;  // 计算中间位置
        
        // 如果目标元素等于中间元素,返回索引
        if (arr[mid] == target) {
            return mid;
        }
        // 如果目标元素小于中间元素,缩小查找范围到左边
        else if (arr[mid] > target) {
            right = mid - 1;
        }
        // 如果目标元素大于中间元素,缩小查找范围到右边
        else {
            left = mid + 1;
        }
    }
    
    return -1;  // 如果没有找到目标元素,返回 -1
}

int main() {
    int arr[] = {1, 3, 5, 6, 7, 9};  // 有序数组
    int size = sizeof(arr) / sizeof(arr[0]);
    int target = 5;  // 目标值
    
    int index = binarySearch(arr, size, target);  // 调用二分查找函数
    
    if (index != -1) {
        printf("元素 %d 在索引 %d 处找到。\n", target, index);
    } else {
        printf("未找到元素 %d。\n", target);
    }
    
    return 0;
}

🔄 冒泡排序

#include <stdio.h>

// 冒泡排序函数
void bubbleSort(int arr[], int size) {
    // 外层循环控制排序的次数
    for (int i = 0; i < size - 1; i++) {
        // 内层循环进行相邻元素的比较和交换
        for (int j = 0; j < size - 1 - i; j++) {
            // 如果当前元素大于下一个元素,交换它们
            if (arr[j] > arr[j + 1]) {
                // 交换两个元素
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

// 打印数组的辅助函数
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[] = {5, 1, 9, 3, 7};  // 测试数组
    int size = sizeof(arr) / sizeof(arr[0]);  // 计算数组长度
    
    printf("排序前的数组:\n");
    printArray(arr, size);  // 打印排序前的数组
    
    bubbleSort(arr, size);  // 调用冒泡排序
    
    printf("排序后的数组:\n");
    printArray(arr, size);  // 打印排序后的数组
    
    return 0;
}

4.2 动态数组实现

🧱 核心数据结构

typedef struct {
    int* data;      // 存储数据的指针  
    int capacity;   // 总容量  
    int size;       // 当前元素数量  
} DynamicArray;

🔄 操作函数实现

初始化

void initArray(DynamicArray* da, int cap) {  
    da->data = malloc(cap * sizeof(int));
    da->capacity = cap;
    da->size = 0;
}  

扩容策略

void resizeArray(DynamicArray* da) {  
    int new_cap = da->capacity * 2;  
    int* new_data = realloc(da->data, new_cap * sizeof(int));  
    if(new_data) {  
        da->data = new_data;  
        da->capacity = new_cap;  
    }  
}  

添加元素

void pushBack(DynamicArray* da, int val) {  
    if(da->size == da->capacity) resizeArray(da);  
    da->data[da->size++] = val;  
}  

4.3 数组与结构体

📦 结构体数组

struct Student {  
    char name[20];  
    int id;  
    float gpa;  
};  

// 声明并初始化  
struct Student class[50] = {  
    {"Alice", 1001, 3.8},  
    {"Bob", 1002, 3.5}  
};  

// 访问元素  
printf("%s的GPA:%.1f", class[0].name, class[0].gpa);  

🔄 结构体包含数组

struct Matrix {  
    int rows;  
    int cols;  
    double data[10][10];  // 固定大小二维数组  
};  

// 动态版本  
struct DynamicMatrix {  
    int rows;  
    int cols;  
    double** data;       // 动态二维数组  
};  

🚨 高频错误总结

错误类型错误示例解决方案
内存泄漏忘记free动态数组Valgrind定期检查
越界访问结构体数组class[50].id = 1003;检查数组大小
扩容策略低效每次扩容+1采用倍增法

📚 综合案例:学生管理系统