做網(wǎng)站怎么買服務(wù)器嗎免費(fèi)seo課程
🔥博客主頁(yè):小王又困了
📚系列專欄:C++
🌟人之為學(xué),不日近則日退
??感謝大家點(diǎn)贊👍收藏?評(píng)論??
?
目錄
一、存儲(chǔ)結(jié)構(gòu)
二、默認(rèn)成員函數(shù)
📒2.1構(gòu)造函數(shù)
📒2.2析構(gòu)函數(shù)
📒2.3拷貝構(gòu)造
📒2.4賦值重載
三、容量操作
📒3.1獲取有效字符長(zhǎng)度
📒3.2獲取對(duì)象空間大小
📒3.3使用reserve擴(kuò)容
四、字符串的遍歷
📒4.1下標(biāo)訪問
📒4.2迭代器訪問
五、修改操作
📒5.1尾插字符
📒5.2尾插字符串
📒5.3任意位置插入字符
📒5.4任意位置插入字符串
📒5.5+=重載
六、其他操作
📒6.1刪除操作
📒6.2查找操作
📒6.3交換操作
📒6.4獲取字符串
📒6.5運(yùn)算符重載
📒6.6清理字符串
📒6.7流操作
🗒?前言:
在上一篇中我們對(duì)string類進(jìn)行了簡(jiǎn)單的介紹,介紹了各個(gè)接口的作用和使用方法,今天我們將為大家介紹string常用接口的模擬實(shí)現(xiàn)。
一、存儲(chǔ)結(jié)構(gòu)
????????string本質(zhì)上是一個(gè)char類型的順序表,所以結(jié)構(gòu)上和順序表類似。
namespace bit
{class string{public:private:char* _str;size_t _size;size_t _capacity;const static size_t npos;};
}
結(jié)構(gòu)上使用命名空間 bit 進(jìn)行封裝,防止與庫(kù)沖突,其中:
- _str?:指向存放字符串存空間的指針
- _size :表示當(dāng)前所存儲(chǔ)的有效字符個(gè)數(shù)
- _capacity?:表示當(dāng)前可存儲(chǔ)的最大容量
- nops:此值設(shè)置為 -1,無(wú)符號(hào)整型轉(zhuǎn)換就是42億,且此值為const和靜態(tài)參數(shù)具有全局效應(yīng),這個(gè)值常常被用來(lái)當(dāng)作循環(huán)結(jié)束判斷條件和查找時(shí)未找到的標(biāo)志,某些函數(shù)設(shè)置其為缺省參數(shù)。
nops的初始化:
#include"string.h"namespace bit {const size_t string::nops = -1; }
小Tips:我們使用聲明與定義分離實(shí)現(xiàn),nops只能在CPP文件中定義,因?yàn)轭惱锩娴撵o態(tài)成員變量相當(dāng)于全局變量,在.h文件中定義會(huì)出現(xiàn)鏈接錯(cuò)誤。我們還要通過(guò)類名::成員(函數(shù)/變量)?定義和實(shí)現(xiàn)函數(shù)!
?
二、默認(rèn)成員函數(shù)
📒2.1構(gòu)造函數(shù)
string.h
string(const char* str = ""); //給缺省值 構(gòu)造空串string.cpp
string::string(const char* str):_size(strlen(str))
{_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);
}
構(gòu)造函數(shù)的思路:
- 構(gòu)造函數(shù)可以接收字符串,如果沒有參數(shù),默認(rèn)構(gòu)造一個(gè)空串
- 通過(guò)strlen先計(jì)算出字符串的長(zhǎng)度,并通過(guò)初始化列表初始化_size
- 使用new開辟空間,這里我們要多開一個(gè)空間存放‘\0’
- 最終將字符串中的字符拷貝到我們所開的空間中
小Tips:因?yàn)?/span>_size的大小沒有包含‘\0’,所以我們要多開辟一個(gè)空間。
📒2.2析構(gòu)函數(shù)
????????我們開辟內(nèi)存是使用?new[ ]?申請(qǐng)的,所以對(duì)應(yīng)使用?delete[ ]釋放。
string::~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;
}
📒2.3拷貝構(gòu)造
????????拷貝構(gòu)造函數(shù),如果我們不寫,編譯器會(huì)默認(rèn)生成一個(gè),但是默認(rèn)生成的拷貝構(gòu)造函數(shù)只支持淺拷貝,新構(gòu)造的對(duì)象只是拷貝了_str的指針地址,兩個(gè)對(duì)象都指向同一塊空間,最終兩個(gè)對(duì)象析構(gòu)時(shí)釋放同一片空間的資源勢(shì)必會(huì)導(dǎo)致程序崩潰!
?
我們需要新構(gòu)造的對(duì)象通過(guò)拷貝構(gòu)造開辟一片新的空間將數(shù)據(jù)復(fù)制過(guò)去,也就是深拷貝,需要我們自己寫一個(gè)拷貝構(gòu)造。
?🌟傳統(tǒng)寫法:
string::string(const string& s) {_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity; }
?🌟現(xiàn)代寫法:
string::string(const string& s) {string tmp(s._str);swap(tmp); } //string s2(s1);
?通過(guò)復(fù)用構(gòu)造函數(shù),構(gòu)造出tmp對(duì)象,在將兩個(gè)對(duì)象進(jìn)行交換,就可以實(shí)現(xiàn)拷貝構(gòu)造。
📒2.4賦值重載
????????賦值重載需要注意自己給自己賦值這種冗余的行為,同時(shí)也要控制空間大小
??🌟傳統(tǒng)寫法:
string& string::operator=(const string& s) {if(this != &s){char* tmp = new char[s._size + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this; }
?🌟現(xiàn)代寫法:
string& string::operator=(const string& s) {if (this != &s){string tmp(s._str);swap(tmp);}return *this; }string& string::operator=(string tmp) {swap(tmp);return *this; }
三、容量操作
📒3.1獲取有效字符長(zhǎng)度
size_t string::size() const
{return _size;
}
小Tips:
- 這個(gè)函數(shù)比較小,可以寫在類中形成內(nèi)聯(lián)函數(shù)。?
- 對(duì)于不涉及對(duì)字符串的增刪查改的函數(shù),使用const修飾this增強(qiáng)安全性。
📒3.2獲取對(duì)象空間大小
????????與size函數(shù)規(guī)則一致。
size_t string::capacity() const
{return _capacity;
}
📒3.3使用reserve擴(kuò)容
void string::reserve(size_t n)
{char* tmp = new char[n + 1];strcpy(tmp, _str); //將原字符串的數(shù)據(jù)拷貝到新空間上delete[] _str; //釋放原字符串的空間_str = tmp;_capacity = n;
}
四、字符串的遍歷
📒4.1下標(biāo)訪問
????????下標(biāo)訪問是通過(guò)重載?[ ]?運(yùn)算符實(shí)現(xiàn)的,在下標(biāo)pos正確的情況下,返回當(dāng)前下標(biāo)字符的引用,否則assert報(bào)錯(cuò)。
char& string::operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
📒4.2迭代器訪問
? ? ? ? 現(xiàn)在我們可以簡(jiǎn)單認(rèn)為迭代器是指針對(duì)象,就是對(duì)指針的封裝。
//迭代器的聲明
typedef char* iterator;
typedef const char* const_iterator; //對(duì)數(shù)據(jù)無(wú)法修改
迭代器的begin返回字符串的地址,end返回字符串末端的下一個(gè)即‘\0’。
string::iterator string::begin()
{return _str;
}string::iterator string::end()
{return _str + _size;
}string::const_iterator string::begin()const
{return _str;
}string::const_iterator string::end()const
{return _str + _size;
}
五、修改操作
📒5.1尾插字符
? ? ? ? 在插入字符前,先要判斷是否需要擴(kuò)容。
void string::push_back(char ch)
{if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4: _capacity * 2;reserve(newcapacity);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;
}
📒5.2尾插字符串
void string::append(char* s)
{size_t len = strlen(s);if (_size +len > _capacity){reserve(_size + len);}strcpy(_str + _size, s);_size += len;
}
📒5.3任意位置插入字符
? ? ? ? 我們要考慮頭插的位置,end和pos的類型都是size_t,代碼會(huì)陷入死循環(huán),這里我們提供兩種解決方法。
🌟方法一:
? ? ? ? 將end的類型定為int,同時(shí)將pos強(qiáng)轉(zhuǎn)為int型。
void string::insert(size_t pos, char ch) {assert(pos <= _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size; }
?
?🌟方法二:
? ? ? ? 將end定位到‘\0’的下一位。
void string::insert(size_t pos, char ch) {assert(pos <= _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size; }
?
我們可以通過(guò)復(fù)用來(lái)實(shí)現(xiàn)尾插
void string::push_back(char ch)
{insert(_size, ch);
}
📒5.4任意位置插入字符串
void string::insert(size_t pos, const char* s)
{assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}//方法一://int end = _size;//while (end >= (int)pos )//{// _str[end + len] = _str[end];// --end;//}//方法二:size_t end = _size+len;while (end > pos+len-1){_str[end] = _str[end - len];--end;} memcpy(_str + pos, s, len);_size += len;
}
我們也可以通過(guò)復(fù)用來(lái)實(shí)現(xiàn)尾插字符串
void string::append(const char* str)
{insert(_size, str);
}
📒5.5+=重載
????????+=運(yùn)算符可以在當(dāng)前字符串尾部追加字符或字符串,我們可以通過(guò)復(fù)用push_back和append函數(shù)來(lái)實(shí)現(xiàn)。
//追加字符
string& string::operator+=(char ch)
{push_back(ch);return *this;
}//追加字符串
string& string::operator+=(const char* str)
{append(str);return *this;
}
六、其他操作
📒6.1刪除操作
????????erase從pos下標(biāo)開始刪除len個(gè)字符,其中len是一個(gè)缺省參數(shù),默認(rèn)是npos。如果沒有傳值或len超過(guò)從pos位置開始到字符串尾部的字符個(gè)數(shù)則默認(rèn)從pos位置開始刪除后面的所有字符,且不允許在空串的情況下進(jìn)行刪除!
void string::erase(size_t pos, size_t len)
{assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}
}
📒6.2查找操作
? ? ? ? 查找函數(shù)是find,是從pos位置開始查找,可以查找一個(gè)字符或一個(gè)子串,查找到后字符返回下標(biāo),字符串返回首字符的地址,如果有多個(gè)重復(fù)的字符或字符串,返回查找到的第一個(gè)字符的下標(biāo)或字符串首的下標(biāo);如果沒找到則返回npos。
🌟查找一個(gè)字符:
size_t string::find(char ch, size_t pos) {for (size_t i = pos; i < _size; i++){if (_str[i] == ch)return i;}return npos; }
🌟查找一個(gè)子串
size_t string::find(const char* str, size_t pos ) {char* p = strstr(_str + pos, str);return p - _str; }
📒6.3交換操作
void string::swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capaicty, s._capaicty);
}
📒6.4獲取字符串
string string::substr(size_t pos, size_t len)
{assert(pos < _size);if (len > _size - pos){string sub(_str + pos);return sub;}else{string sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}return sub;}
}
📒6.5運(yùn)算符重載
? ? ? ? 邏輯判斷運(yùn)算符只需要實(shí)現(xiàn)兩個(gè),其余的通過(guò)復(fù)用就可以全部實(shí)現(xiàn)。?
bool string::operator<(const string& s) const
{return strcmp(_str, s._str) < 0;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}bool string::operator!=(const string& s) const
{return !(*this == s);
}
📒6.6清理字符串
????????clear函數(shù)支持清空一個(gè)字符串,但不是釋放對(duì)象,區(qū)別于析構(gòu)函數(shù)。clear函數(shù)清理字符串并不會(huì)引起縮容,只是在下標(biāo)0位置置為?\0?,_size置為0即可。
void string::clear()
{_str[0] = '\0';_size = 0;
}
📒6.7流操作
????????流操作屬于iostream中的對(duì)象,所以不需要定義在類中作為成員函數(shù),也不需要聲明為友元,因?yàn)槭褂昧黧w去和流插入需要ostream和istream對(duì)象作為左操作參數(shù)。
🌟流插入
ostream& operator<< (ostream& os, const string& str) {for (size_t i = 0; i < str.size(); i++){os << str[i];}return os; }
🌟流提取
istream& Mystring::operator>>(istream& in, string& s) {s.clear(); char buff[256] = {0}; char ch = in.get(); size_t sub = 0; while (ch != ' ' && ch != '\n') //當(dāng)緩沖區(qū)中有空格和換行就結(jié)束提取{buff[sub++] = ch; if (sub == 255) {buff[sub] = '\0'; s += buff; sub = 0; }ch = in.get(); }if (sub != 0) {buff[sub] = '\0'; s += buff;}return is; }
小Tips:我們定義一個(gè)緩沖區(qū)buff,先將字符串輸入到緩沖區(qū)中,如果字符串很長(zhǎng)則分批寫入string字符串中,每次寫入string后就刷新緩沖區(qū)再繼續(xù)接收,這樣就避免了頻繁開辟空間。
🎁結(jié)語(yǔ):?
? ? ?本次的內(nèi)容到這里就結(jié)束啦。希望大家閱讀完可以有所收獲,同時(shí)也感謝各位讀者三連支持。文章有問題可以在評(píng)論區(qū)留言,博主一定認(rèn)真認(rèn)真修改,以后寫出更好的文章。你們的支持就是博主最大的動(dòng)力。
?