C、C++能成为C/C++吗


目录:

    摘要

    指针 vs引用

    宏vs Inline函数

    结构体vs类

    面向过程vs面向对象

    总结

    参考文献

 

摘要

    几年前微软的Dmitry Kakurin同学曾质疑用C语言开发Git(Git用的是纯C而非C++),针对质疑著名的Linux之父Linus Torvalds先生予以回击:C++是一种糟糕的语言,C++想解决的都是一些皮毛的问题,大部分语法基本上纯粹是C语法的扩展,以对象为核心的面向对象语言简直是呓语对象本身没有任何意义。

    本文通过CC++中一些基本概念的对比说明:C++的语法不是纯粹的C语法的扩展。C++在保留C大部分语法的前提下(兼容性的考虑)提供了一些方便于程序员与解决实际问题的特性。无论是C语言还是C++都是优秀的编程语言,正因为C++C语言之间较好的兼容性才使得一些C/C++组织的兴起。

 

一、指针 vs引用

    指针是地址:这是不少教科书上的论断。指针真的是地址吗?事实上,除了地址指针还肩负着更为重要的作用与使命:指针类型、指令与数据的切换。何为指针类型?

    “指针类型”即指针所指向的对象的类型,比如:int*p;其中的指针p的类型就是int,这也就暗含了在32位的Windows操作系统中当我们使用p来索引数据时,要以4字节为单位向下去找(这里暗含了在数据存储时数据是密排的,每个数据中间不出现空隙)。所以说指针不仅仅提供给我们索引数据的起始位置(地址),更重要的是提供了索引数据的方式(指针类型)。在C语言中指针的地位举足轻重,从指针的功能上说指针实现了了指令与数据的切换,比如说在我们实现动态链接库中导出函数的切换时我们用到了指针(函数指针);在处理一些大型的结构体数据时,我们一般将其存储到数组中再用指针进行数据的切换访问(数组指针,所以说数组与指针是分不开的在大师的著作中都是将指针与数组放到一起讲解,甚至将结构体也加进来)。从语言的体系上来说:通过指针这一4字节数据的使用,使我们能索引到并且访问出事先组织好的大型数据,也体现了指针的“以小博大”的设计理念。指针的这种灵活性在给程序员带来巨大的编程便捷的情况下也埋下了深深的隐患:易出错(比如:野指针)。针对这一问题,C++在保留C语言指针这一概念以及它的优点的情况下,又提出了一个引用的概念。

    引用是别名:这也是不少教科书的论断。引用真的仅仅是别名吗?让我们看下面的程序:

 

#include<IOSTREAM.H>

 

void main()

{   

      int i = 4;     //00401059  mov   dword ptr [ebp-0x4],0x4

      int j = 8;     //00401060  mov   dword ptr [ebp-0x8],0x8

 

      int &r = i;   //00401067  lea    eax,[ebp-0x4]

                        //0040106A   mov   [ebp-0xC],eax

 

      r= j;          //0040106D   mov   ecx,[ebp-0xC]

                      //00401070   mov   edx,[ebp-0x8]

                      //00401073   mov   [ecx],edx                

}

 

    通过汇编语言看出r变量存的是地址(eax),所以说引用在形式上被当作一个变量的别名来用而实质则是一个指向变量的指针(编译器与语法功能隐藏了引用的实现细节)。我们知道引用在定义时必须对其进行初始化,这样就规定了一个指针并且同时强制要求它指向一个安全的数值,从而从根源上降低了野指针等指针类错误发生的概率;引用在初始化时不允许对其赋空值(引用的目的是将指针“看住”降低其灵活性而将引用赋空违背了引用设计的指导思想)。

    所以说C++对于引用的引入不仅仅是对“指针”的一次“再封装”,更是在特定的编程条件下方便了程序员(比如:函数的引用传参等)。

 

二、宏vs Inline函数

    “#define PI 3.1415926”、“#define SUM(a,b) (a)+(b)”...对于这样的语句相信学习过C语言编程的人都不陌生,对于这种既可以少打几个字母又可以在程序改变时方便修改,同时在程序运行时可以大大节省时间的方法我们简直是屡试不爽,这就是宏。

    宏替换作为C语言的三大预处理功能之一(宏替换、文件包含、条件编译),在C语言中有着举足轻重的作用。首先宏在使用的时候将会展开成内联代码用以提高程序的执行效率;其次宏服务于任何数据类型(之前的SUM既可以求int的和也能求double的和),可以无须写太多的函数;再者在C语言的标准库文件stdio.h中getchar、putchar、getc等均是由宏来进行定义,这样避免了针对每个字符的处理都需要在运行时进行函数调用而带来的开销。既然宏可以的很好实现求和功能(函数功能),那么我们用宏完全替代函数不是一个很好的选择吗?可惜的是宏的一些天然的致命缺点使我们放弃了这种想法。我们看下面的代码:

 

