C++ 容器类 入门指南:用它替代原生数组
在 C++ 编程中,我们常常需要存储一组相同类型的数据。过去,我们习惯用原生数组,比如 int data[10];。虽然它简单直接,但存在诸多隐患:无法自动管理内存、缺乏边界检查、不能作为函数参数传递时保留大小信息。而 C++ 标准库提供的容器类
如果你正在从 C 风格数组转向更安全、更现代的 C++ 编程,那么掌握
创建数组与初始化
使用 <array>,这是所有操作的基础。
#include <array>
#include <iostream>
int main() {
// 方法1:直接指定大小和初始化值
std::array<int, 5> numbers = {10, 20, 30, 40, 50};
// 方法2:仅指定大小,自动初始化为0
std::array<double, 3> scores = {};
// 方法3:使用列表初始化,大小可推导
std::array grades = {85, 92, 78, 96};
// 输出验证
for (const auto& value : numbers) {
std::cout << value << " ";
}
std::cout << "\n";
return 0;
}
注释说明:
std::array<T, N>中,T 是元素类型,N 是数组大小(必须是编译时常量)。- 使用花括号
{}初始化时,若数量不足,剩余元素将被默认初始化(如 int 为 0)。- 如果不指定大小,编译器会根据初始化列表自动推导(C++ 17 起支持)。
const auto&是推荐的遍历方式,避免拷贝,提高性能。
这个写法比原生数组更安全,因为编译器会检查初始化列表长度是否匹配大小。如果写错了,比如 std::array<int, 3> a = {1,2,3,4};,编译器会报错,而不是默默越界。
访问元素与边界检查
at() 方法。区别在于是否进行运行时边界检查。
#include <array>
#include <iostream>
#include <stdexcept>
int main() {
std::array<int, 4> data = {100, 200, 300, 400};
// 使用下标访问(不检查边界)
std::cout << "data[0] = " << data[0] << "\n"; // 输出 100
std::cout << "data[3] = " << data[3] << "\n"; // 输出 400
// 使用 at() 方法(会检查边界,越界时抛异常)
try {
std::cout << "data.at(5) = " << data.at(5) << "\n";
} catch (const std::out_of_range& e) {
std::cout << "错误:索引超出范围!" << "\n";
}
return 0;
}
注释说明:
data[0]是快速访问,适合性能敏感场景。data.at(5)会检查索引是否在[0, size())范围内,如果越界,抛出std::out_of_range异常。- 在调试阶段建议用
at(),能帮你尽早发现逻辑错误;发布版本可使用[]提升性能。
想象一下,[] 是直接开门,快但可能误开;at() 是输入密码验证,慢但安全。两者各有所长,选择取决于你的使用场景。
容器特性与成员函数
#include <array>
#include <iostream>
int main() {
std::array<std::string, 3> fruits = {"苹果", "香蕉", "橙子"};
// 获取大小
std::cout << "元素个数: " << fruits.size() << "\n";
// 判断是否为空
std::cout << "是否为空: " << (fruits.empty() ? "是" : "否") << "\n";
// 获取第一个和最后一个元素
std::cout << "第一个: " << fruits.front() << "\n";
std::cout << "最后一个: " << fruits.back() << "\n";
// 使用数据指针(兼容 C 风格函数)
const char* c_str = fruits.data(); // 返回指向第一个元素的指针
std::cout << "C 风格指针: " << c_str << "\n";
return 0;
}
注释说明:
size()返回容器大小,是constexpr,可在编译时计算。empty()判断是否为空,对来说只有大小为 0 才返回 true。 front()和back()分别返回首尾元素,等价于data[0]和data[size()-1]。data()返回指向内部存储的原始指针,用于与 C 语言 API 兼容。
这些接口让 <algorithm>),比如你可以用 std::sort 对它排序:
std::sort(fruits.begin(), fruits.end());
与原生数组的对比
为什么不用 int arr[5];?让我们通过对比来看
| 特性 | 原生数组 | C++ 容器类 |
|---|---|---|
| 大小是否可变 | 否,编译时确定 | 否,编译时确定(但更安全) |
| 是否支持拷贝 | 否,需手动 memcpy |
是,支持深拷贝 |
是否支持 size() 方法 |
否 | 是 |
| 是否支持迭代器 | 否 | 是 |
是否支持 at() 边界检查 |
否 | 是 |
| 是否可作为函数参数传递 | 否(会退化为指针) | 是(保持类型信息) |
比如,以下函数参数传递方式,
// 原生数组:会退化为指针,大小丢失
void process_array(int arr[], int size) {
// 无法知道 arr 实际大小
}
// <array>:保留大小信息
void process_array(const std::array<int, 5>& data) {
std::cout << "大小: " << data.size() << "\n"; // 可直接获取
}
在代码可读性和类型安全上,
实际应用案例:学生成绩管理
假设我们要管理一个班级的 5 名学生考试成绩。
#include <array>
#include <iostream>
#include <iomanip>
int main() {
// 使用 <array> 存储 5 个学生的成绩
std::array<double, 5> scores = {88.5, 92.0, 76.5, 95.5, 83.0};
// 计算平均分
double sum = 0.0;
for (const auto& score : scores) {
sum += score;
}
double avg = sum / scores.size();
// 输出结果
std::cout << std::fixed << std::setprecision(1);
std::cout << "成绩列表: ";
for (const auto& s : scores) {
std::cout << s << " ";
}
std::cout << "\n平均分: " << avg << "\n";
return 0;
}
注释说明:
- 使用
std::setprecision(1)控制小数位数。- 遍历使用范围 for 循环,简洁且安全。
scores.size()确保不会因硬编码数字出错。
这个例子展示了
总结
C++ 容器类
它不仅保留了数组的零开销优势,还提供了边界检查、迭代器、成员函数等现代特性,使代码更健壮、更易读。对于 C++ 初学者来说,掌握
在未来的项目中,不妨多用