運算子多載
運算子多載基礎
函式多載(Function Overloading)可以讓一個函式名有多種功能,在不同情況下進行不同的操作,運算子多載(Operator Overloading)也是一個道理,同一個運算子可以有不同的功能,
例子:用+號實作復數加法運算;成員函式多載運算子
#include <iostream>
using namespace std;
class complex{
public:
complex();
complex(double real, double imag);
public:
//宣告運算子多載
complex operator+(const complex &A) const;
void display() const;
private:
double m_real; //實部
double m_imag; //虛部
};
complex::complex(): m_real(0.0), m_imag(0.0){ }
complex::complex(double real, double imag): m_real(real), m_imag(imag){ }
//實作運算子多載
complex complex::operator+(const complex &A) const{
return complex(this->m_real + A.m_real, this->m_imag + A.m_imag);//回傳臨時物件
}
void complex::display() const{
cout<<m_real<<" + "<<m_imag<<"i"<<endl;
}
int main(){
complex c1(4.3, 5.8);
complex c2(2.4, 3.7);
complex c3;
c3 = c1 + c2;
c3.display();// 運行結果: 6.7 + 9.5i
return 0;
}
可以看出:運算子多載是通過函式實作的,它本質上是函式多載,
運算子多載格式:
回傳值型別 operator 運算子名稱 (形參表列){
//TODO:
}operator是關鍵字,專門用于定義多載運算子的函式,我們可以將`operator 運算子名稱`這一部分看做函式名,對于上面的代碼,函式名就是operator+
即是:運算子多載除了函式名不同,其他地方和函式沒有什么區別;
當執行c3 = c1 + c2;陳述句時,編譯器檢測到+號左邊(+號具有左結合性,所以先檢測左邊)是一個 complex 物件,就會呼叫成員函式operator+(),也就是轉換為下面的形式:
c3 = c1.operator+(c2);
c1 是要呼叫函式的物件,c2 是函式的實參,
全域內多載運算子:
運算子多載函式不僅可以作為類的成員函式,還可以作為全域函式,利用友元函式來實作(獲取private屬性,歸屬于全域函式的應用)
更改上述例子:
#include <iostream>
using namespace std;
class complex{
public:
complex();
complex(double real, double imag);
public:
void display() const;
//宣告為友元函式
friend complex operator+(const complex &A, const complex &B);
private:
double m_real;
double m_imag;
};
complex operator+(const complex &A, const complex &B);
complex::complex(): m_real(0.0), m_imag(0.0){ }
complex::complex(double real, double imag): m_real(real), m_imag(imag){ }
void complex::display() const{
cout<<m_real<<" + "<<m_imag<<"i"<<endl;
}
//在全域范圍內多載+
complex operator+(const complex &A, const complex &B){
complex C;
C.m_real = A.m_real + B.m_real;
C.m_imag = A.m_imag + B.m_imag;
return C;
}
int main(){
complex c1(4.3, 5.8);
complex c2(2.4, 3.7);
complex c3;
c3 = c1 + c2;
c3.display();
return 0;
}
通過運算子多載,擴大了C++已有運算子的功能,使之能用于物件,更人性了hhh;
tip: 非靜態成員函式后面加const(加到非成員函式或靜態成員后面會產生編譯錯誤,靜態和非靜態的區別就是,查看下面鏈接),表示成員函式隱含傳入的this指標為const指標,決定了在該成員函式中,任意修改它所在的類的成員的操作都是不允許的(因為隱含了對this指標的const參考);唯一的例外是對于mutable修飾的成員,加了const的成員函式可以被非const物件和const物件呼叫,但不加const的成員函式只能被非const物件呼叫,靜態函式和非靜態函式的區別、什么是mutable
運算子多載的規則
- 可以多載的運算子:+ - * / % ^ & | ~ ! = < > += -= = /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ -- , -> -> () [] new new[] delete delete[] ,自增自減運算子的前置和后置形式都可以多載
長度運算子sizeof、條件運算子: ?、成員選擇符.和域決議運算子::不能被多載,
- 多載不能改變運算子的優先級和結合性,如+-*/等運算子的優先級不會被改變;
- 運算子多載函式不能有默認的引數,否則就改變了運算子運算元的個數(比如+號默認引數為1,結果肯定不對),這顯然是錯誤的(有的博客說多載函式要有遵循固定數量的引數,和公認的相同)
- 運算子多載函式既可以作為類的成員函式,也可以作為全域函式
將運算子多載函式作為類的成員函式時,二元運算子的引數只有一個,一元運算子不需要引數,之所以少一個引數,是因為這個引數是隱含的,是這個類物件本身;如上面的this,通過 this 指標隱式的訪問 c1 的成員變數,
將運算子多載函式作為全域函式時,二元運算子就需要兩個引數,一元運算子需要一個引數,而且其中必須至少有一個引數是物件,好讓編譯器區分這是程式員自定義的運算子,防止程式員修改用于內置型別的運算子的性質,比如:
int operator + (int a,int b){//顯然錯誤,會造成歧義,改變內置型別的運算子的性質(別人設過的東西你別用)
return (a-b);
}
而如果上述的a或者b是一個物件,那么就是成立的;
同時,將運算子多載函式作為全域函式時,一般都需要在類中將該函式宣告為友元函式,原因很簡單,該函式大部分情況下都需要使用類的 private 成員,
- 箭頭運算子
->、下標運算子[ ]、函式呼叫運算子( )、賦值運算子=只能以成員函式的形式多載,
多載數學運算子例子
實際開發中多載數學運算子號非常常見,比如c++只是定義了復數的==,我們來實作復數的+,-,*,例子如下:
#include <iostream>
#include <cmath>
using namespace std;
//復數類
class Complex{
public: //建構式
Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public: //運算子多載
//以全域函式的形式多載!!!!!!!!!!!
friend Complex operator+(const Complex &c1, const Complex &c2);
friend Complex operator-(const Complex &c1, const Complex &c2);
friend Complex operator*(const Complex &c1, const Complex &c2);
friend Complex operator/(const Complex &c1, const Complex &c2);
friend bool operator==(const Complex &c1, const Complex &c2);
friend bool operator!=(const Complex &c1, const Complex &c2);
//以成員函式的形式多載!!!!!!!!!!!!
Complex & operator+=(const Complex &c);
Complex & operator-=(const Complex &c);
Complex & operator*=(const Complex &c);
Complex & operator/=(const Complex &c);
public: //成員函式
double real() const{ return m_real; }
double imag() const{ return m_imag; }
private:
double m_real; //實部
double m_imag; //虛部
};
//多載+運算子
Complex operator+(const Complex &c1, const Complex &c2){
return
Complex c;
c.m_real = c1.m_real + c2.m_real;
c.m_imag = c1.m_imag + c2.m_imag;
return c;
}
//多載-運算子
Complex operator-(const Complex &c1, const Complex &c2){
Complex c;
c.m_real = c1.m_real - c2.m_real;
c.m_imag = c1.m_imag - c2.m_imag;
return c;
}
//多載*運算子 (a+bi) * (c+di) = (ac-bd) + (bc+ad)i
Complex operator*(const Complex &c1, const Complex &c2){
Complex c;
c.m_real = c1.m_real * c2.m_real - c1.m_imag * c2.m_imag;
c.m_imag = c1.m_imag * c2.m_real + c1.m_real * c2.m_imag;
return c;
}
//多載/運算子 (a+bi) / (c+di) = [(ac+bd) / (c2+d2)] + [(bc-ad) / (c2+d2)]i
Complex operator/(const Complex &c1, const Complex &c2){
Complex c;
c.m_real = (c1.m_real*c2.m_real + c1.m_imag*c2.m_imag) / (pow(c2.m_real, 2) + pow(c2.m_imag, 2));
c.m_imag = (c1.m_imag*c2.m_real - c1.m_real*c2.m_imag) / (pow(c2.m_real, 2) + pow(c2.m_imag, 2));
return c;
}
//多載==運算子
bool operator==(const Complex &c1, const Complex &c2){
if( c1.m_real == c2.m_real && c1.m_imag == c2.m_imag ){
return true;
}else{
return false;
}
}
//多載!=運算子
bool operator!=(const Complex &c1, const Complex &c2){
if( c1.m_real != c2.m_real || c1.m_imag != c2.m_imag ){
return true;
}else{
return false;
}
}
//多載+=運算子,開始有&符號
Complex & Complex::operator+=(const Complex &c){
this->m_real += c.m_real;
this->m_imag += c.m_imag;
return *this;//this就是一個指標,*解參考,回傳的是this指向的物件
}
//多載-=運算子
Complex & Complex::operator-=(const Complex &c){
this->m_real -= c.m_real;
this->m_imag -= c.m_imag;
return *this;
}
//多載*=運算子
Complex & Complex::operator*=(const Complex &c){
this->m_real = this->m_real * c.m_real - this->m_imag * c.m_imag;
this->m_imag = this->m_imag * c.m_real + this->m_real * c.m_imag;
return *this;
}
//多載/=運算子
Complex & Complex::operator/=(const Complex &c){
this->m_real = (this->m_real*c.m_real + this->m_imag*c.m_imag) / (pow(c.m_real, 2) + pow(c.m_imag, 2));
this->m_imag = (this->m_imag*c.m_real - this->m_real*c.m_imag) / (pow(c.m_real, 2) + pow(c.m_imag, 2));
return *this;
}
int main(){
Complex c1(25, 35);
Complex c2(10, 20);
Complex c3(1, 2);
Complex c4(4, 9);
Complex c5(34, 6);
Complex c6(80, 90);
Complex c7 = c1 + c2;
Complex c8 = c1 - c2;
Complex c9 = c1 * c2;
Complex c10 = c1 / c2;
cout<<"c7 = "<<c7.real()<<" + "<<c7.imag()<<"i"<<endl;
cout<<"c8 = "<<c8.real()<<" + "<<c8.imag()<<"i"<<endl;
cout<<"c9 = "<<c9.real()<<" + "<<c9.imag()<<"i"<<endl;
cout<<"c10 = "<<c10.real()<<" + "<<c10.imag()<<"i"<<endl;
c3 += c1;
c4 -= c2;
c5 *= c2;
c6 /= c2;
cout<<"c3 = "<<c3.real()<<" + "<<c3.imag()<<"i"<<endl;
cout<<"c4 = "<<c4.real()<<" + "<<c4.imag()<<"i"<<endl;
cout<<"c5 = "<<c5.real()<<" + "<<c5.imag()<<"i"<<endl;
cout<<"c6 = "<<c6.real()<<" + "<<c6.imag()<<"i"<<endl;
if(c1 == c2){
cout<<"c1 == c2"<<endl;
}
if(c1 != c2){
cout<<"c1 != c2"<<endl;
}
return 0;
}
運行結果:

