天天看点

C++_函数重载&引用&内联函数

文章目录

    • 1、缺省参数
      • 1.1、概念
      • 1.2、分类:
      • 1.3、特性:
    • 2、函数重载
      • 2.1、概念
      • 2.2、调用原理
      • 2.3、底层实现原理
      • 2.4、extern "C"
    • 3、引用
      • 3.1、概念
      • 3.2、特性
      • 3.3、三种传参方式区别
      • 3.4、指针和引用的区别
    • 4、内联函数
      • 4.1、宏常量
      • 4.2、宏函数
      • 4.3、C++如何解决宏的缺陷

1、缺省参数

1.1、概念

   在声明或定义函数时,给函数的参数设置一个默认值,当用户对函数进行调用时,如果传递了实参,则使用用户传递的实参;如果没有传递则使用默认值。

1.2、分类:

  • 全缺省参数:所有参数都带有默认值;
  • 半缺省参数:部分参数带有默认值。规则:必须从右往左依次给出。

1.3、特性:

  1. 缺省参数必须从右往左给出;
  2. 不能在函数声明和定义的位置同时给出;
  3. 缺省参数在提供时—常量||全局变量;
  4. C语言不支持。

2、函数重载

2.1、概念

   相同作用域下,函数名字相同,参数列表必须不同(参数个数、参数类型、类型次序)。

    注意:与函数的返回值类型是否相同没关系。

2.2、调用原理

   编译器在编译代码期间,需要对函数的实参类型进行推演,根据推演的结果选择对应合适类型的函数进行调用。

    注意:有该函数存在,则直接调用,如果不存在类型完全匹配的函数,则编译器会尝试进行隐式类型转换,转换完成后,如果有对应类型的函数,则进行调用,否则:编译失败(没有对应类型、调用二义性)。

2.3、底层实现原理

    C++之所以能够支持函数重载,C语言不支持函数重载,是因为C++编译器和C语言编译器对函数名字的修饰规则不同。

注意:不同编译器对函数名修饰的细节可能不太相同,但大概原理是一样的。

(1)C语言编译器对函数名字的修饰规则:只是在函数名字前加 _

(2)C++编译器对函数名字的修饰规则:C++编译器将参数类型放到最终的名字中。例如:g++的修饰规则【_Z+函数长度+函数名+类型首字母】

2.4、extern “C”

    在C++出现以前,很多代码都是C语言写的,而且很底层的库也是C语言写的,为了更好的支持原来的C代码和已经写好的C语言库,需要在C++中尽可能地支持C,而且在正常开发中,有些人擅长C语言,有些人擅长C++,那完全可能出现:C语言和C++混合起来编程。

    在函数前加“extern “C”,意思就是告诉编译器,将该函数按照C语言规则来编译”

extern "C" int Add(int left, int right);
int main()
{
 Add(1,2);
 return 0; }
           

3、引用

3.1、概念

   引用是一个别名,不是新定义一个变量,编译器不会给引用变量重新分配空间。引用变量与其引用地实体共用同一份内存空间。

3.2、特性

  • 引用变量在定义时必须要初始化;
  • 一个变量可以有多个引用;
  • 引用变量一旦引用一个实体之后,就不能再去引用其它的实体。

3.3、三种传参方式区别

    1、传值:

        优点:可以对外部实参起到保护作用。

        缺点:不能通过形参改变外部的实参,因为形参是实参的一份拷贝,在函数体中,修改形参实际修改的是实参的一份拷贝.

        传参的效率低下,而且浪费空间。

    2、传地址:

        优点:可以通过形参改变外部的实参;不需要对实参进行拷贝,传参的效率高、节省空间。

        缺点:在不需要通过形参改变外部实参的情况下,代码安全性不高。可以避免:const

        指针:不安全,每次在使用时必须要判空,代码的可读性比较差。

    3、传引用:

        形参是实参的别名,对形参进行修改可以达到对实参的改变,对于不需要通过形参改变外部实参通过:const

        传参效率高、节省空间、代码可读性高。

    注意:在C++中,一般情况下传参时尽量使用引用。

内置类型:

  1. 如果需要通过形参改变外部的实参,尽量传递引用;
  2. 如果不需要通过形参改变外部的实参,使用传值、传引用+const

自定义类型:

    传参都是用引用, T& 和 const T&

3.4、指针和引用的区别

    说明:引用和指针在底层实现方式是完全相同的,引用就是按照指针的方式来实现的。

    在概念和特性以及使用方式上的区别:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址;
  2. 引用在定义时必须初始化,指针没有要求;
  3. 引用在初始化时引用一个实体后,就不能再引用其它实体,而指针可以在任何时候指向任何一个同类型实体;
  4. 没有NULL引用,但有NULL指针;
  5. 在sizeof中含义不用,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节);
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
  7. 有多级指针,但没有多级引用;
  8. 访问方式不同,指针需要解引用,引用编译器自己处理;
  9. 引用比指针使用起来相对更安全。

4、内联函数

4.1、宏常量

    优点:一改全改,代码的可维护性高;常量名字具有一定的含义—#define MAX_SIZE 100

    缺点:宏常量没有类型的,不会参与到类型检测中,代码的安全性降低,而且一旦编译报错,报错的位置不准确。

4.2、宏函数

    优点:宏函数不是一个真正的函数,在预处理阶段,预处理器已经将宏函数进行替换了,少了函数调用参数压栈,开辟栈帧,返回等的开销了,代码的运行效率提高。

    缺点:

  1. 在实现时,可能会比较麻烦,要到处加括号;
#define MUL(A, B) A*B
int main()
{
 int a = 1, b = 2, c = 3, d = 4;
 cout<< MUL(a+b, c+d)<<endl; // 预处理阶段被替换成:a+b*c+d
 return 0; }
           
  1. 宏函数也没有参数类型,不会有参数类型检测,安全性不高;
  2. 宏函数在预处理阶段会展开,不能调试;
  3. 宏函数具有副作用。
#define MAX(A, B) (((A) > (B))? (A) : (B))
int main()
{
 int a = 10, b = 20;
 cout<<MAX(++b, a)<<endl; // 期望输出21,但实际输出的是22,因为直接替换后,++b算了两次
 return 0; }
           

4.3、C++如何解决宏的缺陷

1、宏常量:const常量,可以达到宏替换的效果,而且具有类型,更加安全。

int main()
{
 const int a = 10;
 
 int* pa = (int*)&a; 
 *pa = 100; // 此处已经将a的值修改为100
 cout << *pa << endl; // 打印100
 cout << a << endl; // 打印10,因为在编译阶段,常量a被替换为10
 return 0; }
           

2、宏函数:解决方案—内联函数

    内联函数:在C++中被inline关键字修饰的函数称为内联函数;如果成员函数在类中定义,编译器也可能会将其当成内联函数来处理。

    特性:

  • inline是一种空间换时间的做法,省去调用函数额外开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数;
  • 在编译阶段,如果编译器将一个函数当成是内联函数的情况下,在编译代码时会对内联函数进行展开,少了函数调用的开销,程序的运行效果提高了;
  • inline是一个建议性的关键字,当修饰函数时,建议编译器将该函数当成内联函数来进行处理,即:在编译阶段,将该函数展开。如果定义为内联函数体内有循环/递归等等,编译器优化时会忽略掉内联;
  • inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到;
  • inline函数具有文件作用域

继续阅读