?C++重載運算符和重載函數
C++的運算符重載在維基百科中的定義如下:
在計算機程序設計中,運算符重載(英語:operator overloading)是多態的一種。這里,運算符(比如+,=或==)被當作多態函數,它們的行為隨著其參數類型的不同而不同。運算符并不一定總是符號。
運算符重載通常只是一種語法糖。它可以簡單地通過函數調用來模擬:
a + b * c
在一個支持運算符重載的語言里,上面的寫法要比下面的寫法有效而簡練:
add(a, multiply(b, c))
(假設運算符* 的優先級高于運算符 +)
當一種語言允許運算符在某種情況下被隱式調用的時候,運算符重載將不只提供寫法上的方便。例如,Ruby中的to_s運算符就是如此,它返回一個對象的字符串表示。
C++中的函數重載在維基百科中的定義如下:
函數重載(英語:function overloading),是Ada、C++、C#、D和Java等編程語言中具有的一項特性,這項特性允許創建數項名稱相同但輸入輸出類型或個數不同的子程序,它可以簡單地稱為一個單獨功能可以執行多項任務的能力。
C++允許在同一個作用域中的某個函數或者運算符指定多個定義,這樣的方式分別被稱為函數重載和運算符重載。
重載聲明是指的一個與之前已經在這個作用域內聲明過的函數或者方法具有相同的名稱的聲明,但是他們的參數和定義并不完全相同。
當調用一個重載函數和重載運算發的時候,編譯器會通過把使用的參數類型的定義中的參數類型進行比較,最終選定最合適的定義,選擇最合適的重載函數或者重載運算符的過程,這樣的過程成員為重載決策。
C++中的函數重載
在同一個作用域內,可以聲明幾個功能類似的同名函數,但是這些同名函數的參數(參數個數,參數類型,參數順序)必須不同,同時不能僅僅通過返回類型的不同來重載函數。
接下來我們看一段函數重載的代碼:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
class printData {
public:
void print(int number){
cout << number <<"n";
}
void print(double number){
cout << number <<"n";
}
void print(string s){
cout << s <<"n";
}
};
int main(){
printData test;
test.print(1);
test.print(1.1);
test.print("abcde");
return0;
}
通過上面的代碼,即使函數名相同,單數傳入的參數類型不同,那么程序調用的函數也不相同,程序的執行結果也不相同,這就是C++中的函數名重載。
C++中的運算符重載
在C++中額可以重載或重定義大多數的內置的運算符,這樣我們就可以使用自定義類型的運算符。
重載運算符的函數通常是帶有特殊函數名的函數,函數是由關鍵字operator和后面的運算符符合組成的,和普通函數相同,重載的運算符有一個返回類型和參數列表。
形式如下:
Boxoperator+(constBox&);
重載加法運算是用于將兩個Box類型的對象進行相加并最終返回Box類型的對象,大多數的重載運算符可以被定義為普通的非成員函數或者類內的成員函數,如果定義的函數是類的非成員函數,那么我們需要在每次執行函數的時候向函數內傳遞兩個參數,形式如下所示:
Boxoperator+(constBox&,constBox&);
下面的代碼通過使用成員函數進行了運算符重載,此時,對象作為參數進行傳遞,代碼如下所示:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classBox{
private:
int length, breadth, height;
public:
int get_volume(){
return length * breadth * height;
}
void set_length(int l){
length = l;
}
void set_breadth(int b){
breadth = b;
}
void set_height(int h){
height = h;
}
Boxoperator+(Box& add_box){
Box box;
box.length = add_box.length + length, box.breadth = add_box.breadth + breadth, box.height = add_box.height + height;
return box;
}
};
int main(){
Box box1, box2, box3;
box1.set_length(1), box1.set_breadth(2), box1.set_height(3);
box2.set_length(4), box2.set_breadth(5), box2.set_height(6);
box3 = box1 + box2;
cout <<"The volume of box1 is: "<< box1.get_volume()<<"n";
cout <<"The volume of box2 is: "<< box2.get_volume()<<"n";
cout <<"The volume of box3 is: "<< box3.get_volume()<<"n";
return0;
}
可重載運算符/不可重載運算符
下面列出C++中可重載和不可重載的運算符。
可重載運算符:
運算符類型 | 運算符名稱 |
---|---|
算數運算符 | + - * / % |
關系運算符 | == != < > <= >= |
邏輯運算符 | |
單目運算符 | + - * & |
自增運算符 | ++ -- |
位運算符 | |
賦值運算符 | = += -= *= /= %= &= |
空間申請和釋放 | new delete new[] delete[] |
其他 | () -> , [] |
不可重載運算符:
運算符名稱 | 運算符符號 |
---|---|
成員訪問運算符 | . |
成員指針訪問運算符 | .* ->* |
域運算符 | :: |
長度運算符 | sizeof |
條件運算符 | ?: |
預處理符號 | # |
下面逐個對可重載的運算符進行演示。
一元運算符重載
一元運算符指的是只對一個操作數進行操作,下面是一元運算符的實例。
遞增運算符(++),遞減運算符(--)
負號(-)
邏輯非運算符(!)
通常一元運算符出現在操作的對象的左邊,下面演示重載負號(-)一元運算符:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classPoint{
private:
int x, y;
public:
Point(int x_x =0,int y_y =0){
x = x_x, y = y_y;
}
Pointoperator-(){
x =-x, y =-y;
return*this;
}
voidShow_Point(){
cout <<"("<< x <<","<< y <<")n";
}
};
int main(){
int x, y;
cin >> x >> y;
Point p(x, y);
-p;
p.Show_Point();
return0;
}
上面設計了一段求點關于原點對稱的點的代碼,重載了-運算符來直接對對象進行操作。
二元運算符重載
二元運算符主要需要兩個參數,我們通常使用的加(+),減(-),乘(*),除(/)都是二元運算符。接下來我們嘗試重載 + 運算符:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classBox{
private:
int length, breadth, height;
public:
int get_volume(){
return length * breadth * height;
}
void set_length(int l){
length = l;
}
void set_breadth(int b){
breadth = b;
}
void set_height(int h){
height = h;
}
Boxoperator+(Box& add_box){
Box box;
box.length = add_box.length + length, box.breadth = add_box.breadth + breadth, box.height = add_box.height + height;
return box;
}
};
int main(){
Box box1, box2, box3;
box1.set_length(1), box1.set_breadth(2), box1.set_height(3);
box2.set_length(4), box2.set_breadth(5), box2.set_height(6);
box3 = box1 + box2;
cout <<"The volume of box1 is: "<< box1.get_volume()<<"n";
cout <<"The volume of box2 is: "<< box2.get_volume()<<"n";
cout <<"The volume of box3 is: "<< box3.get_volume()<<"n";
return0;
}
接下來我們嘗試將上面的代碼進行改寫,將運算符重載的函數以非成員函數的方式進行重載運算符:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classBox{
private:
int length, breadth, height;
public:
int get_volume(){
return length * breadth * height;
}
void set_length(int l){
length = l;
}
void set_breadth(int b){
breadth = b;
}
void set_height(int h){
height = h;
}
friendBoxoperator+(Box& add_box1,Box& add_box2);
};
Boxoperator+(Box& add_box1,Box&add_box2){
Box box;
box.length = add_box1.length + add_box2.length, box.breadth = add_box1.breadth + add_box2.breadth, box.height = add_box1.height + add_box2.height;
return box;
}
int main(){
Box box1, box2, box3;
box1.set_length(1), box1.set_breadth(2), box1.set_height(3);
box2.set_length(4), box2.set_breadth(5), box2.set_height(6);
box3 = box1 + box2;
cout <<"The volume of box1 is: "<< box1.get_volume()<<"n";
cout <<"The volume of box2 is: "<< box2.get_volume()<<"n";
cout <<"The volume of box3 is: "<< box3.get_volume()<<"n";
return0;
}
將重載函數作為非成員函數的時候,是將兩個對象進行相加,并且由于函數是全局函數,那么需要傳入的參數個數為2,同時當重載預算符的函數是全局函數的時候,需要在類中將這個函數聲明為友元函數。
關系運算符重載
C++支持很多種關系運算符,這些關系運算符用來比較C++中的數據類型,我們同時也可以重載任何一個關系運算符,接下來我們嘗試重載>,<,==關系運算符,從而實現一個成績排名的小程序:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classStudent{
private:
string name, ID;
int score1, score2;
public:
Student(string n, string id,int number1,int number2){
name = n, ID = id, score1 = number1, score2 = number2;
}
Student(){
}
booloperator>(Student& s){
if(score1 + score2 > s.score1 + s.score2){
returntrue;
}
else{
returnfalse;
}
}
booloperator<(Student& s){
if(score1 + score2 < s.score1 + s.score2){
returntrue;
}
else{
returnfalse;
}
}
booloperator==(Student& s){
if(score1 + score2 == s.score1 + s.score2){
returntrue;
}
else{
returnfalse;
}
}
};
int main(){
string name, ID;
int score1, score2;
cin >> name >> ID >> score1 >> score2;
StudentStudent1(name, ID, score1, score2);
cin >> name >> ID >> score1 >> score2;
StudentStudent2(name, ID, score1, score2);
if(Student1>Student2){
cout <<"Student1 is better than Student2";
}
elseif(Student1==Student2){
cout <<"Student1 is equal to Student2";
}
elseif(Student1<Student2){
cout <<"Student1 is worse than Student2";
}
return0;
}
輸入輸出運算符重載
C++能夠使用流提取運算符>>和流插入運算符<<來進行輸入和輸出內置的數據類型,我們也可以重載流提取和插入運算符來操作對象或者我們自定義的數據類型。
通常我們會將輸入術后出的函數聲明為類的友元函數,這樣我們在使用的函數就不需要創建對象二直接調用函數。
下面我們將之前的對學生成績的代碼進行修改,添加了重載流提取運算符和流插入運算符的部分:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classStudent{
private:
string name, ID;
int score1, score2;
public:
Student(string n, string id,int number1,int number2){
name = n, ID = id, score1 = number1, score2 = number2;
}
Student(){
}
booloperator>(Student& s){
if(score1 + score2 > s.score1 + s.score2){
returntrue;
}
else{
returnfalse;
}
}
booloperator<(Student& s){
if(score1 + score2 < s.score1 + s.score2){
returntrue;
}
else{
returnfalse;
}
}
booloperator==(Student& s){
if(score1 + score2 == s.score1 + s.score2){
returntrue;
}
else{
returnfalse;
}
}
friend istream &operator>>(istream& input,Student&student){
input >> student.name >> student.ID >> student.score1 >> student.score2;
return input;
}
friend ostream &operator<<(ostream& output,Student&student){
output <<"Student:"<< student.name <<" ID:"<< student.ID <<" total score:"<< student.score1 + student.score2 <<"n";
return output;
}
};
int main(){
string name, ID;
int score1, score2;
cin >> name >> ID >> score1 >> score2;
StudentStudent1(name, ID, score1, score2);
cin >> name >> ID >> score1 >> score2;
StudentStudent2(name, ID, score1, score2);
if(Student1>Student2){
cout <<"Student1 is better than Student2n";
}
elseif(Student1==Student2){
cout <<"Student1 is equal to Student2n";
}
elseif(Student1<Student2){
cout <<"Student1 is worse than Student2n";
}
cout <<Student1<<Student2;
return0;
}
上述代碼我們將流插入和提取運算符進行重載,可以直接對對象中的成員變量進行輸入和輸出。
通常我們輸入輸出習慣上使用cin>>和cout<<,這樣重載的時候需要使用友元函數來重載運算符,如果使用成員函數來重載運算符的時候將會出現d1<
++和--運算符重載
++和--運算符是C++中兩個重要的一元運算符,下面的示例中以時間變化為例演示重載++運算符的前綴和后綴的兩種用法:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classTime{
private:
int hour, minute, second;
public:
Time(){
hour =0, minute =0, second =0;
}
Time(int h,int m,int s){
hour = h, minute = m, second = s;
}
friend ostream&operator<<(ostream& output,Time T){
output <<"The time is "<< T.hour <<":"<< T.minute <<":"<< T.second <<"n";
return output;
}
Timeoperator++(){
++second;
if(second >=60){
++minute;
minute -=60;
}
if(minute >=60){
++hour;
minute -=60;
}
if(hour >=24){
hour =0;
}
return*this;
}
Timeoperator++(int){
++second;
if(second >=60){
++minute;
minute -=60;
}
if(minute >=60){
++hour;
minute -=60;
}
if(hour >=24){
hour =0;
}
return*this;
}
};
int main(){
int hour, minute, second;
cin >> hour >> minute >> second;
Time T(hour, minute, second);
++T;
cout << T;
T++;
cout << T;
return0;
}
遞增和遞減通常是改變對象的狀態,所以對遞增和遞減運算符的重載通常為成員函數。
重載遞增遞減一定要和指針的遞增和遞減進行區分,疑問這里的重載操作的是對象而不是指針(由于指針是內置類型,因此指針的遞增和遞減是無法被重載的),所以通常情況下的遞增和遞減操作的對象是對象內部的成員變量。
遞增和遞減運算符分為前置和后置兩種情況,因為符號相同,因此給后置的版本增加一個int類型的形參,這個形參為0,在函數體中式用不到的,引入這個形參只是為了區分富豪的前置和后置。
賦值運算符重載
和其他運算符相同,C++中允許我們重載賦值運算符,用來創建宇哥對象,比如拷貝構造函數。下面我們演示對賦值運算符的重載:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classTime{
private:
int hour, minute, second;
public:
Time(){
hour =0, minute =0, second =0;
}
Time(int h,int m,int s){
hour = h, minute = m, second = s;
}
friend ostream&operator<<(ostream& output,Time T){
output <<"The time is "<< T.hour <<":"<< T.minute <<":"<< T.second <<"n";
return output;
}
Timeoperator=(Time& T){
hour = T.hour, minute = T.minute, second = T.second;
return*this;
}
};
int main(){
int hour, minute, second;
cin >> hour >> minute >> second;
Time T1(hour, minute, second);
Time T2;
T2 = T1;
cout << T1 << T2;
return0;
}
上述代碼通過將時間進行復制的操作演示了對賦值運算符的重載。
淺拷貝和隱式轉換:通常情況我們使用隱式轉換函數的時候十分謹慎,因為當我們不需要使用轉換蛤屬的時候,這個函數仍可能被調用執行,這些不正確的程序可能會出現一些意想不到的事情,而我們很難對此做出判斷,同時淺拷貝存在指針懸空的問題,所以我們通常禁止隱式轉換,在構造函數前面添加explicit關鍵字,通常情況下隱式轉換的聲明的意義不大,在實際情況中通常使用淺拷貝,不會調用隱式轉換。
函數調用運算符重載
蛤屬調用運算符可以被重載與類的對象,當我們重載()的時候,我們并不是調用了一種新的調用函數的方式,相反的這是創建了一個可以傳遞任意數目參數的運算符函數,下面我們通過求總秒數的例子來演示重載函數調用運算符:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classTime{
private:
int hour, minute, second, total_second;
public:
Time(){
hour =0, minute =0, second =0, total_second =0;
}
Time(int h,int m,int s){
hour = h, minute = m, second = s, total_second =3600*24* hour +3600* minute + second;
}
void show_total_second(){
cout << total_second;
}
Timeoperator()(int h,int m,int s){
Time T;
T.total_second =24*60*60* h +60*60* m + s;
return T;
}
Timeoperator=(Time& T){
this->total_second = T.total_second;
return*this;
}
};
int main(){
int hour, minute, second;
cin >> hour >> minute >> second;
Time T1;
Time T2 = T1(hour, minute, second);
T2.show_total_second();
return0;
}
下標運算符[]重載
下標操作符[]通常用來訪問數組元素,重載這個運算符可以增強操作數組的功能,下面的代碼通過重載下標運算符來更方便得訪問類中數組指定的元素:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
classArray{
private:
int number[10], posion;
public:
int&operator[](int i){
return number[i];
}
Array(){
posion =0;
}
void set_value(int num){
number[posion]= num,++ posion;
}
};
int main(){
Array number;
for(R int i =0; i <10;++i){
int n;
cin >> n;
number.set_value(n);
}
for(R int i =0; i <10;++i){
cout << number[i]<<" ";
}
return0;
}
類成員訪問運算符( -> )可以被重載,但它較為麻煩。它被定義用于為一個類賦予"指針"行為。運算符 -> 必須是一個成員函數。如果使用了 -> 運算符,返回類型必須是指針或者是類的對象。
運算符 -> 通常與指針引用運算符 * 結合使用,用于實現"智能指針"的功能。這些指針是行為與正常指針相似的對象,唯一不同的是,當通過指針訪問對象時,它們會執行其他的任務。比如,當指針銷毀時,或者當指針指向另一個對象時,會自動刪除對象。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define R register
#define LL longlong
#define pi 3.141
#define INF 1400000000
usingnamespace std;
// 假設一個實際的類
classObj{
staticint i, j;
public:
void f()const{ cout << i++<< endl;}
void g()const{ cout << j++<< endl;}
};
// 靜態成員定義
intObj::i =10;
intObj::j =12;
// 為上面的類實現一個容器
classObjContainer{
vector<Obj*> a;
public:
void add(Obj* obj)
{
a.push_back(obj);// 調用向量的標準方法
}
friendclassSmartPointer;
};
// 實現智能指針,用于訪問類 Obj 的成員
classSmartPointer{
ObjContainer oc;
int index;
public:
SmartPointer(ObjContainer& objc)
{
oc = objc;
index =0;
}
// 返回值表示列表結束
booloperator++()// 前綴版本
{
if(index >= oc.a.size()-1)returnfalse;
if(oc.a[++index]==0)returnfalse;
returntrue;
}
booloperator++(int)// 后綴版本
{
returnoperator++();
}
// 重載運算符 ->
Obj*operator->()const
{
if(!oc.a[index])
{
cout <<"Zero value";
return(Obj*)0;
}
return oc.a[index];
}
};
int main(){
constint sz =10;
Obj o[sz];
ObjContainer oc;
for(int i =0; i < sz; i++)
{
oc.add(&o[i]);
}
SmartPointer sp(oc);// 創建一個迭代器
do{
sp->f();// 智能指針調用
sp->g();
}while(sp++);
return0;
}
一些注意的要點
運算符重載不可以改變語法結構;
運算符重載不可以改變操作數的個數;
運算符重載不可以改變運算的優先級;
運算符重載不可以改變結合性。
類重載、覆蓋、重定義之間的區別:
重載指的是函數具有的不同的參數列表,而函數名相同的函數。重載要求參數列表必須不同,比如參數的類型不同、參數的個數不同、參數的順序不同。如果僅僅是函數的返回值不同是沒辦法重載的,因為重載要求參數列表必須不同。(發生在同一個類里)
覆蓋是存在類中,子類重寫從基類繼承過來的函數。被重寫的函數不能是static的。必須是virtual的。但是函數名、返回值、參數列表都必須和基類相同(發生在基類和子類)
重定義也叫做隱藏,子類重新定義父類中有相同名稱的非虛函數 ( 參數列表可以不同 ) 。(發生在基類和子類)
this 指針的作用
this 指針是一個隱含于每一個非靜態成員函數中的特殊指針。它指向正在被該成員函數操作的那個對象。當對一個對象調用成員函數時,編譯器先將對象的地址賦給 this 指針,然后調用成員函數,每次成員函數存取數據成員時由隱含使用 this 指針。
評論
查看更多