导读
现在好多C++软件工程师,大多数都是C语言出身,他们在工作过程中用得更多的还是C语言思想,或许他们还没有意识到C++ 的一些更加有趣的特性。现在给大家解释什么事情是C++可以做好,而C做不好的。希望对这一类人有一点点启发。
目录
- 第一次尝试
- 改进
- 另一种改进
- 不用类来改进
正文
1、第一次尝试
现在给定一个例子:
#include <stdio.h> class Trace{ public: void print( char *s ){ printf("%s", s); } }; int main(){ Trace t; t.print("begin main "); t.print("end main "); }
2、改进
如果能够在必要时关闭跟踪输出(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++会内联(inline)扩展它们,所以就使得即使在不进行跟踪的情况下,在程序中保留Trace对象也不必付出许多代价。只要让print函数不做任何事情,然后重新编译程序,就可以有效地关闭所有Trace对象的输出。
3、另一种改进
如果让它打印到标准输出设备以外的东西上,那又该怎么改进呢?一种方法是可以用继承来创建一种新的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 "); t.print("end main "); }
简而言之,运用C++类的特殊方式,使得对程序的改进变得轻而易举,而不会影响使用这些类的代码。
4、不用类来实现
对于这个问题,典型的C解决方案会怎么样的。
#include <stdio.h> static int noisy = 1; void trace(char *s) { if(noisy) printf("%s ", s); } void trace_on(){ noisy = 1; } void trace_off(){ noisy = 0;}
这个方法是有效的,但是与C++方法比较起来,有3个明显的缺点。
1. 函数trace不是内联,因此即使当跟踪关闭时,它还保持着函数调用的开销。
2. C版本引入了3个全局名字:trace、trace_on和trace_off,而C++只引入了1个。
3. 很难将这个例子一般化,使之能输出到一个以上的文件中。为什么这么说呢?考虑一下我们会怎么样使用这个trace函数:
int main(){ trace("begin main "); // main函数主体 trace ("end main "); }
采用C++,可以只在创建Trace对象时一次性指定文件名。而在C版本中,情况相反,没有合适的位置指定文件名。一个显而易见的办法就是给函数trace 增加一个参数,但是需要找到所有对trace函数的调用,并插入这个新增的参数。另一种办法就是引入名为trace_out的第4个函数,用来将跟踪输出转向到其他文件。这当然也得要求判断和记录跟踪输出是打开还是关闭。考虑一下,譬如,main调用的一个函数恰好利用了trace_out向另一个文件输出,则何时切换输出的开关状态呢?显然,要想使结果正确需要花费相当的精力。