一.函数默认值 c 支持给函数的形式参数进行默认初始化,其规则为从右向左依此初始化。
还有以下需要注意的几点: 1.定义处可以不给出形参的默认值,在声明处可以给出形参的默认值。 2.声明处形参默认值给出要符合以上规则。 3.不能重复给形参默认值进行初始化,即一个形式参数只能初始化一次。
代码语言:javascript复制#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);//错误,不符合规则
函数默认值存在的意义是什么?对比无默认值和带默认值在汇编上的区别 不带默认值测试代码:
代码语言:javascript复制#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 回退形参内存 带默认值测试代码:
代码语言:javascript复制#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修饰能否构成重载?
代码语言:javascript复制void fun(int a){;}
void fun(const int){;}
int main(){return 0;}
不能构成重载
代码语言:javascript复制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程序接口,这样就需要提供这样相互调用机制。
代码语言:javascript复制extern "C" //告诉编译器里边的符号是按照c规则生成
{
;
}
前面已经谈到,由于c和c 生成符号的规则不相同。如果一致,才能调用。举两个例子,谈谈其用法。
(1)c 程序调用c程序 两个源文件分别为main.cpp和sum.c,其中sum.c中包括sum函数的实现,而在main.cpp调用它
代码语言:javascript复制#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 程序相对复杂。