天天看點

4.運算符重載,explicit關鍵字和啞成員

一 短整形運算符重載

二 模拟String類運算符重載

三 SmallInt類運算符重載

四 explicit關鍵字

五 啞成員

短整形運算符重載

//短整形運算符重載
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
using namespace std;
class Int
{
public:
	Int(long i = 0) : m_i(i)
	{}
	Int(const Int& x)
	{
		this->m_i = x.m_i;
	}
public:
	Int operator+(const Int& x)
	{
		return Int(this->m_i + x.m_i);
	}
	Int operator-(const Int& x)
	{
		return Int(this->m_i - x.m_i);
	}
	Int operator*(const Int& x)
	{
		return Int(this->m_i * x.m_i);
	}
	Int operator/(const Int& x)
	{
		return Int(this->m_i / x.m_i);
	}
	Int operator%(const Int& x)
	{
		return Int(this->m_i % x.m_i);
	}
public:
	Int& operator+=(const Int& x) // a += b;
	{
		this->m_i += x.m_i;
		return *this;
	}
	Int& operator-=(const Int& x)
	{
		this->m_i -= x.m_i;
		return *this;
	}
	Int& operator*=(const Int& x)
	{
		this->m_i *= x.m_i;
		return *this;
	}
	Int& operator/=(const Int& x)
	{
		this->m_i /= x.m_i;
		return *this;
	}
	Int& operator%=(const Int& x)
	{
		this->m_i %= x.m_i;
		return *this;
	}
public:
	Int operator>>(const Int& x) //a >> b
	{
		return Int(this->m_i >> x.m_i);
	}
	Int operator<<(const Int& x)
	{
		return Int(this->m_i << x.m_i);
	}
	Int& operator>>=(const Int& x)
	{
		this->m_i >>= x.m_i;
		return *this;
	}
	Int& operator<<=(const Int& x)
	{
		this->m_i <<= x.m_i;
		return *this;
	}
public:
	bool operator==(const Int& x)
	{
		if (this->m_i == x.m_i)
			return true;
		return false;
	}
	bool operator!=(const Int& x)
	{
		if (this->m_i != x.m_i)
			return true;
		return false;
	}
public:
	Int& operator++() //前置++    比後置++效率    可以引用傳回
	{
		this->m_i++;
		return *this;
	}
	Int operator++(int)  //後置++
	{
		Int temp(*this);   //調用拷貝構造函數或:
		//Int tmp(this->m_i);  //用構造函數
		this->m_i++;      //*this++
		return temp;
	}
	Int& operator--() //前置--    比後置++效率    可以引用傳回
	{
		this->m_i--;
		return *this;
	}
	Int operator--(int)  //後置--
	{
		Int Tmp(m_i);
		this->m_i--;
		return Tmp;
	}
public:
	int GetData()const
	{
		return this->m_i;
	}
private:
	long m_i;
};

int main()
{
	Int a(1);  //int a = 1;  a++  ++a  a-- --a
	Int b(2);

	cout << "a = " << a.GetData() << endl;
	cout << "b = " << b.GetData() << endl;

	Int result;
	result = a + b; //result = 1 + 2;
	cout << "result = " << result.GetData() << endl;

	result = ++a;
	result = a++;

	return 0;
}
           

以上同類型的符号重載隻分析一種,因為+ - += -= * *= / /= % %= 都是一樣的邏輯,知一曉百

(1) +号的重載 有整數 a, b = 1, c = 2, d = 3 a = b + c + d; 上述加法要能正常執行就必須等号右邊每兩個數相加并傳回結果,計算順序是無關的 要先算出(b+c) = 3, 然後再算出來3 + d = 6 或者先算出來(c + d) = 5, 然後再算出來b + 5 = 6 最終将結果6傳回給a

有Test類的對象 a, b (1), c(2), d(3)

對象之間是無法直接相加的,是以需要運算符重載

對+号進行重載成類的成員函數如下:

a = b + c + d;

相當于:a = b + c.operator+(d); a = b.operator+( c.operator+(d) );

或: a = b.operator+© + d; a = ( b.operator+© ).operator+(d);

Int operator+(const Int& x)
{
	return Int(this->m_i + x.m_i);
}
           

類的成員函數都是用對象來驅動的,被調用的成員方法的this指針便指向驅動它的對象

