0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C和C++是一回事吗

STM32嵌入式开发 来源:STM32嵌入式开发 作者:STM32嵌入式开发 2020-11-13 18:18 次阅读

C语言虽说经常和c++在一起被大家提起,但可千万不要以为它们是一个东西。现在我们常用的C语言是C89标准,C++是C++99标准的。C89就是在1989年制定的标准。本文在讨论的时候使用的C语言标准是C89,C++标准是C++99。

我们来介绍C语言和C++中那些不同的地方。

函数默认值

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

int FUN(int a = 10);

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

我们都知道,系统在调用任何一个函数的时候都有函数栈帧的开辟,如果函数有参数则需要压入实参。平常在我们人为给定实参的时候,是按照参数列表从右向左依次将参数通过以下指令传入寄存器,再通过push指令压入。现在我们已经给定了函数参数的默认值,那么在压实参的时候只需要一步push初始值即可,效率更高。

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

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

intsum1(inta=10,intb);//错误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++支持函数默认值,且需要遵循从右向左赋初始值。

inline内联函数

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

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

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

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

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

函数重载

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

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

静多态:函数重载,函数模板

动多态(运行时的多态):继承中的多态(虚函数)。

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

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

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符号。

引用

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

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对一个立即数进行引用。

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当然无法调用。

作用域

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

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

std::cin<< "123" <

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

我们只要using namespace Myname;就可以将其中所有符号导入了。这也就是我们经常看到的using namespace std;的意思啦。

责任编辑:xj

原文标题:C和C++是同一个东西吗

文章出处:【微信公众号:STM32嵌入式开发】欢迎添加关注!文章转载请注明出处。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • C语言
    +关注

    关注

    180

    文章

    7598

    浏览量

    136186
  • 编程
    +关注

    关注

    88

    文章

    3592

    浏览量

    93594
  • C++
    C++
    +关注

    关注

    22

    文章

    2104

    浏览量

    73489

原文标题:C和C++是同一个东西吗

文章出处:【微信号:c-stm32,微信公众号:STM32嵌入式开发】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Laird Eccosorb吸波材料的应用案例

    抑制Retimer芯片在22GHz的杂讯是一回事,准确提供所需解决方案的理想特征参数又是另外一回事。阅读下文,了解家全球网络技术制造商如何在对比九种不同的仿真结果后,最终选择Laird Eccosorb GDS(经过莱尔德实验
    的头像 发表于 10-24 09:55 185次阅读
    Laird Eccosorb吸波材料的应用案例

    使用Tina ti仿真ths3201时,发生源两路输入则输出不对是怎么回事

    使用Tina ti仿真ths3201时,发生源两路输入则输出不对,单路输入结果却正确。另外,我下载该仿真模型时,模型没有更新,因为更新之后,ths3201无效,无法正常仿真。请问这是怎么一回事呢?
    发表于 08-14 06:21

    数字工厂与智能工厂是一回事

    本文旨在探讨数字工厂与智能工厂的概念、特点、技术应用及其在现代制造业中的作用。 、引言 随着科技的不断进步,制造业正经历着前所未有的变革。数字工厂和智能工厂作为现代制造业的两个重要概念,正逐渐
    的头像 发表于 06-07 15:10 621次阅读

    用STM8L152使用个矩阵键盘,信号直处于低电平状态的原因?

    想用STM8L152 使用个矩阵键盘,根据的是行列扫描的方法,但是代码调试过程中只能检测到列的信号(高低电平)的变化,信号直处于低电平状态,这是怎么一回事?哪里出问题了?
    发表于 05-15 08:31

    电机驱动芯片和电机控制芯片是一回事

    电机驱动芯片和电机控制芯片在电机系统中各自扮演着重要的角色,但它们并不是一回事
    的头像 发表于 04-08 11:15 1494次阅读

    电机和马达是一回事吗 马达和电机有什么区别

    电机和马达是一回事吗 马达和电机有什么区别 电机和马达是一回事吗? 电机和马达是同个名词的不同表达方式。在些地区,特别是中国,人们更倾向于使用“电机”来指代电动机,而在其他地区则更
    的头像 发表于 02-03 09:19 9505次阅读

    SMT生产过程中抛料是怎么一回事呢?具体需要怎么解决?

    在SMT工厂,生产过程中经常会遇到抛料的情况,甚至有时候抛料会非常严重,影响到生产效率,那么抛料是怎么一回事呢?具体需要怎么解决?
    的头像 发表于 01-24 10:42 2774次阅读

    M453VG6AE中ISP Flash和LDROM是不是一回事?它们基地址分别是什么?

    M453VG6AE芯片中 ISP Flash 和 LDROM 是不是一回事?它们基地址分别是什么?
    发表于 01-17 06:31

    密封性和气密性:并非同一回事

    在工程和制造领域,我们经常听到“密封性”和“气密性”这两个词汇,它们似乎有着相似的含义,但实际上并不是同一回事。为了更好地理解这两个概念,我们需要对它们进行些区分。图片来源于网络,侵权可删首先
    的头像 发表于 01-12 11:05 1079次阅读
    密封性和气密性:并非同<b class='flag-5'>一回事</b>

    C++简史:C++是如何开始的

    MISRA C++:2023,MISRA® C++ 标准的下个版本,来了!为了帮助您做好准备,我们介绍了 Perforce 首席技术支持工程师 Frank van den Beuken 博士撰写
    的头像 发表于 01-11 09:00 536次阅读
    <b class='flag-5'>C++</b>简史:<b class='flag-5'>C++</b>是如何开始的

    开关磁阻电机和交流磁阻同步电机是一回事吗?

    大家好,最近项目中遇到款德国的KAISER无励磁磁阻同步电机,花了段时间研究后,还是有几个基本的概念没有搞明白: 1.开关磁阻电机和交流磁阻同步电机是一回事吗? 2.各自的控制器是什么呢?普通的变频器能实现吗?
    发表于 01-10 06:35

    整流和检波是一回事吗?整流二极管和检波二极管能互相代替吗?

    整流和检波是一回事吗?整流二极管和检波二极管能互相代替吗? 整流和检波是属于电子学中的两个不同概念,虽然它们都与二极管有关,但它们的功能和应用场景是不同的。整流和检波二极管也有些共同之处,但它
    的头像 发表于 12-29 11:28 1678次阅读

    学了这些射频圈“黑话”,咱们就是射频人

    射频指标通俗易懂的解释甲乙两人在起交谈,乙说:“你没有说清楚!”甲说:“你怎么听不明白呢?”其实本来说是一回事,听是另一回事;但说得特别清晰可以弥补听力的不足,同样,听的能力特別强,也可以
    的头像 发表于 12-21 08:28 1858次阅读
    学了这些射频圈“黑话”,咱们就是射频人

    请问KVA和KW是不是一回事

    KVA 和KW是不是一回事? 比如负载时2kw那么我的变压器的容量需要大于2/cosφ呢?
    发表于 12-11 07:43

    C语言和C++中那些不同的地方

    C语言虽说经常和C++起被大家提起,但可千万不要以为它们是个东西。现在我们常用的C语言是C
    的头像 发表于 12-07 14:29 907次阅读
    <b class='flag-5'>C</b>语言和<b class='flag-5'>C++</b>中那些不同的地方