亲宝软件园·资讯

展开

C语言与C++的区别联系 深入浅析C语言与C++的区别与联系

程序媛张小妍 人气:0
想了解深入浅析C语言与C++的区别与联系的相关内容吗,程序媛张小妍在本文为您仔细讲解C语言与C++的区别联系的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:C语言与C++区别分析,C语言与C++联系,下面大家一起来学习吧。

C语言虽说经常和C++在一起被大家提起,但可千万不要以为它们是一种编程语言。我们来介绍C语言和C++中的区别和联系。

首先C++和C语言本来就是两种不同的编程语言,但C++确实是对C语言的扩充和延伸,并且对C语言提供后向兼容的能力。对于有些人说的C++完全就包含了C语言的说法还是有点别扭的。

一、C语言是面向过程语言,而C++是面向对象语言

我们都知道C语言是面向过程语言,而C++是面向对象语言,说C和C++的区别,也就是在比较面向过程和面向对象的区别。

1、面向过程和面向对象的区别

面向过程:面向过程编程就是分析出解决问题的步骤,然后把这些步骤一步一步的实现,使用的时候一个一个的依次调用就可以了。

面向对象:面向对象编程就是把问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。

2、面向过程和面向对象的优缺点

在学习一些比较抽象的概念时,由于我们的理解能力很有限,有时候一些比较恰当的例子也是有助于我们学习的,因此对二者的优缺点比较,还是先举例子,后总结吧!

(1)用面向过程写出来的程序就像一份蛋炒饭,也就是米饭和炒的菜均匀的混合在了一起,因此蛋炒饭入味均匀,不会像盖浇饭那样,可能有时候吃的菜多饭少,还有时候菜少饭多。但是如果你不喜欢吃蛋炒饭,只想吃肉炒饭,那么原来做的这份蛋炒饭就得倒掉了,重新做一份肉炒饭,厨师就得辛苦了!

(2)用面向对象写出来的程序就像一份盖浇饭,也就是米饭和盖菜分别做好,将盖菜放在米饭上面,盖浇饭虽然没有蛋炒饭那样入味均匀,但是如果给了你一份土豆丝盖饭,你又不想吃了,换成牛肉盖饭,厨师只需要将米饭上面的土豆丝倒掉,重新做一份牛肉放在上面就好了。

那么到底蛋炒饭和盖浇饭哪个好吃呢?
谁也不能说到底哪个好,毕竟蛋炒饭的餐馆和盖浇饭的餐馆都很多,而且生意都很不错,存在即为合理!
如果非要将二者进行一个高地的比较的话,那就得先设定一个场景了!
盖浇饭的好处就是”菜”“饭”分离,从而提高了制作盖浇饭的灵活性。饭不满意就换饭,菜不满意换菜。用专业术语来说就是”可维护性“较好,”饭” 和”菜”的耦合度比较低。
蛋炒饭将”蛋”“饭”搅和在一起,想换”蛋”“饭”中任何一种都很困难,耦合度很高,以至于”可维护性”比较差。

二者的简单总结如下:

面向过程语言

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点:没有面向对象易维护、易复用、易扩展

面向对象语言

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护缺点:性能比面向过程低

二、具体语言上的区别

1、关键字的不同

C语言有32个关键字

C++有63个关键字

2、后缀名不同

C源文件后缀.c,C++源文件后缀.cpp,在VS中,如果在创建源文件时什么都不给,默认是.cpp。

3、返回值

C语言中,如果一个函数没有指定返回值类型,默认返回int类型;C++中,如果一个函数没有返回值则必须指定为void。

4、参数列表

在C语言中,函数没有指定参数列表时,默认可以接收任意多个参数;但在C++中,因为严格的参数类型检测,没有参数列表的函数,默认为 void,不接收任何参数。

5、缺省参数

缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的参。(C语言不支持缺省参数)

半缺省参数

void FunTest(int _iParam1, int _iParam2 = 0 )
{}
void FunTest(int _iParam1, int _iParam2 = 0 , int _iParam3/* = 0*/)
{}
void FunTest(int _iParam1, int _iParam2 /* = 0*/,int _iParam3 = 0)
{}

全缺省参数

