Hello world
Hello world 几乎是每个人学习一门语言时写的第一个程序。
C++ 的 Hello world 是这样的:
1 | #include <iostream> |
这段代码虽然只有5行(不算上最后一个大括号的话),但却隐藏着许多C++知识。接下来,让我们一行行的解析这段代码。
第一行
第一行的意思是包含<iostream>这个头文件。 它本身是一段预处理指令。
相关标准描述位于《ISO IEC 14882》(C++11标准文档 )的 16.2 Source file inclusion [cpp.include]。
<...>是指在系统头文件路径中查找。可用以下命令显示系统头文件路径。
1 | g++ -E -x c++ - -v < /dev/null |
第二行
第二行是引入std命名空间。
相关标准描述位于《ISO IEC 14882》(C++11标准文档 )的7.3.4 Using directive [namespace.udir]。
在实际工程中,直接using namespace std;应被禁止,因为std命名空间这会污染当前命名空间。
第三行
第三行定义了main函数,main函数是程序的入口。
相关标准描述位于《ISO IEC 14882》的3.6.1 Main function [basic.start.main]。
标准规定,main函数不可预定义,不可重载,允许的函数原型至少包括:
1 | int main(); |
第四行
这一行是包含知识点最多的一行。
C++ I/O模型
cout 在<iostream>中只有声明:1
extern ostream cout;
其类型为ostream,ostream是一个alias: typedef basic_ostream<char> ostream。
basic_ostream的继承结构如下:
运算符重载
cout <<是C++的运算符重载语法。
第四行用到了<ostream>中定义的两个<<运算符重载函数:
1 | template<class traits> |
这两个函数位于std命名空间下。
这一行等价于:
1 | operator<<(operator<<(cout, "Hello world"), endl); |
ADL(Argument-dependent name lookup)
如果我们去掉第二行using namespace std;,那么这一行就得写成:
1 | std::cout << "Hello world" << std::endl; |
等价于:
1 | operator<<(operator<<(std::cout, "Hello world"), std::endl); |
那么问题来了,我们知道这里用到的两个运算符重载函数位于std命名空间,我们没有引入std 命名空间,编译器是如何找到它们的呢?
答案是ADL (Argument-dependent name lookup)。
相关标准描述位于《ISO IEC 14882》的 3.4.2 Argument-dependent name lookup [basic.lookup.argdep]。
ADL, 又称Koenig查找。当一个函数调用中被调用函数名前面没有类、命名空间等作用域限制时,则称之为无限定域的函数调用。
当编译器对其进行无限定域的函数调用名字查找时,如果一般的名字查找失败,会在函数参数的命名空间中继续查找。
I/O manipulator
std::endl是一个 I/O manipulator。
相关标准描述位于《ISO IEC 14882》的 27.7.3.8 Standard basic_ostream manipulators [ostream.manip]
其函数原型为:
1 | template <class charT, class traits> |
cout << endl 会调用 basic_ostream<charT, traits> 的运算符重载成员函数:
1 | typedef basic_ostream<charT, traits> ostream_type; |
std::endl的定义是这样子的:
1 | template<typename _CharT, typename _Traits> |
经过两层函数调用,cout << endl 变成了 flush(cout.put(cout.widen('\n')))。
第五行
第五行的意思是将 0 这个值作为main函数的返回值返回。
相关标准描述位于《ISO IEC 14882》的3.6.1 Main function [basic.start.main]的第5条。
这条语句有两个作用:
- 结束
main函数。 - 使用返回值调用
std::exit。
根据标准,当这条语句位于main函数最末尾时可以省略。