当前位置: 首页 > wzjs >正文

安阳网站建设开发有哪些网站可以免费推广

安阳网站建设开发,有哪些网站可以免费推广,做平台网站怎么做,杭州建站模板展示目录 一、lambda表达式: lambda表达式语法: lambda表达式的使用: lambda表达式的底层原理: 二、可变参数模板: 参数包: 递归解析参数包: 逗号表达式解析参数包: STL中的emp…

目录

一、lambda表达式:

lambda表达式语法:

lambda表达式的使用:

lambda表达式的底层原理:

二、可变参数模板:

参数包:

递归解析参数包:

逗号表达式解析参数包:

STL中的emplace:

三、移动构造和移动赋值:

四、包装器:

function:

bind:


一、lambda表达式:

如下是一个描述水果的结构体,其中有着水果名,价格,数量

struct Fruit
{string _name;int _price;int _num;
};

接着在主函数中,创建一个vector来存放水果并初始化

vector<Fruit> v = { {"苹果",5,10},{"梨子",4,9},{"西瓜",10,2},{"橙子",6,5} };

接着想要对上述的vector进行排序,分别按照价格的降序,升序,数量的降序,升序

用简单的sort函数是不行的,在以前,我们会使用仿函数来进行判断操作:

如下就是搞四个仿函数:

struct PriceLess
{bool operator()(const Fruit& f1,const Fruit& f2){return f1._price > f2._price;}
};struct PriceGreater
{bool operator()(const Fruit& f1, const Fruit& f2){return f1._price < f2._price;}
};struct NumLess
{bool operator()(const Fruit& f1, const Fruit& f2){return f1._num > f2._num;}
};struct NumGreater
{bool operator()(const Fruit& f1, const Fruit& f2){return f1._num < f2._num;}
};

接着在sort的最后加上一个仿函数的类,这样就能够完成四种排序:

	sort(v.begin(), v.end(), PriceLess());//按照价格降序sort(v.begin(), v.end(), PriceGreater());//按照价格升序序sort(v.begin(), v.end(), NumLess());//按照数量降序sort(v.begin(), v.end(), NumGreater());//按照数量升序序

这样,在main函数中,就能够成功打印出排好序的水果了

但是如果这样写当定义仿函数的时候,不按照规范定义函数,并且定义的仿函数离调用的地方远得很,这样就会降低代码的可读性

在C++11里面引入了lambda表达式,在这种场景就可以使用lambda表达式来增强代码的可读性

lambda表达式是一个匿名函数,恰当使用lambda表达式可以让代码变得简洁,并且可以提高代码的可读性

lambda表达式语法:

lambda表达式完整格式:

[capture-list] (parameters) mutable -> return-type {statement};

[capture-list]:这是捕捉列表,在lambda表达式的第一个位置,是不可以省略的,如果不捕捉就写[],其作用是捕捉上文中的变量提供给lambda函数使用,但是捕捉的变量是不能够修改的,如果想要修改就需要加上后面讲的mutable

[var]:表示值传递方式捕捉变量var
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)
[&var]:表示引用传递捕捉变量var
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)
[this]:表示值传递方式捕捉当前的this指针

注意:

1、上述的父作用域是指包含含lambda函数的语句块

