C++重要知识点(C++11新增语法)(智能指针、右值引用、lambda表达式)

发布于:2025-06-28 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

列表初始化

类型推导

范围for

空值指针

override 和 final关键字

默认成员函数控制:delete 和 default扩展功能

智能指针

右值引用

lambda表达式


列表初始化

  • 是一种用于初始化变量、数组、容器和对象等的方式,它使用花括号 { } 来提供初始值
  • 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);
  • 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表达式中对该变量操作就是对本身操作


网站公告

今日签到

点亮在社区的每一天
去签到