嵌入式c++(九)

一 类型推导

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)是一个常量右值,可以被非常量右值引用绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以归纳为:非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。

二、右值与右值引用
顾名思义,什么是右值引用呢?
右值引用,就是绑定到右值的引用,通过&&来获得右值引用。
那么,什么又是右值呢?
右值,就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值。

右值引用举例说明:

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)是一个常量右值,不可以被常量右值引用绑定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以将右值引用归纳为:非常量右值引用只能绑定到非常量右值上;常量右值引用可以绑定到非常量右值、常量右值上。

从上述可以发现,常量左值引用可以绑定到右值上,但右值引用不能绑定任何类型的左值,若想利用右值引用绑定左值该怎么办呢?
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转换为常量右值,可以被常量右值引用绑定
1
2
3
4
5
6
7
8
9
10
可以发现,编译器利用std::move将左值强制转换为相同类型的右值之后,引用情况跟右值是一模一样的。

三、右值引用与左值引用的区别
(1)左值引用绑定到有确定存储空间以及变量名的对象上,表达式结束后对象依然存在;右值引用绑定到要求转换的表达式、字面常量、返回右值的表达式等临时对象上,赋值表达式结束后就对象就会被销毁。
(2)左值引用后可以利用别名修改左值对象;右值引用绑定的值不能修改。

四、引入右值引用的原因
(1)替代需要销毁对象的拷贝,提高效率:某些情况下,需要拷贝一个对象然后将其销毁,如:临时类对象的拷贝就要先将旧内存的资源拷贝到新内存,然后释放旧内存,引入右值引用后,就可以让新对象直接使用旧内存并且销毁原对象,这样就减少了内存和运算资源的使用,从而提高了运行效率;
(2)移动含有不能共享资源的类对象:像IO、unique_ptr这样的类包含不能被共享的资源(如:IO缓冲、指针),因此,这些类对象不能拷贝但可以移动。这种情况,需要先调用std::move将左值强制转换为右值,再进行右值引用。
参考博客:什么是右值引用?右值引用与左值引用的区别

posted @ 2022-07-20 21:56  周末不下雨  阅读(16)  评论(0编辑  收藏  举报