BlueRoseNote/02-Note/读书笔记/深入应用C++11代码优化与工程级应用/深入应用c++11代码优化与工程级应用第一章.md
2023-06-29 11:55:02 +08:00

13 KiB
Raw Blame History

1.1.1 auto笔记

auto与引用作用则不会抛弃const

int x=0;
const auto& g=x;    //g->const int&
auto& h =g;         //h->const int&

什么时候用auto

比如在使用迭代器遍历的时候,对类型及获取并不关心的时候。

auto推断模板函数对应函数返回值

struct Foo {
	static int get(void) { return 0; }
};

struct Bar {
	static const char* get(void) { return "0"; };
};

struct Bar2 {

};

template <class A>
void func(void)
{
	auto val = A::get();
}

int main()
{
	func<Foo>();
	func<Bar>();
	func<Bar2>();//Bar2类中不包含get方法此时编译器会报错

	system("pause");
    return 0;
}

c++14 增加了函数返回值推导

在C++11下如果你想要打印出一个数的平方可能需要这样

auto square_int = [](int x) { return x * x; };
auto square_double = [](double x) { return x * x; };

std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;

为了保持函数的局部性我们才选择的lambda但C++11的lambda却导致多个类型时代码膨胀且重复此时我们需要回过头来借助全局的模板了。

但C++ 14可以完美的解决上面的问题因为C++14中lambda的参数可以用auto代替具体的类型

auto square = [](auto x) { return x * x; };

std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;

1.1.2 decltype

decltype推导规则

decltype(exp)

  1. exp是标识符、类访问表达式decltype(type)和exp类型一样。
  2. exp是函数调用decltype(exp)和返回值的类型一致。
  3. 其他情况若exp是一个左值则decltype(exp)是exp类型的左值引用否则和exp类型一致。
class Foo{
    static const int Number=0;
    int x;
};

int n=0;
volatile const int &x=n;

decltype(n) a=n;            //a->int
decltype(x) b=n;            //b->const volatile int &
decltype(Foo::Number) c=0;  //c->const int

Foo foo;
decltype(foo.x) d=0;        //d->int
int& func_int_r(void);
int&& func_int_rr(void);
int func_int(void);

const int& func_cint_r(void);
const int&& func_cint_rr(void);
const int func_cint(void);

const Foo func_cfoo(void);

int x=0;

decltype(func_int_r())  a1=x;       //a1->int &
decltype(func_int_rr()) b1=x;       //b1->int &&
decltype(func_int())    c1=x;       //c1->int 

decltype(func_cint_r()) a2=x;       //a2->const int &
decltype(func_cint_r()) b2=x;       //b2->const int &&
decltype(func_cint())   c2=x;       //c2->int &     因为是纯右值所以舍弃掉cv符只有类类型可以携带

decltype(func_cfoo())  ff=foo();    //ff->const foo
struct Foo{int x;};
const Foo foo=Foo();

decltype(foo.x)     a=0;        //a->int
decltype((foo.x))     b=a;        //b->const int &

int n=0,m=0;
decltype(n+m) c=;   c=0;        //c->int    
decltype(n+=m) d=c; d=c;        //d->int &

a和b的结果仅仅多加了一对括号他们得到的类型却是不同的。具体解释请见P12 ++++

decltype实际应用

具体解释请见P12

1.1.3 返回类型后置语法——auto和decltype结合使用

如何实现类似:

template <typename R,typename T,typename U>
R add(T t,U u)
{
    return t+u;
}

int a=1;floatb=2.0;
auto c=add<decltype(a+b)>(a+b);

错误,定义返回值时参数变量还不存在

template <typename T,typename U>
decltype(t+u) add(T t,U u)
{
    return t+u;
}

正确,但是如果T和U是无参构造函数的类就不行了

template <typename T,typename U>
decltype(T()+U()) add(T t,U u)
{
    return t+u;
}

使用c++返回类型后置语法

template <typename T,typename U>
auto add(T t,U u)->decltype(t+u)
{
    return t+u;
}

另一个例子,根据使用的重载函数推导返回类型

int& foo(int& i);
float foo(float& f);

