C++11新特性

news2024/9/28 7:15:53

文章目录

    • 说在前面
    • 花括号{}初始化
      • new的列表初始化
      • STL相关容器的列表初始化
        • 相关语法格式
        • 容器列表初始化的底层原理
    • forward_list和array
    • 与类型相关的新特性
      • decltype
    • 左值引用和右值引用
      • 什么是左值,什么是右值
      • 左值和右值的本质区别
      • 右值引用
        • 如何理解右值引用
      • std::move
      • 移动构造,移动赋值
    • 万能引用和完美转发
    • 模板的可变参数
    • default和delete的新用法
    • lambda表达式
      • 语法格式
      • 捕获列表
      • 底层原理
    • 包装器
      • std::bind
    • 总结

说在前面

作为新年的第一篇博客。在这里先祝大家新年快乐!今天我们要介绍的是C++11 首先,如同一个人从一个一无所知的婴儿成长成为一个社会的精英是需要不断发展的。C++自从诞生的那一刻起,一直都在不断进步,不停更新。对于C++来说,有两个比较重要的时间点。一个就是C++98,这个版本的C++引入了强大的STL。而另外的一个时间点就是我们接下来介绍的C++11 接下来,我们就来看一看C++11更新了哪些有用的东西

花括号{}初始化

在C++11里面,所有的东西都可以使用{}进行初始化,你可能会看到很多有关C++11的书籍里面可能都会有如下的demo代码

#include<iostream>
/*
 * C++11 特性
 * */
void test1()
{
   //C/C++98里面定义变量常用的方式
    int i=5;
    //C++11定义变量常用方式 
    int x={3};
    int y{3};

}    

int main()
{
   test1();
  return 0;
}

不仅仅是内置类型,自定义类型也可以使用如上的方式进行初始化

//自定义类型也可以支持{}初始化 
struct Date
{
  Date(int year=2023,int month=1,int day=22)
     :_year(year)
     ,_month(month)
     ,_day(day)
    {}
  int _year;
  int _month;
  int _day;

};

void test2()
{
  //C++98
   Date d1(2022,1,23);
   //c++11
   Date d3{2022,1,25};
}

实际上,花括号初始化的本质也是调用了构造函数,我们可以对构造函数添加打印语句:

//自定义类型也可以支持{}初始化 
struct Date
{
  explicit Date(int year=2023,int month=1,int day=22)
     :_year(year)
     ,_month(month)
     ,_day(day)
  {
     std::cout<<"Date(int year,int month,int day)"<<std::endl;
  }
  int _year;
  int _month;
  int _day;

};
void test2()
{
  //C++98
   Date d1(2022,1,23);
   //C++11
  // Date d2={2022,1,24};
   //还可以这样写 
   Date d3{2022,1,25};
}

在这里插入图片描述
而因为增加了{}初始化的方式,那么就能够解决在C++98里面使用new构造一块连续的空间的同时无法顺带初始化的问题!

new的列表初始化

在C++98里面,对于一块连续空间的申请和初始化,我们只能分开进行:

void test3()
{  
  //C++98里面申请空间和初始化必须分开
  //int* p=new int[4];
  //for(int i=0;i<4;++i) p[i]=i+1;
  //C++11允许这么做,这里直接在
   int* p=new int[4]{1,2,3,4};
   for(int i=0;i < 4;++i)
   {
       std::cout<<p[i]<<" ";
   }
   std::cout<<std::endl;
   delete[] p; 
}

从C++11以后,我们就可以使用花括号来初始化对应new的连续的数组空间!不过相对来说,这种方式用的还是相对比较少。毕竟更多的情况下,我们都是使用的STL提供的容器。所以这里我们知道有这样一种用法就好了

STL相关容器的列表初始化

STL里面的容器也提供了对应的列表初始化的方式,真可谓是一切都可列表初始化,所以我们才会说到了C++11以后。一切皆可使用列表初始化! 下面我们就来看一看相应的语法格式。

相关语法格式

这里我们用vector来进行演示。基本上我们之前学习的容器都可以使用这样的初始化方式!

//自定义类型也可以支持{}初始化 
struct Date
{
   Date(int year=2023,int month=1,int day=22)
     :_year(year)
     ,_month(month)
     ,_day(day)
  {
     std::cout<<"Date(int year,int month,int day)"<<std::endl;
  }
  //为了方便观察效果
  void PrintDate()
  {
     std::cout<<_year<<" "<<_month<<" "<<_day<<'\n';
  }
  int _year;
  int _month;
  int _day;

};

那么,为什么能够这样给容器初始化呢?在处理容器的{}初始化的时候,编译器做了哪些工作呢?下面我们就来探究一下:

容器列表初始化的底层原理

