操作符重載


 

 

重載不能改變操作符的優先級

 

如果一個內建操作符是一元的,那么所有對它的重載仍是一元的。如果是二元的重載后也是二元的

 

 下面看一個有代表性的例子::

頭文件Complex.h:

#include <iostream>
using namespace std;

class Complex {
public:
   Complex();
   Complex(double);
   Complex(double,double);

   void write() const;
   Complex operator +(const Complex &) const;
   Complex operator -(const Complex &) const;
   Complex operator *(const Complex &) const;
   Complex operator /(const Complex &) const;
private:
   double real;
   double imag;
};

Complex::Complex(){
   real = imag = 0.0;
}

Complex::Complex(double re) {
   real = re;
   imag = 0.0;
};

Complex::Complex(double re,double im) {
   real = re;
   imag = im;
}

void Complex::write() const {
   cout << real << " + " << imag << 'i';
};

Complex Complex::operator + (const Complex &u) const {
   Complex v(real+u.real,imag+u.imag);
   return v;
}

Complex Complex::operator - (const Complex &u) const {
   Complex v(real-u.real,imag-u.imag);
   return v;
}

Complex Complex::operator* (const Complex &u) const {
   Complex v(real * u.real - imag * u.imag,real * u.imag + imag * u.real);
   return v;
}

Complex Complex::operator / (const Complex &u) const {
   double temp = u.real * u.real + u.imag * u.imag;
   Complex v( (real * u.real + imag * u.imag ) / temp, ( imag * u.real - real * u.imag ) / temp);
   return v;
}

 

測試文件:Complex.cpp和測試結果:

 

一個被重載的操作符,就是一個用戶自定義的函數,只不過它可以享受操作符語法所帶來的便利

 

除了內存管理操作符new、new[]、delete、delete[]之外,一個以頂層函數形式被重載的操作符必須在它的參數列表中包含一個類的對象

下標操作符[]、賦值操作符=、函數調用操作符()和指針操作符->必須以類成員函數的形式進行重載(這樣可以保證第一個操作數是類的對象,不然9[x]、6.32=x,不被接受)

操作符(如%)要么以成員函數被重載,要么以頂層函數被重載。對於后者將至少帶一個對象參數,這是因為操作符以頂層函數實現時,如果連一個參數都沒有,那么對如下表達式   x % y  編譯系統就不能區分%是內建的,還是用戶的。如果操作符%是類成員函數,或是有一個類對象參數的頂層函數,編譯系統就能夠根據特定的上下文決定調用哪一個%操作符

 

頂層函數重載操作符,類名和域解析符都沒有了,因為它不是一個類成員函數

 

被重載的操作符,要么是一個類成員函數,要么在它的參數列表中包含一個類成員!!

 

下標操作符[]和函數調用操作符()只能以成員函數的形式被重載,不能成為頂層函數!!

eg:

 

修正后:

 

在上面那種情況,如果想要第二個表達式成功的話,就需要定義一個頂層函數重載+了

 

有一點要明白,當定義頂層函數時,函數里面不能含有對象的私有成員操作,否則編譯通不過,下面有三種方法可以解決:

1,將私有成員設計為public成員,但是這種方法違背了類的信息隱藏原則

2,在Complex中加入用於訪問real和imag的公有成員函數,但是容易造成接口混淆

3,將操作符重載函數聲明為類的friend,但是不符合面向對象原則,應少用,建議僅在操作符重載時使用

 

類的私有成員只能被該類的成員函數和該類的friend函數訪問

類的保護成員只能被該類的或其派生類的成員函數和該類的friend函數訪問

 

class C {

     //....

  friend int f();

  //...

};

該聲明賦予f訪問C的私有和保護成員的權力,因為f不是成員函數,該聲明可以放在C中的private、protected或public的任意部分,不受訪問控制符的影響、限制

 

程序員可對操作符>>進行重載,以支持用戶自定義數據類型。>>的第一個操作數是系統類的對象(如cin是系統類istream的對象),而這些重載函數時以類成員函數的形式實現的。

如果在對用戶自定義的類型重載>>時,就必須對系統類的源代碼進行修改,而這顯然是非常不明智的做法,因此只能將>>重載函數設計為頂層函數

 

輸入流對象總是以引用方式傳遞,這是因為系統為了接受輸入數據,需要更新輸入流對象的某些信息

如果被重載>>的函數中和類的私有數據或者保護數據打交道,則需要將重載操作符聲明為friend

 

拷貝構造函數和賦值操作符(=),都是用來拷貝一個類的對象給另一個相同類型的對象。

拷貝構造函數將一個對象拷貝到另一個新的對象(因為拷貝-構造);賦值操作符將一個對象拷貝到另一個已經存在的對象

 

如果類的作者沒有提供拷貝構造函數,也沒有重載賦值操作符,編譯器將會給這個類提供一個拷貝構造函數和一個賦值操作符。編譯器提供的拷貝構造函數和賦值操作符的運轉機制是:將源對象中的每個數據成員拷貝到目標對象相應的數據成員中

 

 

看例子:

 

如果類的作者定義了指針成員,且該指針指向一塊動態分配的存儲空間,就應該為這個類設計拷貝構造函數,並重載賦值操作符

注意不要返回臨時變量,重載里面錯綜復雜,小心一點

 

下標操作符[]必須要以成員函數的形式進行重載!

 class C {         //可以修改對象

returntype & operator[] (paramtype);

};

或者:

class C {            //不能修改對象

const returntypr & operator[] (paramtype) const;

};

例子:

頭文件:test.h

 

測試文件和結果:

 

上面可以不提供const版本的重載,但是就不能處理非const的對象,所以,有時候要考慮這一點,加上const重載!

 

 

函數調用操作符()必須要以成員函數的形式重載。

class C {

returntype operator()(paramtypes);

};

 

看例子:

頭文件inttwoarray.h

#include <iostream>
#include <string>
using namespace std;

class intTwoArray {
public:
   int & operator()(int,int);
   const int & operator() (int,int) const;
   intTwoArray(int,int);
   int get_size1() const { return size1; }
   int get_size2() const { return size2; }
private:
   int size1;
   int size2;
   int *a;
};

int & intTwoArray::operator() (int i,int j) {
   if( i < 0 || i >= size1 )
      throw string("FirstOutOfBounds");
   if( j < 0 || j >= size2 )
      throw string("SecondOutOfBounds");
   return a[i * size2 + j];
}

const int & intTwoArray::operator() (int i,int j) const {
   if(i < 0 || i >= size1)
      throw  string("FirstOutOfBounds");
   if(j < 0 || j >= size2)
      throw string("SecondOutOfBounds");
   return a[i * size2 + j];
}

intTwoArray::intTwoArray(int s1,int s2) {
   int size = s1 * s2;
   try{
      a = new int[size];
   }
   catch(bad_alloc) {
      cerr << "Can't allocate storage for intTwoArray\n";
      throw;
   }
   size1 = s1;
   size2 = s2;
}

測試程序和結果:

 

自增,自減也可以重載,但是前置,后置一共四種,前后置通過一個int參數區分,但是這個參數沒有什么實際用途,僅僅起到區分的作用

例子:

 頭文件clock.h         (需要注意一點,這個地方注意注釋部分中間的代碼,一定要加上否則。。。。。)

#include <iostream>
#include <iomanip>
using namespace std;

//**********************************************************
class Clock;
ostream & operator << (ostream &,const Clock &);
//**********************************************************

class Clock {
public:
 Clock(int = 12,int = 0 ,int = 0 );
 Clock tick();
 friend ostream & operator << (ostream &,const Clock &);
 Clock operator++();
 Clock operator++(int);
private:
 int hour;
 int min;
 int ap;
};

Clock::Clock(int h,int m,int ap_flag) {
 hour = h;
 min = m ;
 ap = ap_flag;
}

Clock Clock::tick() {
 ++ min;
 if( min == 60) {
  hour ++;
  min = 0;
 }
 if(hour == 13)
  hour = 1;
 if(hour == 12 && min == 0)
  ap = !ap;
 return *this;
}

Clock Clock::operator++() {
 return tick();
}

Clock Clock::operator++(int n) {
 Clock c = *this;
 tick();
 return c;
}

ostream & operator<<(ostream& out,const Clock &c) {
 out << setfill('0') << setw(2) << c.hour << ':' << setw(2) << c.min;
 if(c.ap)
  out << " PM";
 else
  out << " AM";
 return out;
}

測試程序1和結果:

 

測試程序2和結果:

 

 

轉型構造函數可以將其他類型轉換成所需的類的類型。如果要進行相反的轉型動作,即要將類的類型轉換成其他類型,可以對轉型操作符進行重載:

operator othertype();  <====== 相對=====>轉型構造函數

 注意: 聲明中不能包含返回類型,即使void也不行,但函數體中必須包含return語句,用來返回轉型結果

 

雖然轉型操作符重載函數帶來很多便利,但仍需謹慎使用,因為編譯器嘗嘗在需要的時候調用轉型操作符重載函數,而這些隱式的調用對程序員來說,是不可見的,特別是當程序員並不希望發生這種調用時會產生無法預料的后果

 

看例子:

頭文件dict.h:          (注意該頭文件有一個問題,為什么friend不能對類的參數取值!!!必須把friend用的參數定義的外面!!!)

 

#include <iostream>
#include <string>
using namespace std;

 


/********************************************************************/
class Entry;
class Dict;
ostream & operator<<(ostream &,const Entry &);
ostream & operator<<(ostream &,const Dict &);
/********************************************************************/

 

