资讯专栏INFORMATION COLUMN

一、C++11新特性:auto类型推导

gityuan / 1865人阅读

摘要:宋体关键字中的含义宋体不再是一个存储类型指示符如为纯粹类型指示符,而是一个新的类型指示符等是类型指示符来指示编译器,声明变量的类型必须由编译器在编译时期推导而得。


一、auto类型推导

声明:该笔记是在学习《深入理解C++11》、《C++11/14高级编程 Boost程序库探秘》时做的总结,方便以后巩固复习!

1.1、静态类型、动态类型和类型推导

静态类型:C/C++常被成为静态类型的语言,变量必须被定义;

动态类型:python、Perl、JavaScript语言常被称为动态类型的语言,变量不需要声明就可以被使用。

静态类型和动态类型的区别:是在对变量进行类型检测的时间点;静态类型的类型检测主要发生在编译阶段;动态类型的类型检测主要发生在运行阶段。

动态类型语言变量“拿来就用”的特性依赖的是类型推导技术;事实上类型推导也可以用于静态类型的语言;C++11中类型推导的实现方式就有两种:①、auto,②、decltype;先学习auto关键字!

auto关键字在早期的C/C++标准中的含义:

按照C/C++早期标准,声明时使用auto修饰的变量,是具有自动存储的局部变量;几乎无人使用这种含义,因为一般函数内没有声明为static的变量总是具有自动存储的局部变量。

auto关键字C++11中的含义:

auto不再是一个存储类型指示符(如static、extern为纯粹类型指示符),而是一个新的类型指示符(int、float等是类型指示符)来指示编译器,
auto声明变量的类型必须由编译器在编译时期推导而得。

int main(){	double foo();	auto x = 1; //x的类型为int	auto y = foo(); //y的类型为double	struct m 	{		int i;	}str; 	auto str1 = str;//str1的类型是sturct m	auto z;//无法推导,不能通过编译}

auto 声明的变量必须被初始化,以使编译能够从其初始化表达式中推导出其类型。这里可以理解为auto并非一种“类型”,而是一个类型声明时的“占位符”,编译器在编译时会将auto替代为变量实际的类型。

1.2、auto的优势

①、最大优势就是在拥有初始化表达式的复杂类型变量声明时简化代码

由于C++的发展,声明变量类型也变得越来越复杂,很多时候,名字空间、模板成为了类型的一部分,导致程序员在使用库的时候如履薄冰。

#include #incldue <vector>void loopover(std::vector<std:string> & vs){	std::vector<std::string>::iterator i = vs.begin(); //可看出在在不使用u命名空间时,使用iterator 需要书写大量代码	for(; i < vs.end(); i++)	{		...	}}

用auto的话,代码会的可读性可以成倍增长

#include #incldue <vector>void loopover(std::vector<std:string> & vs){	std::vector<std::string>::iterator i = vs.begin(); 	//使用auto,就不需要书写大量代码	for(auto  i = vs.begin(); i < vs.end(); i++)	{		...	}}

②、第二个优势则在于可以免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误

在C/C++中,存在着很多隐式或者用户自定义的类型转换规则(比如整型与字符型进行加法运算后,表达式返回的是整型,这是一条隐式规则)。这些规则并非很容易记忆,尤其是在用户自定义了很多操作符之后。而这个时候,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);    cout << "circumference = " << circumference << endl;    return 0;}
输出:circumference = 1.06814e+11

这里定义了float型的变量radius(半径)以及一个自定义类型PI变量pi(π值),在计算圆周长的时候,使用了auto类型来定义变量circumference。这里,PI在与float类型数据相乘时,其返回值为double。而PI的定义可能是在其他的地方(头文件里),main函数的程序员可能不知道PI的作者为了避免数据上溢或者精度降低而返回了double类型的浮点数。因此main函数程序员如果使用float类型声明circumference,就可能享受不了PI作者细心设计带来的好处。反之,将circumference声明为auto,则毫无问题,因为编译器已经自动地做了最好的选择。

③、第三个优点就是其“自适应”性能够在一定程度上支持泛型的编程

我们再回到上面代码例子,这里假设改动了PI的定义,如将operator*返回值变为long double,此时,main函数并不需要修改,因为auto会“自适应”新的类型。

