第1节 函数调用运算符、function类模板
一、学习c++体会、总述
1、对语言本身的学习 2、大量练习 3、阅读别人的优秀代码
函数调用标记():函数调用运算符
class biggerthanzero {
public:
int operator()(int value) const {
if (value < 0)
return 0;
return value;
}
};
//调用
biggerthanzero obj;
cout<<obj(5)<<endl; //等价于obj.operator()(5)
二、不同调用对象的相同调用形式
若有一个普通函数的参数与返回值与重载的()的参数与返回值相同,这个两个对象的调用形式就是相同的,可以用funciton存储。
三、标准库function类型介绍 #include<functional>
//function<int(int)> 声明一个可调用对象,该可调用对象的参数为int,返回为int
function<int(int)> f1 = echovalue;
function<int(int)> f2 = obj;
function<int(int)> f3 = biggerthanzero();
f1(5);
cout<<f2(-5)<<endl;
cout<<f3(10)<<endl;
简单方法:
map<string, function<int(int)>> m2 = {
{ "ev",echovalue },
{ "obj",obj },
{ "btz",biggerthanzero() }
};
m2["ev"](55);
cout << m2["obj"](-5) << endl;
cout << m2["btz"](10) << endl;
【注】若echovalue有其他参数的函数重载,该可调用对象就不能放入function中,可以定义一个函数指针来解决二义性。
第2节 万能引用universal reference
一、类型区别基本概念
二、万能引用(未定义引用、universal reference)
前提:a)必须是函数模板 b)必须发生了模板类型推断 c)形参类型必须为T&&
效果:若传入左值,万能引用变为左值(int&),若传入右值,万能引用变为右值(int&&)
template<typename T>
void myfun(T &&tmp) {
cout << tmp << endl;
}
//调用
int i = 100;
myfun(i);
myfun(100);
三、万能引用资格的剥夺与辨认
1、剥夺:const T&& tmp 打回原形,直接变成右值引用
2、辨认:类模板中的成员函数形参中的T&& tmp是右值引用(这是因为类对象声明时类模板的类型推断已经进行,对于成员函数testfunc来说相当于void testfunc(int &&){},但是,类模板中的模板成员函数可以是一个万能引用)。
第3节 理解模板类型推断、查看类型推断结果
一、如何查看类型推断结果:依赖boost库打印推断信息
template<typename T>
void myfunc(T tmp) {
cout << "--------------begin--------------" << endl;
using boost::typeindex::type_id_with_cvr;
cout << "T = " << type_id_with_cvr<T>().pretty_name() << endl;
cout << "tmp = " << type_id_with_cvr<decltype(tmp)>().pretty_name() << endl;
cout << "---------------end---------------" << endl;
}
二、理解模板类型推断
1、指针或引用类型(不是万能引用)
对于引用:void myfunc(T& tmp)
【形参不加const】1)实参是引用类型,引用会被忽略,T不会被推倒为引用类型
2)实参中带const,形参会成为const&
【形参加上const】1)同上 2)实参中有const,会抵消,T就不带const了
对于指针:void myfunc(T* tmp)
若形参中不带const,实参中的const会传入T;若形参中带const,T的推断都不带const(带就重复了)。
2、万能引用:void myfunc(T&& tmp)
int i = 18; i是左值,T=int&,tmp=int&
const int j = i; j是左值,T=const int&,tmp=const int&
const int &k = i; k是左值,T=const int&,tmp=const int&
myfunc(100); i是右值,T=int,tmp=int&&
3、传值方式:void myfunc(T tmp)
const穿不进去,因为tmp是一个新副本,但是可以传指针进去,如下:
char mystr[] = "dsfaf";
const char* const point = mystr;
myfunc(point);
T = const char*,tmp = const char*,这证明只传了前一个const,即数组内元素不可变但指向可以变。
4、数组作实参
char mystr[] = "dsfaf";
myfunc(mystr);
T = char*,tmp=char*
5、函数名作实参
void myfunc(T tmp){
推断结果:T=void(*)(void),tmp=void(*)(void),
}
void myfunc(T &tmp){
推断结果:T=void(void),tmp=void(&)(void),
}
三、总结
1、引用类型的实参等于不存在,推断完也是不带引用类型的
2、万能引用实参为左值还是右值推断的结果是不同的
3、按值传递的const无效,指针另当别论
4、数组、函数名作实参被看做是指针,除非函数模板的形参是引用
第4节 引用折叠、转发、完美转发、forward
一、引用折叠规则 c++11
int i = 18;
myfunc(i);
i是左值,T推断为int&,但是tmp应该推断为T&&&啊?
c++有明确含义的引用只有左值引用和右值引用
四中情况的出现会出现引用折叠:第一组(左或右)+第二组(左或右)
【规则】只要有左值引用,结果就为左值,否则为右值!
引用的引用是非法的:& &这种格式一出戏就会报错
二、转发、完美转发
转发:函数模板把收到的参数调用函数来转给该函数(要保证类型不变)
template<typename F, typename T1, typename T2>
void myFuncTemp(F f, T1 t1, T2 t2) { //f就是要转发到的目标函数
f(t1,t2);
}
void myfunc(int i, int j) { //若这里加引用,无法传到外界,改变的其实是t2
j++;
cout << i + j << endl;
}
void func() {
int i = 10;
myFuncTemp(myfunc, 5, i);
}
目的:修改myFuncTemp模板函数,让这个模板函数的参数能够保持给定实参的左值性和const属性?
万能引用出场!!!
void myFuncTemp(F f, T1 && t1, T2 && t2) //若为T&,则只能传const属性
又发现问题:
template<typename F, typename T1, typename T2>
void myFuncTemp(F f, T1 && t1, T2 && t2) {
f(t1,t2);
}
void myfunc(int &&i, int &j) {
j++;
cout << i + j << endl;
}
void func() {
int a = 10;
myFuncTemp(myfunc, 5, a);
}
这样虽然a的值会变,但是当往函数模板myFuncTemp中传入一个右值时,经过f(t1,t2)的中转,t1变成了左值,使得myfunc中的第一个参数无法接收,程序报错!
【完美转发】接受任意类型实参的函数模板
三、std::forward
前提:模板函数+万能引用+转发
能力:按照参数本来的类型转发,强制左值转右值(如果传进来的就是左值,则不动)
f(std::forward<T1>(t1),std::forward<T2>(t2)); //T1中存着原来的实参类型
四、std::move和std::forward区别
forward是有条件的(如上),move不管什么都转成右值,而且move完了就不用了。
void printInfo(int &tmp) {
cout << "left" << endl;
}
void printInfo(int &&tmp) {
cout << "right" << endl;
}
template<typename T>
void myFuncTemp(T&& t) {
printInfo(t);
printInfo(std::forward<T>(t));
printInfo(std::move(t));
}
void func() {
int i = 1;
myFuncTemp(100); //left/right/right
myFuncTemp(i); //left/left/right
}
五、再谈万能个引用
并不是一种新的引用类型,只是一种写程序的方式。
第5节 理解auto类型推断、auto应用场合
一、std::forward用法补充:单纯地转为右值
int d = 1;
int &&i = forward<int>(d);
二、auto类型常规推断(类似于模板推断,相当于T)
c++11,声明变量时根据初始值自动选择匹配的类型,可与指针、引用、const合用
【方法】先推断变量的类型,再由此推auto的类型!
1、传值方式(非指针、非引用):引用属性和const属性都被抛弃,看成新副本
auto x = 27; x=int,auto=int
const auto x2 = x; x2=const int,auto=int
2、指针或引用类型:抛弃引用、不抛弃const
const auto &xy = x; xy=const int&,auto=int
auto y = new auto(100); y=int*,auto=int*
const auto *p = &x; p=const int*,auto=int
auto p2 = &x; p2=int*,auto=int*
3、万能引用类型
auto && wnyy1 = x; x是左值,auto=int&,qnyy1=int&(折叠)
auto && wnyy2 = 100; 100是右值,auto=int,wnyy2=int&&
三、auto类型针对数组和函数的推断:往往推断成指针!
四、auto类型std::initializer_list的特殊推断
int x3 = {30};
x3被推断成std::initializer_list<int>类型,=遇到{,先推断成initializer_list,再根据元素内容推出模板参数的类型
五、auto不适用场合举例
1、不能用于函数参数
void func(auto i,int j){}
2、不能是普通成员变量,只能是static const auto m_i = 4(而且一定要初始化)
六、auto适用场合举例
1、定义容器中的迭代器
2、无法确定参数类型时的写法
第6节 详解decltype含义、decltype主要用途
一、decltype含义和举例:用于推导类型
c++11,返回操作数的数据类型
1、特点
1)decltype的自动类型推断发生在编译时(和autp一样)
2)decltype不会真正计算表达式的值
2、decltype后的圆括号中是个变量:i的const和引用属性在j中都会保留
const int i = 5;
decltype(i) j = 10;
3、decltype后的圆括号中是表达式:代表的类型是返回结果的类型
int *p = new int(10);
int i = 2;
decltype(*p) j = i; //j的类型为int &
若表达式可以作为等号左边的内容(比如*p=4是允许的),返回的就是一个引用
decltype((i)) j = i; //j的类型为int &
4、decltype后的圆括号中是函数
decltype(testf()) tmpv = 14; tmpv的类型是testf()的返回值类型,但不调用testf()
decltype(testf) tmpv2; tmpv2的类型是int(void),是一种可调用对象
所以可以这么用,function<decltype(testf)> tmp = testf;,这样tmp()就是调用testf()函数
二、decltype主要用途(模板编程)
1、应付可变类型
c++98时需要类模板特化,将iterator改为const_iterator才能遍历常量容器。
c++11就可以decltype(T().begin()) iter这样定义迭代器,自动识别该容器是否为常量的。
2、通过变量表达式抽取变量类型
vector<int> myvec = {10,20,30};
decltype(myvec)::size_type mysize = myvec.size();
typedef decltype(sizeof(0)) size_t;
//等价于
typedef unsigned int size_t;
3、auto结合decltype构成返回类型后置语法
auto add(int i,int j)->decltype(i+j){
return i+j;
}
4、decltype(auto)用法
1)用于函数返回类型
template<typename T>
decltype(auto) mydouble(T& v){ //若只有auto会忽略引用,返回右值,无法赋值
v*=2;
return v;
}
//调用
int a = 10;
mydouble(a) = 20;
2)用于变量声明中
int x = 1;
const int &y = x;
auto z = y; z的类型既没有const,也没有&
decltype(auto) z2 = y; z2都有,可以把auto丢失的类型捡回来
第7节 可调用对象、std::function、std::bind
一、可调用对象
1、函数指针
void(*p)(int) = test;
p(50);
2、具有operator()成员函数的类对象
3、可被准以为函数指针的类对象
4、类成员函数的指针
A a;
void(A::*p)(int) = &A::putong;
(a.*p)(555); //调用普通成员函数
二、std::function(不能装类成员函数的指针,其他可调用对象都能装入)
function<void(int)> f1 = test; 绑定普通函数
function<void(int)> f2 = B::test; 绑定类的静态成员函数
function<void(int)> f3 = a; 绑定仿函数(A的对象a)
三、std::bind绑定器
绑定器能够将对象和相关参数绑定到一起,构成一个仿函数。
auto bf1 = std::bind(myfunc1, 10, 20, 30);
bf1();
auto bf2 = std::bind(myfunc1, placeholders::_1, placeholders::_1, placeholders::_2);
//placeholde::_1表示myfunc1的第一个参数和第二个参数由bf2的第一个参数所绑定
//placeholde::_2表示myfunc1的第三个参数由bf2的第二个参数所绑定
bf2(55,66);
std::bind(myfunc1, placeholders::_1, placeholders::_1, placeholders::_2)(10, 20); //直接调用
直接绑定数字是值传递的,但是用placeholders绑定的是可以变的;
void myfunc2(int &x, int &y) {
x++;
y++;
}
//main中
int a = 2;
int b = 3;
auto bf3 = std::bind(myfunc2, a, placeholders::_1);
bf3(b);
cout << "a = " << a << " b = " << b << endl;
绑成员函数的用法
class CT {
public:
void myfunpt(int x, int y) {
cout << "x = " << x << " y = " << y << endl;
m_i = x;
}
int m_i;
};
//main中
CT ct;
function<void(int, int)> bf4 = bind(&CT::myfunpt, &ct, placeholders::_1, 20);
//如果这里ct的调用不加&,那么后面调用bf4时,ct对象中的m_i成员变量就无法修改
bf4(10,55555);
bind和function配合使用
CT ct;
function<int &()> bf5 = bind(&CT::m_i, &ct); //省2次构造和析构
bf5() = 555;
void test(int i,const function<void(int)> &f){
f(i);
}
void func(int x){
cout<<x<<endl;
}
//main中
for(int i=0;i<10;++i){
test(i,func);
}
//或者
auto bf = std::bind(func,placeholders::_1);
for(int i=0;i<10;++i){
test(i,bf);
}
四、总结
bind思想:延迟调用,将可调用对象统一格式保存起来、需要的时候再调用
第8节 lambda表达式、for_each、find_if
一、用法简介 c++11的一种可调用对象
lambda表达式定义了一个匿名函数,可以捕获一定范围内的变量(捕获列表)。
auto f = [](int a)->int{return a+1;};
cout<<f(1)<<endl;
可以在函数内部定义:有返回类型、参数列表、函数体
[捕获列表](参数列表)->返回类型{函数体};
a)参数列表可以给初值,返回类型有时候可以省略
b)参数列表可以省略,甚至()也可以省略
c)捕获列表和函数列表不能省略!
二、捕获变量
int i = 9;
auto f = []{return i;}; 报错,static的变量可以直接用
auto f = [&]{return i;}; 捕获外部作用域中的所有变量,可以修改变量值
auto f = [=]{return i;}; 捕获外部作用域中的所有变量,可以用值,但不可以赋值
auto f = [this]{return m_i;}; 捕获当前类的this指针,lambda表达式有和当前成员函数相同的访问权限
【注】使用=和&默认使用了this
auto f = [i,j]{return i+j;}; 不捕获其他变量
auto f = [&i,j]{i=5;}; 可以给i赋值
auto f = [=,&i,&j]{}; 按值捕获所有变量,按引用捕获后续变量
auto f = [&,i,j]{}; 与上一个相反
总结:lambda表达式对于能访问的外部变量控制的非常细致!
三、lambda表达式延迟调用易出错细节分析
int i = 0;
auto f = [i] {
return i;
};
i = 10;
cout<<f()<<endl;
凡是按值捕获的外部变量,在lambda表达式定义时,就已经复制了一份在lambda表达式变量中,所以输出还是0,解决办法是变=为&,或在捕获变量时加&。
四、lambda表达式中的mutable
int i = 0;
auto f = [i] ()mutable {
i = 10;
return i;
};
这里的()是不能省略的,这个mutable的作用是使i的值也是可以修改的。
五、lambda表达式的类型及存储
c++11中,lambda表达式的类型为“闭包类型”(函数内的函数),是带有operator()的类类型对象
可以用function和bind保存和调用lambda表达式,每个lambda表达式生成一个匿名对象
function<int(int)> fc1 = [](int tmp) {
return tmp;
};
cout << fc1(10) << endl;
function<int(int)> fc2 = bind([](int tmp) {
return tmp;
},placeholders::_1
);
cout << fc2(20) << endl;
不捕获任何变量的lambda表达式可以转换成一个函数指针
using pointType = int(*)(int);
pointType pt = [](int tmp) {
return tmp;
};
cout << pt(100) << endl;
【语法糖】一种简便写法而已,lambda表达式也可以看成一种语法糖。
int a[5];
*(a+1)=3;用a[1]=3;替代
六、lambda表达式再演示和优点总结
1、for_each简介(函数模板) algorithm
vector<int> myvec = { 10,20,30,40,50 };
int sum = 0;
//for_each(myvec.begin(), myvec.end(), myfunc);
for_each(myvec.begin(), myvec.end(), [&sum](int val) {
sum += val;
cout << val << endl;
});
cout << "sum = " << sum << endl;
2、find_if简介:从第一个参数开始,如果第三个参数返回false,就一直遍历到第二个参数
vector<int> myvec = { 10,20,30,40,50 };
auto fi = find_if(myvec.begin(), myvec.end(), [](int val) {
cout << val << endl;
/*if (val > 15)
return true;*/
return false;
});
if (fi == myvec.end())
cout << "not find!!!" << endl;
cout << "result = " << *(fi-1) << endl;
第9节 lambda表达式捕获模式的陷阱分析和展示
一、捕获列表中的&
绑了局部变量就不能在变量作用域外调用lambda表达式(这种错误也被叫做“引用悬空”,可以通过按值绑定来解决)
二、形参列表可以试用auto(c++14)
三、成员变量的捕获问题
捕获这个概念只针对在创建lambda表达式的作用域内可见的非静态局部变量,故成员变量m_at是无法被捕获的
vector<function<bool(int)>> gv;
class AT {
public:
int m_at = 7;
//解决办法
void addItem(){
auto matCopy = m_at; //解决办法!!!
gv.push_back(
//[=](auto tmp) { //似乎不是按值捕获的
//这个lambda表达式是依赖于this指针的,也就是依赖于对象的,所以delete后就失效了
[matCopy](auto tmp){
cout << matCopy << endl;
if (tmp % matCopy == 0)
return true;
return false;
}
);
}
};
void func() {
AT *pat = new AT();
pat->addItem();
delete pat;
//pat不存在,lambda失效
cout<<gv[0](10)<<endl;
}
四、广义lambda捕获(c++14)
直接在[ ]里,[abc = 成员变量名]
五、静态局部变量:保存在静态存储区,直接用就可以,类似于按引用捕获的效果
【提示】慎用静态局部变量!
第10节 可变参数函数、initializer_list、省略号形参
一、可变参数函数:能接受非固定参数个数参数
initializer_list标准库类型:使用前提——所有实参类型相同
二、initializer_list(初始化列表) c++11类模板
1、初始化后不能被改变
initializer_list<string> array1 = { "aa","bb","cc" };
2、拷贝和赋值:不会拷贝和复制array1中的元素,对象地址不同但指向的元素相同,共享同一份数据!
initializer_list<string> array2(array1); //array2和array1指向的首地址相同
3、初始化列表做普通函数参数和构造函数参数
void printInfo(initializer_list<string> tmpList) {
/*for (auto iter = tmpList.begin(); iter != tmpList.end();++iter){
cout << (*iter).c_str() << endl;
}*/
//范围for
for (auto &tmpitem : tmpList) {
cout << tmpitem.c_str() << endl;
}
cout << tmpList.size() << endl;
}
//调用
printInfo({ "ASDFADS","SAFSDFASD" });
class CT {
public:
explicit CT(const initializer_list<int> &tmp) {
//explicit禁止了隐式类型转换
}
};
//调用
//CT ct = { 10,20,30 }; //隐式类型转换
CT ct{ 10,20,30 };
三、省略号形参(...)
int和char*比较适用,需加入#include "stdarg.h"
例子1:
double average(int num, ...) {
va_list valist;
double sum = 0;
va_start(valist, num); //使valist指向起始的参数
for (int i = 0; i < num; ++i) {
sum += va_arg(valist, int); //这里绑定好后va_arg这个东西应该自动从num的下一个元素开始遍历
}
va_end(valist); //释放list
return sum;
}
//调用
cout << average(3, 55, 47, 95) << endl;
例子2:
void test(const char *msg, ...) {
va_list valist;
int csgs = atoi(msg);
va_start(valist, msg); //使valist指向起始的参数,绑的是离...最近的参数
int count = 0;
while (count < csgs) {
char *p;
p = va_arg(valist, char*);
printf("第%d个参数是:%s\n", count, p);
count++;
}
va_end(valist); //释放list
}
//调用
test("3", "qqweq", "qweqw", "erqw");
【注意事项】
a)至少有一个有效形参(普通参数)
b)...形参只能出现在形参列表最后
c)...之前的可以省略
d)若有多个普通参数,va_start(valist, msg)的第二个参数绑...之前的那个参数
e)可变参数类型一般为int和char*
f)不建议使用,但要能看懂!
第11节 萃取(traits)技术概念、范例等
一、类型萃取概述(type traits):泛型编程,stl源码中用的比较多
c++11中提供了很多了很多类型萃取的接口,这些接口其实就是一些类模板
二、类型萃取范例
通过萃取接口中的value值为true或者false可以萃取出对象、类、容器的很多有用信息(如,是否为对象,是否含虚函数等)。
三、迭代器萃取概述(iterators traits)
给定一个迭代器的类型对象,能够萃取出该迭代器的类型。
四、总结
c++中模板与泛型编程和模板元编程这种编程方法用的并不多,这种方法一般用于开发标准库和模板库,实际工作中一般都是开发业务逻辑。