【C++】简单学——缺省参数(默认参数)

目录

意义

分类

全缺省

半缺省

实际应用

C的方式

C++用缺省参数的方式

有缺省参数的声明和定义的分离

原因

表层

​编辑

对声明和定义分离的深度剖析(与缺省参数无关)

概念

函数声明函数定义的时候

可以某些参数默认值

这样,在调用函数时,如果用户没有传值,函数将使用默认值

用珍珠奶茶来举例:珍珠奶茶默认放珍珠(缺省值 / 默认值),如果不特别说明改其他小料的话,就默认放珍珠

语法

int Add(int a = 3, int b = 2)
{
	return a + b;
}

//

int main()
{
	int x = Add();

    //没有传参,默认参数为3和2,所以x为3 + 2 =5


	int y = Add(10, 20);

    //传参传了a和b,默认参数不生效,所以y为10 + 20 =30

    
    int z = Add(8);

    //传参传了a且没传b
    //a的默认参数不生效,b的默认参数生效
    //所以z为 8 + 2 =10

	return 0;
}

规定:实参传参时,从左往右给值,函数形参给缺省值时,从右往左给值

(下面有详细说明)

意义

  1. 提高灵活性:调用者可以选择性地提供参数,而不是被迫为每个参数都提供值。
  2. 简化调用:对于那些通常情况下具有相同值的参数,可以通过默认参数来简化函数的调用,减少重复性代码。

分类

如果要做函数的声明和定义的分离,那么声明和定义当中只能有一个有缺省参数

(毕竟如果两边都给了不同的缺省参数,编译器就不知道用哪个缺省参数了)

全缺省

定义:每个函数参数都给缺省值

语法:

void Func(int a = 10, int b = 20, int c = 30)
{
  cout<< a <<endl;
  cout<< b <<endl;
  cout<< c <<endl;
}

要求:当函数传参时,传参的顺序是从左往右给,不可跳跃着给

即:

int main

{

        Func(3, 5);//a为3,b为5,c为30

        

        以下是错误示范:

        Func(  ,4,  );

        Func(2,   ,  8);

}

半缺省

定义:部分函数参数给值,部分不给

语法:

void Func(int a, int b = 20, int c = 30)
{
  cout<< a <<endl;
  cout<< b <<endl;
  cout<< c <<endl;
}

要求:当函数参数给缺省值时,给缺省值的顺序是从右往左给,不可跳跃着给

即:

以下是错误示范

void Func(int a = 10, int b, int c)/没有从右往左给
{
  cout<< a <<endl;
  cout<< b <<endl;
  cout<< c <<endl;
}

void Func(int a = 10, int b, int c = 30)跳着给
{
  cout<< a <<endl;
  cout<< b <<endl;
  cout<< c <<endl;
}

实际应用

假设我们要搞一个栈

栈初始化的时候

需要开辟空间

C的方式

  1. 要么给一个默认开辟的空间大小(例如默认开四个空间)
  2. 要么每次创建栈的时候都要传值(说明要开辟多大的空间)

以上的两种方法均有缺陷;

方法1的缺陷

栈1要开辟10个空间

栈2要开辟100个空间

默认开辟空间为4时:

栈2在进行开辟空间的过程中会产生大量消耗(调用多次扩容函数)

默认开辟空间为100时:

栈1就会白白浪费了90个空间

方法2的缺陷

创建50个栈就需要手动传参50次

太麻烦了

C++用缺省参数的方式

使用上述C方式的方法2:每次创建栈的时候都要传值

不同的是:开辟的空间大小提前给缺省值

void Init(struct Stack* pst, int size = 4);
{
    pst = (struct Stack*)malloc(sizeof(int) * size);
    /……

}

一般情况下就用缺省值

如果缺省值无法满足我的需求,那就手动传值

int main()
{
	struct Stack st1;
	struct Stack st2;

	Init(&st1);//开辟了4个空间的栈
	Init(&st2, 100);//开辟了100个空间的栈

	return 0;
}

有缺省参数的声明和定义的分离


结论:如果要做函数的声明和定义的分离缺省参数要放在声明

(毕竟如果两边都给了不同的缺省参数,编译器就不知道用哪个缺省参数了)

原因

表层

预处理,编译,汇编,链接构建一个可执行程序的主要步骤

预处理阶段,文件内的头文件将会被直接拷贝过来(头文件展开)

此时,被拷贝过来的函数只有函数的声明,并没有定义

编译阶段,进行的是对语法的检查

其中一个语法检查,就是检查函数参数和个数是否匹配

例:

int Add(int a, int b = 3)
{
	return a + b;
}


