【C++進階二】string的模擬實現
- 1.構造函數和C_str
- C_str:
- 2.operator[]
- 3.拷貝構造
- 3.1淺拷貝
- 3.2深拷貝
- 4.賦值
- 5.迭代器
- 6.比較ascll碼值的大小
- 7.reverse擴容
- 8.push_back尾插和append尾插
- 9.+=
- 10.insert
- 10.1在pos位置前插入字符ch
- 10.2在pos位置前插入字符串str
- 11.resize
- 12.erase
- 12.1從pos位置開始刪除len個數據
- 12.2當pos+len<總長度時,使用strcpy函數拷貝,從而覆蓋刪除要被刪除的字符
- 12.3當pos+len大于總長度或者len等于npos時,剩余長度全部刪除
- 13.流插入<<
- 14.流提取 >>
string底層是一個字符數組,為了跟庫里的string區別,我們定義了一個命名空間將string類包含咋內
1.構造函數和C_str
錯誤示范:
使用new開辟空間可以再次改進構造函數:
最終優化為全缺省的構造函數:
不可以將缺省值設置成nullptr,strlen(str)對于str指針解引用,遇到’\0’終止,解引用NULL會報錯。將缺省值設置成一個空字符串,結尾默認為’\0’
C_str:
const char* C_str()//返回const char*類型的指針
{return _str;
}
返回const char*類型的指針相當于返回字符串
2.operator[]
由于可能存在 string與const string類型,所以設置成兩個函數構成函數重載
char& operator[](size_t pos)//operator[]
{return _str[pos];
}
char& operator[](size_t pos)const//operator[]的函數重載
{return _str[pos];
}
3.拷貝構造
3.1淺拷貝
拷貝構造函數如果不寫編譯器會自動生成,對于內置類型完成值拷貝或者淺拷貝,若使用編譯器自動生成的拷貝構造就會報錯
string s2("hello world");
string s3(s2);
3.2深拷貝
string(const string& s)//拷貝構造:_size(s._size),_capaicty(s._capaicty)
{_str = new char[_capaicty + 1];//開辟一塊空間strcpy(_str, s._str);//將s2拷貝給s1
}
創建一塊同樣大小的空間,并將原來的數據拷貝下來,這樣就是s2與s3指向各自的空間,一個被修改也不會影響另一個
4.賦值
賦值運算符也是默認成員函數,如果不寫會進行淺拷貝/值拷貝
- 第一種兩者空間大小相同,進行值拷貝
- 第二種s1的空間遠大于s2的空間,進行值拷貝會浪費空間,所以系統會按照第三種做法執行
- 第三種,s1的空間太小,需要new開辟一塊空間,將舊空間銷毀,將s2拷貝到新開辟的空間
編譯器默認不會這樣處理,它會直接將舊空間釋放,再去開新空間,并將值拷貝過來
string& operator=(const string& s)//賦值 s1=s3
{if(this != &s)//排除賦值本身的情況{char* tmp = new char[s._capaicty + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capaicty = s._capaicty;}return *this;
}
若釋放舊空間,如果new失敗了,則破壞原有空間,所以使用一個臨時變量tmp接收開辟的空間,如果new成功將tmp傳給_str,若new失敗也不會破壞s1空間
5.迭代器
使用typedef 分別將用iterator代替 char*,const_iterator代替 const char*
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{return _str;
}
iterator end()
{return _str + _size;
}
iterator begin()const //指針指向的內容不能被修改
{return _str;
}
iterator end()const
{return _str + _size;
}
6.比較ascll碼值的大小
通過C語言函數strcmp,比較字符串從頭開始字符的ASCII值,再通過復用來實現剩下的
如果不小心在復用時將const修飾的傳給非const成員就會報錯,所以括號外面加上const,修飾this指針
bool operator==(const string& s)const //s1==s2
{return strcmp(_str, s._str)==0;
}
bool operator<(const string& s)const //s1<s2
{return strcmp(_str, s._str) < 0;
}
bool operator<=(const string& s)const //s1<=s2
{return *this < s || *this == s;
}
bool operator>(const string& s)const //s1>s2
{return !(*this <= s);
}
bool operator>=(const string& s)const //s1>=s2
{//復用return *this > s || *this == s;
}
bool operator!=(const string& s)const //s1!=s2
{return !(*this == s);
}
7.reverse擴容
void reserve(size_t n)//開辟空間
{if(n > _capacity)//防止縮容的問題{char* tmp = new char[n + 1];//多開一個'\0'strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;//計算有效}
}
為了防止new失敗,所以使用臨時變量tmp指向new出來的空間,若new成功,釋放舊空間,并將_str指向新空間
8.push_back尾插和append尾插
通過reserve進行類似擴容的操作,再將ch賦值給當前最后一個字符
void push_back(char ch)//尾插字符
{if(_size + 1 > _capaicty){reserve(2 * _capaicty);//開辟2倍空間}_str[_size] = ch;_size++;
}
void append(const char* str)//尾插 字符串
{ int len = strlen(str);if(_size + len > _capaicty){reserve(_size + len);}strcpy(_str + _size, str);//在原來的字符串后拷貝字符串_size += len;
}
9.+=
string& operator+=(char ch)//+= 字符
{push_back(ch);return *this;
}string& operator+=(const char* str)//+= 字符串 函數重載
{append(str);return *this;
}
10.insert
10.1在pos位置前插入字符ch
void insert(size_t pos, char ch)//在pos位置前插入字符ch
{if(_size + 1 > _capacity)//擴容{reserve(2 * _capacity);}size_t end = _size + 1;//'\0'的下一個位置while(end > pos){//把前面傳給后面_str[end] = _str[end - 1];end--;}_str[pos] = ch;_size++;
}
由于pos與end都是size_t類型,沒有負數,所以當while循環條件設置為
end>=pos
時,如果pos=0,end–-,end變為負數,計算的就是其補碼了,所以while(end>=pos)
一直成立,無法結束循環
把前面的字符傳給后面的字符,當end下標為1時,end-1的下標為0,循環結束
10.2在pos位置前插入字符串str
void insert(size_t pos, const char* str)//在pos位置前插入字符串str
{int len = strlen(str);if(_size + len > _capacity)//擴容{reserve(_size + len);}size_t end = _size + len;while(end > pos+len-1){//把前面傳給后面_str[end] = _str[end-len];end--;}strncpy(_str + pos, str, len);//拷貝len個字節,不包含'\0'_size += len;
}
為了保證最后一次循環時,下標end-len在下標為0的位置上,所以取end為pos+len,如果end>pos+len-1不成立時終止循環,最后一次end取值即為pos+len
使用strncpy函數,將不包含’\0’的str拷貝到_str+pos下標位置
11.resize
分為三種情況
- n<size 刪除數據
- size<n<capacity 剩余空間初始化
- n>capacity 擴容+初始化
void resize(size_t n,char ch)//開辟空間+初始化
{if(n <= _size)//刪除數據保留前n個{_size = n;_str[n] = '\0';}else//n>_size{if(n >_capacity)//擴容{reserve(n);}int i = _size;while(i < n)//剩余空間初始化為ch{_str[i] = ch;i++;}_size = n;_str[_size] = '\0';}
}
12.erase
12.1從pos位置開始刪除len個數據
static const size_t npos = -1;
string& erase(size_t pos = 0, size_t len = npos)//從pos位置開始刪除len個數據
{if(len==npos||pos+len>=_size){//全部刪除_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str +pos+len);//包含'\0'_size -= len;}return *this;
}
12.2當pos+len<總長度時,使用strcpy函數拷貝,從而覆蓋刪除要被刪除的字符
12.3當pos+len大于總長度或者len等于npos時,剩余長度全部刪除
13.流插入<<
使用友元函數是為了在類外面調用類的私有的成員變量,若不需要調用則不用友元函數
ostream& operator<<(ostream& out, const string&s)
{int i = 0;for(i = 0; i < s.size(); i++){out << s[i] << " ";}return out;
}
實現流插入不可以調用C_str(),因為C_str()返回的是一個字符串,遇見’\0’就會結束,但若打印結果有好幾個’\0’,則遇見第一個就會結束,不符合預期
14.流提取 >>
輸入多個值,C++規定空格或換行是值與值之間的區分
錯誤示范:
istream& operator>>(istream& in, string& s)//>>
{char ch;in >> ch;while(ch != ' ' && ch != '\n'){s += ch;in >> ch;}return in;
}
- 上述代碼在循環中無法找到空格/換行,導致循環無法停止
- 輸入的數據存放在緩沖區中,使用循環在緩沖區中提取數據,但是空格/換行不在緩沖區中,因為認為它是多個值之間的間隔
- 使用get就不會認為空格/換行是多個值之間的間隔,若遇見空格/換行就會存放緩沖區中等待提取
istream& operator>>(istream& in, string& s)//>>
{s.clear();char ch = in.get();char buf[128];size_t index = 0;while(ch != ' ' && ch != '\n'){buf[index++] = ch;if(index == 127)//為了防止頻繁擴容{buf[127] = '\0';s += buf;index = 0;}ch = in.get();}if(index != 0){buf[index] = '\0';s += buf;}return in;
}
當需要輸入的string對象中有值存在時,需要先使用clear清空,再輸入新的數據
為了避免頻繁擴容,使用一個128的字符數組接收,若輸入的數據比128小,跳出循環將數組中的數據傳給string類s,若輸入的數據比128大,則將字符數組整體傳給string類s,再正常擴容