C语言和C++的区别

c语言虽说经常和c++在一起被大家提起,但可千万不要以为它们是一个东西。现在我们常用的C语言是C89标准,C++是C++99标准的。C89就是在1989年制定的标准,如今最新的是C11和C++11标准。根据不同的标准,它们的功能也会有所不同,但是越新的版本支持的编译器越少,所以本文在讨论的时候使用的C语言标准是C89,C++标准是C++99.我们来介绍C语言和C++中那些不同的地方。


1.函数默认值


在C++中我们在定义或声明一个函数的时候,有时会在形参中给它赋一个初始值作为不传参数时候的缺省值,例如:

int FUN(int a = 10);

代表没有传参调用的时候,自动给a赋一个10的初始值。然而这种操作在c89下是行不通的,在c语言下这么写就会报错。

我们都知道,系统在调用任何一个函数的时候都有函数栈帧的开辟,如果函数有参数则需要压入实参。平常在我们人为给定实参的时候,是按照参数列表从右向左依次将参数通过

mov  eax/ecx   dword ptr[ebp-4]       //假设是int数据

指令传入寄存器,再通过push指令压入。现在我们已经给定了函数参数的默认值,那么在压实参的时候只需要一步push初始值即可。效率更高。

另外需要注意的是,赋初始值必须从参数列表的右边开始赋值,从左边开始赋值将会出错:

int sum1(int a = 10,int b);        //错误int sum2(int a,int b = 20);            //正确

因为如果sum1的声明是正确的,那么我们调用的时候怎么调用?sum1( ,20)?很可惜这样属于语法错误,调用这么写既然不对那就当然不能这样赋初始值了。相反,sum2的调用:sum2(20);合情合理,没有任何问题。

实际在写工程的时候,我们都习惯将函数的声明写在头文件中而非本文件里,然后在不同的文件中写出它们的定义。那么这种情况可以赋初始值吗?当然可以,不论是定义还是声明处,只要你遵守从右向左赋的规则就可以。甚至你还可以这样给初始值:

int  fun(int a ,int b = 10);int  fun(int a = 20,int b);

眼尖的同学看见了下面的那行代码大喊错误,因为先给左边赋值了!

其实这样声明完全没有问题,两句声明是同一个函数(函数多次声明没有问题),第一句已经给b了一个初始值,运行到第二句时已经等价于int fun(int a = 20,int b = 10);了。但是注意,这两句的顺序不能反转,否则就是错误的。

 

总结:C89标准的C语言不支持函数默认值,C++支持函数默认值,且需要遵循从右向左赋初始值。

 


2.inline内联函数


说到内联函数大家应当不陌生,它又是一个C89标准下C语言没有的函数。它的具体做法和宏非常相似,也是在调用处直接将代码展开,只不过宏它是在预编译阶段展开,而内联函数是在 编译阶段进行处理的。同时,宏作为预处理并不进行类型检查,而inline函数是要进行类型检查的,也就可以称作“更安全的宏”。

内联函数和普通函数的区别:内联函数没有栈帧的开辟回退,一般我们直接把内联函数写在头文件中,include之后就可以使用,由于调用时直接代码展开所以我们根本不需要担心什么重定义的问题——它连符号都没有生成当然不会所谓重定义了。普通函数生成符号,内联函数不会生成符号。

关于inline还需要注意的一点是,我们在使用它的时候往往是用来替换函数体非常小(1~5行代码)的函数的。这种情况下函数的堆栈开销相对函数体大小来说就非常大了,这种情况使用内联函数可以大大提高效率。相反如果是一个需要很多代码才能实现的函数,则不适合使用。一是此时函数堆栈调用开销与函数体相比已经是微不足道了,二是大量的代码直接展开的话会给调试带来很大的不便。三是如果代码体达到一个阈值,编译器会将它变成普通函数。

同时,递归函数不能声明为inline函数。说到底inline只是对编译器的建议,最终能否成功也不一定。同时,我们平常生成的都是debug版本,在这个版本下inline是不起作用的。只有生成release版时才会起作用。

总结:C89没有,在调用点直接展开,不生成符号,没有栈帧的开辟回退,仅在Release版本下生效。一般写在头文件中。

 


3.函数重载


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++根据函数名参数个数参数类型判断重载,属于静多态,必须同一作用域下才叫重载。

 


