被误解的C++——学习和使用,该怎么处理

被误解的C++——学习和使用
学习和使用
C++太复杂了。我猜想你一定是在计算机屏幕前不住地点头。其实我也在使劲的点头。没错,C++着实复杂,大概是迄今为止最复杂的语言。于是,便产生了一个推论:C++难学难用。
这句话也相当准确,但并非所有时候都这样。C++的学习和使用可以分为若干个层面,这些层面由简到繁,由易到难,分别适合不同层次的学习者和使用者。
先让我们来看看C++究竟有多复杂。我大致列了个清单,包括C++的主要语言特性。基本的一些特性,象变量、条件、选择等等,都掠去了,主要集中在体现C++复杂性的方面:
1. 指针。包括变量指针、函数指针等。可以计算。
2. 引用。包括变量的引用、函数的引用等。
3. *函数。
4. 类。包括具体类和抽象类。
5. 重载。包括函数重载和操作符重载。
6. 成员数据。包括static和非static的。
7. 成员函数。包括static和非static的。
8. 虚函数。包括对虚函数的override。
9. 继承。包括多继承、虚继承等。
10. 多态。包括动多态和静多态。
11. 类型转换。包括隐式的和显式的,以及臭名昭著的强制转换。
12. 模板。包括类模板和函数模板。
13. 模板特化。包括完全特化和部分特化。
14. 非类型模板参数。
15. 模板-模板参数。
我对照了C#的特性清单。C++比C#多的特性主要是:1、2、3、9的多继承、10的静多态、13、14、15。
而C#比C++多的特性主要有(这里没有考虑C++/CLI,C++/CLI补充了标准C++):
1. for   each关键字。
2. as、is关键字。
3. seal关键字。
4. abstract关键字。
5. override/new关键字。
6. using关键字的一些补充用法。
7. implicit/explicit关键字。(C++有explicit关键字,但只用于构造函数)
8. interface关键字。
9. property。
10. delegate。
我们来分析一下。C++比C#所多的特性集中在编程技术方面,特别是编程模式,如由模板带来泛型编程和元编程机能。在OOP方面,C++走得更远,更彻底,主要表现在多继承和操作符重载方面。在传统的编程技术上,C++区分了对象和对象的引用(指针和引用)。而C#所有引用对象所持的都是引用。最后,C++拥有*函数。
反过来再看C#,C#所多的都是些关键字。这些关键字都集中在OOP方面,并且都是以使编程技术易于理解为目的的。很多人(包括我在内)都希望C++也拥有C#的这些关键字(至少部分)。但与一般人的理解不同,C++标准委员会实际上是被编译器开发商所把持着,他们对引入关键字尤其神经过敏。没有办法,现实总是有缺憾的。
从这些方面可以看出,C++在编程机制上远远多于C#(Java的机制甚至更少)。对于新入行的人而言,一口气吞下这些内容,足以把他们撑死。相反,C#增加的关键字有助于初学者理解代码的含义。这些就是C#和Java比C++易于学习(易于理解)的真正原因。
但是,必须要强调的是,C#和Java中易于学习和理解的是代码,而不是这些代码背后的技术原理和背景。
我看到过的绝大多数C#代码都充满了重复代码和大量switch操作分派。如果这些程序员充分利用C#和Java的OOP机制,这些严重的代码冗余可以消除一半。(如果C#和Java具备C++那样的泛型编程能力,则另一半也可以消除。)这些程序员都是在没有充分理解语言机制和OOP技术的情况下编写软件,事倍功半。
这种情况在C++也有发生,但相对少些。这大概是因为C++足够复杂,使得学习者产生了“不彻底理解C++就学不会C++,就用不了C++”的想法。这种想法有利有弊,利在于促使学习者充分理解语言和语言背后的技术,而弊在于它吓跑了很多人。实际上,我们一会儿就会看到,C++可以同C#和Java一样,可以在不理解其中原理的情况下,仅仅按照既定规则编程。当然我们不希望这样,这是不好的做法。但鉴于现在业界
的浮躁心态,我们也就入乡随俗吧。
注意了!下面这句话是最关键的,最重要的,也是被长期忽略的:C++之所以复杂,是为了使用起来更简单。听不明白!自相矛盾!胡说!别急,且听我慢慢道来。
(限于篇幅,我这里只给出最后一部分案例代码,完整的案例在我的blog里:)
有三个容器,c1、c2、c3。容器的类型和元素的类型都未知。要求写一个算法框架,把c1里的元素同c2里的元素进行某种运算,结果放到c3里。
由于容器类型未知,必须使用所有容器公共的接口。所以,我写下了如下的C#代码:
public   delegate   void   alg <T1,   T2,   R> (T1   v1,   T2   v2,   R   r);
public   static   void   Caculate_Contain <C1,   T1,   C2,   T2,   C3,   T3>
(C1   c1,   C2   c2,   C3   c3,   alg <T1,   T2,   T3>   a   )
where   C1:   IEnumerable <T1>
where   C2   :   IEnumerable <T2>
where   C3   :   IEnumerable <T3>
{
IEnumerator <T1>   eai1   =   c1.GetEnumerator();
IEnumerator <T2>   eai2   =   c2.GetEnumerator();
IEnumerator <T3>   eai3   =   c3.GetEnumerator();

while   (eai1.MoveNext()   &&   eai2.MoveNext()   &&   eai3.MoveNext())
{
a(eai1.Current,   eai2.Current,eai3.Current);
}
}
//使用
public   static   void   CaculThem(int   v1,   int   v2,int   r)   {
        r=v1*v2;
}
Caculate_Contain(ai1,   ai2,   ai3,   new   alg <int,   int,   int> (CaculThem));
public   static   void   CaculThem2(float   v1,   int   v2,double   r)   {
        r=v1*v2;
}
Caculate_Contain(af1,   ai2,   ad3,   new   alg <float,   int,   double> (CaculThem2));
我使用了一个委托,作为传递处理容器元素的算法的载体。使用时,用具体的算法创建委托的实例。但具体的算法CaculThem()必须同相应的容器元素类型一致。
下面轮到C++:
template <typename   C1,   typename   C2,   typename   C3,   typename   Alg>