網站首頁 編程語言 正文
前言
上一章我們對string的常見接口及使用進行了講解,接下來我們將對一些常見的接口,包括構造函數,析構函數,運算符重載等等進行模擬實現.方便我們理解string接口實現的原理.
在講解之前先說一下string的成員變量.
首先是字符串內容_str,再是字符串的大小_size,最后是字符串的總容量大小_capacity.
class string
{
private:
char* _str;
size_t _size;
size_t _capacity;
};
構造函數
缺省值是一個空串,再給_str開辟空間時要多開辟一個空間存儲'\0'
開好了空間最后需要把內容拷貝到_str.
string(const char* str = "")
{
//這里有個細節,就是先計算出_size大小,然后再直接把_size賦值給_capacity,省了一次strlen()的調用.
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
析構函數
完成對string類成員的資源清理,空間釋放等一些操作.
~string()
{
//釋放_str的空間,并將其指向的空間置為空
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
拷貝構造函數
說到string的拷貝構造函數,這里一定會涉及到深淺拷貝問題.
所以在講解它的拷貝構造函數之前必須先了解它
淺拷貝:也稱位拷貝,編譯器只是將對象中的值拷貝過來。如果對象中管理資源,可能就會導致多個對象共享同一份資源,當一個對象銷毀時就會將該資源釋放掉,但是其他的對象不知道該資源已經被釋放了,以為資源還有效,所以他們會繼續對這個資源進行訪問。這時就出現了違法訪問。深拷貝就是為了解決淺拷貝的問題。
深拷貝:就是給自己重新開辟一塊空間,并將數據拷貝到新開辟的空間中,如果一個類中涉及到資源的管理,其拷貝的構造函數,賦值運算符重載以及析構函數必須要顯式給出。(就是要手動寫,不能用編譯器自動生成的)。一般這種情況都是按照深拷貝方式提供。
所以拷貝的時候,需要重新給_str開辟一塊空間.
string(const string& s)
:_str(new char[s._capacity + 1])
, _size(s._size)
, _capacity(s._capacity)
{
strcpy(_str, s._str);
}
這里也用圖淺淺的介紹一下淺拷貝和深拷貝的區別.
operator=賦值運算符重載
正如上一個所說,=賦值運算符也同樣存在深淺拷貝的問題,所以也必須進行深拷貝.
它和拷貝構造的主要區別就是:拷貝構造是對象還沒有初始化時進行拷貝,而賦值運算符重載是對一個已經存在的變量進行賦值.
當然同樣這里也需要深拷貝
也有一些需要注意的問題:例如s1=s2.我們把s2賦值給s1后,那么原本的s1空間該怎么辦呢?
我們的解決方案是:
把原本的s1空間釋放掉,然后再開辟一塊和s2大小相同的空間,再把內容從s2拷貝到s1
//= 運算符重載
string& operator=(const string& s)
{
//不能自己賦值給自己
if (this != &s)
{//先釋放掉原本的空間
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
c_str
c_str就是返回c語言風格的字符串,既返回char*類型字符串,返回字符串首地址即可.
const char* c_str() const
{
return _str;
}
為什么加const呢?
第一個const是為了使普通對象和const對象都可以調用這個函數,因為權限只可以縮小,不可以放大.
第二個const是保證函數體內的內容不會被改變,既this指針指向的內容無法被改變.
operator[]
實現[]重載,是指傳過來一個下標index,返回它index下標所對應的值
目的是讓字符串可以像數組一樣訪問每一個元素.
char& operator[](size_t index)
{
//下標必須小于字符串總大小
assert(index < _size);
return _str[index];
}
當然為了const對象也可以調用,我們可以再寫一個const修飾的operator[].
const char& operator[](size_t index) const
{
assert(index < _size);
return _str[index];
}
size()
寫一個函數,直接返回_size即可
size_t size() const
{
return _size;
}
那可能會有人想問了:既然返回_size,那我們直接調用它這個成員不就行了,為什么還有套一層函數呢?
這是因為_size是被private修飾的,我們是不能直接訪問私有成員的.
所以需要實現一個公有的函數間接訪問_size.
capacity()
這個所注意的和size完全一致.
size_t capacity() const
{
return _capacity;
}
empty()
只需要判斷當前的size是否等于0即可.
bool empty() const
{
return _size == 0;
}
operator+=
這個重載運算符我們上一章講過是可以插入字符或者插入字符串的,這里也分別復用了push_back和append(),這兩個函數后面將模擬實現.
string& operator+= (const char ch)
{
push_back(ch);
return *this;
}
string& operator+= (const char* str)
{
append(str);
return *this;
}
擴容函數(reserve)
調整容量大小到n
先new一個n+1的新空間,再把原來的數據拷貝到新空間中去,然后釋放掉原來的空間,然后將capacity設置為n.
void reserve(size_t n)
{
//n應該大于之前的容量
if (n > _capacity)
{
//先開辟大小為n+1的空間
char* tmp = new char[n + 1];
//將原來的數據拷貝到tmp
strcpy(tmp, _str);
//釋放掉原來的數據
delete[] _str;
//將擴容后的數據重新賦給_str
_str = tmp;
_capacity = n;
}
}
畫圖來理解一下它?
resize()
resize會有以下兩種情況:
1.若n < _size,既重新調整后的大小小于原來的大小,會發生數據截斷,只保留前n個字符.
2.若n > _size,這里直接復用reserve即可
既如果n<_capacity,此時_capacity不發生變化,多出的空間用ch替代.
如果n>_capacity,此時_capacity需要擴容(1.5倍速度,不一定是n),直到最接近為止.
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
//插入數據
//reserve會和容量進行比較以及是否需要闊人
reserve(n);
//多余的字符用ch替代
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
//字符串結束
_str[n] = '\0';
_size = n;
}
else
{
//刪除數據
//直接將第n個數據改為'\0',這樣相當于將后面的數據全部刪除了.
_str[n] = '\0';
_size = n;
}
}
push_back()
push_back的作用是在原字符串后上拼接一個字符,首先我們現需要判斷空間是否足夠,如不夠,則需要擴容,復用之前的reserve函數,再進行插入數據,最后加上'\0'.
當然還可以利用復用insert()函數進行插入,這個后面再實現.
void push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
_str[_size] = ch;
++_size;
_str[_size] = '/0';
}
}
還以復用insert這樣插入,會使代碼健壯性更強,更加簡潔.
這個inser()函數后面會實現.
insert(_size, ch);
append()
這個與push_back不同的是:push_back()只能插入一個字符,append()只可以插入一個字符串.
這里的問題就出現了,我們不知道追加的字符串長度,自然擴容的時候也不知道擴大到多少,是2倍還是3倍,所以這里要看插入的字符串的長度len,只要要讓空間開到_size+len.
讓空間滿足最低的情況,能把所有的字符容納下,最后利用strcpy將其數據拷貝過來即可.
void append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size = _size + len;
}
當然同樣可以復用insert函數.
insert(_size, str);
下面就該說insert函數了.
insert()
insert也分為兩種情況:插入一個字符或插入多個字符(字符串)
插入一個字符:方法類似于順序表的插入
string& insert(size_t pos, char ch)
{
//插入的位置必須要與字符串大小
assert(pos <= _size);
//如果空間滿了,則需要擴容
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//插入操作
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
插入多個字符:
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
//先把空間騰出來
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
//再把利用strncpy把指定長度的字符串插入
strncpy(_str + pos, str, len);
_size = _size + len;
}
說了插入就該說刪除了.
erase()
這個函數也比較巧妙,首先輸入兩個參數:第一個參數是要開始刪除的下標,第二個參數是要刪除的長度.
首先第二個參數默認缺省值是npos,npos是一個非常大的數.
首先判斷len是否等于npos或者當前位置+len是否大于總長度,若是,則直接將pos位置置為'\0',后面的元素也就相當于刪除了
如果不是,則把pos+len之后的元素拷貝到pos位置之后,這樣就相當于刪除了pos~pos+len之間的這一段字符.再把_size-len,相當于是一個覆蓋的過程.
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
find()
也是實現兩個,利用strstr()函數來查找字符串.
1.如果查找一個字符
? 如果找到,則直接返回字符所對應的下標pos,否則返回npos.
2.如果查找一個字符串
? ?對于這種情況,找到字符串后,我們需要返回第一個字符的下標,通過指針差值確定目標字符串的位置。
1.查找一個字符
思路很簡單,就是利用循環
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
2.查找一個字符串
利用strstr函數,從第pos個位置開始查找,如果找到則返回目標字符串的首元素地址,若沒有找到則返回空指針
size_t find(const char* sub, size_t pos = 0)
{
assert(sub);
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
substr()
?這個函數實現比較簡單,復用之前實現的+=即可
首先計算出實際要切割的長度realLen = len
如果pos+len>_size或者len == npos,則需要重新計算realLen = _size - pos
然后循環realLen次,創建一個string類型的sub變量,每次利用sub+=這個字符即可.
string substr(size_t pos, size_t len = npos)
{
assert(pos < _size);
size_t realLen = len;
if (len == npos || pos + len > _size)
{
realLen = _size - pos;
}
string sub;
for (size_t i = 0; i < realLen; i++)
{
sub += _str[pos + i];
}
return sub;
}
比較大小函數
實現比較大小,只需要實現兩個運算符重載即可:
1. > 或 <其中任意一個
2.==
剩下的>=、<=、!=等等復用即可.
實現> 或 < 時,利用strcmp比較函數即可.
bool operator >(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool operator ==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator >= (const string& s) const
{
return *this > s || *this == s;
}
bool operator <(const string& s) const
{
return !(*this >= s);
}
bool operator <=(const string& s) const
{
return !(*this > s);
}
bool operator !=(const string& s)
{
return !(*this == s);
}
?這樣string的模擬實現基本就完成了,下面是總代碼:
namespace hmylq
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
//拷貝構造 - - - 1
string(const string& s)
:_str(new char[s._capacity + 1])
, _size(s._size)
, _capacity(s._capacity)
{
strcpy(_str, s._str);
}
//拷貝構造 - - - 2
/* string(const string& s)
:_str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
swap(_str, tmp._str);
swap(_size, tmp._size);
swap(_capacity, tmp._capacity);
}*/
//析構函數
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
/
void push_back(char ch)
{
/*if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
_str[_size] = ch;
++_size;
_str[_size] = '/0';
}*/
insert(_size, ch);
}
string& operator += (char ch)
{
push_back(ch);
return *this;
}
void append(const char* str)
{
/*size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size = _size + len;*/
insert(_size, str);
}
string& operator += (const char* str)
{
append(str);
return *this;
}
//= 運算符重載
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
return *this;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
void swap(string& tmp)
{
::swap(_str, tmp._str);
::swap(_size, tmp._size);
::swap(_capacity, tmp._capacity);
}
const char* c_str() const
{
return _str;
}
//
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty() const
{
return _size == 0;
}
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_str[n] = '\0';
_size = n;
}
else
{
_str[n] = '\0';
_size = n;
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
/
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index) const
{
assert(index < _size);
return _str[index];
}
//
bool operator >(const string& s) const
{
return strcmp(_str, s._str) > 0;
}
bool operator ==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool operator >= (const string& s) const
{
return *this > s || *this == s;
}
bool operator <(const string& s) const
{
return !(*this >= s);
}
bool operator <=(const string& s) const
{
return !(*this > s);
}
bool operator !=(const string& s)
{
return !(*this == s);
}
size_t find(char ch, size_t pos = 0)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (ch == _str[i])
{
return i;
}
}
return npos;
}
size_t find(const char* sub, size_t pos = 0)
{
assert(sub);
assert(pos < _size);
const char* ptr = strstr(_str + pos, sub);
if (ptr == nullptr)
{
return npos;
}
else
{
return ptr - _str;
}
}
string& insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
size_t end = _size + len;
while (end >= pos + len)
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);
_size = _size + len;
}
void erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
}
string substr(size_t pos, size_t len = npos)
{
assert(pos < _size);
size_t realLen = len;
if (len == npos || pos + len > _size)
{
realLen = _size - pos;
}
string sub;
for (size_t i = 0; i < realLen; i++)
{
sub += _str[pos + i];
}
return sub;
}
private:
char* _str;
int _size;
int _capacity;
const static size_t npos = -1;
};
}
總結
原文鏈接:https://blog.csdn.net/weixin_47257473/article/details/128529180
相關推薦
- 2022-07-16 借助Redis的過期機制和分布式鎖實現定時任務
- 2022-12-04 詳解Go?依賴管理?go?mod?tidy_Golang
- 2022-04-08 深入理解Golang的反射reflect示例_Golang
- 2022-05-10 bean作用域 設置創建bean是單實例還是多實例
- 2023-02-02 大型項目里Flutter測試應用實例集成測試深度使用詳解_Android
- 2022-02-14 小程序使用了scroll-view滾動組件時,如何判斷滾動的方向
- 2022-09-25 nginx平滑升級、nginx支持的kill信號
- 2022-12-28 React+Electron快速創建并打包成桌面應用的實例代碼_React
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支