【C++】踏上C++的学习之旅(二):缺省参数和函数重载(内含函数重载的底层原理)

文章目录

  • 前言
  • [1. 缺省参数](#1. 缺省参数)
    • [1.1 为什么要有缺省函数?](#1.1 为什么要有缺省函数?)
    • [1.2 什么是缺省参数?](#1.2 什么是缺省参数?)
    • [1.3 缺省参数的分类](#1.3 缺省参数的分类)
      • [1.3.1 全缺省参数](#1.3.1 全缺省参数)
      • [1.3.2 半缺省参数](#1.3.2 半缺省参数)
    • [1.4 使用缺省参数需要避的"坑"](#1.4 使用缺省参数需要避的"坑")
  • [2. 函数重载](#2. 函数重载)
    • [2.1 函数重载概念](#2.1 函数重载概念)
    • [2.2 函数重载的情况](#2.2 函数重载的情况)
    • [2.3 函数重载背后的原理](#2.3 函数重载背后的原理)
      • [2.3.1 编译和链接](#2.3.1 编译和链接)
      • [2.3.2 函数调用的汇编代码讲解](#2.3.2 函数调用的汇编代码讲解)

前言

在我们学习C++的命名空间之后 ,我们知道这是一个解决C语言中无法解决的问题,这个问题被我们称之为"命名冲突"。

那么在本章中 ,我们继续讲解一些在C语言中无法解决的问题,来看看本贾尼大佬(C++的创造者)是怎么解决这些问题的。

1. 缺省参数

1.1 为什么要有缺省函数?

相信大家在学习C语言中,一定遇到过一个这样的苦恼。比如我现在在使用着一个自定义的申请动态内存的空间函数,在C语言中,就只能乖乖的给函数传递实参(一个指针变量和需要开辟的空间大小)。突然有一天,我不想再给这个函数传递需要开辟的空间大小的那个实参了,但是如果不将参数全部传完的话,在C语言的视角中你这个就是一个语法错误了。

本贾尼大佬也是受够了C语言的这种设计,为此缺省参数就在C++中诞生了。缺省参数解决的问题就是当我不想给函数传递对应的参数时,会采取某种机制使得编译器认为你已经给这个函数对应的参数。 那至于是什么机制,大家不妨接着往下看!😊

1.2 什么是缺省参数?

缺省参数是声明或定义函数时为函数的参数指定一个缺省值(默认值)。在调用该函数时,如果我们没有指定实参的话则采用该形参的缺省值(默认值),否则就使用实参的值。

给大家想感受一下缺省参数的魅力:

cpp 复制代码
#include<iostream>
using namespace std;
//这里的形参a就是一个缺省参数
void func(int a = 1)
{
	cout << a << endl;
}


int main()
{
	func();  //没有给定实参
	func(66);//给定了实参
	return 0;
}

1.3 缺省参数的分类

缺省参数被分为两种类型:全缺省参数和半缺省参数

1.3.1 全缺省参数

全缺省参数顾名思义就是在声明或定义函数时,给每一个形参都设置缺省值。

c 复制代码
//全缺省参数
void func1(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	cout << endl;
}

1.3.2 半缺省参数

半缺省参数就是部分形参设置缺省值。

这里有的读者可能会认为,半缺省参数是指一半的形参设置缺省值。这种想法时不可取的!!!
半缺省参数是指部分缺省,而不是一半缺省!!!

c 复制代码
//2.半缺省参数(形参部分缺省)
void func2(int a, int b = 2, int c = 3)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;

	cout << endl;
}

1.4 使用缺省参数需要避的"坑"

一个好东西的使用往往是需要付出一些代价的,那我们就来看看缺省参数时我们需要注意一些细节的点。

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给。
cpp 复制代码
//以下都是一些错误的写法
//1.形参并不遵从右往左依次给出
void func(int a = 10, int b = 20, int c);

//2.缺省参数间隔着给
void func(int a = 10, int b, int c = 30);
  1. 缺省参数不能在函数声明和定义中同时出现。

很多人可能对这点不是很理解这个点,那接下来我给大家讲明白这个点。请大家看下面的代码:

c 复制代码
//错误的例子
//test.h
void Func(int a = 10);

//test.c
void Func(int a = 20)
{
	...
}

如果声明和定义位置同时出现缺省参数,但是恰巧两个位置提供的值不一样,那编译器就无法确定到底该采用哪个缺省值。 会出现歧义!

因此,缺省参数不能同时在函数声明和定义中同时出现!

那有的读者就会问出一个这样的问题:那我到底是在函数声明时使用缺省参数,还是在函数定义中使用缺省参数?

答案是,二者选其一即可!只要不是同时使用就行。

  1. 缺省值必须得是常量或者是全局变量。
  2. C语言不支持(编译器不支持)

2. 函数重载

🍉在语言层面来说,什么叫做"重载"?重载就是"一词多义"。

🍉那将这个说法放到"函数"中,大家也许就可以猜出"函数重载"大概是个什么东西了。讲大白话就是虽然两个函数命名是一样的,但是这两个函数实现的功能不完全一样,甚至是截然不同。

2.1 函数重载概念

函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。

下面我来演示一下函数重载:

cpp 复制代码
#include<iostream>
using namespace std;

int Add(int left, int right)
{
 	cout << "int Add(int left, int right)" << endl;
	 return left + right;
}

double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

代码分析:可以看到,我们定义了两个Add的函数,但是仔细一看会发现,他们形参列表中的参数类型不同,因此这两个函数构成了重载。

2.2 函数重载的情况

  1. 🍉参数类型的不同。
cpp 复制代码
// 1、参数类型不同
int Add(int left, int right)
{
	 cout << "int Add(int left, int right)" << endl;
 	 return left + right;
}

double Add(double left, double right)
{
 	cout << "double Add(double left, double right)" << endl;
 	return left + right;
}
  1. 🍉参数个数不同。
cpp 复制代码
//2. 参数个数不同
void f()
{
 	cout << "f()" << endl;
}

void f(int a)
{
 	cout << "f(int a)" << endl;
}
  1. 🍉参数类型的顺序不同。
cpp 复制代码
// 3、参数类型顺序不同
void f(int a, char b)
{
 	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
 	cout << "f(char b, int a)" << endl;
}
int main()
{
 	Add(10, 20);
 	Add(10.1, 20.2);
 	f();
 	f(10);
 	f(10, 'a');
 	f('a', 10);
 	return 0;
}

注意:仅仅是函数返回值不同是不构成函数重载。

比如:int Func(int a, char b)char Func(int a, char b),这两个函数不构成重载。

练习:现在,我给大家下面一段代码,请大家判断这段代码能否通过编译阶段?如果通过了,那能不能正常的执行出结果?

cpp 复制代码
#include<iostream>
using namespace std;

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

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

int main()
{
	Func();//能编译过吗,如果能,那能正常执行吗?
}

答案是:不能通过编译。它会给出下面错误报告

从代码的角度,我们也能够了解确实是这样的。

2.3 函数重载背后的原理

温馨提示:如果你不想深入了解函数重载背后原理的读者,可以直接略过本小节的内容。

本小节重点围绕着一个话题,"为什么C++支持函数重载,而C语言却不支持呢?"

想要知道答案,我们必须得对编译和链接过程有一定的了解。并且我会用使用反汇编代码,给大家逐一的讲解C++和C语言是如何对待函数的。

本节讲解代码的例子:

c 复制代码
//Stack.h
#pragma once
#include<stdio.h>

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

void StackInit(struct Stack* pst);

void StackPush(struct Stack* pst, int x);

void StackPush(struct Stack* pst, double x);


//Stack.c
#include"Stack.h"

void StackInit(struct Stack* pst)
{
	printf("void StackInit(struct Stack* pst)\n");
}


void StackPush(struct Stack* pst, int x)
{
	printf("void StackPush(struct Stack* pst, int x)\n");
}


void StackPush(struct Stack* pst, double x)
{
	printf("void StackPush(struct Stack* pst, double x)\n");
}




//test.c
#include"Stack.h"

int main()
{
	struct Stack st;

	StackInit(&st);

	StackPush(&st, 1);
	StackPush(&st, 1.1);

}

2.3.1 编译和链接

如果对编译和链接感兴趣的话,可以看看往期的这篇文章编译和链接(细节的king)。这里我画一幅图给大家理解:

2.3.2 函数调用的汇编代码讲解

将代码转到反汇编,我们就能看到函数调用的指令:

可以看到是一条名为call的带用指令,我们执行call指令后,他会跳转到另一条指令jmp

继续让代码往下执行,你又会发现一个新大陆:

你会发现跳转到StackInit函数里面了,这个不就是调用函数了!再仔细观察,你还会发现一件事:jmp真得很牛!

现在,我有个问题想要问一下大家:Stack.h的文件里面只包含了函数的声明,那test.c文件里面的main函数在调用函数时,怎么知道目标函数的地址的?

关于这个问题的理解,可以给大家讲一个故事。假设你现在要准备给女朋友结婚急需一笔资金来买房,这时你想到了你大学时期最要好的一个兄弟,你打电话给他。你说最近...,好兄弟说没有问题,过几天我就把钱打给你。听到这个消息之后,你就去交这个房子的定金了。

以上的这个例子,"买房的人"就像是编译器。编译器是不知道这个函数的地址,但是因为接受了这个承诺,编译器就会认为这个函数是存在的。等到链接的过程时,也就是兑现承诺的时候,就会把这个函数的地址告诉给编译器。

那具体是怎么告诉的呢?这个就涉及到链接过程中,会产生出一个名为"符号表"的东西,上面就记录着每个函数对应的地址。兑现承诺就相当于把地址填入到这个表格中!!!

好了,讲了这么多,回归我们的主体:为什么C++支持函数重载,而C语言却不支持呢?

如果你对我上述的讲法理解的话,那么我接下来的这段话就十分的关键了。
在C语言中,它对函数的名的处理是直接采用函数名本身的不加以任何的修饰。而在C++中,它是通过对函数名的修饰,使得函数重载函数的得以区分。也就是在符号表中可以填入不同的函数标记,这个特性就是得C++能够支持函数重载。

接下来我来证明给大家看看,由于VS的编译器将编译链接这个过程给集成化了,所以我用g++编译器给大家显示。


以上是对应的图,大家可以对应着过程看看。

最后我们可以总结一下:之所以C语言不支持函数重载,而C++支持函数重载,是因为它们对函数名修饰规则不一样导致的。


本文的内容到这里就结束了,如果觉得本文对你有帮助的话,麻烦给偶点个赞吧!!!

相关推荐
for_ever_love__6 小时前
UI学习:UISearchController基础了解和应用
学习·ui·ios·objective-c
心中有国也有家6 小时前
GE图引擎深度解析——CANN的计算图优化与执行引擎
人工智能·pytorch·python·学习·numpy
isyangli_blog7 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008117 小时前
FastAPI APIRouter
开发语言·python
Benszen7 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆7 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木7 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
GHL2842710907 小时前
换脸工作流学习
学习·ai
MC皮蛋侠客8 小时前
C++17 多线程系列(五):C++17 并行算法——从串行到并行的零成本迁移
c++·多线程
_李小白8 小时前
【android opencv学习笔记】Day 28: 滤波算法之中值滤波器
android·opencv·学习