C++标准模板库

article/2025/6/29 3:56:26

C++标准库参考:

C++ 标准库-CSDN博客 

标准模板库STL

C++ 标准库 和 STL 的关系

1. 严格来说,STL ≠ C++ 标准库

  • STL(Standard Template Library) 是 C++ 标准库的一个子集,主要提供泛型编程相关的组件(如容器、迭代器、算法)。

  • C++ 标准库(C++ Standard Library) 包含 STL,但还额外包含其他部分(如 I/O 流、字符串、多线程等)。

2. C++ 标准库的组成

组成部分

功能

是否属于 STL?

STL

容器(vectormap)、算法(sort)、迭代器

✅ 是

I/O 流

iostreamfstream

❌ 否

字符串类

std::string

❌ 否(但设计受 STL 影响)

智能指针

std::shared_ptrstd::unique_ptr

❌ 否

多线程支持

<thread><mutex>(C++11+)

❌ 否

C 兼容库

<cstdio><cmath>

❌ 否

3. 为什么容易混淆?

历史原因

STL 最初由 Alexander Stepanov 开发,独立于 C++ 标准库

1994 年后,STL 被纳入 C++ 标准(C++98),成为标准库的一部分。

术语混用

许多人习惯用 "STL" 代指整个 C++ 标准库(尽管不严谨)。

C++ 官方文档中通常使用 "Standard Library",而 "STL" 多指泛型编程部分。

4. 代码示例对比

(1) STL 部分(泛型编程)

#include <vector>   // STL 容器
#include <algorithm> // STL 算法int main() {std::vector<int> v = {3, 1, 4};std::sort(v.begin(), v.end()); // STL 算法return 0;
}

(2) 非 STL 部分(C++ 标准库的其他内容)

#include <iostream>  // I/O 流(非 STL)
#include <string>    // std::string(非 STL)int main() {std::string s = "Hello"; // 字符串类std::cout << s << std::endl; // I/O 流return 0;
}

5. 关键结论

  • STL 是 C++ 标准库的子集,专注于泛型编程(容器、算法、迭代器)。

  • C++ 标准库 = STL + 其他组件(如 I/O、字符串、多线程等)。

  • 日常交流中,"STL" 有时被泛化指代整个标准库,但严格区分时应注意范围。

建议

  • 在正式场合(如文档、面试)使用 "C++ 标准库" 以保持准确。

  • 讨论泛型编程时,可以明确使用 "STL"

STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),几乎所有的代码都采用了模板类和模版函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。

C++ 标准模板库(Standard Template Library,STL)是一套功能强大的 C++ 模板类和函数的集合,它提供了一系列通用的、可复用的算法和数据结构。

STL 分为多个组件,包括容器(Containers)、迭代器(Iterators)、算法(Algorithms)、函数对象(Function Objects)和适配器(Adapters)等。

使用 STL 的好处:

  • 代码复用:STL 提供了大量的通用数据结构和算法,可以减少重复编写代码的工作。

  • 性能优化:STL 中的算法和数据结构都经过了优化,以提供最佳的性能。

  • 泛型编程:使用模板,STL 支持泛型编程,使得算法和数据结构可以适用于任何数据类型。

  • 易于维护:STL 的设计使得代码更加模块化,易于阅读和维护。

C++ 标准模板库的核心包括以下重要组件组件:

这些个组件都带有丰富的预定义函数,帮助我们通过简单的方式处理复杂的任务。

对于STL的使用,也普遍存在着两种观点。第一种认为STL的最大作用在于充当经典的数据结构和算法教材,因为它的源代码涉及了许多具体实现方面的问题。第二种则认为STL的初衷乃是为了简化设计,避免重复劳动,提高编程效率,因此应该是“应用至上”的,对于源代码则不必深究。对于初学者而言,通过分析源代码,提高对其应用的理解其意义也不同凡响。

不少C++的经典教材对STL都有非常好的讲解,可以选一本去读。在读书时,要开始学着挑着读,跳着读,不必从头到尾,逐页去读。在这个阶段,可以首先学习迭代器utility、在C++编程中建议替代数组的vector,以及实现双向链表的list。等等。

这里简单介绍一些常用的模板库。

容器

容器其实就是一些数据结构的实现。

关于容器部分,可参考:

C++ 容器类 <vector> | 菜鸟教程 (runoob.com)史上最全的各种C++ STL容器全解析 - Seaway-Fu - 博客园 (cnblogs.com)

vector 容器

参考:C++ 容器类 <vector> | 菜鸟教程 (runoob.com)

C++ 中的 vector 是一种序列容器,它允许你在运行时动态地插入和删除元素。

可以看做一个动态数组。

vector 是基于数组的数据结构,但它可以自动管理内存,这意味着你不需要手动分配和释放内存。

与 C++ 数组相比,vector 具有更多的灵活性和功能,使其成为 C++ 中常用的数据结构之一。

vector 是 C++ 标准模板库(STL)的一部分,提供了灵活的接口和高效的操作。

基本特性:

  • 动态大小vector 的大小可以根据需要自动增长和缩小。

  • 连续存储vector 中的元素在内存中是连续存储的,这使得访问元素非常快速。

  • 可迭代vector 可以被迭代,你可以使用循环(如 for 循环)来访问它的元素。

  • 元素类型vector 可以存储任何类型的元素,包括内置类型、对象、指针等。

