文章目录
前言1. 缺省参数1.1 为什么要有缺省函数?1.2 什么是缺省参数?1.3 缺省参数的分类1.3.1 全缺省参数1.3.2 半缺省参数 1.4 使用缺省参数需要避的"坑" 2. 函数重载2.1 函数重载概念2.2 函数重载的情况2.3 函数重载背后的原理2.3.1 编译和链接2.3.2 函数调用的汇编代码讲解
前言
在我们学习C++的命名空间之后 ,我们知道这是一个解决C语言中无法解决的问题,这个问题被我们称之为“命名冲突”。
那么在本章中 ,我们继续讲解一些在C语言中无法解决的问题,来看看本贾尼大佬(C++的创造者)是怎么解决这些问题的。
1. 缺省参数
1.1 为什么要有缺省函数?
相信大家在学习C语言中,一定遇到过一个这样的苦恼。比如我现在在使用着一个自定义的申请动态内存的空间函数,在C语言中,就只能乖乖的给函数传递实参(一个指针变量和需要开辟的空间大小)。突然有一天,我不想再给这个函数传递需要开辟的空间大小的那个实参了,但是如果不将参数全部传完的话,在C语言的视角中你这个就是一个语法错误了。
本贾尼大佬也是受够了C语言的这种设计,为此缺省参数就在C++中诞生了。缺省参数解决的问题就是当我不想给函数传递对应的参数时,会采取某种机制使得编译器认为你已经给这个函数对应的参数。 那至于是什么机制,大家不妨接着往下看!?
1.2 什么是缺省参数?
缺省参数是声明或定义函数时为函数的参数指定一个缺省值(默认值)。在调用该函数时,如果我们没有指定实参的话则采用该形参的缺省值(默认值),否则就使用实参的值。
给大家想感受一下缺省参数的魅力:
#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 全缺省参数
全缺省参数顾名思义就是在声明或定义函数时,给每一个形参都设置缺省值。
//全缺省参数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 半缺省参数
半缺省参数就是部分形参设置缺省值。
这里有的读者可能会认为,半缺省参数是指一半的形参设置缺省值。这种想法时不可取的!!!
半缺省参数是指部分缺省,而不是一半缺省!!!
//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.形参并不遵从右往左依次给出void func(int a = 10, int b = 20, int c);//2.缺省参数间隔着给void func(int a = 10, int b, int c = 30);
缺省参数不能在函数声明和定义中同时出现。 很多人可能对这点不是很理解这个点,那接下来我给大家讲明白这个点。请大家看下面的代码:
//错误的例子//test.hvoid Func(int a = 10);//test.cvoid Func(int a = 20){...}
如果声明和定义位置同时出现缺省参数,但是恰巧两个位置提供的值不一样,那编译器就无法确定到底该采用哪个缺省值。 会出现歧义!
因此,缺省参数不能同时在函数声明和定义中同时出现!
那有的读者就会问出一个这样的问题:那我到底是在函数声明时使用缺省参数,还是在函数定义中使用缺省参数?
答案是,二者选其一即可!只要不是同时使用就行。
缺省值必须得是常量或者是全局变量。C语言不支持(编译器不支持)2. 函数重载
?在语言层面来说,什么叫做“重载”?重载就是“一词多义”。
?那将这个说法放到"函数"中,大家也许就可以猜出“函数重载”大概是个什么东西了。讲大白话就是虽然两个函数命名是一样的,但是这两个函数实现的功能不完全一样,甚至是截然不同。
2.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
下面我来演示一下函数重载:
#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、参数类型不同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;}
?参数个数不同。 //2. 参数个数不同void f(){ cout << "f()" << endl;}void f(int a){ cout << "f(int a)" << endl;}
?参数类型的顺序不同。 // 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)
,这两个函数不构成重载。
练习:现在,我给大家下面一段代码,请大家判断这段代码能否通过编译阶段?如果通过了,那能不能正常的执行出结果?
#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语言是如何对待函数的。
本节讲解代码的例子:
//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++支持函数重载,是因为它们对函数名修饰规则不一样导致的。
本文的内容到这里就结束了,如果觉得本文对你有帮助的话,麻烦给偶点个赞吧!!!