第2部分 与C++第一次亲密接触
在浏览了C++“三分天下”的世界版图之后,便对C++有了基本的了解,算是一只脚跨入了C++世界的大门。那么,怎样将我们的另外一只脚也跨入C++世界的大门呢?是该即刻开始编写C++程序?还是……
正在我们犹豫的时候,便看到前面有一个人被一群满头问号的C++初学者围在当中。我们赶紧挤进去一看,噢,原来是一个C++程序正做自我介绍呢。
2.1 一个C++程序的自白
“大家好,欢迎来到奇妙的C++世界。我是C++世界的迎宾——一个最简单最普通的C++程序,我的名字叫“HelloWorld.exe”。我虽然简单而普通,但却几乎是这个世界上最著名的C++程序。每个来到C++世界的初学者与C++的第一次亲密接触都是通过我来完成的。大家在听我介绍之前,一定会觉得C++程序非常神秘,比如,C++程序是如何创建的?一个完整的C++程序由哪几部分构成?传说中的源文件到底是什么文件?C++程序是如何执行的?其实,我们C++程序一点都不神秘,跟大家一样,我们有自己的老爸老妈,有自己的五官四肢,也有自己的生命过程。什么?大家觉得不可思议?别着急,下面且听我一一道来……”
2.1.1 用Visual Studio创建C++程序
大家进入C++世界最感兴趣的第一件事,就是亲自动手创建一个C++程序。大多数C++程序都是通过一种叫做集成开发环境(Integrated Development Environment,IDE)的软件创建的,可以说,它是创建C++程序的工厂。虽然可以用于创建C++程序的集成开发环境有很多,但是我们首选的还是由微软公司开发的Visual Studio。作为一款经过二十多年不断发展而来的集成开发环境,Visual Studio拥有多种功能各异的版本:有适用于大规模团队开发的旗舰版,也有适用于个人开发的专业版,还有免费的快速版。如果我们只是想利用Visual Studio进行C++的学习,我们可以使用其中的Visual C++ Express版本。它不仅功能齐备,而且小巧。更重要的是,它是免费的,我们可以从微软的网站免费下载安装。Visual Studio是Windows平台上最常用的开发环境,至于其它的开发工具以及Linux平台上的开发工具,请大家参考后文的2.3小节。
现在,请大家在我的引导下,一步一步地使用Visual Studio创建我们的第一个C++程序。第0步,新建项目。从开始菜单找到已经安装好的Visual Studio并点击启动。当第一次启动Visual Studio时,它会要求我们选择界面布局设置,这里我们选择适用于Visual C++的布局设置,然后经过片刻的等待就可以看到Visual Studio华丽丽的起始页了。在Visual Studio中,我们所有的开发工作都是在某个项目中进行的,所以我们利用Visual Studio编写程序的第一步就是创建用于管理程序文件的项目:单击起始页左侧的“新建项目”,在弹出的“新建项目”对话框中,选中左侧树状图中的“Visual C++”节点,然后在右侧的项目类型列表中选中“空项目”这个项目模板,接着在对话框下方输入项目名称“HelloWorld”并选择项目存放的位置,最后点击“确定”按钮,就完成了新项目的创建。
图2-1 新建项目
第1步,添加源文件。新项目的创建,只是搭起了一个空的框架,还等着我们向其中添加一些实质的内容。在左侧的“解决方案资源管理器”中,找到“HelloWorld”项目下的“源文件”分支,然后右键单击“源文件”分支,在弹出的菜单中依次选择“添加->新建项”,就可以得到“添加新项”对话框。在“添加新项”对话框中,选中左侧树状图中的“Visual C++”分支,然后在右侧文件类型列表中选中“C++文件”,接着在对话框下方将文件名称修改为“HelloWorld.cpp”,最后点击“确定”按钮,Visual Studio就会为我们新建一个HelloWorld.cpp源文件并添加到项目中。
图2-2 添加源文件
第2步,编辑代码。有了空白的源文件,就相当于画家支好了画板,作家摆好了稿纸,就等着我们开始编写代码了。在已经被打开的HelloWorld.cpp文件中,我们编辑如下的代码(这里值的特别提醒的是,其中的标点都应该是英文的):
#include <iostream> using namespace std; int main()
{
// 在屏幕输出“Hello World!”字符串
cout<<"Hello World!"<<endl; return ;
}
图2-3 编辑代码
第3步,编译执行程序。代码编辑完成之后,我们就可以编译并执行这个程序向C++世界打个招呼了。这一步可以通过菜单命令“调试->开始执行(不调试)”来完成,但更多时候,我们通过Ctrl+F5快捷键来完成。当我们按下快捷键之后,Visual Studio会编译项目当中的源文件,如果源文件中没有错误,它就会生成相应的HelloWorld.exe可执行文件,随后会启动执行这个程序,从而在DOS窗口打印出一个“Hello World!”字符串向C++世界打招呼。
图2-4 Hello World!
最佳实践:等一下,等一下,我还没看清输出结果呢!
当我们在Visual Studio中执行某个程序时,如果这个程序在执行过程中不需要与用户进行交互,那么它执行时打开的DOS窗口会在其执行完毕后立即关闭。如果这个程序有结果输出,我们甚至来不及看清程序的输出结果。一个程序执行完了,连输出结果都来不及看清这怎么行呢?
为了解决这个问题,我们可以在程序的主函数返回之前加上一条“system("pause");”语句。例如:
int main()
{ // … // 让程序在结束之前暂停
system("pause"); return ;
}
加上这条语句后,程序会在执行完毕之前暂停,这样,我们就有足够的时间看清程序的输出结果了。自然,这条语句也还可以用在程序执行过程中那些需要暂停的地方,以此来提高程序的可交互性。
另外一种查看程序输出结果的方法,就是先启动DOS窗口,然后DOS窗口中手动地执行我们的应用程序。在这种方式下,DOS窗口并不会在程序执行完毕后关闭,所以我们也有时间查看程序的输出结果。
经过这样四个简单的步骤,我们就轻松编写了我们的第一个C++程序,完成了与C++的第一次亲密接触。除了借助Visual Studio创建C++程序之外,我们甚至还可以采取纯粹手工的方式创建C++程序。但无论何种方式,它们的基本流程都是一样的。如果使用Visual Studio方式,因为有开发工具的帮助,上手容易开发效率也会比较高。而如果是使用手工方方式,则可以对整个开发过程进行灵活定制,从而满足我们一些个性化的需求。对于初学者而言,手工方式稍显复杂。最佳的学习路线应该是,先以Visual Studio方式入门,等到有了一定的基础,需要对程序的编写过程有更精细控制的时候,再改用手工方式。这样才不至于在入门阶段就被复杂的手工方式困住了脚步,而在进阶后又受到开发工具的限制。
2.1.2 C++程序=预编译指令+程序代码+注释
麻雀虽小,五脏俱全。大家别看我个头小,只有短短的几行代码,实现的功能也很简单,但是我同样拥有C++程序的“五官和四肢”:预编译指令、程序代码和注释,如图2-5所示。大多数情况下,这三个基本组成部分都被放在一个扩展名为“cpp”的文本文件中,这个文件被称为C++ 源文件。源文件记录了我的“五官和四肢”,规划了我的人生。源文件的编写者就是我的设计师了。通过修改源文件,可以改变我的面貌、我的人生轨迹,让我完成各种任务,实现各种功能。
图2-5 C++程序=预编译指令+程序代码+注释
下面,大家一起来看看我的源文件,从中认识我的“五官和四肢”。
1. 预编译指令
在源文件中,以“#”开始的内容就是预编译指令。它的作用是告诉编译器,让它在真正进行编译之前对源文件进行一些插入文件、替换字符串等预处理,以得到最终参与编译的源文件。例如,在我的源文件HelloWorld.cpp中,第一行就是一个插入文件的预编译指令:
#include <iostream>
其中,“#include”指令用于将指定的文件插入该指令所在的位置,作为整个源文件的一部分。因为这样的文件总是在一个源文件的头部被插入,所以我们通常将这样的文件称为头文件(header file)。在这里,我们插入了“iostream”这个头文件,这是因为我们在程序中需要用到其中定义的cout和endl来完成输出(关于C++的输入输出,可以参考后文2.2小节的介绍)。需要注意的是,“#include”指令后的文件名有两种表示方式:如果使用双引号""来包围一个文件名,则预处理器在处理这个指令的时候,将首先在当前目录(也就是这个源文件所在的目录)下搜索这个文件,如果不存在,则继续在项目的包含目录(包括项目的默认头文件目录,也就是Visual Studio安装目录下的“\VC\include”文件夹,以及在项目属性中设置的项目附加头文件目录)下搜索这个文件;而如果使用尖括号<>来包围一个文件名,预处理器则会直接在项目的包含目录下搜索这个文件。所以,通常我们使用""来插入当前项目目录下的头文件(比如我们自己创建的头文件),而使用<>来插入各种项目包含目录下的库头文件(比如这里的iostream)。这里值得再次提醒的是,代码中使用的所有标点符号(这里使用的尖括号,也包括后面代码中用到的双引号、逗号、分号等)必须是英文的。某些中英文符号非常相似,很容易被初学者搞混淆而引起编译错误,这一点尤其值得初学者注意。
2. 程序代码
程序代码主体由若干C++语句(通常以分号结束的一行代码就是一条语句)构成,可以说语句是构成程序的基本单位。在我的源文件中,第一条C++语句是:
using namespace std;
这条语句表示我所使用的名字空间是std。所谓名字空间,就是程序中各种标识符(比如这里的cout和endl,我们通过这些符号来访问程序中的各种元素,实际上也可以说是这些元素的名字,所以这些符号也被称为标识符)所在的范围,更具体的可以参考7.3.2小节关于名字空间的介绍。在C++中,任何标识符都被定义在某个名字空间中,而同一个标识符也可以在多个名字空间中同时定义。这就像张家村有一个人叫陈良乔,而李家村也有一个人叫陈良乔一样,大家在称呼“陈良乔”这个人的时候,为了表达清楚,我们必须在名字前加上一个前缀,称之为“张家村的陈良乔”或是“李家村的陈良乔”。C++中的名字空间就相当于这里的“张家村”、“李家村”。在这里,我们在后面代码中使用的cout、endl都是来自std这个村的,所以我们用这条语句告诉编译器,如果遇到没有加前缀的标识符(比如,这里的cout),可以到std村去找找看,如果能找到,那就是它了(结果,在std村里找到了std::cout)。C++的大多数内容都定义在std这个名字空间中,所以,很多时候我们都需要在代码中使用这条语句引入std名字空间。当然,如果不使用这条语句,我们也可以在标识符的前面直接加上名字空间的前缀,明确地表示这是来自于某个名字空间的标识符(在代码中,使用 std::cout代替 cout,用std::endl代替endl)。
接下来的一条语句是:
int main()
这条语句连同它后面大括号内的内容,共同构成了main()函数,也称为主函数。所谓函数,是C++程序中最基本的一个组织单元,它把若干条语句组织到一起共同实现某个功能。如果说一条语句相当于人体的一个细胞的话,那么函数就相当于由若干细胞构成的拥有一定功能的器官。而这里的主函数就是一个程序中最重要的“器官”。一个C++程序必须有一个主函数,且只能有一个主函数。当C++程序开始执行的时候,将首先进入主函数,然后逐条地执行其中的语句,直到其中的语句执行完毕退出主函数,程序执行也就宣告结束。可以说,主函数定义了一个C++程序的一生。
知道更多:为什么一个C++程序必须有且只能有一个main函数?
当我们双击执行程序后,执行程序的进程会首先创建主线程,主线程然后调用约定启动运行时库,由启动运行时库调用约定好的main函数,自此开始执行用户的代码。main函数是主线程的执行入口,所以一个C++程序必须有一个main函数。
同时,一个线程不可能拥有多个执行入口。而且在C++中,全局符号(变量、函数) 只能有一个定义。main函数作为一个全局函数,自然也就只能有一个。
接下来的就是主函数中的一条语句:
cout<<"Hello World!"<<endl;
cout是定义在头文件“iostream”中的一个输出流对象,它是C++标准库预定义的对象,通常用于将文字或数字输出到屏幕。前面使用“#include”预编译指令包含“iostream”头文件就是为了在代码中使用这个对象。关于输入/输出流,会在以后的章节中做更详细的介绍,这里只要知道这条语句可以将“Hello World!”这串文字输出到屏幕上即可。值得再次提醒的是,这里的双引号也必须是英文的。
我的最后一条语句是:
return ;
它表示程序成功执行完毕并返回(return)。通常,我们返回一个0值表示程序成功执行(如果在程序的执行过程中出现错误,也可以返回表示错误信息的其他数值。程序的执行者可以接受这个返回值以判断程序是否成功执行)。到这里,主函数中的语句执行完毕,而我的一生也到此终结。
3. 注释
注释是源代码的编写者为了帮助代码的阅读者(代码后期维护人员,也包括编写者自己)更好地理解代码,而在代码中写下的关于某一行或某一段代码的一些解释性文字。虽然源代码中的注释并不会参与最终的编译,不会对程序的功能产生影响,但它会提高代码的可读性,为后期的维护带来极大的便利。例如这里的:
// 在屏幕输出“Hello World!”字符串
就是一条注释,它解释了接下来的一条语句的作用,从而可以让我们对代码有更好的理解。
在形式上,C++中的注释可以分为单行注释和块注释两种。“//”是单行注释符,“//”之后直到换行的所有内容都属于注释。因为内容只有一行,所以它常常用来对代码作简短的解释。例如上面的注释就是一个典型的单行注释。
C++中的块注释我们用一对“/*”和“*/”表示,凡是出现在这对符号之间的所有内容都属于注释。因为它可以包含多行内容,当我们需要对代码做详细解释的时候,可以使用块注释。例如:
/*
这是一段注释
*/
在功能上,注释一般分为序言性注释和解释性注释。序言性注释多位于程序源文件的开始,用来说明程序的文件名、用途、编写时间、维护历史等。在上面的例子中,我们可以在源文件的第一行加上一个序言性注释来解释这个源文件的功能:
// HelloWorld.cpp:在屏幕输出“Hello World!”字符串
序言性注释被广泛用于大型的项目中。通常,每个项目都有自己定义的序言性注释格式,用来向代码的阅读者说明一些必要的信息。下面是从一个实际的项目中摘录的一段序言性注释,它说明了源文件的名字、作用、文件的修改历史等信息,帮助阅读者更好地理解代码。大家可以以此为模板,编写适合于自己的序言性注释。
///////////////////////////////////////////////////////////////////////
// AppDataView.cpp : implementation file
//
//CAppDataView
// This view is designed to display the App Data
//
// Version: 2.1
// Date: September 2001
// Author: Chen Liangqiao
// Email: chenlq@live.com
// Copyright (c) 2002. All Rights Reserved.
//
// History:
/*
27.09.2001 Chen Liangqiao
Added OnCreate(), OnUpdate():
Added usage of mesh tracer layers
Added bugfix for Graphics zoom error
30.10.2001 Chen Liangqiao
Changed order of MPR View only in _TORCHTONAV
08.11.2001 Zeng Me
Added EUpdateReason, used for UpdateAllView(),
Added voxel trafo
Changed the control panel due to new CTestCtrl
*/ ///////////////////////////////////////////////////////////////////////
与序言性注释多位于源文件开始部分不同,解释性注释多分散于源代码的各个部分,用来向代码阅读者解释代码的含义,说明一些必要的问题等。例如,上面例子中的注释:
//在屏幕上输出“Hello World!”字符串
cout<<"Hello World!"<<endl;
这句解释性注释就是用来向代码阅读者说明其下代码的功能是输出字符串“Hello World!”。
最佳实践:什么是好的注释
虽然程序的注释并不影响程序功能的实现,编译器也不会去阅读我们的注释,但是好的注释却可以增加程序代码的可读性,使程序更易于维护。谁都不愿意维护一份没有注释的代码,那无异于阅读天书。那么,什么样的注释才算是好注释呢?
首先,该注释的地方一定要注释。
注释是对代码的“提示和说明”,是为了帮助代码的阅读者更好地理解代码而存在的。当我们认为代码不能被“一眼看穿”而需要加以解释,或者是代码需要特别说明的时候,就应该添加注释,加以额外的解释和说明,帮助阅读者理解代码。例如:
// 判断某个浮点数是否近似整数
bool is_int(double d)
{
// 用浮点数d减去其整数部分(int)d,获得其小数部分
double s = d - (int)d; // 判断小数部分是否在误差范围内
if(s > 0.000001)
return false;
else
return true;
}
这里的注释,恰当地对比较难以理解的代码进行了解释(如果没有注释,很难一下子就理解“double s = d - (int)d;”这行代码的含义到底是什么),提高了代码的可读性。
其次,不该注释的地方最好不要注释。
注释仅仅是对代码的“提示和说明”而已,如果代码本身已经能够很好地做到“望文生义”,也就没有必要“画蛇添足”地加以注释了。另外需要注意的是,注释只是简短的说明性文字,不是详尽的文档。程序的注释不可喧宾夺主,注释过多会让人眼花缭乱,反而降低了代码的可读性。例如,下面代码中的注释就不太合适:
// 判断某个浮点数是否近似整数 // 其参数是表示输入的浮点数d // 其返回值是一个表示是否近似的bool值 bool is_int(double d)
{
// 用浮点数减去其整数部分,获得其小数部分
// 其中,d表示浮点数,(int)d表示浮点数的整数部分
double s = d - (int)d; // 判断小数部分是否在误差范围内
if(s > 0.000001)
return false; // 小数部分大于误差范围,则表示浮点数不近似整数,返回false
else
return true; // 小数部分小于误差范围,则表示浮点数近似整数,返回true
}
这段代码中的注释,对一些含义非常浅显易懂的代码也进行了详尽的解释,注释的内容远超过了代码的内容,这样不但没有增加代码的可读性,反倒是让代码淹没在了复杂的注释中,反而降低了代码的可读性。这样的注释实属“画蛇添足”多此一举。
另外,应该养成良好的代码注释习惯。编写代码时添加必要的注释,修改代码时修改相应的注释,删除无用的注释,保证注释与代码的一致性。
注释应当准确、易懂,避免二义性。错误的注释不但无益反而有害。
注释的位置应与被描述的代码相邻,可以放在代码的上方或右方,不可放在下方。例如:
// 在屏幕输出“Hello World!”字符串
// 对下方的代码进行注释
cout<<"Hello World!"<<endl; int n = ; // 循环次数,对左侧代码进行注释
如果代码比较长,特别是有多重嵌套时,应当在某些段落的结束处加以注释,以便于查看嵌套结构的起始和结束位置。例如,一个多重循环的代码及其注释如下:
for ( int i = ; i < ; ++i )
{
for ( int j = ; j < ; ++j )
{
// 算法处理...
} // j循环结束
} // i循环结束
程序代码不仅仅是写给编译器看的,它更是写给程序员自己或者他人看的。对于编译器来说,代码中有没有注释无所谓,然而对于阅读代码的程序员来说,合适的注释可以很大程度上提高代码的可读性,让代码更易于维护。因而,注释是C++程序代码中必不可少的一部分,而程序代码中是否包含合适的注释,也成为衡量一个程序员是否优秀的标准。
预编译指令、程序代码与注释共同构成了我的“五官与四肢”,但这时候我还只是一个后缀为cpp的文本文件,而要得到最后的可执行的exe文件,还得靠我的父亲母亲:编译器和链接器。