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掉這塊空間。
指派操作符重載的幾種方法:
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;
}
注意上面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指針配置設定自己的空間(注意這時引用計數的變化,舊空間的引用計數減一,新配置設定的空間引用計數加一)。
寫時指派也可以用現代指派交換的方法:
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;
}