第4章 数组与字符串:从内存布局到实战避坑
数组和字符串,说白了就是C++里最基础的数据容器。我刚开始学的时候,觉得这玩意儿太简单了——不就是一堆数据排排坐吗?结果真到项目里,踩过的坑一个接一个。今天咱们就把这块彻底讲透。
4.1 一维数组:最朴素的数据集合
一维数组,你可以想象成酒店里的一排房间。每个房间有个门牌号(下标),里面住着客人(数据)。
// 声明一个能装5个整数的数组
int scores[5];
// 声明并初始化
int scores[5] = {88, 92, 76, 85, 90};
// 也可以省略大小,编译器自动算
int scores[] = {88, 92, 76, 85, 90};
我个人习惯在声明数组时,永远显式指定大小。为什么?因为代码可读性更好,别人一看就知道你要存几个数据。
const int 定义数组大小,而不是直接写数字。这样改起来方便,也不容易出错。
const int STUDENT_COUNT = 5;
int scores[STUDENT_COUNT] = {88, 92, 76, 85, 90};
4.2 二维数组:表格思维
二维数组,其实就是「数组的数组」。我习惯把它想象成Excel表格——行和列。
// 3行4列的矩阵
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问第2行第3列的元素
int val = matrix[1][2]; // 结果是7
嗯,这里要注意:C++里数组下标从0开始。我见过太多新手在这上面栽跟头了。matrix[1][2] 实际上是第2行第3列,不是第1行第2列。
4.3 字符数组与C风格字符串
C风格字符串,说白了就是一个以 '\0' 结尾的字符数组。这是C语言留下的遗产,在C++里依然广泛使用。
// 字符数组
char name[10] = {'J', 'o', 'h', 'n', '\0'};
// 字符串字面量(自动加'\0')
char name[10] = "John";
// 注意:数组大小要足够容纳'\0'
char name[5] = "John"; // 没问题,刚好5个字符(包括'\0')
char name[4] = "John"; // 危险!没有空间放'\0'
为什么会这样?因为C风格字符串的结尾标志就是 '\0'。如果你忘了它,字符串函数(比如 strlen、strcpy)就会一直往后读,直到碰见内存里的某个 '\0' 为止——这通常会导致程序崩溃。
4.4 string类:C++的救星
说实话,我刚开始用C++时,最烦的就是C风格字符串。每次都要手动管理内存、检查边界、记得加'\0'……直到我发现了 std::string。
#include <string>
#include <iostream>
std::string name = "John";
std::string greeting = "Hello, " + name + "!";
std::cout << greeting << std::endl; // 输出:Hello, John!
std::cout << "长度:" << name.length() << std::endl; // 输出:4
你想想看,用 string 类,你不用再关心内存分配、不用再担心缓冲区溢出、不用再手动复制字符串。它自己会管理好一切。
| 操作 | C风格字符串 | std::string |
|---|---|---|
| 复制 | strcpy(dest, src) | str2 = str1; |
| 拼接 | strcat(dest, src) | str3 = str1 + str2; |
| 获取长度 | strlen(str) | str.length() |
| 比较 | strcmp(s1, s2) | s1 == s2 |
std::string。只有在调用C语言库函数、或者需要极致性能优化时,才考虑C风格字符串。
4.5 数组与指针的关系
数组名,其实就是一个指针常量——它指向数组的第一个元素。我当年学到这里时,脑子转了好几个弯才想明白。
int arr[5] = {10, 20, 30, 40, 50};
// arr 等价于 &arr[0]
int* ptr = arr; // 数组名可以直接赋值给指针
// 通过指针访问数组元素
std::cout << *(ptr + 2) << std::endl; // 输出30,等价于 arr[2]
// 指针也可以像数组一样用下标
std::cout << ptr[3] << std::endl; // 输出40
说白了,arr[i] 在编译器眼里就是 *(arr + i)。这就是为什么数组下标从0开始——因为 arr + 0 就是第一个元素的地址。
int arr[5];
int* ptr = arr;
ptr = ptr + 1; // 合法,指针可以移动
// arr = arr + 1; // 编译错误!数组名不能修改
4.6 知识体系总览
下面这张图,是我梳理的本章知识结构。你把它存下来,学完这章后再回头看,会更有感觉。
4.7 实战经验总结
我在项目里用过不少数组和字符串,有几个经验想分享给你:
- 能用
std::vector就别用原生数组——除非你明确知道数组大小且不会变。vector 更安全、更灵活。 - 字符串拼接用
string的+运算符——别用strcat,那玩意儿容易缓冲区溢出。 - 二维数组传参时,第二维大小必须指定——这是C++的语法规定,没办法。
- 遍历数组用范围for循环——C++11开始支持,代码更简洁、更安全。
// 范围for循环示例
int scores[] = {88, 92, 76, 85, 90};
for (int score : scores) {
std::cout << score << " ";
}
// 输出:88 92 76 85 90
嗯,数组和字符串这块,说白了就是C++的基础设施。你把它搞透了,后面学指针、学容器、学算法,都会轻松很多。我当年就是在这块花了足够多的时间,后面才越学越顺。