c和c++的区别 (一)函数默认值、内联函数、函数的重载和c/c++之间的相互调用

一.函数默认值
c++支持给函数的形式参数进行默认初始化,其规则为从右向左依此初始化。

还有以下需要注意的几点:
1.定义处可以不给出形参的默认值,在声明处可以给出形参的默认值。
2.声明处形参默认值给出要符合以上规则。
3.不能重复给形参默认值进行初始化,即一个形式参数只能初始化一次。

#include<iostream>
using namespace std;

//以下的两组声明是正确的
int sum(int a,int b=10);
int sum(int a=10,int b);//由于上一个声明已经将b初始化为10,所以符合从右向左依此初始化的规则

int sum(int a=10,int b);//错误,不符合规则

函数默认值存在的意义是什么?对比无默认值和带默认值在汇编上的区别
不带默认值测试代码:

#include<iostream>
using namespace std;

int sum(int a,int b)
{
    return a+b;
}
int main()
{
    int a = 10;
    int b = 30;
    sum(a,b);
    return 0;
}

我们都知道如果调用不带默认值的sum函数的第一步是压实参

mov eax,dword ptr[ebp-8]
push eax

mov ecx,dword ptr[ebp-4]
push ecx
call sum
add esp,8 回退形参内存
带默认值测试代码:

#include<stdio.h>

int sum(int a,int b=20)
{
    return a+b;
}

int  main()
{
    int a = 10;
    sum(a);
    return 0;
}


push 14h

mov ecx,dword ptr[ebp-4]
push ecx
call sum
add esp,4

结论:对比两者在汇编上的区别,对有函数默认值的函数在汇编语言表现为减少一个mov的指令周期看似比较短,但如果在一个大型项目中上万次调用这样的带有默认值的函数,其优势就体现出来了,大量减少了代码的执行时间,使得代码的效率提高。这就是函数默认值存在的意义!

二.内联函数

1.内联函数是在调用点,将函数的代码全部展开,并且这个过程是在编译阶段进行的。

2.内联函数只在编译器的release版本下起作用,而debug版本无效,还是会有函数栈帧的开辟和回退。其目的是方便程序员调试

3.内联函数实际上只是程序员对编译器的一种建议,其建立的基础是当调用函数函数的开销 > 执行函数的开销时,处理成内联函数是更加高效。但实际上如递归函数是不可能被处理成内联函数的。因为递归函数调用的次数只有在执行完毕才能确定,而内联函数的处理实在编译阶段根据上述规则进行处理的。而递归函数没有给编译器提供这样的规则。


内联函数和宏函数的区别?
内联函数和static函数的区别?

从三个角度分析

函数类型 作用域 符号的产生 栈帧的开辟和回退
内联函数 当前文件可见 不产生符号 没有标准的栈帧开辟和回退
static函数 当前文件可见 产生local的符号,链接器不做处理
宏函数 当前文件可见 不产生符号
普通函数 示具体作用域 产生global的符号,链接器进行处理
#include<iostream>
using namespace std;

static void print()
{
    cout<<"hello"<<endl;
} 

int sum(int a,int b)
{
    return a+b;
}

int main()
{
    return 0;
}

使用objdump -t test.o查看上述代码产生的符号:
这里写图片描述
可以看到static函数和普通函数生成的符号分别为local属性和global属性的。

三.函数的重载

在C语言中,符号的生成仅仅由函数名称决定。我们都知道,如果在同一个项目如果两个函数的函数名称相同,那么编译器在链接会报错。如在a.c和b.c中实现如下的两个同名的函数:
这里写图片描述
但是在c++中却支持这样的机制。为什么不会报出链接错误呢?


在一个项目,有许多源文件。每个源文件独立的进行编译,生成符号。链接的核心就是符号的重定位,即在符号 引用的地方找到符号定义的地方,这时候发现符号相同,因此产生了链接错误。而在c++当同名称函数产生的符号也是不相同的。


c++函数符号的生成:函数名+参数列表(参数个数+参数类型+参数顺序)

下面验证一下在c++中重载函数产生的符号:
这里写图片描述
使用objdump -t test.o查看生成的符号表
这里写图片描述
可以看到在c++中符号的组成是由函数名称和参数列表共同决定的。

函数参数被cosnt修饰能否构成重载?

void fun(int a){;}
void fun(const int){;}
int main(){return 0;}

这里写图片描述
不能构成重载

void fun(int *a){;}
void fun1(const int* a){cout<<"fun(const int* a)"<<endl;}
void fun1(int* const a){cout<<"fun(int* const a)"<<endl;}
int main(){
    int a=10;
    const int *p = &a;
    int* const q=&a;
    fun(p);
    fun(q);
return 0;}

这里写图片描述

因此,可以得出构成重载的条件:
1.函数名称相同。
2.参数列表不同。
3.不能以返回值不同作为判断重载的条件,因为返回值类型符符号的生成无关。
4.对实参的值是否有影响,如被const/volatile修饰的*(指针)/&(引用)可以作为函数重载的前提条件。

最重要的一点,构成重载的函数必须在同一作用域!

四.c和c++之间相互调用

在实际的应用当中,有时候会发生这样的事情,c程序可能需要调用一些优秀的c++程序的接口,而在c++程序中也可能需要调用优秀的c程序接口,这样就需要提供这样相互调用机制。

extern "C" //告诉编译器里边的符号是按照c规则生成
{   
    ;
}

前面已经谈到,由于c和c++生成符号的规则不相同。如果一致,才能调用。举两个例子,谈谈其用法。

(1)c++程序调用c程序
两个源文件分别为main.cpp和sum.c,其中sum.c中包括sum函数的实现,而在main.cpp调用它

#include<iostream>
using namespace std;

int sum(int a,int b);//在main.cpp生成的符号为 sum_int_int    而在sum.c中生成sum
这样必然导致链接错误,找不到sum_int_int

extern "C"
{
    int sum(int a,int b);//在main.cpp生成的符号为sum和在sum.c中生成的符号sum一致
    //这样链接进行符号重定位的时候,在符号引用的地方找到符号定义的地方,不会报出链接
    //错误
}

(2)c程序调用c++程序
由于没有extern “c++”这样的机制,实际上c程序调用c++程序相对还是比较麻烦的,需要在c++源文件在每一个可能被c程序调用的函数外加extern “C”以生成c程序可以识别的符号。

test.cpp

extern "C"
{
    int sum(int a,int b)//生成的符号为sum,而不是sum_int_int
    {
        return a+b;
    }
}

main.c

int sum(int a,int b);//sum函数声明,生成的符号为sum
int main()
{
    sum(a,b);//调用
    return 0;
}


可见,上述的处理不会引起链接错误。但由于其实际应用非常麻烦,现在大多采用的是动态链接库和静态链接库。

综上:c++程序调用c程序相对简单,而c程序调用c++程序相对复杂。

猜你喜欢

转载自blog.csdn.net/ASJBFJSB/article/details/80787656