1. 内联函数(inline function)

在c中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。

但是在c++出现之后,使用预处理宏会出现两个问题:

  1. 第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。
  2. 第二个问题是c++特有的,预处理器不允许访问类的成员,也就是说预处理器宏不能用作类类的成员函数。

为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,c++引入了内联函数(inline function)。内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。

1.1 预处理宏的缺陷

问题一:

  1. #define ADD(x,y) x+y
  2. inline int Add(int x,int y){
  3. return x + y;
  4. }
  5. void test(){
  6. int ret1 = ADD(10, 20) * 10; //希望的结果是300
  7. int ret2 = Add(10, 20) * 10; //希望结果也是300
  8. cout << "ret1:" << ret1 << endl; //210
  9. cout << "ret2:" << ret2 << endl; //300
  10. }

问题二:

  1. #define COMPARE(x,y) ((x) < (y) ? (x) : (y))
  2. int Compare(int x,int y){
  3. return x < y ? x : y;
  4. }
  5. void test02(){
  6. int a = 1;
  7. int b = 3;
  8. //cout << "COMPARE(++a, b):" << COMPARE(++a, b) << endl; // 3
  9. cout << "Compare(int x,int y):" << Compare(++a, b) << endl; //2
  10. }

问题三:
预定义宏函数没有作用域概念,无法作为一个类的成员函数,也就是说预定义宏没有办法表示类的范围。

1.2 内联函数基本概念

在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。

在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。但是必须注意必须函数体和声明必须同时加inline,否则编译器将它作为普通函数来对待。

  1. inline void func(int a);
  2. inline void func(int a){return ++;}

内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压栈,跳转,返回的开销。我们可以理解为内联函数是以空间换时间

为了定义内联函数,通常必须在函数定义前面放一个inline关键字。但是在类内部定义内联函数时并不是必须的。任何在类内部定义的函数自动成为内联函数

1.3 内联函数和编译器

内联函数并不是何时何地都有效,对于任何类型的函数,编译器会将函数类型(包括函数名字,参数类型,返回值类型)放入到符号表中。同样,当编译器看到内联函数,并且对内联函数体进行分析没有发现错误时,也会将内联函数放入符号表。

2. 函数参数

2.1 函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的。
语法:ReturnType FunctionName ([ElementType parameter1 = DefaultValue1,...]){}
示例:

  1. #include<iostream>
  2. using namespace std;
  3. int func(int a, int b = 10, int c = 10) {
  4. return a + b + c;
  5. }
  6. //1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
  7. //错误:int funcx(int a = 10, int b);
  8. //2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
  9. int func2(int a = 10, int b = 10);
  10. int func2(int a, int b) {
  11. return a + b;
  12. }
  13. int main() {
  14. cout << "ret = " << func(20, 20) << endl;
  15. cout << "ret = " << func(100) << endl;
  16. return 0;
  17. }

运行结果:
image.png

总结:

  • 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值

错误写法:int funcx(int a = 10, int b)

  • 函数声明与函数实现时只能有一个默认参数,不然会导致二义性

2.2 函数占位参数

注意:

  • C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
  • 占位参数还可以有默认参数

语法:ReturnType FunctionName ([ElementType = DefaultValue1,...]){}
示例:

  1. #include<iostream>
  2. using namespace std;
  3. //函数占位参数 ,占位参数也可以有默认参数
  4. void func1(int a, int) {
  5. cout << "this is func1" << endl;
  6. }
  7. void func2(int a, int = 20) {
  8. cout << "this is func2" << endl;
  9. }
  10. int main() {
  11. func1(10,10); //占位参数必须填补
  12. func2(10);
  13. return 0;
  14. }

3. 函数重载

3.1函数重载基础

作用:函数名可以相同,提高复用性
函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同 或者 个数不同 或者 顺序不同

