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
容器(
vector
、map
)、算法(sort
)、迭代器✅ 是
I/O 流
iostream
、fstream
❌ 否
字符串类
std::string
❌ 否(但设计受 STL 影响)
智能指针
std::shared_ptr
、std::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() { // 声明一个存储整数的 vector std::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; // 清空 vector numbers.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) 中的一种关联容器,它存储唯一元素,并按照特定排序准则自动排序。基本特性
唯一性:所有元素都是唯一的(不允许重复)
自动排序:元素总是按照指定的排序准则自动排序
不可修改元素:元素是 const 的,不能直接修改(必须先删除再插入新值)
基于红黑树:通常实现为平衡二叉搜索树(红黑树)
头文件:
#include <set>
创建和初始化
#include <iostream> #include <set> int main() { // 空set std::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, 8 return 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; } // 遍历set for (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 算法一起使用。
迭代器主要分为以下几类:
- 输入迭代器(Input Iterator):只能进行单次读取操作,不能进行写入操作。
- 输出迭代器(Output Iterator):只能进行单次写入操作,不能进行读取操作。
- 正向迭代器(Forward Iterator):可以进行读取和写入操作,并且可以向前移动。
- 双向迭代器(Bidirectional Iterator):除了可以进行正向迭代器的所有操作外,还可以向后移动。
- 随机访问迭代器(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}; // 使用迭代器遍历 vector for (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::vector
、std::array
),迭代器可能直接用原生指针实现。类对象:对于非连续存储的容器(如
std::list
、std::map
),迭代器是一个类对象,内部封装了指向节点的指针,并通过重载运算符(如*
、++
)来模拟指针行为。2. 迭代器与指针的关系
(1) 行为类似指针
迭代器的接口设计模仿了指针的行为:
解引用(
*it
):获取元素的引用(或值)。箭头操作符(
it->member
):访问元素的成员(等价于(*it).member
)。自增 / 自减(
++it
、--it
):移动到下一个 / 前一个元素。(2) 但不一定是指针
连续容器(如
vector
):迭代器可能直接封装原生指针,性能与指针无异。
std::vector<int> vec = {1, 2, 3}; auto it = vec.begin(); // 可能是 int* 类型
非连续容器(如
list
、map
):迭代器是类对象,内部维护节点指针,并通过运算符重载实现遍历逻辑。
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)是一种抽象的概念,用于遍历容器(如
vector
、list
、map
等)中的元素。不同类型的迭代器支持不同的操作接口,以下是迭代器的常用接口及其分类: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; // 输出 5 return 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_ptr if (auto temp = weak.lock()) { // 尝试提升为 shared_ptr std::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
管理的对象,例如:
与不支持智能指针的 C 风格 API 交互
需要直接操作原始指针的特殊场景
检查
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; // 输出: 42 std::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; }
三、注意事项
不要手动删除
get()
返回的指针
shared_ptr
会自动管理内存,手动删除原始指针会导致双重释放(undefined behavior)。std::shared_ptr<int> ptr = std::make_shared<int>(42); delete ptr.get(); // 错误:双重释放!
避免长期保存
get()
的返回值
如果shared_ptr
释放了资源(例如引用计数归零),保存的原始指针会变为悬空指针。int* raw = nullptr; { std::shared_ptr<int> ptr = std::make_shared<int>(42); raw = ptr.get(); // 暂时有效 } // ptr 在此处释放资源,raw 变为悬空指针
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_ptr
和std::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
,确保引用计数正确。