首先,官方文档就是我们学习C++11最好的资料之一,我们先来看看C++11种vector对应发生了什么变化:
在这里插入图片描述
首先,上面两个对应的构造函数是后面的知识点。实际上,真正能够让我们使用花括号初始化对应容器元素的就是最后一个构造函数! 下面我们就通过代码来看一看{}究竟被编译器做了怎样的处理:

void test()
{  
  auto l={1,2,3,4,5,77};
  //获取l变量的类型,并以字符串的形式打印
  std::cout<<typeid(l).name()<<std::endl;
}

在这里插入图片描述
这段代码是在Linux上获取的,不过对应的就是文档手册里面的新的构造函数的参数的类型initializer_list<T>! 在C++11以后,{}被编译器识别成为新的initializer_list类型,只要支持这个构造函数就可以使用花括号进行初始化! 所以为了让我们自己实现的vector也能够支持{}初始化,我们也添加这个构造函数


   namespace chy
{
  //vector是一个模板类
  template<typename T>
  class vector
  {
  public:
	  typedef T* iterator;
	  typedef const T* const_iterator;
	  //...
	  //给我们自己实现的vector添加对应的构造函数 
	  //这个时候我们自己实现的vector也就可以支持{}初始化的方式了!
	  vector( std::initializer_list<T> li)
	  {  
	     //initiallizer支持范围for
	      for(auto& e: li)
	      {
	          push_back(e);
	      }
	  }
  private:
	  iterator _start;
	  iterator _finish;
	  iterator _end_of_stroge;
  };
}

其他的容器也是一样的道理,这里我就不一一演示了。 另外,C++11还新增加了几个容器。我们之前介绍的哈希相关的容器就是C++11新增的,接下来我们将介绍两个C++11里面存在感比较低的两个容器—> forward_list和array

forward_list和array

C++11新增加了forward_list和array这两个新容器,下面我们先来看forward_list。首先先来看文档对应的说明:
在这里插入图片描述
首先,不得不说有的时候C++对于类的命名不是特别能够让人见名知义。这个forward_list其实就是我们之前在数据结构那里学习的单链表!下面,我们就来简单使用以下forward_list

//需要包含这个头文件
#include<forward_list>
void test()
{
  std::forward_list<int> fl;
  int a[]={1,2,3,4,5};
  //没有提供尾插接口
  for(auto e:a)
  {
     fl.push_front(e);
  }
  //也支持范围for
 for(auto e:fl)
 {
     std::cout<<e<<" ";
 }
  std::cout<<'\n';
}

在这里插入图片描述
说实话,这个容器的作用远远不及list,即使是我们手写一个简单的单链表也是可以的!不过这个容器的倒是可以作为哈希桶悬挂的子节点结构!
接下来,我们来看一看另外一个容器array.首先我们先来看文档对应的说明:
在这里插入图片描述
从文档来看,array就是一个固定大小的泛型线性数组。下面我们就来简单使用一下array:

void test()
{
   
   //array的简单使用
    std::array<int,5> a={1,2,3,4,5};
    for(auto e:a)
    {
       std::cout<<e<<" ";
    }
    std::cout<<'\n';
}

在这里插入图片描述
使用array要注意如下的几个问题:

1.array是一个静态数组,不能对array插入元素
2.array会对越界进行检查

说实话,C++11这个array确实是没什么太大的作用,它所有的功能,vector基本都有!所以这个容器大家仅仅就是作为一个了解就可以了。不用过分去深究这个容器,相应 的forward_list也是如此。

与类型相关的新特性

C++11还增加了一些有关类型推断的新特性。比如我们经常使用的auto就是在C++11以后才增加的类型推导的能力。 接下来介绍一个C++11新增加的一个和类型相关的特性的一个操作符---->decltype

decltype

这个操作符能够把表达式的类型返回给一个变量,测试的代码如下:

void test()
{
      decltype(10+20)x=10;
      std::cout<<"x变量的类型为: "<<typeid(x).name()<<std::endl;
}

在这里插入图片描述
可以看到这个操作符确实把类型返回了。注意,这个操作符和sizeof操作符一样,并不会去计算对应括号的表达式,仅仅就是返回对应的表达式的类型

左值引用和右值引用

前面讲的特性大多是是C++11相对来说属于锦上添花的。而右值引用可以说式C++11相对来说非常具有革命性意义的一个新特性。它让C++的高效又上了一个台阶! 在正式介绍右值引用之前,我们有必要来明确一个概念:什么是左值 ? 什么是右值?

什么是左值,什么是右值

首先,从字面上的意思来看:左值就是出现在表达式左边的值,右值就是出现在表达式右边的值。比如下面的代码:

