1.Pair
在C++11中,std::pair是一个模板类,用于将两个值组合成一个单元。它可以将两个不同的类型的值配对在一起,并且提供了对这对值的访问和操作。
std::pair的定义
template<class T1, class T2>
struct pair{
T1 first;
T2 second;
};
一些用法
创建和初始化:
可以使用构造函数或花括号初始化列表来创建和初始化std::pair对象。例如:
std::pair<int, std::string> myPair(42, "Hello");
std::pair<double, bool> anotherPair = {3.14, true};
访问成员
std::pair对象的成员可以通过.first和.second进行访问。例如:
std::pair<int, std::string> myPair(42, "Hello");
int x = myPair.first;
std::string str = myPair.second;
比较和排序
std::pair可以进行比较操作,根据.first和.second的值进行比较。std::pair对象可以在容器中进行排序。例如:
std::pair<int, std::string> pair1(42, "Hello");
std::pair<int, std::string> pair2(10, "World");
bool result = (pair1 < pair2); // 比较操作
std::vector<std::pair<int, std::string>> myVector = {pair1, pair2};
std::sort(myVector.begin(), myVector.end()); // 容器排序
使用范例:
std::pair常常用于返回多个值的函数,以及在需要将两个值作为单个单元传递的情况下。例如:
std::pair<int, std::string> getPerson() {
int age = 25;
std::string name = "John";
return std::make_pair(age, name);
}
std::pair<int, int> divideAndRemainder(int dividend, int divisor) {
int quotient = dividend / divisor;
int remainder = dividend % divisor;
return {quotient, remainder};
}
make_pair()
无需写出类型就能生成一个pair对象,例如:
std::pair<int, char> myPair(42, "Hello");
std::make_pair(42, "Hello");
操作函数
std::pair提供了一种便捷的方式来组合两个值,并且可以在多种场景下使用。它是C++中常用的工具之一,用于简化代码和提高代码的可读性。
2.Tuple
Tuple扩展了pair的概念,拥有任意数量的元素,其中每个类型都可以被指定。
Tuple的定义
template<typename... Types>
class tuple;
Tuple示例
#include <iostream>
#include <tuple>
int main() {
// 创建一个包含整数、字符串和浮点数的元组
std::tuple<int, std::string, double> myTuple(42, "Hello", 3.14);
// 访问元组中的元素
int intValue = std::get<0>(myTuple);
std::string stringValue = std::get<1>(myTuple);
double doubleValue = std::get<2>(myTuple);
// 修改元组中的元素
std::get<0>(myTuple) = 100;
// 使用tie函数将元组的元素解包到变量中
int a;
std::string b;
double c;
std::tie(a, b, c) = myTuple;
//std::tie(a, std::ignore, c) = myTuple;忽略某些元素
// 打印元组的元素
std::cout << "Tuple elements: " << a << ", " << b << ", " << c << std::endl;
//c++11也可直接输出myTuple
return 0;
}
操作函数
std::tuple_size
用于获取std::tuple的大小。
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, std::string, double> myTuple;
std::cout << "Tuple size: " << std::tuple_size<decltype(myTuple)>::value << std::endl;
return 0;
}
输出结果为:Tuple size: 3,表示std::tuple中有三个元素。
std::tuple_element
用于获取std::tuple中指定位置的元素类型。
#include <iostream>
#include <tuple>
int main() {
using MyTuple = std::tuple<int, std::string, double>;
std::tuple_element<1, MyTuple>::type myElement;
std::cout << "Element type: " << typeid(myElement).name() << std::endl;
return 0;
}
输出结果为:Element type: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE,表示std::tuple的第二个元素类型为std::string。
std::tuple_cat
用于将多个std::tuple合并成一个大的std::tuple。
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, std::string> tuple1(42, "Hello");
std::tuple<double> tuple2(3.14);
auto combinedTuple = std::tuple_cat(tuple1, tuple2);
std::cout << "Combined tuple size: " << std::tuple_size<decltype(combinedTuple)>::value << std::endl;
return 0;
}
输出结果为:Combined tuple size: 3,表示将tuple1和tuple2合并后,得到了一个包含三个元素的std::tuple。
pair与tuple
从std::pair到std::tuple的转换
可以使用std::make_tuple函数将std::pair转换为std::tuple。
#include <iostream>
#include <tuple>
int main() {
std::pair<int, double> myPair(42, 3.14);
std::tuple<int, double> myTuple = std::make_tuple(myPair.first, myPair.second);
std::cout << "Tuple elements: " << std::get<0>(myTuple) << ", " << std::get<1>(myTuple) << std::endl;
return 0;
}
在上述示例中,我们有一个std::pair<int, double>类型的对象myPair,我们可以使用std::make_tuple将其转换为std::tuple<int, double>类型的对象myTuple。
从std::tuple到std::pair的转换
可以使用std::get函数将std::tuple的元素提取出来,并使用这些元素创建一个std::pair。
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, double> myTuple(42, 3.14);
std::pair<int, double> myPair = std::make_pair(std::get<0>(myTuple), std::get<1>(myTuple));
std::cout << "Pair elements: " << myPair.first << ", " << myPair.second << std::endl;
return 0;
}
在上述示例中,我们有一个std::tuple<int, double>类型的对象myTuple,我们可以使用std::get函数将其元素提取出来,并使用这些元素创建一个std::pair<int, double>类型的对象myPair。
3.Smart pointer智能指针***
指针是c/c++的重要特性,但使用中常常会出现空悬,多次删除,资源泄露等问题,避免这些问题的一个通常做法是使用智能指针。
自C++11起,标准库提供两大类智能指针:shared_ptr和unique_ptr,而auto_ptr被弃用,它们定义在< memory >内
shared_ptr
std::shared_ptr作为智能指针的一种类型,用于更安全和方便地管理动态分配的内存。std::shared_ptr使用引用计数的方式来跟踪资源的所有者,并在不再需要时自动释放资源。
以下是std::shared_ptr的基本用法和示例:
创建std::shared_ptr对象:
可以使用std::make_shared函数或直接使用std::shared_ptr的构造函数来创建std::shared_ptr对象。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2(new int(100));
std::cout << *ptr1 << std::endl; // 输出:42
std::cout << *ptr2 << std::endl; // 输出:100
return 0;
}
在上述示例中,我们使用std::make_shared创建了一个包含整数值的std::shared_ptr对象ptr1,以及使用std::shared_ptr的构造函数创建了另一个std::shared_ptr对象ptr2。
共享拥有资源:
可以将一个std::shared_ptr赋值给另一个std::shared_ptr,这样它们会共享对同一资源的拥有权。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1;
std::cout << *ptr1 << std::endl; // 输出:42
std::cout << *ptr2 << std::endl; // 输出:42
return 0;
}
在上述示例中,我们将ptr1赋值给ptr2,它们现在都指向同一个整数资源,并且共享对该资源的拥有权。
引用计数和资源释放:
std::shared_ptr使用引用计数来跟踪资源的所有者数量。当最后一个std::shared_ptr析构或被赋予新的值时,引用计数会减少并检查是否需要释放资源。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
std::shared_ptr<int> ptr2 = ptr1;
std::shared_ptr<int> ptr3 = ptr1;
std::cout << *ptr1 << std::endl; // 输出:42
std::cout << *ptr2 << std::endl; // 输出:42
std::cout << *ptr3 << std::endl; // 输出:42
ptr1.reset();
std::cout << std::boolalpha;
std::cout << "ptr1 is nullptr: " << (ptr1 == nullptr) << std::endl; // 输出:true
std::cout << "ptr2 is nullptr: " << (ptr2 == nullptr) << std::endl; // 输出:false
std::cout << "ptr3 is nullptr: " << (ptr3 == nullptr) << std::endl; // 输出:false
return 0;
}
在上述示例中,我们创建了三个std::shared_ptr对象ptr1、ptr2和ptr3,它们都指向同一个整数资源。当ptr1调用reset函数后,它不再拥有资源,引用计数减少并且资源被释放,但ptr2和ptr3仍然有效并拥有资源。
std::shared_ptr还提供了其他有用的成员函数,如get(返回底层指针)、use_count(返回引用计数)、unique(检查是否是唯一的拥有者)等。
使用std::shared_ptr可以有效避免内存泄漏和悬空指针等问题,并提供方便的资源管理机制。然而,要注意避免循环引用问题,因为它可能导致资源无法释放。
析构函数
std::shared_ptr的析构函数是由其模板参数指定的删除器(deleter)来执行的。可以通过提供自定义的删除器来自定义析构行为。
删除器是一个可调用对象,用于在std::shared_ptr的引用计数归零时执行资源的释放。删除器可以是函数指针、函数对象、Lambda表达式或自定义类型的对象。
以下是使用自定义删除器的示例:
#include <iostream>
#include <memory>
// 自定义删除器
struct CustomDeleter {
void operator()(int* ptr) {
std::cout << "Custom deleter is called" << std::endl;
delete ptr;
}
};
int main() {
std::shared_ptr<int> ptr(new int(42), CustomDeleter());
return 0;
}
在上述示例中,我们定义了一个名为CustomDeleter的结构体,其中重载了圆括号运算符,以实现自定义的删除行为。在main函数中,我们使用std::shared_ptr的构造函数来创建一个std::shared_ptr对象ptr,并将自定义删除器作为参数传递。
当std::shared_ptr的引用计数归零时,会调用自定义删除器的圆括号运算符来释放资源,并执行我们定义的自定义删除行为。在这个示例中,自定义删除器会输出一条消息,并删除指向整数的指针。
注意,当提供自定义删除器时,需要确保删除器与指针类型兼容,并遵循适当的资源释放规则。通过自定义删除器,可以实现更灵活的资源管理和析构行为,以满足特定的需求。
其他操作
weak_ptr
C++11引入了std::weak_ptr作为一种智能指针类型,用于解决std::shared_ptr可能导致的循环引用问题。std::weak_ptr允许对由std::shared_ptr管理的对象进行弱引用,而不会增加引用计数,也不会阻止对象的销毁。
以下是std::weak_ptr的基本用法和示例:
创建std::weak_ptr对象
可以通过将std::shared_ptr对象转换为std::weak_ptr来创建std::weak_ptr对象。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// 输出:42
if (auto lockedPtr = weakPtr.lock()) {
std::cout << *lockedPtr << std::endl;
} else {
std::cout << "Resource has been released" << std::endl;
}
return 0;
}
在上述示例中,我们创建了一个std::shared_ptr对象sharedPtr来管理一个整数资源,并通过将其转换为std::weak_ptr创建了一个std::weak_ptr对象weakPtr。注意,转换为std::weak_ptr不会增加资源的引用计数。
检查std::weak_ptr是否有效并访问资源
可以使用lock()函数来检查std::weak_ptr是否有效,并获取对资源的共享访问。
#include <iostream>
#include <memory>
int main() {
std::weak_ptr<int> weakPtr;
{
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
weakPtr = sharedPtr;
// 输出:42
if (auto lockedPtr = weakPtr.lock()) {
std::cout << *lockedPtr << std::endl;
} else {
std::cout << "Resource has been released" << std::endl;
}
}
// 输出:"Resource has been released"
if (auto lockedPtr = weakPtr.lock()) {
std::cout << *lockedPtr << std::endl;
} else {
std::cout << "Resource has been released" << std::endl;
}
return 0;
}
在上述示例中,我们在作用域中创建了一个std::shared_ptr对象sharedPtr,并将其转换为std::weak_ptr对象weakPtr。在作用域结束后,sharedPtr被销毁,资源被释放。在后续的代码中,我们通过调用lock()函数检查weakPtr是否有效,并访问资源。如果weakPtr有效,lock()函数将返回一个有效的std::shared_ptr对象,否则返回一个空指针。
查看资源
expired()
用于检查std::weak_ptr是否过期(即指向的资源是否已经被释放)。如果资源已经被释放,则返回true;否则返回false。
use_count()
用于获取与std::weak_ptr共享相同资源的有效std::shared_ptr对象的数量。
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// 输出:1
std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
if (weakPtr.expired()) {
std::cout << "Resource has been released" << std::endl;
} else {
// 输出:42
std::cout << "Value: " << *weakPtr.lock() << std::endl;
}
sharedPtr.reset();
// 输出:0
std::cout << "use_count: " << weakPtr.use_count() << std::endl;
if (weakPtr.expired()) {
std::cout << "Resource has been released" << std::endl;
} else {
std::cout << "Value: " << *weakPtr.lock() << std::endl;
}
return 0;
}
在上述示例中,我们创建了一个std::shared_ptr对象sharedPtr来管理一个整数资源,并通过将其转换为std::weak_ptr对象weakPtr来创建一个弱引用。我们使用use_count()函数来获取与sharedPtr共享相同资源的有效std::shared_ptr对象的数量。在if语句中,我们使用expired()函数检查weakPtr是否过期,然后使用lock()函数获取有效的std::shared_ptr对象并输出其值。在后续的代码中,我们通过调用reset()函数将sharedPtr置空,释放资源,并检查weakPtr是否过期。
需要注意的是,由于std::weak_ptr不增加引用计数,所以调用use_count()函数返回的是与其共享资源的有效std::shared_ptr对象的数量。
其他操作
通过使用std::weak_ptr,我们可以避免std::shared_ptr可能导致的循环引用问题,并更灵活地管理资源的生命周期。
unique_ptr
std::unique_ptr是独占所有权的智能指针,意味着同一时间只能有一个std::unique_ptr拥有指针所指向的对象。当unique_ptr被销毁,其所指向的对象也会自动被销毁。
以下是std::unique_ptr的基本用法和示例:
创建std::unique_ptr对象
可以使用std::make_unique函数或直接使用std::unique_ptr的构造函数来创建std::unique_ptr对象。
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);
std::unique_ptr<int> uniquePtr2(new int(100));
// 输出:42
std::cout << *uniquePtr1 << std::endl;
// 输出:100
std::cout << *uniquePtr2 << std::endl;
return 0;
}
在上述示例中,我们使用std::make_unique函数和构造函数分别创建了两个std::unique_ptr对象uniquePtr1和uniquePtr2来管理两个整数资源。注意,std::make_unique是C++14引入的函数,如果你使用的是C++11,可以直接使用std::unique_ptr的构造函数。
执行所有权的转移
std::unique_ptr具有独占所有权的特性,可以通过移动语义将所有权从一个std::unique_ptr转移到另一个std::unique_ptr。
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> uniquePtr1 = std::make_unique<int>(42);
std::unique_ptr<int> uniquePtr2;
uniquePtr2 = std::move(uniquePtr1);
// 输出:42
std::cout << *uniquePtr2 << std::endl;
return 0;
}
在上述示例中,我们通过使用std::move将uniquePtr1的所有权转移到uniquePtr2。这样,uniquePtr2现在拥有原始资源,并且uniquePtr1不再拥有资源。
使用自定义删除器
可以通过提供自定义删除器来指定std::unique_ptr在释放资源时的行为。删除器是一个函数对象或函数指针,用于定义资源释放的方式。
#include <iostream>
#include <memory>
struct CustomDeleter {
void operator()(int* ptr) {
std::cout << "Deleting resource: " << *ptr << std::endl;
delete ptr;
}
};
int main() {
std::unique_ptr<int, CustomDeleter> uniquePtr(new int(42));
// 输出:42
std::cout << *uniquePtr << std::endl;
return 0;
}
在上述示例中,我们创建了一个带有自定义删除器CustomDeleter的std::unique_ptr对象uniquePtr。当uniquePtr被销毁时,自定义删除器将被调用,并负责释放资源。
std::unique_ptr提供了一种轻量级的智能指针,适用于管理单个所有权的对象,提供了高效的内存管理和安全的资源释放。
关于array
C++删除array需要使用delete[],由于C++无法区分pointer指向的是单个对象还是array,因此指针自动删除会出错,标准库对此提供了特殊版本。
在C++11中,引入了std::unique_ptr的数组版本,即std::unique_ptr<T[]>,用于管理动态分配的数组对象。
下面是std::unique_ptr管理动态数组的示例:
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int[]> uniquePtr(new int[5]);
for (int i = 0; i < 5; i++) {
uniquePtr[i] = i + 1;
}
for (int i = 0; i < 5; i++) {
std::cout << uniquePtr[i] << " ";
}
// 输出:1 2 3 4 5
std::cout << std::endl;
return 0;
}
在上述示例中,我们使用std::unique_ptr<int[]>创建了一个std::unique_ptr对象uniquePtr来管理一个包含5个整数的动态数组。通过使用数组下标运算符[],我们可以访问和修改数组中的元素。当uniquePtr被销毁时,它会自动释放动态数组所占用的内存。
需要注意的是,std::unique_ptr<T[]>只适用于管理通过new T[]动态分配的数组,而不是指向已存在的数组,这个版本不提供操作符’*‘和’->'。在使用std::unique_ptr<T[]>时,不需要手动调用delete[]释放内存,std::unique_ptr会自动处理内存的释放。另外,这一版本不支持不同类型直接的转换,不允许指向派生元素类型。
其他操作
auto_ptr
std::auto_ptr是C++98标准引入的智能指针,用于管理动态分配的对象。然而,自从C++11起,std::auto_ptr已经被废弃,不推荐在新代码中使用,因为它存在一些缺陷和不安全的行为。
以下是一些关于std::auto_ptr的重要注意事项:
所有权的转移
与std::unique_ptr不同,std::auto_ptr支持所有权的转移,即可以将资源的所有权从一个std::auto_ptr转移到另一个std::auto_ptr。这意味着在转移所有权后,原始的std::auto_ptr将不再拥有资源。
不支持数组
std::auto_ptr只适用于管理单个对象,而不支持管理动态分配的数组。
删除器的限制
std::auto_ptr只支持使用默认的删除器,无法自定义删除器。这意味着在销毁std::auto_ptr时,只会调用delete来释放资源。
不安全的拷贝语义
std::auto_ptr的拷贝语义存在问题,它使用的是移动语义而不是传统的拷贝语义。这导致在拷贝后,原始的std::auto_ptr会失去对资源的所有权,可能导致资源的重复释放。
因此,建议使用C++11引入的更安全和更强大的智能指针类型,如std::unique_ptr用于独占所有权的情况,std::shared_ptr用于共享所有权的情况,以及std::weak_ptr用于解决循环引用问题。这些智能指针类型提供了更好的语义和更强的类型检查,能够更好地管理资源并提供更好的内存安全性。
补充
内存开销
相比于裸指针,智能指针通常会引入一定的内存开销。智能指针对象通常包含引用计数等额外的数据成员,这可能会占用更多的内存。虽然这个开销在大多数情况下可以忽略不计,但对于资源非常有限的嵌入式系统等特殊环境下,需要仔细考虑智能指针的使用。
不适合某些情况
智能指针并不是适用于所有情况的通用解决方案。在某些特定的应用场景下,例如与C接口进行交互、处理外部资源等,可能需要手动管理资源,而不适合使用智能指针。
语义差异和注意事项
不同类型的智能指针有不同的语义和行为,需要理解和注意其使用方式。例如,std::shared_ptr的共享所有权可能带来额外的开销和线程安全的问题,而std::unique_ptr则适用于独占所有权的场景。此外,智能指针的使用还需要注意循环引用、空指针检查、潜在的性能影响等问题。
虽然智能指针有其缺陷和注意事项,但它们在大多数情况下提供了方便、安全和可靠的资源管理方式。使用智能指针时,需要理解其语义和行为,并结合具体的应用场景进行选择和使用。