2、语法上捕捉列表可由多个捕捉项组成,并以逗号分割

        比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
                   [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

3、捕捉列表不允许变量重复传递,否则就会导致编译错误。
        比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

4、在块作用域以外的lambda函数捕捉列表必须为空

5、在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
非局部变量都会导致编译报错

6、lambda表达式之间不能相互赋值,即使看起来类型相同

(parameters):参数列表,和普通的函数的参数列表是一样的,如果没有参数要传参的话,是可以省略的

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

-> return-type:这是返回值类型,一般可以省略,如果没有返回值省略,有明确返回值的时候,会让编译器自动推导,所以也可以省略

{statement}:这是函数主体,可以使用在参数列表中传进来的参数,也可以使用捕捉列表中捕捉的参数

lambda表达式的使用:

下面通过lambda表达式构建一个简单的两整数相加

本来是一个匿名函数,可以交给一个有名函数对象,这样使用起来就和正常的函数没什么两样了

int main()
{int x = 1, y = 2;//auto自动推导类型auto retfun = [](int& a, int& b) { return a + b; };int ret = retfun(x, y);//使用时类似于函数cout << ret << endl;return 0;
}

也可以只写一行,这也就是匿名函数

int main()
{int x = 1, y = 2;auto ret = [](int& a, int& b) { return a + b; }(x, y);cout << ret << endl;return 0;
}

当然,在函数体里面也不只能写一行,也可以写多行,接下来实现一个swap:

int main()
{auto Swap = [](int& x, int& y) {int tmp = x;x = y;y = tmp;};int a = 1, b = 2;cout << "a = " << a << " b = " << b << endl;Swap(a, b);cout << "a = " << a << " b = " << b << endl;return 0;
}

这里也可以使用一下引用捕捉列表:

int main()
{int a = 1, b = 2;cout << "a = " << a << " b = " << b << endl;auto Swap = [&]() {int tmp = a;a = b;b = tmp;};Swap();cout << "a = " << a << " b = " << b << endl;return 0;
}

接着使用一下引用捕捉结合值捕捉:

int main()
{int a = 1, b = 1;cout << "a = " << a << " b = " << b << endl;auto Swap = [&a,b]()mutable {a++;b++;};Swap();cout << "a = " << a << " b = " << b << endl;return 0;
}

如上,在函数调用前后,发现a被++了,但是b没有被++,这其实和函数那里的传引用和传值是差不多的,这里的a是一样的,里面的b其实是外面的b的一份拷贝,所以自然也就不会影响外面的b

lambda表达式的底层原理:

实际上,在编译器中,lambda表达式的底层是被转化成了仿函数4

首先:我们看看函数,仿函数,lambda表达式生成的函数对象

int add(int x, int y)
{return x + y;
}class Add
{
public:int operator()(int x,int y){return x + y;}
};int main()
{//普通函数int x = 1,y = 2;auto add1 = add(1,2);//仿函数函数Add add2;//lambda表达式auto add3 = [](int x,int y) { return  x + y; };add3(x,y);cout << "函数 : " << sizeof(add1) << endl;cout << "仿函数 : " << sizeof(add2) << endl;cout << "lambda表达式 : " << sizeof(add3) << endl;return 0;
}

所以可以发现,仿函数和lambda表达式是一样的,其中仿函数是一个空类,其没有成员变量,大小只为1字节,所以lambda表达式也是生成了一个空类

接下来看看仿函数和lambda表达式的反汇编:

class Add
{
public:Add(int base):_base(base){}int operator()(int num){return _base + num;}
private:int _base;
};
int main()
{int base = 1;//函数对象Add add1(base);add1(2);//lambda表达式auto add2 = [base](int num){ return base + num; };add2(2);return 0;
}

将上述代码转化为反汇编,我们可以看到:

在Add函数中,创建对象的时候调用构造函数,并且在后面仿函数中调用了operator()

接下来观察lambda表达式,同样也观察到了类似的代码

在上述的汇编代码中,也可以看到:Add函数在创建函数对象的时候,会调用构造,在使用函数对象的时候,会调用operator(),

同样的道理在lambda表达式中,编译器会先自动生成一个lambda类,在这个类中,会重载operator(),所以lambda表达式的底层实现就是仿函数

lambda表达式的类:

我们可以通过typeid(ret).name(),这样来观察lambda表达式生成的类型


int main()
{auto ret1 = [](int x, int y) {return x + y; };auto ret2 = [](int x, int y) {return x + y; };auto ret3 = [](int x, int y) {return x + y; };cout << typeid(ret1).name() << endl;cout << typeid(ret2).name() << endl;cout << typeid(ret3).name() << endl;return 0;
}

如上,可以看到,lambda表达式生成的类型是不同的,即使表面上看起来lambda表达式是一模一样的,但是编译器会将这些处理成不同的类名,这样就算是两个一模一样的lambda表达式,它们的类型都是不同的

二、可变参数模板:

在C++11以前,类模板和函数模板只能包含固定数量的模板参数,但是在C++11中增添了新特性,能够让用户定义任意数量的参数模板函数,这样大大提高了模板参数泛化

其实可变参数我们已经使用过了,在如下的printf中:

如上,在printf中,可以加上任意个%d,这就是可变参数列表,也就是随便传入多个参数,函数都能够进行接收

那么回到我们的可变参数模板

参数包:

把所有传入的参数,不论数量,类型,统统进行打包,形成了可变参数包

如下为可变参数模板的一般形式:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
template <class ...Args>
void func(Args... args)
{//函数体
}

那么接下来传参的时候,就能够允许传任意数量,任意类型的参数,这样能够极大提高泛化

template<class ...Args>
void Func(Args... argc)
{}int main()
{Func(1);Func(1, 2);Func(1, 1.1);Func(1, 'x');Func(1, "xxxxx");return 0;
}

并且在函数内部还能够打印传入了多少个参数:

template<class ...Args>
void Func(Args... argc)
{cout << sizeof...(argc) << endl;
}int main()
{Func(1);                    //1Func(1, 2, 3, 4, 5);        //5Func(1, 1.1, 'x', "xxxxx"); //4Func(1,'x');                //2Func(1, "xxxxx");           //2return 0;
}

递归解析参数包:

但是对于这个参数包里面的类型不能够直接拿到,这也是一个缺点,如果想拿是只能够拿到第一个参数类型的,所以就需要通过函数递归来进行参数包的解析:

递归思路:

  • 我们给函数模板不传一整个参数包,而是一个函数类型模板参数+函数模板参数包
  • 这样每一次进入这个函数就能够让第一个参从参数包中分离
  • 接着解析完这个参数包后,在后面继续递归使用这个参数包,进而每次将参数包中的第一个参数解析出来,然后还要写一个无参数的函数重载,当参数包中没有参数之后,就会走无参数的重载函数,这样就能够让递归退出
int num = 1;//这是递归出口函数,是func函数的重载
//当参数包中的参数为空后就调用这个函数结束
void Func()
{num = 1;cout << endl;
}//每次都解析第一个参数,在后面递归Func函数,进而每次都解析函数包的第一个参数
template<class T, class ...Args>
void Func(const T& t,Args... args)
{cout << "第" << num++ << "个参数是 : " << t << endl;Func(args...);
}int main()
{cout << "Func(1) : " << endl;Func(1);                  cout << "Func(1, 2, 3, 4, 5);" << endl;Func(1, 2, 3, 4, 5);       cout << "Func(1, 1.1, 'x', xxxxx); " << endl;Func(1, 1.1, 'x', "xxxxx"); cout << "Func(1,'x');" << endl;Func(1,'x');               cout << "Func(1, xxxxx);" << endl;Func(1, "xxxxx");      return 0;
}

逗号表达式解析参数包:

本质是通过数组的列表初始化,当创建数组的时候,将参数包传入数组,然后当数组进行初始化的时候,将参数包展开了,展开后就完成了解析的工作

template <class T>
int FuncArg(const T& t)
{cout << "该参数类型是 : " << typeid(t).name() << " 内容是 : " << t << endl;return 0;
}//args代表0-N的参数包
template <class ...Args>
void Func(Args... args)
{int a[] = { FuncArg(args)...};cout << endl;
}int main()
{cout << "Func(1) : " << endl;	//在上述展开后int a1[] = { FuncArg(1) };Func(1);                  cout << "Func(1, 2, 3, 4, 5);" << endl;//在上述展开后int a2[] = { FuncArg(1),FuncArg(2),FuncArg(3),FuncArg(4),FuncArg(5) };Func(1, 2, 3, 4, 5);       cout << "Func(1, 1.1, 'x', xxxxx); " << endl;//在上述展开后int a3[] = { FuncArg(1),FuncArg(1.1),FuncArg('x'),FuncArg("xxxxx")};Func(1, 1.1, 'x', "xxxxx"); cout << "Func(1,'x');" << endl;//在上述展开后int a4[] = { FuncArg(1),FuncArg('x')};Func(1,'x');               cout << "Func(1, xxxxx);" << endl;//在上述展开后int a5[] = { FuncArg(1),FuncArg("xxxxxx") };Func(1, "xxxxx");      return 0;
}

STL中的emplace:

在C++11中引入了emplace这样的接口,这些新增的函数依赖可变参数包

 如上参数部分并不是右值引用,而是万能引用,其既可以接收左值引用,也可以接收右值引用,还可以接收参数包

emplace系列接口的使用方式

emplace和原容器的插入接口类似,例如emplace_back具备了push_back的所有功能,并且还具备更大的优势

这里以list为例:

其中,无论是push_back还是emplace_back都既能够传左值,又能够传右值,但是push_back能够进行列表初始化,emplace_back不能够进行列表初始化

int main()
{list<pair<int, ppr::string>> lt1;cout << "emplace_back" << endl;cout << endl;cout << "传左值" << endl;pair<int, ppr::string> kv1(1, "one");lt1.emplace_back(kv1);								//传左值cout << endl;cout << "传右值" << endl;lt1.emplace_back(pair<int, ppr::string>(2, "two")); //传右值cout << endl;cout << "传参数包" << endl;lt1.emplace_back(3, "three");                      //传参数包cout << endl;cout << "push_back" << endl;cout << endl;list<pair<int, ppr::string>> lt2;cout << "传左值" << endl;pair<int, ppr::string> kv2(1, "one");lt2.push_back(kv2);								//传左值cout << endl;cout << "传右值" << endl;lt2.push_back(pair<int, ppr::string>(2, "two")); //传右值cout << endl;cout << "列表初始化" << endl;lt2.push_back({ 3, "three" });					  //列表初始化return 0;
}

通过上述的代码,我们可以知道:

无论是emplace_back还是push_back,当传左值的时候都是一样的,首先实例化左值对象,这步是调用构造函数的,接着匹配拷贝构造,进而实现深拷贝

当传右值的时候,实例化右值对象,这里就走移动构造,进而实现移动拷贝

当emplace传入参数包的时候,这直接将纯右值作为参数包进行传递,传递中不展开参数包,直到构造函数再将参数包展开,这样直接传递参数,得益于参数包的优势

emplace的意义:

emplace系列相比于push_back的特点是传入参数包,利用参数包直接构造出对象,这样就能减少一次拷贝,这就是为什么emplace系列接口更高效

emplace系列接口并不是在所有场景下都比原有的插入接口高效,如果传入的是左值对象或右值对象,那么emplace系列接口的效率其实和原有的插入接口的效率是一样的

emplace系列接口真正高效的情况是传入参数包的时候,直接通过参数包构造出对象,避免了中途的一次拷贝

三、移动构造和移动赋值:

在C++11之前,有6个默认成员函数:

  • 构造函数
  • 析构函数
  • 拷贝构造函数
  • 拷贝复制函数
  • 取地址重载函数
  • const取地址重载函数

这些就是如果不写的话,编译器会默认生成的

在C++11之后,有了右值引用+移动语义,所以在类中对应生成了两个新的默认构造函数:移动构造和移动赋值运算符重载

什么时候会生成移动构造函数:

  • 没有自己实现移动构造函数
  • 没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个

满足以上两点,并且没有自己写移动构造,那么编译器就会自动生成一个移动构造

默认生成的移动构造有什么用:

  • 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝
  • 自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用它自己的移动构造,没有实现就调用拷贝构造

什么时候会生成移动赋值重载函数:

  • 没有自己实现移动赋值重载函数
  • 没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个

满足以上两点,并且没有自己写移动赋值重载函数,那么编译器会自动生成一个移动赋值重载函数

默认生成的移动赋值重载函数有什么用:

  • 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝
  • 自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用它自己的移动赋值,没有实现就调用拷贝赋值

接下来通过代码感受:

class Person
{
public://构造函数Person(const char* name = "", int age = 0):_name(name), _age(age){}//拷贝构造函数Person(const Person& p):_name(p._name), _age(p._age){}//拷贝赋值函数Person& operator=(const Person& p){if (this != &p){_name = p._name;_age = p._age;}return *this;}//析构函数~Person(){}
private:ppr::string _name; int _age;        
};int main()
{Person s1("zhangsan", 20);cout << endl;Person s2 = std::move(s1);return 0;
}

如上,这是写的一个Person类,实现Person类中的string是我们自己写的string类的,一开始将析构函数 、拷贝构造、拷贝赋值重载中的任意一个或多个都不屏蔽,当运行的时候,发现会走拷贝构造

当将Person类中的拷贝构造,拷贝赋值,析构函数都屏蔽掉,再次运行程序就会发现走的是移动构造函数

四、包装器:

function:

到现在为止,我们已经学习了三种可调用函数对象类型

函数指针

仿函数

lambda表达式

所以在调用函数的时候,如:int ret = func();

那么这个func是什么呢 ----- 可能是函数名?函数指针?函数对象(仿函数对象)?也有可能是lamber表达式对象?所以这些都是可调用的类型

并且如果想让这些对象用同一个vector装起来显然是不能够直接装的,这里也不能使用auto,因为auto是推导类型,所以在C++11中,就引入了function这个包装器,将可调用对象进行包装,function是基于可变参数模板实现的

原型:

#include <functional>
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;

Ret:被调用函数的返回值

Args:被调用函数的形参

这样就能够通过包装器来包装之前的三个函数对象了

int func1(int a,int b)
{cout << "void func1(int a) : "<< endl;return a + b;
}struct func2
{
public:int operator()(int a,int b){cout << "void operator()(int a) : "<< endl;return a + b;}
};int main()
{auto func3 = [](int a,int b)->int {cout << "auto lambda = [](int a,int b)->int  : " << endl;return a+b; };function<int(int,int)> f1 = func1;function<int(int,int)> f2 = func2();function<int(int,int)> f3 = func3;vector<function<int(int,int)>> v = {f1,f2,f3};int n = 1;for (auto& ch : v){cout << ch(n,n) << endl;n++;}return 0;
}

这样,就能够将他们全部放入同一个vector中了

function解决模板效率低下问题,减少实例化

template<class F,class T>
double Func(F f,T t)
{static int count = 0;count++;cout << "count = " << count << " &count = " << &count << endl;return f(t);
};double func1(double n)
{return n / 3;
}struct func2
{
public:double operator()(double n){return n / 4;}
};int main()
{//function<double(double)> f1 = func1;//cout << Func(f1, 10) << endl;cout << Func(func1, 10) << endl;//function<double(double)> f2 = func2();//cout << Func(f2, 10) << endl;cout << Func(func2(), 10) << endl;//function<double(double)> f3 = [](int n)->double { return n / 5;  };//cout << Func(f3, 10) << endl;cout << Func([](double n)->double { return n / 5;  }, 10) << endl;return 0;
}

如上,通过上述count的值,我们知道这是因为Func这个函数被实例化了三份,这样如果很多的话就会很消耗资源的,但是也可以用function包装器将函数指针,仿函数,lambda表达式包装起来,这样就会只实例化出一份

int main()
{function<double(double)> f1 = func1;cout << Func(f1, 10) << endl;function<double(double)> f2 = func2();cout << Func(f2, 10) << endl;function<double(double)> f3 = [](int n)->double { return n / 5;  };cout << Func(f3, 10) << endl;return 0;
}

function包装器的意义

  • 将可调用对象的类型进行统一,便于我们对其进行统一化管理
  • 包装后明确了可调用对象的返回值和形参类型,更加方便使用者使用

bind:

这个包装器用来接收一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,可以修改参数传递时的位置以及参数个数,C++中的bind本质是一个函数模板

函数原型:

template <class Fn, class... Args>
bind(Fn&& fn, Args&&... args);

参数解析:

fn:可调用对象

args:要绑定的参数列表,这里是万能引用不是右值引用,传值或者是占位符

占位符:placeholders作用域中的_1,_2等等

bind改变传参顺序


void Func(int a, int b)
{cout << "int Func(int a, int b)中 a = " << a << " b = " << b << endl;
}int main()
{//正常调用Func(1, 2);//bind函数改变传参auto func = bind(Func, placeholders::_2, placeholders::_1);func(1, 2);return 0;
}

不仅仅可用auto推导,还可以用包装器包装类型

function<void(int,int)> func = bind(Func, placeholders::_2, placeholders::_1);

在使用 bind 绑定改变参数传递顺序时,参与交换的参数类型,至少需要支持隐式类型转换,否则是无法交换传递的

bind固定参数

bind还可以固定住一个参数,如上,我们将第一个参数固定为100,后面就只需要传一个参数即可‘’


void Func(int a, int b)
{cout << "int Func(int a, int b)中 a = " << a << " b = " << b << endl;
}int main()
{auto func = bind(Func, 100, placeholders::_1);//function<void(int)> func = bind(Func, 100, placeholders::_1);func(1);func(2,3);return 0;
}

如上,如果参数多了的话,后面多出来的就会被舍弃掉,并且无论传参顺序是怎么样的,placeholders作用域中总是从_1开始的

bind包装器的意义

  • 将一个函数的某些参数绑定为固定的值,让我们在调用时可以不用传递某些参数。
  • 可以对函数参数的顺序进行灵活调整。
http://www.dtcms.com/wzjs/193154.html

相关文章:

  • 无锡做网站要多少钱单页网站制作
  • 深圳网站建设科技有限公司网站排名查询
  • 设计好的制作网站seo站内优化培训
  • 做简历网站网页设计用什么软件
  • 西安进一步优化近期防疫措施优化措施最新回应
  • 怎么做网站海外运营推广百度热线电话
  • 中升乙源建设工程有限公司网站网站怎么收录到百度
  • 建站之星做的网站如何导出网站seo快速优化
  • 江干区住房和城市建设局网站百度推广工具
  • 十堰网络销售杭州云优化信息技术有限公司
  • wordpress get_attached_media北京网站seo哪家公司好
  • 做家政网站公司东莞发布最新通告
  • 成都网站建设优秀公司搜索历史记录
  • 自己做头像的网站洛阳seo网络推广
  • 厦门网站建设哪家强关键词seo报价
  • 做外贸做的很好的网站爱站网关键词挖掘工具站长工具
  • 打开网站弹出一张图片 怎么做怎样把自己的产品放到网上销售
  • 天水 网站建设招聘经典软文案例200字
  • 自己做游戏资讯网站推广app赚佣金平台有哪些
  • 怎样学做网站线下推广怎么做
  • 动漫设计本科seo标题生成器
  • 网易云音乐wordpress插件楚雄seo
  • 酒店网站搜索引擎优化方案站长工具高清吗
  • 网站改版设计费进什么科目广州网络营销推广公司
  • 做网站毕设答辩问题软件开发外包平台
  • 中国标准物质信息网网站建设如何用模板做网站
  • 有些人做网站不用钱的,对吗?2345导网址导航下载
  • 网站建设与推广中国营销网站
  • 个人类网站有哪些投放广告的渠道有哪些
  • wordpress影视站怎么推广游戏代理赚钱