//左值和右值
void test()
{
  //从字面意义上来看,这里的a是左值
   int a=10+20;
   //10+20就是一个右值
   //但是下面怎么解释
   int b=a;//a不是左值?怎么出现在了右边
}

最后一个例子不难看出:这样的理解是不正确的,如果这样子理解左值和右值就太肤浅了。 那么左值和右值的本质区别到底是什么呢?或者说,左值具有什么特征,右值又有什么特征呢?

左值和右值的本质区别

明确地给出一个结论:左值是可以取地址地值!右值是不能取地址的值!也就是说:通过能否取地址就可以看出:左值是真正分配了计算机内存空间的值!而右值仅仅只是一个临时量!

右值引用

在C++98以前,我们所使用的引用都叫做左值引用。从C++11开始,我们就要把引用分为左值引用和右值引用了。下面我们就来通过代码来看这两个引用:

void test()
{
   int x=10;
   //rx是x的别名,是左值
   int& rx=x;
   //右值引用使用两个&&
   int&& rrx=10; //10是右值,所以rrx是右值引用
}

无论右值引用还是左值引用,只要是引用,那么都是被引用对象的别名!而右值引用自身是一个左值,因为我们可以对右值引用的那个别名取地址了!

void test()
{
   int x=10;
   //rx是x的别名,是左值
   int& rx=x;
   //右值引用使用两个&&
   int&& rrx=10; //10是右值,所以rrx是右值引用
   std::cout<<"rrx的地址是:" <<&rrx<<std::endl;
}

在这里插入图片描述

如何理解右值引用

从前面的结论可以看出:本来即将消亡的右值,通过右值引用的方式,计算机也确实为了它开辟一块内存空间存储对应的值! 也就是这个别名本质是一个左值!所以右值引用是一个左值,这个左值是右值的别名! 左值引用只能引用左值!而右值引用只能引用右值

std::move

那么有的时候,我们需要把左值转换成为右值。c++11的设计者也考虑到这个点了,所以提供了一个接口:std::move—>可以把左值转换成右值。下面我们来看一段代码:

void fun(int& rx)
{
    std::cout<<"fun(int&)"<<std::endl;
}
void fun(int&& rx)
{
    std::cout<<"fun(int&&)"<<std::endl;
}
void test()
{
   int x=10;
   fun(x);
   fun(std::move(x));
}

在这里插入图片描述
所以我们就可以总结如下:

左值引用只能引用左值,const左值引用可以引用左值也可以引用右值
右值引用可以引用右值和move以后的左值

移动构造,移动赋值

那么C++11推出右值引用难道仅仅是我们前面那样使用吗?答案显然不是!在C++98的时候,C++一直都很害怕这样的场景:

class Solution {
private:
//获取 对应的keyboard里面的行
    size_t getRow(char ch,vector<string>& v)
    {
        for(int i=0;i<v.size();++i)
        {
            if(v[i].find(tolower(ch))!=string::npos)
            {
                return i;
            }
        }
        return v.size();
    }    
public:
    vector<string> findWords(vector<string>& words)
    {
        vector<string> keyboard;
        keyboard.push_back("qwertyuiop");
        keyboard.push_back("asdfghjkl");
        keyboard.push_back("zxcvbnm");
        vector<string> res;
        for(int i=0;i<words.size();++i)
        {
            size_t pos=getRow(words[i][0],keyboard);
            //pos=3,说明不在这一行,继续寻找下一行
            //cout<<pos<<" ";
            if(pos==3) continue;
            else 
            {   
               // cout<<pos<<" ";
                //找到了对应行
                bool flag=true;
                for(char ch:words[i])
                {
                    if(keyboard[pos].find(tolower(ch)) == string::npos)
                    {
                        flag=false;
                        break;
                    }
                }
                if(flag) 
                    res.push_back(words[i]);
            }
        }
        return res;
    }
};

通过前面的学习我们知道,传值返回会发生拷贝。而对于vector这样的类,发生的是深拷贝!一旦这个vector管理的对象特别多,并且每个对象又要进行深拷贝的情况下。传值返回的效率就是一场灾难! C++是一门十分注重效率的编程语言,而右值引用便是为了解决这个问题应运而生的!
我们知道右值编译器是没有为其分配空间的,那么对于自定义类型的右值,C++又将其称为将亡值 既然是将亡值,那么就意味着这个自定义类型的对象会马上调用析构函数清理自己了。但是这个时候这个自定义类型身上的资源又是我所需要的。下面就有如下两种做法:

1.在对象析构之前,我深拷贝一个副本,然后这个对象自行销毁
2.既然这个将亡对象有我想要的资源,那么不妨把它的资源为我所用!然后再让其自行销毁即可!

显然我们都会选择第二种方式。那么c++11以后,就新增加了两个相应的默认成员函数:移动构造和移动赋值