4.const


 

这一部分非常重要。在我的另一篇博客“C语言的32个关键字”中对C语言中的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符号。

 


5.引用


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

    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修饰的都会产生一个临时量来保存这个数据,自然就有地址可取了。

 

总结:引用底层就是指针,使用时会直接解引用,可以配合const对一个立即数进行引用。

 


6.malloc,free && new,delete


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

①:malloc和free是函数,new和delete是运算符。

 

②:malloc在分配内存前需要大小,new不需要。

例如:int *p1 = (int *)malloc(sizeof(int));

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

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

 

③:malloc不安全,需要手动类型转换,new不需要类型转换。

详见上一条。

 

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

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

 

⑤: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不可用就会抛出异常。

 

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

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

 

⑦:new和malloc开辟内存的位置不同。

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

 

⑧:new可以调用malloc(),但malloc不能调用new。

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


7.作用域


C语言中作用域只有两个:局部,全局。C++中则是有:局部作用域,类作用域,名字空间作用域三种。

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

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

例如我们有一个名字空间叫Myname,其中有一个变量叫做data。如果我们希望在其他地方使用data的话,需要在文件头声明:using Myname::data;这样一来data就使用的是Myname中的值了。可是这样每个符号我们都得声明岂不是累死?

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

这也就是我们经常看到的using namespace std;的意思啦。
————————————————
版权声明:本文为CSDN博主「绘夜」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/czc1997/article/details/81254971

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/461955.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“返回指向栈空间的指针”的错误

