首页 > 代码库 > 我用C++的理由——关于C和C++的选择

我用C++的理由——关于C和C++的选择

?

摘自:http://www.xue163.com/32/6/325715.html,作者:王可。整理的很好!


首先,我不会使用Java或C#,能力上不会,主观上也不会,因为两点原因:1,他们都属于解释型的语言,这有很多问题是我无法容忍的,程序的速度和封装的安全性;2,他们都不够底层,没有指针,却加载了内存管理器,对我来说这些都是麻烦和束缚,对我而言他们都不是足够自由的语言。或者说,我无法接受他们对使用者的理念,似乎他们认为使用他们的程序员都是懒惰和容易犯错误的,而他们的高级之处都是依赖各种类库,但对底层的操作却有很多限制,如类型转换,非常罗嗦。C和C++就不同了,他们能够给我充分的自由,由程序员来承担自己逻辑错误的后果,和开发需要的类库,这更符合我的个性。?

我现在对C和C++都能熟练地使用,我的工作主要维护着以往的C代码,对这部分代码的修改与补充,我仍然使用C语言;对新的开发任务我都使用C++,如数字图象处理的类库,数据迁移的模块等等。我首先是一名熟练的C程序员,而后逐步掌握C++,最初使用C++是认为他可能比C更方便和高级,不排除有一种追求时髦的情节。现在我同时是这两种语言的熟练掌握者,也曾经犹豫过到底是最终使用C还是C++,主要是担心C++的速度不如C。?


最近看到了关于C和C++的争论,坚持C语言的论调对我有一定的触动,因为我现在基本都是使用C++开发,他们提出的一些C++的弊端正是C++为之骄傲的东西,我从来都没有考虑过这些,这些观点的确非常新颖,且非常有见地;比如,所谓的面向对象和设计模式,我们是否真的需要或只是为追求一些时髦的概念,这引发了我很深的思考。为此我又特意去尝试强制使用C来写已经用C++写好的类库,发现不行,那太罗嗦;我又尝试仅使用C++基本的内容,而不使用高级特性,如继承、虚函数、模板等等,以及直接实现功能类,而不使用复杂的各种类关系,比如我的模板动态数组,需要使用到内存分配器类,对象构造器类,对象分配器类和模板算法集合,发现也不行,我没有办法改变这种设计。?所以,经过这次再尝试,我决定还是选择C++作为我的主要语言工具,我知道C和C++都不能相互替代,但对于我个人,我还是选择C++。我使用C++,完全可以仅使用它和C语言的交集,我可以选择不使用任何高级特性,或选择我需要的那部分高级特性,在适当的位置;但选择C就意味着几乎不可能,虽然C++的一些特性可以在C里将就或有等价实现方法,但那会另你的代码看上去比较累。?

名字空间,我喜欢用名字空间,虽然这不是大问题,但我觉得使用名字空间更舒服。如果是C语言,我就会选择加一串前缀来防止重名或表示这个函数是此函数库的一部分,如:?
lib_func();?

而C++则是:?
lib::func();?

或:?
using namespace lib;?
func();?

我更乐于接受C++的形式,尤其是当你的函数名也很长的时候,C会令它更长。?

在结构体中使用方法,我可以将从属于结构体的函数作为它的成员方法和它声明在一起,这起到了给函数归类的作用,如果是C,或许作者自己是清楚的,但作为调用者就要在一大堆函数中找出操作这个结构体的函数。而且,按照我的习惯,会使函数名更复杂:?

struct StructA a;

lib_StructA_func(&a);?
而C++的形式是:?
lib::StructA a;?
a.func();?


构造函数,我喜欢用构造函数来初始化结构体,这样会避免使用者忘记使用memset,或者有的时候结构体的初始化值并不是全为0,如句柄应该是-1,或者C语言需要单独为初试化写个函数,如:?
struct StructA a;?
memset(&a, 0, sizeof(a));?

或者:?
struct StructA a;?
lib_StructA_init(&a);?

而C++只有一句:?
lib::StructA a;?

拷贝构造函数、赋值运算符、析构函数,同样的道理,重载这些会另使用者考虑更少的问题,代码也更简洁。?

类的作用域控制,它可以很简单地控制哪些成员和方法是供使用者访问的,哪些是内部的成员和方法,而C你想确保函数肯定不会被访问到,就只能写在源文件里,但这样又不能在自己的库内共享,所以只有利用函数名来告戒使用者,这是内部的函数,最好不要直接调用它:?
void _lib_func();?

通常使用下划线起头来代表此类函数,但你仍不能完全消除这种隐患。?

虚函数,我认为它的确有可能非常影响性能,我也是很少使用它,但它作为回调的实现方式的确很好,或者是我个人更乐于接受这种形式吧。在父类中实现算法,然后提供一组虚函数,只要子类继承它并实现那些需要父类在算法过程中来回调的方法,就可以实现回调的目的,这是在C++中一种非常典型的虚函数用法。而作为C,就必须传递函数指针,和书写设置这些函数指针的函数。虽然从本质道理上说,他们是一样的,但C++的形式更让人乐于接受。?

对于虚函数在教课书中描述的用法,以虚类指针去操作子类,可以简化代码的说法,我倒不喜欢用,而且这样的情况其实比较少,而且极度影响性能。?