class A 
{
public:
   A()
   :_p(nullptr)
   ,_size(0)
   {}
  A(int size)
    :_p(nullptr)
    ,_size(size)
  {
     _p=new int[size];

  }
   //拷贝构造
   A(const A& a)
     :_p(nullptr)
   {
       _p=new int[a._size];
       for(int i=0;i < a._size;++i)
       {
           _p[i]=a._p[i];
       }
       _size=a._size;
       std::cout<<"A(const A& a)"<<std::endl;
   }
   //移动构造 
   A(A&& aa)
     :_p(nullptr)
   {
       std::swap(_p,aa._p);
       std::swap(_size,aa._size);
       std::cout<<"A( A&& aa)"<<std::endl;
   }

  ~A()
  {
     delete _p;
     _p=nullptr;
  }

private:
   int* _p;
   int _size;
};

接下来我们使用gdb进行调试观察结果:
在这里插入图片描述
接下来就要开始构造a3了,使用的是移动构造:
在这里插入图片描述
可以看到,这里的a3和a1的成员发生了交换。相比于先前,我们只能,这种场景我们只能进行深拷贝。当对象很大的时候,效率非常低!而有了C++11的移动构造以后,传值返回的效率得到了大幅度的提升!
而对于传值返回的情况,现代的编译器也会进行一定的大胆的优化,假设我们现在有一个to_string函数:

//一个我们自己写的to_string函数
namespace chy
{
     string to_string(int val)
     {
           string str;
           while(val)
           {
              //...复杂的转换逻辑
           }
           return str;
     }
}

那么在C++98的时候,传值返回的情况如下:
在这里插入图片描述
那么C++11有了移动构造了以后,编译器的传值返回情况又不一样了
在这里插入图片描述
对应的有了移动构造,就会有移动赋值:

A& operator=(A&& aa)
   {
       
       std::swap(_p,aa._p);
       std::swap(_size,aa._size);
       return *this;
   }

既然是作为默认成员函数,那么在特定的条件下,编译器就会生成对应的默认移动构造和移动赋值,需要满足如下三个特性:

1.没有显式提供拷贝构造函数
2.没有显式提供赋值重载
3.没有显式提供析构函数

只有满足如上三条件,编译器才会生成默认的移动构造和移动赋值函数!
默认生成的移动构造对于内置类型浅拷贝,对于自定义类型,如果提供了移动构造函数,那么就会调用移动构造
不过,多数情况下生成的默认移动构造没什么价值,所以这两个默认成员函数一般都需要手动提供。
需要注意的是:在c++11以后,原先将单个元素插入容器的函数接口都提供了对应的右值版本,目的就是为了能够进一步提高运行效率!

万能引用和完美转发

而当模板和右值引用结合起来的时候,又会发生不一样的效果:

template<typename T>
void solve(T&& t)
{  
   std::cout<<"调用!"<<std::endl;
}

void test()
{
   
   A a1;
   A& a2=a1;
  // 左值引用传参
   solve(a2);
 // 右值引用传参 
  solve(std::move(a1));
}

在这里插入图片描述
我们可以看到,无论是左值还是右值,这个模板函数都能匹配! 这个就是右值引用和模板结合到了一起以后。既能够接受左值,也可以接受右值,因此我们就把这个特性叫做万能引用。 但是这个万能引用有一个很不好的缺陷,接下来我们来通过一段代码进行演示:

template<typename T>
void solve(T&& t)
{  
   T tmp(t);
}

void test()
{
   
   A a1;
 // 右值引用传参 
  solve(std::move(a1));
}

在这里插入图片描述
我们发现,move以后的a1本来应该是一个右值,结果传参了以后变成了一个左值,万能引用就会出现引用折叠的问题。使用万能引用了以后,所有的传递的T&&参数都会变成左值! ,但是有的时候我们希望能够把原来的特性保持下去,所以c++11又提供了一种新的方式---->完美转发


template<typename T>
void solve(T&& t)
{  
  //使用forward保持特性
   T tmp(std::forward<T>(t));
}

void test()
{
  A a1;
  solve(std::move(a1));
}

在这里插入图片描述
这里依旧调用的是移动构造函数,很好保持了原来右值的特性。所以如果要保持对应的原来的值的特性,最好使用forward完美转发。

模板的可变参数

接下来我们来讲一讲模板的可变参数,在C语言阶段,我们经常使用如下的函数:

int printf(const char* format,...);

函数声明里面带有省略号的我们称之为可变参数!在C++11里面,引入了可变模板参数!也就是说,以后一个模板函数或者模板类,不仅类型可变,参数的数量也是可以变化的! 下面我们就来看demo代码:

template<typename T>
void PrintArgs(const T& val)
{
    std::cout<<val<<std::endl;
}
template<typename T,typename...  Args>
void PrintArgs(const T& val,Args... args)
{ 
  
   std::cout<<val<<" typeinfo is "<<typeid(val).name()<<std::endl;
   //可以使用sizeof获取传递参数的个数 
    std::cout<<"param num is "<<sizeof...(args)<<std::endl;
    //参数解包
    PrintArgs(args...); 
}
void test()
{
   
 //  A a1;
 // solve(std::move(a1));
 //可以传任意类型,任意数目的参数 
 PrintArgs(1,2.3,'c',std::string("hello world"));
 std::cout<<'\n';
 PrintArgs(1.1,2);
}

在这里插入图片描述
可变参数模板是通过递归实现的,允许有0个或者是1个递归参数包,我们这里使用的是模板的最佳匹配原则来确定递归的出口,如果这类换成一个最佳匹配的函数也可以!
前面这种写法相对比较繁琐,有的大神还设计出了这样的写法:

//大神的写法 
template<class T>
void PrintArgs(T t)
{ 
   std::cout<<std::endl;
}
template<class ... Args>
void PrintArgs(Args... args)
{
   std::cout<<"param num is "<<sizeof...(args)<<std::endl;
   //使用数组进行解包 ,利用逗号表达式的特性进行参数包解析
   int a[]={(PrintArgs(args...),0)};
}

void test()
{
 PrintArgs(1,2.3,'c',std::string("hello world"));
}

在这里插入图片描述
注意:可变参数递归解包是在编译的时候完成的,不能通过运行时的if逻辑来递归!
有了可变模板参数的知识铺垫,那么我们就可以很好讲解c++11里面容器新增的emplace_back和emplace接口:
在这里插入图片描述
emplace系列接口使用的都是可变参数模板,对应如果传递的参数能够匹配到自定义类型的构造函数,就会变成直接构造!提高效率,不过有了移动构造以后,emplace接口带来的效率提升也不是特别明显,所以这个接口了解即可

default和delete的新用法

在C++11里面,又对default和delete这两个关键字进行了功能的扩展。在类和对象章节我们介绍过,只有在不提供任何构造函数的情况下,编译器才会生成默认构造函数! 但是在c++11如果已经提供了其他构造函数的情况下,仍然想使用编译器生成的默认构造函数,可以使用default关键字

class A 
{
public:
//强制编译器生成默认构造函数
  A()=default;
  A(int size)
    :_p(nullptr)
    ,_size(size)
  {
     _p=new int[size];

  }
   //拷贝构造
   A(const A& a)
     :_p(nullptr)
   {
       _p=new int[a._size];
       for(int i=0;i < a._size;++i)
       {
           _p[i]=a._p[i];
       }
       _size=a._size;
       std::cout<<"A(const A& a)"<<std::endl;
   }
   //移动构造 
   A(A&& aa)
     :_p(nullptr)
   {
       std::swap(_p,aa._p);
       std::swap(_size,aa._size);
       std::cout<<"A( A&& aa)"<<std::endl;
   }

  ~A()
  {
     delete _p;
     _p=nullptr;
  }

private:
   int* _p;
   int _size;
};

不仅是默认构造函数,其他构造函数都可以使用default强制编译器默认生成
不仅仅可以强制编译器生成,我们还可以删除默认成员函数:使用delete关键字
比如删除拷贝构造函数:

class A
{
public:
    //强制编译器生成默认构造函数
    A() = default;
    A(int size)
        :_p(nullptr)
        , _size(size)
    {
        _p = new int[size];

    }
    //拷贝构造
    A(const A& a) = delete;
   
    //移动构造 
    A(A&& aa)
        :_p(nullptr)
    {
        std::swap(_p, aa._p);
        std::swap(_size, aa._size);
        std::cout << "A( A&& aa)" << std::endl;
    }

    ~A()
    {
        delete _p;
        _p = nullptr;
    }

private:
    int* _p;
    int _size;
};
int main()
{  
    A a2(a1);
   return 0;
}

在这里插入图片描述

lambda表达式

接下来我们来讲lambda表达式。最早引进lambda表达式的语言式python,后面其他语言看到这个语言很好用,于是都纷纷加到了标准库里面。c++11以后同样也支持这样的用法
比如我们上京东购物,有时想按照商品价格排序,有时候想按照实际排序,有各种各样的排序需求。在c++98里面,我们就要写不同的仿函数来实现,一旦随着项目的扩展,这些仿函数就会越来越多,非常难维护
所以我们就希望能够达到一种排序规则专为一种排序方法所用!而lambda表达式恰好就是这样的一个工具,下面我们就来看lambda表达式的语法

语法格式

lambda表达式子的语法特征:

//lambda表达式的语法格式
[捕捉列表](函数参数){ 函数体}