template<typename T>
auto func(T& val)-> decltype(foo(val)
{
    return foo(val);
}

1.2.2 模板的别名

using和typedef都可以定义一个新的类型别名但是using可以直接定义一个模板别名而不像typedef需要一个struct 外敷类来实现。而且using的可读性更高。

template <typename T>
struct func_t{
    typedef void (*type)(t,t);
};
func_t<int>::type xx_1;

template<typename T>
using func_t=void(*)(T,T);
func_t<int> xx_2;

另外using可以定义任意类型的模板表达方式

template<typename T>
using type_tt = T;
type_tt<int> a;

1.3.2初始化列表的使用细节

初始化列表可以用于对应类型函数的返回值中。 例如就等于Foo123,321.0假设有Foointfloat这个构造函数

Foo function(void)
{
    return {123,321.0};
}

1.3.3初始化列表

自己定义的类并没有类似Vector、Map容器的任意长度的初始化功能如果想要实现可以添加一下构造函数来实现。

class Foo
{
public
    Foo(std::initializer_list<int> list){
    
    }
};

之后就可以执行Foo foo={1,2,3,4,5,6,7}(需要自己实现list对类内部变量的赋值)

同时std::initializer_list可以用于传值

void func(std::initializer_list<int> l){
    for(auto it=l.begin();it!=l.end();++it)
    {std::cout<<*it<<std::endl;}
}

int main()
{
    func({});
    func({1,2,3})
}

PS.std::initializer_list为了提高初始化效率使用的是引用传值所以当传值结束时这些里面的变量也都被销毁了。所以如果需要传值这需要传递它初始化容器的值。

初始化列表同时可以用于检查类型收窄,即因为类型转换而导致的数据丢失。

1.4.1基于范围的for循环

当遍历需要修改容器中的值时,则需要使用引用:

for(auto& n: arr)
{
std::cout<<n++<<std::endl;
}

如果只是希望遍历而不希望修改可以使用const auto& n

1.4.2基于范围的for循环使用细节

在:后面可以写带返回遍历对象的函数,而且这个函数只执行一次。

#include <iostream>
#include <vector>
std::vector<int> arr={1,2,3,4,5,6};

std::vector<int>& get_range()
{
    std::cout<<"get_range->"<<std::endl;
    return arr;
}
int main()
{
    for(auto val: get_range())
    {
        std::cout<<val<<std::endl;
    }
    return 0;
}

在使用基于范围的for循环时如果遍历时修改容器大小很可能出现因为迭代器失效而导致的异常情况。

1.4.3 让基于范围的for循环支持自定义类型

#include <iostream>

namespace detail_range {
template <typename T>
class iterator {
public:
	using value_type = T;
	using size_type = size_t;

private:
	size_type cursor_;
	const value_type step_;
	value_type value_;
public:
	iterator(size_type cur_start, value_type begin_val, value_type step_val)
		:cursor_(cur_start), step_(step_val), value_(begin_val)
	{
		value_ += (step_*cursor_);
	}

	value_type operator*() const { return value_; }

	bool operator!=(const iterator& rhs) const 
	{
		return (cursor_!=rhs.cursor_);
	}

	iterator& operator++(void)
	{
		value_ += step_;
		++cursor_;
		return (*this);
	}
};

template <typename T>
class impl {
public:
	using value_type = T;
	using reference = const value_type&;
	using const_reference = const value_type&;
	using iterator = const detail_range::iterator<value_type>;
	using const_iterator= const detail_range::iterator<value_type>;
	using size_type = typename iterator::size_type;//这里的typename是告诉编译器后面跟着的是类型

private:
	const value_type begin_;
	const value_type end_;
	const value_type step_;
	const size_type max_count_;

	size_type get_adjusted_count(void) const
	{
		if (step_ > 0 && begin_ >= end_)
			throw std::logic_error("End value must be greater than begin value.");
		else if (step_ < 0 && begin_ <= end_)
			throw std:: logic_error("End value must be less than begin value.");

		size_type x = static_cast<size_type>((end_ - begin_) / step_);
		if (begin_ + (step_*x) != end_)
			++x;//为了防止出现类似{0,12,0.5}之类的迭代要求,而做的升位处理
		return x;
	}
public:
	impl(value_type begin_val, value_type end_val, value_type step_val)
		:begin_(begin_val),
		end_(end_val),
		step_(step_val),
		max_count_(get_adjusted_count())
	{}
	size_type size(void) const
	{
		return max_count_;
	}

	const_iterator begin(void) const
	{
		return{ 0,begin_,step_ };
	}

	const_iterator end(void) const
	{
		return{ max_count_,begin_,step_ };
	}
};

template <typename T>
detail_range::impl<T> range(T end)
{
	return{ {},end,1 };
}

template <typename T>
detail_range::impl<T> range(T begin, T end)
{
	return{ begin,end,1 };
}

template <typename T,typename U>
auto range(T begin, T end, U step)->detail_range::impl<decltype(begin + step)>
{
	using r_t = detail_range::impl<decltype(begin + step)>;
	return r_t(begin, end, step);
}
}

int main()
{
	for (int i : detail_range::range(15))
	{
	std::cout << " " << i;
	}
	std::cout << std::endl;

	for (auto i : detail_range::range(2,8,0.5))
	{
		std::cout << " " << i;
	}
	std::cout << std::endl;

	system("pause");
    return 0;
}

1.5.2 可调用对象包装器———std::function

用于解决以下可调用对象的统一操作的问题

  1. 函数指针
  2. 具有operator() 成员函数的类对象(仿函数)
  3. 可被转化为函数指针的类对象
  4. 类成员(函数)指针

std::function也可以作为函数参数来使用也就是相当于回调函数的作用

1.5.3 std::bind绑定器

std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以用std::function进行保存并延迟调用到任何我们需要调用的时候。 作用:

  1. 将可调用对象与其他参数一起绑定成一个仿函数
  2. 将多元可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。

关于bind的使用 bind&要调用的函数,&对象, 要调用函数的参数1要调用函数的参数2...,_1(bind函数的参数1),_2(bind函数的参数2)...)

