網站首頁 編程語言 正文
1.string 成員變量
首先需要一個動態開辟的指針指向這個字符串,然后還需要容量和存儲的個數,并且我們不能和標準庫的string進行沖突所以我們需要寫在我們自己的類域中,并且我們庫中還有一個靜態的變量是npos,就是無符號的-1,代表整形的最大值:
namespace cyf
{
class string
{
public:
//成員函數
private:
char *_str;
size_t size;
size_t capaticy;
const static size_t npos = -1;
};
}
這里有一個特例:static成員變量一般在類中聲明在類外定義,但是const static int型的變量可以直接在類中定義。
2.構造函數
strlen求出的是\0之前的字符個數,所以_size和_capacity標識的是實際存儲的字符個數,在開辟空間時多開辟一個字符用來存儲'\0'。
string(const char* s = "")
{
_size = strlen(s);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, s); //開辟好空間后將s的內容拷貝至_str
}
3.拷貝構造、賦值重載和析構函數
1.拷貝構造
_str 維護的是一塊空間,所以不能簡單的將s._str的值賦值給_str (淺拷貝),而是單獨開辟一塊空間,讓_str指向這一塊空間,再將s._str空間中的值拷貝至新開辟的空間,新開辟的空間比_capacity多開一個字節用來存儲'\0',作為字符串的結束標志。
//string s1(s)
string(const string& s)
{
_size = s._size;
_capacity = s._capacity;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
}
2.賦值重載
首先開辟一塊空間,將字符串的內容拷貝至這個空間,將_str原來指向的空間釋放,_str再指向這個新開辟的空間,size和capacity還是原來的大小。
//s2=s1
string& operator=(const string& s)
{
if (this != &s) //避免自己給自己賦值
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
3.析構函數
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.訪問成員變量
提供接口可以在類的外邊查看字符串的內容,存儲字符串的元素個數和容量
const char* c_str()
{
return _str;
}
size_t size()
{
return _size;
}
size_t capacity()
{
return _capacity;
}
配合之前的構造函數和這里的接口,我們進行驗證:
運行結果:
5.遍歷
遍歷有三種模式:
1.下標+【】
_str是一個指針,那么我們可以通過數組的方式來訪問,只需要重載operator []即可。我們還是要重載兩個版本的,因為普通變量和const變量的訪問權限不一樣。
//普通變量,可讀可寫
char& operator[](size_t pos)
{
assert(pos < _size); //檢查不能越界訪問
return _str[pos];
}
//const變量,只讀屬性
char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
2.迭代器(iterator)
在string中,迭代器就是一個指針,只不過我們進行了封裝,typedef一下就可以啦,同樣我們也要實現兩個版本的,const和非const的。
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
const_iterator begin()const
{
return _str;
}
iterator end()
{
return _str +_size;
}
const_iterator end() const
{
return _str +_size;
}
3.范圍for
我們范圍for的底層就是迭代器,所以我們不用實現,只要實現了迭代器,那么我們就可以直接使用范圍for,范圍for在執行的時候實際還是通過迭代器實現的,上例子:
運行結果:
6.空間的申請
1.reserve
一般是我們原空間容量滿了,需要申請空間擴容,我們的擴容函數還是要先申請空間,然后在進行拷貝,接著我們delete原來的空間,把申請的空間的指針和 容量 賦值過去即可。?
void reserve(size_t n)
{
if (n > _capacity)
{
char* tmp = new char[n + 1]; //多開一個字節留給'\0';
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
2.resize
1. 如果我們是傳入一個正整數大于_size的值,那么我們可以使用傳入的字符(或者缺省值)把我們申請的空間進行初始化,也就是從_size到n-1置為我們傳入的字符,n置為' \0 ',最后把_size置為n。
2.如果傳入一個小于_size正整數,那么我們把0~_size-1進行初始化為傳入的字符(或者缺省值),把n位置置為' \0 ',接著我們會把_size置為n,而_capaticy不變。
void resize(size_t n, char ch = '\0')
{
if (n > _size)
{
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
else //小于
{
_str[n] = '\0';
_size = n;
}
}
7.增刪查改
1.push_back ? 尾插一個字符
上來就檢查容量,_size==_capacity時就說明沒有容量了,得分類討論:1.原來的字符串有元素2.原來的是空字符串,如果字符串為空,就給4個字節大小的容量 。如果原來的字符串不為空但是需要擴容就調用reserve函數進行擴容,容量擴為2倍,2倍比較合適,避免給的小了頻繁的擴容,但是也不能給的太大了,太大了會造成空間的浪費。在_size的位置插入字符ch ,_size++,插入的字符ch 將原來的'\0'給覆蓋了,最后再補上'\0',作為字符串的結束標志。
void push_back(char ch)
{
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
_str[_size] = ch;
++(_size);
_str[_size] = '\0'; //記得處理 \0
}
2.append 尾部插入一個字符串
插入字符串的大小不確定,就需要確定是否需要擴容。當插入的字符串的長度加上當前字符串的有效元素個數大于容量_capacity時,就需要擴容,擴后的容量大小為_size+len ,這里給reverse函數傳的是有效元素的個數,在reverse函數內部為我們多開了一個字符的大小用來存儲'\0'。再進行拷貝工作,這里記得插入元素后_size要進行變換。
void append(const char* str)
{
size_t len = strlen(str);
if (len + _size > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
3.insert ?在指定位置插入一個字符
上來首先檢查要插入的位置是否合理,_size 代表的位置是有效元素的下一個位置即'\0'的位置,pos的范圍【0,_size】string字符串的頭到尾部之間 ,插入元素要檢查容量,記得考慮原string是否為空串的情況。插入數據需要挪動數據,從前往后挪動數據,將end位置確定到'\0'的下一個位置,這樣方便頭插。最終將pos位置騰出來,插入字符ch 插入數據后_size++。
string& insert(size_t pos, char ch)
{
assert(pos <= _size); //等于size 時候相當于尾部插入
if (_size == _capacity)
{
size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
reserve(newcapacity);
}
size_t end = _size + 1; //這里是把\0往 \0的下一個位置挪動 方便頭插
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
return *this;
}
4.insert 在指定位置插入一個字符串
string& insert(size_t pos, const char* str),我們先進行斷言pos不能超過_size,接著我們開辟空間,這次就不考慮空串的問題了,因為我們要指定開辟的字節數,和上面一樣的我們也要進行挪動數據,我們只不過是由每次挪動一個步,變為了挪動 len 步了,最后使用strncpy插入字符串,把_szie +=len 即可。
畫圖理解:
string& insert(size_t pos, const char* str)
{
size_t 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);
_size += len;
return *this;
}
實現了上述的接口當然最好用的還是下面的接口,對push_back和append進行封裝實現string+=
一個字符 和 string+=一個字符串
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
5.刪除接口:erase
指定字符串從開始位置到指定位置刪除元素。得保證刪除的位置在string的內部,當只給定了刪除的起始位置沒有給結束的位置那么就觸法我們的缺省值,即從pos位置開始直到將字符串刪完,或者說給定的結束位置大于了字符串的本身長度,那就從pos位置開始直到刪除完字符串,實現的方法很簡單,直接在pos位置添字符串的結束標志'\0'。 ?如果給定的兩個值都在字符串的內部直接進行從len位置往前拷貝覆蓋掉要刪除的元素。
string& erase(size_t pos, size_t len = npos)
{
assert(pos <= len);
if (len == npos || len >= _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
6.find字符 從某個位置開始查找字符,如果沒有給定開始位置,就用缺省值,默認從開頭尋找,找到了就返回元素的下標,沒有找到就返回npos。
size_t find(char ch, size_t pos = 0) ///默認從pos位置開始尋找,有缺省值0,從pos 位置開始往后尋找
{ //對比找到了就返回下標,找不到返回npos
assert(pos < _size);
while (pos < _size)
{
if (_str[pos] == ch) //遍歷尋找
{
return pos;
}
pos++;
}
return npos;
}
7.find字符串 ?,從某個位置開始往后尋找字符串,找到了就返回下標,找不到就返回npos
這里套用c語言的庫函數strstr進行實現
size_t find(const char* str, size_t pos = 0)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr == nullptr) //strstr找不到返回空指針
{
return npos; //轉換至cpp找不到就返回npos
}
else
{
return ptr - _str; //返回的是下標 指針-指針 ==下標
}
}
8.clear ?清空字符串的內容
直接在第一個位置加入結束標志就將字符串清空了,將清空后_size就為0,
void clear()
{
_size = 0;
_str[0] = '\0';
}
8.重載cin 和 cout
1.cout?
依次的輸出string對象的內容即可
ostream& operator<<(ostream& out,const string& s)
{
for (size_t i = 0; i < s.size(); i++)
{
out << s[i];
}
return out;
}
2.cin
這里注意因為要改變字符串的內容,首先調用clear函數清空原來的內容,因為要改變字符串的內容所以不用const 直接引用改變的就是字符串的本身。因為我們的 in 會默認 '空格' 和 ' \n '是分割符,不進行讀取,這樣我們就沒辦法停止。需要使用下 in 的get函數,讓我們的來讀取 ‘ ?’ ?和 ? ? ? ? ' \n ',我們看下代碼:
void clear()
{
_str[0] = '\0';
_size = 0;
}
istream& operator>>(istream& in, string& s)
{
s.clear();//要先進行清理,否則就會出現剩余的數據也被我們寫入了。
char ch;
ch = in.get();
char buff[32];
size_t i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 31)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
buff[i] = '\0';
s += buff;
return in;
}
cin和cout的重載不一定是類的友元函數,在類中提供接口,我們也可以直接訪問類的成員變量!
原文鏈接:https://blog.csdn.net/m0_61438674/article/details/127757737
相關推薦
- 2022-10-13 詳解python-opencv?常用函數_python
- 2022-07-18 Linux文件系統和日志分析
- 2022-04-14 Python+Tkinter簡單實現注冊登錄功能_python
- 2022-06-16 Python數據結構之遞歸方法詳解_python
- 2022-09-26 C++實現頁面的緩沖區管理器_C 語言
- 2022-07-20 C語言雙向鏈表的原理與使用操作_C 語言
- 2024-03-05 git的使用
- 2022-11-10 android時間選擇控件之TimePickerView使用方法詳解_Android
- 最近更新
-
- 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同步修改后的遠程分支