1. 程式人生 > >《C++面向物件程式設計》課程筆記 lessen4

《C++面向物件程式設計》課程筆記 lessen4

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;
}