深入解析C++ auto自动类型推导(一)

目录

推导规则


        关键字auto在C++98中的语义是定义一个自动生命周期的变量,但因为定义的变量默认就是自动变量,因此这个关键字几乎没有人使用。于是C++标准委员会在C++11标准中改变了auto关键字的语义,使它变成一个类型占位符,允许在定义变量时不必明确写出确切的类型,让编译器在编译期间根据初始值自动推导出它的类型。这篇文章我们来解析auto自动类型推导的推导规则,以及使用auto有哪些优点,还有罗列出自C++11重新定义了auto的含义以后,在之后发布的C++14、C++17、C++20标准对auto的更新、增强的功能,以及auto有哪些使用限制。

推导规则

        我们将以下面的形式来讨论:

​auto var = expr;

        这时auto代表了变量var的类型,除此形式之外还可以再加上一些类型修饰词,如:

const auto var = expr;
// 或者
const auto& var = expr;

        这时变量var的类型是const auto或者const auto&,const也可以换成volatile修饰词,这两个称为CV修饰词,引用&也可以换成指针*,如const auto*,这时明确指出定义的是指针类型。

根据上面定义的形式,根据“=”左边auto的修饰情况分为三种情形:

  • 规则一:只有auto的情况,既非引用也非指针,表示按值初始化

        如下的定义:

auto i = 1;		// i为int
auto d = 1.0;	// d为double

        变量i将被推导为int类型,变量d将被推导为double类型,这时是根据“=”右边的表达式的值来推导出auto的类型,并将它们的值复制到左边的变量i和d中,因为是将右边expr表达式的值复制到左边变量中,所以右边表达式的CV(const和volatile)属性将会被忽略掉,如下的代码:

const int ci = 1;
auto i = ci;		// i为int

        尽管ci是有const修饰的常量,但是变量i的类型是int类型,而非const int,因为此时i拷贝了ci的值,i和ci是两个不相关的变量,分别有不同的存储空间,变量ci不可修改的属性不代表变量i也不可修改。

        当使用auto在同一条语句中定义多个变量时,变量的初始值的类型必须要统一,否则将无法推导出类型而导致编译错误:

auto i = 1, j = 2;		// i和j都为int
auto i = 1, j = 2.0;	// 编译错误,i为int,j为double
  • 规则二:形式如auto&或auto*,表示定义引用或者指针

        当定义变量时使用如auto&或auto*的类型修饰,表示定义的是一个引用类型或者指针类型,这时右边的expr的CV属性将不能被忽略,如下的定义:

int x = 1;
const int cx = x;
const int& rx = x;
auto& i = x;	// (1) i为int&
auto& ci = cx;	// (2) ci为const int&
auto* pi = ℞	// (3) pi为const int*

        (1)语句中auto被推导为int,因此i的类型为int&。

        (2)语句中auto被推导为const int,ci的类型为const int &,因为ci是对cx的引用,而cx是一个const修饰的常量,因此对它的引用也必须是常量引用。

        (3)语句中的auto被推导为const int,pi的类型为const int*,rx的const属性将得到保留。

        除了下面即将要讲到的第三种情况外,auto都不会推导出结果是引用的类型,如果要定义为引用类型,就要像上面那样明确地写出来,但是auto可以推导出来是指针类型,也就是说就算没有明确写出auto*,如果expr的类型是指针类型的话,auto则会被推导为指针类型,这时expr的const属性也会得到保留,如下的例子:

int i = 1;
auto pi = &i;	// pi为int*
const char word[] = "Hello world!";
auto str = word;	// str为const char*

        pi被推导出来的类型为int*,而str被推导出来的类型为const char*。

  • 规则三:形式如auto&&,表示万能引用

        当以auto&&的形式出现时,它表示的是万能引用而非右值引用,这时将视expr的类型分为两种情况,如果expr是个左值,那么它推导出来的结果是一个左值引用,这也是auto被推导为引用类型的唯一情形。而如果expr是个右值,那么将依据上面的第一种情形的规则。如下的例子:

int x = 1;
const int cx = x;
auto&& ref1 = x;	// (1) ref1为int&
auto&& ref2 = cx;	// (2) ref2为const int&
auto&& ref3 = 2;	// (3) ref3为int&&

        (1)语句中x的类型是int且是左值,所以ref1的类型被推导为int&。

        (2)语句中的cx类型是const int且是左值,因此ref2的类型被推导为const int&。

        (3)语句中右侧的2是一个右值且类型为int,所以ref3的类型被推导为int&&。

        上面根据“=”左侧的auto的形式归纳讨论了三种情形下的推导规则,接下来根据“=”右侧的expr的不同情况来讨论推导规则:

  • expr是一个引用

        如果expr是一个引用,那么它的引用属性将被忽略,因为我们使用的是它引用的对象,而非这个引用本身,然后再根据上面的三种推导规则来推导,如下的定义:

int x = 1;
int &rx = x;
const int &crx = x;
auto i = rx;	// (1) i为int
auto j = crx;	// (2) j为int
auto& ri = crx;	// (3) ri为const int&

        (1)语句中rx虽然是个引用,但是这里是使用它引用的对象的值,所以根据上面的第一条规则,这里i被推导为int类型。

        (2)语句中的crx是个常量引用,它和(1)语句的情况一样,这里只是复制它所引用的对象的值,它的const属性跟变量j没有关系,所以变量j的类型为int。

        (3)语句里的ri的类型修饰是auto&,所以应用上面的第二条规则,它是一个引用类型,而且crx的const属性将得到保留,因此ri的类型推导为const int&。

  • expr是初始化列表

        当expr是一个初始化列表时,分为两种情况而定:

auto var = {};	// (1)
// 或者
auto var{};		// (2)

        当使用第一种方式时,var将被推导为initializer_list<T>类型,这时无论花括号内是单个元素还是多个元素,都是推导为initializer_list<T>类型,而且如果是多个元素,每个元素的类型都必须要相同,否则将编译错误,如下例子:

auto x1 = {1, 2, 3, 4};		// x1为initializer_list<int>
auto x2 = {1, 2, 3, 4.0};	// 编译错误

        x1的类型为initializer_list<int>,这里将经过两次类型推导,第一次是将x1推导为initializer_list<T>类型,第二次利用花括号内的元素推导出元素的类型T为int类型。x2的定义将会引起编译错误,因为x2虽然推导为initializer_list<T>类型,但是在推导T的类型时,里面的元素的类型不统一,导致无法推导出T的类型,引起编译错误。

        当使用第二种方式时,var的类型被推导为花括号内元素的类型,花括号内必须为单元素,如下:

auto x1{1};		// x1为int
auto x2{1.0};	// x2为double

        x1的类型推导为int,x2的类型推导为double。这种形式下花括号内必须为单元素,如果有多个元素将会编译错误,如:

auto x3{1, 2};	// 编译错误

        这个将导致编译错误:error: initializer for variable 'x3' with type 'auto' contains multiple expressions。

  • expr是数组或者函数

        数组在某些情况会退化成一个指向数组首元素的指针,但其实数组类型和指针类型并不相同,如下的定义:

const char name[] = "My Name";
const char* str = name;

        数组name的类型是const char[8],而str的类型为const char*,在某些语义下它们可以互换,如在第一种规则下,expr是数组时,数组将退化为指针类型,如下:

const char name[] = "My Name";
auto str = name;	// str为const char*

        str被推导为const char*类型,尽管name的类型为const char[8]。

        但如果定义变量的形式是引用的话,根据上面的第二种规则,它将被推导为数组原本的类型:

const char name[] = "My Name";
auto& str = name;	// str为const char (&)[8]

        这时auto被推导为const char [8],str是一个指向数组的引用,类型为const char (&)[8]。

        当expr是函数时,它的规则和数组的情况类似,按值初始化时将退化为函数指针,如为引用时将为函数的引用,如下例子:

void func(int, double) {}
auto f1 = func;		// f1为void (*)(int, double)
auto& f2 = func;	// f2为void (&)(int, double)

        f1的类型推导出来为void (*)(int, double),f2的类型推导出来为void (&)(int, double)。

  • expr是条件表达式语句

        当expr是一个条件表达式语句时,条件表达式根据条件可能返回不同类型的值,这时编译器将会使用更大范围的类型来作为推导结果的类型,如:

auto i =  condition ? 1 : 2.0;	// i为double

        无论condition的结果是true还是false,i的类型都将被推导为double类型。

(未完待续。。。敬请点击左下角的关注以获得及时更新)


本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“AI与编程之窗”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI与编程之窗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值