選擇是成員函式還是全域函式運算子多載
看例子:
Complex(double real): m_real(real), m_imag(0.0){ } //轉換建構式
//多載+號運算子,設定成全域函式運算子多載
Complex operator+(const Complex &c1, const Complex &c2)
.....
Complex c2 = c1 + 15.6;//正確
Complex c3 = 28.23 + c1;//正確
為什么要設定成全域運算子呢?
因為如果設定成成員函式,Complex c3 = 28.23 + c1;將會是錯誤的;
原理:因為是全域函式,保證了 + 運算子的運算元能夠被對稱的處理,存在轉換建構式,編譯器在檢測到 Complex 和 double(小數默認為 double 型別)相加時,會先嘗試將 double 轉換為 Complex,或者反過來將 Complex 轉換為 double(只有型別相同的資料才能進行 + 運算),如果都轉換失敗,或者都轉換成功(產生了二義性),才報錯,本例中,編譯器會先通過建構式Complex(double real);將 double 轉換為 Complex,再呼叫多載過的 + 進行計算,整個程序類似于下面的形式:

設定成成員函式:根據“+ 運算子具有左結合性”這條原則,Complex c3 = 28.23 + c1會被轉換為Complex c3 = (28.23).operator+(c1),很顯然這是錯誤的,因為 double 型別并沒有以成員函式的形式多載 +,所以成員函式不能對此處理運算元;
為什么成員函式中不能用轉換建構式處理資料28.23為Complex(28.23),而全域函式可以?
C++ 只會對成員函式的引數進行型別轉換,而不會對呼叫成員函式的物件進行型別轉換,因為設定為成員函式,28.23不是函式的引數,是擁有該函式的類物件,呼叫的將是28.23這個double物件的多載+,明顯是沒有的該函式,會報錯;而全域函式,那么呼叫的將是operator+(28.23, c1)這個函式,那么28.23,c1都是函式的引數,是可以呼叫轉換建構式的,
注意ψ(`?′)ψ??:運算子多載的初衷是給類添加新的功能,方便類的運算,它作為類的成員函式是理所應當的,是首選的!
但是因為有處理對稱的需求,每個類都多載一下運算子,過于麻煩了,所以允許采用全域函式多載,所以我們知道:引數具有邏輯的對稱性,我們采用全域函式定義;第一個(最左的)運算物件不出現型別轉換,我們運算子多載定義為成員函式;
C++ 規定,箭頭運算子->、下標運算子[ ]、函式呼叫運算子( )、賦值運算子=只能以成員函式的形式多載,
多載<<和>>運算子
標準庫本身已經對左移運算子<<和右移運算子>>分別進行了多載,使其能夠用于不同資料的輸入輸出,但是輸入輸出的物件只能是 C++ 內置的資料型別(例如 bool、int、double 等)和標準庫所包含的型別別(例如 string、complex、ofstream、ifstream 等)
有時候我們想多載輸入輸出符號,可以輸入輸出我們自己定義的型別,看下例,一樣采用對復數的多載,基于上面的例子,加多實作輸入輸出:
//complex類中定義輸入輸出為友函式(只能定義為友函式),供全域可以使用,因為多載函式中用到了 complex 類的 private 成員變數,定義為成員函式依據左結合性,會非常奇怪,>>的左邊需要一個complex類物件;
friend istream & operator>>(istream & in, complex & A);
friend ostream & operator<<(ostream & out, complex & A);
//多載輸入運算子
istream & operator>>(istream & in, complex & A){
in >> A.m_real >> A.m_imag;//輸入,基于標準庫的類;
return in;//回傳參考更方便再次使用,可以cin>>c1>>c2 ,連續輸入兩個物件;因為(cin>>c1)又是一個cin又可以cin>>c2;
}
//多載輸出運算子
ostream & operator<<(ostream & out, complex & A){
out << A.m_real <<" + "<< A.m_imag <<" i ";//輸出,基于標準庫的類
return out;//與上同理
}
//main函式中:
complex c1, c2, c3;
cin>>c1>>c2;//具體邏輯:>>(cin, c1), >>(cin, c2)
c3 = c1 + c2;
cout<<"c1 + c2 = "<<c3<<endl;
輸入函式定義為全域函式,相當于:operator>>(cin , c);
多載[](下標運算子)
C++ 規定,下標運算子[ ]必須以成員函式的形式進行多載,多載函式在類中的宣告如下:
回傳值型別 & operator[ ] (引數);
或者:
const 回傳值型別 & operator[ ] (引數) const;
使用第一種宣告方式,[ ]不僅可以訪問元素,還可以修改元素,使用第二種宣告方式,[ ]只能訪問而不能修改元素,在實際開發中,我們應該同時提供以上兩種形式,這樣做是為了適應 const 物件,因為通過 const 物件只能呼叫 const 成員函式,如果不提供第二種形式,那么將無法訪問 const 物件的任何元素
例子:
#include <iostream>
using namespace std;
class Array{
public:
Array(int length = 0);
~Array();
public:
int & operator[](int i);
const int & operator[](int i) const;
public:
int length() const { return m_length; }
void display() const;
private:
int m_length; //陣列長度
int *m_p; //指向陣列記憶體的指標
};
Array::Array(int length): m_length(length){
if(length == 0){
m_p = NULL;
}else{
m_p = new int[length];
}
}
Array::~Array(){
delete[] m_p;
}
int& Array::operator[](int i){
return m_p[i];
}
const int & Array::operator[](int i) const{//注意兩個const
return m_p[i];
}
void Array::display() const{
for(int i = 0; i < m_length; i++){
if(i == m_length - 1){
cout<<m_p[i]<<endl;
}else{
cout<<m_p[i]<<", ";
}
}
}
int main(){
int n;
cin>>n;
Array A(n);
for(int i = 0, len = A.length(); i < len; i++){
A[i] = i * 5;
}
A.display();
const Array B(n);
cout<<B[n-1]<<endl; //訪問最后一個元素
return 0;
}
結果:
tip:const加在函式前后的區別
多載++和--
前置和后置++例子:
#include <iostream>
#include <iomanip>
using namespace std;
//秒表類
class stopwatch{
public:
stopwatch(): m_min(0), m_sec(0){ }
public:
void setzero(){ m_min = 0; m_sec = 0; }
stopwatch run(); // 運行
stopwatch operator++(); //++i,前置形式
stopwatch operator++(int); //i++,后置形式,不寫引數名形式
friend ostream & operator<<( ostream &, const stopwatch &);
private:
int m_min; //分鐘
int m_sec; //秒鐘
};
stopwatch stopwatch::run(){
++m_sec;
if(m_sec == 60){
m_min++;
m_sec = 0;
}
return *this;
}
stopwatch stopwatch::operator++(){
return run();
}
stopwatch stopwatch::operator++(int n){
stopwatch s = *this; // 拷貝構造
run();
return s; // 符合 用完再加的思想
}
ostream &operator<<( ostream & out, const stopwatch & s){
out<<setfill('0')<<setw(2)<<s.m_min<<":"<<setw(2)<<s.m_sec;
return out;
}
int main(){
stopwatch s1, s2;
s1 = s2++;
cout << "s1: "<< s1 <<endl;
cout << "s2: "<< s2 <<endl;
s1.setzero();
s2.setzero();
s1 = ++s2;
cout << "s1: "<< s1 <<endl;
cout << "s2: "<< s2 <<endl;
return 0;
}
operator++ (int n) 函式實作自增的后置形式,回傳值是物件本身,但是之后(過了這個運算式)再次使用該物件時,物件自增了,所以在該函式的函式體中,先將物件保存,然后呼叫一次 run() 函式,之后再將先前保存的物件回傳(這個時候沒有自增),在這個函式中引數n是沒有任何意義的,它的存在只是為了區分是前置形式還是后置形式,
結果:

多載new和delete
記憶體管理運算子 new、new[]、delete 和 delete[] 也可以進行多載,其多載形式既可以是類的成員函式,也可以是全域函式,一般情況下,內建的記憶體管理運算子就夠用了,只有在需要自己管理記憶體時才會多載
new:
成員函式形式:
void * className::operator new( size_t size ){
//TODO:
}
全域函式形式:
void * operator new( size_t size ){
//TODO:
}
可以看出:兩種多載形式的回傳值相同,都是void *型別,并且都有一個引數,為size_t型別,在多載 new 或 new[] 時,無論是作為成員函式還是作為全域函式,它的第一個引數必須是 size_t 型別,size_t 表示的是要分配空間的大小,對于 new[] 的多載函式而言,size_t 則表示所需要分配的所有空間的總和,(size_t 在頭檔案 typedef unsigned int size_t;,也就是無符號整型)
多載函式也可以有其他引數,但都必須有默認值,并且第一個引數的型別必須是 size_t,
delete:
成員函式:
void className::operator delete( void *ptr){
//TODO:
}
全域函式:
void operator delete( void *ptr){
//TODO:
}
兩種多載形式的回傳值都是 void 型別,并且都必須有一個 void 型別的指標作為引數,該指標指向需要釋放的記憶體空間;
當我們以類成員多載了new和delete函式,使用例子:
C * c = new C; //分配記憶體空間
//TODO:
delete c; //釋放記憶體空間
如果類中沒有定義 new 和 delete 的多載函式,那么會自動呼叫內建的 new 和 delete 運算子;
例子:
#include<iostream>
using namespace std;
class Foo
{
public:
int _id;
long _data;
string _str;
public:
Foo():_id(0){cout<<"default ctor.this="<<this<<" id="<<_id<<endl;}
Foo(int i):_id(i){cout<<"ctor.this="<<this<<" id="<<_id<<endl;}
~Foo() {cout<<"dtor.this="<<this<<" id="<<_id<<endl;}
static void* operator new(size_t size);
static void operator delete(void* pdead,size_t size);
static void* operator new[](size_t size);
static void operator delete[](void* pdead,size_t size);
};
void* Foo::operator new(size_t size)
{
Foo* p = (Foo *)malloc(size);
cout<<"呼叫了Foo::operator new"<<endl;
return p;
}
void Foo::operator delete(void *pdead,size_t size)
{
cout<<"呼叫了Foo::operator delete"<<endl;
free(pdead);
}
void* Foo::operator new[](size_t size)
{
Foo* p = (Foo*)malloc(size);
cout<<"呼叫了Foo::operator new[]"<<endl;
return p;
}
void Foo::operator delete[](void *pdead, size_t size)
{
cout<<"呼叫了Foo::operator delete[]"<<endl;
free(pdead);
}
int main()
{
Foo* pf = new Foo(7);
Foo* pf1 = new Foo[2];
delete pf;
delete[] pf1;
}
結果:

多載()
直接看例子:將 double型別強制轉換運算子 進行多載
#include <iostream>
using namespace std;
class Complex
{
double real, imag;
public:
Complex(double r = 0, double i = 0) :real(r), imag(i) {};
operator double() { return real; } //多載強制型別轉換運算子 double
};
int main()
{
Complex c(1.2, 3.4);
cout << (double)c << endl; //輸出 1.2
double n = 2 + c; //等價于 double n = 2 + c. operator double()
cout << n; //輸出 3.2
}
型別強制轉換運算子是單目運算子,也可以被多載,但只能多載為成員函式,不能多載為全域函式,經過適當多載后,(型別名)物件這個對 物件 進行強制型別轉換的運算式就等價于物件.operator 型別名(),即變成對運算子函式的呼叫,
多載強制型別轉換運算子時,不需要指定回傳值型別,因為回傳值型別是確定的,就是運算子本身代表的型別,在這里就是 double,
多載后的效果是,第 13 行的(double)c等價于c.operator double(),
有了對 double 運算子的多載,在本該出現 double 型別的變數或常量的地方,如果出現了一個 Complex 型別的物件,那么該物件的 operator double 成員函式就會被呼叫,然后取其回傳值使用,
例如第 14 行,編譯器認為本行中c這個位置如果出現的是 double 型別的資料,就能夠解釋得通,而 Complex 類正好多載了 double 運算子,因而本行就等價于:
double n = 2 + c.operator double();
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/487409.html
標籤:C++
上一篇:Shell腳本::如何在grep命令中放置多個過濾器
下一篇:C++ 獲取指定的多載函式地址
