第0章 序 幕
有一次,我遇到一个人,他曾经用各种语言写过程序,唯独没用过C和C++(www.cppentry.com)。他提了一个问题:“你能说服我去学习C++(www.cppentry.com),而不是C吗?”,这个问题还真让我想了一会儿。我给许许多多人讲过C++(www.cppentry.com),可是突然间我发现他们全都是C程序员出身。到底该如何向从没用过C的人解释C++(www.cppentry.com)呢?
于是,我首先问他使用过什么与C相近的语言。他曾用Ada1编写过大量程序——但这对我毫无用处,我不了解Ada。还好他知道Pascal,我也知道。于是我打算在我们两个之间有限的共通点之上找到一个例子。
下面看看我是如何向他解释什么事情是C++(www.cppentry.com)可以做好而C做不好的。
0.1 第一次尝试
C++(www.cppentry.com)的核心概念就是类,所以我一开始就定义了一个类。我想写一个完整的类定义,它要尽量小,要足够说明问题,而且要有用。另外,我还想在例子中展示数据隐藏(data hiding),因此希望它有公有数据(public data)和私有数据(private data)。经过几分钟的思索,我写下这样的代码:
# include <stdio.h>
class Trace {
public:
void print(char* s) { printf("%s", s); }
};
我解释了这段代码是如何定义一个名叫Trace的新类,以及如何用Trace对象来打印输出消息:
int main()
{
Trace t;
t.print("begin main()\n");
// main函数的主体
t.print("end main()\n");
}
到目前为止,我所做的一切都和其他语言很相似。实际上,即使是C++(www.cppentry.com),直接使用printf也是很不错的,这种先定义类,然后创建类的对象,再来打印这些消息的方法,简直舍近求远。然而,当我继续解释类Trace定义的工作方式时,我意识到,即便是如此简单的例子,也已经触及到某些重要的因素,正是这些因素使得C++(www.cppentry.com)如此强大而灵活。
0.1.1 改进
例如,一旦我开始使用Trace类,就会发现,如果能够在必要时关闭跟踪输出(trace output),这将会是个有用的功能。小意思,只要改一下类的定义就行:
#include <stdio.h>
class Trace {
public:
Trace() {noisy = 0; }
void print(char* s) { if (noisy) printf("%s", s); }
void on() { noisy = 1; }
void off() { noisy = 0; }
private:
int noisy;
};
此时类定义包括了两个公有成员函数on和off,它们影响私有成员noisy的状态。只有noisy为on(非零)才可以输出。因此,
t.off();
会关闭t的对外输出,直到我们通过下面的语句恢复t的输出能力:
t.on();
我还指出,由于这些成员函数定义在Trace类自身的定义内,C++(www.cppentry.com)会内联(inline)扩展它们,所以就使得即使在不进行跟踪的情况下,在程序中保留Trace对象也不必付出许多代价。我立刻想到,只要让print函数不做任何事情,然后重新编译程序,就可以有效地关闭所有Trace对象的输出。
0.1.2 另一种改进
当我问自己“如果用户想要修改这样的类,将会如何?”时,我获得了更深层的理解。
用户总是要求修改程序。通常,这些修改是一般性的,例如“你能让它随时关闭吗?”或者“你能让它打印到标准输出设备以外的东西上吗?”我刚才已经回答了第一个问题。接下来着手解决第二个问题,后来证明这个问题在C++(www.cppentry.com)里可以轻而易举地解决,而在C里却得大动干戈。
我当然可以通过继承来创建一种新的Trace类。但是,我还是决定尽量让示例简单,避免介绍新的概念。所以,我修改了Trace类,用一个私有数据来存储输出文件的标识,并提供了构造函数,让用户指定输出文件:
#include <stdio.h>
class Trace {
public:
Trace() { noisy = 0; f = stdout; }
Trace (FILE* ff) { noisy = 0; f = ff; }
void print(char* s)
{ if (noisy) fprintf(f, "%s", s); }
void on() { noisy = 1; }
void off() { noisy = 0; }
private:
int noisy;
FILE* f;
};
这样改动,基于一个事实:
printf(args);
等价于:
fprintf(stdout, args);
创建一个没有特殊要求的Trace类,则其对象的成员f为stdout。因此,调用fprintf所做的工作与调用前一个版本的printf是一样的。
类Trace有两个构造函数:一个是无参构造函数,跟上例一样输出到stdout;另一个构造函数允许明确指定输出文件。因此,上面那个使用了Trace类的示例程序可以继续工作,但也可以将输出定向到比如说stderr上:
int main()
{
Trace t(stderr);
t.print("begin main()\n");
// main 函数的主体
t.print("end main()\n");
}
简而言之,我运用C++(www.cppentry.com)类的特殊方式,使得对程序的改进变得轻而易举,而且不会影响使用这些类的代码。