13 KiB
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)
- exp是标识符、类访问表达式,decltype(type)和exp类型一样。
- exp是函数调用,decltype(exp)和返回值的类型一致。
- 其他情况,若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初始化列表的使用细节
初始化列表可以用于对应类型函数的返回值中。 例如:就等于Foo(123,321.0),(假设有Foo(int,float)这个构造函数)
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
用于解决以下可调用对象的统一操作的问题
- 函数指针
- 具有operator() 成员函数的类对象(仿函数)
- 可被转化为函数指针的类对象
- 类成员(函数)指针
std::function也可以作为函数参数来使用,也就是相当于回调函数的作用
1.5.3 std::bind绑定器
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以用std::function进行保存,并延迟调用到任何我们需要调用的时候。 作用:
- 将可调用对象与其他参数一起绑定成一个仿函数
- 将多元可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。
关于bind的使用: bind(&要调用的函数,&对象, 要调用函数的参数1,要调用函数的参数2...,_1(bind函数的参数1),_2(bind函数的参数2)...)
注:如果bind的是一个非静态成员函数,第二个参数一定是一个该成员的一个指针,后面才是正常的参数。
数this指针作为一个隐含参数传递给非静态成员函数,用以指向该成员函数所属类所定义的对象。当不同对象调用同一个类的成员函数代码时,编译器会依据该成员函数的this指针所指向的不同对象来确定应该一用哪个对象的数据成员。下面举一个简单例子。
(1)bind(&f)() 假设f是一个全局函数,绑定全局函数并调用;
(2)bind (&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::ignore,y)=tp;//只获取了第三个值