之前我们已经学习了对于C++类型处理的相关知识。作为一个以类型安全为宗旨的语言,对于数据类型的认识是十分重要的。本期我们就来学习一下C++11中更好用的类型表达及其对数据类型的处理。
作者的个人gitee:https://gitee.com/riko-lou-tian/cpp-code-learning
喜欢请支持一下谢谢
目录
auto
前⾯课程中我们已经⽤过auto,auto是⼀个类型说明符,他让编译器替我们分析表达式的类型,auto x = y + z; 编译器⾃动根据y+z相加的结果来推导x的类型,在⼀些类型⽐较⻓的场景,如前⾯讲的迭代器遍历时⾮常有⽤。
编译器推导auto类型时,有时候也会和初始值的类型不⼀样,编译器会适当的改变结果类型,使其更符合初始化规则。⾸先使⽤引⽤其实是使⽤引⽤的对象,特别是当引⽤被⽤作初始值时,真正参与初始化的其实是引⽤对象的值 ,所以编译器推导auto为引⽤对象的类型,⽽不是引⽤。其次⼀个带有const属性的值初始化auto对象推导时忽略掉顶层const,保留底层const。
auto不能⾃动推导出引⽤类型,所以我们如果想将auto推导为引⽤类型,需要明确的指出:
cpp
auto& x = i;
auto不能推导出顶层const,如果想使⽤auto推导出顶层const,需要明确的指出:
cpp
const auto x = ci;
设置⼀个类型为auto引⽤时,初始值中的顶层const属性仍然保留,否则存在权限放⼤问题。
cpp
#include <iostream>
using namespace std;
int main()
{
int i = 0;
int& ri = i;
const int ci = 42; // 顶层const
int* const p1 = &i; // 顶层const
const int* p2 = &ci; // 底层const
const int& ri1 = ci; // 底层const
const int& ri2 = i; // 底层const
auto j = ri; // j类型为int
j++;
auto k = i; // k类型为int
k++;
auto r1 = ci; // r1类型为int,忽略掉顶层const
r1++;
auto r2 = p1; // r2类型为int*,忽略掉顶层const
r2++;
auto r3 = p2; // r3类型为const int*,保留底层const
// (*r3)++; // 报错
auto r4 = ri1; // r4类型为int,因为ri1是ci的别名,ci本⾝的const是⼀个顶层const被忽略掉了
r4++;
auto r5 = ri2; // r5类型为int
r5++;
const auto r7 = ci; // r7类型为const int
auto& r8 = ri1; // r8类型为const int&
auto& r9 = ri2; // r9类型为const int&
auto& r10 = ci; // r10类型为const int&
auto& r11 = ri; // r11类型为int&
//r7++; // 报错
//r8++; // 报错
//r9++; // 报错
//r10++; // 报错
r11++;
return 0;
}
auto& 声明⼀个左值引⽤,它只能绑定到左值,如果初始化对象有const属性,推导时会保持
const 限定符,否则涉及权限放⼤。
const auto& 声明⼀个const 左值引⽤,既可以绑定到左值⼜可以绑定到右值,不会修改绑定
对象。
auto&& 是万能引⽤,遵循引⽤折叠的规则,既可以绑定到左值⼜可以绑定到右值,初始化表达式⾃动推导为左值引⽤或右值引⽤,如果初始化对象有const属性,推导时会保持 const 限定符。
注意:auto不可以定义成员变量!
测试代码:
cpp
#include <iostream>
#include<vector>
using namespace std;
void func(int& x)
{
cout << "void func(int& x)" << endl;
}
void func(int&& x)
{
cout << "void func(int&& x)" << endl;
}
void func(const int& x)
{
cout << "void func(const int& x)" << endl;}
void func(const int&& x)
{
cout << "void func(const int&& x)" << endl;
}
int main()
{
int x = 10;
const int cx = 20;
auto& rx1 = x; // int&
auto& rx2 = cx; // const int&
func(rx1);
func(rx2);
const auto& rx3 = x; // const int&
const auto& rx4 = cx; // const int&
func(rx3);
func(rx4);
// 万能引⽤
auto&& rx5 = x; // int&
auto&& rx6 = cx; // const int&
func(rx5);
func(rx6);
auto&& rx7 = move(x); // int&&
//rx7++;
auto&& rx8 = move(cx); // const int&&
//rx8++;
func(forward<int>(rx7));
func(forward<const int>(rx8));
return 0;
}
结果为:

decltype
如果我们希望⽤表达式推出变量的类型,但是不想⽤表达式的值初始化变量,那么这时可以使⽤decltype。
cpp
decltype(f()) x;
需要注意的是编译器并不会实际调⽤f函数,⽽是⽤f的返回类型作为x的类型。
decltype 处理const和引⽤的⽅式和auto也有所不同,
cpp
decltype(const变量表达式) x
x的类型推出类型为const T,decltype会保留顶层const; decltype(引⽤变量表达式) x ,x的
类型推出类型为T &,decltype会保留引⽤;要注意这⾥跟auto是完全不同的。
decltype还有⼀些特殊处理⽐较奇怪,
cpp
decltype(*p) x;
x的类型是T&,decltye推导解引⽤表达式时,推出类型是引⽤;
cpp
decltype((i)) x;
x的类型是T&,decltye推导解括号括起来的左值表达式时,推出类型是引⽤;
cpp
#include <iostream>
using namespace std;
int main()
{
int i = 0;
const int ci = 0;
const int& rci = ci;
decltype(i) m = 1; // m的类型是int
decltype(ci) x = 1; // x的类型是const int
m++;
x++; // 报错
decltype(rci) y = x; // y的类型是const int&
y++; // 报错
decltype(rci) z; // 报错
int* p1 = &i;
decltype(p1) p2 = nullptr; // p2的类型是int*
// 特殊处理
decltype(*p1) r1 = i; // r1的类型是int&,解引⽤表达式推导出的内容是引⽤
decltype(i) r2;
// r3的类型是int&, (i)是⼀个表达式,变量是⼀种可以赋值特殊表达式,
// 所以会推出引⽤类型
decltype((i)) r3 = i;
return 0;
}
auto必须要通过初始化值推导类型,像类的成员变量这种就没办法使⽤auto,decltype可以很好的解决这样的问题
cpp
#include <iostream>
#include<vector>
using namespace std;
template <typename T>
class A
{
public :
void func(T& container)
{
_it = container.begin();
}
private:
// 这⾥不确定是iterator还是const_iterator,也不能使⽤auto
//typename T::iterator _it;
// 使⽤decltype推导就可以很好的解决问题
decltype(T().begin()) _it;
};
int main()
{
const vector<int> v1;
A<const vector<int>> obj1;
obj1.func(v1);
vector<int> v2;
A<vector<int>> obj2;
obj2.func(v2);
return 0;
}
decltype可以用来实例化lambda表达式的类型进行赋值
cpp
int main()
{
//decltype在lambda表达式中也能用的应用
auto lambda1 = [](int x, int y) { return x + y; };
decltype(lambda1) lambda2 = lambda1; // 复制构造
// 使用
std::cout << lambda1(5, 3) << std::endl; // 8
std::cout << lambda2(5, 3) << std::endl; // 8
return 0;
}
decltype还可以⽤来解决函数尾置返回类型的问题,有时⼀个函数模板的类型跟是不确定的,跟某个参数对象有关,需要进⾏推导,直接⽤decltype推导去做返回类型是不⾏了,因为C++是前置语法,往前找不到这个推导对象,这个对象在参数⾥⾯,所以auto做返回值,然后->decltype(对象)做尾置推导
cpp
template<class R, class Iter>
R Func(Iter it1, Iter it2)
{
R x = *it1;
++it1;
while (it1 != it2)
{
x += *it1;
++it1;
}
return x;
}
// 不⽀持这样写,因为C++是前置语法,编译器遇到对象只会向前搜索
//template<class Iter>
//decltype(*it1) Func(Iter it1, Iter it2)
//{
// auto& x = *it1;
// ++it1;
// while (it1 != it2)
// {
// x += *it1;
// ++it1;
// }
// return x;
//}
// 返回位置⽤auto,函数后⾯接->推导返回类型的位置返回值⽅式
// 要注意decltype(*it1)推导出的是引⽤类型
//template<class Iter>
//auto Func(Iter it1, Iter it2)->decltype(*it1)
//{
// auto& x = *it1;
// ++it1;
// while (it1 != it2)
// {
// x += *it1
// ++it1;
// }
// return x;
//}
int main()
{
vector<int> v = { 1,2,3 };
list<string> lt = { "111","222","333" };
// 这⾥⽆法调⽤上⾯的函数,因为函数模板只能通过实参推导模板类型,⽆法推导R
//auto ret1 = Func(v.begin(), v.end());
//auto ret2 = Func(lt.begin(), lt.end());
// 显⽰实例化能解决问题,但是调⽤就很⿇烦
auto ret1 = Func<decltype(*v.begin())>(v.begin(), v.end());
auto ret2 = Func<decltype(*lt.begin())>(lt.begin(), lt.end());
cout << ret1 << endl;
cout << ret2 << endl;
return 0;
}
decltype(auto) 是 C++14 引⼊的特性,它结合了 auto 的便利性和 decltype 的精确类型推导能⼒。
cpp
//decltype(auto)
int main()
{
int i = 0;
int& ri = i;
const int ci = 42; // 顶层const
int* const p1 = &i; // 顶层const
const int* p2 = &ci; // 底层const
auto j = ri; // j类型为int
decltype(auto) j1 = ri; // j1类型为int&
++j1;
auto r1 = ci; // r1类型为int,忽略掉顶层const
decltype(auto) rr1 = ci; // rr1类型为const int
r1++;
//rr1++;
auto r2 = p1; // r2类型为int*,忽略掉顶层const
decltype(auto) rr2 = p1; // rr1类型为int* const
r2++;
//rr2++;
auto r3 = p2; // r3类型为const int*,保留底层const
decltype(auto) rr3 = p2; // rr3类型为const int*
// (*rr3)++;
return 0;
}
尾置返回类型
尾置返回类型是C++11引⼊的⼀种函数声明语法,它允许将函数的返回类型放在参数列表之后⽽不是函数名前。尾置返回类型的语法这⾥我们简单的做个了解即可,因为C++14引⽤了auto做返回类型时,返回类型⾃动推导,很多地⽅就不太需要尾置返回类型了 。
基本语法
cpp
auto functionName(parameters) -> returnType
{
// 函数体
}
为什么需要尾置返回类型
-
提⾼代码可读性:特别是当返回类型很⻓或复杂时
-
⽀持Lambda表达式:Lambda表达式的返回类型必须使⽤尾置语法
-
模板编程:在模板函数中,返回类型可能依赖于参数类型
使用场景
cpp
// 1. 复杂返回类型
auto getComplexType() -> std::map<std::string, std::vector<int>> {
// ...
}
// 2. 依赖参数类型的返回类型
template <typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}
// 3. lambda表达式
auto lambda = [](int x) -> double { return x * 1.5; };
typedef和using
C++98中我们⼀般使⽤typedef重定义类型名,也很⽅便,但是typedef不⽀持带模板参数的类型重定义。C++11中新增了using可以替代typedef,using 的别名语法覆盖了 typedef 的全部功能,不少场景还更清晰⼀些,⽐如函数指针的重定义,其次最⼤的变化是⽀持带模板参数重定义的语法。
语法格式 :
cpp
using 类型别名 = 类型;
cpp
#include<iostream>
#include <map>
#include <string>
using namespace std;
//typedef map<string, int> CountMap;
//typedef map<string, string> DictMap;
//typedef int DateType;
// typedef void (*Callback)(int);
// using 兼容typedef的⽤法
using CountMap = map<string, int>;
using DictMap = map<string, string>;
using STDateType = int;
using Callback = void (*)(int);
// using⽀持带模板参数的类型重定义
template<class Val>
using Map = map<string, Val>;
template<class Val>
using MapIter = typename map<string, Val>::iterator;
int main()
{
Map<int> countMap;
Map<string> dictMap;
MapIter<int> countIter = countMap.begin();
MapIter<string> dictIter = dictMap.begin();
return 0;
}
本期内容就到这里了,喜欢请点个赞谢谢
封面图自取:
