《C++面向物件程式設計》課程筆記 lessen4
阿新 • • 發佈:2018-12-30
1. 運算子過載的基本概念
運算子過載的形式:
- 運算子過載的實質是函式過載。
- 可以過載為普通函式,也可以過載為成員函式。
- 把含運算子的表示式轉換為對運算子函式的呼叫。
- 把運算子的運算元轉換為運算子函式的引數。
- 運算子被多次過載時,根據實參的型別決定呼叫哪個運算子函式。
返回值型別 operator 運算子 (形參表)
{
...
}
運算子過載的例項:
#include <iostream> using namespace std; class Complex { public: double real,imag; Complex(double r=0.0,double i=0.0):real(r),imag(i){ } Complex operator - (const Complex & c); }; Complex operator +(const Complex & a,const Complex & b) { return Complex (a.real+b.real,a.imag+b.imag); //因為沒有物件名,所以返回的是一個臨時物件 } Complex Complex::operator-(const Complex & c) { return Complex(real-c.real,imag-c.imag); //因為沒有物件名,所以返回的是一個臨時物件 } int main() { Complex a(4,4),b(1,1),c; c = a + b; //等價於 c = operator+(a,b); cout<<c.real<<","<<c.imag<<endl; cout<<(a-b).real<<","<<(a-b).imag<<endl; // a-b 等價於 a.operator - (b) system("pause"); return 0; }
- 過載為成員函式時,引數個數為運算子運算元個數減一。
- 過載為普通函式時,引數個數為運算子運算元個數。
- c = a + b; 等價於 c = operator + (a,b);
- a - b; 等價於 a.operator - (b)
2. 賦值運算子 '=' 的過載
- 賦值運算子 '=' 只能過載為成員函式
#include <iostream> using namespace std; class String { private: char * str; public: String():str(new char[1]) {str[0] = 0;} const char * c_str() { return str; } String & operator = (const char * s); ~String() { delete [] str;} }; String & String::operator= (const char * s) { //過載“=”以使得 obj = "hello" 能夠成立 delete [] str; str = new char[strlen(s)+1]; strcpy(str, s); return * this; } int main() { String s; s = "Good Luck,"; //等價於 s.oprator = ("Good Luck,"); cout<<s.c_str()<<endl; // String s2 = "hello"; //這條是初始化語句而不是賦值語句,不會呼叫過載的賦值運算子。編譯出錯 s = "Shenzhou 8!"; //等價於 s.oprator = ("Shenzhou 8!"); cout<<s.c_str()<<endl; system("pause"); return 0; }
當使用上面的類執行下面語句時
String s1,s2;
s1 = "this";
s2 = "that";
s1 = s2;
效果形如:(呼叫預設的複製建構函式,s1與s2的每個位元組都相同)
造成的影響:
- "this" 所在的空間被捨棄,造成空間浪費。
- 如果 s1 物件消亡,解構函式將釋放 s1.str 指向的地方。而當 s2 物件消亡時,將再一次釋放。而 new 出來的只能被釋放一次。
- 如果執行 s1 = "other" ; 會導致 s2.str 所指向的地方被 delete 。
因此要在 class String 裡新增成員函式:
String & String::operator = (const String & s)
{
if (this == & s)
{
return * this;
}
delete [] str;
str = new char[strlen(s.str)+1];
strcpy( str, s.str);
return * this;
}
3. 可變長陣列類的實現
#include <iostream>
using namespace std;
class CArray
{
int size; //陣列元素的個數
int * ptr;//指向動態分配的陣列
public:
CArray(int s = 0); // s 代表陣列元素的個數
CArray(CArray & a);
~CArray();
void push_back(int v); //用於在陣列尾部新增一個元素 v 。
CArray & operator = (const CArray & a); //用於陣列物件間的賦值
int length(){ return size ;} //返回陣列元素個數
int & CArray::operator [](int i) //返回值為 int 不行,
//( n = a[i] 可以,而 a[i] = 4 出錯,非引用返回值不能作為賦值語句的左值)
{
//用以支援根據下標訪問陣列元素。如n = a[i] 和 a[i] = 4 這樣的語句
return ptr[i];
}
};
CArray::CArray(int s):size(s)
{
if (s==0)
{
ptr = NULL;
}
else
{
ptr = new int[s];
}
}
CArray::CArray(CArray & a)
{
if (!a.ptr)
{
ptr = NULL;
size = 0;
return;
}
ptr = new int[a.size];
memcpy(ptr,a.ptr,sizeof(int)*a.size);
size = a.size;
}
CArray::~CArray()
{
if (ptr)
{
delete [] ptr;
}
}
CArray & CArray::operator=(const CArray & a)
{ //賦值號的作用是使“=”左邊物件裡存放的陣列,大小和內容都和右邊的一樣
if (ptr == a.ptr) //防止 a = a 這樣的賦值導致出錯
{
return *this;
}
if (a.ptr == NULL) //如果 a 裡面的陣列是空的
{
if (ptr)
{
delete [] ptr;
}
ptr = NULL;
size = 0;
return *this;
}
if (size<a.size) //如果原有空間夠大,就不用分配新的空間
{
if (ptr)
{
delete [] ptr;
}
ptr = new int[a.size];
}
memcpy(ptr,a.ptr,sizeof(int)*a.size);
size = a.size;
return *this;
}
void CArray::push_back(int v) //在陣列尾部新增一個元素
{
if (ptr)
{
int * tmpptr = new int[size+1];//重新分配空間
memcpy(tmpptr,ptr,sizeof(int)*size);//拷貝原陣列內容
delete [] ptr;
ptr = tmpptr;
}
else //陣列本來是空的
ptr = new int[1];
ptr[size++] = v; //加入新的陣列元素
}
int main()
{
CArray a; //開始裡的陣列是空的
for(int i=0;i<5;i++) //要用動態分配的記憶體來存放陣列元素,需要一個指標成員變數
{
a.push_back(i);
}
CArray a2,a3;
a2 = a; //需要過載賦值運算子“=”
for(int i=0;i<a.length();++i)
{
cout<<a2[i]<<" "; // a2 是一個物件,但能像陣列一樣使用,需要過載“[]”運算子
}
a2 = a3; // a2 是空的
for(int i=0;i<a2.length();++i) // a2.length() 返回0
{
cout<<a2[i]<<" ";
}
cout<<endl;
a[3] = 100;
CArray a4(a); //初始化語句,需要自己寫複製建構函式
for(int i=0;i<a4.length();++i)
{
cout<<a4[i]<<" ";
}
system("pause");
return 0;
}
4. 流插入運算子和流提取運算子的過載
1 流插入運算子的過載
- cout 是 iostream 中定義的 ostream 類的物件
- “ << ” 能用在 cout 上是因為在 iostream 裡對 “ << ” 進行了過載。
單獨實現 cout<< 5 和 cout<< "this" 的過載:
void ostream::operator <<(int n)
{
... //輸出 n 的語句
return;
}
void ostream::operator <<(char * s)
{
... //輸出 s 的語句
return;
}
實現 cout<< 5 << "this" 的過載 :(想要連續執行,返回值應該為 ostream 類的物件(例:cout))
ostream & ostream::operator <<(int n) //返回型別為 ostream 類的引用
{
... //輸出 n 的語句
return * this; // * this 即 cout 物件,即返回 cout 物件的引用
}
ostream & ostream::operator <<(char * s)
{
... //輸出 s 的語句
return * this;
}
- cout<< 5 << "this" 的函式呼叫形式是:cout.operator <<(5) .operator <<("this")
class CStudent
{
public:
int nAge;
};
ostream & operator <<( ostream & o, CStudent & s)
{
o<<s.nAge;
return o;
}
int main()
{
CStudent s;
s.nAge = 5;
cout<< s << "hello";
system("pause");
return 0;
}
- 上面的程式將 "<<" 過載為全域性函式,引數與運算子運算元個數相同。
- 輸出為 5hello 。(需要連續執行 “<<” 操作,返回值應是 ostream 類的引用)
例題:假定 c 是 Complex 類的物件,"cin>>c" 從鍵盤接受形式為 “a+bi” 的輸入,“cout<<c” 以 “a+bi” 的形式輸出 c 的值。
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Complex
{
double real,imag;
public:
Complex(double r=0,double i=0):real(r),imag(i){ };
friend ostream & operator <<(ostream & os, const Complex & c); //因為要訪問類的私有成員,宣告為類的友元函式。
friend istream & operator >>(istream & is, Complex & c);
};
ostream & operator <<(ostream & os, const Complex & c)
{
os<<c.real<<"+"<<c.imag<<"i";//以 a+bi 的形式輸出
return os;
}
istream & operator >>(istream & is, Complex & c)
{
string s;
is>>s; //將“a+bi”作為字串讀入
int pos = s.find("+",0);
string sTmp = s.substr(0,pos); //分離出代表實部的字串
c.real = atof(sTmp.c_str()); //atof 庫函式能將 const char* 指標指向的內容轉換成 float
sTmp = s.substr(pos+1,s.length()-pos-2); //分離出代表虛部的字串
c.imag = atof((sTmp.c_str()));
return is;
}
int main()
{
Complex c;
int n;
cin>>c>>n;
cout<<c<<","<<n;
system("pause");
return 0;
}
5. 型別轉換運算子的過載
#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 (返回值即為 double,所以不用指定返回值)。
};
int main()
{
Complex c(1.2,3.4);
cout<<(double) c<<endl; //輸出1.2
double n = 2 + (double)c; //等價於 double n = 2 + c.operator double();
cout<<n<<endl; //輸出3.2
system("pause");
return 0;
}
6. 自增、自減運算子的過載
- 前置運算子作為一元運算子過載
過載為成員函式:
T & operator ++();
T & operator --();
過載為全域性函式:
T1 & operator ++(T2);
T1 & operator --(T2);
- 後置運算子作為二元運算子過載,多寫一個沒用的引數。(主要用於區分前置和後置)
過載為成員函式:
T & operator ++(int);
T & operator --(int);
過載為全域性函式:
T1 & operator ++(T2,int);
T1 & operator --(T2,int);
- 自增、自減運算子過載的例項:
#include <iostream>
using namespace std;
class CDemo
{
private:
int n;
public:
CDemo(int i=0):n(i) { }
CDemo & operator ++(); //用於前置形式
CDemo operator ++(int); //用於後置形式 //自增運算子宣告為成員函式
operator int () { return n;} //過載型別轉換運算子。用於輸出 cout<< CDemo物件。
friend CDemo & operator -- (CDemo &);
friend CDemo operator --(CDemo &, int); //自減運算子宣告為友元全域性函式
//原生態的 ++a 返回值為物件 a 的引用,(++a) = 1; //ok
// a++ 返回值為物件 a ,(a++) = 1; //error
};
CDemo & CDemo::operator++()
{//前置++
++n;
return * this;
// ++s即為:s.operator++();
}
CDemo CDemo::operator++(int k)
{//後置++
CDemo tmp(* this);//記錄修改前的物件
n ++;
return tmp; //返回修改前的物件
} // s++即為:s.operator++(0)
CDemo & operator --(CDemo & d)
{
//前置 --
d.n --;
return d;
}//--s即為:operator--(s);
CDemo operator --(CDemo & d, int)
{
//後置--
CDemo tmp(d);
d.n --;
return tmp;
}//s--即為:operator--(s,0);
int main()
{
CDemo d(5);
cout<< (d++) <<","; //等價於 d.operator ++(0);
cout<< d <<",";
cout<< (++d) <<","; //等價於 d.operator ++();
cout<< d <<endl;
cout<< (d--) <<","; //等價於 operator --(d,0);
cout << d <<",";
cout << (--d) <<","; //等價於 operator --(d);
cout << d << endl;
system("pause");
return 0;
}