C++11中的 auto 和 decltype 关键字


0x1 auto 关键字

在C++程序中,每个变量都有自己的类型,这就要求变量在声明的时候必须清楚地知道其右侧初始值的类型。然而做到这一点并非易事,有时候甚至无法做到。在C++11之前的版本中,变量在声明或者初始化的时候,必须显式的指定其类型,但是在C++11中引入了 auto 关键字,用它可以让编译器替我们去分析初始值的类型来推算变量的类型。

auto 的语法规则

auto 是让编译器根据初始值来推断所定义的变量的类型,在 auto 的推断过程中,一般遵循四个原则:

  • 同一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样。
  • 符号 &* 只从属于某个声明符,而非基本类型的一部分。
  • 当引用被当做初始值的时候,真正用于推断 auto 类型的初始值实际上是引用对象的值。
  • 当用于推断 auto 类型的初始值是常量时,如果可以忽略其常量性质,则忽略其常量性质。
int i = 0;
const int c1 = i;
const int &c2 = i;
const int *p2 = &i;
int *const p3 = &i;

// 前两条规则的示例
auto j = 0, *p = &j;		//j的类型是int,p的类型是int *,该语句的基本数据类型是int
auto sz = 0, pi= 3.14;		//错误,sz和pi的类型不同
auto &n = c1, *p1 = &c1;	//n的类型是const int &,p1的类型是const int *,该语句的基本数据类型是const int

// 后两条规则的示例
auto a = i;		//i是一个int型变量,所以a是int型
auto b = c1;	//c1是一个const int型的常量,初始值本身是个常量,故忽略其常量性质,所以b是int型
auto c = c2;	//c2是一个const int &型的引用,所以实际上的初始值是引用对象c1,故c是int型
auto d = &c1;	//c1是一个const int型的常量,对其取地址得到的const int*型的初始值,注意该初始值本身并不是一个常量,而是其指向的对象是常量,故d是const int*型
auto e = p2;	//p2是一个const int*型,初始值并不是常量,故e是const int*型
auto f = p3; 	//p3是一个int *类型的常量指针,所以忽略其常量性质,则f是int *类型
auto &g = c1;	//c1是一个const int型的常量,如果忽略其常量性质,则得到g是与从绑定的int &型变量,显然违背了基本原则,所以该处不能忽略c1的常量性质,则g是const int &类型

auto的常见用法

1、 auto 用于代替冗长复杂的变量声明

// 不使用auto关键字,变量的类型冗长不宜读
#include <map>
int main(void)
{
    
    
    std::unordered_multimap<int, int> resultMap;
    // ...
    std::pair<std::unordered_multimap<int,int>::iterator, std::unordered_multimap<int, int>::iterator> range = resultMap.equal_range(key);
    return 0;
}
// 这个 equal_range 返回的类型声明显得烦琐而冗长,而且实际上并不关心这里的具体类型.
// 这时,通过 auto 就能极大的简化书写,省去推导具体类型的过程:
#include <map>
int main(void)
{
    
    
    std::unordered_multimap<int, int> map;
    // ...
    auto range = map.equal_range(key);
    return 0;
}

2、auto 用于泛型编程

// 在定义模板函数时,用于声明依赖模板参数的变量类型
template <typename T1,typename T2>
void add(T1 a, T2 b)
{
    
    
    auto v = a+b;
    std::cout << v;
}

0x2 decltype 关键字

有时候在编程时会遇到一种情况:希望从表达式的类型推断出要定义的变量的类型,但是不希望用该表达式的值初始化变量。C++11针对这种情况引用了 decltype 关键字,其作用是根据表达式返回数据类型。

decltype 的语法规则

decltype 的用法:

  • decltype(exp) varname = value;   跟据表达式推导出来的类型的必须初始化,比如说引用
  • decltype(exp) varname;  根据表达式推导出来的类型声明变量可以不初始化,比如说int

decltype 在推导类型时完全依据其括号内 exp 的类型,其所遵循的规则可以总结为三点:

  • auto 相比,decltype 不忽视引用和 const ,如果 exp 是一个变量的话,则 decltype(exp) 的类型和 exp 的类型完全一致,包括 const 属性和引用在内。
  • 如果 exp 是一个表达式,则 decltype(exp) 的类型和表达式返回的类型一致。
  • 如果 exp 加上一组括号,即 decltype((exp)) 形式,则推断的类型必然是一个引用。
int i = 0;
int *p = &i;
int &r = i;
const int ci = 0, &cj = ci;

decltype(ci) a = 0;		//a是const int型,因为ci是const int型
decltype(cj) b = a;		//b是const int&型,因为cj是const int&型

decltype((i)) c1 = i;	//c1是int &型,因为i是int型,且被括号包围
decltype(i) c2;			//c2是int型,因为i是int型
decltype(*p) d = i;		//d是int &型,因为p是int *型,对变量的解引用运算会返回对应引用
decltype(i+0) e;		//e是int型,因为i+0表达式返回到是一个int型右值
decltype(i=i+1) f;		//报错,f是int &型,必须初始化,因为赋值运算返回对象的引用

clang 语法树验证如下:
在这里插入图片描述

decltype的常见用法

#include <vector>
using namespace std;
template <typename T>
class Base {
    
    
public:
    void func(T& container) {
    
    
        m_it = container.begin();	//在运行时可能会出错,因为m_it的类型是T::iterator,但是begin()可能会返回const_iterator
    }
private:
    typename T::iterator m_it;  
};

int main()
{
    
    
    const vector<int> v;
    Base<const vector<int>> obj;
    obj.func(v);
    return 0;
}

单独看 Base 类中 m_it 成员的定义,很难看出会有什么错误,但在使用 Base 类的时候,如果传入一个 const 类型的容器,编译器马上就会弹出错误信息,提示没有重载的操作符 =。原因就在于,T::iterator并不能包括所有的迭代器类型,当 T 是一个 const 容器时,应当使用 const_iterator。如果不使用decltype的话,则只能想办法把 const 类型的容器用模板特化单独处理,增加了不少工作量。但是有了 C++11 的 decltype 关键字,就可以直接这样写:

#include <vector>
using namespace std;
template <typename T>
class Base {
    
    
public:
    void func(T& container) {
    
    
        m_it = container.begin();
    }
private:
    decltype(T().begin()) m_it;  //m_it的类型由begin()返回的类型决定
};

int main()
{
    
    
    const vector<int> v;
    Base<const vector<int>> obj;
    obj.func(v);
    return 0;
}

参考资料: 《C++ Prime》、C语言中文网

猜你喜欢

转载自blog.csdn.net/qq_21746331/article/details/114338505
今日推荐