用this指針通路前對象的私有資料成員this->m_i,用常引用的形參const Int& x通路後對象的私有資料成員x.m_i,将資料相加然後構造一個臨時對象,傳回值按值傳回,便實作了将兩個對象相加,并将結果傳回,于是連續的對象相加也能實作

(2) +=重載 對象加等于a += b; 就是 a = a + b ; 單看a += b 則重載的+=是用對象a驅動的,a.operator+=(b); 最終隻有a的值發生了改變,是以b可以用常引用。對象a的生命不受重載的+=函數影響,是以a的值發生改變後可以引用傳回,以值的方式傳回也不會有任何問題。我們都知道函數參數按值傳回會借助臨時變量,由于是對象按值傳回,這樣的話就會多調用構造函數,即臨時變量占用了記憶體空間又調用了構造函數,導緻效率明顯降低了。 是以函數中不受函數控制生存周期的變量,強烈建議用引用傳回

Int& operator+=(const Int& x) // a += b;
{
	this->m_i += x.m_i;
	return *this;
}
           

(3) 以下右移位操作符>>和右移位等的操作符>>=重載和上面分析的+和+=是一樣的道理

Int operator>>(const Int& x) //a >> b
{
	return Int(this->m_i >> x.m_i);
}


Int& operator>>=(const Int& x)
{
	this->m_i >>= x.m_i;
	return *this;
}
           

(4) ==操作符重載如下

bool operator==(const Int& x)
{
	if (this->m_i == x.m_i)
		return true;
	return false;
}
           

覺得上述啰嗦吧,也可以如下的寫法:

但是上面的有更好的邏輯性,且傳回值類型比對都是布爾類型

bool operator==(const Int& x)
{
	return (this->m_i == x.m_i)
}
           

(5) 前置++ 和後置++ 的重載

Int& operator++() //前置++ 比後置++ 效率 可以引用傳回

{

this->m_i++;

return *this;

}

Int operator++(int)  //後置++
{
	Int temp(*this);   //調用拷貝構造函數或:
	//Int tmp(this->m_i);  //用構造函數
	this->m_i++;      //*this++
	return temp;
}
           

前置++ 和後置++ 的重載,其實相當簡單,隻需要知道後置++ 要在函數清單設一個int就OK

前置++ ,是給對象先+1,然後以引用傳回

後置++ ,是先傳回對象,再給對象+1,錯錯錯,大錯特錯。再函數中一但傳回,函數就結束了,return後面的語句就不再執行了

正确的思路是先借助臨時變量記錄一下++的左值,然後給左值+1,然後按值傳回臨時變量(不能用引用傳回,語義上臨時變量不能用引用傳回,臨時變量一出作用 域就死亡了,死亡的東西就是未知的也許,那塊臨時空間還保留着出函數的值,恰巧結果可以正确,但未知和不确定的實物可能就是整個程式死亡的誘因,要嚴謹要嚴謹)。

模拟String類運算符重載

對于3中對模拟String類實作的剖析的例子,我們繼續對其進行運算符重載

實作友元函數:length()

重載以下運算符:

operator[]
operator+
operator+=
operator> 	
operator<	
operator>=	 
operator<=	 
operator==
operator!=
           
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;

/
//length()
//operator[]
//operator+
//operator+=
//operator > < >= <= == !=
/
class String
{
public:
	String(const char* str = "")   //常類型到常類型
	{
		m_data = new char[strlen(str) + 1];
		strcpy(m_data, str);
	}
	String(const String& s)
	{
		m_data = new char[strlen(s.m_data) + 1];
		strcpy(m_data, s.m_data);
	}
	~String()
	{
		delete[]m_data;
		m_data = NULL;
	}
public:
	size_t length()const
	{
		return strlen(m_data);
	}

	char operator[](int i)
	{
		assert(i >= 0 && i < length());
		return m_data[i];
	}

	String& operator=(const String& s)
	{
		if (this != &s)
		{
			delete[](this->m_data);
			//new char[s.length() + 1];
			this->m_data = new char[s.length() + 1];  
			strcpy(this->m_data, s.m_data);
		}
		return *this;
	}

	String operator+(const String& s)
	{
		char* tmp = new char[length() + s.length() + 1];    //??????
		strcpy(tmp, this->m_data);
		strcat(tmp, s.m_data);

		String temp(tmp);
		delete []tmp;
		return temp;
	}
	
