首頁 > 軟體

C++實現MyString的範例程式碼

2022-02-17 16:01:40

MyString的構造、解構、拷貝構造、賦值運算

class String
{
	char* str;
public:
	String(const char* p = NULL) :str(NULL)
	{
		if (p != NULL)
		{
			str = new char[strlen(p) + 1];//strlen()計算至''截至的字元數
			strcpy(str, p);
		}
		else
		{
			str = new char[1]; //額外提供一個空間
			*str = '';
		}
	}
	~String()
	{
		if (str != NULL)
		{
			delete[] str;
		}
		str = NULL;
	}

	//ostream& operator<<(const String* const this, ostream &out)
	ostream& operator<<(ostream& out)const //過載插入操作符
	{
		if (str != NULL)
		{
			out << str;
		}
		return out;
	}

	String(const String& s):str(NULL)
	{
		//str = s.str; 淺拷貝 是同一個空間,會造成一個空間釋放兩次
		//深拷貝
		str = new char[strlen(s.str)+1];
		strcpy(str, s.str);
	}
	String& operator=(const String& s)
	{
		if(this != &s)
		{
			delete[]str;
			str = new char[strlen(s.str)+1]
			strcpy(str,s.str);
		}
		return *this;
	}
};

ostream& operator<<(ostream& out, const String& s)
{
	s << out;
	//s.operator<<(cout);
	//operator<<(&s1,cout);
	return out;
}
int main()
{
	String s1("123");
	s1 << cout;
	//s1.operator<<(cout);
	//operator<<(&s1,cout);

	
	cout << s1 << endl;
	//operator<<(cout, s1);
}

前面之所以對空指標構建物件提供一個空間的原因:使其在賦值過載中只有指向堆區一種情況進行處理

通過此方式進行等號運運算元過載,然後調動拷貝構造對s2進行重寫構造

輸出流重寫

class String
{
	char* str;
public:
	String(const char* p = NULL) :str(NULL)
	{
		if (p != NULL)
		{
			str = new char[strlen(p) + 1];
			strcpy(str, p);
		}
		else
		{
			str = new char[1]; //額外提供一個空間
			*str = '';
		}
	}
	~String()
	{
		if (str != NULL)
		{
			delete[] str;
		}
		str = NULL;
	}

	//ostream& operator<<(const String* const this, ostream &out)
	ostream& operator<<(ostream& out)const //過載插入操作符
	{
		if (str != NULL)
		{
			out << str;
		}
		return out;
	}
};
int main()
{
	String s1("123");
	s1 << cout;
	//s1.operator<<(cout);
	//operator<<(&s1,cout);
}

在這裡通過改寫前的程式碼 operator<<(&s1,cout); 不難看出,將cout初始化out,隨後將this.str輸出至out

ostream& operator<<(ostream& out)const
此處只能使用參照,因為cout在ostream類中進行轉移,該類將拷貝建構函式定義為保護存取屬性,無法使用cout初始化out,繼而只能使用參照;同樣若我們不想使用實參去初始化形參,可以將拷貝建構函式定義為私有或保護型別

若希望輸出符合cout << s1 << endl;此種形式,需要再寫一個全域性函數

class String
{
	char* str;
public:
	String(const char* p = NULL) :str(NULL)
	{
		if (p != NULL)
		{
			str = new char[strlen(p) + 1];
			strcpy(str, p);
		}
		else
		{
			str = new char[1]; //額外提供一個空間
			*str = '';
		}
	}
	~String()
	{
		if (str != NULL)
		{
			delete[] str;
		}
		str = NULL;
	}

	//ostream& operator<<(const String* const this, ostream &out)
	ostream& operator<<(ostream& out)const //過載插入操作符
	{
		if (str != NULL)
		{
			out << str;
		}
		return out;
	}
};

ostream& operator<<(ostream& out, const String& s)
{
	s << out;
	//s.operator<<(cout);
	//operator<<(&s1,cout);
	return out;
}
int main()
{
	String s1("123");
	s1 << cout;
	//s1.operator<<(cout);
	//operator<<(&s1,cout);

	cout << s1 << endl;
	//operator<<(cout, s1);
}

通過此種形式進行翻轉,繼而達到符合 cout << s1 << endl; 的形式

