string類的模擬之深淺拷貝

淺拷貝:淺拷貝只拷貝指針,但拷貝後兩個指針指向同一個內存空間,或者可以說,原對象和拷貝對象共用一個實體,任何一個對象的改變都會引起另一個的改變。當類成員不包括指針何引用時,淺拷貝並無問題;但對於指針與引用成員,當對象的生命週期結束後,淺拷貝會造成同一塊內存單元被釋放兩次,導致內存泄漏。

深拷貝:不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝後的指針指向兩個不同地址。

調用拷貝構造函數後,淺拷貝依然還有聯繫,深拷貝的兩個對象完全獨立。淺拷貝類似於文件創建快捷方式,而深拷貝好比文件複製。編譯器默認提供的默認拷貝構造函數是淺拷貝,深拷貝的構造函數需自己實現。

//淺拷貝  
class String  
{  
public:  
    String(const char* pStr = "")//構造函數  
        :_pStr(new char[strlen(pStr)+1])  
    {  
            strcpy(_pStr,pStr);   
    }  
    String(const String& s)//拷貝構造函數  
    {  
        _pStr = s._pStr;  
    }  
    String& operator=(String& s)//賦值運算符重載  
    {  
        if(_pStr != s._pStr)//判斷是不是自己給自己賦值  
        {  
            _pStr = s._pStr;  
        }  
        return *this;  
    }  
    ~String()//析構函數  
    {  
        if(NULL != _pStr)  
        {  
            delete []_pStr;  
            _pStr = NULL;  
        }  
    }  
private:  
    char* _pStr;  
};  
  
void test()  
{  
    String s1("abcd");  
    String s2(s1);  
    String s3 = s2;//調用拷貝構造函數(編譯器會s2直接初始化s3)  
    String s4;//s4對象已經存在了  
    s4 = s3;//編譯器會調用賦值運算符重載將s3的值賦給s4  
}  
int main()  
{  
    test();    
    return 0;  
}  

S1,S2,S3,S4,指針指向的是同一塊空間,根據棧幀的開闢原則,先對S4,進行析構,然後S4的空間被釋放,S4,接着被置空,接下來堆S3進行析構時,程序崩潰。

既然如此,我們採用深拷貝的方法來構造對象,使得每個對象都擁有獨立的內存空間,在內存釋放是不會發生多次析構的問題。

class String  //深拷貝
{  
public:  
    String(const char* pStr = "")//構造函數  
        :_pStr(new char[strlen(pStr)+1])  
    {  
            strcpy(_pStr,pStr);  
    }  
    String(const String& s)//拷貝構造  
        :_pStr(new char[strlen(s._pStr)+1])  
    {  
        strcpy(_pStr,s._pStr);  
    }  
    String& operator=(const String& s)//賦值運算符重載  
    {  
        if(_pStr != s._pStr)//判斷自賦值  
        {  
            char* temp  = new char[strlen(s._pStr)+1];//先開闢一段新空間  
            strcpy(temp,s._pStr);//將原對象的值賦給新空間  
            delete []_pStr;//釋放當前對象  
            _pStr = temp;//將當前對象指向新開闢的空間  
        }  
        return *this;  
    }  
    ~String()//析構  
    {  
        if(NULL != _pStr)  
        {  
            delete[]_pStr;  
            _pStr = NULL;  
        }  
    }  
private:  
    char* _pStr;  
};  
  
void test()  
{  
    String s1("abcd");  
    String s2(s1);  
    String s3 = s2;//調用拷貝構造函數(編譯器會s2直接初始化s3)  
    String s4;//s4對象已經存在了  
    s4 = s3;//編譯器會調用賦值運算符重載將s3的值賦給s4  
}  
int main()  
{  
    test();  
    system("pause");  
    return 0;  
}  


我們可以看到沒個對象都新開闢了一塊內存,在析構的時候不會因爲析構多次二發生內存泄漏。

