C++?STL-string類底層實(shí)現(xiàn)過程
實(shí)現(xiàn)框架
#include<iostream>
#include<assert.h>
using namespace std;
namespace lzg
{
class string
{
public:
//typedef char* iterator;
using iterator = char*;
using const_iterator = const char*;
//一、默認(rèn)成員函數(shù)
string(const char* str = ""); //默認(rèn)構(gòu)造
string(const string& s); //拷貝構(gòu)造
string& operator=(const string& s); //賦值重載
~string(); //析構(gòu)函數(shù)
//二、迭代器相關(guān)函數(shù)
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
//三、容量和大小相關(guān)函數(shù)
void reserve(size_t n);
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
void resize(size_t n, char ch = '\0');
//四、與修改字符串相關(guān)的函數(shù)
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(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 len = npos);
void clear()
{
_str[0] = '\0';
_size = 0;
}
//五、訪問字符串相關(guān)函數(shù)
char& operator[](size_t i)
{
assert(i < _size);
return _str[i];
}
const char& operator[](size_t i) const
{
assert(i < _size);
return _str[i];
}
const char* c_str() const
{
return _str;
}
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos, size_t len = npos);
private:
char* _str = nullptr;
size_t _size = 0;
size_t _capacity = 0;
public:
static const size_t npos;
};
//六、關(guān)系運(yùn)算符重載函數(shù)
bool operator== (const string& lhs, const string& rhs);
bool operator!= (const string& lhs, const string& rhs);
bool operator> (const string& lhs, const string& rhs);
bool operator< (const string& lhs, const string& rhs);
bool operator>= (const string& lhs, const string& rhs);
bool operator<= (const string& lhs, const string& rhs);
ostream& operator<<(ostream& os, const string& str);
istream& operator>>(istream& is, string& str);
}一、默認(rèn)成員函數(shù)
1.默認(rèn)構(gòu)造函數(shù)
string()
:_str(new char[1] {'\0'}),
_size(0),
_capacity(0)
{}為_str開辟1字節(jié)空間來(lái)存放'\0'_capacity不記錄\0的大小,這樣調(diào)用c_str()就不會(huì)報(bào)錯(cuò)
2.構(gòu)造函數(shù)
string(const char* str)
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}注意在private中的聲明順序,這里初始化列表只走_(dá)size(大小為str的長(zhǎng)度),讓_str和_capacity在函數(shù)體內(nèi)初始化(這樣保證了不會(huì)出錯(cuò),比如三者都在初始化列表的話,會(huì)先初始化_str這樣有風(fēng)險(xiǎn)),同樣的,_str開辟空間時(shí),為'\0'多開辟1字節(jié)
構(gòu)造函數(shù)有一個(gè)默認(rèn)構(gòu)造就行了,寫1個(gè)就行,把1注釋掉,并把2改為全缺省
string(const char* str="")//不要寫成" "這是非空
:_size(strlen(str))
{
_capacity = _size;
_str = new char[_size + 1];
strcpy(_str, str);
}3.拷貝構(gòu)造函數(shù)(重點(diǎn))
在模擬實(shí)現(xiàn)拷貝構(gòu)造函數(shù)前,我們應(yīng)該首先了解深淺拷貝:
我們不寫拷貝構(gòu)造,編譯器默認(rèn)生成的拷貝構(gòu)造是值拷貝也叫淺拷貝
- 淺拷貝:拷貝出來(lái)的目標(biāo)對(duì)象的指針和源對(duì)象的指針指向的內(nèi)存空間是同一塊空間。其中一個(gè)對(duì)象的改動(dòng)會(huì)對(duì)另一個(gè)對(duì)象造成影響。
- 深拷貝:深拷貝是指源對(duì)象與拷貝對(duì)象互相獨(dú)立。其中任何一個(gè)對(duì)象的改動(dòng)不會(huì)對(duì)另外一個(gè)對(duì)象造成影響。
很明顯,我們并不希望拷貝出來(lái)的兩個(gè)對(duì)象之間存在相互影響,因此,我們這里需要用到深拷貝。
//s2(s1) s1
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}這里還有一個(gè)現(xiàn)代(偷懶)寫法
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}現(xiàn)代寫法與傳統(tǒng)寫法的思想不同:先構(gòu)造一個(gè)tmp對(duì)象,然后再將tmp對(duì)象與拷貝(s)對(duì)象的數(shù)據(jù)交換即可。
4.賦值運(yùn)算符重載函數(shù)
與拷貝構(gòu)造函數(shù)類似,賦值運(yùn)算符重載函數(shù)的模擬實(shí)現(xiàn)也涉及深淺拷貝問題,我們同樣需要采用深拷貝
再?gòu)?qiáng)調(diào)一下賦值和拷貝的區(qū)別,賦值是兩個(gè)已創(chuàng)建的對(duì)象之間完成賦值操作,拷貝是指一個(gè)已創(chuàng)建的對(duì)象調(diào)用拷貝構(gòu)造生成一個(gè)新的對(duì)象
// s1 = s3 s3
string& operator=(const string& s)
{
if (this != &s)//避免自己給自己賦值
{
delete[] _str; //釋放掉s1的舊空間,開辟一個(gè)和s3容量的新空間
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}同樣的賦值也有現(xiàn)代寫法
// 現(xiàn)代寫法
// s1 = s3
string& operator=(string s)
{
swap(s);
return *this;
}賦值運(yùn)算符重載函數(shù)的現(xiàn)代寫法是通過采用“值傳遞”接收右值的方法,讓編譯器自動(dòng)調(diào)用拷貝構(gòu)造函數(shù),然后我們?cè)賹⒖截惓鰜?lái)的對(duì)象與左值進(jìn)行交換即可
5.析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}二、迭代器相關(guān)函數(shù)
string類中的迭代器實(shí)際上就是字符指針,只是給字符指針起了一個(gè)別名叫iterator而已。
typedef char* iterator; typedef const char* const_iterator;
begin和end
iterator begin()
{
return _str; //返回字符串中第一個(gè)字符的地址
}
const_iterator begin()const
{
return _str; //返回字符串中第一個(gè)字符的const地址
}
iterator end()
{
return _str + _size; //返回字符串中最后一個(gè)字符的后一個(gè)字符的地址
}
const_iterator end()const
{
return _str + _size; //返回字符串中最后一個(gè)字符的后一個(gè)字符的const地址
}
其實(shí)范圍for的底層就是調(diào)用了begin()和end()迭代器,當(dāng)你模擬實(shí)現(xiàn)了迭代器,范圍for自然就能使用
string s("hello world!!!");
//編譯器將其替換為迭代器形式
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
注:自己寫的begin()和end()函數(shù)名必須是這樣的,不能有任何大小寫否則范圍for就報(bào)錯(cuò)
三、容量和大小相關(guān)函數(shù)
size和capacity
因?yàn)閟tring類的成員變量是私有的,我們并不能直接對(duì)其進(jìn)行訪問,所以string類設(shè)置了size和capacity這兩個(gè)成員函數(shù),用于獲取string對(duì)象的大小和容量。
size函數(shù)用于獲取字符串當(dāng)前的有效長(zhǎng)度(不包括’\0’)。
size_t size() const
{
return _size;
}capacity函數(shù)用于獲取字符串當(dāng)前的容量。(不包括’\0’)。
size_t capacity() const
{
return _capacity;
}reserve和resize
reserve和resize這兩個(gè)函數(shù)的執(zhí)行規(guī)則一定要區(qū)分清楚。
reserve規(guī)則:
- 1、當(dāng)n大于對(duì)象當(dāng)前的capacity時(shí),將capacity擴(kuò)大到n或大于n。
- 2、當(dāng)n小于對(duì)象當(dāng)前的capacity時(shí),什么也不做。
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];//多開一個(gè)空間用于存放'\0'
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}resize規(guī)則:
- 1、當(dāng)n大于當(dāng)前的size時(shí),將size擴(kuò)大到n,擴(kuò)大的字符為ch,若ch未給出,則默認(rèn)為’\0’。
- 2、當(dāng)n小于當(dāng)前的size時(shí),將size縮小到n。
//改變大小
void resize(size_t n, char ch = '\0')
{
if (n <= _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reserve(n);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}四、與修改字符串相關(guān)的函數(shù)
push_back
void push_back(char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c; // 在結(jié)尾位置寫入新字符(覆蓋原'\0')
_size++; // 增加長(zhǎng)度
_str[_size] = '\0'; // 在新結(jié)尾補(bǔ)終止符
}實(shí)現(xiàn)push_back還可以直接復(fù)用下面即將實(shí)現(xiàn)的insert函數(shù)。
//尾插字符
void push_back(char ch)
{
insert(_size, ch); //在字符串末尾插入字符ch
}
append
//尾插字符串
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
// 擴(kuò)2倍不夠,則需要多少擴(kuò)多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}//避免尾插字符串時(shí)如果字符串大于二倍_capacity時(shí)的多次擴(kuò)容
strcpy(_str + _size, str);
_size += len;
}operator+=
- +=運(yùn)算符的重載是為了實(shí)現(xiàn)字符串與字符、字符串與字符串之間能夠直接使用+=運(yùn)算符進(jìn)行尾插。
- +=運(yùn)算符實(shí)現(xiàn)字符串與字符之間的尾插直接調(diào)用push_back函數(shù)即可。
//+=運(yùn)算符重載
string& operator+=(char ch)
{
push_back(ch); //尾插字符串
return *this; //返回左值(支持連續(xù)+=)
}
+=運(yùn)算符實(shí)現(xiàn)字符串與字符串之間的尾插直接調(diào)用append函數(shù)即可。
//+=運(yùn)算符重載
string& operator+=(const char* str)
{
append(str); //尾插字符串
return *this; //返回左值(支持連續(xù)+=)
}
insert
insert函數(shù)的作用是在字符串的任意位置插入字符或是字符串。
void insert(size_t pos, char ch); void intsert(size_t pos,const char* str);

