【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++支持函数重载,是因为它们对函数名修饰规则不一样导致的。


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

相关推荐
Wx120不知道取啥名15 分钟前
C语言之长整型有符号数与短整型有符号数转换
c语言·开发语言·单片机·mcu·算法·1024程序员节
Python私教42 分钟前
Flutter颜色和主题
开发语言·javascript·flutter
代码吐槽菌43 分钟前
基于SSM的汽车客运站管理系统【附源码】
java·开发语言·数据库·spring boot·后端·汽车
Ws_1 小时前
蓝桥杯 python day01 第一题
开发语言·python·蓝桥杯
zdkdchao1 小时前
jdk,openjdk,oraclejdk
java·开发语言
神雕大侠mu2 小时前
函数式接口与回调函数实践
开发语言·python
Y.O.U..2 小时前
STL学习-容器适配器
开发语言·c++·学习·stl·1024程序员节
小魏冬琅2 小时前
探索面向对象的高级特性与设计模式(2/5)
java·开发语言
lihao lihao2 小时前
C++stack和queue的模拟实现
开发语言·c++
TT哇2 小时前
【Java】数组的定义与使用
java·开发语言·笔记