> 文章列表 > C++函数特性全面解析:缺省参数、函数重载以及编译过程

C++函数特性全面解析:缺省参数、函数重载以及编译过程

C++函数特性全面解析:缺省参数、函数重载以及编译过程

目录

一、c++缺省参数

二、和C语言的区别

三、函数的重载

四、函数重载的原理

五、附言(预处理和编译、汇编、链接)

1.预处理阶段

2.编译阶段

3.汇编阶段

4.链接阶段


一、c++缺省参数

C++中,缺省参数(Default Argument)可以在函数声明和定义时指定一个默认值,这样在函数调用时,如果没有提供对应的实参,则使用默认值作为实参。如果提供了对应的实参,则使用提供的实参值。

语法格式如下

返回类型 函数名(参数列表 = 默认值) {// 函数体
}

其中,默认参数可以是任何合法的表达式,可以使用字面值、变量、常量等。

例如,以下函数定义了一个具有两个参数的函数print,第二个参数b含有默认值10:

void print(int a, int b = 10) {std::cout << "a = " << a << ", b = " << b << std::endl;
}

当我们调用该函数时,如果提供了第二个参数的值,则使用提供的值;如果没有提供第二个参数的值,则使用缺省的值(即10)。

例如:

// 调用print函数,提供了第二个参数的值
print(1, 20); // 输出:a = 1, b = 20// 调用print函数,没有提供第二个参数的值
print(2); // 输出:a = 2, b = 10

需要注意的是,对于函数声明和函数定义中都设置了缺省参数的函数,只有函数声明中设置的缺省参数才会生效。因此,在不同的文件中使用同名函数时,需要保证它们的函数声明相同。

二、和C语言的区别

在C语言中,没有缺省参数的概念。因此,在调用函数时,必须提供所有的实参,否则将会导致编译错误。

而在C++中,允许在函数声明和定义时为某些形参指定缺省值,这样在函数调用时,如果调用者没有传递对应的实参,则使用缺省值作为该形参的值。这个特性可以简化函数的调用,并使得代码更加灵活。

除了缺省参数外,C++还支持函数重载(Function Overloading)的特性。函数重载是指在同一个作用域内,允许定义相同名称但参数列表不同的多个函数,这些函数被称为重载函数(Overloaded Functions)。在调用函数时,编译器会根据实参的类型和数量来选择合适的重载函数进行调用。

三、函数的重载

函数的重载(Function Overloading)是指在同一个作用域内,可以定义多个相同名称但参数列表不同的函数。这些函数被称为重载函数(Overloaded Functions)。在调用函数时,编译器会根据实参的类型和数量来选择合适的重载函数进行调用。

函数的重载可以使代码更加简洁、灵活,并且提高了代码的可读性。常见的场景包括:

  1. 处理不同类型的数据:例如,可以使用函数重载来处理不同的基本类型、结构体、类等数据类型,而不必为每种类型都定义一个不同的函数名。

  2. 支持不同的操作方式:例如,可以使用函数重载来实现不同的算法、数据操作、输出格式等操作方式。

  3. 提供不同的接口:例如,可以使用函数重载来提供不同的接口,方便用户根据实际需要来选择使用哪种接口。

例如,以下是一个简单的函数重载的例子:

#include <iostream>// 输出整数
void print(int num) {std::cout << "整数:" << num << std::endl;
}// 输出浮点数
void print(double num) {std::cout << "浮点数:" << num << std::endl;
}int main() {print(10);     // 调用print(int)函数print(3.1415); // 调用print(double)函数return 0;
}

需要注意的是,函数重载的规则是,函数名称必须相同,但参数列表必须不同。参数列表可以包括参数的类型、数量、顺序以及是否有缺省值等信息。对于函数重载的使用者而言,只需要根据实参的类型和数量来调用合适的函数即可。

