#C基础
字符数组
字符数组与字符串的区别
字符数组是元素为字符型(char)的数组。字符串则是字符的序列,以空字符 \0(ASCII 码为 0)作为结束标志。字符数组不一定是字符串,只有以 \0 结尾的字符数组才能被视为字符串。
示例:
c
char arr1[] = {'h', 'e', 'l', 'l', 'o'}; // 字符数组,不是字符串
char arr2[] = {'h', 'e', 'l', 'l', 'o', '\0'}; // 字符串
char arr3[] = "hello"; // 字符串,自动添加 \0
字符数组的初始化方式
常规初始化
c
char a[6] = {'h', 'e', 'l', 'l', 'o', '\0'};
字符串初始化(推荐)
c
char b[6] = "hello"; // 等价于上述常规初始化
char c[] = "hello"; // 数组长度自动推断为 6(包含 \0)
部分初始化
c
char d[10] = "hello"; // 前6个字符为 'h','e','l','l','o','\0',后4个为 '\0'
字符数组的内存存储与可修改性
- 使用字符数组存储字符串时,字符串存储在栈内存中,内容可读写。
- 使用字符指针指向字符串常量时,字符串存储在常量区,内容只读。
c
char arr[] = "hello";
arr[0] = 'H'; // 允许修改
char *ptr = "hello";
// ptr[0] = 'H'; // 错误:试图修改常量区数据
栈区与常量区的区别:
| 特性 | 字符数组(栈区) | 字符指针(常量区) |
|---|---|---|
| 存储位置 | 栈内存 | 常量区 |
| 可修改性 | 可读写 | 只读 |
| 生命周期 | 函数作用域内 | 程序运行期间 |
| 内存分配 | 编译时确定 | 编译时确定 |
字符数组与字符串的转换
有时我们需要将数字转换为字符串,或者将字符串转换为数字。可以使用 sprintf 和 sscanf 函数。
使用 sprintf 将格式化的数据写入字符串
c
char str[100];
int num = 123;
float f = 3.14;
sprintf(str, "整数:%d,浮点数:%.2f", num, f); // str 现在为 "整数:123,浮点数:3.14"
使用 sscanf 从字符串中读取格式化的数据
c
char str[] = "123 3.14";
int num;
float f;
sscanf(str, "%d %f", &num, &f); // num=123, f=3.14
字符数组的常见应用
- 用户输入处理(
gets、fgets、scanf) - 文本文件的读写
- 字符串解析与分割(
strtok) - 简单加密与编码转换
二维数组
二维数组的定义与理解
二维数组可视为"数组的数组"。例如:
c
int a[3][4]; // 3行4列的二维数组
理解方式:
a是一个数组,包含 3 个元素:a[0]、a[1]、a[2]- 每个元素又是一个长度为 4 的
int数组

二维数组的地址与指针类型
以下代码说明了二维数组中不同表达式的类型与地址计算方式:
c
#include <stdio.h>
int main(void) {
int a[3][4];
printf("&a = %p\n", &a); // int (*)[3][4],指向整个二维数组
printf("&a+1 = %p\n", &a + 1); // 跳过一个 3×4 的 int 数组(48字节)
printf("a = %p\n", a); // int (*)[4],指向第一行
printf("a+1 = %p\n", a + 1); // 跳过一个 int[4](16字节)
printf("a[0] = %p\n", a[0]); // int *,指向第一个元素
printf("a[0]+1 = %p\n", a[0] + 1); // 跳过一个 int(4字节)
printf("&a[0][0] = %p\n", &a[0][0]); // int *
printf("&a[0][0]+1 = %p\n", &a[0][0] + 1); // 跳过一个 int
return 0;
}
二维数组的初始化
标准初始化(按行)
c
int a[3][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}
};
省略第一维长度
c
int b[][4] = {
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11}
};
顺序初始化(按内存顺序)
c
int c[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
指定元素初始化(C99 支持)
c
int d[3][4] = {
[1] = {4, 5, 6, 7},
[2][2] = 10
};
二维数组的访问方式
c
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("a[%d][%d] = %d\n", i, j, a[i][j]);
// 等价于:
// *(*(a + i) + j)
// *(a[i] + j)
}
}
二维数组与指针的关系
二维数组名可以看作是指向数组的指针,即指向第一行(一个一维数组)的指针。
例如,对于 int a[3][4];:
a的类型是int (*)[4],即指向含有4个整数的数组的指针。a[0]的类型是int *,指向第一个整型元素。
我们可以使用指针来遍历二维数组:
c
int a[3][4] = {0};
int *p = &a[0][0];
for (int i = 0; i < 3*4; i++) {
*(p+i) = i; // 通过指针访问
}
// 或者使用数组指针
int (*ptr)[4] = a; // ptr指向第一行
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", ptr[i][j]); // 通过数组指针访问
}
}
动态创建二维数组
动态创建二维数组通常有两种方法:
使用指针数组
先创建一个指针数组,每个指针指向一个一维数组。
c
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;
}
}
// 释放
for (int i = 0; i < rows; i++) {
free(arr[i]);
}
free(arr);
使用一维数组模拟二维数组
分配一个一维数组,然后通过索引计算来访问。
c
int rows = 3, cols = 4;
int *arr = (int *)malloc(rows * cols * sizeof(int));
// 使用
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr[i * cols + j] = i * cols + j;
}
}
// 释放
free(arr);
多维数组
多维数组的定义与内存布局
三维数组:
c
int arr3D[2][3][4]; // 2页,每页3行4列
内存中是按行优先顺序连续存储的,即:
arr[0][0][0], arr[0][0][1], ..., arr[0][2][3], arr[1][0][0], ...
四维数组:
c
int arr4D[2][3][4][5]; // 2块,每块3页,每页4行5列
多维数组作为函数参数
传递多维数组时,除第一维外,其余维度必须指定大小:
c
void func(int arr[][4], int rows); // 正确
// void func(int arr[][], int rows); // 错误
// void func(int **arr, int rows, int cols); // 需使用指针传递动态数组
多维数组的应用场景
- 二维数组:矩阵运算、图像像素处理、游戏地图
- 三维数组:立体数据结构、三维图像处理、RGB 色彩空间
- 四维及以上:时间序列数据、多通道图像、科学计算中的多维场
常见问题与编程建议
字符数组常见错误
- 忘记
\0结尾 - 数组越界
- 试图修改字符串常量
- 使用
strcpy前未确保目标数组足够大
多维数组的内存优化
- 对于大型多维数组,考虑使用一维数组模拟,以提高访问效率。
- 使用指针访问多维数组时,注意指针类型与步长。