接下来我们就结合具体的样例进行分析

struct Student 
{
  
  Student(int age,int score,const std::string& name)
    :_age(age)
    ,_score(score)
    ,_name(name)
    {}
   int  _age;
   int _score;
   std::string _name;
   void print()
   {
      std::cout<<_name<<" " << _age <<" "<<_score<<std::endl;
   }
};
void test()
{
 Student a[] ={{15,23,"aa"},{14,55,"cc"},{13,88,"bb"} };
  //显式使用lambda
  auto l=[](const Student& s1,const Student& s2){ return s1._age < s2._age ;};
  std::sort(a,a+3,l);
  for(auto e : a)
  {
     e.print();
  }
}

在这里插入图片描述
但其实,lambda更多情况下用的是这样的:

 std::sort(a,a+3,[](const Student& s1,const Student& s2){ return s1._score < s2._score ;});
  for(auto e : a)
  {
     e.print();
  }

在这里插入图片描述

捕获列表

首先我们要知道一个点。lambda是一个局部匿名函数,既然是函数,那么就代表者,对应的内部是一个全新的作用域!内部作用域不能直接使用外部的变量! 如果我们想要使用对应的变量,就需要进行捕获!

void test()
{
	//lambda捕获列表
	  //可以使用[]捕获对应的元素
	  int a=1,b=2;
	  auto l=[a,b](){ return a+b; };
	  std::cout<<l()<<std::endl;
}

在这里插入图片描述
这种值的捕获方式是值捕获,默认是对捕获的值带了一个const,想要更改值捕获的话就要使用mutable关键字,不过即使更改内部的a,b也对外面没有影响!所以这个mutable几乎没有 ,不过我们还可以使用引用捕获

 int a=1,b=2;
  int ret=0;
  //auto l=[a,b](){ return a+b; };
  //std::cout<<l()<<std::endl;
  //引用捕获
  auto l=[&ret,a,b](){ret =a+b; return 0;};
  l();
  std::cout<<"ret="<<ret<<std::endl;

在这里插入图片描述
引用捕捉和值捕捉可以混合来,下面这三种写法都是可以的

//=表示用值捕捉所有变量
auto l1=[=](){};
//表示所有变量引用捕获
auto l2=[&](){};
//ret引用捕获,其他变量值捕获
auto l3=[&ret,=](){};

关于lambda的介绍我们就暂时讲到这里。下面我们来看一看这个lambda对于编译器来说究竟是什么?

底层原理

我们将编译的汇编代码进行研究:
在这里插入图片描述
可以看到,lambda函数被编译器处理以后就是一个仿函数!这个仿函数是lambda+一个唯一的uuid!所以即使两个lambda在我们看来一模一样。它们也是不同的类型 值得一提的是,lambda函数还可以和函数指针相互赋值,不过我们并不建议这么去做!

包装器

学到现在,c++11有三个可以进行调用的东西:

1.函数指针
2.仿函数对象
3.lambda

而如果当这些函数统统编程模板的时候。同样的模板就会实例化出三分代码,显然这三份代码还是有一定的冗余!毕竟对于我们都是希望使用对应的()的功能! 因此C++11提供了函数包装器:

void func()
{
   std::cout<<"func pointer"<<std::endl;
}
struct Func
{
   void operator()()
   {
     std::cout<<"Func()"<<std::endl;
   }
};
auto f=[]() -> void {std::cout<<"lambda"<<std::endl;};

void test()
{
	  std::vector<std::function<void()>> fuctions;
	  fuctions.push_back(func);
	  fuctions.push_back(Func());
	  fuctions.push_back(f);
	  for(auto& uf : fuctions)
	  {
	      uf();
	  }
  std::cout<<'\n'; 
}

在这里插入图片描述
也就是说:但凡是返回值和参数都相同的函数,我们都可以直接使用一个function对象来接收!接下来我们改造一下曾经的逆波兰表达式的题目,用C++11的方式改造:

class Solution {
private:
//纯c++11的方式
unordered_map<string,function<int(int,int)>> hash=
{
    {"+" ,[](int a,int b){return a+b;}},
    {"-" ,[](int a,int b){return a-b;}},
    {"*",[](int a,int b){return a*b;}},
    {"/",[](int a,int b){return a/b;}}     
};
public:
    int evalRPN(vector<string>& tokens)
    {
       stack<long long > st;
       for(const auto& str:tokens)
       {
            if(hash.find(str)==hash.end())
            {
                st.push(stoll(str));
            }
            else 
            {
                long long right=st.top();
                st.pop();
                long long left=st.top();
                st.pop();
                long long ret=hash[str](left,right);
                st.push(ret);
            }
          
       }
         return static_cast<int>(st.top());
    }
};

std::bind