void intsert(size_t pos, char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
size_t end = _size;
while (end >=(int)pos)
{
_str[end + 1] = _str[end];
end--;
}
_str[pos] = ch;
_size++;
}insert函數(shù)插入字符串時(shí)也是類似思路

void intsert(size_t pos, const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
size_t newCapacity = 2 * _capacity;
// 擴(kuò)2倍不夠,則需要多少擴(kuò)多少
if (newCapacity < _size + len)
newCapacity = _size + len;
reserve(newCapacity);
}
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}erase
erase函數(shù)的作用是刪除字符串任意位置開始的n個(gè)字符。

void erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{ //從后往前挪
size_t end = pos + len;
while (end <= _size)
{
_str[end-len] = _str[end];//覆蓋pos及后len長(zhǎng)度的值,完成刪除
end++;
}
_size -= len;
}
}
clear
clear函數(shù)用于將對(duì)象中存儲(chǔ)的字符串置空,實(shí)現(xiàn)時(shí)直接將對(duì)象的_size置空,然后在字符串后面放上’\0’即可
//清空字符串
void clear()
{
_size = 0; //size置空
_str[_size] = '\0'; //字符串后面放上'\0'
}
swap
有三個(gè)swap,有兩個(gè)是string里的swap函數(shù),另外一個(gè)是算法庫(kù)的swap函數(shù)
std::swap template <class T> void swap (T& a, T& b); std::string::swap void swap (string& str); void swap (string& x,sring& y);
算法庫(kù)中的swap也能交換自定義類型,但代價(jià)非常大

