在C ++中蛙紫,我們可以使操作符(operator)為用戶定義的類在調(diào)用層可以像一般運算表達式一樣參與運算。 這意味著C ++能夠為操作符提供數(shù)據(jù)類型的特殊含義铲敛,這種能力稱為操作符重載(operator overloading)乡恕。例如,我們可以在string之類的類中重載操作符“ +”亲善,以便僅使用+即可連接兩個字符串。
重載“+”操作符
下面我們用一個類似超市的結(jié)算小程序來作為本小節(jié)的示例
Good類接口
#ifndef GOOD_HH
#define GOOD_HH
#include <string>
class Good
{
std::string d_name;
double d_price;
public:
Good();
Good(std::string, double);
double price() const;
void set_price(double price);
std::string name() const;
bool operator<(const Good &obj) const;
//兩個Good實例 1+Good實例2
Good operator+(Good const &good);
};
#endif
Customer接口
#ifndef CUSTOMER_HH
#define CUSTOMER_HH
#include "../header/good.hh"
#include <iostream>
#include <map>
#include <string>
class Customer
{
std::string d_fullname; //用戶名
double d_pay; //應(yīng)付金額
double d_bonus; //獎勵積分
double d_count; //購買數(shù)量
double d_discnt; //讓利總計
std::map<Good, double> cart;
public:
Customer(std::string, double);
//購買
void buy(const Good, double);
//支付
void payment();
//結(jié)帳信息
void display_info();
private:
//折扣
void discount(const Good &, double);
};
#endif
類實現(xiàn)
下面的重點就是 Good Good::operator+(Good const &obj)的實現(xiàn),“+”操作符的重載函數(shù)內(nèi)部封裝了兩個Good類中的d_name字符串的拼接操作逗柴,以及兩個Good對象的d_price的加法賦值蛹头。
Good Good::operator+(Good const &obj)
{
this->d_price += obj.d_price * 0.95;
this->d_name = this->d_name + "和" + obj.d_name;
}
調(diào)用代碼
int main(int argc, char const *argv[])
{
Good a{"香蕉", 5.7};
Good b{"奇異果", 13.4};
Good c{"榴蓮", 13.5};
Good d{"蘋果", 5.7};
Good e{"奶蕉", 7.7};
Good r = a + e;
Customer p1{"Lisa", 800};
p1.buy(a, 23);
p1.buy(c, 12);
p1.buy(d, 32);
p1.buy(r, 45);
p1.payment();
p1.display_info();
return 0;
}
程序輸出
因此,我們知道重載操作符實質(zhì)上就是重載函數(shù),函數(shù)內(nèi)部封裝了運算對象(即:對象內(nèi)部各種數(shù)據(jù)成員)的對應(yīng)運算操作戏溺。
重載operator[]
除非你是你自己實現(xiàn)類似動態(tài)數(shù)組的順序存儲結(jié)構(gòu),才需要重載索引操作符渣蜗,使用標準庫的的順序容器,重載索引操作符顯得有些畫蛇添足旷祸。
以下是有關(guān)[]重載的一些特殊情況
- 當我們自己實現(xiàn)的順序存儲結(jié)構(gòu),需要檢查索引越界問題耕拷,[]的重載可能很有用。
- 我們必須在函數(shù)中通過引用返回托享,因為像“ arr [i]”之類的表達式可以用作左值骚烧。
關(guān)于重載下標運算符的具體示例可以看我這篇文章,里面說的很詳細
《C++ 數(shù)據(jù)結(jié)構(gòu)--動態(tài)順序表的實現(xiàn)》
重載operator ++
重載增量運算符(operator)和減量運算符(operator--)會帶來一個小問題:每個運算符都有兩個版本,因為它們可以用作后綴運算符(例如x)或用作前綴運算符(例如x)嫌吠。
用作后綴運算符時止潘,該值的對象作為右值掺炭,臨時const對象返回辫诅,且后遞增變量本身從視圖中消失。 用作前綴運算符時涧狮,變量會遞增炕矮,其值將作為左值返回,并且可以通過修改前綴運算符的返回值來再次更改者冤。 盡管在運算符重載時不需要這些特性肤视,但強烈建議在任何重載的增量或減量運算符中實現(xiàn)這些特性。
假設(shè)我們圍繞size_t值類型定義一個包裝器類涉枫。 這樣的類可以提供以下(部分顯示)接口:
class Customer
{
size_t d_bonus; //獎勵積分
public:
Customer(std::string, size_t);
Customer &operator++();
}
類的最后一個成員聲明前綴重載的增量運算符邢滑。 返回的左值是Customer&。 該成員很容易實現(xiàn):
Customer &Customer::operator++()
{
++d_bonus;
return *this;
}
要定義后綴運算符愿汰,需要定義該運算符的重載版本困后,并期望使用(虛擬)int參數(shù)。 這可能被認為是錯誤的衬廷,或者是函數(shù)重載的可接受的應(yīng)用程序摇予。 無論您對此有何看法,都可以得出以下結(jié)論:
- 沒有參數(shù)的重載增量和減量運算符是前綴運算符吗跋,應(yīng)返回對當前對象的引用侧戴。
- 具有int參數(shù)的重載增量和減量運算符是后綴運算符宁昭,并且應(yīng)在使用后綴運算符的位置返回一個值,該值是對象的副本酗宋。
后綴增量運算符在Customer類的接口中聲明如下:
//后綴增量操作符重載
Customer operator++(int);
實現(xiàn)如下,請注意,運算符的參數(shù)并未使用积仗。 在實現(xiàn)和聲明中消除前綴和后綴運算符的歧義只是實現(xiàn)的一部分。
Customer Customer::operator++(int)
{
Customer tmp{*this};
++d_bonus;
return tmp;
}
在上面的示例中蜕猫,增加當前對象的語句提供了空位保證斥扛,因為它僅涉及對原始類型的操作。 如果初始副本構(gòu)造拋出異常丹锹,則原始對象不會被修改稀颁,如果return語句拋出異常,則對象已被安全地修改楣黍。 但是增加一個對象本身可能會引發(fā)異常匾灶。 在這種情況下,如何實現(xiàn)增量運算符租漂? 再次阶女,swap是我們最好的選擇。 當數(shù)據(jù)成員增量執(zhí)行增量操作可能拋出時哩治,以下是前綴和后綴運算符提供了有力的保證:
重載new操作符和delete操作符
當運算符new重載時秃踩,它必須定義一個void *返回類型,并且其第一個參數(shù)的類型必須為size_t业筏。 默認運算符new僅定義一個參數(shù)憔杨,但是重載版本可以定義多個參數(shù)。 第一個沒有明確指定蒜胖,但是從重載了new運算符的類的對象的大小推導(dǎo)得出消别。 在本節(jié)中,將討論重載運算符new台谢。
new和delete操作符的作用域
- 如果使用某個類的成員函數(shù)來重載這些運算符寻狂,則意味著這些運算符僅針對該特定類才被重載
- 如果重載是在類外完成的(即它不是類的成員函數(shù)),則只要您使用這些運算符(在類內(nèi)或類外)朋沮,都將調(diào)用重載的“ new”和“ delete”蛇券。 這是全局超載。
new運算符的函數(shù)原型
void *operator new(size_t n);
重載的new運算符接收的大小為size_t類型樊拓,該大小指定要分配的內(nèi)存字節(jié)數(shù)纠亚。 重載的new的返回類型必須為void *。重載的函數(shù)返回一個指向分配的內(nèi)存塊開頭的指針骑脱。
delete運算符的函數(shù)原型
void operator delete(void*);
該函數(shù)接收一個必須刪除的void *類型的參數(shù)菜枷。 函數(shù)不應(yīng)該返回任何東西。
注意:默認情況下叁丧,重載的new和delete運算符函數(shù)都是靜態(tài)成員啤誊。 因此岳瞭,他們無權(quán)訪問此指針。
類接口
class Good
{
std::string d_name;
double d_price;
public:
Good(std::string, double);
//重載new操作符原型
void *operator new(size_t n);
//重載delete操作符
void operator delete(void *);
void display();
};
類實現(xiàn)
void *Good::operator new(size_t n)
{
std::cout << "重載new操作符" << std::endl;
void *p = ::new Good();
return p;
}
void Good::operator delete(void *p)
{
std::cout << "重載delete操作符" << std::endl;
free(p);
p = NULL;
}
void Good::display()
{
std::cout << "品名:" << d_name << std::endl;
std::cout << "價格:" << d_price << "RMB" << std::endl;
}
調(diào)用代碼
#include "source/good.cpp"
#include <iostream>
int main(int argc, char const *argv[])
{
Good *g = new Good("香焦", 10);
g->display();
delete g;
return 0;
}
在上面的新重載函數(shù)中蚊锹,是通過new運算符分配了動態(tài)內(nèi)存瞳筏,但是它是全局的new運算符,否則它內(nèi)部將遞歸調(diào)用因為new的內(nèi)容將一次又一次地重載牡昆,就像下面的代碼
void *p=new Good(); //錯誤的示例
正確的示例,通過::作用域操作符,從全局調(diào)用new操作符
void *p=::new Good();
在全局作用域重載new和delete操作符
void *operator new(size_t n){
std::cout<<"全局作用域重載new操作符"<<std::endl;
void *p=malloc(n);
return p;
}
void operator delete(void *p){
std::cout<<"全局作用域重載delete操作符"<<std::endl;
free(p);
p=NULL;
}
int main(){
int n=5;
int *p=new int[5];
for(int i=0;i<n;i++){
p[i]=i;
}
for(size_t i=0;i<n;i++){
std::cout<<p[i]<<std::endl;
}
delete p;
}
在上面的代碼中姚炕,在new操作符的重載函數(shù)中,我們無法使用::new int [5]分配內(nèi)存丢烘,因為它將以遞歸方式進行柱宦。 我們只需要使用malloc分配內(nèi)存。
輸出
全局作用域重載new操作符
0 1 2 3 4
全局作用域重載delete操作符
重載new/delete操作符的原因
- 重載的new運算符函數(shù)可以接受參數(shù)播瞳; 因此掸刊,一個類可以具有多個重載的new運算符的能力。 這使程序員在自定義對象的內(nèi)存分配方面具有更大的靈活性赢乓。 例如:
void *operator new(size_t n,
- 重載的new或delete運算符還為類的對象提供了垃圾回收忧侧。
- 可以在重載的新運算符函數(shù)中添加異常處理例程。
- 提供了重載版本的new和delete操作符能夠做一些編譯器默認版本的new和delete不能做的自定義操作牌芋。 例如蚓炬,您可能會編寫一個自定義運算符delete,以用0覆蓋釋放的內(nèi)存躺屁,以提高應(yīng)用程序數(shù)據(jù)的安全性肯夏。
- 可以在新函數(shù)中使用realloc函數(shù)動態(tài)重新分配內(nèi)存。
- 重載的新運算符還使程序員能夠從程序中榨取一些額外的性能楼咳。 例如熄捍,在一個類中,為了加快新節(jié)點的分配速度母怜,維護了一個已刪除節(jié)點的列表,以便在分配新節(jié)點時可以重用它們的內(nèi)存缚柏。在這種情況下苹熏,重載的delete運算符會將節(jié)點添加到列表中 刪除的節(jié)點和重載的new運算符將從列表中分配內(nèi)存,而不是從堆中分配內(nèi)存以加速內(nèi)存分配币喧。 當刪除的節(jié)點列表為空時轨域,可以使用堆中的內(nèi)存。
operator函數(shù)和普通函數(shù)有什么區(qū)別杀餐?
- operator函數(shù)與普通函數(shù)相同
- 唯一的區(qū)別是干发,操作符的名稱始終是操作符operator關(guān)鍵字,后跟operator關(guān)鍵字的符號史翘,并且在使用相應(yīng)的操作符時會調(diào)用operator函數(shù)枉长。
- 幾乎所有的操作符可以重載,但以下C++內(nèi)置的操作符號是無法重載的冀续。
重載操作符的注意事項
- 為了使操作符重載起作用,至少一個操作數(shù)必須是用戶定義的類對象必峰。
- 賦值操作符:編譯器會為每個類自動創(chuàng)建一個默認的賦值操作符洪唐。 默認的賦值操作符確實將右側(cè)的所有成員分配到左側(cè),并且在大多數(shù)情況下都可以正常工作(此行為與復(fù)制構(gòu)造函數(shù)相同)吼蚁。 有關(guān)更多詳細信息凭需,請參見此內(nèi)容。
- 轉(zhuǎn)換操作符:我們還可以編寫可用于將一種類型轉(zhuǎn)換為另一種類型的轉(zhuǎn)換操作符肝匆。
操作員重載規(guī)則
- 僅內(nèi)置操作符可以重載粒蜈。 無法創(chuàng)建新的操作符。
- 操作符的優(yōu)先級和關(guān)聯(lián)性不能更改旗国。
- 重載的操作符不能具有默認參數(shù)薪伏,但函數(shù)調(diào)用operator() 可以具有默認參數(shù)。
- 不能僅對內(nèi)置類型重載操作符粗仓。 必須至少使用一個操作數(shù)定義類型
- 必須將賦值(=)嫁怀,下標([]),函數(shù)調(diào)用(“()”)和成員選擇(->)操作符定義為成員函數(shù)