#define SUM(a, b)   (a)+(b)

#define SUM(a, b, c)  (a)+(b)+(c)       //重定义

#define Recursion(n)  Recursion((n)-1)+1  //未定义

 

    也就是说宏不支持重载也不接受递归调用,你也许会说不支持重载我不用重载不接受递归调用我不用递归调用也能把程序写的很好。那么我们看下面的代码:

 

#define  PI  3.14159;   //注意我多写了个分号

 

void main()

{

      …

      cout << PI << endl; //编译时错误发生在此处

      …

}

 

    这说明宏在定义时不进行语法检查,它的语法错误发生在宏展开的位置,你也许会说这不是个问题不都是在编译时出现错误吗,位置无关紧要。值得注意的是我们这里是测试程序,在实际的大程序中编译是需要消耗很长时间的,比如说你的程序需要编译4个小时而你恰恰使用了一个具有语法错误的宏,并且更巧的是你在程序的末尾用到了这个宏,这导致的结果是当你编译3个半小时的时候出现编译错误,我想这都不是我们希望看到的。也许你又会说这种情况不就是耗费点时间无伤大雅(哥多的就是时间),那么我们看下面的程序:

 

#denfine SQUARE(i) i*i     //注意正确的是#denfine SQUARE(i) (i)*(i)   

 

void main()

{

      int i = 2;

      cout << SQUARE(i+3) << endl; 

}

 

    这样就会带来致命的错误:希望是5的平方25,得到的结果却是2+3*2+3=11也就是说宏不进行语义检查(可以通过编译的)。

    针对宏的种种问题,C++在保留宏的高效执行效率的基础上使用了inline函数这一概念,通过inline函数与const、模板等相关技术的融合实现了宏的全部功能。当函数冠以inline标识符时就会通知编译器来形成函数的内联代码,而不是形成通过普通函数调用机制进行调用的代码,所以总的来说:在形式上inline函数是函数(在编译时进行语法与语义检查),运行起来是宏(节省时间,不进行建栈清栈工作)。看到这也许你会有这样的疑问:既然inline函数这么好用,为什么我们不把所有的函数都声明为inline函数呢。这里需要注意的是不是我们把函数声明为inline函数编译器就一定会把函数当作inline来处理,这与编译器的智能化水平有关。比如说:

 

inline int fun(int i)

{

      return (i<2) ? 1 : i*fun(i-1);

}

 

    对于一个智能化水平高的编译器当我们调用fun(6)的时候编译器会得出常量720,进而在编译的阶段将fun(6)的位置用720替代,当然并不是所有的编译器都可以达到这个效果。

 

三、结构体vs类

    何为结构体?我们为什么要引入结构体这一概念?从C与C++的发展历史来看他们一直继承着基本的计算机制,也就是说他们一直都在避免产生新的基本类型(产生新的基本类型就得考虑他的安全性与有效性)。从编程者的角度来说:一旦有新的类型产生总会希望有另外更为简单的新类型的出现。所以提供一种表述类型的机制要比单纯提供一种类型要好,于是结构体的概念应运而生。结构体是什么?结构体是以一些(接近)主观的类型为元素所构成的集合。比如说:

 

struct Student    //根据实际情况人为设定其中元素

{

      int id;

      int age;

      …

};

 

    然而,在实际的情况下我们总是要操作数据的,比如说我们需要一个获得年龄的操作getAge和一个设置年龄的操作setAge,你也许会说这还不简单在Student结构体中加入getAge与setAge这两个函数不就行了,可惜的是C语言中的结构体中是不允许出现函数的。针对这一问题C++做了一定程度上的改造使得C++中的结构体里是允许放入函数的,在此长舒一口气,不过转念一想无论是在C语言还是在C++的编程中,几乎见不到有人把函数写到结构体中的。在C++中出现频率极高的是类而不是结构体,既然使用结构体就可以很好的将问题表述清楚,那么C++为什么要引入类的概念呢?不是多此一举吗?让我们看一下用类实现的Student:

 

class Student

{

private:     //私有成员数据与其他的实现细节

      int id;

      int age;

      …

public:      //公用接口

      …  

}

 

    所以说与结构体相比,类具有一些天然的优势:

    1、类的构建者可以明确规定与说明类中对象的初始化方式(private:私有创建、public:公有创建…)

    2、类的使用者不需要知道类的实现细节通过构建者提供的接口进行数据的访问。

    3、类的构建者可以在隐藏某些类中对象的实现细节的前提下,提供给类的使用者一些相关的灵活性。

    在此说明一下第三点,我们看下面的代码:

 

class myStudent : Student

{

//类的使用者自己按照实际情况自行定义

public:         

      …

private:

