auto类型推导

静态类型和动态类型

在编程语言的分类中C++常常被冠以“静态类型”的称号,静态类型和动态类型的区别在于对于变量进行类型检查的时间点。对于所谓的静态类型,类型检查主要发生在编译阶段;对于动态类型,类型检查发生在运行阶段。比如Python中拿来就用的语言特性,这归功于类型推导。
C++中类型推导的方式是重定义了auto关键字以及decltype :::warning 某种意义上来讲,auto并非一种类型,而是一个类型声明的占位符,编译器在编译阶段会将auto替代为变量的实际的类型。 :::

  1. #include<iostream>
  2. using namespace std;
  3. int main(){
  4. auto name="world.\n"; //name -> char*
  5. cout<< name << endl;
  6. }

auto的优势

  1. 初始化表达式复杂的变量声明时简化代码
  2. 可以免除在一些类型声明时的麻烦或者避免错误
  3. 一定程度上能支持泛型的编程,例如在不同平台上或是不同编译环境下;

    auto使用细则

  4. auto可以和引用以及指针结合使用 ```cpp int x; int* y=&x; double foo(); int &bar();

auto a=&x; //int auto& b=x; //int& auto c=y; //int auto d=y; //int auto e=foo(); //wrong 不能指向临时变量

  1. 2. autoconst以及volatile结合
  2. volatileconst代表了变量的两种不同属性:易失性和常量性。<br />auto可以和cv限制符一起使用,不过声明为auto的变量并不能从其初始化表达式中继承cv限制符。(顶层const和底层const?)
  3. ```cpp
  4. double foo();
  5. float* bar();
  6. const auto a=foo(); //a: const double
  7. auto d=a; //d: double
  8. auto e& e=a //e:const double &

auto使用限制

  1. auto不能是函数的形参类型
  2. 对于结构体而言,非静态成员变量的类型不能是auto
  3. 不能声明auto数组
  4. 在实例化模板时不能使用auto作为模板参数

    decltype

    C++98对动态类型的支持就是运行时识别(RTTI)。RTTI机制是为每一个类型产生一个type_info类型数据,可以在程序中使用typeid随时查询一个变量的类型。
    但是在泛型编程中,类型成了未知数。模板中表达式的返回类型不可由模板编写者确定。C++11中给出了decltype方法

    1. int main(){
    2. int i;
    3. decltype(i) j=0;
    4. cout<<typeid(j).name<<endl; //j: int
    5. }

    decltype的类型推导并不像auto一样是从变量声明的初始化表达式获得变量的类型的,decltype总是以一个普通的表达式作为参数,返回该参数的类型。并且和auto一样,也是在编译时进行的。

    decltype应用

  5. 最典型的应用就是和using或typedef结合

    1. using size_t =decltype(sizeof(0));
    2. using ptrdiff_t decltype((int*) 0-(int*) 0);
    3. vector<int> vec;
    4. typedef decltype(vec.begin()) vectype;

    即,遇到一些具有复杂类型的表达式时,可以利用decltype和using、typedfe的组合来转换成一个简单的表达式,增加代码可读性和可维护性。

decltype推导4规则

  1. int i;
  2. decltype(i) a;
  3. decltype((i)) b; //wrong b:int&

当使用decltype(e)获取变量类型时,编译器将根据以下4个规则来进行判断:

  1. 如果e是一个不带括号的标记符表达式,或是类成员访问表达式,那么decltype(e)就是e所命名的实体的类型。
  2. 否则,若e的类型时Te是一个将亡值,则判断类型为T&&
  3. 否则,若e的类型时Te是一个左值,则判断类型为T&
  4. 否则,判断类型为T。 :::tips 标记符表达式(id-expression):
    出去关键字、字面量等编译器需要的标记之外的程序员自定义的标记都可以是标记符,单个标记符对应的表达式就是标记符表达式。
    例如int arr[4]arr就是标记符表达式,而arr[3]等则不是。 :::
    1. int arr[5]={0};
    2. int* ptr=arr;
    3. // rule 1
    4. decltype(arr) var1; //标记符表达式 var1: int[5]
    5. decltype(ptr) var2;
    6. //rule 2
    7. decltype(RvalRef()) var6=1; //int&&
    8. //rule 3
    9. decltype(true?i:i) var7=i; //int& 三元运算符返回左值
    10. decltype(++i); //int& ++i返回左值
    11. decltype(arr[3]) var10=i; //int& []操作返回左值
    12. //rule 4
    13. decltype(1) var13; //int
    14. decltype(i++) var14 //int

:::info 此外值得注意的是decltype可以继承表达式的cv限制符,但是不会让对象的成员继承cv限制符。 :::

追踪返回类型

在C++98中,如果一个函数模板的返回类型依赖于实际的入口参数类型,那么返回类型在模板实例化之前都无法确定。
C++11引入新语法—追踪返回类型,来声明和定义这样的函数。

  1. template<typename T1,typename T2>
  2. auto Sum(T1& t1,T2& t2)->decltype(t1+t2){
  3. return t1+t2;
  4. }

追踪返回类型的函数和普通函数的最大区别在于返回类型的后置,(因为C++编译时是从左往右扫描的,如果decltype在前面,会出现t1,t2未声明的情况)

范围for

很多时候对于一个有范围的集合而言,由程序员来说明循环范围是多余的,也容易犯错误,C++11引入了基于范围的for循环。

  1. vector<int> vec;
  2. for(auto v:vec){
  3. cout<<v<<endl;
  4. }

能否使用范围for需要依靠一定的条件:

  1. for迭代循环的范围是可以确定的
  2. for循环要要求迭代的对象实现++和==操作