【C++学习】C++入门 | 缺省参数 | 函数重载 | 探究C++为什么能够支持函数重载

news2024/11/17 3:56:37

写在前面:

上一篇文章我介绍了C++该怎么学,什么是命名空间,以及C++的输入输出,

这里是传送门:http://t.csdn.cn/Oi6V8

这篇文章我们继续来学习C++的基础知识。

目录

写在前面:

1. 缺省参数

2. 函数重载

3. C++是如何支持函数重载的

写在最后:


1. 缺省参数

在学习C语言的时候,如果一个函数存在参数,

比如说这个函数:

void Func(int a) {}

我们在调用的时候就一定要给他传参,

而C++提供的缺省参数,能让我们对函数的传参更加灵活,

举个例子:

#include <iostream>
using namespace std;

void Func(int a = 10) {
	cout << a << endl;
}

int main()
{
	Func();  //没有传参的时候,使用参数的默认值
	Func(20);//传参的时候,使用指定的实参
	return 0;
}

我们给函数设置一个缺省值,这样在我们不给函数传参的时候,

函数的形参会自动使用缺省参数,而如果我们自己给函数传参,

函数形参使用的就是我们指定或者说传给他的值。

输出:

10 
20

那如果有多个函数参数的函数呢?

来看下一个例子: 

#include <iostream>
using namespace std;

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

int main()
{
	Func();
	return 0;
}

如果是这样的一个函数,

我们传参的时候能不能只传一部分呢?

来看例子:

#include <iostream>
using namespace std;

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

int main()
{
	Func();
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	return 0;
}

输出:

a = 10 b = 20 c = 30
a = 1 b = 20 c = 30
a = 1 b = 2 c = 30
a = 1 b = 2 c = 3

我们发现这样是可行的,

那如果我想要跳着传呢?

比如说这样传上面函数的参数:

Func(1, , 2);

这样是不行的,会报错,

实际上,我们只能按顺序来传,从左往右依次传参,其他都是不行的。

你可能会有疑问,为什么要这样设计,跳着传好像也没什么啊?

我也不知道为啥,因为C++祖师爷就是这么规定的,可能祖师爷不喜欢吧。

我们上述的缺省参数的用法其实叫全缺省,

我们还可以用半缺省,也就是一些参数给缺省值,一些参数不给,

举个例子:

#include <iostream>
using namespace std;
 
//半缺省
void Func(int a, int b = 20, int c = 30) {
	cout << "a = " << a << " ";
	cout << "b = " << b << " ";
	cout << "c = " << c << endl;
}

int main()
{
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	return 0;
}

可别搞错了哦,半缺省只是一部分参数不使用缺省参数,

而不是真的一半的参数。

这里就有有一个规定,缺省也必须是连续的,

而且缺省必须是从右往左的缺省,不然就会报错:

比如说这样子,编译器就会报错:

void Func(int a = 10, int b, int c = 30) {}

这里要分清楚:

缺省,是要从右往左缺省

传参,是要从左往右传参

那为什么祖师爷要设置这样一个语法呢?

实际上,这个语法在一些场景还是非常有用的,

我们来看这样一个场景,

比如说我们要对一个栈进行初始化:

#include <iostream>
using namespace std;

struct Stack {
	int* a;
	int top;
	int capacity;
};

//初始化一个栈
void StackInit(struct Stack* ptr) {
	ptr->a = (int*)malloc(sizeof(int) * 4);
	if (ptr->a == nullptr) {
		perror("StackInit::malloc::fail");
		return;
	}

	ptr->top = 0;
	ptr->capacity = 4;
}

int main()
{
	struct Stack st;
	StackInit(&st);
	//然后我们之后要对栈插入100个数据

	return 0;
}

如果我们明知道之后就要往栈里插入100个数据,

而我们初识化默认就是初始化大小是4个整形,

那之后插入数据的过程就会频繁扩容导致不必要的损耗,

如果我们多加一个参数:

#include <iostream>
using namespace std;

struct Stack {
	int* a;
	int top;
	int capacity;
};

//初始化一个栈
void StackInit(struct Stack* ptr, int defaultCapacity = 4) {
	ptr->a = (int*)malloc(sizeof(int) * defaultCapacity);
	if (ptr->a == nullptr) {
		perror("StackInit::malloc::fail");
		return;
	}

	ptr->top = 0;
	ptr->capacity = defaultCapacity;
}