深拷貝的現代寫法,基本思想利用臨時對象來構造出需要拷貝的對象,然後將個對象的內容交換。(可以看做是一種剝奪)
//深拷貝(簡潔版2)  
class String  
{  
public:  
    String(const char* pStr = "")//構造函數  
        :_pStr(new char[strlen(pStr)+1])  
    {  
        if(0 == *pStr)  
        {  
            *_pStr = '\0';  
        }  
        else  
        {  
            strcpy(_pStr,pStr);  
        }  
    }  
    String(String& s)//拷貝構造  
        :_pStr(NULL)//防止交換後temp指向隨機空間,本函數調用結束導致內存泄漏以致崩潰  
    {  
        String temp(s._pStr);//如果不給出臨時變量,交換後s的值將爲NULL  
        std::swap(_pStr,temp._pStr);  
    }  
//1  
    String& operator=(const String &s)//賦值運算符重載  
    {  
        if(_pStr != s._pStr)  
        {  
            String temp(s._pStr);//如果不給出臨時變量,交換後s的值將爲NULL  
            std::swap(_pStr,temp._pStr);  
        }  
        return *this;  
    }  
    /* 2 
    String& operator=(const String& s) 
    { 
    if (this != &s) 
    { 
    String temp(s); 
    std::swap(_pStr, temp._pStr); 
    } 
    return *this; 
    }*/  
  
    /* 3 
    String& operator=(String temp) 
    { 
        std::swap(_pStr, temp._pStr); 
        return *this; 
    }*/  
    ~String()//析構函數  
    {  
        if(NULL == _pStr)  
        {  
            return;  
        }  
        else  
        {  
            delete[]_pStr;  
            _pStr = NULL;  
        }  
    }  
private:  
    char* _pStr;  
};  
  
void test()  
{  
    String s1("abcd");  
    String s2(s1);  
    String s3 = s2;//調用拷貝構造函數(編譯器會s2直接初始化s3)  
    String s4;//s4對象已經存在了  
    s4 = s3;//編譯器會調用賦值運算符重載將s3的值賦給s4  
}  
int main()  
{  
    test();  
    system("pause");  
    return 0;  
}  
利用深拷貝實現string類的模擬。(完整版)
String.cpp

#include <iostream>
#include <assert.h>
#include <string.h>
#include "String.h"

using namespace std;
現代寫法    -----   剝奪    -------利用構造函數創建,然後交換

String::String(const String& s)
:_size(s._size)
, _capacity(s._capacity)
, _str(new char[strlen(s._str) + 1])

{
	strcpy(_str, s._str);
}

//String::String(const String& s)
//:_str(NULL)
//{
//	//構造函數
//	String tmp(s._str);
//	//交換
//	swap(_str, tmp._str);
//}


String& String::operator=(const String& s)
{
	//if (this != &s)//自賦值
	//{
	//	delete[]_str;	//釋放原有空間
	//	_str = new char[strlen(s._str) + 1];
	//	strcpy(_str, s._str);
	//	_size = s._size;		
	//	_capacity = s._capacity;
	//}
	//return *this;

	String tmp(s._str);
	swap(_str, tmp._str);
	return *this;

	//swap(_str, s._str);			//不爲錯,額外的內存開銷
	//return *this;

}

String::~String()
{
	if (_str != NULL)
	{
		delete[]_str;
		_str = NULL;			//可置可不置空
		_size = 0;
		_capacity = 0;
	}
}

const char* String::c_str()
{
	return _str;
}


void String::Swap(String& s)		//s1.Swap(s2);
{
	assert(this != &s);
	swap(_str, s._str);
	swap(_size, s._size);
	swap(_capacity, s._capacity);
}

void String::Expand(size_t n)	
{
	if (n > _capacity)
	{

		char* tmp = new char[_capacity];
		strcpy(tmp, _str);
		delete[]_str;
		_str = tmp;
		_capacity = n;
	}
}

void String::PushBack(char ch)
{
	if (_size  >= _capacity)
	{
		Expand(_capacity * 2);
	}
	_str[_size++] = ch;
	_str[_size] = '\0';
}