注意: 函数的返回值不可以作为函数重载的条件
示例:

  1. #include<iostream>
  2. using namespace std;
  3. //函数重载需要函数都在同一个作用域下
  4. void func()
  5. {
  6. cout << "func 的调用!" << endl;
  7. }
  8. void func(int a)
  9. {
  10. cout << "func (int a) 的调用!" << endl;
  11. }
  12. void func(double a)
  13. {
  14. cout << "func (double a)的调用!" << endl;
  15. }
  16. void func(int a ,double b)
  17. {
  18. cout << "func (int a ,double b) 的调用!" << endl;
  19. }
  20. void func(double a ,int b)
  21. {
  22. cout << "func (double a ,int b)的调用!" << endl;
  23. }
  24. //函数返回值不可以作为函数重载条件
  25. //int func(double a, int b)
  26. //{
  27. // cout << "func (double a ,int b)的调用!" << endl;
  28. //}
  29. int main() {
  30. func();
  31. func(10);
  32. func(3.14);
  33. func(10,3.14);
  34. func(3.14 , 10);
  35. return 0;
  36. }

运行结果:
image.png

3.2 引用作为重载条件

示例:

#include<iostream>
using namespace std;

//1、引用作为重载条件
void func(int& a)
{
    cout << "func (int &a) 调用 " << endl;
}

void func(const int& a)
{
    cout << "func (const int &a) 调用 " << endl;
}

int main() {

    int b = 10;

    //若b为const int 类型,则调用func(const int& a),
    //若b为int 类型,则调用func(int& a)
    func(b);

    //只能调用后者,前者int& a = 120 有语法错误! 
    func(120);//调用有const

    return 0;
}

运行结果:
image.png

3.3 函数重载碰到函数默认参数

示例:

#include<iostream>
using namespace std;

void func2(int a, int b = 10)
{
    cout << "func2(int a, int b = 10) 调用" << endl;
}

void func2(int a)
{
    cout << "func2(int a) 调用" << endl;
}

int main() {

    //func2(10); //碰到默认参数产生歧义,需要避免
    func2(10, 10);//调用前者
    return 0;
}

运行结果:
image.png

3.4 函数重载实现原理

编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数类型来修饰不同的函数名,比如void func(); 编译器可能会将函数名修饰成_func,当编译器碰到void func(int x),编译器可能将函数名修饰为_func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char我这里使用”可能”这个字眼是因为编译器如何修饰重载的函数名称并没有一个统一的标准,所以不同的编译器可能会产生不同的内部名。

void func(){}
void func(int x){}
void func(int x,char y){}

// 以上三个函数在linux下生成的编译之后的函数名为:
_Z4funcv //v 代表void,无参数
_Z4funci //i 代表参数为int类型
_Z4funcic //i 代表第一个参数为int类型,第二个参数为char类型

3.5 extern “C”浅析

以下在Linux下测试:
c函数: void MyFunc**(){}** ,被编译成函数: MyFunc
c++函数: void MyFunc**(){}**,被编译成函数: _Z6Myfuncv

通过这个测试,由于c++中需要支持函数重载,所以c和c++中对同一个函数经过编译后生成的函数名是不相同的,这就导致了一个问题,如果在c++中调用一个使用c语言编写模块中的某个函数,那么c++是根据c++的名称修饰方式来查找并链接这个函数,那么就会发生链接错误,以上例,c++中调用MyFunc函数,在链接阶段会去找Z6Myfuncv,结果是没有找到的,因为这个MyFunc函数是c语言编写的,生成的符号是MyFunc。
那么如果我想在c++调用c的函数怎么办?
extern “C”的主要作用就是为了实现c++代码能够调用其他c语言代码。加上extern “C”后,这部分代码编译器按c语言的方式进行编译链接,而不是按c++的方式。

#include<stdio.h>

#if __cplusplus      // __cplusplus
extern "C"{
#endif


    void func1();
    int func2(int a,int b);


#if __cplusplus
}
#endif
#include"MyModule.h"

void func1(){
    printf("hello world!");
}
int func2(int a, int b){
    return a + b;
}
#include<iostream>
using namespace std;

#include"MyModule.h"  

int main(){
    func1();
    cout << func2(10, 20) << endl;
    return EXIT_SUCCESS;
}

在c文件中定义好宏以后,就可以在cpp中正常使用