7 学习字符数组和二维数组的定义和引用
1 字符数组(字符串处理)
1.1 基础概念
1. 定义: char s[]; 中 [] 是类型说明符,用于文本处理,可存储字符串内容。定义时若直接赋值字符串(如 char s[] = "Hello"; ),系统默认在末尾追加 '\0' 作为字符串结束标志;若用字符逐个初始化(如 char s[] = {'H','e','l','l','o','\0'}; ),需手动加 '\0' 。
2. 特性:字符串常量(如 "abcd123_20204045" )会被编译为 ASCII 码,存储在只读区域,运行中不可修改,具有单引号(字符)、双引号(字符串)、结尾 '\0' 标识的特性。
1.2 字符串操作函数
1. 输出函数
puts(const char *s) :向标准输出打印字符串,自动换行。参数 s 是字符数组名(本质是指针,指向字符型变量 ),如 puts(s); 等价于 puts(&s[0]); ,会从首地址开始输出,遇到 '\0' 停止。
while (*s != '\0')
{
putchar(*s);
s++;
}
putchar('\n');
2. 输入函数
1)gets(char *s) :从标准输入读入一行字符串,不检查越界,可能导致缓冲区溢出,C11 标准已弃用。
2)fgets(char *s, int size, FILE *stream) :更安全的输入函数, size 限制读入字符数(实际存 size - 1 个有效字符 + '\0' ), stream 一般填 stdin (标准输入 )。会根据数组大小决定存储内容,若输入超长,剩余字符留在输入缓冲区。
3)scanf("%s", s) :读入字符串时,遇到空白字符(空格、换行、制表符 )停止,不存储 '\n' ,且不检查越界,需确保数组空间足够。
3. 长度与比较
1)strlen(const char *s) :计算字符串长度,统计到 '\0' 为止(不包含 '\0' ),返回值类型是 unsigned long ,使用需包含头文件 <string.h> 。注意 sizeof(s) 计算的是数组总字节数(包含 '\0' 及未使用空间 ),与 strlen 结果不同。
2)strcmp(const char *s1, const char *s2) :比较两个字符串,按字典序逐字符对比 ASCII 码。返回值: s1 > s2 时为正数, s1 < s2 时为负数, s1 == s2 时为 0 。字符串比较不能直接用关系运算符(如 == > ),需用 strcmp ;给字符数组赋值也不能用赋值运算符(如 s2 = s1; ),需用 strcpy 。
示例(找较大字符串):
char max[100], s1[100], s2[100];
scanf("%s %s", s1, s2);
if (strcmp(s1, s2) > 0)
{
strcpy(max, s1);
}
else
{
strcpy(max, s2);
}
puts(max);
4. 拷贝与拼接
1)strcpy(char *dest, const char *src) :将 src 字符串拷贝到 dest 数组,会覆盖 dest 原有内容,需保证 dest 空间足够大(至少能存 src 内容 + '\0' ),使用需包含 <string.h> 。
简易实现逻辑:
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = '\0';
2)strcat(char *dest, const char *src) :将 src 字符串拼接到 dest 字符串末尾, dest 需有足够空间容纳拼接后内容(原 dest 长度 + src 长度 ),拼接前会自动找到 dest 的 '\0' 位置,从该位置开始覆盖。
示例:
char s1[100] = "Hello", s2[] = "World";
strcat(s1, s2);
puts(s1); // 输出 HelloWorld
1.3 字符数组注意点
1)'\0' 区分: '0' 是字符 0 的 ASCII 码(值为 48 ), '\0' 是字符串结束标志(值为 0 ); "0" 实际存储为 '0' + '\0' , "" 是空字符串,仅存 '\0' 。
2)数组与指针差异:字符数组 char s[3] = {"Beijing"}; 是直接给数组初始化内容(本质是逐个元素赋值 );若用指针 char *p = "Beijing"; , p 是指向字符串常量的指针,字符串常量存储在只读区, p 可改指向,但不能通过 p 修改字符串内容(否则程序崩溃 )。
3)赋值限制:字符数组不能直接整体赋值(如 s2 = s1; 非法 ),需用 strcpy ;遍历赋值可通过下标逐个字符操作,如:
char s1[10], s2[10] = "Hello";
int i = 0;
while (s2[i] != '\0')
{
s1[i] = s2[i];
i++;
}
s1[i] = '\0'; // 手动补结束符
puts(s1);
2 二维数组
2.1 基础概念
1. 定义: 类型说明符 数组名[常量表达式][常量表达式]; ,如 int a[3][4]; ,可用于图像处理(存储像素矩阵 )、数学矩阵运算(如行列式计算 )等场景。
2. 本质:可理解为“一维数组的数组”, a[0] a[1] a[2] 分别是三个一维数组(长度为 4 ), a 是指向这些一维数组的指针(数组指针 ), a[0][0] 是具体元素, &a[0][0] 是元素指针, &a[0] 是一维数组指针,二者在数值上可能相同,但类型不同。
2.2 使用与操作
1. 遍历:通过嵌套循环实现,外层控制行,内层控制列。若需按“内层列、外层行”(内列外排 )或“内层行、外层列”(内行外列 )逻辑处理,调整循环顺序即可。
示例(按行输出,行列下标从 0 开始 ):
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
for (int i = 0; i < 3; i++)
{ // 行循环
for (int j = 0; j < 4; j++)
{ // 列循环
printf("%d ", a[i][j]);
}
puts(""); // 每行结束换行
}
示例(按列赋值,从 1 开始递增 ):
int a[3][4], t = 1;
for (int j = 0; j < 4; j++)
{ // 列循环(外列)
for (int i = 0; i < 3; i++)
{ // 行循环(内行)
a[i][j] = t++;
}
}
// 输出验证
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", a[i][j]);
}
puts("");
}
2. 初始化:可按行初始化(如 int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}; ),也可省略内层大括号(按顺序填充,如 int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; )。若初始化值不足,剩余元素默认填 0 。初始化时只能省略行号,不能省略列号 ,因为列号决定每个一维数组的长度。
3. 行列数计算:
1)行数: int rows = sizeof(a) / sizeof(a[0]); , sizeof(a) 是整个二维数组字节数, sizeof(a[0]) 是一行(一维数组 )的字节数,相除得行数。
2)列数: int cols = sizeof(a[0]) / sizeof(a[0][0]); , sizeof(a[0]) 是一行字节数, sizeof(a[0][0]) 是单个元素字节数,相除得列数。
2.3 应用扩展(三阶行列式计算示例)
三阶行列式可通过二维数组存储矩阵,用循环实现计算。以下是基于“对角线法则”的循环版代码:
#include <stdio.h>
int main(void)
{
int a[3][3] = {1,2,3,4,5,6,7,8,9}; // 三阶矩阵
int det = 0;
// 外层循环控制符号(主副对角线)
for (int sign = 1; sign >= -1; sign -= 2)
{
for (int i = 0; i < 3; i++)
{
// 生成行列偏移,覆盖行列式六项乘积
int offset1 = (i + 1) % 3;
int offset2 = (i + 2) % 3;
if (sign == 1)
{
// 主对角线方向累加
det += a[0][i] * a[1][offset1] * a[2][offset2];
}
else
{
// 副对角线方向累减
det -= a[0][i] * a[1][offset2] * a[2][offset1];
}
}
}
printf("三阶行列式结果:%d\n", det);
return 0;
}
逻辑说明:通过 sign 控制主( + )、副( - )对角线乘积的加减; offset1 offset2 生成循环偏移,遍历行列式展开所需的元素组合,模拟对角线法则的六项乘积计算,最终得到行列式结果。