char *GetMemory(void) {char p[] "hello world";return p;//试图返回一个指向栈空间的指针 }

VMware vCenter Converter 关闭SSL加密,提高35-40%性能

VMware vCenter Converter 关闭SSL加密,提高35-40%性能原文&#xff1a;http://www.vmwarearena.com/2013/07/improving-transfer-rate-of-p2v-and-v2v.htmlVMware vCenter Converter Standalone 5.0 is a free tool from VMware to easily perform P2V(Physical to virtual co…

以下题目需要当场编写实现,,答案自己写

以下题目需要当场编写实现 1、 写一个通用方法把任何一个类似“abcdefg”的字符串倒叙排。 2、 把字符数组String[] str{“6”,”5”,”3”,”1”,”2”,”4”}中的字符数字按照从小到大调整位置。  两种方法 3、 用杨辉三角规律打一个数字三角型。 4、 把List容器中的数字元…

迭代之嵌套的for循环

/*主要内容: 1.在嵌套表达式中使用类型的运算符。 2.嵌套的for循环。*/ #include<iostream> usingnamespace std; int main() { int i,k,j,m; for(i1;i<5;i2)//递增表达式使增量每次增加2. { for(j1;j<4;j) { kij; cout<<"i"<<i<<…

MFC中CFileDialog用法

CFileDialog文件选择对话框的使用&#xff1a;首先构造一个对象并提供相应的参数&#xff0c;构造函数原型如下&#xff1a; CFileDialog::CFileDialog( BOOL bOpenFileDialog, LPCTSTR lpszDefExt NULL, LPCTSTR lpszFileName NULL, DWORD dwFlags OFN_HIDEREADONLY | OFN…

回文的判断代码

//写一函数int fun(char *p)判断一字符串是否为回文&#xff0c;是返回1&#xff0c;不是返回0&#xff0c;出错返回 - 1 int fun(char *p) {if (p NULL)return -1;else{int length 0;inti 0;int judge 1;length strlen(p);for (i 0; i<length / 2; i){if (p[i] ! p[…

Codeforces - 1194C - From S To T - 子序列 - 排序

https://codeforces.com/contest/1194/problem/C 好像没什么好说的&#xff0c;要能构造s必须是t的子序列&#xff0c;并且相差的字符集合d是p的子集。 用双指针法求两遍子序列就可以了&#xff0c;甚至不需要sort&#xff0c;假如用桶排的话就是O(qn)的。 下面这个错在哪里呢&…

如何在JS中改变Extjs combox 的值

Ext.getCmp(selectList).setValue(store.getAt(0).get(id));转载于:https://blog.51cto.com/owen563/1316856

图像拼接算法及实现

第一章 绪论 1.1 图像拼接技术的研究背景及研究意义 图像拼接(image mosaic)是一个日益流行的研究领域&#xff0c;他已经成为照相绘图学、计算机视觉、图像处理和计算机图形学研究中的热点。图像拼接解决的问题一般式&#xff0c;通过对齐一系列空间重叠的图像&#xff0c;构…

我的收藏

csdn www.csdn.net 高技术 www.iteye.com 上次买过的手机贴膜http://item.taobao.com/item.htm?id15873819314 www.oschina.com www.itpub.com 转载于:https://www.cnblogs.com/sure32121893/archive/2012/06/25/2560788.html

字符串循环右移的一道题目

void * loopmove_3(char *pstr, int steps) {int n strlen(pstr);//不包含\0steps % n;//移动的步数n - steps;if ((strlen(pstr)<1) || (steps 0)) {return(NULL); //表示没有操作}else {char *tmp malloc(strlen(pstr) 1);if (NULL ! tmp) {strcpy(tmp, pstr n);*(ps…

程序员,你还在 Select * 吗?

应用程序慢如牛&#xff0c;原因多多&#xff0c;可能是网络的原因、可能是系统架构的原因&#xff0c;还有可能是数据库的原因。 那么如何提高数据库SQL语句执行速度呢&#xff1f;有人会说性能调优是数据库管理员&#xff08;DBA&#xff09;的事&#xff0c;然而性能调优跟程…

学习笔记-记ActiveMQ学习摘录与心得(二)

上个周末被我玩过去了&#xff0c;罪过罪过&#xff0c;现在又是一个工作日过去啦&#xff0c;居然有些烦躁&#xff0c;估计这几天看的东西有点杂&#xff0c;晚上坐下来把自己首要工作任务总结总结。上篇学习博客讲了ActiveMQ的特性及安装部署&#xff0c;下面先把我以前启动…

matlab删除、创建文件夹

在某一目录下&#xff0c;创建名字为1、2、3、4、5的五个文件夹&#xff1a; for i1:5 file_name sprintf(%s,num2str(i)); file_path_name strcat(savepath,file_name); file_path_name_ strcat(file_path_name,\); if exist(file_path_name_)0 %该文件…

谈谈C#中的三个关键词new , virtual , override(装载 Winner.Net)

C#支持单继承&#xff0c;说到继承就不得不说new&#xff0c;virtual和override这三个关键词&#xff0c;灵活正确的使用这三个关键词&#xff0c;可以使程序结构更加清晰&#xff0c;代码重用性更高。 以下是msdn中对new&#xff0c;virtual和override的定义&#xff1a; …

不同类型数据所占的字节数

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 数据类型的长度&#xff08;所占的字节数&#xff09;&#xff0c;与机器字长及编译器都有关系。 所以&#xff0c;int、long int、short int等数据类型的长度可能随编译器而异。 几条铁定的原则&#xff08…

物联网概念升级,万物互联来袭

物联网概念尚在升温&#xff0c;万物互联又袭来。本月中旬&#xff0c;知名IT研究与咨询公司Gartner在2013 GartnerSymposium/Itxpo全球大会上向大家分享了他们对2014科技趋势的预测。在会上&#xff0c;Gartner提及的“万物互联”概念倍受科技界媒体关注。Gartner认为&#xf…

java遍历实体类的属性名称与值

//循环遍历OaInfoAssess实体中的属性与值for (Field field : oaInfoAssess.getClass().getDeclaredFields()){ //设置可以获取私人属性 field.setAccessible(true); try { Class type field.getType();// 得到此属性的类型 if(type String.class){ /…

unsigned char s1 : 2的用法

#include<stdio.h> #include<stdlib.h> //默认按照四字节对齐 //#pragma pack(1) union V {struct X{unsigned char s1 : 2;unsigned char s2 : 3;unsigned char s3 : 3;} x;unsigned char c; } v; //#pragma pack()int main(void) {v.c 100;//对应的二进制数字是…

MATLAB中排序函数sort()的用法

MATLAB中排序函数sort()可以对参数的元素进行升序排序或降序排序。 具体的用法如下&#xff1a; Ysort(X) sort()的参数可以是向量&#xff0c;矩阵&#xff0c;数组等等。当X是向量时&#xff0c;sort(X)对X的元素进行升序排序&#xff1b;当X是矩阵时&#xff0c;sort(X)对…