目录
列表初始化
- 是一种用于初始化变量、数组、容器和对象等的方式,它使用花括号 { } 来提供初始值
int arr[]{1,2,3,4,5}; class myclass { public: myclass(int a,int b) :_a(a),_b(b) {} private: _a = 1; _b = 1; } myclass obj{ 3,4 };
类型推导
- auto:让编译器根据初始化表达式的类型来推导变量的类型
auto num = 10; auto str = "hello";
- decltype:用于获取表达式的类型,它可以用于声明变量或函数返回值类型
int x = 5; decltype(x) y = 10; //y类型为int template<typename T> decltype(auto) add(T a,T b) { return a+b; }
- decltype(auto)是类型说明符,用于自动推导函数返回值和变量类型
范围for
- 用于简化便利容器或数组的操作
//其语法格式 for(auto& element : container) { //操作element(对container中的每个元素进行遍历) }
空值指针
- 用于表示指针不指向任何有效的对象或内存地址,可以使用nullptr来初始化一个空值指针
override 和 final关键字
- override:用于显示的表示一个函数重写了基类中的虚函数
- final
- 修饰虚函数表示该虚函数在派生类中不能被进一步重写
- 修饰类表示该类不能被继承
默认成员函数控制:delete 和 default扩展功能
- delete扩展功能
- 阻止特定构造函数的生成
class Myclass { public: Myclass() = delete; } //这样就无法通过默认构造创建类的对象
- 阻止特定类型的转换构造函数
- 用来删除接受特定类型参数的转换构造函数,来避免不期望的类型转换
class Myclass { public: Myclass(int) = delete; } //这里将阻止从int类型到Myclass类型的隐式转换
- 用来删除接受特定类型参数的转换构造函数,来避免不期望的类型转换
- 阻止特定构造函数的生成
- default扩展功能
- 显示默认移动构造函数和移动赋值运算符
Myclass(Myclass&&) = default; Myclass& operator=(Myclass&&) = default
- 在继承体系中使用:在派生类中,可以使用default来显示调用基类的默认构造、拷贝构造函数等
- 显示默认移动构造函数和移动赋值运算符
智能指针
- 什么是智能指针
- 是一种用于管理动态分配内存的工具,它能够自动释放所管理的对象,从而防止内存泄漏和悬空指针等问题
- RAII
- 是一种利用对象生命周期来控制程序资源的简单技术
- 在对象构造时获取资源,接着控制资源在对象的生命周期保持有效,最后对象析构时释放资源
- 两大好处
- 不需要显示的释放资源
- 采用这种方式,对象所需的资源在其生命周期始终保持有效
- 智能指针的实现原理
- unique_ptr
- 是一种独占式智能指针,它拥有对对象的唯一所有权,在生命周期结束时,会自动释放管理的对象,可以通过std::move将所有权转移给其他std::unique_ptr
- 适用于管理单个对象
- 不可以进行拷贝构造,确保每个对象只有一个所有者
- shared_ptr
- 是一种共享式智能指针,多个std::shared_ptr可以指向同一个对象,通过引用计数来管理对象生命周期,当最后一个指向对象的std::shared_ptr被销毁时才会释放
- 适用于需要多个地方共享对象所有权的场景
- 可能会出现循环引用的问题,用 weak_ptr 解决
- make_shared
- 模拟实现
template<class T> class my_share_ptr { public: my_share_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)) {} template<class D> //实现定制删除器 my_share_ptr(T* ptr = nullptr,D del) :_ptr(ptr), _pcount(new int(1)),_del(del) { } my_share_ptr(const my_share_ptr<T>& sp):_ptr(sp),_pcount(sp._pcount) { ++(*_pcount); } void release() { if (--(*_pcount) == 0) { del(_ptr) delete _pcount; _ptr = nullptr; _pcount = nullptr; } } ~my_share_ptr() { release(); } my_share_ptr<T>& operator=(const my_share_ptr& sp) { //this->~my_share_ptr(); release(); _ptr = sp._ptr; _pcount = sp._pcount; ++(*_pcount); return *this; } private: T* _ptr; int* _pcount; std::function<void(T* ptr)> _del = [](T* ptr) {delete ptr; }; };
- 循环引用出现场景
- weak_ptr
- 是一种弱引用智能指针,它不用于对象多有权,不会增加对象的引用计数,主要用于解决shared_ptr循环引用问题
右值引用
- 左值
- 左值是可以取地址,有名字,能在表达式左边或右边出现的表达式
- 特点:具有持久性,在程序中能存在较长时间,有自己的内存地址,可以通过该地址对其进行访问和修改
- 右值
- 右值是不能取地址,通常没有名字,只能出现在表达式右边的值
- 特点:临时性,生命周期短暂,通常在表达式计算结束后销毁
- 什么是右值引用
- 右值引用本身的属性是左值(因为只有是左值才能拿到别人的资源)
- 右值引用是对右值的引用,通过&&来声明。
- 作用
- 实现移动语义:在没有右值引用之前,对象的拷贝通常通过复制拷贝来完成的,这会导致对象内容的深拷贝,开销较大。有了右值引用,就可以实现移动语义,即将资源从一个对象移动到另一个对象,而不进行拷贝
- 完美转发:右值引用可以将参数按照其原本类型(左值或右值)转发给其它函数,确保在转发过程中不会改变参数的左右值属性,从而实现更高效更灵活的函数调用
- 右值引用和引用的区别
- 概念:
- 引用:是对象的别名,用于给已存在的对象取一个额外的名字
- 右值引用:用于绑定右值
- 区别
- 引用只能绑定左值,不能直接绑定右值。右值引用专门用于绑定右值,不能绑定左值
- 可以通过const&的方式绑定右值
- 引用行用于函数参数传递和返回值,以避免对象的拷贝提高效率。右值引用主要实现移动语义和完美转发
- 引用绑定对象的生命周期不受引用影响。右值引用绑定右值会延长右值生命周期,使其与右值引用生命周期相同
- 引用只能绑定左值,不能直接绑定右值。右值引用专门用于绑定右值,不能绑定左值
- 概念:
- 右值引用能否引用左值
- 不能直接引用,但可以通过std::move函数将左值转换为右值(并不是真转换)
int num = 10; int&& rref = std::move(num);
- 不能直接引用,但可以通过std::move函数将左值转换为右值(并不是真转换)
- move函数
- move的功能是将左值转换为右值引用。它并不执行任何实际的移动操作,只是告诉编译器这个左值可以当成右值来处理
- 右值引用标识可被移动的对象,这是移动语义的基础
- 移动语义
- 核心是转换资源所有权而非复制(把原对象的资源指针给新对象),可以通过右值引用,移动构造,移动复制实现,避免不必要的拷贝
- 移动构造和移动赋值
- 移动构造:用右值引用参数,窃取原对象资源,并将原对象资源置空,避免重复释放
class MyClass { public: // 移动构造函数 MyClass(MyClass&& other) noexcept { this->data = other.data; // 转移资源(指针) other.data = nullptr; // 原对象资源置空 } private: int* data; };
- 移动赋值:类似移动构造,处理对象赋值时的资源转移,需注意自身赋值判断
class MyString { private: char* data; // 指向字符串数据的指针 size_t length; // 字符串长度 public: MyString& operator=(MyString&& other) noexcept { if (this != &other) { delete[] data; // 释放当前对象资源 data = other.data; // 直接转移指针 length = other.length; other.data = nullptr; // 置空原对象(右值) other.length = 0; } std::cout << "移动赋值运算符被调用,高效转移资源" << std::endl; return *this; } }
- 移动构造:用右值引用参数,窃取原对象资源,并将原对象资源置空,避免重复释放
完美转发:通过模板和右值引用实现的技术,能将函数参数原封不动的传递给其它函数,保留参数的左右值属性和顶层const修饰
作用:在函数模板中,避免参数被错误推导为左值,或丢失const等属性
#include <iostream> #include <utility> // 包含 std::forward // 目标函数:接收左值引用 void process(int& x) { std::cout << "处理左值: " << x << "\n"; } // 目标函数:接收右值引用 void process(int&& x) { std::cout << "处理右值: " << x << "\n"; } // 中间转发函数模板 template <typename T> void forwarder(T&& arg) { process(std::forward<T>(arg)); // 完美转发,保留 arg 的左右值属性 } int main() { int a = 10; forwarder(a); // 实参是左值,转发为左值 -> 调用 process(int&) forwarder(20); // 实参是右值,转发为右值 -> 调用 process(int&&) forwarder(std::move(a)); // 强制转为右值,转发为右值 -> 调用 process(int&&) return 0; }
lambda表达式
- 概念:可以在代码中直接定义并使用,简化短函数的定义
- 基本语法:
[capture-list](paremeters)specifiers -> return-type {body}
捕获列表:捕获外部变量
[]空捕获:标识lambda表达式不捕获任何外部变量,它只能访问自己参数列表中的变量和全局变量
[=]值捕获:会以值的方式捕获所有外部作用域中被用到的变量,lambda表达式中使用捕获变量不会影响外部
[&]引用捕获:同值捕获,但使用的捕获变量值会影响外部
[a,&b]混合捕获
用例
无捕获,无参数
auto f = []{ return 42; }; // 定义Lambda,返回42 std::cout << f(); // 输出:42
值捕获外部变量
int x = 10; auto f = [=]() { return x * 2; }; // 值捕获x,返回20 std::cout << f(); // 输出:20
引用捕获
int x = 10; auto f = [&]() { x += 5; }; // 引用捕获x,修改原值 f(); std::cout << x; // 输出:15
带参数和返回值类型
auto add = [](int a, int b) -> int { return a + b; }; // 加法Lambda std::cout << add(3, 5); // 输出:8
mutable允许修改值捕获的变量
int x = 10; auto f = [x]() mutable { x += 5; return x; }; // 值捕获x,mutable允许修改副本 std::cout << f(); // 输出:15(修改的是副本) std::cout << x; // 输出:10(原值未变)
底层实现:底层实现原理式基于仿函数
生成匿名类:编译器会为每个Lambda表达式生成一个唯一的匿名类,这个类重载了operator(),使得该类可以像函数一样被调用
捕获变量实现
值捕获:编译器会在匿名类中添加一个对应类型的成员变量来存储捕获的值,并在构造函数中初始化
引用捕获:编译器在匿名类中添加一个引用类型的成员变量,用于存储外部变量的引用,这样在lambda表达式中对该变量操作就是对本身操作