文章目录
- 一 类型推导
- 1.1 意义
- 1.2 boost安装
- 1.3 反例
- 1.4 类型推导的使用场景
- 二 可调用对象
- 2.1 通过函数调用符操作的对象称之为可调用对象
- 2.2 可调用对象
- 2.3 函数对象(仿函数)
- 三 lambda 表达式
- (1)本质
- (2)格式
- (3) 捕获列表
- (4)注意事项
- 四 function包装器
- 4.1 本质
- 4.2 作用
- 4.3 意义
- 4.4 案例
- 五 bind适配器
- (1)本质
- (2) 作用
- (3)bind可以绑定的对象
- 1> 普通函数
- 2> 函数对象
- 面试题:什么是右值引用?右值引用与左值引用的区别
一 类型推导
1.1 意义
理解编译器推导规则有利于高效的使用c++
从明显或者冗余的类型拼写中解放出来,这样使得c++也更有适配性。
1.2 boost安装
sudo apt update
sudo apt-get install libboost-all-dev
1.3 反例
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
const int num = 1;
auto tmp = num;
tmp++;
return 0;
}
1.4 类型推导的使用场景
#include <iostream>
#include <boost/type_index.hpp>
#include <vector>
using boost::typeindex::type_id_with_cvr;
using namespace std;
//引用折叠
//T:int & func(T &&t) --->func(int & &&t)
//T:int && func(T &&t) --->func(int && &&t)
//折叠规则:有左即为左,全右则为右
//转发:完美转发
//std:forward<T>();
void test(int &num1)
{
cout<<"int &num1"<<endl;
cout<<"num1 = "<<num1<<endl;
}
/*void test(int &&num2)
{
cout<<"int &&num2"<<endl;
cout<<"num2 = "<<num2<<endl;
}*/
/*
万能引用(未定义引用):只能再函数模板中使用
作用:既能接收左值,也能接收右值
*/
//注意事项:
//1、模板参数必须紧跟&&符号
//void func(vector<T> &&v) //---》这个不是万能引用,因为vector和v之间有<T>
//2.const属性会剥夺万能引用的权限
//void func(const T && Parm) //-->这个也不是万能引用,因为被const修饰
//3.万能引用不是一种新的数据类型,它只存在于函数模板中
//test(int &num)表示需要传入左值引用,如果是(int &&num)表示需要传入右值引用
//在万能引用内部,右值传入变成左值,左值传入还是左值
//std::forward:可以解决万能引用内,变成右值std::forward<int &&>(t),传入test(int &&num)
//std::move:将左值变右值
//建议:对于右值引用使用std::move,对于万能引用使用std::forward。
template <typename T>
void print(T &&t) //右值引用(也称万能引用)
//void print(T &num) //左值引用
{
t++;
cout<<t<<endl;
test(std::forward<int &&>(t));
//test(std::forward<int &>(t));
test(t);
cout<<"void print(T &&t)"<<endl;
cout<<"T type:"<<type_id_with_cvr<T>().pretty_name()<<endl;
cout<<"t type:"<<type_id_with_cvr<decltype(t)>().pretty_name()<<endl;
}
//按值传递
/*
int --->推导规则:T:int t:int
const int --->推导规则:T: int t:int(忽略const 属性)
int& --->推导规则:T: int t:int(忽略&属性)
int* --->推导规则:T: int* t:int*(不会忽略指针属性)
*/
#if 0
template<typename T>
void func(T t)
{
cout<<"void func(T t)"<<endl;
cout<<"T type:"<<type_id_with_cvr<T>().pretty_name()<<endl;
cout<<"t type:"<<type_id_with_cvr<decltype(t)>().pretty_name()<<endl;
}
//按指针传递
/*
int --->推导规则:T:int t:int*
const int --->推导规则:T: const int t:const int*
*/
template<typename T>
void func(T *t)
{
cout<<"void func(T *t)"<<endl;
cout<<"T type:"<<type_id_with_cvr<T>().pretty_name()<<endl;
cout<<"t type:"<<type_id_with_cvr<decltype(t)>().pretty_name()<<endl;
}
#endif
//按引用传递 同引用
template<typename T>
void func(T &t) //T:int t:int&
{
cout<<"void func(T &t)"<<endl;
cout<<"T type:"<<type_id_with_cvr<T>().pretty_name()<<endl;
cout<<"t type:"<<type_id_with_cvr<decltype(t)>().pretty_name()<<endl;
}
int main(int argc, char const *argv[])
{
#if 0
int num = 5;
print(5);
int &num1 = num;
int &&num2 = std::move(num);
print(num1);
print(num2);
//按值传递 推导规则
int num =100;
const int count = 0;
int &num1 = num;
func(num);
func(count);
func(num1);
func(&num);
//按指针传递
int num = 1;
const int count = 0;
func(&num);
func(&count);
//按引用传递
int num = 1;
const int count = 0;
func(num);
func(count);
#endif
//万能引用--转发
int num = 1;
const int count = 0;
print(6);
return 0;
}
二 可调用对象
2.1 通过函数调用符操作的对象称之为可调用对象
2.2 可调用对象
普通函数
类成员函数/静态函数
类的成员函数指针
可转换为函数指针的类
#include <iostream>
using namespace std;
class Test
{
public:
void print() //成员函数
{
cout << "this is Test" << endl;
}
};
void print(void *pa)
//void print()
{
cout << "普通函数" << endl;
delete pa;
}
//typedef void(*T)(void *);
using P_FUNC = void(*)(void *);
class A
{
public:
using P_FUNC = void(*)(void);
static void print(void)
{
cout << "this is class A" << endl;
}
operator int()
{
return 1;
}
operator P_FUNC() //操作隐式转换,使得对象A转化成P_FUNC类型对象,此时的返回值是P_FUNC,就是函数名
{
return print;
}
};
int main(void)
{
//print();
Test t;
t.print();
P_FUNC p1 = print;
//p1();
void(Test::*p_func)(void) = &Test::print; //p_func是一个成员函数指针
(t.*p_func)(); //成员函数指针也可以作为可调用对象
A a;
a();
cout << a << endl;
//-------
//shared_ptr<A> p(new A(), print);
shared_ptr<A> p(new A(), p1);//传回调函数
}
2.3 函数对象(仿函数)
重载函数调用运算符
函数对象和普通函数的区别
class B
{
public:
void operator()(int a,int b)
{
cout << "hello class B" << endl;
}
};
//内置的函数对象
plus<int> p1;
cout << p1(5, 6) << endl;
minus<int> p2;
cout << p2(7, 8) << endl;
less<int> p3;
cout << p3(7, 8) << endl;
greater<int> p4;
cout << p4(8, 9) << endl;
三 lambda 表达式
(1)本质
实际就是匿名函数,能够给捕获一定范围的变量
与普通函数不同,可以在函数的内部定义。
(2)格式
[捕获列表](参数列表)->返回值类型{语句块};
说明:
1.返回值类型可以由编译器推导出来,可以省略不写
2.参数可以有默认值
3.c++14的lambda形参可以使用auto声明
4.lambda的调用方法和普通函数一样
#include <iostream>
using namespace std;
int main(void)
{
/*void print()
{
cout << "hello world" << endl;
}*/
//auto p = []() {};
//auto p = [](auto num = 10)->int {}; //c++14后lambda可以推导形参
auto p = [](void *)
{
cout << "hello world" << endl;
return 'a'; //自动推导
};
//p();
//lambda的调用时机:1延时调用 2.定义并调用
//shared_ptr<int> p2(new int(6), p);//1延时调用
shared_ptr<int> p3(new int(5), [](void *p) {
cout << "hello share ptr" << endl;
delete p;
});
return 0;
}
(3) 捕获列表
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A(int num, string s):m_num(num),m_s(s)
{
}
void test()
{
int count = 10;
//[&]或者[=]:默认捕获this指针
auto p = [this]() //只捕获this指针,可以修改,但是不能访问外部的变量
{
//cout << count << endl;
cout << m_num << endl;
cout << m_s << endl;
cout << "lambda p" << endl;
};
p();
}
public:
int m_num;
string m_s;
};
int main(void)
{
#if 0
/*void print()
{
cout << "hello world" << endl;
}*/
//auto p = []() {};
//auto p = [](auto num = 10)->int {}; //c++14后lambda可以推导形参
auto p = [](void *)
{
cout << "hello world" << endl;
return 'a'; //自动推导
};
//p();
//lambda的调用时机:1延时调用 2.定义并调用
//shared_ptr<int> p2(new int(6), p);//1延时调用
shared_ptr<int> p3(new int(5), [](void *p) {
cout << "hello share ptr" << endl;
delete p;
});
#endif
//捕获列表:
int num = 5;
int count = 8;
string s1 = "hello world";
//auto p = []() //不捕获外部的任何变量
//auto p1 = [&]() //按引用捕获外部所有的变量
//auto p1=[=]() //按等号捕获,只能使用外部所有的值,不能修改
//auto p1 = [num,s1]() //只能捕获num,count,只能使用,不能修改
//auto p1 = [=,&num,&count] //其它变量都是按照等号捕获,num和count按照引用捕获
auto p1 = [&,num,count]() //其它变量都是按照引用捕获,num,count按照值捕获
{
cout << "num1 = " << num << endl;
//num = 100;
s1 = "hello kitty";
//cout << count << endl;
cout << s1 << endl;
};
p1();
cout << "num1 = " << num << endl;
A a(1,"hello world");
a.test();
return 0;
}
(4)注意事项
1.引用悬挂
按引用捕获会导致指向局部变量的引用,当lambda离开局部作用域时,会导致引用的那个变量被释放,lambda里面的那个引用会发生引用悬挂。
2. this陷阱
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <memory>
typedef std::function<void(void)> FP;
using namespace std;
class Point
{
public:
Point(int x, int y) :m_x(x), m_y(y)
{
}
void print()
{
//解决方法1:产生副本,捕获副本,x,y也存在失效的风险
int x = m_x;
int y = m_y;
//this指针已经失效,空间释放,导致访问不到M_x,m_y
cout << "x: " << m_x << " y: " << m_y << endl;
//v1.push_back([=]() {cout << "x: " << m_x << " y: " << m_y << endl; });
//v1.push_back([x,y]() {cout << "x: " << x << " y: " << y << endl; });
//c++14:广义lambda捕获
v1.push_back([a = m_x, b = m_y]() {cout << "x: " <<a << " y: " << b << endl; });
}
static void print_History()
{
for_each(v1.begin(), v1.end(), [](FP p) {
if (p)
{
p();
}
});
}
private:
int m_x;
int m_y;
typedef function<void(void)> FP;
static vector<FP> v1;
};
vector<FP> Point::v1 = vector<FP>();
int main(void)
{
unique_ptr<Point> p;
p.reset(new Point(1, 2));
p->print();
p.reset(new Point(2, 3));
p->print();
p.reset(new Point(3, 4));
p->print();
Point::print_History();
return 0;
}
四 function包装器
4.1 本质
一个类模板,用于包装可调用对象,可以容纳除了类成员(函数)指针之外的所有可调用对象。
4.2 作用
可以用同一的方式来保存或者传递可调用对象。
4.3 意义
实现了一台消失机制,可以用统一的方式处理不同类型的可调用对象
function进一步深化以数据为中心的面向对象思想(函数也被对象化)。
4.4 案例
#include <iostream>
#include <functional>
using namespace std;
void print()
{
cout << "hello world" << endl;
}
class Test
{
public:
int operator()(int a, int b)
{
return a + b;
}
};
class A
{
public:
static int func(int a,int b)
{
cout << "this is class A" << endl;
return a + b;
}
};
int add(int a, int b)
{
return a + b;
}
void test(int a, int b, int(*p)(int, int)) //仿函数无法调用
{
cout << " int(*p)(int, int))" << endl;
cout << p(a, b) << endl;
}
//一个函数指针,返回值int 传入(int,int)
void test(int a, int b, function<int(int, int)> fp) //适配性更好
{
cout << "function<int(int, int)>" << endl;
cout << fp(a, b) << endl;
}
int main(void)
{
#if 0
function<void(void)> fp(print);
fp();
auto p = [](int a, int b)
{
return a + b;
};
function<int(int, int)> fp2(p);
cout << fp2(1, 2) << endl;
Test t;
//function<int(int, int)> fp3(t);
function<int(int, int)> fp3 = t;
cout << fp3(1, 2) << endl;
auto p2 = A::func;
function<void(void)> fp4 = p2;
#endif
//function作为函数的参数
test(1, 2, add);
auto p = [](int a, int b)
{
return a + b;
};
test(6,7,p);
Test t;
test(9, 8, t);
test(5, 6, A::func);
return 0;
}
五 bind适配器
(1)本质
函数模板,返回值是一个仿函数,也是可调用对象
c++11合并之前的bind1和bind2
(2) 作用
将多元的可调用对象与其参数一定绑定成一个仿函数对象
将多元(n)的可调用对象转成一元或(n-1)元的可调用对象,即只绑定部分参数。
(3)bind可以绑定的对象
1> 普通函数
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders; //使用占位符
int add(int a, int b)
{
cout << "a = " << a << " ";
cout << "b = " << b << endl;
return a + b;
}
void test(function<int(void)> t)
{
cout << t() << endl;
}
void test(function<int(int)> &t)
{
cout << t(5) << endl;
}
int main(void)
{
add(5, 6);
//test(add);
auto p = bind(add, 5, 6); //将add转为int(void)
test(p);
cout << p() << endl;
auto p1 = bind(add, placeholders::_1, 6);
cout << p1(9) << endl;
auto p2 = bind(add, 2, placeholders::_1);
cout << p2(5) << endl;
auto p3 = bind(add, std::placeholders::_2, std::placeholders::_1);
p3(3, 4);
return 0;
}
2> 函数对象
class Test //函数对象
{
public:
int operator()(int a, int b)
{
cout << "a = " << a << " ";
cout << "b = " << b << endl;
return a + b;
}
};
//绑定函数对象
Test t;
cout << t(5, 6) << endl;
//test(t);
auto p = bind(t, std::placeholders::_1, 6);
test(p);
less<int> le;
cout << le(5, 6) << endl;
auto p1 = bind(le, 5, std::placeholders::_1);
cout << p1(6) << endl;
3> 类的成员函数(_1:必须是某个对象的地址)
4> 类的数据成员(_1:必须是某个对象的地址)
//适配成员属性
Test t1;
t1.m_a = 100;
t1.m_count = 200;
auto p1 = bind(&Test::m_a, t1); //将t1和m_a进行绑定,t1按照值进行适配
cout << p1() << endl;
p1() = 1000;
cout << t1.m_a << endl;
(4)使用案例
注意:bind预先绑定的参数需要传具体的变量或者值进去,对于预先绑定的参数是按值传递的。
stl中算法
#include <iostream>
#include <functional>
using namespace std;
using namespace std::placeholders; //使用占位符
int add(int a, int b)
{
cout << "a = " << a << " ";
cout << "b = " << b << endl;
return a + b;
}
/*void test(function<int(void)> &t)
{
cout << t() << endl;
}
void test(const function<int(int)> &t)
{
cout << t(5) << endl;
}*/
class Test //函数对象
{
public:
int operator()(int a, int b)
{
cout << "a = " << a << " ";
cout << "b = " << b << endl;
return a + b;
}
int add(int a, int b)
{
cout << "a = " << a << " ";
cout << "b = " << b << endl;
return a + b;
}
int m_a;
int m_count;
};
void test(function<int(Test *,int)> &t)
{
}
int main(void)
{
//add(5, 6);
test(add);
//auto p = bind(add, 5, 6); //将add转为int(void)
//test(p);
//cout << p() << endl;
//auto p1 = bind(add, placeholders::_1, 6);
//cout << p1(9) << endl;
//auto p2 = bind(add, 2, placeholders::_1);
//cout << p2(5) << endl;
//auto p3 = bind(add, std::placeholders::_2, std::placeholders::_1);
//p3(3, 4);
//绑定函数对象
//Test t;
//cout << t(5, 6) << endl;
test(t);
//auto p = bind(t, std::placeholders::_1, 6);
//test(p);
//less<int> le;
//cout << le(5, 6) << endl;
//auto p1 = bind(le, 5, std::placeholders::_1);
//cout << p1(6) << endl;
//适配成员函数
//test(成员函数)://error:function不能包装类的成员函数和成员函数指针,需要通过bind适配器成可调用的对象
//因为bind返回的是仿函数,它是一个可调用对象,所以可以用function包装器包装
//适配成员函数时第一个参数是对象的地址
//Test t;
//auto p = bind(&Test::add, std::placeholders::_1, 5,std::placeholders::_2);
//cout << p(&t,3) << endl;
test(p);
//适配成员属性
Test t1;
t1.m_a = 100;
t1.m_count = 200;
auto p1 = bind(&Test::m_a, t1); //将t1和m_a进行绑定,t1按照值进行适配
cout << p1() << endl;
p1() = 1000;
cout << t1.m_a << endl;
return 0;
}
面试题:什么是右值引用?右值引用与左值引用的区别
一、左值与左值引用
什么是左值引用呢?
左值引用,就是绑定到左值的引用,通过&来获得左值引用。
那么,什么是左值呢?
左值,就是在内存有确定存储地址、有变量名,表达式结束依然存在的值。
左值可以分为两类:非常量左值和常量左值;同理,右值也可以分为两类:非常量右值和常量左值。
左值引用举例说明:
int a=10; //非常量左值(有确定存储地址,也有变量名)
const int a1=10; //常量左值(有确定存储地址,也有变量名)
const int a2=20; //常量左值(有确定存储地址,也有变量名)
//非常量左值引用
int &b1=a; //正确,a是一个非常量左值,可以被非常量左值引用绑定
int &b2=a1; //错误,a1是一个常量左值,不可以被非常量左值引用绑定
int &b3=10; //错误,10是一个非常量右值,不可以被非常量左值引用绑定
int &b4=a1+a2; //错误,(a1+a2)是一个常量右值,不可以被非常量左值引用绑定
//常量左值引用
const int &c1=a; //正确,a是一个非常量左值,可以被非常量右值引用绑定
const int &c2=a1; //正确,a1是一个常量左值,可以被非常量右值引用绑定
const int &c3=a+a1; //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定
const int &c4=a1+a2; //正确,(a1+a2)是一个常量右值,可以被非常量右值引用绑定
可以归纳为:非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。
二、右值与右值引用
顾名思义,什么是右值引用呢?
右值引用,就是绑定到右值的引用,通过&&来获得右值引用。
那么,什么又是右值呢?
右值,就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值。
右值引用举例说明:
int a=10; //非常量左值(有确定存储地址,也有变量名)
const int a1=20; //常量左值(有确定存储地址,也有变量名)
const int a2=20; //常量左值(有确定存储地址,也有变量名)
//非常量右值引用
int &&b1=a; //错误,a是一个非常量左值,不可以被非常量右值引用绑定
int &&b2=a1; //错误,a1是一个常量左值,不可以被非常量右值引用绑定
int &&b3=10; //正确,10是一个非常量右值,可以被非常量右值引用绑定
int &&b4=a1+a2; //错误,(a1+a2)是一个常量右值,不可以被非常量右值引用绑定
//常量右值引用
const int &&c1=a; //错误,a是一个非常量左值,不可以被常量右值引用绑定
const int &&c2=a1; //错误,a1是一个常量左值,不可以被常量右值引用绑定
const int &&c3=a+a1; //正确,(a+a1)是一个非常量右值,可以被常量右值引用绑定
const int &&c4=a1+a2; //正确,(a1+a2)是一个常量右值,不可以被常量右值引用绑定
可以将右值引用归纳为:非常量右值引用只能绑定到非常量右值上;常量右值引用可以绑定到非常量右值、常量右值上。
从上述可以发现,常量左值引用可以绑定到右值上,但右值引用不能绑定任何类型的左值,若想利用右值引用绑定左值该怎么办呢?
C++11中提供了一个标准库move函数获得绑定到左值上的右值引用,即直接调用std::move告诉编译器将左值像对待同类型右值一样处理,但是被调用后的左值将不能再被使用。
std::move使用举例说明:
int a=10; //非常量左值(有确定存储地址,也有变量名)
const int a1=20; //常量左值(有确定存储地址,也有变量名)
//非常量右值引用
int &&d1=std::move(a); //正确,将非常量左值a转换为非常量右值,可以被非常量右值引用绑定
int &&d2=std::move(a1); //错误,将常量左值a1转换为常量右值,不可以被非常量右值引用绑定
//常量右值引用
const int &&c1=std::move(a); //正确,将非常量左值a转换为非常量右值,可以被常量右值引用绑定
const int &&c2=std::move(a1); //正确,将常量左值a1转换为常量右值,可以被常量右值引用绑定
可以发现,编译器利用std::move将左值强制转换为相同类型的右值之后,引用情况跟右值是一模一样的。
三、右值引用与左值引用的区别
(1)左值引用绑定到有确定存储空间以及变量名的对象上,表达式结束后对象依然存在;右值引用绑定到要求转换的表达式、字面常量、返回右值的表达式等临时对象上,赋值表达式结束后就对象就会被销毁。
(2)左值引用后可以利用别名修改左值对象;右值引用绑定的值不能修改。
四、引入右值引用的原因
(1)替代需要销毁对象的拷贝,提高效率:某些情况下,需要拷贝一个对象然后将其销毁,如:临时类对象的拷贝就要先将旧内存的资源拷贝到新内存,然后释放旧内存,引入右值引用后,就可以让新对象直接使用旧内存并且销毁原对象,这样就减少了内存和运算资源的使用,从而提高了运行效率;
(2)移动含有不能共享资源的类对象:像IO、unique_ptr这样的类包含不能被共享的资源(如:IO缓冲、指针),因此,这些类对象不能拷贝但可以移动。这种情况,需要先调用std::move将左值强制转换为右值,再进行右值引用。
参考博客:什么是右值引用?右值引用与左值引用的区别
标签:cout,右值,int,void,左值,c++,嵌入式,引用 From: https://blog.51cto.com/u_15909950/5930349