【C++ 11】(一)自动类型推断 - auto关键字

自动类型推断(Automatic Type Inference)是指编程语言在编译时自动根据上下文推导变量的类型,而无需显式指定变量的类型。

1. auto 定义变量

在C++ 11中,自动类型推断是通过关键字auto关键字来实现的。auto类型并不代表一种实际的数据类型,它只是一个类型声明的“占位符”,auto类型变量是在编译时根据给定的初始值推导出变量的类型。

C++中,变量分为局部变量、全局变量、类成员变量,下面我们使用auto关键字声明定义这几种类型变量的方面,来讨论自动类型推导的用法。

1.1 局部变量

大多数情况下,我们都是使用auto声明定义局部变量的,形式如下所示:      

auto x = 42;                // int x = 42;
auto name = "John";         // const char *name = "John"
auto pi = 3.14f;            // float pi = 3.14f;

除上例之外,auto类型还可以和指针、引用结合使用,也可以带上const 、volatile限定符。推导规则如下:

int var = 110;

auto *a = &var;             // int *a = &var;
auto  b = &var;             // int *b = &var;
auto &c =  var;             // int &c =  var;

const auto a1  = var;       // const int a1 =  var;
auto a2 = a1;               // int a2 =  a1;
                            // => 由于a2是非指针/引用类型、因此const属性被删除
const auto &a3 = var;       // const int &a3 = var;
                            // => 由于a3是引用类型、因此const属性被保留
auto &4 =  a3;              // const int &a4 =  a3;
                            // => 由于a4是引用类型、因此const属性被保留

结论:

  • 当变量是指针或引用类型时,auto推导结果会保留const、volatile属性
  • 当变量不是指针或引用类型时,auto推导结果中不会保留const、volatile属性

在这里,对字符串类型变量需要说明一下,因为使用字符串初始化的auto类型变量,通常会被推导成const char *类型。但是有时候我们还想让auto类型推导成std::string类型,那么只需要初始化的字符串的末尾追加s符号标注即可,如下:

void func() {
    auto c_str = "Hello, world";  // const char *
    auto str = "Hello, world"s; // std::string
    ...

使用的auto声明定义的局部变量,经常被使用在基于范围的 for 循环中遍历容器、数组等当中的元素,如下:

int nums[] = {1, 2, 3, 4, 5};

for (int num : nums) {
    std::cout << num << ' ';
}

关于此用法的详细说明,请参考【C++ 11】(二)基于范围的for循环

1.2 全局变量

C++中,全局变量分为两种:外部全局变量和静态局部变量。

1.2.1 外部全局变量

C++中,为了避免在不同的源文件中出现重复定义的问题,外部全局变量会被声明为extern,然后实现在另一个文件中进行定义实现。

在C++ 11中,外部全局变量是不能被定义成auto类型的。如下定义会在编译期间报错的。

extern auto g_variable;  // 编译时会报错
...
auto g_variable = true;

1.2.2 静态全局变量

我们再说说静态全局变量,它是指在一个文件中定义的全局变量,是由static定义的变量。它的生命周期会持续到程序的结束,并且只能在当前文件中访问。

在C++ 11中,静态全局变量是可以定义成auto类型的。在编译期间,它的类型会根据给定的初始值推导出真正的类型。如下示例:

static auto s_variable = 3.1415f; // 推导成 float 类型

int main(void) {
    std:cout << "variable is " << s_variable << std::endl;
    return 0;
}

1.3 类成员变量

C++中,类成员变量分为两种:静态成员变量和非静态成员变量。

1.3.1 静态成员变量

由于类的静态成员变量在声明定义时,不能给定一个初始值,无法推导出类型,所以它无法被定义成auto类型。如下示例:

class Sample {
public:
    static auto _value;  // 编译时报错
    //static auto _value = 3; // 编译时报错,不支持此写法
};
auto Sample::_value = 10;

1.3.2 非静态成员变量

对于非静态成员变量,由于能够给定初始值,我本认为是可以使用auto关键来进行声明定义的,但是事实上非静态成员变量被声明auto类型后,编译阶段就会报错。所以所以它无法被定义成auto类型。如下示例:

class Sample {
public:
    //int  _value = 10; // 编译成功
    auto _value = 10;  // 编译时报错
};

1.3.3 静态常量

在类中,虽然无论是静态成员变量或是非静态成员变量都不能被定义成auto类型,但是类的静态常量确是可以使用auto来声明定义的。需要注意,一般情况下,使用const关键字只能声明定义 int 类型的常量,若想定义声明其它类型的静态常量时,可使用C++ 11新引入的 constexpr 关键字来定义声明。如下所示:

class Sample {
public:
    static const auto _value = 1;  // 编译成功
    //static const auto _pi = 3.14;  // 编译报错
    static constexpr auto _pi = 3.14; // 编译成功
};

2. auto 定义函数

在C++ 11中,无论是函数的返回值还是函数参数都是不能被定义成auto类型的,但从C++ 14后就支持了这种特性。

2.1 函数返回值

从C++ 14后,对于任何函数及类中的成员函数的返回值都可定义成auto类型,但是对类中的虚函数(virtual定义)的返回值是不能够使用auto类型的。

在C++ 14中,关于函数auto类型返回值的推导,我们经常会遇到以下几种情况:

  • 返回数据的类型一致
#include <iostream>

template <typename T>
auto max(const T& a, const T& b) {
    return (a < b) ? b : a;
}

int main(void) {
    std::cout << max(4, 5) << std::endl;
    return 0;
}

编译成功,输出结果:

5
  • 返回数据的类型不一致
#include <iostream>

template <typename T, typename U>
auto max(const T& a, const U& b) {
    return (a < b) ? b : a;
}

int main(void) {
    //std::cout << max("7.2", 5) << std::endl; // 编译时会报错
    std::cout << max(7.2, 5) << std::endl;
    return 0;
}

编译成功,输出结果:

7.2

但是,我发现一个很奇怪的问题,如果将上例函数中的三目运算符修改成使用 if 语句检查判断,用多个return语句返回不同类型的数据时,就会发生编译错误。如下:

#include <iostream>

template <typename T, typename U>
auto max(const T& a, const U& b) {
    if (a < b)
        return b;
    else
        return a; // 编译时会报错
}

int main(void) {
    std::cout << max(7.2, 5) << std::endl;
    return 0;
}

如何解决这种问题呢?可以直接使用三目运算符解决,也可以使用“后置返回值类型”声明返回值的类型。如下:

#include <iostream>

template <typename T, typename U>
auto max(const T& a, const U& b) -> decltype(a + b) { // 后置返回值类型声明
    if (a < b)
        return b;
    else
        return a;
}

int main(void) {
    std::cout << max(7.2, 5) << std::endl;
    return 0;
}

编译成功,输出结果:

7.2

2.2 函数参数

在C++14中引入的“泛型Lambda表达式”,它可以将参数声明定义成auto类型,实现函数参数类型的自动推导。如下示例:

auto func = [](auto arg1, auto arg2) {
    // Lambda函数体
};

3. auto 使用说明

3.1 用途

3.1.1 简化变量类型的声明

使用auto可以省去手动指定变量类型的麻烦。编译器会根据初始化表达式的类型来推断出变量的类型,使代码更简洁。

auto num = 10;
auto str = "Hello";
auto res = calculate_result();

特别适用于处理复杂或模板化的类型,因为它消除了在代码中显式声明冗长或冗余类型名称的需要。例如:

vector<vector<string>> vec;
for(vector<vector<string>>::iterator i = vec.begin();i != vec.end(); i++) {
    // .... code ... 
}

使用auto声明后:

vector<vector<string>> vec;
for(auto i = vec.begin();i != vec.end(); i++) {
    // .... code ... 
}

3.1.2 支持复杂类型的推断

auto可以推断复杂的类型,例如模板类型、迭代器类型和lambda表达式类型。

std::vector<int> nums = {1, 2, 3, 4, 5};
auto it = nums.begin();  // 推导为 std::vector<int>::iterator

auto add = [](int a, int b) { return a + b; };  // 推导为 lambda表达式类型
auto res = add(3, 5);  // res的类型将推导为lambda表达式的返回类型

3.1.3 提高代码的灵活性和重构能力

使用auto可以避免重复修改变量声明的类型,尤其是在复杂的类型改变时。这样可以提高代码的重构能力和可维护性。

auto data = fetch_data_from_network(); // 假设返回类型为 std::vector<int>
// ... 执行一些操作
auto newData = modify_data(data);  // 修改后的数据的类型不必手动更改

尤其在使用auto定义泛型的时候,大大提高了代码灵活性和使用上的复杂性,例如不使用auto的情况下,定义一个模板:

class A {
public:
    static int get() { return 0x10; }
};

class B {
public:
    static double get() { return 3.14f; }
};

template <class X, typename Y>
void func(void) {
    Y v = X::get();
    cout << "value: " << v << "\n";
};

int main() {
    func(A, int);
    func(B, double);
    return 0;
}

在使用auto的情况下,代码可以修改成以下样式: 

class A {
public:
    static int get() { return 0x10; }
};

class B {
public:
    static double get() { return 3.14f; }
};

template <class X>        // 此处不用另外追加模板参数
void func(void) {
    auto v = X::get();
    cout << "value: " << v << "\n";
};

int main() {
    func(A);
    func(B);
    return 0;
}

3.2 制限

在使用C++ 11 auto关键字声明定义的时修,也是有制限的。

  • 不能用于作为函数参数的声明
int func(auto a, auto b) {	        // error
    // .... code ...
}
  • 不能用于类中静态常量之外的变量的声明
class A {
    auto v1 = 0;                    // error
    static auto v2 = 0;             // error
    static const auto v3 = 10;      // ok
}
  • 不能用于定义数组
int func() {
    int array[] = {1,2,3,4,5};      // 定义数组
    auto t1 = array;                // ok, t1被推导为 int* 类型
    auto t2[] = array;              // error, auto无法定义数组
    auto t3[] = {1,2,3,4,5};;       // error, auto无法定义数组
}
  • 不能用于推导出模板参数
template <typename T>
struct A{}

int func() {
    A<double> t;
    A<auto> t1 = t;                 // error, 无法推导出模板类型
    return 0;
}

总而言之,auto是C++ 11引入的功能之一,它可以帮助简化代码、减少冗余,并提高编写和维护代码的效率。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值