天天看點

C++面試題之模拟實作string類

         C++中的string類是一個很常見的面試題,string類裡必須有的構造函數,拷貝構造,指派運算符重載,析構函數等成員函數,下面看看是如何實作以及如何處理動态記憶體

拷貝構造的幾種實作方法:

1.傳統深拷貝

//傳統深拷貝
	String(const String& s)
		:_str(new char[strlen(s._str) + 1])    //深拷貝構造函數
	{
		strcpy(_str, s._str);
		cout << "String(const String& s)" << endl;
	}
           

如果不用引用計數的話,在這裡隻能用深拷貝

深淺拷貝問題連接配接:http://blog.csdn.net/dream_1996/article/details/61923747

2.現代方法拷貝

//現代方式拷貝
	String(const String& s)     
		:_str(NULL)                  
	{                                
		String tmp(s._str);         
		std::swap(_str, tmp._str);
		cout<<"String(const String& s)"<<endl;
	}

           

這裡不用給_str申請空間,直接把_str賦成空,然後用s._str構造一個tmp對象,把tmp和*this的_str的空間交換,這時tmp._str就指向空,而_str現在指向原來s._str的空間,在函數調用結束時tmp會自動釋放

3.寫時拷貝

//寫時拷貝
	String(const String& s)
		:_str(s._str)
		, _size(s._size)
	{
		++(*_size);
	} 
           
private:
	char *_str;
	int *_size;
           

    寫時拷貝技術是通過"引用計數"實作的,因為淺拷貝的缺陷,是以在這個時候我們就引入了引用計數的拷貝。但是當其中一個對象改變它的值時,其他對象的值就會随之改變,是以此時我們采取這樣一種做法,就是寫時拷貝。寫時拷貝指用淺拷貝的方法拷貝其他對象,多個指針指向同一塊空間,隻有當對其中一個對象修改時,才會開辟一個新的空間給這個對象,和它原來指向同一空間的對象不會收到影響。在定義成員變量的時候多配置設定4個位元組,_size用來記錄有多少個指針指向塊空間,當有新的指針指向這塊空間時,引用計數加一,當要釋放這塊空間時,引用計數減一(假裝釋放),直到引用計數減為0時才真的delete掉這塊空間。

C++面試題之模拟實作string類

指派操作符重載的幾種方法:

1.傳統指派

//傳統指派
	String& operator=(const String& s2)             //想要str1 = str2,不能直接讓str1指向str2那段空間,必須從新開辟一段,否則會在析構時出問題
	{
		delete[] _str;                                     //先把this指針指向的_str的空間釋放了,再給_str開辟新的空間
		if (_str != s2._str)
		{
			_str = new char[strlen(s2._str) + 1];
			strcpy(_str, s2._str);
		}
		return *this;
	}
           
C++面試題之模拟實作string類

注意上面s是引用,是以不用釋放s的空間。

2.現代方法指派

String& operator=(String s2)     //這裡調用拷貝構造函數開辟一段空間複制s2,然後交換s2和*this的字元串
	{
		std::swap(_str, s2._str);
		return *this;
	}
           

交換_str和s2._str所指向的空間後,就成功指派了,然後在函數結束時調用析構函數釋放s2的空間

3.寫時指派

//寫時指派s1 = s2
	String& operator=(const String& s)
	{
		//1.判斷s1和s2是否指向統一塊空間
		//2.減減s1指向空間的引用計數,若引用計數為0了,就釋放s1的空間
		if (_str != s._str)
		{
			if (--(*_size) == 0)
			{
				delete _size;
				delete[] _str;
				_str = NULL;
				 _size = NULL;
			}    
			this->_str = s._str;     //讓s1._str指向s2._str
			this->_size = s._size;   //讓s1._size指向s2._size
			++(*s._size);            //引用計數加1
		}
		return *this;
	}
           

當有的指針要改變這塊空間的值時,再為_size指針配置設定自己的空間(注意這時引用計數的變化,舊空間的引用計數減一,新配置設定的空間引用計數加一)。

C++面試題之模拟實作string類

寫時指派也可以用現代指派交換的方法:

String& operator=(String s)  //這裡會調用寫時拷貝構造,會使s的引用計數加1
	{
		swap(_str, s._str);
		swap(_size, s._size);    //s在函數調用完就自動釋放了
		return *this;
	}
           

下面是一個用深拷貝的方式模拟實作string類的程式:

#include<iostream>
#include<string>
#include<assert.h>
#pragma warning (disable:4996)
using namespace std;


