寫在前面:
上一篇文章我介紹了C++該怎么學(xué),什么是命名空間,以及C++的輸入輸出,
這里是傳送門:http://t.csdn.cn/Oi6V8
這篇文章我們繼續(xù)來(lái)學(xué)習(xí)C++的基礎(chǔ)知識(shí)。
目錄
寫在前面:
1. 缺省參數(shù)
2. 函數(shù)重載
3. C++是如何支持函數(shù)重載的
寫在最后:
1. 缺省參數(shù)
在學(xué)習(xí)C語(yǔ)言的時(shí)候,如果一個(gè)函數(shù)存在參數(shù),
比如說(shuō)這個(gè)函數(shù):
void Func(int a) {}
我們?cè)谡{(diào)用的時(shí)候就一定要給他傳參,
而C++提供的缺省參數(shù),能讓我們對(duì)函數(shù)的傳參更加靈活,
舉個(gè)例子:
#include <iostream>
using namespace std;
void Func(int a = 10) {
cout << a << endl;
}
int main()
{
Func(); //沒(méi)有傳參的時(shí)候,使用參數(shù)的默認(rèn)值
Func(20);//傳參的時(shí)候,使用指定的實(shí)參
return 0;
}
我們給函數(shù)設(shè)置一個(gè)缺省值,這樣在我們不給函數(shù)傳參的時(shí)候,
函數(shù)的形參會(huì)自動(dòng)使用缺省參數(shù),而如果我們自己給函數(shù)傳參,
函數(shù)形參使用的就是我們指定或者說(shuō)傳給他的值。
輸出:
10
20
那如果有多個(gè)函數(shù)參數(shù)的函數(shù)呢?
來(lái)看下一個(gè)例子:?
#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;
}
如果是這樣的一個(gè)函數(shù),
我們傳參的時(shí)候能不能只傳一部分呢?
來(lái)看例子:
#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
我們發(fā)現(xiàn)這樣是可行的,
那如果我想要跳著傳呢?
比如說(shuō)這樣傳上面函數(shù)的參數(shù):
Func(1, , 2);
這樣是不行的,會(huì)報(bào)錯(cuò),
實(shí)際上,我們只能按順序來(lái)傳,從左往右依次傳參,其他都是不行的。
你可能會(huì)有疑問(wèn),為什么要這樣設(shè)計(jì),跳著傳好像也沒(méi)什么???
我也不知道為啥,因?yàn)镃++祖師爺就是這么規(guī)定的,可能祖師爺不喜歡吧。
我們上述的缺省參數(shù)的用法其實(shí)叫全缺省,
我們還可以用半缺省,也就是一些參數(shù)給缺省值,一些參數(shù)不給,
舉個(gè)例子:
#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;
}
可別搞錯(cuò)了哦,半缺省只是一部分參數(shù)不使用缺省參數(shù),
而不是真的一半的參數(shù)。
這里就有有一個(gè)規(guī)定,缺省也必須是連續(xù)的,
而且缺省必須是從右往左的缺省,不然就會(huì)報(bào)錯(cuò):
比如說(shuō)這樣子,編譯器就會(huì)報(bào)錯(cuò):
void Func(int a = 10, int b, int c = 30) {}
這里要分清楚:
缺省,是要從右往左缺省
傳參,是要從左往右傳參
那為什么祖師爺要設(shè)置這樣一個(gè)語(yǔ)法呢?
實(shí)際上,這個(gè)語(yǔ)法在一些場(chǎng)景還是非常有用的,
我們來(lái)看這樣一個(gè)場(chǎng)景,
比如說(shuō)我們要對(duì)一個(gè)棧進(jìn)行初始化:
#include <iostream>
using namespace std;
struct Stack {
int* a;
int top;
int capacity;
};
//初始化一個(gè)棧
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);
//然后我們之后要對(duì)棧插入100個(gè)數(shù)據(jù)
return 0;
}
如果我們明知道之后就要往棧里插入100個(gè)數(shù)據(jù),
而我們初識(shí)化默認(rèn)就是初始化大小是4個(gè)整形,
那之后插入數(shù)據(jù)的過(guò)程就會(huì)頻繁擴(kuò)容導(dǎo)致不必要的損耗,
如果我們多加一個(gè)參數(shù):
#include <iostream>
using namespace std;
struct Stack {
int* a;
int top;
int capacity;
};
//初始化一個(gè)棧
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);
//然后我們之后要對(duì)棧插入100個(gè)數(shù)據(jù)
return 0;
}
這樣如果我們有需要就可以直接指定開(kāi)辟空間大小,
不需要的時(shí)候不傳第二個(gè)參數(shù),也能自動(dòng)使用默認(rèn)的大小初始化。
這個(gè)問(wèn)題就很好的解決了。
這里再補(bǔ)充一嘴,C語(yǔ)言的時(shí)候我們其實(shí)通常是這樣解決這種問(wèn)題的:
#include <iostream>
using namespace std;
#define DEFAULT_CAPACITY 100
struct Stack {
int* a;
int top;
int capacity;
};
//初始化一個(gè)棧
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);
//然后我們之后要對(duì)棧插入100個(gè)數(shù)據(jù)
return 0;
}
通過(guò)定義一個(gè)宏的形式,
這樣如果要修改初識(shí)化大小,就只用修改宏定義就行,
但是這樣是沒(méi)有C++這種用法靈活,
如果我們要?jiǎng)?chuàng)建兩個(gè)棧,一個(gè)容量100,一個(gè)容量4的時(shí)候,他就做不到了:
#include <iostream>
using namespace std;
struct Stack {
int* a;
int top;
int capacity;
};
//初始化一個(gè)棧
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);
//然后我們之后要對(duì)棧插入100個(gè)數(shù)據(jù)
struct Stack st2;
StackInit(&st2);
return 0;
}
在這個(gè)場(chǎng)景下,使用C++就非常的舒適。
當(dāng)然啦,我們也不能說(shuō)C語(yǔ)言就不好,C語(yǔ)言也是有他自己獨(dú)特的優(yōu)勢(shì)的。
這里還有一個(gè)細(xì)節(jié)要注意,
在使用缺省參數(shù)的時(shí)候,聲明和定義不能都給缺省。
那是給聲明還是給定義缺省值呢?
我就直接說(shuō)了,只能給聲明缺省值,
我來(lái)解釋一下為什么,我們調(diào)用函數(shù)的時(shí)候,其實(shí)看到的就是聲明,
如果需要傳參就傳參,如果有缺省值沒(méi)傳參,傳的參數(shù)就自動(dòng)變成缺省參數(shù)的值,
而定義不關(guān)心這些,定義只知道你一定要給我傳兩個(gè)參數(shù),
所以我們只給聲明缺省值。
#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);
//然后我們之后要對(duì)棧插入100個(gè)數(shù)據(jù)
struct Stack st2;
StackInit(&st2);
return 0;
}
//初始化一個(gè)棧
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. 函數(shù)重載
什么是函數(shù)重載,我們來(lái)看一個(gè)例子:
#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
這個(gè)就是函數(shù)重載,
我們發(fā)現(xiàn)這兩個(gè)函數(shù)函數(shù)名相同,但是參數(shù)類型卻不同,
C語(yǔ)言是不允許同名函數(shù)的,而C++函數(shù)重載可以支持這個(gè)語(yǔ)法,
在函數(shù)調(diào)用的時(shí)候,能夠根據(jù)你傳的參數(shù)自動(dòng)匹配類型。
補(bǔ)充:函數(shù)重載對(duì)函數(shù)返回值沒(méi)有要求,也就是返回值不同是不構(gòu)成重載的。
這里我直接總結(jié)出函數(shù)重載的規(guī)則,記住就行了:
1. 在同一個(gè)作用域
2. 函數(shù)名相同
3. 參數(shù)的類型不同或者類型的個(gè)數(shù)或者順序不同
這個(gè)時(shí)候就出現(xiàn)了有趣的情況,
來(lái)看例子:
#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;
}
你覺(jué)得這段代碼構(gòu)成重載嗎?
答案是構(gòu)成的,因?yàn)樗现剌d的規(guī)則,是可以編譯通過(guò)的,
但是,如果我們無(wú)參調(diào)用這個(gè)函數(shù)呢? 編譯器就會(huì)直接報(bào)錯(cuò):
f()
不能這樣子調(diào)用,因?yàn)檫@樣存在調(diào)用歧義。
其實(shí)函數(shù)重載就這一點(diǎn)點(diǎn)知識(shí),已經(jīng)講完了,
但是,有一個(gè)問(wèn)題,為什么C語(yǔ)言不支持重載,而C++能支持重載呢?
C++是怎么支持重載的呢?
其實(shí)是在編譯鏈接的過(guò)程,函數(shù)名修飾規(guī)則有所不同。
3. C++是如何支持函數(shù)重載的
這里我需要先做的一個(gè)小的鋪墊內(nèi)容,
我們?cè)谶M(jìn)行函數(shù)調(diào)用的時(shí)候,調(diào)用函數(shù)的底層是怎么樣的?
比如說(shuō)這一段代碼:
#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;
}
來(lái)看匯編代碼:
我們可以看到,匯編實(shí)際上是使用call 指令來(lái)調(diào)用函數(shù)的,
然后在調(diào)用 jump 指令跳轉(zhuǎn)到函數(shù)的定義:
?
這個(gè)時(shí)候我們就進(jìn)入函數(shù)了:
?那么了解函數(shù)是怎么調(diào)用之后,我們?cè)倮^續(xù)探究函數(shù)重載,?
這里我采用gcc環(huán)境來(lái)進(jìn)行演示,
先來(lái)看C語(yǔ)言,還是這段代碼:
#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;
}
這樣子肯定是編譯不通過(guò)的,
看到這個(gè)報(bào)錯(cuò),conflicing types,其實(shí)就是函數(shù)名沖突了,
我們修改一下代碼,讓他能夠編譯通過(guò):
?
#include <stdio.h>
void f(int a, double b) {
printf("f(int a, double b)\n");
}
int main()
{
f(1, 1.1);
return 0;
}
來(lái)看看他的匯編代碼是怎么樣的:
?我們通過(guò)匯編可以看到,匯編代碼中 call 的這個(gè)函數(shù)的函數(shù)名是 f?
跟我們?cè)O(shè)置的函數(shù)名是相同的,
要是我們定義了兩個(gè)函數(shù)名相同的函數(shù),那 call < f > ,究竟call 的是誰(shuí)?
那就會(huì)出現(xiàn)函數(shù)命名的沖突問(wèn)題,
但是這些只是我們現(xiàn)在的推測(cè),接下來(lái)我們看看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++的環(huán)境(C++兼容C語(yǔ)言)
看看我們發(fā)現(xiàn)了什么?
兩個(gè)同樣的函數(shù),到了C++環(huán)境下編譯出來(lái)的匯編代碼,他的函數(shù)名怎么這么奇怪?
上面是帶有兩個(gè)函數(shù)參數(shù)的函數(shù) f (int a, double b)?
我們?cè)賮?lái)看看那個(gè)帶著一個(gè)函數(shù)參數(shù)的同名函數(shù) f (int a):
發(fā)現(xiàn)了嗎?
他們?cè)贑++代碼中函數(shù)名是相同的,
但是到了匯編代碼這里,函數(shù)名卻不一樣了,使用 call 指令調(diào)用的函數(shù)就不一樣了,
這樣就沒(méi)有所謂的函數(shù)名沖突的問(wèn)題了,
現(xiàn)在你應(yīng)該大致理解為什么C語(yǔ)言不支持同名函數(shù)了,
C語(yǔ)言下編譯出來(lái)的匯編代碼的函數(shù)名是跟C語(yǔ)言代碼寫的函數(shù)名是相同的,
就自然不支持同名函數(shù),而C++編譯出來(lái)的匯編代碼展現(xiàn)的函數(shù)名明顯不相同。
我們?cè)僮屑?xì)看看:
?
?可以看到他的命名規(guī)則還是有一點(diǎn)講究的。
不過(guò)他的命名規(guī)則的細(xì)節(jié)我就不深究了,最重要的是理解函數(shù)重載的底層是怎么樣的。
祖師爺創(chuàng)造這些語(yǔ)法還是有跡可循的,
同時(shí)我們也能感受到,真正設(shè)計(jì)一個(gè)語(yǔ)言還是非常困難的,
需要對(duì)各方面的知識(shí)有著深入的理解。
這里還是補(bǔ)充一嘴:學(xué)習(xí)C++的時(shí)候,多學(xué)底層還是非常重要的,
我們要做到:知其然,知其所以然,這樣才能體現(xiàn)我們學(xué)習(xí)的優(yōu)勢(shì),算是我的一些感想吧。
補(bǔ)充:
這個(gè)時(shí)候我們又能理解一個(gè)點(diǎn),
函數(shù)重載為什么不能支持函數(shù)返回值不同呢?一定要返回值相同才能重載。
我們剛剛分析的匯編代碼中的函數(shù)名修飾規(guī)則,
實(shí)際上匯編在調(diào)用函數(shù)的時(shí)候,就是通過(guò)call 指令查找函數(shù)的過(guò)程,
來(lái)看這段代碼:
#include <stdio.h>
void func();
int func();
int main()
{
func();
return 0;
}
返回值在調(diào)用的時(shí)候不會(huì)體現(xiàn),
也就是說(shuō)我們?cè)谡{(diào)用 func() 函數(shù)的時(shí)候,我們不知道調(diào)用的是哪一個(gè),
如果參數(shù)不同,編譯器至少知道這是兩個(gè)不同的函數(shù),
到 call 的時(shí)候才可能出現(xiàn)查找函數(shù)出現(xiàn)歧義,
而調(diào)用 func() 這個(gè)函數(shù),編譯器就不知道究竟想調(diào)用哪個(gè)函數(shù),
所以在編譯階段就會(huì)直接報(bào)錯(cuò),
所以就算把返回值加入函數(shù)名修飾規(guī)則,編譯也走不到那一步。
來(lái)看一眼編譯器是怎么說(shuō)的:
?直接給你飄紅了,還貼心的告訴你無(wú)法支持按返回類型區(qū)分的函數(shù)重載。
這里補(bǔ)充一句:
為什么我不在Windows下或者說(shuō)VS下探究這個(gè)函數(shù)名修飾規(guī)則,
而是跑到了gcc/g++環(huán)境下去看呢?
因?yàn)閂S他的函數(shù)名修飾規(guī)則比較復(fù)雜,沒(méi)有g(shù)++那么清晰,
如果你感興趣的話可以上網(wǎng)搜一下VS下的函數(shù)名修飾規(guī)則,這里就不展示了。
寫在最后:
以上就是本篇文章的內(nèi)容了,感謝你的閱讀。
如果感到有所收獲的話可以給博主點(diǎn)一個(gè)贊哦。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-547485.html
如果文章內(nèi)容有遺漏或者錯(cuò)誤的地方歡迎私信博主或者在評(píng)論區(qū)指出~文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-547485.html
到了這里,關(guān)于【C++學(xué)習(xí)】C++入門 | 缺省參數(shù) | 函數(shù)重載 | 探究C++為什么能夠支持函數(shù)重載的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!