5. 数组与字符串:一维数组、二维数组、字符数组与C风格字符串、string类、数组与字符串的遍历
数组和字符串,说白了就是C++里最基础的数据容器。我刚开始学的时候,觉得这东西太简单了,不就是一堆数据排排坐吗?结果后来在项目里踩了不少坑,才发现这里面的门道还真不少。今天咱们就好好聊聊这个话题。
5.1 一维数组:最简单的数据集合
一维数组就像一排带编号的抽屉。每个抽屉里放一个数据,编号从0开始。嗯,这里要注意,C++的数组索引是从0开始的,不是从1。我见过不少新手在这上面栽跟头。
// 声明一个能放5个整数的数组
int scores[5];
// 声明并初始化
int scores[5] = {85, 92, 78, 90, 88};
// 也可以省略大小,编译器会自动计算
int scores[] = {85, 92, 78, 90, 88};
小提示:我个人习惯在声明数组时就初始化,哪怕只是全部赋0。因为未初始化的局部数组里存的是随机值,这在调试时特别容易让人抓狂。
访问数组元素很简单,用方括号加索引就行:
cout << "第三个成绩是:" << scores[2] << endl; // 输出78
scores[0] = 95; // 修改第一个元素
警告:C++不会检查数组下标是否越界!我曾经在项目里遇到过,一个同事写了 scores[5] = 100,数组只有5个元素,索引最大是4。结果程序运行时好时坏,查了两天才发现是越界写入了相邻内存。这种bug特别隐蔽,一定要小心。
5.2 二维数组:表格数据的好帮手
二维数组,你可以把它想象成一个表格,有行有列。比如存储一个班级3个学生、每科4门课的成绩:
// 3行4列的二维数组
int grades[3][4] = {
{85, 92, 78, 90}, // 第一个学生
{88, 76, 95, 82}, // 第二个学生
{90, 85, 88, 91} // 第三个学生
};
// 访问第2个学生的第3门课成绩
cout << grades[1][2] << endl; // 输出95
说白了,二维数组在内存里其实也是连续存储的,按行优先排列。你想想看,编译器只是帮我们把 grades[i][j] 转换成了 grades[i * 列数 + j] 这样的线性地址。
5.3 字符数组与C风格字符串
C风格字符串,其实就是以'\0'结尾的字符数组。这个'\0'是字符串结束标志,千万不能丢。
// 字符数组
char name[10] = {'Z', 'h', 'a', 'n', 'g', '\0'};
// 更简单的写法
char name[] = "Zhang"; // 自动在末尾加'\0'
// 注意:这个数组大小是6,不是5!
cout << "数组大小:" << sizeof(name) << endl; // 输出6
重点:用双引号括起来的字符串字面量,末尾自动有'\0'。但用花括号初始化时,你得自己加。我见过有人写 char str[5] = "Hello";,这就有问题——"Hello"需要6个字符的空间(包括'\0'),数组只给了5个,编译能过但运行时会出问题。
C风格字符串常用函数(需要包含 <cstring>):
| 函数 | 作用 | 示例 |
|---|---|---|
| strlen() | 获取字符串长度(不含'\0') | strlen("Hello") → 5 |
| strcpy() | 复制字符串 | strcpy(dest, src) |
| strcat() | 拼接字符串 | strcat(dest, src) |
| strcmp() | 比较字符串 | 相等返回0 |
避坑指南:我曾经在项目里用 strcpy 时没注意目标数组的大小,结果源字符串比目标数组长,直接覆盖了后面的内存。从那以后,我改用 strncpy 或者直接用C++的 string 类了。
5.4 string类:C++的现代方案
说实话,C风格字符串用起来挺麻烦的。C++标准库提供了 string 类,用起来舒服多了。你想想看,不用管内存管理,不用操心'\0',多省心。
#include <string>
using namespace std;
string name = "Zhang San";
string greeting = "Hello, " + name; // 可以直接拼接
cout << "长度:" << name.length() << endl; // 输出9
cout << "第一个字符:" << name[0] << endl; // 输出Z
// 比较字符串
if (name == "Zhang San") {
cout << "匹配成功" << endl;
}
string 类常用方法:
| 方法 | 作用 | 示例 |
|---|---|---|
| length() / size() | 获取字符串长度 | s.length() |
| empty() | 判断是否为空 | s.empty() |
| substr(pos, len) | 截取子串 | s.substr(1, 3) |
| find(str) | 查找子串位置 | s.find("an") |
| c_str() | 转成C风格字符串 | s.c_str() |
5.5 数组与字符串的遍历
遍历,说白了就是把每个元素都过一遍。最常用的就是for循环。
一维数组遍历
int scores[] = {85, 92, 78, 90, 88};
int size = sizeof(scores) / sizeof(scores[0]);
// 传统for循环
for (int i = 0; i < size; i++) {
cout << scores[i] << " ";
}
// C++11 范围for循环(推荐)
for (int score : scores) {
cout << score << " ";
}
二维数组遍历
int grades[3][4] = {
{85, 92, 78, 90},
{88, 76, 95, 82},
{90, 85, 88, 91}
};
// 双重循环遍历
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << grades[i][j] << " ";
}
cout << endl;
}
字符串遍历
string str = "Hello";
// 用索引遍历
for (int i = 0; i < str.length(); i++) {
cout << str[i] << " ";
}
// 用范围for遍历(更简洁)
for (char ch : str) {
cout << ch << " ";
}
小技巧:遍历时如果要修改元素,范围for循环要用引用:for (int &score : scores)。不加引用的话,你改的是副本,原数组不变。这个坑我踩过,当时改了半天发现数组值没变,差点怀疑人生。
嗯,到这里数组和字符串的基础知识就讲完了。记住一点:能用 string 就别用C风格字符串,能用范围for循环就别用传统for循环。这些习惯能帮你省下不少调试时间。