class String
{
	friend ostream& operator<<(ostream& out, String& s);
public:
	String(const char* str = "")
		:_str(new char[strlen(str) + 1])
		, _size(strlen(str))
		, _capacity(strlen(str))
	{
		strcpy(_str, str);
	}
	String(const String& s)
	{
		String tmp(s._str);
		this->Swap(tmp);
	}
	//檢查并配置設定記憶體
	void CheckCapacity(int count)
	{
		size_t len = _size + count;
		if (len > _capacity)
		{
			char* tmp = (char*)realloc(_str, len + 1);  //開辟空間時len+1是總長,不+1就沒地放\0
			if (tmp != NULL)
			{
				_str = tmp;
			}
			else
			{
				cout << "開辟記憶體失敗" << endl;
				exit(1);
			}
			_capacity = len;
		}
	}
	//尾插
	void PushBack(char ch)
	{
		CheckCapacity(5);
		_str[_size] = ch;
		_str[_size + 1] = '\0';
		++_size;
	}
	//在指定位置插入指定長度的字元串對象
	void Insert(size_t pos, const String&s, size_t SubPos, size_t Len)
	{
		assert(pos <= _size&&SubPos<s._size);

		while (Len>0)  //插幾個循環幾次
		{
			--Len;
			this->Insert(pos, s._str[SubPos - 1]);
			++pos;
			++SubPos;
		}
	}
	//插入字元
	inline void Insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		CheckCapacity(5);
		int end = _size;
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}

	String SubStr(size_t pos, size_t len)
	{
		assert(pos < _size);
		size_t count = len;
		if (pos + len>_size - 1)
		{
			count = _size - pos;
		}

		String Sub;
		Sub.CheckCapacity(count);
		char* dst = Sub._str;
		char* src = _str + pos;
		for (size_t i = 0; i < count; i++)
		{
			dst[i] = src[i];
		}
		dst[count] = '\0';
		Sub._size = count;
		return Sub;
	}

	//插入指定字元串
	void Insert(size_t pos, const char* str)
	{
		assert(pos <= _size);

		int len = strlen(str);
		CheckCapacity(_size + len);
		int end = _size;
		while (end >= (int)pos)//1 2 3 4 5 6
		{
			_str[end + len] = _str[end];
			--end;
		}
		int i = 0;
		while (len)
		{
			_str[pos + i] = str[i];
			--len;
			++i;
			_size++;
		}
	}
	//删除指定長度字元串
	void Erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if ((pos + len) >= _size)
		{
			_size = pos;
			_str[_size] = '\0';
		}
		else
		{
			char* dst = _str + pos + len;
			char* src = _str + pos;
			strcpy(src, dst);
			_size -= len;
		}

	}

	int Find(char ch)
	{
		int i = strlen(_str);
		int j = 0;
		for (j = 0; j < i; j++)
		{
			if (ch == _str[j])
			{
				return j;
			}
		}
		return -1;
	}
	int Find(char* str)
	{
		int i = strlen(_str);
		int j = 0;
		for (j = 0; j < i; j++)
		{
			int n = j;
			char* tmp = str;
			while (*tmp == _str[n])
			{
				tmp++;
				n++;
			}
			if (*tmp == '\0')
			{
				return j;
			}
			else
			{
				continue;
			}
		}
		return -1;
	}
	//重載[]
	char& operator[](const size_t a)
	{
		if (a<0 || a>_size)
		{
			cout << "修改對象字元下标有誤" << endl;
			exit(1);
		}
		return _str[a];
	}
	//指派
	String& operator=(String s)
	{
		this->Swap(s);
		return *this;
	}
	//交換對象
	void Swap(String& s)
	{
		swap(_str, s._str);
		swap(_size, s._size);
		swap(_capacity, s._capacity);
	}
	~String()
	{
		if (_str != NULL)
		{
			delete[] _str;
			_size = NULL;
			_size = _capacity = 0;
		}
	}
private:
	char* _str;
	size_t _size;
	size_t _capacity;
};
//重載輸出符
ostream& operator<<(ostream& out, String& s)
{
	out << s._str;
	return out;
}


int main()
{
	String s1("abaaaaaacdef");
	String s2("123456");
	//s1[5] = 'a';     //增
	//s1.PushBack('a');   尾插
	//s1.Insert(0, "c");  //插一串
	//s2.Erase(0, 2);        //删
	String tmp = s1.SubStr(2, 6);

	//int ret = s2.Find("345");
	//if (ret == -1)
	//{
	//	cout << "沒找到" << endl;
	//}
	//else
	//{
	//	cout << "在下标[" <<ret<<"]處"<< endl;
	//}
	cout << s1 << endl;
	cout << tmp << endl;
	cout << s2 << endl;
	return 0;
}