int main()
{
	struct Stack st;
	StackInit(&st, 100);
	//然后我们之后要对栈插入100个数据

	return 0;
}

这样如果我们有需要就可以直接指定开辟空间大小,

不需要的时候不传第二个参数,也能自动使用默认的大小初始化。

这个问题就很好的解决了。

这里再补充一嘴,C语言的时候我们其实通常是这样解决这种问题的:

#include <iostream>
using namespace std;

#define DEFAULT_CAPACITY 100

struct Stack {
	int* a;
	int top;
	int capacity;
};

//初始化一个栈
void StackInit(struct Stack* ptr) {
	ptr->a = (int*)malloc(sizeof(int) * DEFAULT_CAPACITY);
	if (ptr->a == nullptr) {
		perror("StackInit::malloc::fail");
		return;
	}

	ptr->top = 0;
	ptr->capacity = DEFAULT_CAPACITY;
}

int main()
{
	struct Stack st;
	StackInit(&st);
	//然后我们之后要对栈插入100个数据

	return 0;
}

通过定义一个宏的形式,

这样如果要修改初识化大小,就只用修改宏定义就行,

但是这样是没有C++这种用法灵活,

如果我们要创建两个栈,一个容量100,一个容量4的时候,他就做不到了:

#include <iostream>
using namespace std;

struct Stack {
	int* a;
	int top;
	int capacity;
};

//初始化一个栈
void StackInit(struct Stack* ptr, int defaultCapacity = 4) {
	ptr->a = (int*)malloc(sizeof(int) * defaultCapacity);
	if (ptr->a == nullptr) {
		perror("StackInit::malloc::fail");
		return;
	}

	ptr->top = 0;
	ptr->capacity = defaultCapacity;
}

int main()
{
	struct Stack st1;
	StackInit(&st1, 100);
	//然后我们之后要对栈插入100个数据

	struct Stack st2;
	StackInit(&st2);

	return 0;
}

在这个场景下,使用C++就非常的舒适。

当然啦,我们也不能说C语言就不好,C语言也是有他自己独特的优势的。

这里还有一个细节要注意,

在使用缺省参数的时候,声明和定义不能都给缺省。

那是给声明还是给定义缺省值呢?

我就直接说了,只能给声明缺省值,

我来解释一下为什么,我们调用函数的时候,其实看到的就是声明,

如果需要传参就传参,如果有缺省值没传参,传的参数就自动变成缺省参数的值,

而定义不关心这些,定义只知道你一定要给我传两个参数,

所以我们只给声明缺省值。

#include <iostream>
using namespace std; 

struct Stack {
	int* a;
	int top;
	int capacity;
};

//声明给缺省
void StackInit(struct Stack* ptr, int defaultCapacity = 4);

int main()
{
	struct Stack st1;
	StackInit(&st1, 100);
	//然后我们之后要对栈插入100个数据

	struct Stack st2;
	StackInit(&st2);

	return 0;
}

//初始化一个栈
void StackInit(struct Stack* ptr, int defaultCapacity) {
	ptr->a = (int*)malloc(sizeof(int) * defaultCapacity);
	if (ptr->a == nullptr) {
		perror("StackInit::malloc::fail");
		return;
	}

	ptr->top = 0;
	ptr->capacity = defaultCapacity;
}

2. 函数重载

什么是函数重载,我们来看一个例子:

#include <iostream>
using namespace std;

void add(int x, int y) {
	cout << "int" << endl;
}

void add(double x, double y) {
	cout << "double" << endl;
}

int main()
{
	add(1, 2);
	add(1.1, 2.2);
	return 0;
}

输出:

int
double

这个就是函数重载,

我们发现这两个函数函数名相同,但是参数类型却不同,

C语言是不允许同名函数的,而C++函数重载可以支持这个语法,

在函数调用的时候,能够根据你传的参数自动匹配类型。

补充:函数重载对函数返回值没有要求,也就是返回值不同是不构成重载的。

这里我直接总结出函数重载的规则,记住就行了:

1. 在同一个作用域

2. 函数名相同

3. 参数的类型不同或者类型的个数或者顺序不同

这个时候就出现了有趣的情况,

来看例子:

#include <iostream>
using namespace std;

void f() {
	cout << "f()" << endl;
}

void f(int a = 0) {
	cout << "f(int a = 0)" << endl;
}

int main()
{

	return 0;
}

你觉得这段代码构成重载吗?

答案是构成的,因为他符合重载的规则,是可以编译通过的,

