前言
??個(gè)人主頁:@小沈YO.
??小編介紹:歡迎來到我的亂七八糟小星球??
??專欄:C++ 心愿便利店
??本章內(nèi)容:函數(shù)重載、引用
記得 評(píng)論?? +點(diǎn)贊?? +收藏?? +關(guān)注??哦~
提示:以下是本篇文章正文內(nèi)容,下面案例可供參考
??一、函數(shù)重載
自然語言中,一個(gè)詞可以有多重含義,人們可以通過上下文來判斷該詞真實(shí)的含義,即該詞被重載了。
比如:以前有一個(gè)笑話,國有兩個(gè)體育項(xiàng)目大家根本不用看,也不用擔(dān)心。一個(gè)是乒乓球,一個(gè)是男足。前者是“誰也贏不了!”,后者是“誰也贏不了!"
??1.1.函數(shù)重載概念
函數(shù)重載:是函數(shù)的一種特殊情況,C++允許在同一作用域中聲明幾個(gè)功能類似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個(gè)數(shù) 或 類型 或 類型順序)不同,常用來處理實(shí)現(xiàn)功能類似數(shù)據(jù)類型不同的問題
#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;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
return 0;
}
#include<iostream>
using namespace std;
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(10);
return 0;
}
#include<iostream>
using namespace std;
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()
{
f(10, 'a');
f('a', 10);
return 0;
}
#include<iostream>
using namespace std;
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(int b, char a)
{
cout << "f(int b, char a)" << endl;
}
#include<iostream>
using namespace std;
void f(char a, int b)
{
cout << "f(int a,char b)" << endl;
}
int f(char a, int b)
{
cout << "f(int a, char b)" << endl;
}
int main()
{
return 0;
}
#include<iostream>
using namespace std;
//構(gòu)成函數(shù)重載
void func(int a)
{
cout << "void func(int a)" << endl;
}
void func(int a, int b = 1)
{
cout << "void func(int a,int b)" << endl;
}
int main()
{
func(1,2);
//調(diào)用存在歧義
func(10);
return 0;
}
重載和缺省參數(shù)碰撞是可以構(gòu)成重載的(參數(shù)個(gè)數(shù)不同),但是會(huì)出現(xiàn)調(diào)用歧義(當(dāng)調(diào)用func(1,2)是不會(huì)出現(xiàn)問題的,但是調(diào)用func(10),編譯器就不知道調(diào)用哪個(gè)因?yàn)閮蓚€(gè)都符合調(diào)用)
??1.2.C++支持函數(shù)重載的原理 – 名字修飾
在C/C++中,一個(gè)程序要運(yùn)行起來,需要經(jīng)歷以下幾個(gè)階段:預(yù)處理、編譯、匯編、鏈接,最終形成一個(gè)可執(zhí)行程序
對(duì)于C語言來說,當(dāng)經(jīng)過預(yù)處理、編譯、匯編、鏈接
//#include<iostream>
//using namespace std;
void func(int i, double d)
{
//cout << "void func(int i, double d)" << endl;
}
void func(double d, int i)
{
//cout << "double func(double d, int i)" << endl;
}
int main()
{
func(1, 5.2);
func(5.2, 1);
return 0;
}
#include<iostream>
using namespace std;
void func(int i, double d)
{
//cout << "void func(int i, double d)" << endl;
}
void func(double d, int i)
{
//cout << "double func(double d, int i)" << endl;
}
int main()
{
func(1, 5.2);
func(5.2, 1);
return 0;
}
先提前說明一下,這部分不懂得可以看C語言—程序環(huán)境和預(yù)處理(底層原理萬字詳解)
實(shí)際項(xiàng)目通常是由多個(gè)頭文件和多個(gè)源文件構(gòu)成,而通過C語言階段學(xué)習(xí)的編譯鏈接,我們可以知道,【當(dāng)前a.cpp中調(diào)用了b.cpp中定義的Add函數(shù)時(shí)】,編譯后鏈接前,a.o的目標(biāo)文件中沒有Add的函數(shù)地址,因?yàn)锳dd是在b.cpp中定義的,所以Add的地址在b.o中。那么怎么辦呢?
- 所以鏈接階段就是專門處理這種問題,鏈接器看到a.o調(diào)用Add,但是沒有Add的地址,就會(huì)到b.o的符號(hào)表中找Add的地址,然后鏈接到一起。
- 那么鏈接時(shí),面對(duì)Add函數(shù),鏈接接器會(huì)使用哪個(gè)名字去找呢?這里每個(gè)編譯器都有自己的函數(shù)名修飾規(guī)則。
- 使用g++修飾后的名字通過下面我們可以看出gcc的函數(shù)修飾后名字不變。而g++的函數(shù)修飾后變成【_Z+函數(shù)長度+函數(shù)名+類型首字母】
結(jié)論:在linux下,采用gcc編譯完成后,函數(shù)名字的修飾沒有發(fā)生改變
結(jié)論:在linux下,采用g++編譯完成后,函數(shù)名字的修飾發(fā)生改變,編譯器將函數(shù)參
數(shù)類型信息添加到修改后的名字中
#include<iostream>
using namespace std;
int func(double d, int i)
{
cout << "void func(int i, double d)" << endl;
return 0;
}
void func(double d, int i)
{
cout << "double func(double d, int i)" << endl;
}
int main()
{
func(1.1, 1);
func(1, 1.1);
return 0;
}
- 通過這里就理解了C語言沒辦法支持重載,因?yàn)橥瘮?shù)沒辦法區(qū)分。而C++是通過函數(shù)修飾規(guī)則來區(qū)分,只要參數(shù)不同,修飾出來的名字就不一樣,就支持了重載。
- 如果兩個(gè)函數(shù)函數(shù)名和參數(shù)是一樣的,返回值不同是不構(gòu)成重載的,因?yàn)檎{(diào)用時(shí)編譯器沒辦法區(qū)分。
??二、引用
??2.1.引用的概念
引用不是新定義一個(gè)變量,而是給已存在變量取了一個(gè)別名,編譯器不會(huì)為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間。
比如:孫悟空,唐僧稱為"悟空",江湖上人稱"齊天大圣"。
類型& 引用變量名(對(duì)象名) = 引用實(shí)體;
void TestRef()
{
int a = 10;
int& ra = a;//<====定義引用類型
printf("%p\n", &a);
printf("%p\n", &ra);
}
??2.2.引用特性
- 引用在定義時(shí)必須初始化
- 一個(gè)變量可以有多個(gè)引用
- 引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體
void TestRef()
{
1. 引用在定義時(shí)必須初始化
int a = 10;
// int& ra; // 該條語句編譯時(shí)會(huì)出錯(cuò)
2. 一個(gè)變量可以有多個(gè)引用
int& ra = a;//給a取別名
int& rra = a;//給a取別名
int& raa = ra;//給b(a的別名)取別名也是可以的
3. 引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體(C++的引用不可以改變指向但是Java可以)
int x = 1;
b = x;
//這里是賦值而不是把b變成x的別名
}
??2.3.常引用
void TestConstRef()
{
權(quán)限的放大:就相當(dāng)于帶上金箍圈的孫悟空(const)摘下了金箍圈變得肆無忌憚
const int a = 10;
//int& ra = a; // 該語句編譯時(shí)會(huì)出錯(cuò),a為常量 --- 權(quán)限的放大在這里去掉了const也就是去掉了金箍圈
const int& ra = a;--- 權(quán)限的平移:帶上金箍圈無論是孫悟空還是齊天大圣它都有限制不會(huì)肆無忌憚
權(quán)限的縮?。罕緛硎谴篝[天宮的齊天大圣被戴上了金箍圈(const)就有了限制
int x=10;
const int& y=x;
double d = 12.34;
//int& rd = d; // 該語句編譯時(shí)會(huì)出錯(cuò),類型不同
const int& rd = d;
在C/C++中有規(guī)定:發(fā)生類型轉(zhuǎn)換,會(huì)產(chǎn)生一個(gè)臨時(shí)變量,例如上面這一小段代碼const int& rd = d,轉(zhuǎn)換時(shí)會(huì)有一個(gè)int類型的臨時(shí)變量,臨時(shí)變量再給rd,但是臨時(shí)變量具有常性int& rd = d這里就是權(quán)限的放大不能通過編譯,所以這種是對(duì)的const int& rd = d
// int& b = 10; // 該語句編譯時(shí)會(huì)出錯(cuò),b為常量 - 不能變成常量對(duì)象的別名
const int& b = 10;--- 權(quán)限平移
}
在引用的過程中:
- 權(quán)限可以平移
- 權(quán)限可以縮小
- 權(quán)限不可以放大
#include<iostream>
using namespace std;
int func()
{
int a = 0;
return a;
}
int main()
{
const int& ret = func();
return 0;
}
??2.4.使用場(chǎng)景
#include<iostream>
using namespace std;
void Swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
int main()
{
int i = 3, j = 6;
Swap(i, j);
cout << i << endl;
cout << j << endl;
return 0;
}
實(shí)際原理:是會(huì)生成一個(gè)臨時(shí)變量(可能寄存器充當(dāng)也可能其他方式),n會(huì)在返回值之前拷貝給臨時(shí)變量,臨時(shí)變量不會(huì)在Count函數(shù)的棧幀,一般是在寄存器或者上一層函數(shù)的棧幀
#include<iostream>
using namespace std;
int Count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int ret = Count();
return 0;
}
會(huì)出現(xiàn)類似野指針的危險(xiǎn):n都銷毀了,在訪問n的別名
問:n都銷毀了還能訪問它的別名嗎?
答:可以,因?yàn)?mark>空間銷毀并不是這塊空間就沒了而是被系統(tǒng)回收,就像酒店里的房間,退房后房間不會(huì)消失而是被回收你的入住權(quán)租給別人或者空著,而野指針就是退房后你還偷偷藏了房間的鑰匙,然后偷偷跑進(jìn)房間。但是這里不是野指針,返回n的別名是不合法的
#include<iostream>
using namespace std;
int& Count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int ret = Count();
cout << ret << endl;
cout << ret << endl;
return 0;
}
程序的結(jié)果有兩種可能:1和隨機(jī)值------>調(diào)用函數(shù)返回n的別名,當(dāng)int ret=Count(),函數(shù)Count()棧幀已經(jīng)銷毀了,再去訪問這塊空間就會(huì)出現(xiàn)兩種可能性第一種是1拷貝給ret,還有一種可能是隨機(jī)值(取決于棧幀銷毀后空間是否會(huì)被置成隨機(jī)值取決于編譯系統(tǒng))
這種代表ret也是n的別名,第一次訪問打印ret是1,第二次就變成了隨機(jī)值為什么
知識(shí)點(diǎn)補(bǔ)充:cout是一個(gè)函數(shù)調(diào)用,(調(diào)用函數(shù)先傳參)第一次先傳參取到的還是1然后進(jìn)行函數(shù)調(diào)用,Count函數(shù)的棧幀銷毀,第一次函數(shù)調(diào)用占用的還是那塊空間只不過可能比之前Count函數(shù)棧幀大或者小此時(shí)函數(shù)調(diào)用覆蓋這塊空間而ret還是這塊空間的別名所以取到的就是一個(gè)隨機(jī)值,但也不一定是隨機(jī)值,當(dāng)Count函數(shù)棧幀很大n在下面,就不會(huì)被覆蓋
#include<iostream>
using namespace std;
int& Count()
{
int n = 0;
n++;
// ...
return n;
}
int main()
{
int& ret = Count();
cout << ret << endl;
cout << ret << endl;
return 0;
}
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :"<< ret <<endl;
return 0;
}
注意:如果函數(shù)返回時(shí),出了函數(shù)作用域,如果返回對(duì)象還在(還沒還給系統(tǒng)),則可以使用引用返回,如果已經(jīng)還給系統(tǒng)了,則必須使用傳值返回。
??2.5.傳值、傳引用效率比較
以值作為參數(shù)或者返回值類型,在傳參和返回期間,函數(shù)不會(huì)直接傳遞實(shí)參或者將變量本身直接返回,而是傳遞實(shí)參或者返回變量的一份臨時(shí)的拷貝,因此用值作為參數(shù)或者返回值類型,效率是非常低下的,尤其是當(dāng)參數(shù)或者返回值類型非常大時(shí),效率就更低
#include<iostream>
using namespace std;
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作為函數(shù)參數(shù)
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作為函數(shù)參數(shù)
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分別計(jì)算兩個(gè)函數(shù)運(yùn)行結(jié)束后的時(shí)間
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
#include<iostream>
using namespace std;
#include <time.h>
struct A { int a[10000]; };
A a;
//值返回
A TestFunc1() { return a; }
//引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
//以值作為函數(shù)的返回值類型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
//以引用作為函數(shù)的返回值類型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
//計(jì)算兩個(gè)函數(shù)運(yùn)算完成之后的時(shí)間
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
TestReturnByRefOrValue();
return 0;
}
通過上述代碼的比較,發(fā)現(xiàn)傳值和指針在作為傳參以及返回值類型上效率相差很大。
傳引用傳參(任何時(shí)候都可以用)
- 提高效率
- 輸出型參數(shù)(形參的修改,影響的實(shí)參)
傳引用返回(出了函數(shù)作用域?qū)ο筮€在才可以用)
- 提高效率
- 修改返回對(duì)象(末尾彩蛋處有體現(xiàn))
??2.6.引用和指針的區(qū)別
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int& ra = a;
cout << "&a = " << &a << endl;
cout << "&ra = " << &ra << endl;
return 0;
}
#include<iostream>
using namespace std;
int main()
{
int a = 0;
int* p1 = &a;
int& ref = a;
++(*p1);
++ref;
return 0;
}
引用和指針的不同點(diǎn):文章來源:http://www.zghlxwxcb.cn/news/detail-670669.html
- 引用概念上定義一個(gè)變量的別名,指針存儲(chǔ)一個(gè)變量地址。
- 引用在定義時(shí)必須初始化,指針沒有要求
- 引用在初始化時(shí)引用一個(gè)實(shí)體后,就不能再引用其他實(shí)體,而指針可以在任何時(shí)候指向任何一個(gè)同類型實(shí)體
- 沒有NULL引用,但有NULL指針
- 在sizeof中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個(gè)數(shù)(32位平臺(tái)下占4個(gè)字節(jié))
- 引用自加即引用的實(shí)體增加1,指針自加即指針向后偏移一個(gè)類型的大小
- 有多級(jí)指針,但是沒有多級(jí)引用
- 訪問實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來相對(duì)更安全
??三、末尾彩蛋(帶你回溯時(shí)空聯(lián)想之前)
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
struct SeqList
{
int a[10];
int size;
};
//讀取第i個(gè)位置的接口
int SLAT(struct SeqList* ps, int i)
{
assert (i <ps->size) ;
return ps->a [i];
}
//修改第i個(gè)位置的接口
void SLModify(struct SeqList* ps, int i, int x)
{
assert(i < ps->size);
ps->a[i] = x;
}
int main()
{
return 0;
}
#include<iostream>
#include<assert.h>
#include<stdlib.h>
using namespace std;
struct SeqList
{
int a[10];
int size;
};
int& SLAT(struct SeqList& ps, int i)
{
assert(i < ps.size);
return (ps.a[i]);
}
int main()
{
struct SeqList s;
s.size = 3;
SLAT(s, 0) = 10;
SLAT(s, 1) = 20;
SLAT(s, 2) = 30;
cout << SLAT(s, 0) << endl;
cout << SLAT(s, 1) << endl;
cout << SLAT(s, 2) << endl;
return 0;
}
文章來源地址http://www.zghlxwxcb.cn/news/detail-670669.html
- 讀取i位置:減少了拷貝,返回此時(shí)位置的別名
- 修改i位置:數(shù)組中第i個(gè)位置的值出了作用域肯定還在,因?yàn)榻Y(jié)構(gòu)體在外面不在函數(shù)的棧幀里面所以存在,出了作用域不會(huì)銷毀,得到它的別名后,通過賦值加加等就會(huì)修改
- 如果不用引用返回的就是它的臨時(shí)拷貝打印是沒有問題的修改卻是不可以的因?yàn)榕R時(shí)對(duì)象具有常性不能修改
到了這里,關(guān)于【C++心愿便利店】No.2---函數(shù)重載、引用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!