如果bind的是一个非静态成员函数第二个参数一定是一个该成员的一个指针后面才是正常的参数。

数this指针作为一个隐含参数传递给非静态成员函数用以指向该成员函数所属类所定义的对象。当不同对象调用同一个类的成员函数代码时编译器会依据该成员函数的this指针所指向的不同对象来确定应该一用哪个对象的数据成员。下面举一个简单例子。

1bind&f)() 假设f是一个全局函数绑定全局函数并调用

2bind (&A::f, A())() 假设A是一个构造函数为空的类这个形式绑定了类的成员函数故第二个参数需要传入一个成员成员静态函数除外

1.6 lambda表达式

[capture](params)opt->ret{body;};
auto f=[](int a)->int {return a+1;};

c++11允许省略lambada表达式的返回值定义

auto x1=[](int i) {return i;};
auto x2=[](){return {1,2};); //error:初始化列表无法推断

在没有参数列表时,()是可以省略的

auto f1=[]{return 1;};
class A{
  public:
  int i_=0;
  void func(int x,int y)
  {
      auto x1=[]{return i_;};           //error,没有捕获外部变量
      auto x2=[=]{return i_+x+y;};      //ok
      auto x3=[&]{return i_+x+y;};      //ok
      auto x4=[this]{return i_;};       //ok
      auto x5=[this]{return i_+x+y;};   //error,没有捕获x与y
      auto x6=[this,x,y]{return i_+x+y;};//Ok
      auto x7=[this]{return i_++}   //ok
  }
};
int a=0,b=1;
auto f1=[]{return a;};          //error没有捕获外部变量
auto f2=[&]{return a++;};       //ok
auto f3=[=]{return a;};         //ok
auto f4=[=]{return a++;};       //error, a是以复制方式捕获的无法修改
auto f5=[a]{return a+b;};       //error,没有捕获b变量
auto f6=[a,&b]{return a+(b++);};//ok
auto f7=[=,&b]{return a+(b++);};//ok

需要注意的是默认状态下lambda表达式无法修改通过复制方式捕获的外部变量如果希望修改需啊需要使用引用方式来捕获。

int a=0;
auto f=[=]{return a;};
a+=1;
std::cout<<f()<<std::endl;

在以上lambda表示式中修改外部变量并不会真正影响到外部我们却仍然无法修改他们。 那么如果希望去修改按值捕获的外部变量就需要显式致命表达式为mutable:

int a=0;
auto f1=[=]{return a++;};           //error
auto f2=[=]()mutable{return a++;};  //ok,mutable

需要注意的是被mutable修饰的lambda就算没有参数也需要写参数列表。

我们可以使用std::function与std::bind来操作lambda表达式另外对于没有捕获任何变量的lambda表达式还可以被转化为一个普通的函数指针

using func_t=int(*)(int);
func_t f=[](int a){return a;};
f(123);

lambda表示式可以基本代替std::function(一些老式库不支持lambda)

1.7 tupe元组

tuple<const char*,int>tp=make_tuple(sendPack,nSendSize);//构建一个tuple

使用tuple相当于构建一个对应的结构体
可以使用std::tie来进行解包来获取对应的数值,也可以用来创建元组。

int x=1
int y=2;
string s="aa";
auto tp=std::tie(x,s,y);
//tp是实际类型是std::tuple<int&,string&,int&>
const char* data=tp.get<0>();
int len=tp.get<1>();

int x,y;
string a;
std::tie(x,a,y)=tp;

你可以使用std::ignore占位符来表示不解某个位置的值。

std::tie(std::ignore,std::ignorey)=tp;//只获取了第三个值