C++11常用的一部分新特性(下)

简介: C++11常用的一部分新特性(下)

STL容器中的empalce相关接口函数

emplace_back是可以不传参的,那么默认用户的就是匿名构造,传入的值就是0。

那么emplace_back的意义在哪里呢?

#include<iostream>
#include<vector>
using namespace std;
int main()
{
  vector<pair<int, string>>arr;
  arr.emplace_back(1, "xxx");//这里可以这样初始化(直接构造),push_back只能构造一个对象去传,当然emplace_back也可以构造一个对象传
  return 0;
}

其实就是一种优化,如果传入的是右值编译器可以直接优化成直接构造(移动构造或者是移动赋值都省下了),不需要任何拷贝构造或者是移动构造(如果是左值还是构造+深拷贝)。

lambda表达式

为什么要有lambda表达式

这个和仿函数有些类似。

举个例子,如果定义水果类,创建多个水果对象,那么他们分别有名字,价格,评价等等属性,如果想通过sort函数来实现对于不同对象的排序就要写很多个仿函数,非常的麻烦。

#include<iostream>
#include<vector>
#include <algorithm>
#include <functional>
using namespace std; 
struct Goods
{
  string _name;// 名字
  double _price;// 价格
  int _evaluate;// 评价
  Goods(const char* str, double price, int evaluate)
    :_name(str)
    , _price(price)
    , _evaluate(evaluate)
  {}
};
struct ComparePriceLess
{
  bool operator()(const Goods& gl, const Goods& gr)
  {
    return gl._price < gr._price;
  }
};
struct ComparePriceGreater
{
  bool operator()(const Goods& gl, const Goods& gr)
  {
    return gl._price > gr._price;
  }
};
int main()
{
  vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
  sort(v.begin(), v.end(), ComparePriceLess());
  sort(v.begin(), v.end(), ComparePriceGreater());
  return 0;
}

那么这个时候lambda表达式就可以上场了。

#include<iostream>
#include<vector>
#include <algorithm>
#include <functional>
using namespace std; 
struct Goods
{
  string _name;// 名字
  double _price;// 价格
  int _evaluate;// 评价
  Goods(const char* str, double price, int evaluate)
    :_name(str)
    , _price(price)
    , _evaluate(evaluate)
  {}
};
struct ComparePriceLess
{
  bool operator()(const Goods& gl, const Goods& gr)
  {
    return gl._price < gr._price;
  }
};
struct ComparePriceGreater
{
  bool operator()(const Goods& gl, const Goods& gr)
  {
    return gl._price > gr._price;
  }
};
int main()
{
  vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };
  sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
    return g1._price < g2._price; });//按照价格排序
  sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
    return g1._evaluate < g2._evaluate; });//按照评价排序
  return 0;
}

这是按照价格排序的结果:

这是按照评价排序:

lambda表达式的格式

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

lambda表达式各部分说明

