1 C++函数

本文深入讲解了C++中的函数概念,包括默认参数、占位参数、函数重载、内联函数等内容,并探讨了宏与内联函数的区别及const修饰符的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 函数


函数调用机制:

  1. 准备工作:将实参,局部变量,返回地址,若干寄存器,入栈
  2. 执行代码
  3. 清理现场
  4. 将入栈变量出栈,

1.1 函数参数

1.1.1 默认参数

:**

默认参数性质

  1. 参数数可以是常量,也可以是表达式
  2. 默认参数只可以放到形参列表后,而且一旦为某个形参指定了默认值,那么它后面的所有形参都必须有默认值
  3. 如果函数声明有默认值,那么实现时不可以有默认值。编译器禁止声明和定义时同时定义缺省参数值
    • 原因:声明是用户可以看到的部分,客户非常信任地使用这个特性
    • 实践证明,缺省参数可以在定义中,也可以在声明中,只要全局的声明和定义中只有一个带参数即可

默认参数的作用:

  • 通过使用默认参数,可以减少要定义的析构函数、方法以及方法重载的数量。
#include<iostream>
using namespace std;

//常量默认参数
void func(int n, float b=1.2, char c='@'){
    cout<<n<<", "<<b<<", "<<c<<endl;
}
//表达式默认参数
float d = 10.8;
void func(int n, float b=d+2.9, char c='@'){
    cout<<n<<", "<<b<<", "<<c<<endl;
}

//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
	return a + b;
}

int main(){
    //为所有参数传值
    func(10, 3.5, '#');
    //为n、b传值,相当于调用func(20, 9.8, '@')
    func(20, 9.8);
    //只为n传值,相当于调用func(30, 1.2, '@')
    func(30);

    return 0;
}

1.2.2 占位参数

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

语法: 返回值类型 函数名 (数据类型){}

在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该技术

示例:

//函数占位参数 ,占位参数也可以有默认参数
void func(int a, int) {
	cout << "this is func" << endl;
}

int main() {

	func(10,10); //占位参数必须填补

	system("pause");

	return 0;
}

1.2 函数重载

1.2.1 函数重载规则

重载条件:

  1. 函数名称相同
  2. 参数列表不同。参数列表不同包括参数的个数不同、类型不同或顺序不同。
  3. 引用可以作为重载条件加上const;可基于函数的引用形参是指向 const 对象还是指向非 const 对象
  4. const 修饰函数可实现重载,普通函数和常函数。

注意事项:

  1. 参数名称不同不可以作为重载条件。
  2. 返回值类型不可以作为重载条件。

本质:

重载决议:C++代码在编译时会根据参数列表对函数进行重命名,

​ 例如void Swap(int a, int b)会被重命名为_Swap_int_intvoid Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错。

本质:函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

1.2.2 重载的二义性

二义性是指在编译过程中无法找出最匹配的函数,或者说编译器在函数匹配过后还是有多个函数满足要求,无法确定该执行那一个引发的错误。

1.参数数目引发的二义性(默认参数问题)

int get(){
    return 5;
}
int get(int a = 5){
    return a;
}
//调用get()
//不给参数和有默认参数会造成歧义。

2.参数隐式转换引发二义性

可以使用explict关键字禁用隐式转换

int get(int m){
    return m;
}
 
long get(long m){
    return m;
}
//double d = 1.234;
//调用get(d);double既可以隐式转换未long,也可以是int,或者说一般的数值类型之间都可以进行隐式类型转换,故无法确定那一个更加匹配。

3.引用作为重载条件

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

void func(const int &a)
{
	cout << "func (const int &a) 调用 " << endl;
}
int a=10;
func(a);调用1 1.是变量可读可写   2.是可读  无二意性
func(10);调用2  1. int &a=10;语法错误,引用必须指向一个内存
    			2. const int a=10;合法,创建临时向量 存储10,然后引用指向。

1.3 内联函数