	String& operator+=(const String& s)
	{
		char* tmp = new char[strlen(this->m_data) + strlen(s.m_data) + 1];
		strcpy(tmp, this->m_data);
		strcat(tmp, s.m_data);

		delete[]m_data;
		this->m_data = tmp;
		return *this;
	}

public:
	bool operator==(const String& s)
	{
		if (strcmp(this->m_data, s.m_data) == 0)
			return true;
		return false;
	}
	bool operator!=(const String& s)
	{
		if (strcmp(this->m_data, s.m_data) != 0)
			return true;
		return false;
	}
	bool operator>(const String& s)
	{
		if (strcmp(this->m_data, s.m_data) > 0)
			return true;
		return false;
	}
	bool operator<(const String& s)
	{
		if (strcmp(this->m_data, s.m_data) < 0)
			return true;
		return false;
	}
	bool operator>=(const String& s)
	{
		if (strcmp(this->m_data, s.m_data) < 0)
			return false;
		return true;
	}
	bool operator<=(const String& s)
	{
		if (strcmp(this->m_data, s.m_data) > 0)
			return false;
		return true;
	}
private:
	char* m_data;
};


int main()
{
	String s1("Hello"); //s1[0] ==> H
	String s2("Bit.");

	s1 = s2;
	for (int i = 0; i < s1.length(); ++i)
		cout << s1[i];
	cout << endl;


	String s = s1 + s2; //s = HelloBit
	for (int i = 0; i < s.length(); ++i)
		cout << s[i];
	cout << endl;


	s1 += s2;    //

	for (int i = 0; i < s1.length(); ++i)
		cout << s1[i];
	cout << endl;

	return 0;
}
           

運算符重載之重載為成員函數:

下面的程式定義了一個簡單的SmallInt類,用來表示從-128到127之間的整數。

類的唯一的資料成員val存放一個-128到127(包含-128和127這兩個數)之間的整數,為了友善,

類SmallInt還重載了一些運算符。

閱讀SmallInt的定義,回答題目後面的問題。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class SmallInt;
ostream& operator<<(ostream& os, const SmallInt& si);
istream& operator>>(istream& is, SmallInt& si);

//SmallInt si(1000);

class SmallInt
{
public:
	SmallInt(int i = 0);
	//重載插入和抽取運算符
	friend ostream& operator<<(ostream& os, const SmallInt& si);
	friend istream& operator>>(istream& is, SmallInt& si);

	//重載算術運算符
	SmallInt operator+(const SmallInt& si) { return SmallInt(val + si.val); }
	SmallInt operator-(const SmallInt& si) { return SmallInt(val - si.val); }
	SmallInt operator*(const SmallInt& si) { return SmallInt(val * si.val); }
	SmallInt operator/(const SmallInt& si) { return SmallInt(val / si.val); }
	//重載比較運算符
	bool operator==(const SmallInt& si) { return (val == si.val); }
private:
	char val;
};
SmallInt::SmallInt(int i)
{
	while (i > 127)
		i -= 256;
	while (i < -128)
		i += 256;
	val = i;
}

ostream& operator<<(ostream& os, const SmallInt& si)
{
	os << (int)si.val;
	return os;
}

istream& operator>>(istream& is, SmallInt& si)
{
	int tmp;
	is >> tmp;
	si = SmallInt(tmp);
	return is;
}

int  main()
{
	SmallInt si(1000);
	cout << si << endl;
	SmallInt si1;
	cin >> si1;
	cout << si1 << endl; 
	return 0;
}
           

問題:(本小題4分)上面的類定義中,

重載的插入運算符和抽取運算符被定義為類的友元函數?//可以

能不能将這兩個運算符定義為類的成員函數?//不能

如果能,寫出函數原型,如果不能,說明理由。

文法上可以,語義上不可以代碼如下:

///

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//SmallInt si(1000);

class SmallInt
{
public:
	SmallInt(int i = 0);
	//重載插入和抽取運算符
	ostream& operator<<(ostream& os)
	{
		os << (int)this->val;
		return os;
	}
	istream& operator>>(istream& is)
	{
		int tmp;
		is >> tmp;
		this->val = tmp;
		return is;
	}