同时,对于不同的平台上的代码维护,auto也会带来一些“泛型”的好处。这里我们以strlen函数为例,在32位的编译环境下,strlen返回的为一个4字节的整型,而在64位的编译环境下,strlen会返回一个8字节的整型。虽然系统库为其提供了size_t类型来支持多平台间的代码共享支持,但是使用auto关键字我们同样可以达到代码跨平台的效果。

auto v = strlen("hello world!")

由于size_t的适用范围往往局限于中定义的函数,auto的适用范围明显更为广泛。

当auto应用于模板的定义中,其“自适应”性会得到更加充分的体现。如:

#include using namespace std;template<typename T1, typename T2>double Sum(T1 & t1, T2 & t2){    auto s = t1 + t2;    return s;}int main(){    int a = 3;    long b = 5;    float c = 1.0f, d = 2.3f;    auto e = Sum<int,long>(a,b);//s的类型被推导为long    auto f = Sum<float,float>(c,d);//s的类型被推导为float	cout << e << endl;	cout << f << endl;	return 0;}
输出:83.3

在上面程序中,由于类型T1、T2要在模板实例化时才能确定,所以在Sum中将变量s的类型声明为auto的。在函数main中我们将模板实例化时,Sum中的s变量会被推导为long类型,而Sum中的s变量则会被推导为float。可以看到,auto与模板一起使用时,其“自适应”特性能够加强C++中“泛型”的能力。不过在这个例子中,由于总是返回double类型的数据,所以Sum模板函数的适用范围还是受到了一定的限制。

④、在宏定义中,避免出现性能问题

#include using namespace std;#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); 	cout << m1 << endl; 	cout << m2 << endl;	return 0;  }

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

在传统的C++98标准中,由于a和b的类型无法获得,所以我们无法定义MAX2这样高性能的宏。而新的标准中的auto则提供了这种可行性。

1.3、auto使用时注意事项

①、auto类型指示符与指针和引用之间的关系

int x = 1;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();// intauto & h = bar();// int&

变量a、c、d的类型都是指针类型,且都指向变量x。实际上对于a、c、d三个变量而言,声明其为auto *或auto并没有区别。

而如果要使得auto声明的变量是另一个变量的引用,则必须使用auto &,如同本例中的变量b和h一样。

②、auto与volatile和const之间也存在着一些相互的联系

volatile和const代表了变量的两种不同的属性:易变的和常量的。

在C++标准中,它们常常被一起叫作cv限制符(cv-qualifier)。鉴于cv限制符的特殊性,C++11标准规定auto可以与cv限制符一起使用,不过声明为auto的变量并不能从其初始化表达式中“带走”cv限制符

double foo();float * bar();const auto a = foo(); //a:const doubleconst auto & b = foo(); //b:const double&volatile auto * c = bar(); //c:volatile float*auto d = a; //d:doubleauto & e = e; //e:const double &auto f = c; //f:float *volatile auto & g = c; //g:volatile float * &

可以看出通过非cv限制的类型初始化一个cv限制的类型,如变量a、b、c所示。不过通过auto声明的变量d、f却无法带走a和f的常量性或者易失性。这里的例外还是引用,可以看出,声明为引用的变量e、g都保持了其引用的对象相同的属性(事实上,指针也是一样的)。

③、auto可以用来声明多个变量的类型,不过这些变量的类型必须相同

如果这些变量的类型不相同,编译器则会报错。事实上,用auto来声明多个变量类型时,只有第一个变量用于auto的类型推导,然后推导出来的数据类型被作用于其他的变量。

auto x = 1, y = 2;//m是一个指向const int类型变量的指针,n是一个int类型的变量const auto* m = &x, n = 1;//auto i = 1, j = 3.14f; //编译失败auto o = 1,&p = o,*q = &p; //从左向右推导