void String::PushBack(const char* str)
{
	assert(str);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		Expand(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

void String::PushFront(char ch)
{
	if (_size + 1 > _capacity)	//增容
	{
		Expand(_capacity * 2);
	}
	for (int i = _size + 1; i >= 0; --i)	//搬移
	{
		_str[i + 1] = _str[i];
	}
	_str[0] = ch;
	_size++;
}

void String::PushFront(const char* str)
{
	assert(str);
	size_t len = strlen(str);
	if (len + _size > _capacity)
	{
		Expand(len + _size);
	}
	for (int i = _size; i >= 0; --i)
	{
		_str[i + len] = _str[i];
	}
	memcpy(_str, str, len);

	_size += len;
}

void String::Insert(size_t pos, char ch)
{
	assert(pos < _size);
	if (_size + 1 > _capacity)
	{
		Expand(_capacity * 2);
	}
	for (int i = _size; i >= (int)pos; --i)	//強轉pos防止發生無限循環
	{
		_str[i + 1] = _str[i];
	}
	_str[pos] = ch;
	_size++;
}

void String::Insert(size_t pos, const char* str)
{
	assert(str);
	assert(pos <= _size);
	size_t len = strlen(str);
	if (len + _size > _capacity)
	{
		Expand(_size + len);
	}
	for (int i = _size; i >= (int)pos; --i)
	{
		_str[i + len] = _str[i];
	}
	while (*str != '\0')
	{
		_str[pos++] = *(str++);
	}
	_size += len;
}

void String::Erase(size_t pos, size_t n)
{
	//1.pos
	assert(pos < _size);
	//pos+n
	if (pos + n >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		strcpy(_str + pos, _str + pos + n);
		_size -= n;
	}
}

size_t String::Find(char ch, size_t pos)
{
	for (size_t i = pos; i < _size; i++)
	{
		if (ch == _str[i]) return i;
	}
	return -1;
}

size_t String::Find(const char* str, size_t pos)
{
	assert(str != NULL);
	assert(pos < _size);
	assert(_size > strlen(str));
	const char* src = _str + pos;

	while (*src != '\0')//標記母串的查找位置
	{
		const char* match = str;
		const char* cur = src;
		while (*match != '\0' && *match == *cur)//子串沒有走到結尾
		//子串的當前字符不等於母串
		{
				match++;
				cur++;
		}
		if (*match == '\0')
		{
			return src - _str;
		}
		src++;
	}
	return -1;
}

void String::Replace(size_t pos, int len, const char* sub2)
{
	//pos > _size  == _size   <  _size
	//len > strlen(sub2)              pos + len > size
	//sub2
}



bool String::operator>(const String& s)const
{
	size_t i = 0;
	while (_str[i] != '\0' && s._str[i] != '\0')
	{
		if (_str[i] == s._str[i])
			i++;
		else if (_str[i] > s._str[i])
			return true;
		else
			return false;
	}
	if (_str[i] != '\0')
		return true;
	else
		return false;
}

bool String::operator>=(const String& s)const
{
	return *this > s || *this == s;
}

bool String::operator<(const String& s)const
{
	return !(*this >= s);
}

bool String::operator<=(const String& s)const
{
	return !(*this > s);
}

bool String::operator==(const String& s)const
{
	size_t i = 0;
	while (_str[i] != '\0' && s._str[i] != '\0')
	{
		if (_str[i] == s._str[i])
			i++;
		else
			return false;
	}
	if (_str[i] == '\0' && s._str[i] == '\0')
		return true;
	else
		return false;
}

bool String::operator!=(const String& s)const
{
	return !(*this == s);
}

String String::operator+(char ch)
{
	String tmp(*this);		//構造
	tmp.PushBack(ch);			
	
	return tmp;
}

String& String::operator+=(char ch)
{
	PushBack(ch);
	return *this;
}

String String::operator+(const char* str)
{
	String tmp(*this);
	tmp.PushBack(str);

	return tmp;
}

String& String::operator+=(const char* str)
{
	PushBack(str);
	return *this;
}

void TestString()
{
	String s1("change world");
	cout << s1.Find("wor")<<endl;

}

int main()
{
	TestString();
	return 0;
}
String.h
#pragma
#include <string.h>

#define NULL 0

class String
{
public:

	String(const char* str = "")
	:_str(new char[strlen(str) + 1])	
	, _size(strlen(str))
	, _capacity(strlen(str))		//size和capacity保持一致'\0'不算給_capacity
	{
		strcpy(_str, str);
	}

	String(const String& s);
	String& operator=(const String& s);
	~String();

	const char* c_str();

	void Swap(String& s);
	void Expand(size_t n);
	void PushBack(char ch);
	void PushBack(const char* str);
	void PushFront(char ch);
	void PushFront(const char* str);

	void Insert(size_t pos, char ch);
	void Insert(size_t pos, const char* str);
	void Erase(size_t pos, size_t n = 1);
	void Replace(size_t pos, int len, const char* sub2);


	size_t Find(char ch, size_t pos);
	size_t Find(const char* str, size_t pos = 0);

	String operator+(char ch);
	String& operator+=(char ch);
	String operator+(const char* str);
	String& operator+=(const char* str);


	bool operator>(const String& s)const;
	bool operator>=(const String& s)const;
	bool operator<(const String& s)const;
	bool operator<=(const String& s)const;
	bool operator==(const String& s)const;
	bool operator!=(const String& s)const;

private:
	size_t _size;
	size_t _capacity;
	char* _str;

};











發佈了102 篇原創文章 · 獲贊 21 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章