第二章:对象的创建与使用 (1) (基础知识与基本语法)

可以说,从这里开始算是C++的起步了,在接触对象之前,需要了解如何在一段代码当中创建以及使用一个对象。而在这之前,也需要一些基础的知识来作为铺垫。

首先,无论是什么语言,它们最终都会通过一系列的变化,从一种我们人类易于理解的形式,变化成计算机可以执行的形式。而中间这个转化的过程就是翻译,执行翻译操作的工具叫做翻译器。翻译器分为两种,一种是解释器,一种是编译器。解释器通过读取源代码并将其转化成可执行的机器指令,一般情况下,解释器会将自身先加载到内存,然后通过读取一行或者一小段源代码,然后将其在内存中解释成为机器指令,再对其进行执行,然后再读取下一行或者一小段代码。这样会使代码的执行效率非常低下,但是从写代码到执行代码中间并不需要太多时间,而且一旦发生了错误,解释器便可以很快速的指出错误所在。所以解释器的优点在于“较好的交互性和适用于快速程序开发”。当然也有一些解释语言为了提高自身效率,会先将整个程序转化成某种中间语言,然后用更为快速的解释器来执行它。而且解释器还有一个很明显的优点就是可以一定程度上直接将源代码复制到不同的平台使用,甚至可以不用经过太多的处理,基于不同平台的解释器会将代码解释为本平台可以运行的指令并运行它。解释器的工作模式可以总结为边翻译边运行。而编译器则不同,编译器会将源代码直接编译为汇编语言或者机器指令,这个过程比较复杂,一般分多步来执行,关于这方面的知识可以参见编译原理方面的书籍。至少可以看出,这样一来程序节省了运行时的翻译所需时间,大大的提高了程序的运行效率。其工作模式可以总结为先翻译后运行。但是弊端也显而易见,首先从源代码转换到机器指令所耗费的时间将是很大的,而且修改一处将会导致所有的源码都需要重新编译,一旦发生了错误还很难发现错误的所在地,调试程序也成为了一件很难的事情。所幸的是,聪明的工程师们总能解决几乎所有可以被提出来的问题,例如C语言便采用了分段编译这样一个方法来解决第一个问题,将源代码分为一个一个区块,将每一块单独测试完成后便可以进行单独的编译,最后再使用连接器将各个区块的代码连接起来,加上可以被操作系统装载的部分指令,便生成了一个可执行程序。而想要对代码进行调试的话,便需要编译器在编译后的机器指令中间加入一些与源代码有关的信息,以便于一些源代码层的调试器来跟踪程序的执行情况,而在最后源代码可以正常工作后则可以由编译器可以产生不含这些信息的机器指令。我们常用的Visual C++可以通过在工程选项中选择生成debug版本或者release版本来控制这些信息的有无,(当然除此之外一般情况下release版本也会包含更多的优化,在编译中生成更快更短更少冗余的机器指令是编译过程中优化器所做的的工作),而在GNU C++编译器当中我们也可以通过添加debug参数来实现加入调试信息。有了这些东西很多时候会使解释性语言显得不那么的具有诱惑力,然而编译器编译后的程序的工作平台往往是局限的,想要适应更多的平台就必须使用编译器针对不同平台重新编译。这也是为了运行效率所作出的一点牺牲吧。

上一段中提到了C语言采用了分段编译来解决程序“牵一发而动全身”的问题,当然C++当中也采用了这一具有优势的特性,由其在大型工程中,很有可能一次全局的编译要耗费好几个小时甚至好几天的时间。但是在分段编译的时候,也有一些问题需要思考,首先对于一个代码块,当它要引用其他代码块或者数据块的时候,最起码要知道一些其他代码块或者数据块的必要信息,这样编译器才能为他正确的预留一个“接口”让连接器将它引用的代码块连接上来,并且提供给它正确的信息。对于一个数据块这个基本信息就是它的名字和类型组成,对于一个代码块则是它的名字还有它需要的信息类型以及执行完这个代码块之后可以得到的信息类型。当然在C和C++当中这些代码块的最小的单位为函数,而引用这些函数的动作被称为函数的调用,函数调用的时候便需要传递给函数它所需要的参数,并且读取函数执行完之后传递回来的返回值。编译器为了保证能够正确的为一个函数的调用预留接口,并且可以让编译器检查我们传入数据的正确性,于是要求我们在调用这个函数之前一定要对这个函数进行声明,这样编译器才可以获得足够的信息来避免错误。而这个函数的本体可以放置在另外的编译单元当中,而这包含了代码块的函数本体则称为函数的定义。可以由编译器对其进行单独的编译,只要正确的配置了连接器,连接器总能正确的将它们拼接起来。

“声明”是一个很有意思的概念,它会告诉编译器有这样一个名字的数据块或者函数,它是什么什么样子的,你可以在接下来的代码当中根据这些信息预留出它的接口,并让连接器将它的函数体连接上去,接下来的代码便可以正确运行。而“定义”则是告诉编译器下边花括号当中的代码块即是这个名字,或者请在程序运行至这个位置时为这个名字的变量分配空间。在C和C++当中,我们可以在很多地方进行相同的声明,但是定义只能出现在一个地方,如果一个函数或者变量具有多个定义的话,连接器将会报告出错误。

以下为一些详细的使用方法,几乎全部摘自书上。

首先声明一个函数:

double func1(int, float);

其中int表示函数返回值类型,func1为函数名称,可以是除了保留字以外的任何字符串,括号当中是函数的参数表,依次为函数输入参数的类型。当然函数声明的时候也是可以给参数命名的只不过这里的命名对于编译器来书毫无意义,它会将其忽略掉,不过给它命名会给程序员阅读代码带来很多便利。正如下面的声明和上面的声明意义完全相同。

double func1(int a, float b);

当然值得注意的一点是在C和C++当中空的参数列表具有不同的意思,对于C语言来说一个空的参数列表意味着这个函数可以匹配任意的参量表,而对于C++来说这意味着这个函数不能接受任何参数。当然如果需要在C语言当中表示这个函数不能接受任何参数的话需要在括号内加入void,而在C++当中表示这个函数可以接受任何参数的话需要在括号内加入省略号。示例如下:

int func2();

int func2(void);

int func2(…);

而函数的定义也不是特别复杂,定义一个函数即是把函数声明部分的分号替换成为花括号就好:

double func1(int a, float b) {}

花括号中间为函数的主体部分,值得注意的是,如果函数当中使用到了参数表中的某个参数,那么这个参数必须具有一个名字,即标识符,而如果始终没有用到某个参数,则这个参数可以只写它的参数类型,这样的参数称为占位符。一个用途是函数更新后参数需求减少时可以进行占位而不必让编译器弹出警告。

声明一个变量则稍稍有点复杂,这是一个耐人寻味的问题,因为当我们声明一个变量的时候,编译器便获取到了足够的信息可以对它进行内存分配,所以如果我们单独写出一行类似于:

int a;

这样的代码之后,编译器默认这是一条声明和定义。但是如果我们不想在这个地方定义变量的话,我们需要向编译器特别说明。通过在前边加上extern关键字来实现这一点:

extern int a;

这样就可以区分定义和声明一个变量了,当然函数也可以用相同的方法区分,不过会显得多此一举。

以上便是一些关于C++的基础的知识以及基础的语法,通过了解本章后半部分关于库及模板的介绍之后,便可以构建一个简单的C++程序了。我将它分为两部分来叙述。