使用auto声明了两个类型相同变量x和y,并用逗号进行分隔,这可以通过编译。而在声明变量i和j的时候,按照我们所说的第一变量用于推导类型的规则,那么由于x所推导出的类型是int,那么对于变量j而言,其声明就变成了int j =3.14f,这无疑会导致精度的损失。而对于变量m和n,就变得非常有趣,这里似乎是auto被替换成了int,所以m是一个int *指针类型,而n只是一个int类型。同样的情况也发生在变量o、p、q上,这里o是一个类型为int的变量,p是o的引用,而q是p的指针。auto的类型推导按照从左往右,且类似于字面替换的方式进行。事实上,标准里称auto是一个将要推导出的类型的“占位符”(placeholder)。这样的规则无疑是直观而让人略感意外的。当然,为了不必要的繁琐记忆,程序员可以选择每一个auto变量的声明写成一行(有些观点也认为这是好的编程规范)。

④、只要能够进行推导的地方,C++11都为auto指定了详细的规则,保证编译器能够正确地推导出变量的类型

包括C++11新引入的初始化列表,以及new,都可以使用auto关键字

 #include  auto x = 1; auto x1(1); auto y {1};      // 使用初始化列表的auto      	auto z = new auto(1);    // 可以用于new       

auto变量y的初始化使用了初始化列表,编译器可以保证y的类型推导为int。而z指针所指向的堆变量在分配时依然选择让编译器对类型进行推导,同样的,编译器也能够保证这种方式下类型推导的正确性。

⑤、不过auto也不是万能的,受制于语法的二义性,或者是实现的困难性,auto往往也会有使用上的限制

  #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<auto> v = {1};  }  

①、对于函数fun来说,auto不能是其形参类型。可能读者感觉对于fun来说,由于其有默认参数,所以应该推导fun形参x的类型为int型。但事实却无法符合大家的想象。因为auto是不能做形参的类型的。如果程序员需要泛型的参数,还是需要求助于模板。

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

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

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

欢迎关注公众号:Kevin的嵌入式学习站

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123196.html

相关文章

  • 四、C++11特性:追踪返回类型

    摘要:四追踪返回类型宋体声明该笔记是在学习深入理解高级编程程序库探秘时做的总结,方便以后巩固复习引入追踪返回类型的目的宋体追踪返回类型配合与会真正释放中泛型编程的能力简化函数的定义,提高代码的可读性。 ...

    tianyu 评论0 收藏0
  • C++入门篇(2)之引用,内联函数,auto和范围遍历

    文章目录 前言extern C引用1.概念2.语法3.引用特性4.常量引用5.引用做函数返回值6.引用注意点7.传值、传引用效率比较 内联函数1.概念2.特性 auto关键字1.概念2.auto的使用细则3.auto不能推导的场景 基于范围的for循环(C++11)使用条件 指针空值nullptr 前言 承接上文入门篇1,博主这次将会继续更新以下内容:extern ,引用 ,内联, a...

    wangtdgoodluck 评论0 收藏0
  • C++】C++快速入门

    摘要:中包含的即为命名空间的成员。使用输入输出更方便,不需增加数据格式控制,比如整形,字符可以连续输出,表示换行缺省参数备胎,就是给汽车准备一个备用轮胎,一旦那个轮子爆胎或者出了问题,备用轮胎就方便及时地取而代之,汽车就不至于中途抛锚。 ...

    TalkingData 评论0 收藏0
  • [初识C++] 何为最:心酸历史

    摘要:上面需要了解的是这俩个版本都是破蛹成蝶的版本世界挑战榜咋才前三还没挤进去呀,你想想世界上有几千中编程语言,在其中脱颖出来,可以说是天之娇子,凤毛麟角了。支持正版图灵上面买吧,如果没钱买盗版吧学完以后买本正版支持一下,创作不易是吧 ...

    forrest23 评论0 收藏0
  • Python学习之路21-序列构成的数组

    摘要:第行把具名元组以的形式返回。对序列使用和通常号两侧的序列由相同类型的数据所构成当然不同类型的也可以相加,返回一个新序列。从上面的结果可以看出,它虽抛出了异常,但仍完成了操作查看字节码并不难,而且它对我们了解代码背后的运行机制很有帮助。 《流畅的Python》笔记。接下来的三篇都是关于Python的数据结构,本篇主要是Python中的各序列类型 1. 内置序列类型概览 Python标准库...

    ralap 评论0 收藏0

发表评论

0条评论

gityuan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<