目录
[1.1 基本概念](#1.1 基本概念)
[1.2 数组的本质](#1.2 数组的本质)
[1.3 数组特点](#1.3 数组特点)
[1.4 地址计算公式](#1.4 地址计算公式)
[2.1 初始化方式](#2.1 初始化方式)
[2.2 默认值规则](#2.2 默认值规则)
[2.3 数组越界访问(⚠️ 重点)](#2.3 数组越界访问(⚠️ 重点))
[2.4 栈空间限制](#2.4 栈空间限制)
[3.1 索引规则](#3.1 索引规则)
[3.2 访问方式](#3.2 访问方式)
[4.1 基本遍历](#4.1 基本遍历)
[4.2 数组长度计算](#4.2 数组长度计算)
[5.1 内存地址基础](#5.1 内存地址基础)
[5.2 操作系统与地址空间](#5.2 操作系统与地址空间)
[5.3 地址打印示例](#5.3 地址打印示例)
[5.4 索引的本质](#5.4 索引的本质)
[6.1 数组作为函数参数](#6.1 数组作为函数参数)
[6.2 数组退化原理](#6.2 数组退化原理)
[6.3 索引越界防护](#6.3 索引越界防护)
[7.1 求最值](#7.1 求最值)
[7.2 数组求和与平均](#7.2 数组求和与平均)
[7.3 生成不重复随机数](#7.3 生成不重复随机数)
[7.4 反转数组](#7.4 反转数组)
[7.5 打乱数组(洗牌算法)](#7.5 打乱数组(洗牌算法))
[7.6 查找算法](#7.6 查找算法)
[7.7 排序算法](#7.7 排序算法)
[8.1 基本概念](#8.1 基本概念)
[8.2 定义与初始化](#8.2 定义与初始化)
[8.3 内存布局](#8.3 内存布局)
[8.4 遍历二维数组](#8.4 遍历二维数组)
[8.5 二维数组作为函数参数](#8.5 二维数组作为函数参数)
[8.6 不规则二维数组(指针数组)](#8.6 不规则二维数组(指针数组))
[9.1 栈数组 vs 堆数组](#9.1 栈数组 vs 堆数组)
[9.2 动态数组基本用法](#9.2 动态数组基本用法)
[9.3 动态数组函数返回](#9.3 动态数组函数返回)
[9.4 动态数组扩容](#9.4 动态数组扩容)
[9.5 动态二维数组](#9.5 动态二维数组)
[11.1 学生成绩管理系统](#11.1 学生成绩管理系统)
数组 :程序中用于存储相同数据类型 的多个值的连续内存空间
核心价值:批量处理数据、提高代码效率、便于数据管理
1、数组的定义
1.1 基本概念
cpp
// 语法格式
数据类型 数组名[长度];
// 示例
int arr[5]; // 存储5个整数
float scores[10]; // 存储10个浮点数
char name[20]; // 存储20个字符
1.2 数组的本质
cpp
[]运算符的本质:先计算地址,再访问元素
arr[i] 的原理:
1. 获取数组首地址 &arr[0]
2. 计算偏移量:i * sizeof(元素类型)
3. 目标地址 = 首地址 + 偏移量
4. 访问该地址处的数据
公式:&arr[i] = &arr[0] + i * sizeof(元素类型)
⚠️ 重要:数组长度在编译时确定,运行时是"无用信息",编译器只记录首地址和元素类型
1.3 数组特点
| 特点 | 说明 |
|---|---|
| 连续内存 | 数组元素在内存中连续存储 |
| 类型相同 | 所有元素必须是同一数据类型 |
| 有序性 | 元素按索引顺序排列 |
| 首地址 | 数组名 = &arr[0](数组首地址) |
| 长度固定 | 定义后长度不可改变 |
| 随机访问 | 可通过索引直接访问任意元素 |
1.4 地址计算公式
cpp
// 数组第n个元素的地址
&arr[n] = 数组首地址 + n * sizeof(元素类型)
// 示例:int arr[5],首地址为0x1000
&arr[0] = 0x1000 + 0 * 4 = 0x1000
&arr[1] = 0x1000 + 1 * 4 = 0x1004
&arr[2] = 0x1000 + 2 * 4 = 0x1008
&arr[3] = 0x1000 + 3 * 4 = 0x100C
&arr[4] = 0x1000 + 4 * 4 = 0x1010
2、数组的初始化
2.1 初始化方式
cpp
// 方式1:完全初始化
int arr1[5] = {1, 2, 3, 4, 5};
// 方式2:部分初始化(剩余元素自动补0)
int arr2[5] = {1, 2, 3}; // arr2[3]=0, arr2[4]=0
// 方式3:省略长度(由初始化元素个数决定)
int arr3[] = {1, 2, 3, 4, 5}; // 长度为5
// 方式4:全部置0
int arr4[5] = {0}; // 所有元素为0
int arr5[5] = {}; // 所有元素为0(C99标准)
// 方式5:指定索引初始化(C99)
int arr6[5] = {[2] = 10, [4] = 20}; // arr6[2]=10, arr6[4]=20, 其他为0
2.2 默认值规则
| 数据类型 | 默认值 |
|---|---|
| 整数类型 | 0 |
| 浮点类型 | 0.0 |
| 字符类型 | '\0' |
| 指针类型 | NULL |
2.3 数组越界访问(⚠️ 重点)
cpp
//错误示例:数组越界
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int a = 20; // 在栈上,可能紧邻arr分配
// 越界访问:arr的有效索引是0-4
for (int i = 0; i < 13; i++) { //循环13次,越界!
arr[i] = i + 1;
}
printf("a = %d\n", a); // 输出可能不是20!
return 0;
}
//正确示例:使用sizeof计算长度
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int a = 20;
// 计算数组元素个数
int arr_size = sizeof(arr) / sizeof(arr[0]); // 5
for (int i = 0; i < arr_size; i++) { //循环5次
arr[i] = i + 1;
}
printf("a = %d\n", a); // 输出20
return 0;
}
2.4 栈空间限制
cpp
// 栈空间有限(通常1-8MB),大数组会导致栈溢出
#include <stdio.h>
int main() {
// 可能导致栈溢出
// int bigArr[1000000]; // 约4MB
// 大数组应使用动态分配(堆空间)
int *bigArr = (int*)malloc(1000000 * sizeof(int));
if (bigArr != NULL) {
// 使用数组...
free(bigArr);
}
return 0;
}
💡 建议:超过1MB的数组建议使用动态分配(malloc)
3、元素访问
3.1 索引规则
| 规则 | 说明 |
|---|---|
| 从0开始 | 第一个元素索引为0 |
| 连续递增 | 索引依次+1,不间断 |
| 有效范围 | 0 到 长度-1 |
3.2 访问方式
cpp
int arr[5] = {10, 20, 30, 40, 50};
// 获取元素
int value = arr[2]; // value = 30
// 修改元素
arr[2] = 300; // arr[2]变为300
// 使用指针访问(等价)
int value2 = *(arr + 2); // value2 = 300
arr[2] = 400; // 等价于 *(arr + 2) = 400
4、数组遍历
4.1 基本遍历
cpp
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
// 正向遍历
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
// 反向遍历
for (int i = len - 1; i >= 0; i--) {
printf("%d ", arr[i]);
}
4.2 数组长度计算
cpp
// 公式:元素个数 = 总字节数 / 单个元素字节数
int len = sizeof(arr) / sizeof(arr[0]);
// 或
int len = sizeof(arr) / sizeof(int);
⚠️ 注意 :此方法仅适用于当前作用域内的数组,数组作为函数参数时会退化为指针,sizeof结果为指针大小(8字节)
5、内存中的变量与数组
5.1 内存地址基础
| 概念 | 说明 |
|---|---|
| 内存 | 程序运行时临时存储数据的空间 |
| 内存地址 | 内存格子的编号,一个格子=1字节 |
| 地址表示 | 十六进制(如0x7fff5fbff8ac) |
| 地址查询 | &变量名 |
5.2 操作系统与地址空间
| 系统类型 | 地址位数 | 最大支持内存 |
|---|---|---|
| 32位 | 32位二进制 | 4GB |
| 64位 | 64位二进制 | 16EB(理论) |
5.3 地址打印示例
cpp
#include <stdio.h>
int main() {
int a = 10;
int arr[3] = {1, 2, 3};
// 打印变量地址
printf("a的地址: %p\n", (void*)&a);
// 打印数组地址
printf("arr的地址: %p\n", (void*)arr);
printf("arr[0]的地址: %p\n", (void*)&arr[0]);
printf("arr[1]的地址: %p\n", (void*)&arr[1]);
printf("arr[2]的地址: %p\n", (void*)&arr[2]);
// 验证地址连续性
printf("地址间隔: %ld 字节\n", (char*)&arr[1] - (char*)&arr[0]);
return 0;
}
5.4 索引的本质
cpp
索引 = 偏移量(从首地址开始偏移的单位数)
arr[0] → 首地址 + 0 * sizeof(元素类型)
arr[1] → 首地址 + 1 * sizeof(元素类型)
arr[i] → 首地址 + i * sizeof(元素类型)
这就是为什么索引从0开始!
6、一维数组常见问题
6.1 数组作为函数参数
cpp
#include <stdio.h>
// 错误:无法获取数组长度
void printArrWrong(int arr[]) {
// sizeof(arr) = 8(指针大小),不是数组大小!
int len = sizeof(arr) / sizeof(arr[0]); // 错误!
printf("长度: %d\n", len);
}
// 正确:额外传递长度参数
void printArr(int arr[], int len) {
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
printArr(arr, len); // 正确调用
return 0;
}
6.2 数组退化原理
cpp
// 函数调用时的实际情况
func(arr); // 等价于 func(&arr[0]);
// 本质:
// 1. 运行时:传递的是数组首地址
// 2. 编译时:数组类型"退化"为指针类型
// 3. 结果:长度信息丢失,需额外传递
// 三种等价的函数声明
void func1(int arr[]);
void func2(int *arr);
void func3(int arr[5]); // 5被忽略
6.3 索引越界防护
| 检查项 | 正确值 |
|---|---|
| 最小索引 | 0 |
| 最大索引 | 长度 - 1 |
| 有效范围 | [0, 长度) |
cpp
// 安全访问示例
int arr[5] = {1, 2, 3, 4, 5};
int index = 3;
if (index >= 0 && index < 5) {
printf("%d\n", arr[index]);
} else {
printf("索引越界!\n");
}
7、数组算法题
7.1 求最值
cpp
#include <stdio.h>
int main() {
int arr[] = {1, 4, 6, 8, 12, 164, 76, 34};
int len = sizeof(arr) / sizeof(arr[0]);
// 求最大值
int max = arr[0];
int maxIndex = 0;
for (int i = 1; i < len; i++) {
if (arr[i] > max) {
max = arr[i];
maxIndex = i;
}
}
printf("最大值: %d (索引: %d)\n", max, maxIndex);
// 求最小值
int min = arr[0];
int minIndex = 0;
for (int i = 1; i < len; i++) {
if (arr[i] < min) {
min = arr[i];
minIndex = i;
}
}
printf("最小值: %d (索引: %d)\n", min, minIndex);
return 0;
}
7.2 数组求和与平均
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int arr[10] = {0};
int len = sizeof(arr) / sizeof(arr[0]);
// 设置随机数种子
srand(time(NULL));
// 生成10个1-100的随机数
printf("生成的随机数: ");
for (int i = 0; i < len; i++) {
arr[i] = rand() % 100 + 1;
printf("%d ", arr[i]);
}
printf("\n\n");
// 求和
int sum = 0;
for (int i = 0; i < len; i++) {
sum += arr[i];
}
printf("总和: %d\n", sum);
// 求平均数
double avg = (double)sum / len;
printf("平均数: %.2f\n\n", avg);
// 统计比平均数小的元素个数
int count = 0;
for (int i = 0; i < len; i++) {
if (arr[i] < avg) {
count++;
}
}
printf("比平均数小的元素个数: %d\n", count);
return 0;
}
7.3 生成不重复随机数
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdbool.h>
// 检查数字是否在数组中已存在
bool contains(int arr[], int len, int num) {
for (int i = 0; i < len; i++) {
if (arr[i] == num) {
return true;
}
}
return false;
}
int main() {
int arr[10] = {0};
int len = sizeof(arr) / sizeof(arr[0]);
// 设置随机数种子
srand(time(NULL));
// 生成10个不重复的1-100随机数
printf("生成的不重复随机数: ");
for (int i = 0; i < len; ) { // 注意:i只在成功添加时递增
int num = rand() % 100 + 1;
if (!contains(arr, i, num)) { // 检查前i个元素
arr[i] = num;
i++;
}
}
// 输出结果
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
7.4 反转数组
cpp
#include <stdio.h>
// 打印数组
void printArr(int arr[], int len) {
printf("[");
for (int i = 0; i < len; i++) {
printf("%d", arr[i]);
if (i < len - 1) printf(", ");
}
printf("]\n");
}
int main() {
int arr[5];
int len = sizeof(arr) / sizeof(arr[0]);
// 键盘录入
printf("请输入5个整数:\n");
for (int i = 0; i < len; i++) {
printf("第%d个: ", i + 1);
scanf("%d", &arr[i]);
}
printf("反转前: ");
printArr(arr, len);
// 反转数组(双指针法)
int left = 0;
int right = len - 1;
while (left < right) {
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
left++;
right--;
}
printf("反转后: ");
printArr(arr, len);
return 0;
}
7.5 打乱数组(洗牌算法)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// Fisher-Yates 洗牌算法(推荐)
void shuffle(int arr[], int len) {
srand(time(NULL));
for (int i = len - 1; i > 0; i--) {
// 生成0到i之间的随机索引
int j = rand() % (i + 1);
// 交换arr[i]和arr[j]
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
printf("打乱前: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
shuffle(arr, len);
printf("打乱后: ");
for (int i = 0; i < len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
7.6 查找算法
(1)顺序查找
cpp
#include <stdio.h>
// 顺序查找:返回目标值的索引,未找到返回-1
int sequentialSearch(int arr[], int len, int target) {
for (int i = 0; i < len; i++) {
if (arr[i] == target) {
return i;
}
}
return -1;
}
int main() {
int arr[] = {11, 22, 55, 77, 44};
int len = sizeof(arr) / sizeof(arr[0]);
int target = 55;
int index = sequentialSearch(arr, len, target);
if (index != -1) {
printf("找到 %d,索引: %d\n", target, index);
} else {
printf("未找到 %d\n", target);
}
return 0;
}
(2)二分查找(折半查找)
cpp
#include <stdio.h>
// 二分查找:数组必须有序!
int binarySearch(int arr[], int len, int target) {
int left = 0;
int right = len - 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; // 未找到
}
int main() {
// 注意:数组必须有序!
int arr[] = {11, 22, 44, 55, 77}; // 已排序
int len = sizeof(arr) / sizeof(arr[0]);
int target = 55;
int index = binarySearch(arr, len, target);
if (index != -1) {
printf("找到 %d,索引: %d\n", target, index);
} else {
printf("未找到 %d\n", target);
}
return 0;
}
(3)查找算法对比
| 算法 | 时间复杂度 | 空间复杂度 | 前提条件 |
|---|---|---|---|
| 顺序查找 | O(n) | O(1) | 无 |
| 二分查找 | O(log n) | O(1) | 数组有序 |
| 插值查找 | O(log log n) | O(1) | 有序且均匀分布 |
| 分块查找 | O(√n) | O(1) | 块间有序 |
7.7 排序算法
(1)冒泡排序
cpp
#include <stdio.h>
void bubbleSort(int arr[], int len) {
for (int i = 0; i < len - 1; i++) {
// 优化:如果某趟没有交换,说明已有序
int swapped = 0;
for (int j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
swapped = 1;
}
}
// 如果没有交换,提前结束
if (!swapped) break;
}
}
int main() {
int arr[] = {3, 5, 2, 1, 4};
int len = sizeof(arr) / sizeof(arr[0]);
printf("排序前: ");
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\n");
bubbleSort(arr, len);
printf("排序后: ");
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
(2)选择排序
cpp
#include <stdio.h>
void selectionSort(int arr[], int len) {
for (int i = 0; i < len - 1; i++) {
// 假设当前位置是最小值
int minIndex = i;
// 在后面找到真正的最小值
for (int j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换
if (minIndex != i) {
int temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
}
}
int main() {
int arr[] = {3, 5, 2, 1, 4};
int len = sizeof(arr) / sizeof(arr[0]);
selectionSort(arr, len);
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\n");
return 0;
}
(3)排序算法对比
| 算法 | 平均时间 | 最好时间 | 最坏时间 | 空间 | 稳定性 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
| 快速排序 | O(n log n) | O(n log n) | O(n²) | O(log n) | 不稳定 |
| 归并排序 | O(n log n) | O(n log n) | O(n log n) | O(n) | 稳定 |
8、二维数组
8.1 基本概念
| 维度 | 数学概念 | C语言表示 |
|---|---|---|
| 一维 | 向量 | int arr[5] |
| 二维 | 矩阵 | int arr[3][5] |
| 三维 | 立方体 | int arr[2][3][5] |
8.2 定义与初始化
cpp
// 方式1:完全初始化
int arr1[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 方式2:省略第一维(行数)
int arr2[][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8}
}; // 自动推断为2行
// 方式3:部分初始化
int arr3[3][4] = {
{1, 2}, // 第1行
{5, 6, 7}, // 第2行
{9} // 第3行
}; // 未初始化的元素为0
// 方式4:一维数组指针数组(不规则二维数组)
int row1[] = {1, 2, 3};
int row2[] = {4, 5, 6, 7, 8};
int row3[] = {9, 10};
int *arr4[] = {row1, row2, row3}; // 每行长度可不同
8.3 内存布局
cpp
二维数组在内存中是连续存储的(行优先)
int arr[3][4] 的内存布局:
┌─────┬─────┬─────┬─────┐
│ 0,0 │ 0,1 │ 0,2 │ 0,3 │ ← 第0行
├─────┼─────┼─────┼─────┤
│ 1,0 │ 1,1 │ 1,2 │ 1,3 │ ← 第1行
├─────┼─────┼─────┼─────┤
│ 2,0 │ 2,1 │ 2,2 │ 2,3 │ ← 第2行
└─────┴─────┴─────┴─────┘
地址计算:&arr[i][j] = 首地址 + (i * 列数 + j) * sizeof(元素)
8.4 遍历二维数组
cpp
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int rows = 3;
int cols = 4;
// 方式1:普通遍历
printf("普通遍历:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d", arr[i][j]);
}
printf("\n");
}
// 方式2:指针遍历
printf("\n指针遍历:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d", *(*(arr + i) + j));
}
printf("\n");
}
return 0;
}
8.5 二维数组作为函数参数
cpp
#include <stdio.h>
// 正确:必须指定列数
void printMatrix1(int arr[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%4d", arr[i][j]);
}
printf("\n");
}
}
// 正确:使用指针
void printMatrix2(int (*arr)[4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%4d", arr[i][j]);
}
printf("\n");
}
}
// 正确:扁平化处理
void printMatrix3(int *arr, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d", *(arr + i * cols + j));
}
printf("\n");
}
}
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
printMatrix1(arr, 3);
printMatrix2(arr, 3);
printMatrix3(&arr[0][0], 3, 4);
return 0;
}
⚠️ 重要 :二维数组作为参数时,列数必须指定,行数可以省略
8.6 不规则二维数组(指针数组)
cpp
#include <stdio.h>
int main() {
// 每行长度不同的二维数组
int row1[] = {1, 2, 3};
int row2[] = {4, 5, 6, 7, 8};
int row3[] = {9, 10};
// 记录每行的长度
int lens[] = {3, 5, 2};
// 指针数组
int *arr[] = {row1, row2, row3};
int rows = 3;
// 遍历
for (int i = 0; i < rows; i++) {
for (int j = 0; j < lens[i]; j++) {
printf("%4d", arr[i][j]);
}
printf("\n");
}
return 0;
}
9、动态数组
9.1 栈数组 vs 堆数组
| 特性 | 栈数组 | 堆数组(动态) |
|---|---|---|
| 分配位置 | 栈区 | 堆区 |
| 长度确定 | 编译时 | 运行时 |
| 生命周期 | 函数结束自动释放 | 手动free释放 |
| 大小限制 | 较小(1-8MB) | 较大(受内存限制) |
| 分配函数 | 无需 | malloc/calloc |
| 释放函数 | 自动 | free |
9.2 动态数组基本用法
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 1. 分配内存
int n = 10;
int *arr = (int*)malloc(n * sizeof(int));
// 检查分配是否成功
if (arr == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 2. 使用数组
for (int i = 0; i < n; i++) {
arr[i] = i * 10;
}
// 3. 打印
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 4. 释放内存(必须!)
free(arr);
arr = NULL; // 避免悬空指针
return 0;
}
9.3 动态数组函数返回
cpp
#include <stdio.h>
#include <stdlib.h>
// 函数返回动态数组
int* createArray(int size) {
int *arr = (int*)malloc(size * sizeof(int));
if (arr == NULL) {
return NULL;
}
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr; // 返回堆内存地址
}
int main() {
int *p = createArray(5);
if (p != NULL) {
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
printf("\n");
free(p); // 调用者负责释放
p = NULL;
}
return 0;
}
9.4 动态数组扩容
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 初始容量
int capacity = 5;
int size = 0;
int *arr = (int*)malloc(capacity * sizeof(int));
// 添加元素
for (int i = 0; i < 10; i++) {
// 容量不足时扩容
if (size >= capacity) {
capacity *= 2; // 容量翻倍
int *temp = (int*)realloc(arr, capacity * sizeof(int));
if (temp == NULL) {
free(arr);
printf("扩容失败!\n");
return 1;
}
arr = temp;
}
arr[size++] = i * 10;
}
// 打印
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
9.5 动态二维数组
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// 分配行指针数组
int **arr = (int**)malloc(rows * sizeof(int*));
// 分配每行的数据
for (int i = 0; i < rows; i++) {
arr[i] = (int*)malloc(cols * sizeof(int));
}
// 使用
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr[i][j] = i * cols + j;
printf("%4d", arr[i][j]);
}
printf("\n");
}
// 释放(先释放每行,再释放行指针)
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
return 0;
}
10、常见错误与避坑指南
| 错误类型 | 错误示例 | 正确写法 |
|---|---|---|
| 数组越界 | arr[5](长度5) |
arr[0]到arr[4] |
| 未初始化 | int arr[5];直接使用 |
int arr[5] = {0}; |
| 参数丢失长度 | void func(int arr[]) |
void func(int arr[], int len) |
| 栈溢出 | int big[1000000]; |
使用malloc动态分配 |
| 忘记free | malloc后不释放 |
配对使用free |
| 二维数组参数 | void func(int arr[][]) |
void func(int arr[][4]) |
| 二分查找无序 | 对无序数组二分 | 先排序再二分 |
| 悬空指针 | free(p)后继续使用 |
free(p); p = NULL; |
11、综合练习
11.1 学生成绩管理系统
cpp
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define MAX_STUDENTS 100
typedef struct {
int id;
char name[50];
float score;
} Student;
// 生成随机成绩
void generateScores(Student students[], int n) {
srand(time(NULL));
for (int i = 0; i < n; i++) {
students[i].id = i + 1;
sprintf(students[i].name, "学生%d", i + 1);
students[i].score = rand() % 101; // 0-100
}
}
// 计算平均分
float calculateAverage(Student students[], int n) {
float sum = 0;
for (int i = 0; i < n; i++) {
sum += students[i].score;
}
return sum / n;
}
// 查找最高分
int findMaxScoreIndex(Student students[], int n) {
int maxIndex = 0;
for (int i = 1; i < n; i++) {
if (students[i].score > students[maxIndex].score) {
maxIndex = i;
}
}
return maxIndex;
}
// 排序(按成绩降序)
void sortByScore(Student students[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - 1 - i; j++) {
if (students[j].score < students[j + 1].score) {
Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}
}
// 打印学生信息
void printStudents(Student students[], int n) {
printf("%-10s %-20s %-10s\n", "学号", "姓名", "成绩");
printf("----------------------------------------\n");
for (int i = 0; i < n; i++) {
printf("%-10d %-20s %-10.1f\n",
students[i].id, students[i].name, students[i].score);
}
}
int main() {
Student students[MAX_STUDENTS];
int n = 10; // 学生数量
// 生成成绩
generateScores(students, n);
printf("=== 原始成绩 ===\n");
printStudents(students, n);
printf("\n平均分: %.2f\n", calculateAverage(students, n));
int maxIndex = findMaxScoreIndex(students, n);
printf("最高分: %s (%.1f分)\n",
students[maxIndex].name, students[maxIndex].score);
printf("\n=== 排序后 ===\n");
sortByScore(students, n);
printStudents(students, n);
return 0;
}