最后我们介绍的是C++11引入的bind函数,对应的函数原型如下:

template <class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);
template <class Ret, class Fn, class... Args>
  /* unspecified */ bind (Fn&& fn, Args&&... args);

这个函数有如下的两个作用:

1.将一个参数固定,也就是绑定一个固有的参数
2.调整参数的顺序

接下来我们来看示例代码:

int Sub(int a,int b)
{
    return a-b;
}
class A 
{
public:
    int ASub(int a,int b)
    {
       return a-b;
    }
};
void test()
{
   std::function<int(int,int)> f(&A::Asub);
}

这段代码是错误的 ! 原因是:成员函数有一个隐含的this指针!那么有没有方式可以不需要这个this指针呢?答案是肯定的,解决的方法就是使用std::bind函数

class A 
{
public:
    A(int a=0)
    :_a(a)
      {}
    int ASub(int x,int y)
    {
       return  (x-y)*_a;
    }
private:
    int _a;
};
void test()
{  
  // std::bind(&A::ASub,A(),std::placeholders::_1,std::placeholders::_2);
  // std::function<int(int,int)> f(&A::ASub); 错误,非静态成员函数有this指针 
  // 解决方式:使用std::bind绑定this指针 
  // 为了演示效果,给A加一个成员 
  
  std::function<int(int,int)> f1(std::bind(&A::ASub,A(),std::placeholders::_1,std::placeholders::_2));
  std::cout<<f1(2,3)<<std::endl;
  std::function<int(int,int)> f2(std::bind(&A::ASub,A(2),std::placeholders::_1,std::placeholders::_2));
  std::cout<<f2(2,3)<<std::endl;

}

bind还有一个作用就是改变参数的位置。而我们代码里面的_1和_2就是我们要显式传递的参数的位置,也就是占位符!可以通过占位符来改变参数顺序:

void test()
{  
  
   std::function<int(int,int)> f1(std::bind(Sub,std::placeholders::_1,std::placeholders::_2)); 
   std::function<int(int,int)> f2(std::bind(Sub,std::placeholders::_2,std::placeholders::_1));
   std::cout<<f1(2,3)<<std::endl;
   std::cout<<f2(2,3)<<std::endl;
}

在这里插入图片描述
不过相对而言,这个调整顺序并不是很常用。

总结

以上就是C++11的部分新特性的介绍。而C++11更多的新特性,例如智能指针我们会在后续的文章里面重点介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/339697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软考系统架构设计师】2022下综合知识历年真题

【软考系统架构设计师】2022下综合知识历年真题 【2022下架构真题第01题&#xff1a;绿色】 01.云计算服务体系结构如下图所示&#xff0c;图中①、②、③分别与SaaS、PaaS、Iaas相对应&#xff0c;图中①、②、③应为( ) A.应用层、基础设施层、平台层 B.应用层、平台层、基础…

Linux驱动开发(一)

linux驱动学习记录 一、背景 在开始学习我的linux驱动之旅之前&#xff0c;先提一下题外话&#xff0c;我是一个c语言应用层开发工作人员&#xff0c;在工作当中往往会和硬件直接进行数据的交互&#xff0c;往往遇到数据不通的情况&#xff0c;常常难以定位&#xff0c;而恰巧…

静态分析工具Cppcheck在Windows上的使用

之前在https://blog.csdn.net/fengbingchun/article/details/8887843 介绍过Cppcheck&#xff0c;那时还是1.x版本&#xff0c;现在已到2.x版本&#xff0c;这里再总结下。 Cppcheck是一个用于C/C代码的静态分析工具&#xff0c;源码地址为https://github.com/danmar/cppcheck …

Python之字符串精讲(上)

前言 字符串是所有编程语言在项目开发过程中涉及最多的一个内容。大部分项目的运行结果&#xff0c;都需要以文本的形式展示给客户&#xff0c;曾经有一位久经沙场的老程序员说过一句话&#xff1a;“开发一个项目&#xff0c;基本上就是在不断的处理字符串”。下面对Python中…

自命为缓存之王的Caffeine(3)

您好&#xff0c;我是湘王&#xff0c;这是我的CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e;缓存的存储空间是远远小于磁盘的。所以对于有些过期的数据&#xff0c;就需要定期进行清理&#xff0c;腾出存储空间。Caffeine又是怎么做的呢&#xff1f;Caffei…

SpringBoot+Vue在线小说系统

简介&#xff1a;本项目采用了基本的springbootvue设计的在线小说系统。详情请看截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 特别说明&#xff1a;本系统设计网络爬虫&#xff0c;遵循爬虫规则&#xff0c;此项目用于学习&a…

2023关键词:挑战