      …

}

    通过Student类的private权限,构建者就可以隐藏对象的实现细节,通过类的继承派生机制,类的使用者就可以定义出自己的成员。同时类的构建者也可以提供虚函数通过C++多态的机制给予使用者更为灵活的灵活性。在实际应用中,我们所提供给用户的框架代码一般是编译完成的代码,我们给用户提供的一般也只是包含相关数据结构与函数声明的头文件并不提供实现文件,此时采用类的机制要比结构体好得多。

    当然类也不是万能的,正确使用类才能达到事半功倍的效果。打个比方,类就如同我们平常使用的包裹一样,买菜的时候我们用兜子装菜,上学的时候我们用书包装书,旅行的时候我们用旅行包装一些旅行的必备用品……只有这样针对不同的情况我们使用不同的包裹才能方便我们的生活。如果我们把我们一生中用到的所有东西都准备好放到一个“大大”的包裹中走到哪里背到哪里,那么包裹将不会方便我们的生活反而会成为我们生活的负担,所以说对于类的设计一定要以实际情况为依托,千万不要出现“万能类”的情况。

 

四、面向过程vs面向对象

    面向过程与面向对象那种编程思想好一些、高级一些呢?这是一个老生常谈的问题,但至今仍然争论不休。人们似乎对于这种近乎哲学的问题向来都抱以极大的“热情”。当面向对象兴起的时候人们纷纷效仿,不知哪位专家学者说了句“面向对象的编程思想才是高级的编程思想”这给予了喜欢“赶潮流”的我们极大的鼓舞。几年前Linus Torvalds先生的一番论断使我们瞬间“惊醒”:原来我们应该追逐的是面向过程,面向过程的编程思想才是王道?这里也不乏有国内的一些所谓著名专家学者的追捧,可奇怪的是作为他们论调的论据竟然仅仅是Linus Torvalds先生所说的原话。也许在技术问题上的讨论,我们依赖的并不是技术本身而是个人崇拜。

    无论是面向过程还是面向对象都是人类看待事物方式,只不过角度不同而已。我们可以容忍现实生活中不同人对于同种事物的不同看法,为什么容忍不了在计算机世界中共存的两种编程思想呢?也许我们从小就被灌输了“专一”的思想,“好的东西就是好的不好的东西就是不好的”,“我一定要考上大学”……我想我们更应该用一种发散的思维去看待问题,从不同的角度去看问题也许就会发现问题的本质。

 

五、总结

    通过上述的论述我想说明的是C++并不是C语言简单的扩展,它在保留C语言中一些基本概念的优点的前提下通过对概念的再封装或通过提出一个相关的概念来方便编程者用以实现编程操作。

    最后回到文章题目所提出的问题上来:CC++能成为C/C++吗?

1、从历史的角度看:

    从语言的发展角度来说CISO C)与C++ISO C++)有着相同的出身就是K&R CKKernighanRRitchie),CC++都继承了Classic C的主要部分,与Classic C都不是100%的兼容,所以说CC++有着天然的相似性可以称之为“兄弟”。

 

2、从现在的情况看:

    目前CC++之间有着诸多兼容的地方:除了本文上述所论之外,CC++之间可以共享同文件,可调用各自语言的库,此外C++所提供的带虚函数的类也有为兼容性考虑的方面,比如说:

class Student

{

      …

      virtual void printGrade()

      {

             cout<< “成绩优秀” << endl;   //C++风格

      }

      …

}

 

class myStudent : Student

{

      …

      void printGrade()

     {

       printf(“成绩优秀\n”);       //C语言风格

     }

    …

}

 

3、从未来的发展看:

    历史经验表明凡是走兼容性道路的产品往往具有较强的生命力(比如:Windows系列操作系统),不兼容则会带来诸多问题。对于语言之间的不兼容就要求编程者要十分清楚不同语言的语法特征,这样在混合语言的编程中会造成严重的歧义与bug。随着各种C/C++组织的兴起以及CC++混合代码的大量使用使得CC++之间的不兼容性所带来的问题日趋明显。要想解决这一问题首先要使得CC++支持各自的编程思想,那么然后呢?B.Stroustrup先生的观点是消除所有的不兼容性,我们暂且不论观点的正确与否,我想B.Stroustrup观点的努力方向总是对的,CC++能成为C/C++吗?我想说:路漫漫其修远兮,吾将上下而求索。

 

 

参考文献:

《The C Programming Language》Dennis Ritchie,BrianKernighan

《The C++ ProgrammingLanguage》Bjarne Stroustrup

《C and C++:Siblings》Bjarne Stroustrup

《C and C++:a Case forCompatibility》Bjarne Stroustrup

《C and C++:Case Studies inCompatibility》Bjarne Stroustrup

《Why C++ is not just anObject-Oriented Programming Language》Bjarne Stroustrup

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值