但是,如果我们无参调用这个函数呢? 编译器就会直接报错:

f()

不能这样子调用,因为这样存在调用歧义。

其实函数重载就这一点点知识,已经讲完了,

但是,有一个问题,为什么C语言不支持重载,而C++能支持重载呢?

C++是怎么支持重载的呢?

其实是在编译链接的过程,函数名修饰规则有所不同。

3. C++是如何支持函数重载的

这里我需要先做的一个小的铺垫内容,

我们在进行函数调用的时候,调用函数的底层是怎么样的?

比如说这一段代码:

#include <stdio.h>

void f(int a) {
	printf("f(int a)\n");
}

void f(int a, double b) {
	printf("f(int a, double b)\n");
}

int main()
{
	f(1);
	f(1, 1.1);
	return 0;
}

来看汇编代码:

我们可以看到,汇编实际上是使用call 指令来调用函数的,

然后在调用 jump 指令跳转到函数的定义:

 

这个时候我们就进入函数了:

 那么了解函数是怎么调用之后,我们再继续探究函数重载, 

这里我采用gcc环境来进行演示,

先来看C语言,还是这段代码:

#include <stdio.h>

void f(int a) {
	printf("f(int a)\n");
}

void f(int a, double b) {
	printf("f(int a, double b)\n");
}

int main()
{
	f(1);
	f(1, 1.1);
	return 0;
}

这样子肯定是编译不通过的,

看到这个报错,conflicing types,其实就是函数名冲突了,

我们修改一下代码,让他能够编译通过:
 

#include <stdio.h>

void f(int a, double b) {
	printf("f(int a, double b)\n");
}

int main()
{
	f(1, 1.1);
	return 0;
}

来看看他的汇编代码是怎么样的:

 我们通过汇编可以看到,汇编代码中 call 的这个函数的函数名是 f 

跟我们设置的函数名是相同的,

要是我们定义了两个函数名相同的函数,那 call < f > ,究竟call 的是谁?

那就会出现函数命名的冲突问题,

但是这些只是我们现在的推测,接下来我们看看C++的汇编是怎么操作的:

还是这段代码:

#include <stdio.h>

void f(int a) {
	printf("f(int a)\n");
}

void f(int a, double b) {
	printf("f(int a, double b)\n");
}

int main()
{
	f(1);
	f(1, 1.1);
	return 0;
}

但是我们换成了C++的环境(C++兼容C语言)

看看我们发现了什么?

两个同样的函数,到了C++环境下编译出来的汇编代码,他的函数名怎么这么奇怪?

上面是带有两个函数参数的函数 f (int a, double b) 

我们再来看看那个带着一个函数参数的同名函数 f (int a):

发现了吗?

他们在C++代码中函数名是相同的,

但是到了汇编代码这里,函数名却不一样了,使用 call 指令调用的函数就不一样了,

这样就没有所谓的函数名冲突的问题了,

现在你应该大致理解为什么C语言不支持同名函数了,

C语言下编译出来的汇编代码的函数名是跟C语言代码写的函数名是相同的,

就自然不支持同名函数,而C++编译出来的汇编代码展现的函数名明显不相同。

我们再仔细看看:

 

 可以看到他的命名规则还是有一点讲究的。

不过他的命名规则的细节我就不深究了,最重要的是理解函数重载的底层是怎么样的。

祖师爷创造这些语法还是有迹可循的,

同时我们也能感受到,真正设计一个语言还是非常困难的,

需要对各方面的知识有着深入的理解。

这里还是补充一嘴:学习C++的时候,多学底层还是非常重要的,

我们要做到:知其然,知其所以然,这样才能体现我们学习的优势,算是我的一些感想吧。

补充:

这个时候我们又能理解一个点,

函数重载为什么不能支持函数返回值不同呢?一定要返回值相同才能重载。

我们刚刚分析的汇编代码中的函数名修饰规则,

实际上汇编在调用函数的时候,就是通过call 指令查找函数的过程,

来看这段代码:

#include <stdio.h>

void func();
int func();

int main()
{
	func();
	return 0;
}

返回值在调用的时候不会体现,

也就是说我们在调用 func() 函数的时候,我们不知道调用的是哪一个,

如果参数不同,编译器至少知道这是两个不同的函数,

到 call 的时候才可能出现查找函数出现歧义,

而调用 func() 这个函数,编译器就不知道究竟想调用哪个函数,