	//重載算術運算符
	SmallInt operator+(const SmallInt& si) { return SmallInt(val + si.val); }
	SmallInt operator-(const SmallInt& si) { return SmallInt(val - si.val); }
	SmallInt operator*(const SmallInt& si) { return SmallInt(val * si.val); }
	SmallInt operator/(const SmallInt& si) { return SmallInt(val / si.val); }
	//重載比較運算符
	bool operator==(const SmallInt& si) { return (val == si.val); }
private:
	char val;
};
SmallInt::SmallInt(int i)
{
	while (i > 127)
		i -= 256;
	while (i < -128)
		i += 256;
	val = i;
}
int  main()
{
	SmallInt si(1000);
	si << cout << endl;
	SmallInt si1;
	si1 >> cin;
	si1 << cout << endl;
	return 0;
}
           

按題目要求重載,隻能像知識總結中一中的那種不符合使用習慣(使用就必須如下)的重載辦法

si << cout << endl;
si1 >> cin;
si1 << cout << endl;
           

重載為友元函數是可以的

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//SmallInt si(1000);

class SmallInt
{
public:
	SmallInt(int i = 0);
	//重載插入和抽取運算符
	friend ostream& operator<<(ostream& os, const SmallInt& s);
	friend istream& operator>>(istream& is, SmallInt& s);

	//重載算術運算符
	SmallInt operator+(const SmallInt& si) { return SmallInt(val + si.val); }
	SmallInt operator-(const SmallInt& si) { return SmallInt(val - si.val); }
	SmallInt operator*(const SmallInt& si) { return SmallInt(val * si.val); }
	SmallInt operator/(const SmallInt& si) { return SmallInt(val / si.val); }
	//重載比較運算符
	bool operator==(const SmallInt& si) { return (val == si.val); }
private:
	char val;
};
SmallInt::SmallInt(int i)
{
	while (i > 127)
		i -= 256;
	while (i < -128)
		i += 256;
	val = i;
}
ostream& operator<<(ostream& os, const SmallInt& s)
{
	os << s.val;
	return os;
}
istream& operator>>(istream& is, SmallInt& s)
{
	int tmp;
	is >> tmp;
	s.val = tmp;
	return is;
}

int  main()
{
	SmallInt si(65);
	SmallInt si1;
	cout << si<<endl;
	cin>>si1;
	cout << si1 << endl;
	return 0;
}
           

explicit關鍵字

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Test
{
public:
	Test(int d = 0) : m_data(d)  	//構造函數的類型轉換(隐式)Test t1;   t1 = 998;
	{
		m_count++;
	}
	~Test()
	{
		m_count--;
	}
public:
	int GetData()const
	{return this->m_data;}
public:
	operator int() //強制轉換
	{
		return this->m_data;
	}
public:
	void list()
	{
		fun();
	}
	static void fun()
	{
		m_count = 10;
		cout<<"Test::fun() static"<<endl;
	}
private:
	int m_data;
	static int m_count;
};

int Test::m_count = 0; //

int main()
{
	Test t1;
	t1 = 998;
	return 0;
}
           

t1 = 998; //理論上我們知道普通變量是不能給對象指派的

但是實際上構造函數不僅可以構造對象還能夠隐式的做類型轉換,998給對象指派,會首先調用構造函數将整形998構造成一個匿名對象,然後用匿名對象去給對象t1指派。

如果我們給上述代碼的構造函數前加上explicit 關鍵字,構造函數便不能做隐式的類型轉化了

explicit Test(int d = 0) : m_data(d)
{
	m_count++;
}
           

這樣寫編譯就無法通過如下圖:

4.運算符重載,explicit關鍵字和啞成員

啞成員

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class Test
{
public:
	Test(int d = 0) :m_data(d)
	{}
public:
	class Tmp //内部類
	{
	public:
		Tmp(int a , int b) : x(a), y(b)
		{}
	public:
		int GetX()
		{return x;}
	public:
		int x;  //
		int y;  //啞成員
	};
public:
	int m_data;
};
int main()
{
	Test T;
	cout <<"sizeof(T) = "<< sizeof(T) << endl;
	Test::Tmp tp(1,2);
	cout<<"sizeof(tp) = "<<sizeof(tp) << endl;
	return 0;
}
           

内部類的資料成員稱為啞成員,因為執行個體的外部類對象的大小不包含内部類的大小,并且外部類中隻能通路自己的資料成員及函數成員,無法通路内部類的任何成員

建立類中類的對象時必須加外部類的作用域通路符:

Test::Tmp tp(1,2); //建立内部類Tmp的對象時就得加外部類Test的作用域通路符Test::

建立的外部類的對象大小不帶内部類

4.運算符重載,explicit關鍵字和啞成員

繼續閱讀