4.2 auto类型推导

一、动静态类型

静态类型:使用前需要声明和定义。

动态类型:无需声明直接使用(需要定义)。如Python、Perl、JavaScript

本质区别在于类型检查时机,静态在编译期,动态在运行时(依赖类型推导)

C++11引入了两种类型推导:auto和decltype。两者都是静态类型,因此必须是编译期推导的类型。

而且auto声明必须定义,否则无法推导。实际上auto不是类型,只是类型占位符,在编译期推导成功后进行类型替换。

二、auto优势

简化代码
#include 
#include 
void loopover(std::vector & vs) {
    std::vector::iterator i = vs.begin(); // 想要使用iterator,往往需要
    书写大量代码
    for (; i < vs.end(); i++) {
        // 一些代码
    }
}
#include 
#include 
void loopover(std::vector & vs) {
    for (auto i = vs.begin(); i < vs.end(); i++) {
        // 一些代码
    }
}

对比上述两例代码可以看到使用auto,能够简化代码类型。

避免声明出错,尤其是有隐式转换的操作时
class PI {
public:
double operator* (float v) {
    return (double)val * v;     // 这里精度被扩展了
}
const float val = 3.1415927f;
};
int main() {
    float radius = 1.7e10;
    PI pi;
    auto circumference = 2 * (pi * radius);
}

上述代码radius未float类型,而在经过计算之后得到的circumference精度得到了扩展(double),而有时我们容易依赖原有类型将circumference声明为float类型,这就使得计算结果精度降低了。

而使用auto则会自动推导为double类型。

但是,对于计算溢出的场景,自动推导并不能扩展其类型(因为只能对类型结果推导,而不能对计算结果推导)

#include 
using namespace std;
int main() {
    unsigned int a = 4294967295;     //最大的unsigned int值
    unsigned int b = 1;
    auto c = a + b;           // c的类型依然是unsigned int
    cout << "a = " << a << endl;     // a = 4294967295
    cout << "b = " << b << endl;     // b = 1
    cout << "a + b = " << c << endl;// a + b = 0
    return 0;
}

上述仍然会将c推导为unsigned int类型,而非更大存储范围的unsigned long。

自适应性在模板上的支持
template
double Sum(T1 & t1, T2 & t2) {
    auto s = t1 + t2;    // s的类型会在模板实例化时被推导出来
    return s;
}
int main() {
    int a = 3;
    long b = 5;
    float c = 1.0f, d = 2.3f;
    auto e = Sum(a, b);       // s的类型被推导为long
    auto f = Sum(c, d);     // s的类型被推导为float
}
// 编译选项:g++ -std=c++11 4-2-7.cpp

可以自动推导多个输入类型参数计算后的结果类型。当然这里来看仍然需要要求为double类型(因为返回值为double),但是实际上结合后面的decltype可以实现追踪返回类型,从而将返回类型也实现自动推导。

宏上的使用
#define Max1(a, b) ((a) > (b)) ? (a) : (b)
#define Max2(a, b) ({ \
auto _a = (a); \
auto _b = (b); \
(_a > _b) ? _a: _b; })
int main() {
    int m1 = Max1(1*2*3*4, 5+6+7+8);
    int m2 = Max2(1*2*3*4, 5+6+7+8);
}

我们定义了两种类型的宏Max1和Max2。两者作用相同,都是求a和b中较大者并返回。前者采用传统的三元运算符表达式,这可能会带来一定的性能问题。因为a或者b在三元运算符中都出现了两次,那么无论是取a还是取b,其中之一都会被运算两次。而在Max2中,我们将a和b都先算出来,再使用三元运算符进行比较,就不会存在这样的问题了

三、auto使用细则

不继承&
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();     // 编译失败, 指针不能指向一个临时变量
auto & f = foo();    // 编译失败, nonconst的左值引用不能和一个临时变量绑定
auto g = bar();      // int
auto & h = bar();    // int&

auto不继承引用,所以如果需要引用时需要自己添加,否则可能会引起拷贝,从而导致性能问题。

不继承cv限制符
double foo();
float * bar();
const auto a = foo();         // a: const double
const auto & b = foo();      // b: const double&
volatile auto * c = bar();   // c: volatile float*
auto d = a;                      // d: double
auto & e = a;                   // e: const double &
auto f = c;                      // f: float *
volatile auto & g = c;        // g: volatile float * &

同引用一样,auto不会继承const和volatile关键字,需要自己添加。

忽略*
#include 
#include 
using namespace std;
int i = 1;
int & j = i;
int * p = &i;
const int k = 1;
int main() {
    auto* v3 = p;                  // v3的类型是int*
    v3 = &i;
}

auto在进行类型推导时,会对auto后冗余的*进行忽略,如p通过自动推导为int *,而auto*则不会被认为是int **,而是int *。

有些场合不适用
#include 
using namespace std;
void fun(auto x =1){}  // 1: auto函数参数,无法通过编译
struct str{
auto var = 10;    // 2: auto非静态成员变量,无法通过编译
};
int main() {
    char x[3];
    auto y = x;
    auto z[3] = x; // 3: auto数组,无法通过编译
    // 4: auto模板参数(实例化时),无法通过编译
    vector v = {1};
}
// 编译选项:g++ -std=c++11 4-2-13.cpp

1)对于函数fun来说,auto不能是其形参类型。如果需要泛型的参数,还是需要求助于模板。

2)对于结构体来说,非静态成员变量的类型不能是auto的。同样的,由于var定义了初始值,读者可能认为auto可以推导str成员var的类型为int的。但编译器阻止auto对结构体中的非静态成员进行推导,即使成员拥有初始值。

3)声明auto数组。我们可以看到,main中的x是一个数组,y的类型是可以推导的,而声明auto z[3]这样的数组同样会被编译器禁止。

4)在实例化模板的时候使用auto作为模板参数,如main中我们声明的vector v。虽然读者可能认为这里一眼而知是int类型,但编译器却阻止了编译。

你可能感兴趣的:(深入理解C++11新特性,C++11)