使用场景:

  • 当你需要一个可以动态增长和缩小的数组时(类型仍然需要保持一致)。

  • 当你需要频繁地在序列的末尾添加或移除元素时。

  • 当你需要一个可以高效随机访问元素的容器时。

在 C++ 中,使用 <vector> 需要包含头文件 <<vector>>。以下是一些基本的语法:

声明一个 vector

std::vector<int> myVector;//注意,int是该容器的类型,放在vector类后面的尖括号<>里

添加元素:

myVector.push_back(10);

访问元素:

int firstElement = myVector[0];

获取元素数量:

size_t size = myVector.size();

清空 vector

myVector.clear();

下面是一个使用 <vector> 的简单示例,包括输出结果。

#include <iostream>
#include <vector>int main() {// 声明一个存储整数的 vectorstd::vector<int> numbers;// 添加元素numbers.push_back(10);numbers.push_back(20);numbers.push_back(30);// 输出 vector 中的元素std::cout << "Vector contains: ";for (int i = 0; i < numbers.size(); ++i) {std::cout << numbers[i] << " ";}std::cout << std::endl;// 添加更多元素numbers.push_back(40);numbers.push_back(50);// 再次输出 vector 中的元素std::cout << "After adding more elements, vector contains: ";for (int i = 0; i < numbers.size(); ++i) {std::cout << numbers[i] << " ";}std::cout << std::endl;// 访问特定元素std::cout << "The first element is: " << numbers[0] << std::endl;// 清空 vectornumbers.clear();// 检查 vector 是否为空if (numbers.empty()) {std::cout << "The vector is now empty." << std::endl;}return 0;
}

输出结果:

Vector contains: 10 20 30 
After adding more elements, vector contains: 10 20 30 40 50 
The first element is: 10 
The vector is now empty.

<vector> 是 C++ STL 中一个非常有用的容器,它提供了动态数组的功能,使得元素的添加和删除变得更加灵活和方便。通过上述示例,初学者可以快速了解 <vector> 的基本用法和操作。随着学习的深入,你将发现 <vector> 在实际编程中的强大功能和广泛应用。

set容器

std::set 是 C++ 标准模板库 (STL) 中的一种关联容器,它存储唯一元素,并按照特定排序准则自动排序。

基本特性

  1. 唯一性:所有元素都是唯一的(不允许重复)

  2. 自动排序:元素总是按照指定的排序准则自动排序

  3. 不可修改元素:元素是 const 的,不能直接修改(必须先删除再插入新值)

  4. 基于红黑树:通常实现为平衡二叉搜索树(红黑树)

  5. 头文件#include <set>

创建和初始化

#include <iostream>
#include <set>int main() {// 空setstd::set<int> set1;// 初始化列表std::set<int> set2 = {3, 1, 4, 1, 5, 9}; // 实际存储:1, 3, 4, 5, 9// 复制构造函数std::set<int> set3(set2);// 使用迭代器范围初始化int arr[] = {7, 2, 8, 2, 8};std::set<int> set4(arr, arr + 5); // 存储:2, 7, 8return 0;
}

常用操作示例

#include <iostream>
#include <set>int main() {std::set<std::string> names;// 插入元素names.insert("Alice");names.insert("Bob");names.insert("Charlie");names.insert("Alice"); // 不会被插入,因为已存在// 查找元素auto it = names.find("Bob");if (it != names.end()) {std::cout << "Found: " << *it << std::endl;}// 删除元素names.erase("Charlie");// 检查是否为空if (!names.empty()) {std::cout << "Set size: " << names.size() << std::endl;}// 遍历setfor (const auto& name : names) {std::cout << name << " ";}std::cout << std::endl;// 检查元素是否存在 (C++20)if (names.contains("Alice")) {std::cout << "Alice is in the set" << std::endl;}return 0;
}

std::set 是C++中处理有序唯一元素集合的强大工具,特别适合需要保持元素有序且唯一的场景。

map 容器

参考:C++ 容器类 <map> | 菜鸟教程 (runoob.com)

在 C++ 中,<map> 是标准模板库(STL)的一部分,它提供了一种关联容器,用于存储键值对(key-value pairs)。

map 容器中的元素是按照键的顺序自动排序的,这使得它非常适合需要快速查找和有序数据的场景。

定义和特性

  • 键值对map 存储的是键值对,其中每个键都是唯一的。

  • 排序map 中的元素按照键的顺序自动排序,通常是升序。

  • 唯一性:每个键在 map 中只能出现一次。

  • 双向迭代器map 提供了双向迭代器,可以向前和向后遍历元素。

包含头文件:

#include <map>

声明 map 容器:

std::map<key_type, value_type> myMap;
  • key_type 是键的类型。
  • value_type 是值的类型。

插入元素:

myMap[key] = value;

访问元素:

value = myMap[key];

遍历 map:

for (std::map<key_type, value_type>::iterator it = myMap.begin(); it != myMap.end(); ++it) {std::cout << it->first << " => " << it->second << std::endl;
}

下面是一个使用 map 的简单实例,我们将创建一个 map 来存储员工的姓名和他们的年龄,并遍历这个 map 来打印每个员工的姓名和年龄。

#include <iostream>#include <map>
#include <string>int main() {// 创建一个 map 容器,存储员工的姓名和年龄std::map<std::string, int> employees;// 插入员工信息employees["Alice"] = 30;employees["Bob"] = 25;employees["Charlie"] = 35;// 遍历 map 并打印员工信息for (std::map<std::string, int>::iterator it = employees.begin(); it != employees.end(); ++it) {std::cout << it->first << " is " << it->second << " years old." << std::endl;}return 0;
}

输出结果:

Alice is 30 years old.
Bob is 25 years old.
Charlie is 35 years old.

map 是 C++ STL 中一个非常有用的容器,特别适合需要快速查找和有序数据的场景。 

迭代器

参考:

C++ 标准库 <iterator> | 菜鸟教程 (runoob.com)

C++ 标准库中的 <iterator> 头文件提供了一组工具,用于遍历容器中的元素。迭代器是 C++ 标准模板库(STL)中的核心概念之一,它允许程序员以统一的方式访问容器中的元素,而不需要关心容器的具体实现细节。

迭代器是一个对象,它提供了一种方法来遍历容器中的元素。迭代器可以被视为指向容器中元素的指针,但它比指针更加灵活和强大。迭代器可以用于访问、修改容器中的元素,并且可以与 STL 算法一起使用。

迭代器主要分为以下几类:

  1. 输入迭代器(Input Iterator):只能进行单次读取操作,不能进行写入操作。
  2. 输出迭代器(Output Iterator):只能进行单次写入操作,不能进行读取操作。
  3. 正向迭代器(Forward Iterator):可以进行读取和写入操作,并且可以向前移动。
  4. 双向迭代器(Bidirectional Iterator):除了可以进行正向迭代器的所有操作外,还可以向后移动。
  5. 随机访问迭代器(Random Access Iterator):除了可以进行双向迭代器的所有操作外,还可以进行随机访问,例如通过下标访问元素。

迭代器的语法通常如下:

#include <iterator>// 使用迭代器遍历容器
for (ContainerType::iterator it = container.begin(); it != container.end(); ++it) {// 访问元素 *it
}

下面是一个使用 <iterator> 头文件和迭代器遍历 std::vector 的示例:

#include <iostream>
#include <vector>
#include <iterator>int main() {// 创建一个 vector 容器并初始化std::vector<int> vec = {1, 2, 3, 4, 5};// 使用迭代器遍历 vectorfor (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 auto 关键字简化迭代器类型for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用 C++11 范围 for 循环for (int elem : vec) {std::cout << elem << " ";}std::cout << std::endl;return 0;
}

输出结果:

1 2 3 4 5 
1 2 3 4 5 
1 2 3 4 5 

通过使用 <iterator> 头文件,我们可以方便地遍历 C++ STL 容器中的元素。迭代器提供了一种统一的接口,使得我们可以在不同的容器之间切换,而不需要改变遍历的代码。这大大提高了代码的可重用性和可维护性。

对于初学者来说,理解迭代器的概念和使用方式是非常重要的,因为它们是 C++ STL 的基础。希望这篇文章能帮助你更好地理解迭代器,并在你的 C++ 编程中有效地使用它们。


迭代器获取的是容器元素的指针吗

在 C++ 中,迭代器(Iterator)是一种抽象的 “指针类似物”,用于遍历容器中的元素,但它不一定直接是指针。具体来说:

1. 迭代器的本质

迭代器是一个抽象概念,它定义了一组操作(如 *++== 等),使得我们可以不关心容器的具体实现,统一地访问容器元素。迭代器的实现方式取决于容器类型:

原生指针:对于连续存储的容器(如 std::vectorstd::array),迭代器可能直接用原生指针实现。

类对象:对于非连续存储的容器(如 std::liststd::map),迭代器是一个类对象,内部封装了指向节点的指针,并通过重载运算符(如 *++)来模拟指针行为。

2. 迭代器与指针的关系

(1) 行为类似指针

迭代器的接口设计模仿了指针的行为:

解引用(*it:获取元素的引用(或值)。

箭头操作符(it->member:访问元素的成员(等价于 (*it).member)。

自增 / 自减(++it--it:移动到下一个 / 前一个元素。

(2) 但不一定是指针

连续容器(如 vector

迭代器可能直接封装原生指针,性能与指针无异。

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();  // 可能是 int* 类型

非连续容器(如 listmap

迭代器是类对象,内部维护节点指针,并通过运算符重载实现遍历逻辑。

std::list<int> lst = {1, 2, 3};
auto it = lst.begin();  // 是一个类对象,封装了指向链表节点的指针

特性

指针

迭代器

类型

原生数据类型(如 int*

通常是类模板(如 vector<int>::iterator

实现方式

直接存储内存地址

可能封装指针,或使用其他方式实现遍历逻辑

功能范围

仅支持内存操作(解引用、算术运算)

不同迭代器支持不同操作(如随机访问、双向移动)

安全性

需手动管理内存,易越界

部分迭代器提供越界检查(如 at()

适用场景

直接操作内存

抽象遍历容器,不依赖容器类型

3. 迭代器与指针的区别

4. 获取迭代器指向的指针

如果需要访问迭代器内部的原生指针(例如传递给 C 函数),可通过以下方式:

连续容器(如 vector

std::vector<int> vec = {1, 2, 3};
auto it = vec.begin();
int* ptr = &(*it);  // 获取迭代器指向的元素的地址

非连续容器

通常无法直接获取原生指针,因为元素可能分散存储(如链表节点)。

5. 示例代码

#include <iostream>
#include <vector>
#include <list>int main() {// 连续容器(vector)的迭代器std::vector<int> vec = {10, 20, 30};auto vec_it = vec.begin();int* vec_ptr = &(*vec_it);  // 获取原生指针std::cout << "Vector iterator value: " << *vec_it << std::endl;std::cout << "Vector pointer value: " << *vec_ptr << std::endl;// 非连续容器(list)的迭代器std::list<int> lst = {10, 20, 30};auto lst_it = lst.begin();// int* lst_ptr = &(*lst_it);  // 虽然可以获取地址,但不建议用于算术运算std::cout << "List iterator value: " << *lst_it << std::endl;return 0;
}

总结

迭代器是指针的抽象:它模仿指针的行为,但实现方式更灵活。

连续容器的迭代器:可能直接基于原生指针实现。

非连续容器的迭代器:通过类对象封装节点指针,重载运算符实现遍历。

获取原生指针:对于连续容器,可通过 &(*it) 获取元素地址;非连续容器通常不建议这样做。


C++迭代器有哪些常用接口

在 C++ 中,迭代器(Iterator)是一种抽象的概念,用于遍历容器(如 vectorlistmap 等)中的元素。不同类型的迭代器支持不同的操作接口,以下是迭代器的常用接口及其分类:

1.常用迭代器接口

(1) 所有迭代器都支持的操作

解引用(Dereference):获取当前元素。

*it;      // 返回当前元素的引用
it->mem;  // 等价于 (*it).mem

自增(Increment):移动到下一个元素。

++it;  // 前置自增(推荐,效率更高)
it++;  // 后置自增(返回旧值)

(2) 双向迭代器额外支持

自减(Decrement):移动到前一个元素。

--it;  // 前置自减
it--;  // 后置自减

(3) 随机访问迭代器额外支持

算术运算

it + n;  // 向前移动 n 个位置
it - n;  // 向后移动 n 个位置
it += n; // 复合赋值
it -= n;

比较运算

it1 < it2;   // 判断 it1 是否在 it2 前面
it1 <= it2;
it1 > it2;
it1 >= it2;

下标访问

it[n];  // 等价于 *(it + n)

(4) 迭代器差值

距离计算:两个迭代器之间的元素个数(仅随机访问迭代器支持)。

it2 - it1;  // 返回类型为 ptrdiff_t

2.容器的迭代器相关函数

每个容器都提供以下成员函数:

begin() 和 end()

container.begin();  // 返回指向第一个元素的迭代器
container.end();    // 返回指向“尾后”(past-the-end)的迭代器

cbegin() 和 cend():返回常量迭代器(C++11 起)。

rbegin() 和 rend():返回反向迭代器(双向及以上支持)。

crbegin() 和 crend():返回常量反向迭代器。

迭代器辅助函数(<iterator> 头文件)

advance(it, n):将迭代器 it 移动 n 步(随机访问迭代器直接计算,其他迭代器循环自增 / 自减)。

std::advance(it, 3);  // 向前移动 3 步

distance(it1, it2):计算两个迭代器之间的距离。

std::ptrdiff_t dist = std::distance(it1, it2);

next(it, n) 和 prev(it, n)**:返回 it 之后 / 之前的第 n 个迭代器(C++11 起)。

auto next_it = std::next(it, 2);  // 等价于 it + 2(随机访问迭代器)
auto prev_it = std::prev(it, 1);  // 等价于 it - 1(双向迭代器)

示例代码

#include <iostream>
#include <vector>
#include <iterator>int main() {std::vector<int> vec = {10, 20, 30, 40, 50};// 正向遍历(使用随机访问迭代器)for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";  // 解引用获取元素}std::cout << std::endl;// 反向遍历for (auto it = vec.rbegin(); it != vec.rend(); ++it) {std::cout << *it << " ";  // 反向迭代器的 ++ 是向前移动}std::cout << std::endl;// 使用迭代器算术auto it = vec.begin();std::cout << *(it + 2) << std::endl;  // 输出第 3 个元素(30)// 计算距离std::ptrdiff_t dist = std::distance(vec.begin(), vec.end());std::cout << "Size: " << dist << std::endl;  // 输出 5return 0;
}

3.迭代器失效(Iterator Invalidation)

修改容器可能导致迭代器失效,例如:

vector:插入 / 删除元素可能导致内存重新分配,所有迭代器失效。

list:插入 / 删除元素仅使指向被删除元素的迭代器失效。

使用迭代器时需注意容器操作对迭代器的影响。

总结

迭代器的接口根据类型不同而不同,但核心操作包括解引用(*)、自增(++)和比较(!=)。随机访问迭代器提供最丰富的功能(如 +[]),而输入 / 输出迭代器功能最少。理解迭代器的分类有助于正确使用容器和算法。

内存管理库 <memory>

参考:C++ 内存管理库 <memory> | 菜鸟教程 (runoob.com)

<memory> 是 C++ 标准库中的一个头文件,它包含了用于动态内存管理的模板和函数。

在 C++ 中,内存管理是一个重要的概念。动态内存管理允许程序在运行时分配和释放内存,这在处理不确定大小的数据结构时非常有用。然而,不正确的内存管理可能导致内存泄漏、野指针等问题。

<memory> 头文件提供了智能指针等工具,帮助开发者更安全地管理动态内存。

智能指针

智能指针是 <memory> 头文件中的核心内容。它们是 C++11 引入的特性,用于自动管理动态分配的内存。智能指针的主要类型有:

  • std::unique_ptr:独占所有权的智能指针,同一时间只能有一个 unique_ptr 指向特定内存。

  • std::shared_ptr:共享所有权的智能指针,多个 shared_ptr 可以指向同一内存,内存在最后一个 shared_ptr 被销毁时释放。

  • std::weak_ptr:弱引用智能指针,用于与 shared_ptr 配合使用,避免循环引用导致的内存泄漏。


举例:使用 std::unique_ptr

#include <iostream>
#include <memory>class MyClass {
public:void doSomething() {std::cout << "Doing something" << std::endl;}
};int main() {std::unique_ptr<MyClass> myPtr(new MyClass());myPtr->doSomething(); // 使用智能指针调用成员函数// 当 main 函数结束时,myPtr 被销毁,自动释放 MyClass 对象的内存return 0;
}

输出结果:

Doing something

其实,就是用来定义一个指针的类型,比如:

std::shared_ptr<MyClass> myPtr2 = myPtr1;

就是定一个指针对象myPtr2,该指针的指针类型是shared_ptr<MyClass>,shared_ptr<MyClass>表示shared_ptr类型是指向MyClass类型。

C++中,用各种类来定义对象,如果有类型,那么类型就需要放在类后面的尖括号<>中。


使用 std::shared_ptr

#include <iostream>
#include <memory>class MyClass {
public:void doSomething() {std::cout << "Doing something" << std::endl;}
};int main() {std::shared_ptr<MyClass> myPtr1(new MyClass());std::shared_ptr<MyClass> myPtr2 = myPtr1;myPtr1->doSomething(); // 使用 myPtr1 调用成员函数myPtr2->doSomething(); // 使用 myPtr2 调用成员函数// 当 myPtr1 和 myPtr2 都被销毁时,MyClass 对象的内存才会被释放return 0;
}

输出结果

Doing something
Doing something

std::weak_ptr(弱指针)

基本特性

  • 不控制生命周期:观察 shared_ptr 但不增加引用计数

  • 解决循环引用:打破 shared_ptr 的循环引用问题

  • 需转换为 shared_ptr:要访问资源必须先转换为 shared_ptr

  • 无直接访问:不能直接解引用或访问资源

使用示例

#include <memory>void example_weak() {auto shared = std::make_shared<int>(42);std::weak_ptr<int> weak = shared;// 使用前必须转换为 shared_ptrif (auto temp = weak.lock()) {  // 尝试提升为 shared_ptrstd::cout << "Value: " << *temp << std::endl;} else {std::cout << "Resource already freed" << std::endl;}// 检查资源是否有效std::cout << "Expired: " << weak.expired() << std::endl;// 不增加引用计数std::cout << "Shared use count: " << shared.use_count() << std::endl;  // 输出1
}

std::unique_ptr 是 C++11 引入的智能指针,与普通(裸)指针相比有显著差异。以下是它们的详细对比:

内存管理

特性

std::unique_ptr

普通指针

内存释放

自动释放(离开作用域时)

需要手动 delete

所有权语义

明确表达独占所有权

无所有权概念

异常安全

保证资源释放

可能因异常导致内存泄漏

示例对比

// unique_ptr 示例
void unique_example() {std::unique_ptr<MyClass> ptr(new MyClass());// 自动释放,即使抛出异常
}// 普通指针示例
void raw_example() {MyClass* ptr = new MyClass();// 如果这里抛出异常...delete ptr;  // 这行可能不会执行
}

所有权与转移

特性

std::unique_ptr

普通指针

所有权转移

只能通过 std::move 转移

可以随意复制

复制语义

不可复制

可以任意复制

空指针状态

可通过 reset() 或 nullptr 设置

需要手动设置为 nullptr

make_unique和make_shared

在 C++ 中,std::make_unique 和 std::make_shared 是两个用于创建智能指针的函数模板,它们分别用于创建 std::unique_ptr 和 std::shared_ptr 对象。下面详细介绍它们的用法和区别:


1. std::make_unique

功能:创建一个 std::unique_ptr 对象,用于管理动态分配的对象,提供独占所有权。

头文件<memory>(C++14 及以后)。

语法

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args );

参数与返回值

模板参数

T:要创建的对象类型(无需显式指定,可自动推导)。

Args...:构造 T 对象所需参数的类型包(可变参数模板)。

函数参数

args...:传递给 T 构造函数的参数(使用完美转发)。

返回值

std::unique_ptr<T>:指向新创建的 T 对象的独占智能指针。

示例

#include <memory>
#include <string>auto ptr = std::make_unique<int>(42);  // 创建一个管理 int 的 unique_ptr
auto str = std::make_unique<std::string>("hello");  // 创建管理 string 的 unique_ptr

2. std::make_shared

功能:创建一个 std::shared_ptr 对象,用于管理动态分配的对象,允许多个指针共享所有权。

头文件<memory>(C++11 及以后)。

语法

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );

示例

#include <memory>
#include <string>auto ptr = std::make_shared<int>(42);  // 创建一个管理 int 的 shared_ptr
auto str = std::make_shared<std::string>("hello");  // 创建管理 string 的 shared_ptr

C++中shared_ptr指针.get()啥意思

在 C++ 中,std::shared_ptr 是一种智能指针,用于管理动态分配的对象,并在不再需要时自动释放内存。get() 是 std::shared_ptr 的一个成员函数,它返回存储的原始指针(raw pointer)。

一、get() 函数的作用

get() 函数返回 shared_ptr 内部管理的原始指针。这允许你在需要原始指针的场景中使用 shared_ptr 管理的对象,例如:

  1. 与不支持智能指针的 C 风格 API 交互

  2. 需要直接操作原始指针的特殊场景

  3. 检查 shared_ptr 是否为空(当 get() == nullptr 时)。

二、语法和示例

T* get() const noexcept;

示例 1:获取原始指针

#include <iostream>
#include <memory>int main() {std::shared_ptr<int> my_shared = std::make_shared<int>(42);// 使用 get() 获取原始指针int* raw_ptr = my_shared.get();std::cout << *raw_ptr << std::endl;  // 输出: 42std::cout << *my_shared << std::endl; // 等价于: *my_shared.get()return 0;
}

示例 2:与 C 风格 API 交互
假设存在一个接受原始指针的 C 函数:

void process_data(int* data) {if (data) {std::cout << "Processing: " << *data << std::endl;}
}int main() {std::shared_ptr<int> ptr = std::make_shared<int>(100);// 将原始指针传递给C函数process_data(ptr.get()); // 安全:ptr仍管理内存return 0;
}

三、注意事项

  1. 不要手动删除 get() 返回的指针
    shared_ptr 会自动管理内存,手动删除原始指针会导致双重释放(undefined behavior)。

    std::shared_ptr<int> ptr = std::make_shared<int>(42);
    delete ptr.get(); // 错误:双重释放!
    
  2. 避免长期保存 get() 的返回值
    如果 shared_ptr 释放了资源(例如引用计数归零),保存的原始指针会变为悬空指针。

    int* raw = nullptr;
    {std::shared_ptr<int> ptr = std::make_shared<int>(42);raw = ptr.get(); // 暂时有效
    } // ptr 在此处释放资源,raw 变为悬空指针
    
  3. get() 不影响引用计数
    get() 仅返回原始指针,不会增加或减少 shared_ptr 的引用计数。

new和make_shared有啥异同?

在 C++ 中,new 和 std::make_shared 都可用于动态创建对象,但它们在语法、内存管理、性能和安全性上存在显著差异。以下是详细对比:

一、基本语法与功能

1. new 操作符

  • 语法T* ptr = new T(args...);

  • 功能

    • 手动分配内存。

    • 调用构造函数初始化对象。

    • 返回原始指针,需手动管理内存(通过 delete 释放)。

示例

int* num = new int(42);       // 分配一个 int
std::string* str = new std::string("hello"); // 分配一个 string

2. std::make_shared

  • 语法std::shared_ptr<T> ptr = std::make_shared<T>(args...);

  • 功能

    • 一次性分配内存,同时存储对象和引用计数。

    • 返回 std::shared_ptr,自动管理内存(引用计数为 0 时自动释放)。

示例

std::shared_ptr<int> num = std::make_shared<int>(42);
std::shared_ptr<std::string> str = std::make_shared<std::string>("hello");

二、核心区别

特性

new

std::make_shared

内存分配

分两步:先分配对象内存,再初始化。

一次性分配:同时存储对象和引用计数。

内存布局

对象和引用计数(若使用 shared_ptr)分开存储。

对象和引用计数在同一块内存中。

返回类型

原始指针 T*

智能指针 std::shared_ptr<T>

内存管理

需手动 delete,否则导致内存泄漏。

自动管理,引用计数为 0 时自动释放。

异常安全性

可能存在内存泄漏(如构造函数抛异常)。

完全异常安全。

性能

略慢(两次内存分配 + 可能的碎片)。

更快(一次分配 + 缓存局部性更好)。

自定义删除器

支持(通过 shared_ptr 的构造函数)。

不支持(必须使用 new)。

数组支持

支持 new T[size]

C++17 起支持 std::make_shared<T[]>

C++中的智能指针和原始指针占用相同字节的地址空间吗?

在 C++ 里,智能指针和原始指针所占用的地址空间字节数是否相同,取决于具体情况。下面为你详细介绍:

原始指针

原始指针本质上是一个内存地址,在 32 位系统中,它占用 4 字节;在 64 位系统中,则占用 8 字节。例如:

int* ptr = new int(42); // ptr 是一个原始指针

智能指针

智能指针属于类模板,它对原始指针进行了封装,并且会自动管理内存。不同类型的智能指针,其占用空间也不一样:

1. std::unique_ptr

该指针会独占对象的所有权,在大多数情况下,它的大小和原始指针相同。这是因为编译器对其进行了优化,省去了额外的开销。

std::unique_ptr<int> uptr = std::make_unique<int>(42);
// uptr 的大小通常和原始指针相同

2. std::shared_ptr

此指针借助引用计数来管理对象,它的大小一般是原始指针的两倍。这是因为它内部不仅包含一个指向对象的指针,还包含一个指向引用计数的指针。

std::shared_ptr<int> sptr = std::make_shared<int>(42);
// sptr 的大小通常是原始指针的两倍

3. std::weak_ptr

该指针是一种弱引用,依赖于std::shared_ptr的引用计数,它的大小和std::shared_ptr一样。

std::weak_ptr<int> wptr = sptr;
// wptr 的大小通常和 sptr 相同

总结

  • 一般而言,std::unique_ptr占用的空间和原始指针相同。

  • std::shared_ptrstd::weak_ptr占用的空间通常是原始指针的两倍。

不过,这些都和具体的实现相关,你可以通过sizeof操作符来获取实际的大小。

enable_shared_from_this<>啥意思

std::enable_shared_from_this<T> 是 C++ 标准库中的一个模板类,用于让类的对象能够安全地获取指向自身的 std::shared_ptr。这在需要将当前对象的引用传递给外部函数或存储在容器中时特别有用。

一、核心用途:安全获取自身的 shared_ptr

当一个对象被 std::shared_ptr 管理时,直接通过 this 指针返回自身会导致引用计数不一致,可能引发双重释放。enable_shared_from_this 提供了 shared_from_this() 方法,安全地返回一个新的 std::shared_ptr,与现有管理该对象的 shared_ptr 共享引用计数。

错误示例(直接返回 this):

class Bad {
public:std::shared_ptr<Bad> getShared() {return std::shared_ptr<Bad>(this); // 危险!创建独立的 shared_ptr}~Bad() { std::cout << "Bad destroyed" << std::endl; }
};// 错误使用
std::shared_ptr<Bad> p1(new Bad());
std::shared_ptr<Bad> p2 = p1->getShared(); // p1 和 p2 独立管理同一对象
// 程序结束时,该对象会被双重释放(未定义行为)

二、正确用法:继承 enable_shared_from_this

#include <memory>class Good : public std::enable_shared_from_this<Good> {
public:std::shared_ptr<Good> getShared() {return shared_from_this(); // 安全返回共享的 shared_ptr}
};// 正确使用
std::shared_ptr<Good> p1(new Good());
std::shared_ptr<Good> p2 = p1->getShared(); // p1 和 p2 共享同一引用计数
// 程序结束时,对象仅被释放一次

三、使用条件与注意事项

必须通过 shared_ptr 创建对象

错误示例:

Good obj; // 栈上直接创建对象
std::shared_ptr<Good> p = obj.shared_from_this(); // 抛出异常!

对象必须已经被 std::shared_ptr 管理,否则调用 shared_from_this() 会抛出 std::bad_weak_ptr 异常。

模板参数必须是当前类

class Derived : public std::enable_shared_from_this<Derived> {}; // 正确
class Derived : public std::enable_shared_from_this<Base> {};    // 错误!

线程安全

shared_from_this() 本身是线程安全的,但返回的 shared_ptr 在多线程环境下的使用需额外同步。

四、实现原理

enable_shared_from_this 内部维护一个 std::weak_ptr,当对象被 std::shared_ptr 首次创建时,该 weak_ptr 会被自动初始化为指向该对象。shared_from_this() 通过这个 weak_ptr 创建新的 shared_ptr,确保引用计数正确。


http://www.hkcw.cn/article/JRyhTZLXsV.shtml

相关文章

Grafana对接Prometheus数据源

实验环境 在上一章的基础上完成 一、Grafana介绍 Grafana是一个独立的开源项目&#xff0c;它可以对接很多项目&#xff0c;实现各种功能的扩展 使用grafana对接Prometheus可以更好的展示Prometheus的metrics&#xff08;指标&#xff09; 二、Grafana安装 1、安装Grafana…

Flex弹性布局

Flexible Box&#xff08;弹性盒子&#xff09;布局是CSS3中引入的一种强大的布局模式&#xff0c;它能够更高效地处理容器内项目的排列、对齐和空间分配问题&#xff0c;特别适合构建响应式设计。 基本属性 Flex容器&#xff1a;设置了display: flex或display: inline-flex的…

Vue3(watch,watchEffect,标签中ref的使用,TS,props,生命周期)

Vue3&#xff08;watch&#xff0c;watchEffect&#xff0c;标签中ref的使用,TS,props,生命周期&#xff09; watch监视 情况三&#xff1a;监视reactive定义的对象类型的数据 监视reactive定义的对象类型的数据&#xff0c;默认开启深度监视。地址没变&#xff0c;新值和旧…

AI书签管理工具开发全记录(九):用户端页面集成与展示

文章目录 AI书签管理工具开发全记录&#xff08;九&#xff09;&#xff1a;用户端页面集成与展示前言设计思路实现步骤1. 路由配置2. 全局样式设置3. 首页实现4. Vite配置 设计说明1. 部分UI设计2. 响应式布局3. 加载更多功能 效果展示效果展示 AI书签管理工具开发全记录&…

基于IRI-2020模型的电离层特征参量计算与可视化

1. 研究背景 电离层是地球大气层中重要的组成部分&#xff0c;位于地面以上约60-1000公里高度范围内&#xff0c;包含大量自由电子和离子。电离层对无线电通信、卫星导航和空间天气监测等现代技术系统具有重要影响。国际参考电离层模型(IRI)是由国际空间研究委员会(COSPAR)和国…

Chapter 10 Inductive DC–DC Converters

Chapter 10 Inductive DC–DC Converters Design of Power Management Integrated Circuits - Bernhard Wicht 电感型DC-DC用电感做功率转换, 因为有开关, 也被称为开关型DC-DC. 电感型DC-DC相比LDO, 效率更高, 但是不那么"干净". 相比于电容型DC-DC (switched cap…

美股收涨 世纪铝业涨超21% 金属板块领涨市场

美东时间周一,美股三大指数集体收涨。道指涨0.08%,纳指涨0.67%,标普500指数涨0.41%。热门科技股多数上涨,AMD、Meta涨幅超过3%,超微电脑接近3%,博通超过2%,英伟达超过1%。特斯拉和谷歌跌幅超过1%。贵金属和金属原材料板块表现突出,世纪铝业涨幅超过21%,黄金资源超过16…

最大规模换俘 领导人会晤 俄乌再谈判有乾坤

当地时间6月2日,俄乌两国代表团在土耳其伊斯坦布尔就和平解决俄乌冲突举行第二轮直接谈判。谈判历时约一小时,于当地时间16时许结束。5月第一轮谈判后,乌称“毫无成果”,俄称“基本满意”。而本次土耳其总统埃尔多安则表示谈判取得了“重大成果”。俄乌双方就大规模换俘、交…

国足抵达雅加达备战世预赛18强赛 关键战在即

中国男足国家队于6月2日晚抵达印度尼西亚首都雅加达,准备参加5日在那里举行的2026美加墨世界杯亚洲区预选赛18强赛第9轮对阵印尼队的关键比赛。当地时间晚上10点30分,中国队在主教练伊万科维奇的带领下走出雅加达苏加诺-哈达国际机场,并登上大巴前往酒店。伊万科维奇在机场接…

尼日利亚洪灾致200余人遇难 救援工作已停止

尼日利亚洪灾致200余人遇难 救援工作已停止!当地时间6月2日,尼日利亚尼日尔州莫夸地方政府副主席穆萨金布库证实,近期洪灾导致的死亡人数已增至200人,另有500多人失踪。他表示救援工作已经停止,认为失踪人员已无生还可能。目前工作人员正加紧掩埋尸体,以防止疾病蔓延。5月…

②Pybullet干涉检查指令getContactPoints与 getClosestPoints介绍

1、指令格式说明 getContactPoints指令说明 该指令根据最近一次调用stepSimulation指令&#xff0c;返回接触点信息。它的输入参数信息如下所示; 该指令执行后在有干涉的情况下返回信息如下所示。没有干涉时&#xff0c;返回数据为空。 以下为使用示例&#xff0c;该示例为机…

Vue-Leaflet地图组件开发(二)地图核心功能实现

第二篇&#xff1a;Vue-Leaflet地图核心功能实现 1. 地图视窗管理 1.1 视窗状态持久化方案 // 增强版视窗保存功能 const saveLocation async (options {}) > {try {const {saveToLocal true, // 默认保存到本地存储saveToServer false, // 是否保存到服务器notif…

DAY 37 超大力王爱学Python

知识点回顾&#xff1a; 过拟合的判断&#xff1a;测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint&#xff0c;还包含训练状态 早停策略 作业&#xff1a;对信贷数据集训练后保存权重&#xff0c;加载权重后继续训练50轮&#xf…

ubuntu 添加应用到启动菜单

使用Alacarte菜单编辑器 Alacarte是一个简单易用的菜单编辑器&#xff0c;可以帮助用户添加、删除或编辑应用程序的启动菜单项。 安装Alacarte sudo apt-get install alacarte 执行alacarte alacarte 使用说明 选择新建项目进行添加 "Name"栏填自定义的名称&quo…

3,信号与槽机制

这里绘制好了QT控件,现在需要点击控件,出现相应的响应操作 目录 法一 通过图形界面编写: 1,鼠标选中控件 ,右击,点击转到槽 选择相应的触发操作, 在widget.cpp和widget.h,分别自动增加如下代码: 需要手动添加进程头文件 : 查找QProcess如何使用 保存修改,并…

贪心算法应用:最小反馈顶点集问题详解

贪心算法应用&#xff1a;最小反馈顶点集问题详解 1. 问题定义与背景 1.1 反馈顶点集定义 反馈顶点集(Feedback Vertex Set, FVS)是指在一个有向图中&#xff0c;删除该集合中的所有顶点后&#xff0c;图中将不再存在任何有向环。换句话说&#xff0c;反馈顶点集是破坏图中所…

【Doris基础】Apache Doris中的Version概念解析:深入理解数据版本管理机制

目录 引言 1 Version概念基础 1.1 什么是Version 1.2 Version的核心作用 1.3 Version相关核心概念 2 Version工作机制详解 2.1 Version在数据写入流程中的作用 2.2 Version在数据查询流程中的作用 2.3 Version的存储结构 3 Version的进阶特性 3.1 Version的合并与压…

vLLM实战部署embedding、reranker、senseVoice、qwen2.5、qwen3模型

概述 一个开源的支持高并发的高性能大模型推理引擎。在这篇博客有简单提到过。 学习资料&#xff1a;官方文档&#xff0c;官方中文文档&#xff0c;中文文档。 modelscope 通过vLLM&#xff08;或其他平台、框架&#xff09;部署模型前&#xff0c;需要先下载模型。国内一…

Java函数式编程(中)

三、Stream API &#xff08;1&#xff09;创建操作 构建Arrays.stream(数组)根据数组构建Collection.stream()根据集合构建Stream.of(对象1, 对象2, ...)根据对象构建 生成IntStream.range(a, b)根据范围生成&#xff08;含a 不含b&#xff09;IntStream.rangeClosed(a, b)…

16.FreeRTOS

目录 第1章 FreeRTOS 实时操作系统 1.1 认识实时操作系统 1.1.1 裸机的概念 1.1.2 操作系统的概念 1.2 操作系统的分类 1.3 常见的操作系统 1.4 认识实时操作系统 1.4.1 可剥夺型内核与不可剥夺型内核 1.4.2 嵌入式操作系统的作用 1.4.3 嵌入式操作系统的发展 1.4.4…