合肥网站建设卫来科技,优化方案英语,网页游戏开发语言,网站技术实现方案致前行的人#xff1a; 人生像攀登一座山#xff0c;而找寻出路#xff0c;却是一种学习的过程#xff0c;我们应当在这过程中#xff0c;学习稳定冷静#xff0c;学习如何从慌乱中找到生机。 目录 1.C11简介
2.统一的列表初始化
2.1 #xff5b;#xff5d;初始化 …致前行的人 人生像攀登一座山而找寻出路却是一种学习的过程我们应当在这过程中学习稳定冷静学习如何从慌乱中找到生机。 目录 1.C11简介
2.统一的列表初始化
2.1 初始化
编辑 2.2 std::initializer_list
3. 声明
3.1 auto
3.2 decltype
3.3nullptr
4 范围for循环
5.STL中一些变化
6 右值引用和移动语义
6.1 左值引用和右值引用
6.2 左值引用与右值引用比较
6.3 右值引用使用场景和意义
6.4 右值引用引用左值及其一些更深入的使用场景分析
6.5完美转发
7.新的类功能
8.lambda表达式
8.1lambda表达式语法
8.2 函数对象与lambda表达式
9.可变参数模板
10.包装器
10.1function包装器
10.2bind 1.C11简介
在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)使得C03这个名字已经取代了 C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞 进行修复语言的核心部分则没有改动因此人们习惯性的把两个标准合并称为C98/03标准。 从C0x到C11C标准10年磨一剑第二个真正意义上的标准珊珊来迟。相比C98/03C11则带来了数量可观的变化其中包含了约140个新特性以及对C03标准中约600个缺陷的修正这使得C11更像是从C98/03中孕育出的一种新语言。相比较而言C11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全不仅功能更强大而且能提升程序员的开发效率公司实际项目开发中也用得比较多所以我们要作为一个重点去学习。C11增加的语法特性非常篇幅非常多我们这里没办法一一讲解所以本节课程主要讲解实际中比较实用的语法。
2.统一的列表初始化
2.1 初始化
在C98中标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如
struct Point
{int _x;int _y;
};
int main()
{int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0;
}C11扩大了用大括号括起的列表(初始化列表)的使用范围使其可用于所有的内置类型和用户自 定义的类型使用初始化列表时可添加等号()也可不添加。
struct Point
{int _x;int _y;
};
int main()
{int x1 1;int x2{ 2 };int array1[]{ 1, 2, 3, 4, 5 };int array2[5]{ 0 };Point p{ 1, 2 };// C11中列表初始化也可以适用于new表达式中int* pa new int[4]{ 0 };return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化
class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout Date(int year, int month, int day) endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2022, 1, 1); // old style// C11支持的列表初始化这里会调用构造函数初始化Date d2{ 2022, 1, 2 };Date d3 { 2022, 1, 3 };return 0;
} 2.2 std::initializer_list
文档介绍
std::initializer_list是什么类型
int main()
{// the type of il is an initializer_listauto il { 10, 20, 30 };cout typeid(il).name() endl;return 0;
} std::initializer_list使用场景 std::initializer_list一般是作为构造函数的参数C11对STL中的不少容器就增加 std::initializer_list作为参数的构造函数这样初始化容器对象就更方便了。也可以作为operator 的参数这样就可以用大括号赋值。
例如 int main()
{vectorint v1 { 1,2,3,4,5 };vectorint v2{ 1,2,3,4,5 };listint l1{ 1,2 };mapstring, string dict { {string,字符串}, {sort,排序} };return 0;
}
3. 声明
3.1 auto
在C98中auto是一个存储类型的说明符表明变量是局部自动存储类型但是局部域中定义局 部的变量默认就是自动存储类型所以auto就没什么价值了。C11中废弃auto原来的用法将 其用于实现自动类型推断这样要求必须进行显示初始化让编译器将定义对象的类型设置为初 始化值的类型。 int main()
{mapstring, int m;auto it1 m.begin();cout typeid(it1).name() endl;return 0;
}
运行截图 3.2 decltype
关键字decltype将变量的类型声明为表达式指定的类型。
// decltype的一些使用使用场景
templateclass T1, class T2
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout typeid(ret).name() endl;
}
int main()
{const int x 1;double y 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(x) p; // p的类型是int*cout typeid(ret).name() endl;cout typeid(p).name() endl;F(1, a);return 0;
}
运行截图 3.3nullptr
由于C中NULL被定义成字面量0这样就可能回带来一些问题因为0既能指针常量又能表示 整形常量。所以出于清晰和安全的角度考虑C11中新增了nullptr用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
4 范围for循环
容器支持迭代器之后编译器自动识别范围for替换为迭代器进行遍历
int main()
{vectorint v{ 1,2,3,4,5 };for (auto e: v){cout e ;}cout endl;return 0;
}
运行截图 5.STL中一些变化
在C11更新之后对CSTL进行了修改包括增加了一些容器和对原来存在的容器新增了新的接口函数。
新容器 1.array:是针对C语言中数组越界检查不严格所出的C语言数组越界检查是抽查而array数组越界严格检查当出现越界读或者越界写的时候就会抛异常
int main()
{arrayint, 10 a;a[10]; //越界读a[10] { 0 }; //越界写return 0;
}
运行截图 容器中的一些新方法
如果我们再细细去看会发现基本每个容器中都增加了一些C11的方法但是其实很多都是用得 比较少的。比如提供了cbegin和cend方法返回const迭代器等等但是实际意义不大因为begin和end也是可以返回const迭代器的这些都是属于锦上添花的操作。 实际上C11更新后容器中增加的新方法最有用的插入接口函数的右值引用版本
但是这些接口到底意义在哪网上都说他们能提高效率他们是如何提高效率的 请看下面的右值引用和移动语义章节的讲解。另外emplace还涉及模板的可变参数也需要再继 续深入学习后面章节的知识。
6 右值引用和移动语义
6.1 左值引用和右值引用
传统的C语法中就有引用的语法而C11中新增了的右值引用语法特性所以从现在开始我们 之前学习的引用就叫做左值引用。无论左值引用还是右值引用都是给对象取别名。
什么是左值什么是左值引用
左值是一个表示数据的表达式如变量名或解引用的指针可以获取它的地址并且可以给它进行赋值左值可以出现在赋值符号的左边右值不能出现在左边。定义时const修饰的左值不能给它赋值但是可以取它的地址左值引用就是给左值取别名。
int main()
{//以下的p b c *p都是左值int* p new int(0);int b 1;const int c 1;//以下几个是对上面左值的引用int* rp p;int rb b;const int rc c;int pValue *p;return 0;
}
什么是右值什么是右值引用
右值也是一个表示数据的表达式如字面常量表达式返回值函数返回值这个不能是左值引用返回等等。右值可以出现在赋值符号的右边但是不能出现在赋值符号的左边右值不能取地址右值是不能够被修改的。右值引用就是对右值取别名。
int main()
{double x 1.1;double y 2.2;//以下几个是常见的右值10;x y;fmin(x, y);//以下几个都是对右值的引用int rr1 10;double rr2 x y;double rr3 fmin(x, y);return 0;
}
需要注意的右值是不能取地址的但是给右值取别名之后会导致右值被存储到特定位置且可 以取到该位置的地址也就是说例如不能取字面量10的地址但是rr1引用后rr1就具有了左值属性可以对rr1取地址也可以修改rr1。如果不想rr1被修改可以用const int rr1 去引用是不是感觉很神奇这个了解一下实际中右值引用的使用场景并不在于此这个特性也不重要。
int main()
{double x 1.1, y 2.2;int rr1 10;const double rr2 x y;rr1;rr2; //errreturn 0;
}
运行截图
6.2 左值引用与右值引用比较
左值引用总结
1.左值引用只能引用左值不能引用右值
2.但是const左值既可以引用左值也可以引用右值
int main()
{//左值只能引用左值不能引用右值int a 10;int ra1 a;//int ra2 10; //err因为10是左值//const左值既可以引用左值也可以引用右值const int ra3 10;const int ra4 a;return 0;
}
右值引用总结:
1.右值引用只能引用右值不能引用左值
2.右值可以引用move后的左值
int main()
{//右值引用只能引用右值不能引用左值int r1 10;int a 10;//int r2 a; // error C2440: “初始化”: 无法从“int”转换为“int ”//右值引用可以引用move以后的左值int r3 move(a);return 0;
}
6.3 右值引用使用场景和意义
前面我们可以看到左值引用既可以引用左值和又可以引用右值那为什么C11还要提出右值引 用呢是不是画蛇添足呢下面我们来看看左值引用的短板右值引用是如何补齐这个短板的
namespace ns
{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str _size;}string(const char* str ):_size(strlen(str)),_capacity(_size){cout string(char* str) endl;_str new char[_capacity 1];strcpy(_str, str);}void swap(string s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string(const strings):_str(nullptr){cout string(const string s) -- 深拷贝 endl;string tmp(s._str);swap(tmp);}string operator(const string s){cout string operator(string s) -- 深拷贝 endl;string tmp(s);swap(tmp);return *this;}~string(){delete[] _str;_str nullptr;}char operator[](size_t pos){return _str[pos];}void reserve(size_t n){if (n _capacity){char* tmp new char[n 1];strcpy(tmp, _str);delete[] _str;_str tmp;_capacity n;}}void push_back(char ch){if (_size _capacity){int newCapacity _capacity 0 ? 4 : _capacity * 2;reserve(newCapacity);}_str[_size] ch;_size;_str[_size] \0;}string operator(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}private:char* _str;int _size;int _capacity;};
}
左值引用的使用场景
做参数和做返回值都可以提高效率。
void func1(ns::string s)
{}
int main()
{ns::string s1(hello world);func1(s1);// string operator(char ch) 传值返回存在深拷贝// string operator(char ch) 传左值引用没有拷贝提高了效率s1 !;return 0;
}左值引用的短板
但是当函数返回对象是一个局部变量出了函数作用域就不存在了就不能使用左值引用返回 只能传值返回。
例如在下面这个函数整数转字符串中只能传值返回传值返回则至少会存在一次拷贝构造编译器优化后的结果 ns::string to_string(int value){bool flag true;if (value 0){flag false;value -value;}string str;while (value 0){int x value % 10;value / 10;str (0 x);}if (flag false){str -;}reverse(str.begin(), str.end());return str;}int main(){ns::string str ns::to_string(1234);return 0;} 当函数返回的是一个局部对象出了函数作用域就不存在了把这种值称为将亡值不能用左值引用返回针对这种问题右值引用就可以解决
解决方式增加移动构造移动构造的本质是将参数右值的资源窃取过来占位己有那么就不用做深拷贝了所以它叫移动构造就是窃取别人的资源构造自己。 //移动构造string(string s):_str(nullptr),_size(0),_capacity(0){cout string(string s) -- 移动语义 endl;swap(s);}
再运行时就没有调用深拷贝的拷贝构造而是调用了移动构造移动构造中没有开辟新空间。拷贝数据所以效率提高了
运行截图 不仅仅有移动构造还有移动赋值 //移动赋值string operator(string s){cout string operator(string s) -- 移动语义 endl;swap(s);return *this;}
运行截图 这里运行后我们看到调用了一次移动构造和一次移动赋值。因为如果是用一个已经存在的对象 接收编译器就没办法优化了。ns::to_string函数中会先用str生成构造生成一个临时对象但是 我们可以看到编译器很聪明的在这里把str识别成了右值调用了移动构造。然后在把这个临时 对象做为ns::to_string函数调用的返回值赋值给str这里调用的移动赋值。
6.4 右值引用引用左值及其一些更深入的使用场景分析
按照语法右值引用只能引用右值但右值引用一定不能引用左值吗因为有些场景下可能 真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时可以通过move 函数将左值转化为右值。C11中std::move()函数位于utility头文件中该函数名字具有迷惑性它并不搬移任何东西唯一的功能就是将一个左值强制转化为右值引用然后实现移动语义。
int main()
{ns::string s1(hello world); //s1是左值调用拷贝构造ns::string s2(s1);//当把s1 move处理之后会被当成右值调用移动构造ns::string s3(move(s1));return 0;
}
注一般不建议这样使用因为调用移动构造是将s1的资源换给s3了s1被置空了如图所示 STL容器插入接口函数也增加了右值引用版本
如图所示 int main()
{listns::stringlt;ns::string s1(111111);//调用拷贝构造lt.push_back(s1);//调用移动构造lt.push_back(ns::string(222222));//调用移动构造lt.push_back(333333);return 0;
}
运行截图 模拟实现list的右值引用版本的插入
//结点构造list_node(T val):_next(nullptr),_prev(nullptr),_data(move(val)){};
//push_back:void push_back(T val){insert(end(), move(val));}
//insert:iterator insert(iterator pos, T val){node* newnode new node(move(val));node* cur pos._pnode;node* prev cur-_prev;prev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;_size;return iterator(newnode);}
注右值在传给下一层的时候属性变为右值所以用move以后的左值向下传递
int main()
{lt::listns::string lt;ns::string s1(1111);// 这里调用的是拷贝构造lt.push_back(s1);// 下面调用都是移动构造lt.push_back(2222);lt.push_back(std::move(s1));return 0;
}
运行截图 6.5完美转发
模板中的 万能引用
模板中的不代表右值引用而是代表万能引用既能接受左值又能接受右值
templateclass T
void perfectForward(T t)
{}
int main()
{perfectForward(10); //右值int a;perfectForward(a); //左值perfectForward(move(a)); //右值const int b 10;perfectForward(b); //const左值perfectForward(move(b)); //const右值return 0;
}
运行截图 模板中的万能引用只提供了能够同时接受左值和右值的能力后续在使用的时候都退化为左值属性
void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }
templateclass T
void perfectForward(T t)
{Fun(t);
}
运行截图 如何在传递过程中保持它的左值或者右值属性就需要通过完美转发来解决
std::forward 完美转发在传参的过程中保留对象原生类型属性
void Fun(int x) { cout 左值引用 endl; }
void Fun(const int x) { cout const 左值引用 endl; }void Fun(int x) { cout 右值引用 endl; }
void Fun(const int x) { cout const 右值引用 endl; }
templateclass T
void perfectForward(T t)
{//forwardT在传参的时候保持t的原有属性Fun(forwardT(t));
}
int main()
{perfectForward(10); //右值int a;perfectForward(a); //左值perfectForward(move(a)); //右值const int b 10;perfectForward(b); //const左值perfectForward(move(b)); //const右值return 0;
}
运行截图 通过万能引用就可以解决list中插入的时候右值属性改变的问题
//结点构造list_node(T val):_next(nullptr),_prev(nullptr),_data(forwardT(val)){};
//push_back:void push_back(T val){insert(end(), forwardT(val));}
//insert:iterator insert(iterator pos, T val){node* newnode new node(forwardT(val));node* cur pos._pnode;node* prev cur-_prev;prev-_next newnode;newnode-_prev prev;newnode-_next cur;cur-_prev newnode;_size;return iterator(newnode);}
注右值引用也被称为是折叠引用如图所示 7.新的类功能 默认成员函数
原来C中有6个默认成员函数构造函数析构函数拷贝构造函数赋值重载函数取地址重载和const取地址重载。在C11中新增了两个默认成员函数移动构造函数和移动赋值重载函数。
针对移动构造函数和移动赋值重载函数需要注意以下几点
如果自己没有实现移动构造函数且没有实现析构函数拷贝构造拷贝赋值重载函数中的任意一个那么编译器默认会生成一个移动构造函数。默认的移动构造函数对于内置类型会按字节拷贝自定义类型的成员看这个成员是否实现了移动构造如果实现了则调用移动构造没有实现则调用拷贝构造函数
如果你没有自己实现移动赋值重载函数且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数对于内置类型成员会执行逐成员按字节拷贝自定义类型成员则需要看这个成员是否实现移动赋值如果实现了就调用移动赋值没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
如果你提供了移动构造或者移动赋值编译器不会自动提供拷贝构造和拷贝赋值。
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:ns::string _name;int _age;
};
int main()
{Person s1;Person s2 s1;Person s3 std::move(s1);Person s4;s4 std::move(s2);return 0;
}
运行截图
强制生成默认函数的关键字default:
C11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数但是因为一些原 因这个函数没有默认生成。比如我们提供了拷贝构造就不会生成移动构造了那么我们可以 使用default关键字显示指定移动构造生成。
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(Person p) default;
private:ns::string _name;int _age;
};
int main()
{Person s1;Person s2 s1;Person s3 std::move(s1);return 0;
}
运行截图 禁止生成默认函数的关键字delete:
假设某个类对象不让支持拷贝该如何实现呢
思路1在C98中可以将拷贝构造函数声明为私有外部就不能在调用拷贝构造函数但是函数内部任然可以调用拷贝构造函数所以可以将拷贝构造函数只声明不实现此时调用拷贝构造函数就会有错误
class A
{
public:void func(){A tmp(*this);}A(){}~A(){delete[] p;}
private:A(const A aa); //只声明不实现int* p new int[10];
};
int main()
{A aa1;aa1.func();A aa2(aa1);return 0;
}
运行截图 思路2在C11中对于这种不想让外面拷贝构造的函数用delete修饰再调用的时候直接报错
class A
{
public:void func(){A tmp(*this);}A(){}~A(){delete[] p;}A(const A aa) delete;
private:int* p new int[10];
};
int main()
{A aa1;aa1.func();A aa2(aa1);return 0;
}
运行截图 8.lambda表达式 在C98中如果想要对一个数据集合中的元素进行排序可以使用std::sort方法。
#include algorithm
#include functional
int main()
{int array[] { 4,1,8,5,3,7,0,9,2,6 };// 默认按照小于比较排出来结果是升序std::sort(array, array sizeof(array) / sizeof(array[0]));for (auto e : array){cout e ;}cout \n;// 如果需要降序需要改变元素的比较规则std::sort(array, array sizeof(array) / sizeof(array[0]), greaterint());for (auto e : array){cout e ;}cout \n;return 0;
}
运行截图 如果待排序元素为自定义类型需要用户定义排序时的比较规则
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()
{vectorGoods v { { 苹果, 2.1, 5 }, { 香蕉, 3, 4 }, { 橙子, 2.2,3 }, { 菠萝, 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());for (const auto e : v){cout e._name e._price e._evaluate ;cout endl;}cout endl;sort(v.begin(), v.end(), ComparePriceGreater());for (const auto e : v){cout e._name e._price e._evaluate ;cout endl;}cout endl;
}
运行截图 随着C语法的发展人们开始觉得上面的写法太复杂了每次为了实现一个algorithm算法 都要重新去写一个类如果每次比较的逻辑不一样还要去实现多个类特别是相同类的命名 这些都给编程者带来了极大的不便。因此在C11语法中出现了lambda表达式。
lambda表达式
int main()
{vectorGoods 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._price g2._price; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) {return g1._evaluate g2._evaluate; });sort(v.begin(), v.end(), [](const Goods g1, const Goods g2) {return g1._evaluate g2._evaluate; });
}
上述代码就是使用C11中的lambda表达式来解决可以看出lambda表达式实际是一个匿名函 数。
8.1lambda表达式语法
lambda表达式书写格式[capture-list] (parameters) mutable - return-type { statement }
1. lambda表达式各部分说明
[capture-list] : 捕捉列表该列表总是出现在lambda函数的开始位置编译器根据[]来判断接下来的代码是否为lambda函数捕捉列表能够捕捉上下文中的变量供lambda函数使用。 (parameters)参数列表。与普通函数的参数列表一致如果不需要参数传递则可以连同()一起省略 mutable默认情况下lambda函数总是一个const函数mutable可以取消其常量性。使用该修饰符时参数列表不可省略(即使参数为空)。 -returntype返回值类型。用追踪返回类型形式声明函数的返回值类型没有返回值时此部分可省略。返回值类型明确情况下也可省略由编译器对返回类型进行推导。 {statement}函数体。在该函数体内除了可以使用其参数外还可以使用所有捕获到的变量。
注意 在lambda函数定义中参数列表和返回值类型都是可选部分而捕捉列表和函数体可以为 空。因此C11中最简单的lambda函数为[]{}; 该lambda函数不能做任何事情。
int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[] {};int a 3, b 4;auto add [] (int x,int y){return x y; };cout add(a, b) endl;//交换两个数引用捕捉int x 20, y 10;auto swap [x,y]() mutable{int tmp x;x y;y tmp;};swap();cout x x y y endl;//混合捕捉auto fun [, y]() {return a y; };cout fun() endl;return 0;
}
运行截图 通过上述例子可以看出lambda表达式实际上可以理解为无名函数该函数无法直接调用如果想要直接调用可借助auto将其赋值给一个变量。
2. 捕获列表说明 捕捉列表描述了上下文中那些数据可以被lambda使用以及使用的方式传值还是传引用。 [var]表示值传递方式捕捉变量var []表示值传递方式捕获所有父作用域中的变量(包括this) [var]表示引用传递捕捉变量var []表示引用传递捕捉所有父作用域中的变量(包括this) [this]表示值传递方式捕捉当前的this指针
注意 a. 父作用域指包含lambda函数的语句块编译器向上查找。 b. 语法上捕捉列表可由多个捕捉项组成并以逗号分割。 比如[, a, b]以引用传递的方式捕捉变量a和b值传递方式捕捉其他所有变量 [a, this]值传递方式捕捉变量a和this引用方式捕捉其他变量 c. 捕捉列表不允许变量重复传递否则就会导致编译错误。 比如[, a]已经以值传递方式捕捉了所有变量捕捉a重复 d. 在块作用域以外的lambda函数捕捉列表必须为空。 e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量捕捉任何非此作用域或者非局部变量都会导致编译报错。 f. lambda表达式之间不能相互赋值即使看起来类型相同
void (*PF)();
int main()
{auto f1 [] {cout hello world endl; };auto f2 [] {cout hello world endl; };//f1 f2; // 编译失败---提示找不到operator()// 允许使用一个lambda表达式拷贝构造一个新的副本auto f3(f2);f3();// 可以将lambda表达式赋值给相同类型的函数指针PF f2;PF();return 0;
}
运行截图 8.2 函数对象与lambda表达式
函数对象又称为仿函数即可以像函数一样使用的对象就是在类中重载了operator()运算符的函数对象
class Rate
{
public:Rate(double rate):_rate(rate) {}double operator()(double money, int year){return money * year * _rate;}
private:double _rate;
};
int main()
{//函数对象double rate 0.49;Rate r1(rate);r1(1000, 2);//lambda表达式auto r2 [](double money, int year)-double{return money * rate * year;};r2(2000, 3);return 0;
}
从使用方式上来看函数对象与lambda表达式完全一样。 函数对象将rate作为其成员变量在定义对象时给出初始值即可lambda表达式通过捕获列表可 以直接将该变量捕获到。 实际在底层编译器对于lambda表达式的处理方式完全就是按照函数对象的方式处理的即如 果定义了一个lambda表达式编译器会自动生成一个类在该类中重载了operator()。
9.可变参数模板
C11的新特性可变参数模板能够创建可以接受可变参数的函数模板和类模板相比C98/03类模版和函数模版中只能含固定数量的模版参数可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象使用起来需要一定的技巧所以这块还是比较晦涩的。
下面是一个基本可变参数的函数模板
// Args是一个模板参数包args是一个函数形参参数包
// 声明一个参数包Args...args这个参数包中可以包含0到任意个模板参数。
template class ...Args
void ShowList(Args... args)
{}
上面的参数args前面有省略号所以它就是一个可变模版参数我们把带省略号的参数称为“参数 包”它里面包含了0到NN0个模版参数。我们无法直接获取参数包args中的每个参数的 只能通过展开参数包的方式来获取参数包中的每个参数这是使用可变模版参数的一个主要特 点也是最大的难点即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变 参数所以我们要用一些奇招来一一获取参数包的值。
递归函数方式展开参数包
// 递归终止函数
void ShowList()
{cout endl;
}
// 展开函数
template class T, class ...Args
void ShowList(T value, Args... args)
{cout value ;ShowList(args...);
}
int main()
{ShowList(1);ShowList(1, A);ShowList(1, A, std::string(sort));return 0;
}
逗号表达式展开参数包
template class T
void PrintArg(T t)
{cout t ;
}
//展开函数
template class ...Args
void ShowList(Args... args)
{int arr[] { (PrintArg(args), 0)... };cout endl;
}
int main()
{ShowList(1);ShowList(1, A);ShowList(1, A, std::string(sort));return 0;
}
STL容器中的empalce相关接口函数
template class... Args
void emplace_back (Args... args);
可以看到的emplace系列的接口支持模板的可变参数和万能引用。那么相对insertemplace系列接口的优势到底在哪里呢
1.对于内置类型没有任何区别
#includelist
int main()
{listint list1;list1.push_back(1);list1.emplace_back(2);return 0;
}
2.对于自定义类型
int main()
{listpairint, charlist1;list1.push_back(make_pair(1, a));//可以直接将值传过去直接构造//不需要先构造一个临时对象然后用这个临时对象拷贝构造list1.emplace_back(2, b);return 0;
}
下面试一下带有拷贝构造和移动构造的ns:string:
int main()
{pairint, ns::string kv(20, sort);std::list std::pairint, ns::string mylist;mylist.emplace_back(kv); // 左值mylist.emplace_back(make_pair(20, sort)); // 右值mylist.emplace_back(10, sort); // 构造pair参数包cout endl;mylist.push_back(kv); // 左值mylist.push_back(make_pair(30, sort)); // 右值mylist.push_back({ 40, sort }); // 右值return 0;
}
运行截图 观察上图可以发现emplace对于右值系列是直接构造没有移动语义当类中没有移动构造的时候emplce能够大大提高效率
10.包装器
10.1function包装器
function包装器 也叫作适配器。C中的function本质是一个类模板也是一个包装器。 那么我们来看看我们为什么需要function呢
//ret func(x);
// 上面func可能是什么呢那么func可能是函数名函数指针函数对象(仿函数对象)也有可能
//是lamber表达式对象所以这些都是可调用的类型如此丰富的类型可能会导致模板的效率低下
//为什么呢我们继续往下看
templateclass 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;// lambda表达式cout useF([](double d)-double { return d / 4; }, 11.11) endl;return 0;
}
运行截图 通过上面的程序验证我们会发现useF函数模板实例化了三份。 包装器可以很好的解决上面的问题。
std::function在头文件functional
// 类模板原型如下
template class T function; // undefined
template class Ret, class... Args
class functionRet(Args...);
模板参数说明
Ret: 被调用函数的返回类型
Args…被调用函数的形参
使用方法
int f(int a, int b)
{return a b;
}
struct Functor
{
public:int operator() (int a, int b){return a b;}
};
class Plus
{
public:static int plusi(int a, int b){return a b;}double plusd(double a, double b){return a b;}
};
int main()
{// 函数名(函数指针)functionint(int, int) func1 f;cout func1(1, 2) endl;// 函数对象functionint(int, int) func2 Functor();cout func2(1, 2) endl;// lambda表达式functionint(int, int) func3 [](const int a, const int b){return a b; };cout func3(1, 2) endl;// 类的静态成员函数 --指针functionint(int, int) func4 Plus::plusi;cout func4(1, 2) endl;// 类的普通成员函数 --指针functiondouble(Plus, double, double) func5 Plus::plusd;cout func5(Plus(), 1.1, 2.2) endl;return 0;
}有了包装器如何解决模板的效率低下实例化多份的问题呢
#include functional
templateclass 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()
{// 函数名functiondouble(double) func1 f;cout useF(func1, 11.11) endl;// 函数对象functiondouble(double) func2 Functor();cout useF(func2, 11.11) endl;// lambda表达式functiondouble(double) func3 [](double d)-double { return d / 4; };cout useF(func3, 11.11) endl;return 0;
}
运行截图 包装器的使用场景oj链接
class Solution {
public:int evalRPN(vectorstring tokens) {stackint st;mapstring,functionint(int,int) opFuncMap {{,[](int x,int y)-int {return x y;} },{-,[](int x,int y)-int {return x - y;} },{*,[](int x,int y)-int {return x * y;} },{/,[](int x,int y)-int {return x / y;} },};for(const auto str : tokens){if(opFuncMap.count(str) 0){st.push(stoi(str));}else{int right st.top();st.pop();int left st.top();st.pop();st.push(opFuncMap[str](left,right));}}return st.top();}
};
10.2bind
bind函数定义在头文件中是一个函数模板它就像一个函数包装器(适配器)接受一个可调用对象callable object生成一个新的可调用对象来“适应”原对象的参数列表。一般而言我们用它可以把一个原本接收N个参数的函数fn通过绑定一些参数返回一个接收M个M可以大于N但这么做没什么意义参数的新函数。同时使用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);
可以将bind函数看作是一个通用的函数适配器它接受一个可调用对象生成一个新的可调用对 象来“适应”原对象的参数列表。 调用bind的一般形式auto newCallable bind(callable,arg_list); 其中newCallable本身是一个可调用对象arg_list是一个逗号分隔的参数列表对应给定的 callable的参数。当我们调用newCallable时newCallable会调用callable,并传给它arg_list中 的参数。 arg_list中的参数可能包含形如_n的名字其中n是一个整数这些参数是“占位符”表示 newCallable的参数它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对 象中参数的位置_1为newCallable的第一个参数_2为第二个参数以此类推。
// 使用举例
#include functional
int Plus(int a, int b)
{return a b;
}
class Sub
{
public:int sub(int a, int b){return a - b;}
};int main()
{//表示绑定函数plus 参数分别由调用 func1 的第一二个参数指定functionint(int, int) func1 bind(Plus, placeholders::_1,placeholders::_2);//auto func1 bind(Plus, placeholders::_1, placeholders::_2);//func2的类型为 functionvoid(int, int, int) 与func1类型一样//表示绑定函数 plus 的第一二为 1 2auto func2 bind(Plus, 1, 2);cout func1(1, 2) endl;cout func2() endl;Sub s;// 绑定成员函数functionint(int, int) func3 bind(Sub::sub, s,placeholders::_1, placeholders::_2);// 参数调换顺序functionint(int, int) func4 bind(Sub::sub, s,placeholders::_2, placeholders::_1);cout func3(1, 2) endl;cout func4(1, 2) endl;return 0;
}