大家好,我是阿咘~上一篇咱们吃透了 C++ 的三大特性,不少朋友在评论区说 “终于能写面向对象的代码了,但存数据的时候总头疼 —— 用数组怕越界,手动管理内存又容易错”。其实 C++ 早就为我们准备了 “容器” 这个神器 —— 容器是 C++ 标准库(STL)里的 “数据存储工具”,能自动管理内存、提供便捷的增删改查接口,比如存列表数据用 vector,存键值对用 map,处理字符串用 string。今天咱们就聚焦新手最常用的三种容器,从 “怎么用” 到 “什么时候用”,结合实际场景讲清楚,帮大家告别 “手动管理数组” 的烦恼~

目录

一、先搞懂:什么是 C++ 容器?为什么要用容器?

二、容器 1:vector—— 最常用的 “动态数组”

1. 核心用法:创建 + 增删改查(附代码)

2. 关键知识点:避免踩坑

3. 适用场景:什么时候用 vector?

三、容器 2:map——“键值对” 神器,快速查找

1. 核心用法:创建 + 增删改查(附代码)

2. 关键知识点:避免踩坑

3. 适用场景:什么时候用 map?

四、容器 3:string——C++ 的 “字符串” 容器

1. 核心用法:创建 + 拼接 + 查找 + 替换(附代码)

2. 关键知识点:避免踩坑

3. 适用场景:什么时候用 string?

五、阿咘的总结:三种容器的 “选择指南”


一、先搞懂:什么是 C++ 容器?为什么要用容器?

在讲具体容器前,先回答一个新手常问的问题:“我用数组(int arr [100])也能存数据,为什么还要用容器?”

简单说,容器是 “升级版的数组 / 结构体”,它的核心优势有两个:

  1. 自动管理内存:比如数组的大小是固定的(定义 int arr [10] 后,就只能存 10 个元素),而容器(比如 vector)能 “动态扩容”—— 存满了会自动申请更大的内存,不用我们手动调用 malloc/free;
  1. 自带实用接口:比如想找容器里的某个元素,不用自己写循环遍历,容器的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

字符串专用,接口丰富

处理文本(用户名、文件内容)

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