
03_C语言-数组篇
本文最后更新于 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 | 字符串拷贝 | strncpy | strcpy(dest, src) |
strcat | 字符串拼接 | strncat | strcat(str, "!") |
strcmp | 字符串比较 | strncmp | strcmp("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 | 采用倍增法 |
📚 综合案例:学生管理系统
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 小道士