同样,学习计算机语言时,应从程序的基本结构开始学起。
只有这样,才能一步一步了解其具体细节,如循环和对象等。
本文对C++程序的基本结构做一概述,并预览后面将介绍的主题,如函数和类。
首先介绍一个显示消息的简单C++程序。
#include
using namespace std;
int main() {
cout << "I love C++ !";
cout << endl;
cout << "Do you like C++ ?" << endl;
return 0;
}
C++对大小写敏感,也就是说区分大写字符和小写字符。这意味着大小写必须与示例中相同。
例如,该程序使用的是cout
,如果将其替换为Cout或COUT,程序将无法通过编译,并且编译器将指出使用了未知的标识符(编译器也是对拼写敏感的,因此请不要使用kout或coot)。
文件扩展名cpp是一种表示C++程序的常用方式。
运行结果如下:
如果已经使用过C语言进行编程,则看到cout
函数(而不是printf( )函数)时可能会小吃一惊。
事实上,C++能够使用printf( )、scanf( )和其他所有标准C输入和输出函数,只需要包含常
规C语言的stdio.h文件。
不过本书介绍的是C++,所以将使用C++的输入工具,它们在C版本的基础上作了很多改进。
例如:
#include
#include
using namespace std;
int main() {
cout << "I love C++ !";
cout << endl;
cout << "Do you like C++ ?" << endl;
printf("I love C++,too");
return 0;
}
结果展示为:
上述程序只包含一个名为main( )的函数,各个元素的含义如下所示:
注释,由前缀//标识
预处理器编译指令#include
函数头:int main( )
编译指令using namespace
函数体,用{和}括起
使用C++的cout工具显示消息的语句
结束main( )函数的return语句
语句是要执行的操作。为理解源代码,编译器需要知道一条语句何时结束,另一条语句何时开始。
有些语言使用语句分隔符。例如,FORTRAN通过行尾将语句分隔开来,Pascal使用分号分隔语句。在Pascal中,有些情况下可以省略分号,例如END前的语句后面,这种情况下,实际上并没有将两条语句分开。
不过C++与C一样,也使用终止符(terminator),而不是分隔符。终止符是一个分号,它是语句的结束标记,是语句的组成部分,而不是语句之间的标记。
结论是:在C++中,不能省略分号。
如果程序要使用C++输入或输出工具,请提供这样两行代码:
#include
using namespace std;
可使用其他代码替换第2行,这里使用这行代码旨在简化该程序(如果编译器不接受这几行代码,则说明它没有遵守标准C++98,使用它来编译本书的示例时,将出现众多其他的问题)。为使程序正常工作,只需要知道这些。下面更深入地介绍一下这些内容。
C++和C一样,也使用一个预处理器,该程序在进行主编译之前对源文件进行处理。使用了#include编译指令:
#include
该编译指令导致预处理器将iostream文件的内容添加到程序中。这是一种典型的预处理器操作:在源代码被编译之前,替换或添加文本。
这提出了一个问题:为什么要将iostream文件的内容添加到程序中呢?答案涉及程序与外部世界之间的通信。iostream中的io指的是输入(进入程序的信息)和输出(从程序中发送出去的信息)。C++的输入/输出方案涉及iostream文件中的多个定义。为了使用cout来显示消息,第一个程序需要这些定义。#include编译指令导致iostream文件的内容随源代码文件的内容一起被发送给编译器。实际上,iostream文件的内容将取代程序中的代码行#include 。原始文件没有被修改,而是将源代码文件和iostream组合成一个复合文件,编译的下一阶段将使用该文件。
使用cin和cout进行输入和输出的程序必须包含文件iostream。
去掉修饰后,示例程序的基本结构如下:
int main() {
stataments;
return 0;
}
这几行表明有一个名为main( )的函数,并描述了该函数的行为。
这几行代码构成了函数定义(function definition)。
该定义由两部分组成:第一行int main( )叫函数头(function heading)
,花括号({和})中包括的部分叫函数体
。
函数头对函数与程序其他部分之间的接口进行了总结;函数体是指出函数应做什么的计算机指令。
在C++中,每条完整的指令都称为语句。所有的语句都以分号结束
,因此在输入示例代码时,请不要省略分号
。
main( )中最后一条语句叫做返回语句(return statement),它结束该函数。
1.作为接口的函数头
就目前而言,需要记住的主要一点是,C++句法要求main( )函数的定义以函数头int main( )开始。
通常,C++函数可被其他函数激活或调用,函数头描述了函数与调用它的函数之间的接口。
位于函数名前面的部分叫做函数返回类型,它描述的是从函数返回给调用它的函数的信息。
函数名后括号中的部分叫做形参列表(argument list)或参数列表(parameter list);它描述的是从调用函数传递给被调用的函数的信息。
这种通用格式用于main( )时让人感到有些迷惑,因为通常并不从程序的其他部分调用main( )。
然而,通常,main( )被启动代码调用,而启动代码是由编译器添加到程序中的,是程序和操作系统(UNIX、Windows 7或其他操作系统)之间的桥梁。事实上,该函数头描述的是main( )和操作系统之间的接口。
来看一下main( )的接口描述,该接口从int开始。C++函数可以给调用函数返回一个值,这个值叫做返回值(return value)。
在这里,从关键字int可知,main( )返回一个整数值。接下来,是空括号。通常,C++函数在调用另一个函数时,可以将信息传递给该函数。括号中的函数头部分描述的就是这种信息。在这里空括号意味着main( )函数不接受任何信息,或者main( )不接受任何参数。
(main( )不接受任何参数并不意味着main( )是不讲道理的、发号施令的函数。相反,术语参数(argument)只是计算机人员用来表示从一个函数传递给另一个函数的信息)。
简而言之,下面的函数头表明main( )函数可以给调用它的函数返回一个整数值,且不从调用它的函数那里获得任何信息:
int main()
很多现有的程序都使用经典C函数头:
main()
在C语言中,省略返回类型相当于说函数的类型为int。然而,C++逐步淘汰了这种用法。
也可以使用下面的变体:
int main(void)
在括号中使用关键字void明确地指出,函数不接受任何参数。
C++(不是C)中,让括号空着与在括号中使用void等效
(在C中,让括号空着意味着对是否接受参数保持沉默)。
有些程序员使用下面的函数头,并省略返回语句:
void main()
这在逻辑上是一致的,因为void返回类型意味着函数不返回任何值。
如果编译器到达main( )函数末尾时没有遇到返回语句,则认为main( )函数以如下语句结尾:
return 0;
这条隐含的返回语句只适用于main( )函数,而不适用于其他函数。
2.为什么main( )不能使用其他名称
之所以将程序中的函数命名为main( ),原因是必须这样做。
通常,C++程序必须包含一个名为main( )的函数(不是Main( )、MAIN( )或mane( )。记住,大小写和拼写都要正确)。
由于上面的程序只有一个函数,因此该函数必须担负起main( )的责任。在运行C++程序时,通常从main( )函数开始执行。因此,如果没有main( ),程序将不完整,编译器将指出未定义main( )函数。
存在一些例外情况。例如,在Windows编程中,可以编写一个动态链接库(DLL)模块,这是其他Windows程序可以使用的代码。由于DLL模块不是独立的程序,因此不需要main( )。用于专用环境的程序—如机器人中的控制器芯片—可能不需要main( )。有些编程环境提供一个框架程序,该程序调用一些非标准函数,如_tmain( )。在这种情况下,有一个隐藏的main( ),它调用_tmain( )。但常规的独立程序都需要main()
C++注释以双斜杠(//)打头
。注释是程序员为读者提供的说明,通常标识程序的一部分或解释代码的某个方面。编译器忽略注释,毕竟,它对C++的了解至少和程序员一样,在任何情况下,它都不能理解注释。
C++注释以//打头,到行尾结束。注释可以位于单独的一行上,也可以和代码位于同一行。
C++也能够识别C注释,C注释包括在符号/和/之间:
#include /* a C style comment */
由于C-风格注释以*/结束
,而不是到行尾结束,因此可以跨越多行。
可以在程序中使用C或C++风格的注释,也可以同时使用这两种注释。但应尽量使用C++注释,因为这不涉及到结尾符号与起始符号的正确配对,所以它产生问题的可能性很小。
事实上,C99标准也在C语言中添加了//注释。
像iostream这样的文件叫做包含文件(include file)—由于它们被包含在其他文件中;
也叫头文件(header file)—由于它们被包含在文件起始处。
C++编译器自带了很多头文件,每个头文件都支持一组特定的工具。
C语言的传统是,头文件使用扩展名h,将其作为一种通过名称标识文件类型的简单方式。例如,头文件math.h支持各种C语言数学函数,但C++的用法变了。
现在,对老式C的头文件保留了扩展名h(C++程序仍可以使用这种文件),而C++头文件则没有扩展名。有些C头文件被转换为C++头文件,这些文件被重新命名,去掉了扩展名h(使之成为C++风格的名称),并在文件名称前面加上前缀c(表明来自C语言)。例如,C++版本的math.h为cmath。
有时C头文件的C版本和C++版本相同,而有时候新版本做了一些修改。对于纯粹的C++头文件
(如iostream)来说,去掉h不只是形式上的变化,没有h的头文件也可以包含名称空间—本章的下一个主题。
对头文件的命名约定进行了总结:
由于C使用不同的文件扩展名来表示不同文件类型,因此用一些特殊的扩展名(如.hpp或.hxx)表示C++头文件是有道理的,ANSI/ISO委员会也这样认为。问题在于究竟使用哪种扩展名,因此最终他们一致同意不使用任何扩展名。
如果使用iostream,而不是iostream.h,则应使用下面的名称空间编译指令来使iostream中的定义对程序可用:
using namespace std;
这叫做using编译指令。
名称空间支持是一项C++特性,旨在让您编写大型程序以及将多个厂商现有的代码组合起来的程序时更容易,它还有助于组织程序。一个潜在的问题是,可能使用两个已封装好的产品,而它们都包含一个名为wanda( )的函数。这样,使用wanda( )函数时,编译器将不知道指的是哪个版本。名称空间让厂商能够将其产品封装在一个叫做名称空间的单元中,这样就可以用名称空间的名称来指出想使用哪个厂商的产品。
按照这种方式,类、函数和变量便是C++编译器的标准组件,它们现在都被放置在名称空间std中。仅当头文件没有扩展名h时,情况才是如此。
这意味着在iostream中定义的用于输出的cout变量实际上是std::cout,而endl实际上是std::endl。
因此,可以省略编译指令using。
#include
int main() {
std::cout << "I love C++ !" << std::endl;
return 0;
}
输出结果为:
然而,多数用户并不喜欢将引入名称空间之前的代码(使用iostream.h和cout)转换为名称空间代码(使用iostream和std::cout),除非他们可以不费力地完成这种转换。于是,using编译指令应运而生。下面的一行代码表明,可以使用std名称空间中定义的名称,而不必使用std::前缀:
using namespace std;
这个using编译指令使得std名称空间中的所有名称都可用。这是一种偷懒的做法,在大型项目中一个潜在的问题。更好的方法是,只使所需的名称可用,这可以通过使用using声明来实现:
用这些编译指令替换下述代码后,便可以使用cin和cout,而不必加上std::前缀。
using namespace std;
然而,要使用iostream中的其他名称,必须将它们分别加到using列表中。本书首先采用这种偷懒的方法,其原因有两个。首先,对于简单程序而言,采用何种名称空间管理方法无关紧要;其次,本书的重点是介绍C++的基本方面。
现在来看一看如何显示消息。
cout << "I love C++ !";
cout << endl;
双引号括起的部分是要打印的消息。
在C++中,用双引号括起的一系列字符叫做字符串,因为它是由若干字符组合而成的。
<<符号表示该语句将把这个字符串发送给cout;该符号指出了信息流动的路径。
cout是什么呢?它是一个预定义的对象,知道如何显示字符串、数字和单个字符等(第1章介绍过,对象是类的特定实例,而类定义了数据的存储和使用方式)。
cout对象有一个简单的接口,如果string是一个字符串,则下面的代码将显示该字符串:
cout << string
对于显示字符串而言,只需知道这些即可。
然而,现在来看看C++从概念上如何解释这个过程。从概念上看,输出是一个流,即从程序流出的一系列字符。cout对象表示这种流,其属性是在iostream文件中定义的。cout的对象属性包括一个插入运算符(<<),它可以将其右侧的信息插入到流中。请看下面的语句(注意结尾的分号):
cout << “Come up and C++ me some time.”;
它将字符串“Come up and C++ me some time.”插入到输出流中。因此,与其说程序显示了一条消息,不如说它将一个字符串插入到了输出流中。不知道为什么,后者听起来更好一点(参见下图)。
如果熟悉C后才开始学习C++,则可能注意到了,插入运算符(<<)看上去就像按位左移运算符(<<),这是一个运算符重载的例子,通过重载,同一个运算符将有不同的含义。编译器通过上下文来确定运算符的含义。C本身也有一些运算符重载的情况。例如,&符号既表示地址运算符,又表示按位AND运算符;* 既表示乘法,又表示对指针解除引用。这里重要的不是这些运算符的具体功能,而是同一个符号可以有多种含义,而编译器可以根据上下文来确定其含义(这和确定“sound card”中的“sound”与“sound financial basic”中的“sound”的含义是一样的)。C++扩展了运算符重载的概念,允许为用户定义的类型(类)重新定义运算符的含义。
现在来看看程序第二个输出流中看起来有些古怪的符号:
cout << endl;
endl是一个特殊的C++符号,表示一个重要的概念:重起一行。在输出流中插入endl将导致屏幕光标移到下一行开头。诸如endl等对于cout来说有特殊含义的特殊符号被称为控制符(manipulator)。和cout一样,endl也是在头文件iostream中定义的,且位于名称空间std中。
打印字符串时,cout不会自动移到下一行,第一条cout语句将光标留在输出字符串的后面。每条cout语句的输出从前一个输出的末尾开始。
C++还提供了另一种在输出中指示换行的旧式方法:C语言符号\n
:
显示字符串时,在字符串中包含换行符,而不是在末尾加上endl,可减少输入量:
另一方面,如果要生成一个空行,则两种方法的输入量相同,但对大多数人而言,输入endl更为方便:
一个差别是,endl确保程序继续运行前刷新输出(将其立即显示在屏幕上);
而使用“\n”不能提供这样的保证,这意味着在有些系统中,有时可能在您输入信息后才会出现提示。
在C++中,分号标示了语句的结尾。因此,在C++中,回车的作用就和空格或制表符相同。也就是说,在C++中,通常可以在能够使用回车的地方使用空格,反之亦然。这说明既可以把一条语句放在几行上,也可以把几条语句放在同一行上。
这样虽然不太好看,但仍然是合法的代码。必须遵守一些规则,具体地说,在C和C++中,不能把空格、制表符或回车放在元素(比如名称)中间,也不能把回车放在字符串中间。
一行代码中不可分割的元素叫做标记(token)。通常,必须用空格、制表符或回车将两个标记分开,空格、制表符和回车统称为空白(white space)。有些字符(如括号和逗号)是不需要用空白分开的标记。下面的一些示例说明了什么情况下可以使用空白,什么情况下可以省略:
虽然C++在格式方面赋予了您很大的自由,但如果遵循合理的风格,程序将更便于阅读。有效但难看的代码不会令人满意。多数程序员都使用程序所示的风格,它遵循了下述规则:
每条语句占一行。
每个函数都有一个开始花括号和一个结束花括号,这两个花括号各占一行。
函数中的语句都相对于花括号进行缩进。
与函数名称相关的圆括号周围没有空白。
前三条规则旨在确保代码清晰易读;第四条规则帮助区分函数和一些也使用圆括号的C++内置结构(如循环)。