所以在编译阶段就会直接报错,

所以就算把返回值加入函数名修饰规则,编译也走不到那一步。

来看一眼编译器是怎么说的:

 直接给你飘红了,还贴心的告诉你无法支持按返回类型区分的函数重载。

这里补充一句:

为什么我不在Windows下或者说VS下探究这个函数名修饰规则,

而是跑到了gcc/g++环境下去看呢?

因为VS他的函数名修饰规则比较复杂,没有g++那么清晰,

如果你感兴趣的话可以上网搜一下VS下的函数名修饰规则,这里就不展示了。

写在最后:

以上就是本篇文章的内容了,感谢你的阅读。

如果感到有所收获的话可以给博主点一个哦。

如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/676666.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浅谈【AI、算力赋能】“大算力”时代的到来

&#x1f53b;一、【&#x1f4a3; 话题引入&#xff1a;“AI算力最强龙头”&#xff0c;你怎么看&#xff1f;】 &#x1f648; AI人工智能是否可以取代人类&#xff1f;    &#x1f648; 应不应该限制人工智能的发展&#xff1f;      &#x1f648; AI研究及龙头行业迎…

011-从零搭建微服务-接口文档(一)

写在最前 如果这个项目让你有所收获&#xff0c;记得 Star 关注哦&#xff0c;这对我是非常不错的鼓励与支持。 源码地址&#xff08;后端&#xff09;&#xff1a;https://gitee.com/csps/mingyue 源码地址&#xff08;前端&#xff09;&#xff1a;https://gitee.com/csps…

【P2】VMware 下 docker 快速搭建漏洞靶场 DVWA

文章目录 一、docker 快速搭建漏洞靶场指南二、执行步骤三、为 kali 配置 docker 加速器四、访问 dockerhub 的 dvwa 镜像五、漏洞利用初探&#xff0c;修改 requests 请求参数远程执行命令六、vulhub 搭建漏洞复现 包括什么是 docker、docker 和虚拟机的的区别、docker 搭建 D…

阿里云服务器的虚拟化技术和资源隔离如何?是否支持私有云部署?

阿里云服务器的虚拟化技术和资源隔离如何&#xff1f;是否支持私有云部署&#xff1f;   一、阿里云服务器的虚拟化技术   阿里云服务器采用了业界领先的虚拟化技术&#xff0c;为用户提供了强大而灵活的计算性能。这主要体现在以下几个方面&#xff1a;   1.1 弹性伸缩 …

强化学习从基础到进阶-案例与实践[3]:表格型方法:Sarsa、Qlearning;蒙特卡洛策略、时序差分等以及Qlearning项目实战

【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧&#xff08;调参、画图等、趣味项目实现、学术应用项目实现 专栏详细介绍&#xff1a;【强化学习原理项目专栏】必看系列&#xff1a;单智能体、多智能体算法原理项目实战、相关技巧…

【CMake 入门与进阶(13)】 CMake如何设置交叉编译(附代码)

cmake如果不设置交叉编译&#xff0c;默认情况下&#xff0c;会使用主机系统&#xff08;运行 cmake 命令的操作系统&#xff09;的编译器来编译我们的工程&#xff0c;那么得到的可执行文件或库文件只能在 Ubuntu 系统运行&#xff0c;如果我们需要使得编译得到的可执行文件或…

javaWeb医药管理系统

一、引言 二、项目截图 2.1 首页设计 2.2一级页面设计 2.2-1注册界面 2.2-2管理员登录界面 2.3二级页面设计 药品信息模块 药品销售 用户信息 三、项目基本要求 1.主要功能 医药管理系统的主要功能为&#xff1a;、药品更新、药品查询 药品更新功能分为三部分&…

前端Vue自定义支付密码输入键盘Keyboard和支付设置输入框Input

前端Vue自定义支付密码输入键盘Keyboard和支付设置输入框Input&#xff0c; 下载完整代码请访问uni-app插件市场地址&#xff1a;https://ext.dcloud.net.cn/plugin?id13166 效果图如下&#xff1a; # cc-defineKeyboard #### 使用方法 使用方法 <!-- ref:唯一ref pas…

VMware vCenter Server 7.0 Update 3m 发布下载(重要安全更新)

VMware vCenter Server 7.0 Update 3m 发布下载&#xff08;重要安全更新&#xff09; 请访问原文链接&#xff1a;https://sysin.org/blog/vmware-vcenter-7-u3/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.org VMware vCente…