利用模板类来实现数据结构,是一种对使用者来说非常直观的事情,而传统的C方法,即定义一个void *指针,实现和使用起来是很罗嗦的,如:?
struct Array?
{?
void *buffer; /* 数据地址 */?
int bufsize; /* 内存的总字节数 */?
int typesize; /* 元素的字节长度 */?
int count; /* 元素的有效个数 */?
};?

这种形式有两个问题:1,访问元素需要做指针类型转换,并且没有了类型检查,容易出错;2,如果将数据结构的方法都封装起来,那么肯定没有C++的模板类库快,因为后者大部分函数都是内联的。?
那么就只有第二种形式,即完全用宏来做,这就是所谓的用宏来实现与C++模板类等价的功能,相信很多人都见过那种代码,它几乎可以达到和C++模板类一样的形式:(注意,这些都是宏)?
lib_DeclareArray(a, int); /* 声明一个int数组 */?
lib_Array_Element(a, 0) = 100; /* 访问第一个元素 */?

C++模板类的形式:(在编译后是实例化的真正函数)?
lib::Array<int> a;?
a.Element(0) = 100;?

它甚至可能比C++更快,因为它全是宏,但是它的实现代码可读性太差,而且非常难以调试。?
C++的这一优点在于使用模板的直观和有内联函数的支持,如果没有内联函数,我想我不会选择使用模板类来实现数据结构。?
?
如果说名字空间,成员函数,自动初始化和摧毁,虚函数的回调,这些其实都不是大问题的话,因为只是C++的形式更好一些,那么对我来说,模板和模板类就是不可或缺的。如果要用C实现和C++一样的模板机制,那对程序员的技巧要求太高了,而且产生的代码不仅多而且乱,对于我习惯了用C++的模板,就不会再乐意去使用C的宏技巧。而且C只能使用宏,然后这些宏所代表的代码大段大段地嵌入调用者的代码里,使它的函数变得十分庞大,这就好比一个模板类的所有成员函数都必须是内联函数,而C++是可以选择的。?
模板还有一个好处,就是只需要为算法写一份代码,尤其当算法不太关心或完全独立于参数的类型时,你不使用这项技术,就要写很多份代码。如果类型是有限的,那么还好,你最多就写那么几份雷同的函数,你还可以利用宏的技巧,但如果你的函数需要接受不确定的参数类型,或对无限种可能的类型都有效,那么就不是写多少份代码的问题,而是你根本就不能完美或完全地实现。?
比如copy一个变量到另一个变量,就这么简单的算法,用C++只要实现copy构造函数或赋值运算符,就可以做到:?
template<class t>?
void copy(t &a, const t &b)?
{?
a.~t();?
new (&a) t(b);?
}

如果有利用赋值运算符就更简单:?
template<class t>?
void copy(t &a, const t &b)?
{?
a = b;?
}?

但如果是C,你就必须指定一个赋值函数的地址:?
void copy(void *pa, const void *pb, void (* func)(void *, const void *))?
{?
(*func)(pa, pb);?
}

那这个函数就没有意义了,在C++中写copy构造函数和赋值运算符是一种默认习惯,但在C里写一个独立的复制函数,就不是习惯了。如果没有模板,大量的此类操作都要通过函数指针传递。?
?
异常,如果充分体会返回错误代码和使用异常的区别,那么你就会认为这不是谁替代谁的问题,至少你在C++里就多了一种方式,而在C里你只能:?
int ret = 0;?
if(SUCCESS != (ret = func1()))?
return ret;?
else if(SUCCESS != (ret = func2()))?
return ret;?
else if(SUCCESS != (ret = func3()))?
return ret;?
else?
return SUCCESS;?

看上去很累,而且程序的逻辑看上去很不清晰。?
而C++的形式:?
try?
{?
func1();?
func2();?
func3();?
}?
catch(Exception &e)?
{?
return ERROR;?
}?

你可以选择catch或不管,让更上层的函数去捕获,也可以处理一部分后继续向上抛,或完全将异常处理掉。而你仍然可以选择返回错误代码的方式。?
对我来说,这是两种错误处理的方式,我会根据需要而选择不同的方式,并不是用异常代替所有的返回错误代码。而C语言中也有类似的东西,就是longjmp,所谓的超级goto,它和异常很像,但如果要像C++那样处理多级异常就会很累,因为longjmp必须在错误发生时指定跳转到哪里去的位置,那么就不能像C++那样在任何层次上都可以捕获。不过它也有一个功能是C++的异常做不到的,就是可以跳到更远的地方去,可以转到之前的调用顺序上,而不仅仅是层次,如:?
func1();?
func2(); /* 可以再跳回func1 */?

而C++中func1不可能捕获func2的异常。因此,C++的异常传递顺序,是根据函数堆栈的深度,而C的longjmp是根据过程,它可以回到之前走过的任何位置。但我想自己永远也不可能使用到这个功能。?
?
更重要的,我写的是库,是要给人用的,如果一个变长字符串都是一大堆宏,那还怎么叫人用。开发库就要考虑到使用者的体验和为使用者节约他的代码,而不是为他的程序降低可读性,让他满地都是你的宏名,而他自己的逻辑都被你的代码给淹没了。我认为在这个方面C++做的很好,他是以现代人的理解去展现你的接口的,在C++中简洁的东西性能都是快的,而C做不到C++的简洁,如果要做到最大的性能,却又一定是复杂的。

?

我用C++的理由——关于C和C++的选择