class Entry {
public:
   Entry() {flag = false;}
   void add(const string &,const string &);
   bool match(const string &) const;
   void operator=(const string &);
   void operator=(const char *);
   friend ostream & operator<<(ostream &,const Entry &);
   bool valid() const { return flag; }
   void del() { flag = false; }
private:
   string word;
   string def;
   bool flag;
};

 

void Entry::operator=(const string &str) {
   def = str;
   flag = true;
}

 

void Entry::operator=(const char * str) {
   def = str;
   flag = true;
}

 

ostream & operator<<(ostream &out,const Entry &e) {
   out << e.word << "  defined as: " << e.def ;
   return out;
}

 

void Entry::add(const string &w,const string &d) {
   word = w;
   def = d;
}

 

bool Entry::match(const string &key) const {
   return key == word;
}

 

enum {MaxEntries = 100};

 

class Dict {
public: 
   friend ostream & operator<<(ostream &,const Dict &);
   void remove(const string &w);
   Entry & operator[] (const string &);
   Entry & operator[] (const char *);
private:
   Entry entries[MaxEntries+1];

 

};

 

ostream & operator<<(ostream &out,const Dict &d) {
   for(int i = 0 ;i < MaxEntries; i++)
      if(d.entries[i].valid())
         out << d.entries[i] << endl;
   return out;
}

 

Entry &Dict::operator[](const string &k) {                  //返回引用是該程序的技巧。因為程序通過這個地方,和前面重載的=操作符填充字典返回因為是為了修改對應的值,即賦值
   for(int i = 0;  i < MaxEntries && entries[i].valid(); i ++)
      if(entries[i].match(k))
         return entries[i];
   string not_found = "***not in dictionary";
   entries[i].add(k,not_found);
   return entries[i];
}

 

Entry & Dict::operator[](const char *k) {
   string s = k;
   return operator[](s);
}

 

void Dict::remove(const string &w)
{
   int i,j;
   for(i = 0 ;i< MaxEntries; i ++)
      if(entries[i].valid() && entries[i].match(w))
         break;
   if(i == MaxEntries)
      return ;
   for(j = i + 1; j < MaxEntries && entries[j].valid() ; j++)
      entries[j-1] = entries[j];
   entries[j-1].del();                    //最后一個失靈,即最后一個標志位設置為false,因為已經移到前面了
}

 

 

測試程序和結果:

 

 

 

內存管理操作符new、new[]、delete和delete[]既可以用成員函數也可以用頂層函數重載

嵌入式內存有限,應用程序經常需要直接管理內存

 

new操作符重載:

void * C::operator new(size_t size) {

  //...

}

void *  operator new(size_t  size) {

     //...

}

返回值void *

new和new[]操作符重載函數的第一個參數必須是size_t類型,其數值等於被創建對象大小,其他參數是可選的

 

 

delete操作符重載:

void C::operator delete( void * objPtr) {

  //...

}

void operator delete(void *objPtr) {

   //...

}

 

delete和delete[]操作符重載函數的第一個參數必須是void*類型,返回值必須是void,而其他參數則是可選的

看例子:

頭文件frame.h:

#include <iostream>
#include <string>
using namespace std;

const int  MaxFrames = 4;
const int  DataSize = 128;

class Frame {
public:
   Frame() { name = "NoName"; print(); }
   Frame(const char *n) { name = n; print(); }
   Frame(const string &n) { name = n ; print(); }
   Frame(const string&,const void *,unsigned);
   void print() const;
   void *operator new(size_t);

   void delete(void *);
private:
   string name;
   unsigned char data[DataSize];
};

Frame * allFrames = 0;
unsigned char framePool[MaxFrames * sizeof(Frame)];
bool alloc[MaxFrames];

Frame::Frame(const string &n,const void *d, unsigned bsize) {
   name = n;
   memcpy(data,d,bsize);
   print();
}

void Frame::print() const {
   cout << name << "  created.\n";
}

void * Frame::operator new(size_t size) {
   if(size != sizeof(Frame))
      throw string("Not a Frame");
   if(allFrames == 0) {
      allFrames = reinterpret_cast<Frame *>(framePool);
      for(int i = 0 ;i < MaxFrames ;i ++)
         alloc[i] = false;
    }
   for(int i = 0; i < MaxFrames ;i ++)
      if(!alloc[i]) {
         alloc[i] = true;
         return allFrames + i;
      }
   throw string("Out Of Storage");
   return 0;
}

void Frame::operator delete(void *adr) {

  int i = static_cast<unsigned char *>(adr) - framePool;

  i /= sizeof(Frame);

  alloc[i] = false;

}

 

測試程序和結果:

 

 

 

一個類中的成員函數可以使另一個類的friend:

 

常見編程錯誤:

1,賦值操作符是唯一一個不能被繼承的操作符

2,除了內存管理操作符,所有的操作符要么以類成員函數形式重載,要么其參數列表中至少有一個類的對象

3,[],(),=和->必須以成員函數重載

4,如果一個一元操作符以頂層函數重載,必定有一個參數

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM