C++11 标准库新容器与核心语言特性详解
一、 单向链表 forward_list
C++11 引入了 std::forward_list,这是一个单向链表容器。与双向链表 std::list 相比,它由于不需要维护前向指针,在内存占用上更加精简,适用于对内存敏感且只需单向遍历的场景。
其核心接口可归纳为以下几类:
- 迭代器与边界:提供
before_begin()、begin()、end()及其const版本。注意before_begin()是单向链表特有的,用于支持在头部之前插入或删除元素。 - 容量与访问:支持
empty()、max_size()和front()。由于是单向链表,不支持size()和back(),以保持 O(1) 的操作复杂度。 - 元素修改:包括
push_front()、pop_front()、emplace_front()。对于非头部操作,提供insert_after()、erase_after()和emplace_after()。 - 链表专属操作:如
splice_after()(拼接)、remove()、remove_if()、unique()、merge()、sort()和reverse()。
二、 容器元素的删除:Erase-Remove 惯用法
在 C++ 中,标准算法 std::remove_if 并不会真正改变容器的大小。它通过移动元素,将不需要删除的元素前置,并返回一个指向新逻辑结尾的迭代器。要真正释放内存并改变容器大小,必须结合容器自身的 erase 方法。
#include <iostream>
#include <vector>
#include <algorithm>
bool isEven(int val) {
return val % 2 == 0;
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8};
// remove_if 将偶数移到末尾,返回新逻辑结尾
auto new_end = std::remove_if(numbers.begin(), numbers.end(), isEven);
// 真正删除多余的元素
numbers.erase(new_end, numbers.end());
for (int n : numbers) {
std::cout << n << " ";
}
return 0;
}
需要注意的是,这种惯用法不适用于关联容器(如 std::set 或 std::map),因为它们不允许通过算法随意修改元素位置,应直接使用其成员函数 erase。
三、 容器交换操作 swap 的底层差异
C++11 提供了非成员函数 std::swap。对于大多数动态容器(如 std::vector),swap 仅交换内部指针和状态,时间复杂度为 O(1),且原有迭代器依然有效,只是它们现在指向了另一个容器的元素。然而,对于 std::array,由于其内存是连续且固定分配的,swap 会逐个交换元素,时间复杂度为 O(N),且迭代器依然指向原容器,只是元素值发生了改变。
#include <iostream>
#include <vector>
#include <array>
int main() {
std::vector<int> vecA = {10, 20};
std::vector<int> vecB = {30, 40};
auto itA = vecA.begin();
std::swap(vecA, vecB);
// itA 仍然有效,但现在指向 vecB 中的 10
std::cout << "Vector swap - itA points to: " << *itA << "\n";
std::array<int, 2> arrX = {1, 2};
std::array<int, 2> arrY = {3, 4};
auto itX = arrX.begin();
std::swap(arrX, arrY);
// itX 仍然指向 arrX,但 arrX 的首元素变成了 3
std::cout << "Array swap - itX points to: " << *itX << "\n";
return 0;
}
四、 就地构造 emplace 系列函数
emplace、emplace_front 和 emplace_back 允许直接在容器的内存空间中构造对象,避免了 push_back 可能带来的临时对象创建和拷贝/移动开销。
#include <iostream>
#include <vector>
#include <string>
class Employee {
public:
Employee(std::string n, int a) : name(std::move(n)), age(a) {
std::cout << "Constructor called\n";
}
private:
std::string name;
int age;
};
int main() {
std::vector<Employee> staff;
// 使用 emplace_back 直接传递构造参数,无需创建临时对象
staff.emplace_back("Alice", 28);
// push_back 需要先构造临时对象,再移动或拷贝进容器
// staff.push_back(Employee("Bob", 30));
return 0;
}
五、 内存容量优化 shrink_to_fit
动态容器(如 std::vector)在扩容时通常会分配比实际需求更多的内存(即 capacity 大于 size)。如果后续不再添加元素,可以通过调用 shrink_to_fit() 请求编译器释放多余的内存,使 capacity 降至与 size 相等。请注意,这是一个非绑定请求,具体行为依赖于标准库实现。
#include <iostream>
#include <vector>
int main() {
std::vector<double> data;
for (int i = 0; i < 100; ++i) {
data.push_back(i * 1.1);
}
std::cout << "Before: size=" << data.size() << ", capacity=" << data.capacity() << "\n";
data.shrink_to_fit();
std::cout << "After: size=" << data.size() << ", capacity=" << data.capacity() << "\n";
return 0;
}
六、 无序关联容器 unordered_map
C++11 引入了基于哈希表实现的无序容器。std::unordered_map 提供平均 O(1) 的查找、插入和删除复杂度,而传统的 std::map 基于红黑树,复杂度为 O(log N),但能保持元素按 key 有序排列。
#include <iostream>
#include <unordered_map>
#include <map>
int main() {
std::unordered_map<int, std::string> hash_map;
std::map<int, std::string> tree_map;
hash_map[3] = "Three";
hash_map[1] = "One";
hash_map[2] = "Two";
tree_map[3] = "Three";
tree_map[1] = "One";
tree_map[2] = "Two";
std::cout << "Unordered Map traversal:\n";
for (const auto& pair : hash_map) {
std::cout << pair.first << ": " << pair.second << "\n"; // 输出顺序不确定
}
std::cout << "Ordered Map traversal:\n";
for (const auto& pair : tree_map) {
std::cout << pair.first << ": " << pair.second << "\n"; // 严格按 1, 2, 3 输出
}
return 0;
}
七、 智能指针 shared_ptr
std::shared_ptr 通过引用计数机制实现共享所有权。当最后一个指向该对象的 shared_ptr 被销毁或重置时,对象内存会被自动释放。推荐使用 std::make_shared 进行初始化,它会将控制块和对象内存一次性分配,提升性能并增强异常安全性。
#include <iostream>
#include <memory>
#include <string>
int main() {
auto ptr1 = std::make_shared<std::string>("Hello");
std::cout << "ptr1 count: " << ptr1.use_count() << "\n"; // 1
{
auto ptr2 = ptr1; // 引用计数增加
std::cout << "ptr2 count: " << ptr2.use_count() << "\n"; // 2
} // ptr2 离开作用域,引用计数减 1
std::cout << "ptr1 count after scope: " << ptr1.use_count() << "\n"; // 1
ptr1.reset(); // 手动重置,引用计数归零,内存释放
return 0;
}
八、 构造函数新特性:委托与默认
C++11 允许在一个构造函数中调用同一个类的另一个构造函数(委托构造),从而减少代码重复。同时,使用 = default 可以显式要求编译器生成默认的特殊成员函数(如默认构造函数、拷贝构造函数等),这在某些情况下比手动实现更高效且语义更明确。
#include <iostream>
class Sensor {
public:
// 默认构造函数
Sensor() = default;
// 完整参数构造函数
Sensor(int id, double val, bool active)
: sensor_id(id), value(val), is_active(active) {}
// 委托构造:调用三参数构造函数
Sensor(int id) : Sensor(id, 0.0, false) {}
// 显式默认拷贝构造
Sensor(const Sensor&) = default;
void print() const {
std::cout << "ID: " << sensor_id << ", Val: " << value << ", Active: " << is_active << "\n";
}
private:
int sensor_id = 0;
double value = 0.0;
bool is_active = false;
};
int main() {
Sensor s1;
Sensor s2(101, 25.5, true);
Sensor s3(102); // 使用委托构造
s1.print();
s2.print();
s3.print();
return 0;
}