canvas详解07-裁剪

裁切路径 裁切路径和普通的 canvas 图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分。如右图所示。红边五角星就是裁切路径,所有在路径以外的部分都不会在 canvas 上绘制出来。 如果和上面介绍的 globalCompositeOperation 属性作一比较,它可以实现与 source-in …

基于cycle of curves的Nova证明系统(2)

主要见斯坦福大学Wilson Nguyen、Dan Boneh和微软研究中心Srinath Setty 2023年论文《Revisiting the Nova Proof System on a Cycle of Curves》。 前序博客见&#xff1a; 基于cycle of curves的Nova证明系统&#xff08;1&#xff09; 5. IVC Proof进一步压缩 本文提出了…

【Rust】1、实战:语法和数据结构、生命周期-所有权-借用、自制 CPU、内存、文件

文章目录 零、Rust 好用的资源一、概述1.1 安全性1.1.1 垂悬指针1.1.2 数据竞争1.1.3 迭代器失效 1.2 性能1.3 vscode 設置 二、基础语法2.1 循环2.2 引用2.3 生命周期2.4 泛型2.5 实战grep项目2.6 数组2.6.1 数组和切片2.6.2 动态数组2.6.3 初始化 2.7 包含第三方库2.8 命令行…

深入理解深度学习——BERT(Bidirectional Encoder Representations from Transformers):输入表示

分类目录&#xff1a;《深入理解深度学习》总目录 相关文章&#xff1a; BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;&#xff1a;基础知识 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09…

【Linux】MySQL 高级 SQL 语句 (一)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 MySQL 高级 SQL 语句 MySQL 高级(进阶) SQL 语句SELECT&#xff1a;显示表格中一个或数个字段的所有数据记录DISTINCT&#xff1a;不显示重复的数据记录WHERE&#xff1a;有条…

CVE-2022-25411

文章目录 CVE-2022-25411一、漏洞介绍二、渗透步骤1、打开网站2、目录扫描3、访问后台4、添加文件后缀5、上传shell6、查看flag值 CVE-2022-25411 一、漏洞介绍 Maxsite CMS文件上传漏洞。 MaxSite CMS是俄国MaxSite CMS开源项目的一款网站内容管理系统。马克斯程序(MaxCMS)以…

【Syncfusion系列】SfDataGrid 轻松实现分页和Excel导出

前言 Syncfusion 封装了一个控件 SfDataGrid &#xff0c;通过SfDataGrid我们只需要 极少量 代码就能分页和Excel导出。 效果展示 包安装 安装下面三个包 将表格绑定到数据库 这次我使用的是一个本地的小型数据库&#xff1a;sqlit 我通过 sqlit-net 这个包 进行访问 sqlit…

Dify 基于 ChatGPT 构建本地知识库问答应用

一、Dify 自从 ChatGPT 横空出世之后&#xff0c;其极高的语言理解和交互能力不仅让人惊呼&#xff0c;ChatGPT不仅能够处理事实性问题&#xff0c;还能理解和生成情感色彩更浓厚的对话内容&#xff0c;能够识别用户的情感倾向&#xff0c;并据此作出相应的回应。这么好的东西…

Linux——文件基础IO的文件描述符和重定向理解

目录 前言&#xff1a; 首先来回顾一下open函数&#xff0c;即在进程中同时打开多个文件&#xff1a; Linux底层进程与文件的关系 &#xff1a; 2.1关闭stdin&#xff1a; 运行结果&#xff1a; ​编辑由结果知&#xff1a;fd1指向文本文件cyq.txt&#xff0c;原本fd1是默…

机器学习多步时间序列预测解决方案

近年来&#xff0c;随着机器学习与深度学习的发展机器学习平台的成熟&#xff0c;数据科学家们不再需要关心底层的基础设施及构建复杂的训练与推理环境&#xff0c;从而可以把主要的时间与精力放在数据与算法本身。在机器学习变得更容易的今天&#xff0c;越来越多的传统行业已…

HAL库——STM32CubeMX中断相关配置(中断反转LED状态)

STM32CubeMX中断相关配置 文章目录 STM32CubeMX中断相关配置1. 选择你要用的芯片(双击打开)2. 设置串口写入3. 配置时钟树&#xff0c;外部时钟为系统时钟&#xff08;PLL倍频时钟&#xff09;4. 查看原理图&#xff0c;找到可以中断控制的器件&#xff0c;或者外接小灯来达到中…