函数调用机制

  • C++程序的执行过程可以被当做多个函数的调用过程,从main函数开始,到main函数结束;然而函数的调用将会产生开销,因此短代码的函数开销不可忽略,而长函数的函数开销可以忽略。
    1. 准备工作:将实参,局部变量,返回地址,若干寄存器,入栈
    2. 执行代码
    3. 清理现场
    4. 将入栈变量出栈,

**背景:**当函数体代码比较多,函数调用机制时间可以被忽略

​ 当函数体代码比较少,函数调用机制时间相对较长,不可忽略。

解决方法:内联函数

​ 消除时空开销,提高效率。在函数编译时将内联函数调用处,直接用内联函数的代码替换。如此,便不需要函数调用。

注意事项:

内联函数特性

  • 内联函数不应该有声明,内敛函数省略函数原型,直接将函数的整个定义放入提供函数原型的地方。(声明时进行定义,不需要将声明的定义分离)
  • 函数定义时使用inline有效声明inline编译器无视
  • 编译器对是否内联有自己的想法,程序员仅仅是提供建议,最终是否内联还是取决于编译器
  • 内联函数是一种空间换时间的做法,编译期间,内联函数代码展开,省去函数调用开销,代码很长或者有循环/递归的函数不适宜使用作为内联函数。可能会造成代码膨胀。
  • 在debug模式下,内联函数默认是不会展开的,因为debug模式主要是用来调试代码的

内联函数失去函数的本质

  • 函数是一段可以重复使用的代码,它位于虚拟地址空间中的代码区,也占用可执行文件的体积,而内联函数的代码在编译后就被消除了,不存在于虚拟地址空间中,没法重复使用
#include <iostream>
using namespace std;
//声明处inline关键字会被忽略
inline void swap(int*,int*);

//内联函数,交换两个数的值
inline void swap(int *a, int *b){
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

int main(){
    int m, n;
    cin>>m>>n;
    cout<<m<<", "<<n<<endl;
    swap(&m, &n);
    cout<<m<<", "<<n<<endl;

    return 0;
}

1.3.1 内联函数代替带参数的宏

宏的调用机制是字符串的替换,而不是按值传递。

当n = 9 时,SQ(n) = 81

如果把SQ(n)换成SQ(n+1)却得不到100,因为sq = n+1*n+1;

#include <iostream>
using namespace std;
#define SQ(y) y*y
int main(){
    int n, sq;
    cin>>n;
    sq = SQ(n+1);
    cout<<sq<<endl;
    return 0;

}

1.3.2内联函数代替宏

  • 宏可以带参数,宏的机制是字符串替换

    注意踩坑
    #define SQ(y) y*y
    当n = 9 时,SQ(n) = 81
    如果把SQ(n)换成SQ(n+1)却得不到100,因为sq = n+1*n+1;
    要想得到正确的结果,还应该对宏加以限制,在两边增加( ),如下所示:
    #define SQ(y) ( (y)*(y) )
    
  • C++推荐使用内联函数代替宏,函数调用比字符串替换相比快捷

  • 头文件和内联函数

    • 内联函数可以定义在头文件中,且该文件可以被多次include
    • 非内联函数不可以定义在头文件中,且头文件多次include会有重复定义错误

1.3.3宏

  • 宏的优点:宏函数在预处理阶段展开了,减少了函数调用的开销(传参,参数压栈以及栈帧花销)…

  • 宏的缺点:宏函数可能会存在一定的副作用;在预处理阶段进行替换,不会参与编译,少了类型检测;宏函数不能调试

  • 在C++中,宏常量可以用const修饰的常量来代替,宏函数可以用内联函数来代替

1.3.4const修饰变量

  • 在C语言中,const修饰的变量不能成为常量,只能称为不能被修改的变量

  • 在C++中,被const修饰的变量称为常量,且有宏替换的功能

  • 被const修饰的变量的替换发生在编译阶段,在所有使用const常量的位置用常量的值替换该常量

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值