嵌入式c++(九)

简介: 嵌入式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)是一个常量右值,可以被非常量右值引用绑定


可以归纳为:非常量左值引用只能绑定到非常量左值上;常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。


二、右值与右值引用

顾名思义,什么是右值引用呢?

右值引用,就是绑定到右值的引用,通过&&来获得右值引用。

那么,什么又是右值呢?

右值,就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值。


右值引用举例说明:


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将左值强制转换为右值,再进行右值引用。


相关文章
|
3月前
|
开发框架 Linux C语言
C、C++、boost、Qt在嵌入式系统开发中的使用
C、C++、boost、Qt在嵌入式系统开发中的使用
91 1
|
3月前
|
算法 Linux 程序员
嵌入式工程师以及C++程序员到公司就业需要掌握那些技术?
嵌入式工程师以及C++程序员到公司就业需要掌握那些技术?
|
3月前
|
数据处理 C++ UED
如何作为一个嵌入式软件工程师博主获得铁粉:C/C++ 技术分享之道
如何作为一个嵌入式软件工程师博主获得铁粉:C/C++ 技术分享之道
89 0
|
3月前
|
C语言 数据安全/隐私保护 C++
嵌入式中如何把C++代码改写成C语言代码
嵌入式中如何把C++代码改写成C语言代码
51 0
|
3月前
|
存储 缓存 Java
嵌入式系统中C++内存管理基本方法
嵌入式系统中C++内存管理基本方法
113 0
|
3月前
|
存储 编译器 程序员
嵌入式系统中C++基础知识精髓
嵌入式系统中C++基础知识精髓
79 0
|
3月前
|
关系型数据库 数据库 C++
嵌入式数据库sqlite3【基础篇】基本命令操作,小白一看就懂(C/C++)
嵌入式数据库sqlite3【基础篇】基本命令操作,小白一看就懂(C/C++)
|
3月前
|
存储 编译器 C++
嵌入式中C++ 编程习惯与编程要点分析
嵌入式中C++ 编程习惯与编程要点分析
32 1
|
3月前
|
架构师 数据挖掘 程序员
嵌入式系统中C++ 类的设计和实现分析
嵌入式系统中C++ 类的设计和实现分析
48 1
|
3月前
|
算法 小程序 编译器
嵌入式中C++开发的基本操作方法
嵌入式中C++开发的基本操作方法
38 0