C++的内联函数、函数重载、函数的默认参数与占位参数
- 引言
- 一、内联函数
- 1.1、声明内联函数
- 1.2、宏函数和内联函数的区别
- 1.3、内联函数的注意事项
- 二、函数重载
- 2.1、函数重载的概述
- 2.2、函数重载的条件
- 2.3、函数重载的底层实现原理
- 三、函数的默认参数
- 四、占位参数
- 五、extern "C"浅析
- 总结
引言
💡 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
👉
🎖️ CSDN实力新星,社区专家博主
👉
🔔 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、高级编程;掌握各个知识点。
👉
🔔 专栏地址:C++从零开始到精通
👉
🔔 博客主页:https://blog.csdn.net/Long_xu
🔔 上一篇:【025】C++对C的扩展之引用(reference)详解
一、内联函数
C++中的内联函数是一种特殊类型的函数,它在编译时会被直接嵌入到调用它的代码中,而不是像普通函数那样进行函数调用。这种方式可以减少函数调用的开销,提高程序的运行效率。
以下是定义内联函数的语法:
inline 返回类型 函数名(参数列表) {
// 函数体
}
其中,关键字“inline”表示这个函数是一个内联函数。需要注意的是,只有短小精悍的代码才适合使用内联函数。如果内联函数过于复杂,则可能会导致代码膨胀和可读性下降。
另外,在使用类成员函数时,默认情况下编译器会将其视为内联函数。因此,在类定义中声明和实现成员函数时不需要加上“inline”关键字。
1.1、声明内联函数
内联函数必须在定义的时候使用关键字inline修饰,不能在声明的时候使用inline。
// 函数声明时不要使用inline关键字
int mAdd(int x,int y);
int main()
{
cout<<mAdd(100,200)<<endl;
}
// 内联函数在定义的时候使用inline
inline mAdd(int x,int y)
{
return x+y;
}
内联函数 在编译阶段 将内联函数中的函数体替换函数调用处,避免函数调用时的开销。
1.2、宏函数和内联函数的区别
宏函数和内联函数都会在适当的位置进行展开,避免函数调用开销。
- 宏函数在预处理阶段展开,内联函数在编译阶段展开。
- 宏函数的参数没有类型,不能保证参数的完整性;内联函数有参数类型,能保证参数的完整性。
- 宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员;内联函数有作用域的限制,能作为命名空间、结构体、类的成员。
1.3、内联函数的注意事项
(1)在内联函数定义的时候加inline修饰。
(2)类中的成员函数默认 都是内联函数(不加inline也是内联函数)。
(3)有时候就算加上inline也不一定是内联函数,能不能成为内联函数有以下的内联函数条件决定:
- 不能存在任何形式的循环语句;
- 不能存在过多的条件判断语句;
- 函数体不能过于庞大;
- 不能对函数取地址。
(4)有时候不加inline修饰也有可能是内联函数。
(5)是不是内联函数 由编译器决定。
也就是说,我们添加inline修饰只是希望这个函数是内联函数,但是能不能成为内联函数由编译器决定。
二、函数重载
2.1、函数重载的概述
C++函数重载是指在一个类中定义多个同名函数,这些函数的参数列表不同(参数数量、类型或顺序等),以便根据调用时传递的实参来选择合适的函数进行调用。其实现原理是通过编译器对每个重载函数生成唯一的名称来区分不同的函数。
函数重载可以提高代码复用性和可读性,同时也方便了程序员使用和维护。需要注意的是,虽然多个函数名相同但参数不同的函数被视为不同的函数,但返回值类型不能作为区分标准。此外,如果两个或多个重载函数有相似功能,建议将它们设计成共享一个基础算法或操作,并且保持接口统一。
同一个函数名在不同场景下可以具有不同的含义。函数重载是C++多态的特性(静态多态),用同一个函数名代表不同的函数功能。
2.2、函数重载的条件
同一作用域,函数的参数类型不同、个数不同、顺序不同都可以重载。返回值类型不能作为重载的条件。
#include <iostream>
using namespace std;
void func(int x)
{
cout<<"int"<<endl;
}
void func(char x)
{
cout<<"char"<<endl;
}
void func(int x,char y)
{
cout<<"int char"<<endl;
}
void func(char x, int y)
{
cout<<"char int"<<endl;
}
void func(double x)
{
cout<<"double"<<endl;
}
int main()
{
func(100);
func(100,'y');
func('y');
func('y',100);
func(100.2);
return 0;
}
输出:
int
int char
char
char int
double
为什么函数返回值不能作为重载条件?
这是因为函数调用时实参和形参的匹配过程是在编译期完成的,而编译器无法确定一个表达式的返回类型,因此不能以返回值类型作为重载条件。
比如以下示例:
int add(int a, int b) { return a + b; }
float add(int a, int b) { return (float)a + (float)b; }
如果允许通过返回值类型来区分同名函数,那么上述代码就合法了。但问题是,在调用add(1,2)时,编译器无法确定应该选择哪个函数。因为传入的参数都是int型,可以被两个函数接受,并且两个函数的返回值也都可以转换成其他数据类型。这样就会产生二义性错误。
因此,在C++中必须使用参数列表来区分同名函数,禁止使用返回值作为重载的条件。
2.3、函数重载的底层实现原理
C++中的函数重载是通过名称修饰(Name Mangling)实现的。当我们定义一个函数时,编译器会将函数名和参数列表一起进行名称修饰,生成一个新的、唯一的符号名称,这个符号名称就是该函数在目标代码中的标识。
也就是说,对于每个函数,在编译期间都会生成一个完整的符号名。这个符号名包含了函数名、参数类型以及参数数量等信息。例如,下面是两个重载函数的原型:
void func(int a, int b);
void func(double x, double y);
在编译期间,它们会被分别转换为以下两个不同的符号名:
_Z4funcii
_Z4funcdd
这些符号名使用特定的命名规则来保证它们在链接时可以正确地解析成相应的函数。
当调用一个重载函数时,编译器会根据传递给它的实参列表确定需要调用哪个版本。如果实参能够与某个重载版本匹配,则选择该版本进行调用;否则就产生错误。
C++中的函数重载本质上是一种静态多态性机制。它通过在编译期间对不同形式的同名函数进行名称修饰来区分不同版本,并且可以根据传递给它们的实参类型和数量进行选择。
不同的编译器可能会产生不同的内部名。
三、函数的默认参数
C++在什么函数原型的时候可以为一个或多个参数指定默认的参数值,当函数调用的时候如果没有指定这个值,编译器自用默认值代替。
注意点:
- 如果一个形参设置了默认参数值,那么它后面位置的形参也需要设置默认参数值。
- 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数。
- 默认参数和函数重载同时出现一定要注意二义性。
#include <iostream>
using namespace std;
void func(int a=0,int b=100,int c=200)
{
cout<<a<<" "<<b<<" "<<c<<endl;
}
// 如果一个形参设置了默认参数值,那么它后面位置的形参也需要设置默认参数值。
void func01(int a,int b=0,int c=100)
{
cout<<a<<" "<<b<<" "<<c<<endl;
}
// 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数。
void func02(int a,int b=0,int c=100);
void func02(int a,int b,int c)
{
cout<<a<<" "<<b<<" "<<c<<endl;
}
int main()
{
// 没有传参,使用默认参数
func();
// 如果传入一个参数,那么第二个及以后的参数使用默认参数
func(100);
// 如果传入两个参数,那么第三个及以后的参数使用默认参数
func(100,200);
return 0;
}
输出:
0 100 200
100 100 200
100 200 200
默认参数和函数重载同时出现一定要注意二义性。
#include <iostream>
using namespace std;
void func(int a,int b)
{
cout<<a<<" "<<b<<endl;
}
void func(int a,int b=100)
{
cout<<a<<" "<<b<<endl;
}
int main()
{
func(100,200);//OK
func(10);// error,产生二义性
return 0;
}
四、占位参数
C++声明函数时,可以设置占位参数。占位参数只有参数类型,而没有参数名。 一般情况下,在函数体内部无法使用占位参数。另外,占位参数也可以设置默认值;没有设置默认值的占位参数的函数在调用时必须拥有一个实参。
#include <iostream>
using namespace std;
void func01(int a,int b,int)
{
// 函数内部无法使用占位参数
cout<<a<<" "<<b<<endl;
}
void func02(int a,int b,int =100)
{
// 函数内部依旧无法使用占位参数
cout<<a<<" "<<b<<endl;
}
int main()
{
func01(100,200);// error, 错误调用,占位参数也是参数,必须传参数
func01(100,200,300);// OK
func02(100,200);// OK
func02(100,200,300);// OK
return 0;
}
占位参数可用于操作符重载的后置++的时候。
五、extern "C"浅析
在C++中,可以使用extern "C"
来声明一个函数或变量是以C语言的方式进行编译和链接的。这个关键字通常用于在C++程序中调用C语言库的函数或变量。
当使用extern "C"
修饰函数时,编译器会禁止对该函数进行名称重整(Name Mangling),也就是说,它不会将函数名加上类似于参数类型、返回值类型等标志信息作为后缀来生成独一无二的符号名。这样,在链接时就可以找到与之匹配的C语言库中的函数。
通俗的讲:
c函数void myFunc(){}
被编译成函数myFunc
;而C++函数void myFunc(){}
被编译成函数_Z6myFuncv
;这是由于C++需要支持函数重载,所以c和C++中对同一个函数经过编译后生成的函数名是不相同的;这就导致一个问题,如果在C++中调用一个使用c语言编写模块中的某个函数,那么C++是根据C++的名称修饰方式来查找并链接这个函数,这就会发生链接错误。
比如上面举的例子,C++中调用C语言实现的myFunc函数,在链接阶段会去找_Z6myFuncv
,结果是找不到,因为void myFunc(){}
函数是C语言编写的,生成的函数名是myFunc
。那么如果想在C++调用c的函数怎么办?extern "C"
的主要作用就是为了实现C++代码能够调用其他C语言代码,加上extern "C"
后,这部分代码编译器按照c语言的方式进行编译和链接,而不是按照C++的方式。
使用extern "C"
的格式:
#if __cplusplus
extern "C"{
#endif
//函数声明
//...
#if __cplusplus
}
#endif
使用示例:
func.h
#ifndef _FUNC_H_
#define _FUNC_H_
#if __cplusplus
extern "C" {
#endif
int func(int a, int b);
#if __cplusplus
}
#endif
#endif // !_FUNC_H_
func.c
#include <stdio.h>
#include "func.h"
int func(int a,int b)
{
printf("c func\n");
return a + b;
}
mian.cpp
#include "func.h"
int main()
{
func(100, 200);
return 0;
}
总结
-
内联函数。内联函数是指在编译时将函数调用处直接替换为函数体的代码,从而提高程序的执行效率。可以使用关键字inline来声明一个内联函数。
-
函数重载。函数重载是指在同一作用域中定义多个名称相同但参数个数或类型不同的函数。通过函数重载可以简化代码并提高可读性。
-
函数的默认参数。函数的默认参数是指在定义函数时给某些参数赋予默认值,在调用该函数时如果没有传递这些参数,则会自动使用默认值。
-
占位参数。占位参数是指在定义函数时声明了一个没有名字的参数,只起到占位符的作用,不能被实际使用。通常用于需要传入一定数量的参数,但是具体值在调用时才确定的情况下。