未失踪人口回归… 好久不见&#xff0c;不经意间拖更2个多月。今天周末&#xff0c;外面淅淅沥沥下着小雨&#xff0c;这种窝在床上的时刻最适合写点东西了。 但是建议大家在办公或者写博客的时候尽量还是端正坐姿&#xff0c;我就是因为喜欢这样靠在床背上&#xff0c;长时间…

Spring Security 从入门到精通

前言 Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多&#xff0c;因为相比与Spr…

Vue3+ElementPlus+koa2实现本地图片的上传

一、示例图二、实现过程利用Koa2书写提交图片的后台接口这个模块是我写的项目中的其中一个板块——上传图片&#xff0c;这个项目的后台接口主要是是使用了后端的Koa2框架&#xff0c;前端小伙伴想要试着自己书写一些增删改查的接口可以从这个入手&#xff0c;Koa2用来了解后端…

力扣HOT100 11-15

11.盛水最多的容器 思路&#xff1a;最大水量 底边 * 高度。较短的一边控制最大水量&#xff0c;因此&#xff0c;采用双指针的方式&#xff0c;左、右指针指向开始和末尾&#xff0c;逐个向中间移动&#xff0c;判断左右指针所指向的高度哪个更低&#xff0c;它就向中间移动一…

ubuntu中解决Failed to connect to 127.0.0.1 port xxxxx: Connection refused

ubuntu中解决Failed to connect to 127.0.0.1 port xxxxx: Connection refused 方法一 查看一下代理 git config --global http.proxy git config --global https.proxy 有就取消,没有就换一种方法 git config --global --unset http.proxy git config --global --unse…

计算机网络之http03:HTTPS RSA握手解析

不同的秘钥交换算法,握手过程可能略有差别 上文对HTTPS四次握手的学习 SSL/TLS Secure Sockets Layer/Transport Layer Security 协议握手过程 四次通信&#xff1a;请求服务端公钥 2次 秘钥协商 2次 &#xff08;1&#xff09;ClientHello请求 客户端向服务端发送client…

状态机设计举例

⭐本专栏针对FPGA进行入门学习&#xff0c;从数电中常见的逻辑代数讲起&#xff0c;结合Verilog HDL语言学习与仿真&#xff0c;主要对组合逻辑电路与时序逻辑电路进行分析与设计&#xff0c;对状态机FSM进行剖析与建模。 &#x1f525;文章和代码已归档至【Github仓库&#xf…

JavaScript内置支持类Array

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>内置支持类Array</title> </head> <body bgcolor"antiquewhite"> <script type"text/javasc…

【Spark分布式内存计算框架——Spark Core】8. 共享变量

第七章 共享变量 在默认情况下&#xff0c;当Spark在集群的多个不同节点的多个任务上并行运行一个函数时&#xff0c;它会把函数中涉及到的每个变量&#xff0c;在每个任务上都生成一个副本。但是&#xff0c;有时候需要在多个任务之间共享变量&#xff0c;或者在任务(Task)和…

T35,没有token是什么意思?

描述 输入一个升序数组 array 和一个数字S&#xff0c;在数组中查找两个数&#xff0c;使得他们的和正好是S&#xff0c;如果有多对数字的和等于S&#xff0c;返回任意一组即可&#xff0c;如果无法找出这样的数字&#xff0c;返回一个空数组即可。 数据范围: 0≤len(array)≤…

常规网页布局

单列布局1 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>单列布局1-头主尾等宽</title><style>.container {max-width: 960px; /*设置最大宽度为固定值*/margin: 0 auto; /*设置内部子…

Delphi 中TImageCollection和TVirtualImageList 控件实现high-DPI

一、概述RAD Studio允许你通过使用TImageCollection组件和TVirtualImageList组件&#xff0c;在你的Windows VCL应用程序中包含缩放、高DPI、多分辨率的图像。这两个组件位于Windows 10面板中&#xff1a;注意&#xff1a;如果你使用FireMonkey进行跨平台应用&#xff0c;请看T…

用VSCode在共用服务器上使用连接自己的Docker容器进行开发

问题描述 我们实验室有一台很牛的Linux服务器&#xff0c;核多卡多硬盘大&#xff0c;它是大家共用的&#xff0c;组里给我们每个人都创建了一个普通用户&#xff0c;没有sudo权限&#xff0c;所以不能用apt。 但是每个人对开发环境的需求都是不一样的&#xff0c;比如我要用…

年前无情被裁,面试大厂的这几个月…

2月份了&#xff0c;金三银四也即将来临&#xff0c;在这个招聘季&#xff0c;大厂也开始招人&#xff0c;但还是有很多人吐槽说投了很多简历&#xff0c;却迟迟没有回复… 另一面企业招人真的变得容易了吗&#xff1f;有企业HR吐槽&#xff0c;简历确实比以前多了好几倍&…