MyString加號運運算元過載

int main()
{
	String s1("123");
	String s2("456");

	String s3;
	s3 = s1 + s2;
	S3 = s1 + "789";
	s3 = "789" + s1;
}

分別寫三個加號運運算元過載,來對應上面的三個情況(類+類、類+字串、字串+類)

	String operator+(const String& s)const
	{
		char *p = new char(strlen(this->str) + strlen(s.str) + 1);
		strcpy(p, this->str);
		strcat(p, s.str);
		return String(p);
	}

第一個為成員函數,但是存在記憶體漏失,需要進行下面的步驟

在私有成員變數中,建立一個新的建構函式,直接將p給到str,而沒有建立新的空間;並且在加號運運算元過載進行修改使其呼叫私有的建構函式

private:
	String(char*p,int)//兩個引數與公有構造區分
	{
		str = p;
	}
public:
	String operator+(const String& s)const
	{
		char *p = new char(strlen(this->str) + strlen(s.str) + 1);
		strcpy(p, this->str);
		strcat(p, s.str);
		return String(p,1);
	}

這樣就解決了原本記憶體漏失的問題
接下來完成剩餘兩個等號運運算元過載

String operator+(const char* s)const
{
	char* p = new char(strlen(this->str) + strlen(s) + 1);
	strcpy(p, this->str);
	strcat(p, s);
	return String(p, 1);
	//return *this + String(s)
	//上面的方式更方便,但是會構造兩個臨時物件
}

此處需要寫在類外,並且需要類內新增友元函數

friend String operator+(const char* t, const String s);

String operator+(const char* t, const String s)
{
	char* p = new char(strlen(s.str) + strlen(t) + 1);
	strcpy(p, s.str);
	strcat(p, t);
	return String(p, 1);
	//return String(p) + s; 與上面同理,並且不需要友元函數
}

討論一個衍生問題

class String
{
private:
	char* str;
public:
	String(const char* p = NULL) :str(NULL)
	{
		if (p != NULL)
		{
			str = new char[strlen(p) + 1];
			strcpy(str, p);
		}
		else
		{
			str = new char[1]; //額外提供一個空間
			*str = '';
		}
	}
	~String()
	{
		if (str != NULL)
		{
			delete[] str;
		}
		str = NULL;
	}
	String(const String& s)
	{
		//str = s.str; 淺拷貝 是同一個空間,會造成一個空間釋放兩次
		//深拷貝
		str = new char[strlen(s.str)];
		strcpy(str, s.str);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			delete[]str;
			str = new char[strlen(s.str)];
			strcpy(str, s.str);
		}
		return *this;
	}
};
String fun()
{
	String s2("456");
	return s2;
}
int main()
{
	String s1;
	s1 = fun();

	return 0;
}

討論此程式執行的過程總共建立了多少個物件:

主函數執行首先開闢main函數棧幀,建立s1物件,預設構造只有大小為一的空間存放“”;之後執行fun()函數,分配fun棧幀,然後建立s2物件,建立一個堆區,str指向堆區空間;並且將按值返回,需要構建一個臨時物件(將亡值);

將亡值概念:表示式過程中所產生的不具有名字的一個實體,叫做將亡值;將亡值的生存期僅在表示式的呼叫過程中,表示式呼叫結束,將亡值就會結束

構建臨時物件呼叫拷貝構造,fun函數結束,s2生存期結束,調動解構函式;首先釋放s2呼叫資源,再歸還s2空間;回到主函數,把將亡值賦值給s1呼叫賦值語句,接著呼叫將亡值的解構函式進行釋放

這個過程中總共建立了三個物件,分別是s1、s2、將亡值物件

那麼如果我們對fun以參照進行返回

String& fun()
{
	String s2("456");
	return s2;
}
int main()
{
	String s1;
	s1 = fun();
	
	return 0;
}

當以參照返回,就不會返回一個s2的備份,從參照底層來看會返回s2的地址;這樣會從一個已死亡物件來獲取資料,繼而會得到隨機值

隨後介紹的右值拷貝構造與右值賦值語句可以解決這個問題

到此這篇關於C++實現MyString的範例程式碼的文章就介紹到這了,更多相關C++ MyString內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com