将复合类型与标准容器结合需管理生命周期、内存布局及交互机制,核心是按值或智能指针存储,确保构造、拷贝、移动、比较、哈希等操作正确高效。

C++中将复合类型与标准容器结合,核心在于理解和管理这些自定义类型在容器中的生命周期、内存布局以及它们如何与容器的内部机制(如排序、查找、哈希)交互。说白了,就是要把我们自己定义的数据结构,无论是简单的结构体还是复杂的类,稳妥地放进
std::vector
std::map
std::set
将复合类型与标准容器结合,通常有两种主要策略:按值存储和按引用(通常是智能指针)存储。
1. 按值存储(Value Semantics)
这是最直接也最常见的做法。你定义一个结构体或类,然后直接将其对象存储在容器中。例如:
立即学习“C++免费学习笔记(深入)”;
struct MyData {
int id;
std::string name;
// 默认构造函数
MyData() : id(0), name("default") {}
// 带参数构造函数
MyData(int i, const std::string& n) : id(i), name(n) {}
// 为了在某些容器(如std::set, std::map的键)中使用,可能需要比较操作符
bool operator<(const MyData& other) const {
return id < other.id;
}
// 为了在std::unordered_map/set中使用,需要相等操作符
bool operator==(const MyData& other) const {
return id == other.id && name == other.name;
}
};
// 使用
std::vector<MyData> dataVec;
dataVec.push_back(MyData(1, "Alice"));
dataVec.emplace_back(2, "Bob"); // 更高效,直接在容器内部构造
std::map<int, MyData> dataMap;
dataMap[3] = MyData(3, "Charlie");
std::set<MyData> dataSet;
dataSet.insert(MyData(4, "David"));关键点:
std::vector::resize
std::map
std::set
operator<
std::unordered_map
std::unordered_set
operator==
2. 按引用(智能指针)存储(Reference Semantics)
当复合类型对象较大、拷贝开销高昂,或者需要多态行为时,存储智能指针(如
std::unique_ptr
std::shared_ptr
class Base {
public:
virtual void print() const = 0;
virtual ~Base() = default;
};
class DerivedA : public Base {
public:
void print() const override { std::cout << "DerivedA\n"; }
};
class DerivedB : public Base {
public:
void print() const override { std::cout << "DerivedB\n"; }
};
// 使用
std::vector<std::unique_ptr<Base>> objects;
objects.push_back(std::make_unique<DerivedA>());
objects.push_back(std::make_unique<DerivedB>());
for (const auto& p : objects) {
p->print();
}
// 如果需要共享所有权
std::vector<std::shared_ptr<MyData>> sharedDataVec;
auto d1 = std::make_shared<MyData>(5, "Eve");
sharedDataVec.push_back(d1);
sharedDataVec.push_back(std::make_shared<MyData>(6, "Frank"));关键点:
std::unique_ptr
std::shared_ptr
高效存储自定义对象,这事儿挺关键的,尤其是在处理大量数据或者性能敏感的场景下。我个人觉得,这里面学问不小,不仅仅是把东西塞进去那么简单。
首先,理解push_back
emplace_back
std::vector
push_back
emplace_back
emplace_back
struct ComplexObject {
std::string large_string;
std::vector<int> large_vector;
ComplexObject(const std::string& s, int count) : large_string(s) {
large_vector.resize(count);
// ... 更多复杂的初始化
}
// 拷贝构造函数可能很昂贵
ComplexObject(const ComplexObject& other) = default;
// 移动构造函数可以优化
ComplexObject(ComplexObject&& other) noexcept = default;
};
std::vector<ComplexObject> objects;
// 低效:先构造临时对象,再拷贝/移动
objects.push_back(ComplexObject("data_A", 1000));
// 高效:直接在vector内部构造
objects.emplace_back("data_B", 1000);其次,预留内存也是一个常常被忽视但非常有效的优化手段。如果你大致知道容器会存储多少个元素,使用
std::vector::reserve()
再者,选择合适的数据结构本身就是一种高效存储。如果你需要频繁地在中间插入或删除元素,
std::list
std::deque
std::vector
std::unordered_map
std::map
最后,考虑对象大小和生命周期。对于那些非常小、没有资源管理的复合类型(比如只有几个
int
std::unique_ptr
std::shared_ptr
当你的复合类型要作为
std::map
Key
std::set
对于std::map
std::set
这两个容器底层通常是红黑树,它们依赖元素的严格弱序(Strict Weak Ordering)来维护内部的有序性。这意味着你的复合类型必须提供一个operator<
Comparator
struct Point {
int x, y;
// 必须提供operator<,用于std::map/std::set的排序
bool operator<(const Point& other) const {
if (x != other.x) {
return x < other.x;
}
return y < other.y;
}
};
std::set<Point> uniquePoints;
uniquePoints.insert({1, 2});
uniquePoints.insert({2, 1});
uniquePoints.insert({1, 2}); // 不会重复插入
std::map<Point, std::string> pointNames;
pointNames[{1, 2}] = "Center";
pointNames[{0, 0}] = "Origin";关键点:
operator<
a < a
a < b
b < a
a < b
b < c
a < c
a
b
b
c
a
c
Point
const
operator<
const
struct PointComparator {
bool operator()(const Point& a, const Point& b) const {
// 比如,我们想按y坐标优先排序
if (a.y != b.y) {
return a.y < b.y;
}
return a.x < b.x;
}
};
std::set<Point, PointComparator> customSortedPoints;对于std::unordered_map
std::unordered_set
这些容器底层是哈希表,它们不关心元素的排序,但需要快速确定元素的“等价性”和“哈希值”。因此,你的复合类型需要提供:
operator==
size_t
#include <functional> // for std::hash
struct PointHash {
size_t operator()(const Point& p) const {
// 一个简单的哈希组合,实际应用中可能需要更复杂的算法
return std::hash<int>()(p.x) ^ (std::hash<int>()(p.y) << 1);
}
};
// 如果在Point结构体内部定义operator==
// bool operator==(const Point& other) const {
// return x == other.x && y == other.y;
// }
std::unordered_set<Point, PointHash> unorderedPoints;
unorderedPoints.insert({1, 2});
std::unordered_map<Point, std::string, PointHash> unorderedPointNames;
unorderedPointNames[{1, 2}] = "Center";关键点:
operator==
a == a
a == b
b == a
a == b
b == c
a == c
std::hash
Hasher
operator==
operator==
unordered
我个人在写
operator<
set
map
复合类型如果内部管理着资源(比如动态分配的内存、文件句柄、网络连接等),那么将其放入标准容器时,就必须格外小心。这不仅仅是效率问题,更是正确性和安全性的核心。稍有不慎,就可能导致内存泄漏、双重释放、野指针,甚至程序崩溃。
这里面最核心的理念就是资源获取即初始化(RAII),以及C++11引入的移动语义。
1. 遵循“三/五/零法则”
std::unique_ptr
std::shared_ptr
std::vector
std::string
std::fstream
示例:一个管理动态内存的复合类型
class MyResource {
private:
int* data;
size_t size;
public:
// 构造函数:分配资源
MyResource(size_t s) : size(s), data(new int[s]) {
std::cout << "MyResource constructed, size: " << size << "\n";
}
// 析构函数:释放资源
~MyResource() {
std::cout << "MyResource destructed, size: " << size << "\n";
delete[] data;
}
// 拷贝构造函数:深拷贝,避免多个对象指向同一资源
MyResource(const MyResource& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + other.size, data);
std::cout << "MyResource copy constructed, size: " << size << "\n";
}
// 拷贝赋值运算符:深拷贝,处理自赋值和资源清理
MyResource& operator=(const MyResource& other) {
if (this != &other) { // 避免自赋值
delete[] data; // 释放旧资源
size = other.size;
data = new int[other.size];
std::copy(other.data, other.data + other.size, data);
}
std::cout << "MyResource copy assigned, size: " << size << "\n";
return *this;
}
// 移动构造函数:转移资源所有权,避免不必要的深拷贝
MyResource(MyResource&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // 源对象不再拥有资源
other.size = 0;
std::cout << "MyResource move constructed, size: " << size << "\n";
}
// 移动赋值运算符:转移资源所有权
MyResource& operator=(MyResource&& other) noexcept {
if (this != &other) {
delete[] data; // 释放旧资源
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
std::cout << "MyResource move assigned, size: " << size << "\n";
return *this;
}
};
// 容器操作
std::vector<MyResource> resources;
resources.reserve(2); // 预留空间,减少重新分配
resources.push_back(MyResource(10)); // 触发移动构造
resources.emplace_back(20); // 直接构造,可能触发移动构造(取决于编译器优化)2. 智能指针的妙用
坦白说,手动编写上述“五法则”的代码既繁琐又容易出错。这就是为什么我个人非常推崇使用智能指针来管理动态资源。
std::unique_ptr
std::shared_ptr
// 使用智能指针管理资源,遵循“零法则”
class MyResourceSmart {
private:
std::unique_ptr<int[]> data; // 使用unique_ptr管理动态数组
size_t size;
public:
MyResourceSmart(size_t s) : size(s), data(std::make_unique<int[]>(s)) {
std::cout << "MyResourceSmart constructed, size: " << size << "\n";
}
// 默认的析构、拷贝、移动函数都能正常工作!
// 拷贝构造:会调用unique_ptr的拷贝构造(但unique_ptr没有拷贝构造,需要自己实现深拷贝)
// 如果需要拷贝,可以这样实现:
MyResourceSmart(const MyResourceSmart& other) : size(other.size), data(std::make_unique<int[]>(other.size)) {
std::copy(other.data.get(), other.data.get() + other.size, data.get());
std::cout << "MyResourceSmart copy constructed (deep), size: " << size << "\n";
}
// 移动构造:unique_ptr有移动构造,默认即可
MyResourceSmart(MyResourceSmart&& other) noexcept = default;
MyResourceSmart& operator=(MyResourceSmart&& other) noexcept = default;
// 拷贝赋值
MyResourceSmart& operator=(const MyResourceSmart& other) {
if (this != &other) {
size = other.size;
data = std::make_unique<int以上就是C++如何实现复合类型与标准容器结合的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号