【阿咘的 C++ 学习笔记】新手必学!vector、map、string 容器实战指南(附完整代码)
大家好,我是阿咘~上一篇咱们吃透了 C++ 的三大特性,不少朋友在评论区说 “终于能写面向对象的代码了,但存数据的时候总头疼 —— 用数组怕越界,手动管理内存又容易错”。其实 C++ 早就为我们准备了 “容器” 这个神器 —— 容器是 C++ 标准库(STL)里的 “数据存储工具”,能自动管理内存、提供便捷的增删改查接口,比如存列表数据用 vector,存键值对用 map,处理字符串用 string。今天咱们就聚焦新手最常用的三种容器,从 “怎么用” 到 “什么时候用”,结合实际场景讲清楚,帮大家告别 “手动管理数组” 的烦恼~
目录
1. 核心用法:创建 + 拼接 + 查找 + 替换(附代码)
一、先搞懂:什么是 C++ 容器?为什么要用容器?
在讲具体容器前,先回答一个新手常问的问题:“我用数组(int arr [100])也能存数据,为什么还要用容器?”
简单说,容器是 “升级版的数组 / 结构体”,它的核心优势有两个:
- 自动管理内存:比如数组的大小是固定的(定义 int arr [10] 后,就只能存 10 个元素),而容器(比如 vector)能 “动态扩容”—— 存满了会自动申请更大的内存,不用我们手动调用 malloc/free;
- 自带实用接口:比如想找容器里的某个元素,不用自己写循环遍历,容器的find方法能直接搞定;想排序,调用sort接口就行,省去了手写排序算法的麻烦。
C++ 容器分很多种,今天咱们重点讲三种新手最常用的:
- vector:动态数组,适合存 “有序的列表数据”(比如学生成绩、商品价格);
- map:键值对容器,适合存 “一一对应的数据”(比如学号→姓名、ID→成绩);
- string:字符串容器,比 C 语言的 char 数组好用太多(支持直接拼接、查找、替换)。
二、容器 1:vector—— 最常用的 “动态数组”
vector 就像 “可以自动长大的数组”,比如你要存一个班级的学生成绩,刚开始不知道有多少人,用 vector 就不用提前定大小,来了一个学生就加一个成绩,非常灵活。
1. 核心用法:创建 + 增删改查(附代码)
先看一段完整代码,包含 vector 的常用操作,后面逐行讲解:
#include <iostream>
#include <vector> // 使用vector必须包含这个头文件
#include <algorithm> // 包含sort、find等算法函数
using namespace std;
int main() {
// 1. 创建vector:存int类型的成绩,初始为空
vector<int> scores;
// 2. 新增元素(常用两种方式)
scores.push_back(95); // 从尾部添加元素(最常用,效率高)
scores.push_back(88);
scores.push_back(92);
scores.emplace_back(76); // 和push_back类似,效率略高(直接在容器内构造元素)
// 3. 访问元素(三种方式)
cout << "第1种访问方式:下标(和数组一样)" << endl;
cout << "第1个成绩:" << scores[0] << endl; // 注意:下标从0开始,越界会报错
cout << "第3个成绩:" << scores[2] << endl;
cout << "\n第2种访问方式:at()方法(推荐,越界会抛异常提示)" << endl;
cout << "第2个成绩:" << scores.at(1) << endl; // 比下标更安全,建议用at()
cout << "\n第3种访问方式:遍历(for循环)" << endl;
// 普通for循环:用size()获取容器大小(元素个数)
for (int i = 0; i < scores.size(); i++) {
cout << "成绩[" << i << "]:" << scores[i] << " ";
}
cout << endl;
// 范围for循环(C++11及以上支持,更简洁)
for (int score : scores) {
cout << score << " ";
}
cout << endl;
// 4. 修改元素
scores.at(2) = 98; // 把第3个成绩(下标2)从92改成98
cout << "\n修改后第3个成绩:" << scores[2] << endl;
// 5. 删除元素
scores.pop_back(); // 从尾部删除元素(最常用)
cout << "\n删除最后一个元素后,容器大小:" << scores.size() << endl;
// 6. 其他常用操作
cout << "\n容器是否为空:" << (scores.empty() ? "是" : "否") << endl; // 判断是否为空
sort(scores.begin(), scores.end()); // 排序(从小到大),需要包含<algorithm>头文件
cout << "排序后成绩:";
for (int score : scores) {
cout << score << " ";
}
cout << endl;
return 0;
}
2. 关键知识点:避免踩坑
- 下标越界问题:vector 的下标和数组一样从 0 开始,但如果用scores[5]访问,而容器只有 3 个元素(size=3),会导致 “未定义行为”(可能报错,也可能出现乱码);用scores.at(5)会直接抛异常 “out_of_range”,更安全,新手建议优先用at();
- push_back 和 emplace_back 的区别:如果存的是简单类型(int、double),两者区别不大;如果存的是自定义类(比如 Student),emplace_back会直接在容器内构造对象,push_back会先构造临时对象再拷贝,emplace_back效率更高,建议优先用;
- size () 和 capacity () 的区别:size()是 “当前元素个数”,capacity()是 “容器当前能容纳的最大元素个数”(比如 vector 存了 3 个元素,capacity 可能是 4,预留了 1 个位置),扩容时 capacity 会翻倍(比如从 4 变成 8),这样能减少频繁申请内存的开销。
3. 适用场景:什么时候用 vector?
- 存 “有序的列表数据”,比如:
-
- 一个班级的学生成绩(按学号顺序排列);
-
- 一个购物车的商品价格(按添加顺序排列);
- 需要频繁从尾部增删元素(push_back/pop_back 效率极高,时间复杂度 O (1));
- 需要排序或查找元素(配合 sort、find 算法)。
三、容器 2:map——“键值对” 神器,快速查找
map 是 “键值对(key-value)” 容器,比如 “学号(key)→姓名(value)”“身份证号(key)→地址(value)”,通过 key 能快速找到对应的 value,就像查字典一样 —— 知道 “单词(key)”,就能快速找到 “释义(value)”。
1. 核心用法:创建 + 增删改查(附代码)
#include <iostream>
#include <map> // 使用map必须包含这个头文件
#include <string>
using namespace std;
int main() {
// 1. 创建map:key是int类型(学号),value是string类型(姓名)
map<int, string> studentMap;
// 2. 新增键值对(三种方式)
studentMap[101] = "张三"; // 最常用:通过key直接赋值
studentMap.insert(pair<int, string>(102, "李四")); // 用insert+pair
studentMap.emplace(103, "王五"); // 用emplace(效率高,推荐)
// 3. 访问元素(两种方式)
cout << "第1种访问方式:通过key访问(类似数组下标)" << endl;
cout << "学号101:" << studentMap[101] << endl;
cout << "学号102:" << studentMap[102] << endl;
cout << "\n第2种访问方式:遍历(用迭代器,map会自动按key排序)" << endl;
// map的迭代器:it->first是key,it->second是value
for (map<int, string>::iterator it = studentMap.begin(); it != studentMap.end(); it++) {
cout << "学号:" << it->first << ",姓名:" << it->second << endl;
}
// 范围for循环(C++11及以上,更简洁)
for (auto& pair : studentMap) { // auto自动推导类型为pair<int, string>
cout << "学号:" << pair.first << ",姓名:" << pair.second << endl;
}
// 4. 查找元素(关键!用find方法,效率高)
cout << "\n查找学号103:" << endl;
auto findIt = studentMap.find(103); // 查找key=103的元素
if (findIt != studentMap.end()) { // 找到:返回对应的迭代器
cout << "找到!姓名:" << findIt->second << endl;
} else { // 没找到:返回end()
cout << "未找到学号103" << endl;
}
// 5. 修改元素
studentMap[102] = "李小四"; // 直接通过key修改value
cout << "\n修改后学号102:" << studentMap[102] << endl;
// 6. 删除元素
// 方式1:通过key删除
studentMap.erase(101);
// 方式2:通过迭代器删除
auto delIt = studentMap.find(103);
if (delIt != studentMap.end()) {
studentMap.erase(delIt);
}
cout << "\n删除后容器大小:" << studentMap.size() << endl;
return 0;
}
2. 关键知识点:避免踩坑
- map 会自动按 key 排序:map 的底层是 “红黑树”,插入元素后会自动按 key 从小到大排序(比如上面的学号 101、102、103,遍历的时候会按这个顺序输出),如果不需要排序,用unordered_map(底层是哈希表,查找效率更高,时间复杂度 O (1));
- 通过 key 访问不存在的元素会怎样:如果用studentMap[104]访问一个不存在的 key(104),map 会自动创建一个键值对(key=104,value 是默认值,比如 string 的默认值是空字符串),这可能导致意外的错误,所以查找元素时建议用find方法,不要直接用下标;
- unordered_map 和 map 的区别:
-
- map:有序(按 key 排序),查找时间复杂度 O (log n),适合需要排序的场景;
-
- unordered_map:无序,查找时间复杂度 O (1),适合频繁查找、不需要排序的场景(比如缓存数据),新手建议优先用unordered_map。
3. 适用场景:什么时候用 map?
- 存 “一一对应的数据”,比如:
-
- 学号→姓名、ID→用户信息;
-
- 配置项(key 是配置名,value 是配置值,比如 “port”→8080);
- 需要通过 key 快速查找元素(比 vector 的遍历查找效率高太多,vector 查找时间复杂度 O (n),map 是 O (log n))。
四、容器 3:string——C++ 的 “字符串” 容器
C 语言里用 char 数组(char str [100])处理字符串很麻烦(比如拼接要写 strcat,查找要写 strstr),而 C++ 的 string 容器自带了很多实用接口,处理字符串非常方便。
1. 核心用法:创建 + 拼接 + 查找 + 替换(附代码)
#include <iostream>
#include <string> // 使用string必须包含这个头文件
using namespace std;
int main() {
// 1. 创建string
string str1 = "Hello"; // 直接赋值
string str2("C++"); // 用构造函数
string str3(5, '!'); // 重复5个'!',结果是"!!!!!"
// 2. 字符串拼接(三种方式)
string str4 = str1 + " " + str2; // 用+拼接(最常用)
str4 += "!"; // 用+=拼接
cout << "拼接后:" << str4 << endl; // 输出:Hello C++!
// 3. 访问字符(两种方式)
cout << "\n第1个字符:" << str4[0] << endl; // 下标访问
cout << "第3个字符:" << str4.at(2) << endl; // at()访问(更安全)
// 4. 查找子串(find方法)
size_t pos = str4.find("C++"); // 查找"C++"在str4中的位置(从0开始)
if (pos != string::npos) { // string::npos表示“未找到”
cout << "\n找到子串\"C++\",位置:" << pos << endl; // 输出:6(Hello后有个空格,位置0-4是Hello,5是空格,6开始是C++)
} else {
cout << "未找到子串" << endl;
}
// 5. 替换子串(replace方法)
// 语法:replace(起始位置, 替换长度, 新字符串)
str4.replace(pos, 3, "C Plus Plus"); // 把位置6开始的3个字符(C++)换成"C Plus Plus"
cout << "\n替换后:" << str4 << endl; // 输出:Hello C Plus Plus!
// 6. 截取子串(substr方法)
// 语法:substr(起始位置, 截取长度),长度省略则截取到末尾
string subStr = str4.substr(6, 10); // 从位置6开始,截取10个字符
cout << "\n截取的子串:" << subStr << endl; // 输出:C Plus Pl
// 7. 其他常用操作
cout << "\n字符串长度:" << str4.size() << endl; // 长度(字符个数)
cout << "是否为空:" << (str4.empty() ? "是" : "否") << endl;
str4.clear(); // 清空字符串
cout << "清空后是否为空:" << (str4.empty() ? "是" : "否") << endl;
return 0;
}
2. 关键知识点:避免踩坑
- string 和 char 数组的转换:如果需要把 string 转成 C 语言的 char 数组,用c_str()方法(比如const char* cStr = str4.c_str();),注意返回的是 const 指针,不能修改;
- find 方法的返回值:find返回的是size_t类型(无符号整数),不要用 int 接收,否则如果未找到(返回 string::npos,是一个很大的无符号数),用 int 接收会变成负数,导致判断错误;
- 字符串拼接的效率:如果频繁拼接字符串(比如在循环里),直接用+=比用+效率高,因为+会创建临时对象,+=是直接在原字符串上修改。
3. 适用场景:什么时候用 string?
- 所有需要处理字符串的场景,比如:
-
- 用户输入的用户名、密码;
-
- 读取文件内容、网络传输的文本数据;
-
- 字符串的拼接、查找、替换、截取(比 C 语言的 char 数组方便太多)。
五、阿咘的总结:三种容器的 “选择指南”
最后用一张表总结三种容器的核心区别和适用场景,新手可以直接对照着选:
| 容器 | 核心特点 | 适用场景 | 关键优势 |
| vector | 动态数组,有序,尾部增删快 | 存有序列表(成绩、商品),频繁尾部操作 | 自动扩容,支持下标访问,配合 sort 排序方便 |
| map/unordered_map | 键值对,快速查找 | 存一一对应数据(学号→姓名),频繁按 key 查找 | 查找效率高,map 有序,unordered_map 更快 |
| string | 字符串专用,接口丰富 | 处理文本(用户名、文件内容) |
更多推荐



所有评论(0)