c和c++的区别先总结4点
1. 函数的默认值
C++可以给函数定义或声明时给参数赋初值
(c语言后来制定的c99标准也能给默认值,但由于现在普遍学习的是c89标准,因此没有此功能)
使用默认值的好处是什么?
使用默认值的原理又是什么?
使用默认值有何规则限定?请接着看。
比如main函数先定义俩变量
int a;
int b;
然后调用sum(a,b)的时候
压入参数的时候的时候执行的汇编就是
Move eax,dword ptr[ebp-8]
Push eax
Move ecx,dword ptr[ebp-4]
Push ecx
可以看出压入参数的顺序先b再a的。是从右往左的
如果在定义或者声明的时候给函数的参数了默认值,再或者调用时直接参数给了立即数
比如 Int sum (int a,int b=10);这样
到时候执行main的调用sum这行的汇编就会变成
Push 0Ah
Move ecx,dword ptr[ebp-4]
Push ecx
可以看到少了一行汇编指令,直接把默认值作为立即数压入。
无疑是提高了效率
那么使用默认值的规则是什么。
刚才从原理上看到了,参数压栈的顺序在底层的汇编体现是从右往左压的。
因此默认值的的给定也必须从右往左给,可以少给,但不可以跳着给。
比如sum2(int a,int b,int c);这个函数可以
sum2(int a,int b,int c=3);或
sum2(int a,int b=2,int c=3);或
sum2(int a=1,int b=2,int c=3);或
其中任意一种给发。
但绝不能这样给
sum2(int a=1,int b,int c);或
sum2(int a=1,int b,int c=3);或
sum2(int a,int b=2,int c);或
为什么不能以上这些给发
比如sum2(int a,int b=2,int c);这样给。是不是以为着调用的时候
必须这样sum2(1,,3);这显然违反语法的。
刚只是用声明举例。
然而实际上是可以声明和定义都可以给,但不能给重复,一个参数的默认值只能给一次
例如
Int sum(int a,int b=20);
Int sum(int a=10,int b);
两次声明是可以的。
单独来看并不符合Int sum(int a=10,int b);刚说的从右至左的给默认值规则。
但由于它的上一行存在Int sum(int a,int b=20);已对b给过默认值了。因此再只给a默认值并不违法。
如果
Int sum(int a,int b=20);
Int sum(int a,int b=20);
这样是不可以的
最后值得注意的是,刚才说的都是声明与定义在main函数上方,因为编译时的顺序是一行一行从上往下读的,如果这样给定默认值,显然也是不对的。
例如
Int sum2(int a,int b,int c=3);
Main()
{
....
Sum2(1)
...
}
Sum2(int a,int b=2,int c);
2. 内敛函数inline
内联函数是在编译时 在调用点把内联函数的代码展开在调用点处,并不会生成函数的符号。
例如
Inline Int sum (int a,int b)
{return a+b;}
Main()
{
Int x=10;
Int y=20;
Sum(x,y);
}
编译时就是
Main()
{
Int x=10;
Int y=20;
x+y;
}
并不会给sum函数开辟个新栈,而是直接的代码替换。
这么看来内联函数和宏非常的相似。那么既然内敛叫内敛,宏叫宏,那么肯定有区别所在。
区别就是 宏是单纯的字符替换,在预编译的阶段,不会做任何的词法解析,类型检查,也就是说宏出错的可能性非常高,不安全。而inline内敛函数是在编译时期,会进行词法解析,类型检查,词法、类型有误就会编译失败。
所以说,inline相当于安全版的宏。
那么既然内敛函数名字中有个函数,那么它和函数有是否有本质区别呢?
答案是肯定的。
首先一个是在调用方的代码替换,一个是另开辟新栈的相对独立执行。
其次一般函数会生成符号,而内敛函数本质来讲只是个安全宏,所以不会生成符号。
那么既然内敛函数这么安全,也不用开辟新栈效率高,为什么不定义所有的函数为内敛函数呢?
首先内敛函数只对本文件可见,且编译时不生成符号,这对于跨文件的工程来说,显然是不好的。
其次,如果多次使用内联函数,将进行大量的代码替换,造成.txt段过于冗余。
最后我们说的效率高,是在函数的执行开销小于栈帧的开辟开销的情况下成立的。
也就是说假如代码只有简单的短短几行,用内敛的确能提高效率。
但如果函数代码过于复杂,内敛的效率还是没有一般函数高的。
最后值得注意的是内敛函数只算一个给编译器的建议,也就是说编译器可能不会使用内敛,因为某些函数使用代码替换会导致错误,比如递归函数,递归的核心就是开辟栈帧递归数据然后层层计算最后收尾,简单的代码替换怎么能确定递归的尾巴?
并且inline只在release版本生效,debug版本是不启作用的。
3.函数的重载
c语言中函数的定义不能重名,如果重名就会出现重定义,因为函数生成的符号只由函数名决定。
但是c++中的函数定义可以重名,但是重名的前提是函数的参数不能完全相同,因为c++的函数符号是由函数名和参数类型决定的。
我们都知道在不同作用域可以定义相同名字的变量。比如main中的局部变量和本文件的全局变量就可以同名。并且main中优先使用的是离自己作用域最近局部变量。
int sum(int a,int b);
int main()
{
int sum(char *a,char *p);
sum(1,2);
return 0;
}
例如这样就会出错,sum(1,2)只会看到int sum(char *a,char *p);
而无法看到int sum(int a,int b);
所以说函数重载必须在同一个作用域内。
还有一个点就是实参和形参的转化问题。
我们先回一下c语言的类型转换
箭头所指的线路都是编译器默认会选择的优先转向。
比如float类型变量参与运算时,都会默认转换成double型,然后参与运算,目的是为了提高精度。
举例
比如short a=-1;和 usigned int b=1;比较 。
看似b比较大吧,但其实结果我们知道的,a和b比较时结果却是a比较大,这是为什么?我们看a>b 这个式子,a是short,b是u_int ,按道理a要先转化成int型,这时是仍为-1,然后发现b是usigned,继续转化为usigned,然而usigned中不可能有-1,我们知道-1应该是很大的数,即2^32-1 。所以a>b这个式子其实最后值是1,即a比b大。
可是如果不是箭头所指呢,即我定义的函数形参是float,然后重载一个形参为int的。
最后我传入实参为double型,编译器就不知道double是该优先转成float还是int,因此就会有问题。
4. c和c++函数互相调用
之前也说了,c和c++的函数生成符号不一样,那么c++和c在互相调用对方函数的时候就会出错,因为根本无法找到对应的符号。
而由于c++是基于c语言而来,肯定会想到兼容性的问题,因此如果c++想要使用c的函数,c++会有特殊处理,可以把函数符号转化成c的不带参数的形式,只用在c++文件里这样
Extern ”C”
{
Int sum(int ,int )
}
这样编译时同时也会生成c语言版本的函数符号,到时候c++想要使用c中定义的函数也可以找到符号。
但是反过来呢,如果c想要使用c++文件里定义的函数呢。c可是不会兼容c++的,c可不能在自己文件里使用extern”c++”这种东西。
那么该怎么做呢?
其实可以创建一个接口文件。
比如这是cpp文件
Sum.cpp
Int sum(int a,int b)
{
Return a+b;
}
这是.c文件
Main()
{
Int a=10;
Int b=20;
sum(a,b);
}
目前来看,.c文件是绝对使用不了.cpp里的sum定义的。
那么这时就要新建一个mid.cpp接口文件了
这个文件内容如下
Mid.cpp
Int sum(int a,int b)
extern”C”
{
Mysum(int a,int b)
{
return sum(a,b);
}
}
首先mid文件中先声明了sum.cpp中sum函数,由于都是cpp文件,因此sum对于mid是可见的,那么mid就可以看到sum的定义体,那么接下来mid文件只要把这个sum函数封装一下,并且用extern”C”生成C符号的形式封装出一个函数,这个函数的功能和sum完全一样,参数一样,返回值也一样,这不就能让c使用c++的函数了吗