以上内容是Add.cpp
//
以下内容是test.cpp


int Add(int a, int b);从Add.h头文件拷贝过来的函数声明

int main()
{
	int c = Add(3);

	return 0;
}

这个项目一共有三个文件:Add.cpp   、   Add.h   、   test.cpp

在这个项目中,我将Add.h头文件包含到了test.cpp

预编译期间Add.h头文件test.cpp中展开了

编译阶段,要对语法进行检查

test.cpp中,调用了Add(3),但头文件拷贝过来的的Add(int a, int  b)只有两个参数

所以,因为参数个数不匹配,所以就报错

对声明和定义分离的深度剖析(与缺省参数无关)

此内容仅了解即可

为了更深度地学习声明和定义分离

我们需要了解以下的过程中究竟发生了什么

未进行声明和定义的分离:

C++代码

以上的步骤,在汇编指令的角度为以下步骤:

汇编代码:

在汇编指令中,有几个比较关键的部分(除了关键部分,可以暂时忽视)

分别是两个转折点:CallJmp

每当我们在进行调用函数时,实际上会被转化成CallJmp汇编指令

函数的调用本质上是去Call一个地址

当函数调用时,Call会根据某个地址(07211CCh),找到指定的Jmp指令,然后Jmp指令根据Add(07218D0h)实际的地址

跳转到Add函数的第一条指令位置(07218D0h),然后就把剩下的一系列的指令从内存中依次取出

最后发送给计算机的大脑(CPU)去依次执行,执行的这个过程本质上就是函数的实现

这样就可以通过调用Add(3 , 3)来完成Add的功能了

函数的地址在底层中实际就是这个函数的第一条指令的地址

(所以,如果函数没有定义,只有声明,是不会产生函数的地址的

进行了声明和定义的分离:

此时就存在一个问题:

在未进行声明和定义分离时,我们可以很轻松的在test.cpp中找到Add的地址

但是进行了声明和定义分离后,尽管在预处理阶段已经把Add的声明拷贝到了test中,但是因为在test中找不到Add的定义(只有声明),也就代表着在test中没法找到Add的地址

毕竟Add的定义在Add.cpp中

所以在声明和定义分离的情况中,有一个很尴尬的问题

有的人不用,用的人没有(test.cpp需要地址却没有地址可用,Add.cpp不需要地址却不使用地址)

这样的话,当函数进行调用时(转化成Call某个地址),Call没有地址可用了,也就没法调用函数了

这样的话编译器就会报错了

为了解决这个问题

编译器让所有的源文件进行合作:

汇编阶段生成符号表链接阶段符号表的合并和重定位可以有效的解决这个问题

(简化版↓↓↓)

预处理:

  • 进行预处理指令的操作(宏替换、条件编译、删注释),
  • 头文件展开

编译:

  • 检查语法,语法不合格就报错(函数的参数类型和个数不匹配、找不找得到标识符)
  • 生成汇编指令

汇编:

  • 生成符号表(会把所有源文件里面的全局函数取出来,构成一个表)

链接:

  • 符号表的合并和重定位(将所有源文件里面的符号表合并起来名字相同的就合在一起),那些找不到地址的函数就可以在这里找到)

点击查看详细版(施工中)

所以,尽管test中没有找到Add的地址,但是如果在链接阶段合并符号表时找到了地址,那test就可以继续调用Add函数了

因此,在test中,即使没有找到函数的定义,也不会报错

相当于test向编译器做了个承诺

但如果到了链接期间,仍然没有找到Add的地址(即Add没有做出来),就还是会报错的

形象的例子:

你和朋友一起组装一个玩具城堡,你负责设计和制作城堡的塔楼,而你的朋友负责设计和制作城堡的城墙。

你设计了一个部件,比如说是塔楼的顶部,但是具体的塔楼结构还没有被制作完成,因此你只提供了塔楼顶部的设计图和承诺(函数的声明)


你的朋友需要在城墙的一部分上添加一个连接塔楼的门。尽管你的朋友只得到了你提供的设计图,但是他知道塔楼的顶部部件会在最终的城堡中存在,因此他可以在城墙上安装门,而不用等到你的塔楼完全制作完成。


即使你的朋友在城墙设计的过程中,并没有看到完整的塔楼,但是他知道这个部件最终会被添加进来,所以他可以继续进行设计和制作,而不会出现错误(尽管test中没有找到Add的地址,但有了函数的声明,所以也选择相信能找到Add的地址,不会报错误)。

但如果最终你并没有制作塔楼的顶部,这个城堡就是一个失败的城堡了(如果到了链接期间,仍然没有找到Add的地址,就还是会报错的)

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CtrlZ大牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值