颜林林的个人网站

现代C++学习笔记(4):auto的前世今生

2022-01-25 00:01

Image (题图来自网络并做适当修改)

1

auto,是C++语言继承自C语言的第一个关键字。嗯,按字母顺序排名的话,它确实是第一个。

之所以这样说,是因为我的确拥有过一本关于C语言的大部头书籍,该书从头到尾完全就是按照字母顺序来组织编排的。标准C语言的所有关键字、函数、运算符、宏、头文件等内容全混到一起,只按字母进行排列,挨个列出并作介绍。这根本就不是教材,而是一本字典。在那个互联网还远没有普及的年代,手上缺乏其他教材,于是我竟然把这本书从头到尾一页不落地啃了下来,这奠定了我后来的C/C++语言基础。

我也曾听说有不止一位朋友,趁着假期按帮助文档中的列表,学习R语言base、stats、utils等包的所有函数,后来成了大牛。因此,对于计算机语言这件事,鼓励大家不妨趁着年轻,多下一些笨功夫,能受益终身。

说回到 auto,在C语言中,最初它用于变量定义,放在变量类型的前面,说明变量的存储模式。与它同类的还有另外三个,分别是:

auto int a; /* 自动变量 / register int r; / 寄存器变量 / static int s; / 静态变量 / extern int e; / 外部变量 */ 2

接下来,分别解释下这四种变量存储模式:

自动变量(auto):在C语言中,这样的变量定义,通常都要求写在函数体的开始,它编译成机器指令时,会成为堆栈控制的相关代码,即从堆栈中预留出空间,当做变量使用,一旦函数退出,由于堆栈的还原,会自动被释放掉。所以这种变量被称为“自动”变量。然而,因为绝大多数变量其实都是以这种方式存在的,所以为了简化,该“auto”关键字可以省略,就写成“int a;”,效果完全一样。于是,该关键字,成了可有可无的存在。

寄存器变量(register):这是与CPU指令集相关的,早期计算机及编程非常注重代码的运行效率,恨不得从每个牙缝里抠出哪怕一点点运行时间和存储空间。CPU内置有几个存放变量的单元,称为寄存器,其存取速度比内存快得多,内存中的数据,要进行计算,通常也会先拷贝到寄存器中,然后运算指令才能正确工作。为了优化,早期的C语言编译器,就会允许程序员自己指定某个变量强制使用寄存器,而不要放入内存,以此来手工提高程序运行效率。然而,随着编译器的升级,这种人工干预的方式已经显得非常没有必要,甚至有时候反而还会拖累效率。最终,这个关键字被淘汰在历史中。

静态变量(static):这种变量,在编译过程中,通常是会放入单独的一个数据段。程序一旦从磁盘读入内存,就会存在于内存中,而不像自动变量那样,是在函数调用的过程中,临时分配而得。因此,它的生存期覆盖了整个程序的运行过程,几乎永远不用担心变量会失效。而相应地,自动变量的生存期,只存在于其所在函数的调用阶段,函数运行结束,该变量就失效,这也是为什么不能把自动变量的地址作为函数的返回值传出,真要试图传出,只能通过拷贝变量的方式进行。注意,在这里,与 static 对应的关键字并不是 dynamic,而是 auto,大概是因为auto更好写吧,哈哈。

外部变量(extern):这个关键字的存在,是为了让不同的C/C++源文件(.c 或 .cpp)能够使用同一个变量。如果没有这个关键字,在不同的源文件中分别写了相同的变量名,则编译过程中,会给每个模块(源文件的编译结果)都分配一块该变量的空间。最后在把这些模块链接起来,生成最终可执行文件时,会因为变量名冲突而报错。extern会告诉编译器,这个变量已经在另一个模块中分配空间,当前这个模块中,就别分配了,以此来确保最终的链接正常。

在理解完上面一大段关于上古C语言的基础知识后,我们可以认识到,auto和register都已经被淘汰了。流传下来,至今仍在C和C++语言中可使用的,也就只剩下后面两种,static 和 extern。而在 C++的新标准中,既然auto已经早已毫无存在感,那不妨赋予它新的含义和用法。

3

下面,列举几个新标准中的auto的新用法,体会一下auto重生后的魅力:

一、迭代器类型的定义:

不用auto: #include #include #include void foo() { std::map<int, std::string> m; m[1] = “abc”; m[5] = “xyz”; for (std::map<int, std::string>::const_iterator it = m.begin(); it != m.end(); ++it) { std::cout « it->first « " => " « it->second « std::endl; } } 使用auto: #include #include #include void foo() { std::map<int, std::string> m; m[1] = “abc”; m[5] = “xyz”; for (auto it = m.begin(); it != m.end(); ++it) { std::cout « it->first « " => " « it->second « std::endl; } } 可以看到,既然 m.begin() 返回的必然是迭代器类型,那为什么还非要每次都写“std::map<int, std::string>::const_iterator”那么长的定义呢?于是 auto 的出现,避免了大量冗长代码的书写负担。

二、新的for循环:

不用auto: #include #include void foo() { std::string s = “hello”; for (int i = 0; i < s.size(); ++i) { std::cout « s[i]; } std::cout « std::endl; } 使用auto: #include #include void foo() { std::string s = “hello”; for (auto c : s) { std::cout « c; } std::cout « std::endl; } 三、函数返回值类型后置写法:

不用auto: int foo(int x, int y) { return (x * y); } 使用auto: auto foo(int x, int y) -> int { return (x * y); }

--- END ---

注:本文首发表于“不靠谱颜论”公众号,并同步至本站。

相关文章