void FunTest(int _iParam1 = 0, int _iParam = 1)
{ }
 //注意:慎用缺省函数,否则会产生二义性
void FunTest ()
{}
void FunTest (int a = 10 )
{}
//假如使用不带实参方式调用FunTest()函数时,编译器将不知道调用哪一个,产生二义性

注意:

6、函数重载

函数重载:函数重载是函数的一种特殊情况,指在同一作用域中,声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数、类型、顺序)必须不同,返回值类型可以相同也可以不同,常用来处理实现功能类似数据类型不同的问题。(C语言没有函数重载,C++支持函数重载)。

C语言中产生函数符号的规则是根据名称产生,这也就注定了c语言不存在函数重载的概念。而C++生成函数符号则考虑了函数名、参数个数、参数类型。需要注意的是函数的返回值并不能作为函数重载的依据,也就是说int sum和double sum这两个函数是不能构成重载的!

我们的函数重载也属于多态的一种,这就是所谓的静多态。

使用重载的时候需要注意作作用域问题:请看如下代码。

#include <iostream>
using namespace std; 
bool compare(int a,int b)
{
    return a > b;
} 
bool  compare(double a,double b)
{
    return a > b;
} 
int main()
{
    //bool compare(int a,int b);
    compare(10,20);
    compare(10.5,20.5);
    return 0;
}

我在全局作用域定义了两个函数,它们由于参数类型不同可以构成重载,此时main函数中调用则可以正确的调用到各自的函数。

但是请看main函数中被注释掉的一句代码。如果我将它放出来,则会提出警告:将double类型转换成int类型可能会丢失数据。

这就意味着我们编译器针对下面两句调用都调用了参数类型int的compare。由此可见,编译器调用函数时优先在局部作用域搜索,若搜索成功则全部按照该函数的标准调用。若未搜索到才在全局作用域进行搜索。

总结:C语言不存在函数重载,C++根据函数名参数个数参数类型判断重载,属于静多态,必须同一作用域下才叫重载。

7、const

C语言中被const修饰的变量不是常量,叫做常变量或者只读变量,这个常变量是无法当作数组下标的。然而在C++中const修饰的变量可以当作数组下标使用,成为了真正的常量。这就是C++对const的扩展。

C语言中的const:被修饰后不能做左值,可以不初始化,但是之后没有机会再初始化。不可以当数组的下标,可以通过指针修改。简单来说,它和普通变量的区别只是不能做左值而已。其他地方都是一样的。

C++中的const:真正的常量。定义的时候必须初始化,可以用作数组的下标。const在C++中的编译规则是替换(和宏很像),所以它被看作是真正的常量。也可以通过指针修改。需要注意的是,C++的指针有可能退化成C语言的指针。

比如以下情况:

int b = 20;
const int a = b;

这时候的a就只是一个普通的C语言的const常变量了,已经无法当数组的下标了。(引用了一个编译阶段不确定的值)

const在生成符号时,是local符号。即在本文件中才可见。

如果非要在别的文件中使用它的话,在文件头部声明:extern cosnt int data = 10;

这样生成的符号就是global符号。

总结

C中的const叫只读变量,只是无法做左值的变量;

C++中的const是真正的常量,但也有可能退化成c语言的常量,默认生成local符号。

8、引用

说到引用,我们第一反应就是想到了他的兄弟:指针。引用从底层来说和指针就是同一个东西,但是在编译器中它的特性和指针完全不同。

    int a = 10;
	int &b = a;
	int *p = &a;
 
	//b = 20;
	//*p = 20;

首先定义一个变量a = 10,然后我们分别定义一个引用b以及一个指针p指向a。

我们来转到反汇编看看底层的实现:

可以看到底层实现完全一致,取a的地址放入eax寄存器,再将eax中的值存入引用b/指针p的内存中。至此我们可以说(在底层)引用本质就是一个指针。

了解了底层实现,我们回到编译器。我们看到对a的值的修改,指针p的做法是*p = 20;即进行解引用后替换值。底层实现:

再来看看引用修改:

我们看到修改a的值的方法也是一样的,也是解引用。只是我们在调用的时候有所不同:调用p时需要*p解引用,b则直接使用就可以。由此我们 推断出:引用在直接使用时是指针解引用。p直接使用则是它自己的地址。

