Part1第1章 型别推导
1条款1:理解模板型别推导
//一般的函数模板声明
代码语言:javascript复制//一般的函数模板声明
template<typename T>
void fun(ParamType param);
fun(expr);//从expr中推导T和paramType的型别
//情况1:param是指针或引用, 但不是万能引用
代码语言:javascript复制//情况1:param是指针或引用, 但不是万能引用
template<typename T>
void f(T& param)
{
cout<<"fT1: "<<param<<endl;
}
template<typename T>
void fPtr(T* param)
{
cout<<"fT2: "<<*param<<endl;
}
//情况2:param是万能引用
代码语言:javascript复制//情况2:param是万能引用
template<typename T>
void fW(T&& param)//万能引用
{
//可以接收左值和右值
param = 40;
cout<<"fW: "<<param<<endl;
}
//情况3:param非指针也非引用
代码语言:javascript复制//情况3:param非指针也非引用
template<typename T>
void fF(T param)
{
//按值传递, param是实参的一个副本一个全新的对象
// param = "50";
cout<<"fF: "<<param<<endl;
}
//情况4:利用声明数组引用能力创造一个模板,推导数组含有的元素个数
代码语言:javascript复制//情况4: 利用声明数组引用能力创造一个模板,推导数组含有的元素个数
template<typename T,std::size_t N>
constexpr std::size_t arraySize(T (&)[N]) noexcept
{
//在编译期常量形式返回数组尺寸
return N;
}
//情况5:函数实参
代码语言:javascript复制//情况5: 函数实参
template<typename T>
void fFFunc(T param)
{
//按值传递, param是实参的一个副本一个全新的对象
// param = "50";
cout<<"fFFunc: "<<param<<endl;
}
template<typename T>
void fYYunc(T& param)
{
cout<<"fYYunc: "<<param<<endl;
}
void someFunc(int a,double b)
{
cout<<"SF: "<<a<<" "<<b<<endl;
}
客户端测试:
代码语言:javascript复制int main()
{
int x = 27;//x的型别是int
const int cx = x;//cx的型别是 const int
const int& rx = x;//rx是x的型别为 const int 的引用
//情况1:
//对模板函数调用型别的推导
f(x); //T的型别是int , param的型别是 int&
f(cx);//T的型别是const int, param的型别是const int&
f(rx);//T的型别是const int, param的型别是const int&
const int *px = &x;//px是指涉到x的指针,型别为 const int
fPtr(&x);//T的型别是int param的型别是int*
fPtr(px);//T的型别是const int, param的型别是const int*
//情况2:
fW(x);//x是左值 T的型别是int&, param的型别也是int&
//只读const 编译不过
// fW(cx);//cx是左值 T的型别是int&, param的型别也是int&
// fW(rx);//cx是左值 T的型别是int&, param的型别也是int&
fW(27);//27是个右值 T的型别是int param的型别变成了 int&&
int &&r = 100;//r绑定到一个右值,但是r变量本身是个左值
fW(r);
//情况3: 区别与情况2中注释部分
//请注意:及时cx rx是const的,param仍然不具有const型别,因为param是
//完全独立于cx和rx存在的对象,它们的一个副本,是可以修改的。
fF(x);//T和param都是int
fF(cx);//T和param都是int
fF(rx);//T和param都是int
const char* const ptr = "FUN FUN";//ptr是指涉到const对象的const指针
fF(ptr);//传递型别为const char *const的实参
//数组实参
const char name[] = "JPJPJP";//name的类型是const char[13]
const char *ptrToName = name ;//数组退化为指针
fF(name);//name是哥数组,但是T的型别却被推导成 const char*
f(name);//按引用传递
//情况4:
int keyVals[] = {1,2,3,4,5,6};
int mappedVals[arraySize(keyVals)];
cout<<arraySize(keyVals)<<endl;
//现代C
std::array<int,arraySize(keyVals)> mapped;
//情况5:
fFFunc(someFunc);//函数指针 void (*)(int,double)
fYYunc(someFunc);//函数引用 void (&)(int,double)
}
输出:
fT1: 27 fT1: 27 fT1: 27 fT2: 27 fT2: 27 fW: 40 fW: 40 fW: 40 fF: 40 fF: 27 fF: 40 fF: FUN FUN fF: JPJPJP fT1: JPJPJP 6 fFFunc: 1 fYYunc: 1
2条款2:理解auto型别推导
auto类别推导其实就是模板类别推导,只不过模板类别推导涉及模板、函数和形参,而auto和它们无关
主要思想:
//条款1:函数模板推导
// template
// void f(ParamType param);
// f(expr);
//条款2 auto应用在 条款1中可以如下解释:
//1, auto 扮演了模板中的 T 这个角色
//2, 变量的型别修饰词扮演的是 ParamType 的角色
//情况1:
代码语言:javascript复制//情况1:
auto x =27;//x的型别修饰词是其自身
const auto cx =x;//x的型别修饰词为 const auto
const auto& rx =x;//型别修饰词变成了 const autp&
//对应的模板类别
template<typename T>
void func_for_x(T param)
{
cout<<"T: "<<param<<endl;
}
template<typename T>
void func_for_cx(const T param)
{
cout<<"const T: "<<param<<endl;
}
template<typename T>
void func_for_rx(const T& param)
{
cout<<"const T&: "<<param<<endl;
}
// 情况2:右值引用
代码语言:javascript复制// 情况2: 右值引用
auto&& uref1 = x;//x的型别是int 左值 所以 uref1的型别是 int&
auto&& uref2 = cx;//cx的型别是 const int, 左值 所以 uref2的型别是 const int&
auto&& uref3 = 27;//27的型别是 int 右值 所以 uref3的型别是 int&&
//情况3:数组退化成指针
代码语言:javascript复制//情况3:数组退化成指针
const char name[] = "IIIIII";//name的型别是 const char[7]
auto arr1 = name;//arr1的型别 const char*
auto& arr2 = name;//arr2的型别 const char (&)[7]
void someFunc(int a,double b)//someFunc是个函数 型别是 void(int,double)
{
cout<<"someFunc: "<<a<<" "<<b<<endl;
}
auto func1 = someFunc;//func1的型别是 void (*)(int,double)
auto& func2 = someFunc;//func1的型别是 void (&)(int,double)
//情况4:声明int的方式
代码语言:javascript复制//情况4: 声明int的方式
//C 98
int x1 = 27;
int x2(27);
//C 11
int x3 = {27};
int x4{27};
//转换成 auto
auto x11 = 27;
auto x22(27);//类型int 值 27
auto x33 = {27};//型别是 std::initializer_list<T> 值是 {27} 里面的类型必须一致
auto x44{27};//同上
//情况5:auto 返回值和 lambda表达式
代码语言:javascript复制//情况5:auto 返回值和 lambda表达式
//必须使用模板型别推导而不是 auto 型别推导
// auto createInitList()
// {
// return {1,2,3};//错误,
// }
// std::vector<int> v;
// auto resetV = [&v](const auto& newValue) {
// v = newValue;
// };
// resetV({1,2,3});//错误
测试用例:
代码语言:javascript复制int main()
{
//测试1:
func_for_x(27);//param的型别就是 x 的型别
func_for_cx(x);//param的型别就是 cx 的型别
func_for_rx(x); //param的型别就是 rx 的型别
//测试2:
func_for_x(name);//param的型别就是 x 的型别
func_for_cx(arr1);//param的型别就是 cx 的型别
func_for_rx(arr2); //param的型别就是 rx 的型别
func1(1,2);
func2(3,4);
//测试4
func_for_x(x11);
func_for_x(x22);
func_for_x(x3);
func_for_x(x4);
}
//要点速记
//1, 一般下, auto型别推导和模板型别推导一样,但是 auto型别推导会假定用大括号括起来的初始化表达式代表一个
//std::initializer_list, 但是模板型别推导却不会
//2, 在函数返回值或 lambda的形参中使用 auto,意思是使用模板型别推导而不是 auto 型别推导
3条款3:理解 decltype
//decltype作用:对于给定的名字或表达式, decltype 能告诉你该名字或表达式的型别
//情况1:鹦鹉学舌,返回给定的名字或表达式的确切型别而已
代码语言:javascript复制//情况1: 鹦鹉学舌,返回给定的名字或表达式的确切型别而已
class Widget{
};
const int i = 0; //decltype(i) 是 const int
bool f(const Widget& w);//decltype(w)是 const Widget&, decltype(f)是bool(const Widget&)
struct Point{ //decltype(Point::x)是 int
int x,y;
};
Widget w;//decltype(w)是 Widget
if(f(w))//decltype(f(w))是Widget
//std::vector的简化版
template<typename T>
class vector{
public:
T& operator[](std::size_t index);
};
vector<int> v;//decltype(v)是vector<int>
if(v[0] == 0) //decltype(v[0]) 是 int&
//情况2:使用 decltype 计算返回值型别
//一般来说,含有型别 T 的对象的容器,其 operator[] 会返回 T&, 注意一点 std::vector对应的 operator[] 并不返回 bool& ,而是返回一个全新对象。
代码语言:javascript复制//情况2:使用 decltype 计算返回值型别
//一般来说,含有型别 T 的对象的容器,其 operator[] 会返回 T&, 注意一点 std::vector<bool> 对应的 operator[] 并不返回 bool&
//而是返回一个全新对象
void authenticateUser()
{
cout<<"I am lyy"<<endl;
}
template<typename Container, typename Index>
auto authAndAccess11(Container& c, Index i) -> decltype(c[i])//C 11
{
authenticateUser();
return c[i];
}
//以上好处是:使用这样一个声明之后,operator[]返回值是什么型别,authAndAccess的返回值就是什么型别
template<typename Container, typename Index>
auto authAndAccess14(Container& c, Index i)//C 14
{
authenticateUser();
return c[i]; //返回值型别是根据 c[i] 推导出来
}
//以上设计会有隐患:大多数含有 型别 T 的对象的容器的 operator[] 会返回 T&, 初始化表达的引用性会被忽略
std::deque<int> d;
//验证用户, 并返回d[5], 然后将其赋值为10
authAndAccess11(d,5) = 10; // 没有问题,可以通过编译
authAndAccess14(d,5) = 10; //无法通过编译
//d[5] 返回的是 int& , 但是对 authAndAccess 的返回值实施 auto 型别推导将剥去引用,这么一来返回值型别就成了 int 作为函数的返回值,该 int 是个右值, 所以上述代码其实是尝试将 10 赋给一个右值 int, C 中无法通过编译
//如上改进:authAndAccess,指定 这个函数的返回值型别与表达式 c[i]返回的型别完全一致
//如下:auto指定了欲实施推导的型别,推导过程中采用的是 decltype 的规则
template<typename Container, typename Index>
decltype(auto)
authAndAccess1414(Container& c, Index i)
{
authenticateUser();
return c[i];
}
//情况3:变量声明的场合,在初始化表达式处应用 decltype 型别推导规则
代码语言:javascript复制// //情况3:变量声明的场合,在初始化表达式处应用 decltype 型别推导规则
Widget w;
const Widget& cw = w;
auto myWidget1 = cw;//auto型别推导:myWidget1的型别是 Widget
decltype(auto) myWidget2 = cw;//decltype型别推导:myWidget2的型别是 const Widget&
//情况2的改进:容器的传递方式是非常量的左值引用,因为返回该容器的某个元素的引用,就意味着允许客户对容器进行修改,这也意味着无法向容器中传递右值容器,右值是不能绑定到左值引用的。
代码语言:javascript复制//万能引用的实现
template<typename Container, typename Index>
decltype(auto)
authAndAccessWN14(Container&& c, Index i)//C 14
{
authenticateUser();
return std::forward<Container>(c)[i];
}
//万能引用的实现
template<typename Container, typename Index>
auto
authAndAccessWN11(Container&& c, Index i)
-> decltype(std::forward<Container>(c)[i])//C 11
{
authenticateUser();
return std::forward<Container>(c)[i];
}
// //情况4:函数返回值
代码语言:javascript复制// //情况4: 函数返回值
decltype(auto) f1()
{
int x =0;
return x;//decltype(x)是int,所以f1 返回的是int
}
decltype(auto) f2()
{
int x =0;
return (x);//decltype(x)是int&,所以f2 返回的是int&
}
//但是,注意 f2返回的是一个局部变量的引用,未定义报错
测试用例:
代码语言:javascript复制int main()
{
//测试1:
std::vector<int> c = {1,2,3};//条款2的讲解
cout<<authAndAccess11(c,1)<<endl;
cout<<authAndAccess14(c,1)<<endl;
std::deque<int> d;
authAndAccess11(d,5) = 10;
//authAndAccess14(d,5) = 10;
authAndAccess1414(d,5) = 100;
cout<<d[5]<<endl;
//测试2:
//左值
cout<<authAndAccessWN11(c,2)<<endl;
cout<<authAndAccessWN14(c,2)<<endl;
//右值
auto c_new = std::move(c);
cout<<authAndAccessWN11(c_new,0)<<endl;
cout<<authAndAccessWN14(c_new,0)<<endl;
//测试3:
cout<<f1()<<endl;
cout<<f2()<<endl;
}
I am lyy 2 I am lyy 2 I am lyy I am lyy 100 I am lyy 3 I am lyy 3 I am lyy 1 I am lyy 1 0 Segmentation fault
//要点速记
//1, 绝大多数情况下,decltype 会得出变量或表达式的型别而不作任何修改
//2, 对于型别为 T 的左值表达式,除非该表达式仅有一个名字,decltype总是得出型别 T&
//3, C 14 支持 decltype(auto) ,和auto一样,它会从其初始化表达式出发来推导型别,但是它的型别推导使用的是 decltype的规则
4条款4:掌握查看型别推导结果的方法
//查看型别推导的三个阶段:撰写代码阶段,编译阶段和运行阶段
//撰写代码阶段
代码语言:javascript复制//撰写代码阶段
const int theAnswer = 42;
auto x = theAnswer;//x的型别推导是 int
auto y = &theAnswer;//y的型别推导是 const int*
//编译器诊断信息
代码语言:javascript复制//编译器诊断信息
//如要查看上面 x和y推导而得到的型别, 先声明一个类模板,但不去定义
template<typename T>
class TD;
//结果可想而知:只要试图实现该模板,就会诱发一个错误信息,原因是找不到实现模板所需要的定义
//运行时输出
代码语言:javascript复制//运行时输出
template<typename T>
void f(const T& param)
{
using std::cout;
cout<<"T = "<<typeid(T).name()<<endl;
cout<<"param = "<<typeid(T).name()<<endl;
}
class Widget
{
};
//测试
代码语言:javascript复制//测试
int main()
{
//测试1
// TD<decltype(x)> xType;
// TD<decltype(y)> yType;
//测试2: 运行时输出型别
std::cout<<typeid(x).name()<<endl;
std::cout<<typeid(y).name()<<endl;
//测试3:
Widget *w = new Widget;
std::vector<Widget*> createVec = {w,w,w};
const auto vw = createVec;
if(!vw.empty())
{
f(&vw[0]);
}
}
结果:
i PKi T = PKP6Widget param = PKP6Widget
Part2第2章 auto
//如何引导 auto 得出正确的结果
5条款6:优先选用auto,而非显式型别声明
//情况1:没有初始化值
代码语言:javascript复制//情况1:没有初始化值
int x; //它的值是不确定的
//使用迭代器的提领结果来初始化局部变量:
template<typename It>
void dwim(It b, It e)
{
while( b != e)
{
typename std::iterator_traits<It>::value_type //currValue的型别是这么一大串
currValue = *b;
}
}
//改进:用auto ,其型别都推导自其初始化物,所以它们必须初始化
auto x3 = 0;
template<typename It>
void dwimAuto(It b, It e)
{
while( b != e)
{
auto currValue = *b;
}
}
//情况2:auto使用了型别推导,就可以用它来表示只有编译器才掌握的型别
代码语言:javascript复制//情况2:auto使用了型别推导,就可以用它来表示只有编译器才掌握的型别
class Widget
{
};
auto derefUPLess =
[](const std::unique_ptr<Widget>& p1,
const std::unique_ptr<Widget>& p2)
{
return *p1 < *p2;
}
//使用C 4 可以在 lambda 表达式的形参中使用auto了
auto derefLess =
[](const auto& p1,
const auto& p2)
{
return *p1 < *p2;
}
//情况3:auto 大智慧
代码语言:javascript复制//情况3:auto 大智慧
//以下代码存在什么隐患呢?
std::unordered_map<std::string, int> m;
for(const std::pair<std::string,int>& p : m)
{
}
//std::unordered_map的键值部分是 const,所以哈希表中的 std::pair的型别并不是
//std::pair<std::string,int>,而应是 std::pair<const std::string, int>,可是
//上面的循环中,用以声明变量 p 的型别并不是这个。因此编译器需要将const 转换成 非const
//转换原理:对 m中的每个对象都做一次复制操作,形成一个 p想要绑定的型别的临时对象,
//然后把 p 这个引用绑定到该临时对象,在循环的每次迭代结束时,该临时对象都会被析构一次。
//改进:使用 auto化解
for(const auto& p : m)
{
//1,如果对 p取地址,肯定会取得一个指涉 到 m中的某个元素的指针
//2,而上面哪个,取得的则是一个指涉到临时对象的指针,而且这个对象会在循环迭代结束时被析构
}