除了 C++,许多编程语言也支持函数重载的特性。以下是一些常见编程语言的函数重载支持情况:

  1. Java:Java 支持函数重载,允许在同一类中定义多个名称相同但参数列表不同的方法。和 C++ 一样,Java 使用函数签名来区分不同的方法,并根据实参类型和数量来选择合适的方法进行调用。

  2. Python:Python 不直接支持函数重载的特性,因为 Python 函数中的参数类型不需要指定。但是,可以通过参数默认值、可变参数等方式来实现类似函数重载的功能。

  3. JavaScript:JavaScript 不支持函数重载,因为 JavaScript 中的函数参数是动态类型的。当定义多个同名函数时,最后一个函数定义会覆盖前面的定义,只有最后一个函数能够被调用。

  4. C#:C# 支持函数重载,和 Java 类似,使用函数签名来区分不同的方法。和 C++ 不同的是,C# 不允许参数具有缺省值。

  5. Ruby:Ruby 支持函数重载,但和 Python 一样,由于 Ruby 函数参数类型不需要指定,函数重载的判断是基于参数数量和类型的。

需要注意的是,虽然不同编程语言对函数重载的实现可能有所不同,但其核心原理都是相同的,即根据函数签名和实参的类型、数量等信息来选择合适的函数进行调用。

四、函数重载的原理

函数重载的原理是,编译器在函数调用时根据实参的类型和数量来判断应该调用哪个重载函数。编译器对函数名进行标记(Mangling)以区分不同的函数版本,每个重载函数都有一个特定的函数签名(Function Signature),该函数签名包括函数名和参数列表的信息。

例如,以下是两个函数版本的函数签名:

  • print(int)
  • print(double)

在编译时,编译器会使用函数签名来生成唯一的函数名,以便在链接时正确地将函数调用与相应的函数定义关联起来。这个过程称为名称修饰(Name Decoration)或函数符号化(Function Symbolization)。

当程序调用一个重载函数时,编译器将根据实参的类型和数量来选择合适的重载函数。如果找到了一个完全匹配的重载函数,则直接调用该函数;否则,编译器会尝试进行隐式类型转换(Implicit Type Conversion)或选择最佳匹配(Best Match)的重载函数进行调用。如果仍然无法确定要调用哪个重载函数,则会导致编译错误。

需要注意的是,函数重载的特性只针对函数,不能用于变量、类成员等其他标识符。此外,过多的函数重载可能会导致代码可读性下降,因此需要谨慎使用。

五、附言(预处理和编译、汇编、链接)

预处理、编译、汇编、链接是将源代码转换成可执行程序的四个主要阶段。下面我们逐一介绍这四个阶段的作用。

1.预处理阶段

在这个阶段,预处理器(Preprocessor)会根据源代码中的预处理指令修改原始代码,并生成一个新的修改后的代码文件。预处理器的任务包括:

  • 处理 #include 指令,将头文件插入到源代码中;
  • 处理 #define 指令,进行宏替换;
  • 处理 #if、#ifdef、#ifndef 等条件编译指令;
  • 处理 #pragma 指令等。

处理后的代码包含宏替换后的内容和被插入的头文件内容。预处理生成的文件通常被命名为 .i 文件。

2.编译阶段

在这个阶段,编译器(Compiler)读取预处理生成的文本文件(通常是 .i 文件),并把它们翻译成汇编语言的形式。编译器的任务包括:

  • 词法分析,将输入的文本流分解成符号(Token)序列;
  • 语法分析,根据语法规则确定输入的语句是否正确;
  • 中间代码生成,生成中间代码;
  • 优化,对中间代码进行优化以提高代码执行效率;
  • 目标代码生成,最后将代码转换成具体的机器语言代码。编译器生成的文件通常被命名为 .s 文件。

3.汇编阶段

在这个阶段,汇编器(Assembler)将汇编语言代码转换成机器语言代码。每种机器都有自己的汇编语言,因此汇编器是特定于机器的。汇编器的任务包括:

  • 识别汇编语言中的符号和指令;
  • 产生机器语言代码;
  • 将机器语言代码写入目标文件中。汇编器生成的文件通常被命名为 .o 文件。

4.链接阶段

在这个阶段,连接器(Linker)将不同的目标文件(即 .o 文件)和库文件链接到一起,形成一个单独的可执行文件。链接器的任务包括:

  • 符号解析,将符号(Symbol)引用与符号定义关联起来;
  • 重定位,将各个目标文件在内存中的位置确定下来;
  • 跳转目标地址修正,对代码中的跳转地址进行修正。

最终的可执行文件包含程序的二进制代码和所需的其他资源,可以直接运行。