这样我们也了解了,我们给引用开辟的这块内存是根本访问不到的。如果直接用就直接解引用了。即使打印&b,输出的也是a的地址。

在此附上将指针转为引用的小技巧:int *p = &a,我们将 引用符号移到左边 将 *替换即可:int &p = a。

接下来看看如何创建数组的引用:

int array[10] = {0};       //定义一个数组

我们知道,array拿出来使用的话就是数组array的首元素地址。即是int *类型。

那么&array是什么意思呢?int **类型,用来指向array[0]地址的一个地址吗?不要想当然了,&array是整个数组类型。

那么要定义一个数组引用,按照上面的小诀窍,先来写写数组指针吧:

int (*q) [10] = &array;

将右侧的&对左边的*进行覆盖:

int (&q)[10] = array;

测试sizeof(q) = 10。我们成功创建了数组引用。

经过上面的详解 ,我们知道了引用其实就是取地址。那么我们都知道一个立即数是没有地址的,即

int &b = 10;

这样的代码是无法通过编译的。那如果你就是非要引用一个立即数,其实也不是没有办法:

const int &b  = 10;

即将这个立即数用const修饰一下,就可以了。为什么呢?

这时因为被const修饰的都会产生一个临时量来保存这个数据,自然就有地址可取了。

9、malloc,free && new,delete

这个问题很有意思,也是重点需要关注的问题。malloc()和free()是C语言中动态申请内存和释放内存的标准库中的函数。而new和delete是C++运算符、关键字。new和delete底层其实还是调用了malloc和free。它们之间的区别有以下几个方面:

1)、malloc和free是函数,new和delete是运算符。

2)、malloc在分配内存前需要大小,new不需要。

例如:

int *p1 = (int *)malloc(sizeof(int));

int *p2 = new int;     //int *p3 = new int(10);

malloc时需要指定大小,还需要类型转换。new时不需要指定大小因为它可以从给出的类型判断,并且还可以同时赋初始值。

3)、malloc不安全,需要手动类型转换,new不需要类型转换。

详见上一条。

4)、free只释放空间,delete先调用析构函数再释放空间(如果需要)。

与第⑤条对应,如果使用了复杂类型,先析构再call operator delete回收内存。

5)、new是先调用构造函数再申请空间(如果需要)。

与第④条对应,我们在调用new的时候(例如int *p2 = new int;这句代码 ),底层代码的实现是:首先push 4字节(int类型的大小),随后call   operator new函数分配了内存。由于我们这句代码并未涉及到复杂类型(如类类型),所以也就没有构造函数的调用。如下是operator new的源代码,也是new实现的重要函数:

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                        _THROW_NCEE(_XSTD bad_alloc, );
                }
 
        return (p);
        }

我们可以看到,首先malloc(size)申请参数字节大小的内存,如果失败(malloc失败返回0)则进入判断:如果_callnewh(size)也失败的话,抛出bad_alloc异常。_callnewh()这个函数是在查看new handler是否可用,如果可用会释放一部分内存再返回到malloc处继续申请,如果new handler不可用就会抛出异常。

6)、内存不足(开辟失败)时处理方式不同。

malloc失败返回0,new失败抛出bad_alloc异常。

7)、new和malloc开辟内存的位置不同。

malloc开辟在堆区,new开辟在自由存储区域。

8)、new可以调用malloc(),但malloc不能调用new。

new就是用malloc()实现的,new是C++独有malloc当然无法调用。

10、作用域

C语言中作用域只有两个:局部,全局。

C++中则是有:局部作用域,类作用域,名字空间作用域三种。

所谓名字空间就是namespace,我们定义一个名字空间就是定义一个新作用域。访问时需要以如下方式访问(以std为例)

std::cin<< "123" <<std::endl;

例如我们有一个名字空间叫Myname,其中有一个变量叫做data。

如果我们希望在其他地方使用data的话,需要在文件头声明:using Myname::data;

这样一来data就使用的是Myname中的值了。可是这样每个符号我们都得声明岂不是累死?

我们只要using namespace Myname;就可以将其中所有符号导入了。

这也就是我们经常看到的using namespace std;的意思啦。

加载全部内容

相关教程
猜你喜欢
用户评论