C++学习 part1
文章目录
- 参考
- 可调用对象
- std::function 包装器
- std::bind 绑定器
- lambda
- using与模版
- 右值引用
- std::move
- std::forward
- &&的特性(别的含义)
- std::initializer_list<T> 任意长度的数据的初始化
- auto
- decltype
- 智能指针
- std::shared_ptr
- 初始化
- 获取原始指针和解引用
- 指定删除器
- std::unique_ptr
- std::weak_ptr
- 返回管理this的shared_ptr
- 解决循环引用导致内存泄漏问题
- constexpr
- 常量表达式函数
- 原始字面量
- 异常
- 强类型枚举
- std::any
- std::variant
- std::optional<type> 判断文件是否打开
- std::tuple 多返回值/结构化绑定
参考
- 本文仅是学习笔记,更多详细请看原链接。
- 大丙,可挑选并行内容 ing+按标签选择
- c++17好用的新特性总结 ing
可调用对象
详解参考原链接
- 可调用对象有如下几种定义:可调用类型
- 函数和函数指针
- 仿函数
- lambda表达式
- std::bind和std::function创建的对象
- 因为不同可调用类型,可能具有相同的调用形式,std::bind和std::function统一了可调用对象的各种操作。
std::function 包装器
#include <functional>
std::function<返回值类型(参数类型列表)> diy_name = 可调用对象;
- 非静态的类的成员函数指针和类成员变量指针不能包装。
#include <iostream>
#include <functional>
using namespace std;int add(int a, int b)
{cout << a << " + " << b << " = " << a + b << endl;return a + b;
}class T1
{
public:static int sub(int a, int b){cout << a << " - " << b << " = " << a - b << endl;return a - b;}
};class T2
{
public:int operator()(int a, int b){cout << a << " * " << b << " = " << a * b << endl;return a * b;}
};int main(void)
{// 绑定一个普通函数function<int(int, int)> f1 = add;// 绑定一个静态类成员函数function<int(int, int)> f2 = T1::sub;// 绑定一个仿函数T2 t;function<int(int, int)> f3 = t;// 函数调用f1(9, 3);f2(9, 3);f3(9, 3);return 0;
}
- 可取代函数指针的作用,作为回调函数使用
#include <iostream>
#include <functional>
using namespace std;class A
{
public:// 构造函数参数是一个包装器对象A(const function<void()>& f) : callback(f){}void notify(){callback(); // 调用通过构造函数得到的函数指针}
private:function<void()> callback;
};class B
{
public:void operator()(){cout << "我是要成为海贼王的男人!!!" << endl;}
};
int main(void)
{B b;A a(b); // 仿函数通过包装器对象进行包装a.notify();return 0;
}
std::bind 绑定器
- 将可调用对象与其参数一起绑定成一个仿函数。
- 绑定后的结果可以使用std::function进行保存。
- std::function是不能实现对类成员函数指针或者类成员指针的包装的,但是可通过绑定器std::bind的配合实现。
- 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数,甚至可以重排参数顺序,绑定引用参数。
- 但需要注意iostream对象不可以被拷贝,如果希望传递给bind一个对象(引用要绑定对象),而又不拷贝,需要使用std::ref(包装成按引用传递的值)或者std::cref(包装按const引用传递的值)来包装。
- 占位符_n都在std:: placeholders空间。例如
std:: placeholders::_1,代表这个位置将在函数调用时被传入的第一个参数所替代,以此类推。
- 使用方法:
// 绑定非类成员函数/变量
auto f = std::bind(可调用对象地址, 绑定的参数/占位符);
// 绑定类成员函/变量
auto f = std::bind(类函数/成员的地址, 类实例对象地址, 绑定的参数/占位符);
- 使用方法:
#include <iostream>
#include <functional>
using namespace std;void callFunc(int x, const function<void(int)>& f)
{if (x % 2 == 0){f(x);}
}void output(int x)
{cout << x << " ";
}int main(void)
{// 使用绑定器绑定可调用对象和参数auto f1 = bind(output, placeholders::_1);for (int i = 0; i < 10; ++i){callFunc(i, f1);}cout << endl;return 0;
}
- 绑定参数
#include <iostream>
#include <functional>
using namespace std;void output(int x, int y)
{cout << x << " " << y << endl;
}int main(void)
{// 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数bind(output, 1, 2)();bind(output, placeholders::_1, 2)(10);bind(output, 2, placeholders::_1)(10);// error, 调用时没有第二个参数// bind(output, 2, placeholders::_2)(10);// 调用时第一个参数10被吞掉了,没有被使用bind(output, 2, placeholders::_2)(10, 20);bind(output, placeholders::_1, placeholders::_2)(10, 20);bind(output, placeholders::_2, placeholders::_1)(10, 20);return 0;
}// 结果
1 2 // bind(output, 1, 2)();
10 2 // bind(output, placeholders::_1, 2)(10);
2 10 // bind(output, 2, placeholders::_1)(10);
2 20 // bind(output, 2, placeholders::_2)(10, 20);
10 20 // bind(output, placeholders::_1, placeholders::_2)(10, 20);
20 10 // bind(output, placeholders::_2, placeholders::_1)(10, 20);
- 包装类成员函数指针和类成员指针:
#include <iostream>
#include <functional>
using namespace std;class Test
{
public:void output(int x, int y){cout << "x: " << x << ", y: " << y << endl;}int m_number = 100;
};int main(void)
{Test t;// 绑定类成员函数function<void(int, int)> f1 = bind(&Test::output, &t, placeholders::_1, placeholders::_2);// 绑定类成员变量(公共)function<int&(void)> f2 = bind(&Test::m_number, &t);// 调用f1(520, 1314);f2() = 2333;cout << "t.m_number: " << t.m_number << endl;return 0;
}
lambda
原链接
- 定义匿名函数,并且可以捕获一定范围内的变量。
- 没有捕获任何变量的lambda表达式,还可以转换成一个普通的函数指针。
[capture](params) opt -> ret {body;};
- 其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体。
- 可以忽略参数列表、返回类型和函数选项,但必须永远包含捕获列表和函数体。
- opt:
- mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
- exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
- capture:
- [] 不捕获
- [&] 按引用捕获
- [=] 按值捕获,且在函数体中只读,除非使用mutable,则可以修改
- 因为lambda会被当做一个带operator()的类,即仿函数;
- 且默认operator是const的成员函数。
- [=,&foo]
- [bar] 按值捕获 bar 变量, 同时不捕获其他变量
- [&bar] 按引用捕获 bar 变量, 同时不捕获其他变量
- [this] 捕获当前类中的this指针
- 如果已经使用了 & 或者 =, 默认添加此选项
using与模版
- typedef不支持给模版定义别名,需要用类包装。
- 可以通过使用using来为一个模板定义别名:
template <typename T>
using mymap = map<int, T>;mymap<string> m;
m.insert(make_pair(1,"dankokoko"));
右值引用
- 右值没名字,不可用&取地址,分两种:
- 纯右值:非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和 lambda 表达式等。
- 将亡值:与右值引用相关的表达式,比如,T&&类型函数的返回值、 std::move 的返回值等。
- 右值引用就是对一个右值进行引用的类型。因为右值是匿名的,所以我们只能通过引用的方式找到它,必须初始化。
int&& b = 520;
- 不能使用左值初始化一个右值引用类型,需要借助std::move。
- 只能将右值初始化给常量左值引用,右值引用自身也是一个左值。
- 右值引用具有移动语义,移动语义可以将资源(堆、系统对象等)通过浅拷贝从一个对象转移到另一个对象(而不是完全拷贝一份,例如对于指针,只复制指针本身(即内存地址),而不复制指针所指向的那块内存,之后新旧指针都指向同一块内存地址),这样就能减少不必要的临时对象的创建、拷贝以及销毁,可以大幅提高C++应用程序的性能。
- 例如临时容器很大时,可以使用std::move
- 例如定义移动构造函数:
T::T(T&& another)
- 例如定义移动赋值运算符:
T&& T::operator=(T&& rhs)
#include <iostream>
using namespace std;class Test
{
public:Test() : m_num(new int(100)){cout << "construct: my name is jerry" << endl;}Test(const Test& a) : m_num(new int(*a.m_num)){cout << "copy construct: my name is tom" << endl;}// 添加移动构造函数Test(Test&& a) : m_num(a.m_num){a.m_num = nullptr;//新指针已经指向了想要内存的地址了,旧指针清空保存的内存地址了.cout << "move construct: my name is sunny" << endl;}~Test(){delete m_num;cout << "destruct Test class ..." << endl;}int* m_num;
};Test getObj()
{Test t;return t;
}int main()
{Test t = getObj();cout << "t.m_num: " << *t.m_num << endl;return 0;
};
std::move
- 使用std::move方法可以将左值转换为右值。
- 使用这个函数并不能移动任何东西,而是和移动构造函数一样都具有移动语义,将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
- 使用:
class Test
{
public:Test(){}......
};
int main()
{Test t;Test && v1 = t; // errorTest && v2 = move(t); // okreturn 0;
}// 临时容器很大时:
list<string> ls;
ls.push_back("hello");
ls.push_back("world");
......
list<string> ls1 = ls; // 需要拷贝, 效率低
list<string> ls2 = move(ls);
std::forward
- 右值引用自身是一个左值,当一个右值引用作为函数参数的形参时,在函数内部转发该参数给内部其他函数时,它就变成一个左值了。
- 可以使用std::forward实现完美转发,即按照最开始传入的是什么类型,就是什么类型。
- 使用:
std::forward<T>(t);
#include <iostream>
using namespace std;template<typename T>
void printValue(T& t)
{cout << "l-value: " << t << endl;
}template<typename T>
void printValue(T&& t)
{cout << "r-value: " << t << endl;
}template<typename T>
void testForward(T && v)
{printValue(v);printValue(move(v));printValue(forward<T>(v));cout << endl;
}int main()
{testForward(520);int num = 1314;testForward(num);testForward(forward<int>(num));testForward(forward<int&>(num));testForward(forward<int&&>(num));return 0;
}// 结果
l-value: 520
r-value: 520
r-value: 520l-value: 1314
r-value: 1314
l-value: 1314l-value: 1314
r-value: 1314
r-value: 1314l-value: 1314
r-value: 1314
l-value: 1314l-value: 1314
r-value: 1314
r-value: 1314
&&的特性(别的含义)
- 模板参数中指定为T&&,自动类型推导指定为auto &&,这两种情况都是未定的引用类型。
- 但是 const T&&表示一个右值引用。
- 通过右值推导 T&& 或者 auto&& 得到的是一个右值引用类型。
- 通过非右值(右值引用、左值、左值引用、常量右值引用、常量左值引用)推导 T&& 或者 auto&& 得到的是一个左值引用类型。
template<typename T>
void f(T&& param);
void f1(const T&& param);
f(10);
int x = 10;
f(x);
f1(x); // error, x是左值
f1(10); // ok, 10是右值
decltype(x)&& v3 = y; // error,相当于int&& v3 = y;
std::initializer_list 任意长度的数据的初始化
- 可以接收任意长度的初始化列表,但是要求元素必须是同种类型T
- 内部有三个成员接口:
size(), begin(), end()
- 对象只能被整体初始化或者赋值。
- 使用:作为参数类型
#include <iostream>
#include <string>
using namespace std;void traversal(std::initializer_list<int> a)
{for (auto it = a.begin(); it != a.end(); ++it){cout << *it << " ";}cout << endl;
}int main(void)
{initializer_list<int> list;cout << "current list size: " << list.size() << endl;traversal(list);list = { 1,2,3,4,5,6,7,8,9,0 };cout << "current list size: " << list.size() << endl;traversal(list);cout << endl;list = { 1,3,5,7,9 };cout << "current list size: " << list.size() << endl;traversal(list);cout << endl;////////////////////////////////////////////////////////////////// 直接通过初始化列表传递数据 //////////////////////////////////////////////////////////////////traversal({ 2, 4, 6, 8, 0 });cout << endl;traversal({ 11,12,13,14,15,16 });cout << endl;return 0;
}
auto
- 不是实际的数据类型,是占位符
- 使用auto声明的变量必须要进行初始化,在编译时将auto占位符替换为真正的类型。
auto 变量名 = 变量值;
- 限制:
- 不能作为函数参数使用。
- 不能使用auto关键字定义数组
- 无法使用auto推导出模板参数,
Test<auto> t1 = t; // error, 无法推导出模板类型
- 不能用于类的非静态成员变量的初始化
class Test
{auto v1 = 0; // errorstatic auto v2 = 0; // error,类的静态非常量成员不允许在类内部直接初始化static const auto v3 = 10; // ok
}
- 当变量不是指针或者引用类型时,推导的结果中不会保留const、volatile关键字,反之则保留。
- 使用:
int temp = 110;
auto *a = &temp; //int
auto b = &temp; // int*
auto &c = temp; //int
auto d = temp; //intint tmp = 250;
const auto a1 = tmp; //int
auto a2 = a1; // int
const auto &a3 = tmp; // int
auto &a4 = a3; // const int
decltype
- 不需要或者不能定义变量,得到某种类型
decltype (表达式)
- decltype的推导是在编译期完成的,它只是用于表达式类型的推导,并不会计算表达式的值。
- 使用:
int a = 10;
decltype(a) b = 99; // b -> int
decltype(a+3.14) c = 52.13; // c -> double
decltype(a+b*c) d = 520.1314; // d -> double#include <iostream>
#include <string>
using namespace std;class Test
{
public:string text;static const int value = 110;
};int main()
{int x = 99;const int &y = x;decltype(x) a = x; // intdecltype(y) b = x; // const int &decltype(Test::value) c = 0;// const intTest t;decltype(t.text) d = "hello, world";//stringreturn 0;
}
- 表达式是函数调用,使用decltype推导出的类型和函数返回值一致
- 但是注意返回类型如果是纯右值,则只有返回类类型时才会保留const和volatile
class Test{...};
//函数声明
int func_int(); // 返回值为 int
int& func_int_r(); // 返回值为 int&
int&& func_int_rr(); // 返回值为 int&&const int func_cint(); // 返回值为 const int
const int& func_cint_r(); // 返回值为 const int&
const int&& func_cint_rr(); // 返回值为 const int&&const Test func_ctest(); // 返回值为 const Test//decltype类型推导
int n = 100;
decltype(func_int()) a = 0; // int
decltype(func_int_r()) b = n; // int&
decltype(func_int_rr()) c = 0; // int&&
decltype(func_cint()) d = 0; // int,因为返回的是纯右值,只有类类型才能带const和volatile
decltype(func_cint_r()) e = n; // const int&
decltype(func_cint_rr()) f = 0; // const int&&
decltype(func_ctest()) g = Test();//const Test
- 表达式是一个左值,或者被括号( )包围,使用 decltype推导出的是表达式类型的引用,且如果有const、volatile限定符则不能忽略
#include <iostream>
#include <vector>
using namespace std;class Test
{
public:int num;
};int main() {const Test obj;//带有括号的表达式decltype(obj.num) a = 0;// intdecltype((obj.num)) b = a;// const int&//加法表达式int n = 0, m = 0;decltype(n + m) c = 0;//intdecltype(n = n + m) d = n;//int&return 0;
}
- 配合auto+返回类型后置:
auto func(参数1, 参数2, ...) -> decltype(参数表达式)
#include <iostream>
using namespace std;
// R->返回值类型, T->参数1类型, U->参数2类型
template <typename T, typename U>
// 返回类型后置语法
auto add(T t, U u) -> decltype(t+u)
{return t + u;
}int main()
{int x = 520;double y = 13.14;// auto z = add<int, double>(x, y);auto z = add(x, y); // 简化之后的写法cout << "z: " << z << endl;return 0;
}//或者
int& test(int &i)
{return i;
}double test(double &d)
{d = d + 100;return d;
}template <typename T>
// 返回类型后置语法
auto myFunc(T& t) -> decltype(test(t))
{return test(t);
}
- 可以配合泛型编程,推导迭代器类型:
#include <list>
#include <iostream>
using namespace std;template <class T>
class Container
{
public:void func(T& c){for (m_it = c.begin(); m_it != c.end(); ++m_it){cout << *m_it << " ";}cout << endl;}
private://当 T 是一个 非 const 容器得到一个 T::iterator,//当 T 是一个 const 容器时就会得到一个 T::const_iteratordecltype(T().begin()) m_it;
};int main()
{const list<int> lst{ 1,2,3,4,5,6,7,8,9 };Container<const list<int>> obj;obj.func(lst);return 0;
}
智能指针
- 智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。
- 智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。
#include<memory>
std::shared_ptr
初始化
- 共享智能指针
- 初始化:通过构造函数、std::make_shared辅助函数以及reset方法
- 构造函数(默认,拷贝,移动)
- 如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数不会+1.
- 如果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;
- 如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,因此内存的引用计数不会变化。
#include <iostream>
#include <memory>
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存shared_ptr<int> ptr1(new int(520));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;//1// 使用智能指针管理一块字符数组对应的堆内存shared_ptr<char> ptr2(new char[12]);cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;//1// 创建智能指针对象, 不管理任何内存shared_ptr<int> ptr3;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;// 0// 创建智能指针对象, 初始化为空shared_ptr<int> ptr4(nullptr);cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;// 0return 0;
}
#include <iostream>
#include <memory>
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptr<int> ptr1(new int(520));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;//1//调用拷贝构造函数shared_ptr<int> ptr2(ptr1);cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;//2shared_ptr<int> ptr3 = ptr1;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;//3//调用移动构造函数shared_ptr<int> ptr4(std::move(ptr1));cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;//3std::shared_ptr<int> ptr5 = std::move(ptr2);cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;// 3return 0;
}
- 不要使用一个原始指针初始化多个shared_ptr,会运行出错:
int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p); // error, 编译不会报错, 运行会出错
- std::make_shared初始化:
Args&&... args
为要初始化的数据;如果T是类,则这里是构造函数参数
template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
- std::shared_ptr::reset初始化:
- 对于一个未初始化的共享智能指针,可以通过reset方法来初始化;
- 当智能指针中有值的时候,调用reset会使引用计数减1。
- ptr:指向要取得所有权的对象的指针
- d:删除器
- alloc :分配器
void reset() noexcept;template< class Y >
void reset( Y* ptr );template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );
#include <iostream>
#include <string>
#include <memory>
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptr<int> ptr1 = make_shared<int>(520);shared_ptr<int> ptr2 = ptr1;shared_ptr<int> ptr3 = ptr1;shared_ptr<int> ptr4 = ptr1;cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;ptr4.reset();cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;shared_ptr<int> ptr5;ptr5.reset(new int(250));cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;return 0;
}//结果
ptr1管理的内存引用计数: 4
ptr2管理的内存引用计数: 4
ptr3管理的内存引用计数: 4
ptr4管理的内存引用计数: 4ptr1管理的内存引用计数: 3
ptr2管理的内存引用计数: 3
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 0ptr5管理的内存引用计数: 1
- 查看当前有多少个智能指针同时管理着这块内存:
long use_count() const noexcept;
获取原始指针和解引用
T* get() const noexcept;
#include <iostream>
#include <string>
#include <memory>
using namespace std;int main()
{int len = 128;shared_ptr<char> ptr(new char[len]);// 得到指针的原始地址char* add = ptr.get();memset(add, 0, len);strcpy_s(add,40,"我是要成为海贼王的男人!!!");cout << "string: " << add << endl;shared_ptr<int> p(new int);*p = 100;cout << *p.get() << " " << *p << endl;return 0;
}
指定删除器
- 当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。
- 在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数。
- C++11中使用shared_ptr管理动态数组时,需要指定删除器,因为默认删除器是 delete ptr,而不是 delete[] ptr。
- 也可以使用
std::default_delete<T>()
函数作为删除器,也是通过调用delete来实现的。
- 也可以使用
- 使用:
#include <iostream>
#include <memory>
using namespace std;// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{delete p;cout << "int 型内存被释放了...";
}int main()
{shared_ptr<int> ptr(new int(250), deleteIntPtr);//可以是lambdashared_ptr<int> ptr(new int(250), [](int* p) {delete p; });// c++ 11/14shared_ptr<int> ptr(new int[10], [](int* p) {delete[]p; });shared_ptr<int> ptr(new int[10], default_delete<int[]>());// c++ 17std::shared_ptr<int[]> sp(new int[10]); // 无需指定删除器!return 0;
}
std::unique_ptr
- 独占智能指针
- 唯一且独占地拥有其指向的原始指针(即内存地址的所有权)。
- 可通过构造函数初始化
unique_ptr<int> ptr1(new int(10));
- 不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr
- std::unique_ptr不允许复制
- 可以通过函数返回给其他的std::unique_ptr,还可以通过std::move来转译给其他的std::unique_ptr,但是此时原始指针的所有权就被转移了,还是独占
- 使用reset方法解除对原始内存的管理,或者初始化,或者重新指定独占智能指针管理的原始内存
void reset( pointer ptr = pointer() ) noexcept;
- 获得原始地址:
int main()
{unique_ptr<int> ptr1(new int(10));unique_ptr<int> ptr2 = move(ptr1);ptr2.reset(new int(250));cout << *ptr2.get() << endl; // 得到内存地址中存储的实际数值 250return 0;
}
- 删除器:
- unique_ptr指定删除器和shared_ptr指定删除器是有区别的,unique_ptr指定删除器的时候需要确定删除器的类型
shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; }); // ok
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; }); // errorint main()
{// 只有lambda没有捕获任何变量才能编译通过,否则则需要使用std::function包装器来包装using func_ptr = void(*)(int*);unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });// 包装unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });return 0;
}
std::weak_ptr
- 弱引用智能指针
- 没有重载操作符*和->,因为它不共享指针,不能操作资源(可简间接操作),构造不会增加引用计数,析构也不会减少引用计数。
- 用来监视shared_ptr中管理的资源是否存在。
- 可以获取管理所监测资源的shared_ptr对象:
shared_ptr<element_type> lock() const noexcept;
- 如果赋值给新的共享智能指针,引用计数+1
- 初始化:
// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
// 通过shared_ptr对象构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;
#include <iostream>
#include <memory>
using namespace std;int main()
{shared_ptr<int> sp(new int);weak_ptr<int> wp1;weak_ptr<int> wp2(wp1);weak_ptr<int> wp3(sp);weak_ptr<int> wp4;wp4 = sp;//隐式类型转换weak_ptr<int> wp5;wp5 = wp3;return 0;
}
- 获得当前所观测资源的引用计数:
long int use_count() const noexcept;
- 判断观测的资源是否已经被释放:
bool expired() const noexcept;
- 返回true表示资源已经被释放, 返回false表示资源没有被释放
- 清空:
void reset() noexcept;
- 不再不监测任何资源,expired会返回true。
返回管理this的shared_ptr
- 如果使用同一个指针this单独构造了两个智能指针对象sp1和sp2,此时这二者之间是没有任何关系的,因为sp2并不是通过sp1初始化得到的实例对象。
- 那么在离开作用域之后this将被构造的两个智能指针各自析构,导致重复析构的错误,即this析构了两次!
- 这个问题可以通过weak_ptr来解决,通过weak_ptr返回管理this资源的共享智能指针对象shared_ptr。
- 或者使用模板类
std::enable_shared_from_this<T>
- 这个类中有一个方法叫做
shared_from_this()
,通过这个方法可以返回一个共享智能指针,在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptr的lock()方法返回一个shared_ptr对象。
- 这个类中有一个方法叫做
#include <iostream>
#include <memory>
using namespace std;struct Test : public enable_shared_from_this<Test>
{shared_ptr<Test> getSharedPtr(){return shared_from_this();}~Test(){cout << "class Test is disstruct ..." << endl;}
};int main()
{shared_ptr<Test> sp1(new Test);cout << "use_count: " << sp1.use_count() << endl;// 1shared_ptr<Test> sp2 = sp1->getSharedPtr();cout << "use_count: " << sp1.use_count() << endl;// 2return 0;
}
解决循环引用导致内存泄漏问题
- 内存泄漏例子:
#include <iostream>
#include <memory>
using namespace std;struct TA;
struct TB;struct TA
{shared_ptr<TB> bptr;~TA(){cout << "class TA is disstruct ..." << endl;}
};struct TB
{shared_ptr<TA> aptr;~TB(){cout << "class TB is disstruct ..." << endl;}
};void testPtr()
{shared_ptr<TA> ap(new TA);shared_ptr<TB> bp(new TB);cout << "TA object use_count: " << ap.use_count() << endl;// 1cout << "TB object use_count: " << bp.use_count() << endl;// 1ap->bptr = bp;bp->aptr = ap;cout << "TA object use_count: " << ap.use_count() << endl;//2cout << "TB object use_count: " << bp.use_count() << endl;//2
}int main()
{ // 此时ap,bp引用计数都为2,但是离开这个作用域,计数都只减1,那么ap和bp都不会自动析构。testPtr();return 0;
}
- 解决方法:
#include <iostream>
#include <memory>
using namespace std;struct TA;
struct TB;struct TA
{weak_ptr<TB> bptr;~TA(){cout << "class TA is disstruct ..." << endl;}
};struct TB
{shared_ptr<TA> aptr;~TB(){cout << "class TB is disstruct ..." << endl;}
};void testPtr()
{shared_ptr<TA> ap(new TA);shared_ptr<TB> bp(new TB);cout << "TA object use_count: " << ap.use_count() << endl;//1cout << "TB object use_count: " << bp.use_count() << endl;//1ap->bptr = bp;bp->aptr = ap;cout << "TA object use_count: " << ap.use_count() << endl;//2cout << "TB object use_count: " << bp.use_count() << endl;//1
}int main()
{testPtr();return 0;
}
constexpr
- const:变量只读(例如修饰函数参数,或者修饰引用),修饰常量
- 变量只读不等于常量
- constexpr :修饰常量表达式
- 常量表达式: 由多个(≥1)常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。
- 建议凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
- 不能使用constexpr修饰自定义数据类型 ,到那时可以修饰一个结构体/类常量的对象
const int m = f(); // 不是常量表达式,m的值只有在运行时才会获取。
const int i=520; // 是一个常量表达式
const int j=i+1; // 是一个常量表达式constexpr int i=520; // 是一个常量表达式
constexpr int j=i+1; // 是一个常量表达式struct Test
{int id;int num;
};int main()
{constexpr Test t{ 1, 2 };constexpr int id = t.id;constexpr int num = t.num;t.num += 100;// error,不能修改常量cout << "id: " << id << ", num: " << num << endl;return 0;
}
常量表达式函数
- constexpr修饰函数的返回值,这种函数被称作常量表达式函数
- 这些函数主要包括以下几种:普通函数/类成员函数、类的构造函数、模板函数
- 函数必须有返回值
- 函数在使用之前,必须有对应的定义语句。
- 整个函数的函数体中,不能出现非常量表达式之外的语句(using 指令、typedef 语句以及 static_assert 断言、return语句除外)。
- 对于修饰模板函数,如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数
- 对于修饰构造函数,构造函数的函数体必须为空,并且必须采用初始化列表的方式为各个成员赋值
// 定义函数模板
template<typename T>
constexpr T dispaly(T t) {return t;
}#include <iostream>
using namespace std;struct Person {constexpr Person(const char* p, int age) :name(p), age(age){}const char* name;int age;
};int main()
{constexpr struct Person p1("luffy", 19);cout << "luffy's name: " << p1.name << ", age: " << p1.age << endl;return 0;
}
原始字面量
R “xxx(原始字符串)xxx”
,()两边的字符串可以省略- 两边字符串会被忽略,并且加的字符串必须在括号两边同时出现 。
- 原始字面量R可以直接表示字符串的实际含义,而不需要额外对字符串做转义或连接等操作。
#include<iostream>
#include<string>
using namespace std;
int main()
{string str = "D:\hello\world\test.text";cout << str << endl;string str1 = "D:\\hello\\world\\test.text";cout << str1 << endl;string str2 = R"(D:\hello\world\test.text)";cout << str2 << endl;string str3 = R"luffy(D:\hello\world\test.text)luffy";cout << str3 << endl;//string str3 = R"luffy(D:\hello\world\test.text)robin"; // 语法错误,编译不通过return 0;
}// 结果
D:helloworld est.text
D:\hello\world\test.text
D:\hello\world\test.text
D:\hello\world\test.text
异常
原文
- 语法:可以直接在程序中将各种类型的异常抛出,从而强制终止程序的运行。
- 抛出任意异常类型:
struct MyException
{MyException(string s) :msg(s) {}string msg;
};double divisionMethod(int a, int b)
{if (b == 0){throw MyException("division by zero!!!");// throw 100;}return a / b;
}
- 不允许抛出任何异常:使用 noexcept
double divisionMethod(int a, int b) noexcept
{if (b == 0){cout << "division by zero!!!" << endl;return -1;}return a / b;
}// 常量表达式的结果会被转换成一个bool类型的值,
// 值为 true,表示函数不会抛出异常
// 值为 false,表示有可能抛出异常这里
double divisionMethod(int a, int b) noexcept(常量表达式);
强类型枚举
原文
- 枚举enum,具名(有名字)的enum类型的名字,以及 enum 的成员的名字都是全局可见的。
- 强类型枚举
enum class Colors :type{ Red, Green, Blue };
- 可以指定底层类型, type 可以是除 wchar_t 以外的任何整型
- 强作用域
- 转换限制,强类型枚举成员的值不可以与整型隐式地相互转换
- 强类型枚举不会进行隐式类型转换,因此枚举值不能直接给int行变量赋值(虽然强类型枚举的枚举值默认就是整形,但其不能作为整形使用)
enum class China { Shanghai, Dongjing, Beijing, Nanjing, };
enum class Japan:char { Dongjing, Daban, Hengbin, Fudao };
int main()
{int m = Shanghai; // errorint n = China::Shanghai; // errorif ((int)China::Beijing >= 2){cout << "ok!" << endl;}cout << "size1: " << sizeof(China::Dongjing) << endl;// 4cout << "size2: " << sizeof(Japan::Dongjing) << endl;// 1return 0;
}
std::any
- 在类型安全的容器中存储任意类型的单个值
// 构造
#include <any>
std::any a; // 创建一个空的any对象
a = 42; // 存储一个int
a = 3.14; // 现在存储一个double,原来的int被覆盖
a = std::string("Hello"); // 现在存储一个std::string
auto value = std::make_any<int>(66);
std::any a = 42;// 检查是否有值和获取类型名
if (a.has_value()) {std::cout << "a has value: " << a.type().name() << std::endl; // 输出类型名(如"i"表示int)
}// 清空
a.reset();
if (!a.has_value()) {std::cout << "a is empty now." << std::endl;
}// std::any_cast,获取正确类型的值,如果不是按指针获取,失败会抛出异常
std::any a = 42;
try {int value = std::any_cast<int>(a); // 正确std::cout << "The value is: " << value << std::endl;// double d = std::any_cast<double>(a); // 错误!类型不匹配,将抛出 std::bad_any_cast
} catch (const std::bad_any_cast& e) {std::cerr << "Cast failed: " << e.what() << '\n';
}// 但是按指针获取(失败时返回 nullptr,不会抛出异常)
std::any a = 42;
if (auto pInt = std::any_cast<int>(&a); pInt != nullptr) { // C++17 带初始化的ifstd::cout << "The value is: " << *pInt << std::endl;
}
std::variant
- 安全联合体,并且支持非平凡类型(如带有构造/析构函数的类)。
- 替代例如
std::vector<std::any>
#include <variant>
std::variant<int, double, std::string> myVariant;
// 赋值为 int
myVariant = 42;// 赋值为 double
myVariant = 3.14;// 赋值为 string
myVariant = "Hello, variant!";// 使用 std::get 访问值(如果类型不匹配会抛出 std::bad_variant_access 异常)
try {std::string s = std::get<std::string>(myVariant);std::cout << "String value: " << s << std::endl;
} catch (const std::bad_variant_access& e) {std::cout << "Variant doesn't hold a string" << std::endl;
}// 使用 std::get_if 安全地访问值(返回指针,如果类型不匹配返回 nullptr)
if (auto pval = std::get_if<int>(&myVariant)) {std::cout << "Int value: " << *pval << std::endl;
} else {std::cout << "Variant doesn't hold an int" << std::endl;
}// 检查当前存储的类型
// 使用 index() 获取当前存储类型的索引(按模板参数顺序从0开始)
std::cout << "Current index: " << myVariant.index() << std::endl;// 使用 holds_alternative 检查是否持有特定类型
if (std::holds_alternative<int>(myVariant)) {std::cout << "Holds an int" << std::endl;
}
std::optional 判断文件是否打开
std::tuple 多返回值/结构化绑定
- 结构化绑定