[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。

(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。

mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。

{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。

注意:

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为

空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

#include<iostream>
using namespace std;
int main()
{
  //其实lambda就是要给可调用的对象
  auto compare = [](int x, int y) {return x > y; };//这个类型编译器认识,但是我们不认识
  cout << compare(1, 2) << endl;
  return 0;
}

那么,在外部定义的变量能在lambda表达式中使用吗?

#include<iostream>
using namespace std;
int main()
{
  int a = 10;
  int b = 20;
  auto add1 = []() { return a + b; };
  return 0;
}

这里是不可以的,因为是两个不同的作用域,这个时候需要捕捉这两个变量才可以使用。

auto add1 = [a, b]() { return a + b; };

这个时候编译就通过了。

那么如果想交换两个变量呢?

#include<iostream>
using namespace std;
int main()
{
  int a = 10;
  int b = 20;
  auto swap1 = [a, b]()
  {
    int c = a;
    a = b;
    b = c;
  };
  return 0;
}

这里是不允许的,如果想修改要加mutable。

auto swap1 = [a, b]()mutable
  {
    int c = a;
    a = b;
    b = c;
  };

但是外部的a和b并没有发生改变,也就是说捕捉的对象是传值拷贝,加了一个const的变量,mutable只是让他们变成非const属性的值。

如果想改变就要这样:

#include<iostream>
using namespace std;
int main()
{
  int a = 10;
  int b = 20;
  auto swap1 = [&a, &b]()//这里虽然是外部a和b的别名,但是不能修改a和b的名字,不然就不能捕捉了
  {
    int c = a;
    a = b;
    b = c;
  };
  return 0;
}

这里也无法在捕捉列表取地址。

在捕捉列表里面可以用=就是捕捉父作用域向上的变量,&是捕捉父作用域向上的变量别名。(向上就是在捕捉列表语句前面的所有变量)

#include<iostream>
using namespace std;
int main()
{
  int a = 10;
  int b = 20;
  auto add1 = [=]() { return a + b; };
  auto swap1 = [&]()
  {
    int c = a;
    a = b;
    b = c;
  };
  return 0;
}

注意:这些可以进行混合捕捉,比如对父作用域向上的变量进行传值捕捉,对于某一个或者是某些进行引用捕捉。

lambda的底层

#include<iostream>
using namespace std;
class Rate
{
public:
  Rate(double rate) : _rate(rate)
  {}
  double operator()(double money, int year)
  {
    return money * _rate * year;
  }
private:
  double _rate;
};
int main()
{
  // 函数对象
  double rate = 0.49;
  Rate r1(rate);
  r1(10000, 2);
  // lamber
  auto r2 = [=](double monty, int year)->double {return monty * rate * year;};
  r2(10000, 2);
  return 0;
}

第一个是仿函数的反汇编,第二个是lambda表达式的反汇编,也就是说本质都是一样的调用仿函数。

并且lambda表达式的类型名字也很繁琐。

包装器

function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

#include<iostream>
using namespace std;
template<class F, class T>
T useF(F f, T x)
{
  static int count = 0;
  cout << "count:" << ++count << endl;
  cout << "count:" << &count << endl;
  return f(x);
}
double f(double i)
{
  return i / 2;
}
struct Functor
{
  double operator()(double d)
  {
    return d / 3;
  }
};
int main()
{
  // 函数名
  cout << useF(f, 11.11) << endl;
  // 函数对象
  cout << useF(Functor(), 11.11) << endl;
  // lamber表达式
  cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
  return 0;
}

这里实例化的三分不同的函数,有没有什么办法让他不生成这么多的函数。

#include<iostream>
#include<functional>
using namespace std;
int f(int a, int b)
{
  return a + b;
}
struct Functor
{
public:
  int operator() (int a, int b)
  {
    return a + b;
  }
};
int main()
{
  function<int(int, int)> f1;//第一个int是返回值,括号里面的是参数
  f1 = f;//封装到f1中
  cout << f1(1, 2) << endl;
  function<int(int, int)> f2(f);//这种方法也可以将f封装到f2中
  cout << f2(1, 2) << endl;
  function<int(int, int)> f3 = Functor();
  cout << f3(1, 2) << endl;
  /*function<int(int, int)> f4(Functor());//这里不可以,因为编译器会识别成为函数指针
  cout << f4(1, 2) << endl;*/
  function<int(int, int)> f5 = [](const int a, const int b){return a + b; };
  cout << f5(1, 2) << endl;
}

类中的成员函数也是可以包装的,但是要注意:

#include<iostream>
#include<functional>
using namespace std;
class Plus
{
public:
  static int plusi(int a, int b)
  {
    return a + b;
  }
  int plusd(int a, int b)
  {
    return a + b;
  }
};
int main()
{
  //这里存入的是函数指针
  function<int(int, int)> f1 = &Plus::plusi;//静态成员函数可以不加&
  cout << f1(1, 2) << endl;
  function<int(Plus, int, int)> f2 = &Plus::plusd;//非静态成员函数必须加&,这里还要加一个参数,因为传参还有一个this指针
  cout << f2(Plus(), 1, 2) << endl;//这里是传进去一个Plus类型的对象,其实就是利用这个对象调用该成员函数而已
}

其实包装器就是对于可调用对象类型的大统一。

那么使用的场景呢?

力扣:逆波兰表达式

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string, function<int(int, int)>> opMap=
        {
            {"+",[](int x, int y){return x+y;}},
            {"-",[](int x, int y){return x-y;}},
            {"/",[](int x, int y){return x/y;}},
            {"*",[](int x, int y){return x*y;}}
        };//这里就是统一了类型
        for(auto& e:tokens)//在map中查找符号
        {
            if(opMap.count(e))
            {
                int a = st.top();
                st.pop();
                int b = st.top();
                st.pop();
                st.push(opMap[e](b, a));//找到之后就将栈中的两个值通过map中储存的包装器中的lamber表达式进行运算,这里要注意数的顺序,先去取出来的在左边,后取出来的在右边
            }
            else
            {
                st.push(stoi(e));
            }
        }
        return st.top();
    }
};

bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind(Fn&& fn, Args&&... args);

举例:

#include<iostream>
#include <functional>
using namespace std;
int Plus(int a, int b)
{
  return a - b;
}
int main()
{
  //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
  function<int(int, int)> func1 = bind(Plus, placeholders::_1,placeholders::_2);//placeholders是占位对象
  cout << func1(1, 2) << endl;
  //_1代表本来的第一个参数,_2代表本来的第二个参数
  //这里调参数
  function<int(int, int)> func3 = bind(Plus, placeholders::_2, placeholders::_1);
  cout << func3(1, 2) << endl;
  return 0;
}

那么实际的作用在哪里呢?

比如包装器包装的是类的成员函数,传参的时候第一个总是类的匿名对象,写起来很麻烦。

#include<iostream>
#include <functional>
using namespace std;
class Sub
{
public:
  int sub(int a, int b)
  {
    return a - b;
  }
};
int main()
{
  function<int(int, int)> func1 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
  cout << func1(1, 2) << endl;//这里本来是三个参数,但是第一个参数已经被绑定了,包装器的参数也不用写三个了,这里也不用传三个参数了
  return 0;
}

相关文章
|
1月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
103 59
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
1月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
26天前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
32 0
|
2月前
|
编译器 C++ 计算机视觉
C++ 11新特性之完美转发
C++ 11新特性之完美转发
49 4
|
2月前
|
Java C# C++
C++ 11新特性之语法甜点1
C++ 11新特性之语法甜点1
31 4
|
2月前
|
安全 程序员 编译器
C++ 11新特性之auto和decltype
C++ 11新特性之auto和decltype
38 3
|
2月前
|
设计模式 缓存 安全
C++ 11新特性之week_ptr
C++ 11新特性之week_ptr
35 2