我們自己實(shí)現(xiàn)swap完全不用這么復(fù)雜,直接把s1、s2指針及數(shù)據(jù)交換一下就行了
//交換兩個(gè)對(duì)象的數(shù)據(jù)
void swap(string& s)
{
//調(diào)用庫(kù)里的swap
::swap(_str, s._str); //交換兩個(gè)對(duì)象的C字符串
::swap(_size, s._size); //交換兩個(gè)對(duì)象的大小
::swap(_capacity, s._capacity); //交換兩個(gè)對(duì)象的容量
}
還有一個(gè)swap存在的意義是什么呢?
void swap(string& s1,string& s2)
{
s1.swap(s2);
}這樣在類外面調(diào)用swap函數(shù)時(shí)就不會(huì)調(diào)到算法庫(kù)的swap函數(shù)了(模板實(shí)例化會(huì)走上面的swap函數(shù))
五、訪問字符串相關(guān)函數(shù)
operator[ ]
[ ]運(yùn)算符的重載是為了讓string對(duì)象能像C字符串一樣,通過[ ] +下標(biāo)的方式獲取字符串對(duì)應(yīng)位置的字符。
//[]運(yùn)算符重載(可讀可寫)
char& operator[](size_t i)
{
assert(i < _size); //檢測(cè)下標(biāo)的合法性
return _str[i]; //返回對(duì)應(yīng)字符
}
在某些場(chǎng)景下,我們可能只能用[ ] +下標(biāo)的方式讀取字符而不能對(duì)其進(jìn)行修改。
例如,對(duì)一個(gè)const的string類對(duì)象進(jìn)行[ ] +下標(biāo)的操作,我們只能讀取所得到的字符,而不能對(duì)其進(jìn)行修改。所以我們需要再重載一個(gè)[ ] 運(yùn)算符,用于只讀操作。
//[]運(yùn)算符重載(可讀可寫)
const char& operator[](size_t i)const
{
assert(i < _size); //檢測(cè)下標(biāo)的合法性
return _str[i]; //返回對(duì)應(yīng)字符
}
c_str
c_str函數(shù)用于獲取對(duì)象C類型的字符串
//返回C類型的字符串
const char* c_str()const
{
return _str;
}
小知識(shí)點(diǎn)- npos
在 C++ 標(biāo)準(zhǔn)庫(kù)中,?npos? 是一個(gè)特殊的靜態(tài)常量成員,主要用于表示“無(wú)效位置”或“未找到”的狀態(tài)
注:只有整形才能在聲明中定義
static const size_t npos=-1;
?核心用途
(1) ?查找函數(shù)失敗時(shí)的返回值
std::string str = "Hello";
size_t pos = str.find('x'); // 查找不存在字符
if (pos == std::string::npos) { // 必須用 npos 檢查
std::cout << "Not found!";
}(2) ?表示“直到字符串末尾”?
std::string sub = str.substr(2, std::string::npos); // 從索引2到末尾
特性
- ?無(wú)符號(hào)值?:由于是
size_t類型,避免直接與-1比較,而應(yīng)使用npos。• - ?足夠大?:其值(如 18446744073709551615)保證不會(huì)與任何有效索引沖突。
find函數(shù)
- find函數(shù)是用于在字符串中查找一個(gè)字符或是字符串
- find函數(shù):正向查找即從字符串開頭開始向后查找
1、正向查找第一個(gè)匹配的字符。
size_t find(char ch,size_t pos=0)
{
//默認(rèn)從首字符開始查找
assert(pos<_size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == ch)
{
return i;
}
}
//找不到返回npos
return npos;
}2、正向查找第一個(gè)匹配的字符串。
這里用到了一個(gè)strstr函數(shù)
char * strstr (char * str1, const char * str2 );
返回指向 str1 中第一次出現(xiàn)的 str2 的指針,如果 str2 不是 str1 的一部分,則返回空指針。
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ptr = strstr(_str, str);
if (ptr)//如果找到了
{
return ptr - _str; //下標(biāo)
}
else //為空
{
return npos;
}
}substr函數(shù)
substr函數(shù)用來(lái)取字符串中的子字符串,位置和長(zhǎng)度由自己決定
默認(rèn)從首字符取,默認(rèn)取的長(zhǎng)度是取到尾
string substr (size_t pos = 0, size_t len = npos) const;
調(diào)用拷貝構(gòu)造,返回pos位后len長(zhǎng)度的字符串
string substr(size_t pos, size_t len)
{
assert(pos < _size);
//len長(zhǎng)度足夠大就直接取到尾
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = pos;i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}六、關(guān)系運(yùn)算符重載函數(shù)
>、>=、<、<=、==、!=
關(guān)系運(yùn)算符有 >、>=、<、<=、==、!= 這六個(gè),但是對(duì)于C++中任意一個(gè)類的關(guān)系運(yùn)算符重載,我們均只需重載其中的兩個(gè),剩下的四個(gè)關(guān)系運(yùn)算符可以通過復(fù)用已經(jīng)重載好了的兩個(gè)關(guān)系運(yùn)算符來(lái)實(shí)現(xiàn)。
例如,對(duì)于string類,我們可以選擇只重載 < 和 == 這兩個(gè)關(guān)系運(yùn)算符。
注:重載在類外面,命名域例:lzg里。這樣操作數(shù)就不用限定死是string類的
- ?
lhs?:代表 ?Left-Hand ?Side (左側(cè)操作數(shù)/左側(cè)值) - ?
rhs?:代表 ?Right-Hand ?Side (右側(cè)操作數(shù)/右側(cè)值)
//==運(yùn)算符重載
bool operator== (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}
//<運(yùn)算符重載
bool operator< (const string& lhs, const string& rhs)
{
return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}剩下的四個(gè)關(guān)系運(yùn)算符的重載,就可以通過復(fù)用這兩個(gè)已經(jīng)重載好了的關(guān)系運(yùn)算符來(lái)實(shí)現(xiàn)了。
//<=運(yùn)算符重載
bool operator<= (const string& lhs, const string& rhs)
{
return lhs < rhs || lhs == rhs;
}
//!=運(yùn)算符重載
bool operator!= (const string& lhs, const string& rhs)
{
return !(lhs == rhs);
}
//>運(yùn)算符重載
bool operator> (const string& lhs, const string& rhs)
{
return !(lhs <= rhs);
}
//>=運(yùn)算符重載
bool operator>= (const string& lhs, const string& rhs)
{
return !(lhs < rhs);
}>>和<<運(yùn)算符的重載
>>運(yùn)算符的重載
ostream& operator<<(ostream& os, const string& str)
{
for (size_t i = 0; i < str.size(); i++)
{
os << str[i];
}
return os;
}<<運(yùn)算符的重載
istream& operator>>(istream& is, string& str)
{
str.clear();
char ch;
//is >> ch;
ch = is.get();
while (ch != ' ' && ch != '\n')
{
str += ch;
ch = is.get();
}
return is;
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)實(shí)戰(zhàn)教學(xué)
在本篇文章里小編給大家分享了關(guān)于C語(yǔ)言實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)實(shí)戰(zhàn)教學(xué)內(nèi)容,有興趣的朋友們可以跟著學(xué)習(xí)參考下。2019-01-01
C語(yǔ)言實(shí)現(xiàn)旅游資訊管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)旅游資訊管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
C++中的動(dòng)態(tài)分派在HotSpot?VM中的應(yīng)用小結(jié)
多態(tài)是面向?qū)ο缶幊陶Z(yǔ)言的重要特性,它允許基類的指針或引用指向派生類的對(duì)象,而在具體訪問時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定,這篇文章主要介紹了C++的動(dòng)態(tài)分派在HotSpot?VM中的重要應(yīng)用,需要的朋友可以參考下2023-09-09
C++文件關(guān)鍵詞快速定位出現(xiàn)的行號(hào)實(shí)現(xiàn)高效搜索
這篇文章主要為大家介紹了C++文件關(guān)鍵詞快速定位出現(xiàn)的行號(hào)實(shí)現(xiàn)高效搜索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
MFC實(shí)現(xiàn)簡(jiǎn)單計(jì)算器
這篇文章主要為大家詳細(xì)介紹了MFC實(shí)現(xiàn)簡(jiǎn)單的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05
C++中如何實(shí)現(xiàn)回調(diào)的方法示例
這篇文章主要給大家介紹了關(guān)于C++中如何實(shí)現(xiàn)回調(diào)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用c++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
QT使用共享內(nèi)存實(shí)現(xiàn)進(jìn)程間通訊
這篇文章主要為大家詳細(xì)介紹了QT如何使用共享內(nèi)存實(shí)現(xiàn)進(jìn)程間通訊,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12

