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