微網(wǎng)站開發(fā)平臺免費網(wǎng)絡推廣公司介紹
🌈個人主頁:秋風起,再歸來~
🔥系列專欄:C++從入門到起飛? ? ? ? ??
🔖克心守己,律己則安
目錄
1、多文件之間的關系
2、模擬實現(xiàn)常用的構(gòu)造函數(shù)
2.1 無參構(gòu)造函數(shù)
2.2 有參的構(gòu)造函數(shù)
?2.3 析構(gòu)函數(shù)(順便實現(xiàn))
3、size()、capacity()、[]運算符重載
4、模擬實現(xiàn)簡單的正向迭代器
5、reserve、push_back、append
6、operator+=、insert、erase
7、find、substr
8、非成員函數(shù)operator比較系列
9、非成員函數(shù)operator<<、operator>>
10. 完結(jié)散花
1、多文件之間的關系
>string.h
在string.h中我們用來包含各種頭文件以及定義我們的string類和非成員函數(shù)的聲明!
注意:在string.h中string類的定義和非成員函數(shù)的聲明放到我們自己定義的命名空間my_string中(原因就是為了和庫里面的std:string類進行區(qū)分!)
>string.cpp
在string.cpp中我們來完成string類中一些(類里面短小頻繁調(diào)用的函數(shù)聲明和定義不用分離)成員函數(shù)和非成員函數(shù)的定義!
注意:在string.cpp中我們要包含“string.h”并且要在命名空間域中完成成員函數(shù)和非成員函數(shù)的定義!
>test.cpp
這個文件用來測試我們的接口是否有bug!
2、模擬實現(xiàn)常用的構(gòu)造函數(shù)
這里我們實現(xiàn)的是簡單的string類,沒有搞vs下用buff數(shù)組來存放字符串那一套,所以我們就只有三個成員變量:
private:char* _str;//指向字符串的指針size_t _size;//有效字符個數(shù)(不包含'\0')size_t _capacity;//空間大小(不包含'\0')
注意:這里的有效字符個數(shù)_size和空間大小_capacity都不包含'\0'!但我們實際開空間時都會多開一個來存放’\0‘?
2.1 無參構(gòu)造函數(shù)
聲明:以下實現(xiàn)的函數(shù)都是直接在類里面定義(短小頻繁調(diào)用,在類里面直接默認為inline)
>有誤的無參構(gòu)造函數(shù)
string():_str(nullptr)//有問題,標準庫里面的是可以輸出空字符串的, _size (0),_capacity (0)
{}
在寫構(gòu)造函數(shù)時,一般我們都是建議顯示寫初始化列表的,于是我們上來可能就會寫出如上的代碼!不過上面的代碼并不符合C++標準規(guī)定,當我們空參構(gòu)造時,庫里面輸出的是一個空字符串,而上面寫的構(gòu)造函數(shù)卻是不能直接訪問的空指針!
在測試代碼前,因為我們還沒有重載流提取和流插入函數(shù),那我們就先實現(xiàn)一個簡單的c_str()函數(shù)來幫助我們實現(xiàn)打印輸出!
>返回C字符串(c_str())
//返回C字符串
const char* c_str() const
{return _str;
}
>指定命名空間使用庫里面的string
std::string s1;//指定命名空間用的是庫里面的string
cout << s1.c_str() << endl;
通過調(diào)試我們發(fā)現(xiàn),庫里面的string空參構(gòu)造一個string對象s1時, s1里面存放的是一個’\0‘,并輸出一個空字符串!
>未指定命名空間,在命名空間my_string內(nèi),優(yōu)先使用自己實現(xiàn)的string
//未指定命名空間,在命名空間my_string內(nèi),優(yōu)先使用自己實現(xiàn)的string
string s1;
cout << s1.c_str() << endl;
?通過調(diào)試我們發(fā)現(xiàn),我們模擬實現(xiàn)的string空參構(gòu)造一個string對象s1時, s1里面存放的是一個nullptr,所以我們在輸出時,程序就直接崩潰了!
>正確的無參構(gòu)造函數(shù)?
那我們就按照標準庫的規(guī)定來寫,在走初始化列表時開一個空間來存放’\0‘即可!
string():_str(new char[1]{'\0'})//實際的空間大小要比capacity大1來存放'\0', _size (0),_capacity (0)
{}
2.2 有參的構(gòu)造函數(shù)
走初始化列表通過計算str的長度來開辟空間并確定_size和_capacity的大小,然后在函數(shù)體內(nèi)將str的值拷貝到_str中,我們就完成了帶參數(shù)的構(gòu)造函數(shù)!
string(const char* str):_str(new char[strlen(str) + 1]), _size(strlen(str)),_capacity(strlen(str))
{strcpy(_str, str);
}
?不過這里并不建議走初始化列表,因為每次都要調(diào)用strlen(),有性能和效率的消耗。
建議寫下面這一種版本!
string(const char* str)//加上缺省值合二為一
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//記住開空間時多開一個strcpy(_str, str);//會把str的'\0'拷貝進來
}
當然,我們還可以將無參構(gòu)造函數(shù)和帶參構(gòu)造函數(shù)合二為一!?
string(const char* str="")//加上缺省值合二為一
{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//記住開空間時多開一個strcpy(_str, str);//會把str的'\0'拷貝進來
}
?注意:合二為一之后我們就要將之前寫的無參構(gòu)造函數(shù)屏蔽掉,不然編譯器不知道調(diào)用誰就會報錯!(一個類中只能有一個默認構(gòu)造函數(shù)(即可以無參調(diào)用的構(gòu)造函數(shù))!)
?2.3 析構(gòu)函數(shù)(順便實現(xiàn))
因為有資源的申請,所以我們要顯示實現(xiàn)我們的析構(gòu)函數(shù)!
~string()
{delete[] _str;_str = nullptr;_capacity = _size = 0;
}
3、size()、capacity()、[]運算符重載
聲明:以下實現(xiàn)的函數(shù)都是直接在類里面定義(短小頻繁調(diào)用,在類里面直接默認為inline)
這些接口都比較簡單我就不贅述了
//返回size和capacity
size_t size() const
{return _size;
}
size_t capacity() const
{return _capacity;
}
//[]運算符重載
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
//const版本
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}
好啦!到這里,我們配合一下前面實現(xiàn)的一些接口,測試一下有沒有什么問題!?
string s1("hello world");
for (size_t i = 0; i < s1.size(); i++)
{cout << s1[i];
}
cout << endl;
for (size_t i = 0; i < s1.size(); i++)
{s1[i] += 2;cout << s1[i];
}
好,這里看到結(jié)果也是沒有任何問題的呢!
4、模擬實現(xiàn)簡單的正向迭代器
聲明:以下實現(xiàn)的函數(shù)都是直接在類里面定義(短小頻繁調(diào)用,在類里面直接默認為inline)
上面用下標訪問遍歷了我們的string對象,范圍for這么方便,那我們也來嘗試用它來遍歷一下吧!
string s1("hello world");
for (auto ch :s1 )
{cout << ch;
}
完蛋了!一寫出來就給我們報了一大堆的錯誤!
不過,我在上一篇博客里寫到了范圍for的底層就是迭代器,我們冷靜下來思考并結(jié)合報錯就會發(fā)現(xiàn)原來我們自己實現(xiàn)的string類中目前還沒有迭代器,所以我們不能用范圍for來遍歷s1
那我們怎么來實現(xiàn)string類的迭代器呢?
這里我就直接告訴大家,所有的迭代器iterator都是typedef出來的!在這里迭代器其實就是典型的封裝的一種體現(xiàn),所有的容器(鏈表,隊列,樹等)都有迭代器,并且使用他們的迭代器的方式都是一樣的(即迭代器給我們提供了統(tǒng)一的接口,但其底層的實現(xiàn)并不相同,不過我們在使用時并不關心它底層的細節(jié),我們只要掌握了迭代器的使用方式,就會對所有容器進行操作。這種封裝的方式大大方便了我們對容器的使用!)
好啦!到這里我們就來實現(xiàn)一下string類里面簡單的一個迭代器吧!
我們上篇文章就說過在string中,正向迭代器的使用就可以把它當做指針來看(不一定是指針),那我們在實現(xiàn)時不就可以參考使用原始指針的方式來實現(xiàn)我們的簡單迭代器呢?
1. 我們將char*重新命名為iterator(即iterator就是char* 的類型)
2. 然后我們再實現(xiàn)begin()和end()倆個接口來返回_str的開頭和結(jié)尾
typedef char* iterator;
//正向迭代器
iterator begin()const
{return _str;
}
iterator end() const
{return _str+_size;
}
好啦!到這里我們就實現(xiàn)好了一個簡單的正向迭代器!我們來測試一下:
string s1("hello world");for (auto ch :s1 ){cout << ch;}cout<< endl;
?我們再簡單的實現(xiàn)一下正向常量迭代器!
//正向常量迭代器
typedef const char* const_iterator;
const_iterator cbegin() const
{return _str;
}
const_iterator cend() const
{return _str + _size;
}
?這里,我們就不實現(xiàn)反向迭代器了(用原始指針已經(jīng)解決不了了),因為它要用到一個叫適配器的東西(目前我也不知道那是啥玩意),還挺復雜的~
5、reserve、push_back、append
>reserve
標準庫里面的reserve只有在預留空間大于容量時才會擴容并且決不改變有效字符個數(shù),strcpy會拷貝到\0!
void string::reserve(size_t n){//標準庫里面的reserve只有在預留空間大于容量時才會擴容//并且決不改變有效字符if (n > _capacity){char* tmp = new char[n + 1];//開新空間,記得加一strcpy(tmp, _str);//拷貝數(shù)據(jù)到新空間delete[] _str;//釋放舊空間_str = tmp;//_str指向新空間_capacity = n;}return;}
??好啦!寫到這里,我們來測試一下上面的接口是否有問題!
string s1("hello world");
cout << "capacity:" << s1.capacity() << endl;
//reserve
s1.reserve(20);
cout << "capacity:" <<s1.capacity() << endl << endl;
string s1("hello world");
cout << "capacity:" << s1.capacity() << endl;
//reserve
s1.reserve(10);
cout << "capacity:" <<s1.capacity() << endl << endl;
好啦, 也是沒有任何問題的!
>push_back
先判斷是否需要擴容,空間為0,則開4個空間,否則二倍擴,記得要手動放一個'\0'!
void string::push_back(char c)
{//先判斷是否需要擴容if (_size == _capacity){ //空間為0,則開4個空間,否則二倍擴!reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;//有效字符已經(jīng)更新_str[_size] = '\0';//記得要手動放一個'\0'
}
??好啦!寫到這里,我們來測試一下上面的接口是否有問題!?
string s1("hello world");
cout << s1.c_str() << endl;
s1.push_back('x');
cout << s1.c_str() << endl<<endl;
?OK啊!也是沒有任何問題的!
>append?
先判斷是否需要擴容,原字符串與追加的字符串總長度大于2 * _capacity,就開len + _size,否則二倍擴,不要忘了更新_size,strcpy會拷貝到\0!
string& string::append(const char* s){size_t len = strlen(s);if (len + _size > _capacity){//原字符串與追加的字符串總長度大于2 * _capacity,就開len + _size,否則二倍擴reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);}strcpy(_str + _size, s);_size += len;//不要忘了更新_sizereturn *this;}
?好啦!寫到這里,我們來測試一下上面的接口是否有問題!??
string s1("hello world");
cout << s1.c_str() << endl;
s1.append("test append ");
cout << s1.c_str() << endl << endl;
6、operator+=、insert、erase
>operator+=
直接復用push_back即可!
string& string::operator+=(char c){push_back(c);return *this;}
因為是復用的代碼,就不測試了!?
>insert(任意位置前插入一個字符)
//任意位置前插入一個字符
string& string::insert(size_t pos, char c)
{assert(pos <=_size);//先判斷是否需要擴容if (_size == _capacity){ //空間為0,則開4個空間,否則二倍擴!reserve(_capacity == 0 ? 4 : _capacity * 2);}for (size_t i = _size; i >=pos; i--){//把pos位置開始 的字符全部后移一個位置_str[i + 1] = _str[i];}_str[pos] = c;_size++;return *this;
}
?好啦!寫到這里,我們來測試一下上面的接口是否有問題!
我們先尾插一個字符!
string s1("hello world");
cout << s1.insert(s1.size(), '*').c_str() << endl << endl;
沒有什么問題!
我們再頭插一個字符!
string s1("hello world");
cout << s1.insert(0, '&').c_str() << endl << endl;
?我的發(fā)!壞了,程序直接崩潰了。我們程序員最害怕的就是自己寫的程序測試出bug來,不過我們不要慌,我們調(diào)試一下來解決問題!
按照我們的挪動邏輯,循環(huán)結(jié)束前后i的位置應該如下!?
我們來調(diào)試檢查檢查一下哪里出了問題!
通過調(diào)試我們發(fā)現(xiàn)頭插前 i 的值雀氏為11
所有的數(shù)據(jù)都按我們的想法挪 動到后面去了,不過!i的值卻不是-1,而是一個非常大的數(shù)值!
好了,這里我們就大概明白哪里出問題了,這里其實就是C語言遺留下來的一個坑,i的類型是size_t是無符號的整型,當i的值為-1時,其在內(nèi)存中的補碼是全一的序列,而它又是無符號的整型(正數(shù)),因此這全一的序列會被認為是該值的原碼?(即這是整型的最大值!)
解決這個問題的方法有很多,比如可以用int來解決,不過我這里就直接改變一下挪動的邏輯啦!
for (size_t i = _size+1; i >pos; i--)
{//把pos位置開始 的字符全部后移一個位置_str[i] = _str[i-1];
}
這樣挪i就不會到-1?
>insert(任意位置前插入一個字符串)
注釋很詳細啦,友友們認真看哦~
//任意位置前插入一個字符串
string& string::insert(size_t pos, const char* s)
{assert(pos <=_size);size_t len = strlen(s);if (len + _size > _capacity){//原字符串與追加的字符串總長度大于2 * _capacity,就開len + _size,否則二倍擴reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);}//把pos位置開始 的字符全部后移len個位置//1、用庫函數(shù)memmove一個一個字節(jié)的拷貝挪動(注意一定要多挪動一個字節(jié),把'\0'也挪動到后面去)memmove(_str + pos + len, _str + pos, (len+1) * sizeof(char));//2、手動挪/*for (size_t i = _size + len; i > pos+len-1; i--){_str[i] = _str[i - len];}*///一個一個字符拷貝for (size_t i = 0; i < len ; i++){_str[pos+i] =s[i];}_size += len;return *this;
}
>erase?
string& erase(size_t pos, size_t len=npos);
上面的代碼是類里面的成員函數(shù)聲明有缺省值npos(這是在類里面聲明的一個靜態(tài)的常量成員)
static const size_t npos;
注意:
//static const size_t npos=-1;
//特殊的可以在聲明(類內(nèi)部)處定義的static成員變量
//而且只有整形可以
//static const double d = 1.1;報錯:const double類型不能包含類內(nèi)初始值設定項?
不過,我們這里還是建議讓靜態(tài)成員變量定義到類外中,不過,我們要注意定義一定不要在頭文件中,不然我們在test.cpp和string.cpp中包含了倆次npos的定義,這時在鏈接時編譯器就找不到重復定義的成員從而發(fā)生鏈接錯誤!
所以我們在string.cpp中定義npos!
聲明處給了缺省值,定義處就不能顯示寫缺省值了!先判斷pos的有效性,再判斷從pos位置開始的字符夠不夠刪,如果不夠,直接在pos位置放\0,并更新有效字符的個數(shù)為pos。如果夠刪,就走挪動的邏輯!
從任意位置開始刪除len個字符string& string::erase(size_t pos, size_t len){assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{//把pos+len位置開始的字符全部前移len個位置//1、用庫函數(shù)memmove一個一個字節(jié)的挪動(注意一定要多挪動一個字節(jié),把'\0'也挪動到后前 //面去)//memmove(_str + pos, _str +pos+ len, (_size -pos+1) * sizeof(char));//2、手動挪for (size_t i = pos + len; i <=_size; i++){_str[i-len] = _str[i];}_size -= len;}return *this;}
7、find、substr
>find(從pos位置開始找字符c)
循環(huán)遍歷查找即可,沒什么好說的,但要注意如果沒有找到返回npos
//從pos位置開始找字符c
size_t string::find(char c, size_t pos )
{assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;
}
?>find從(pos位置開始找字符串s)
找子串問題,可以用kmp算法,但該算法在實際應用中用的并不多(C語言中strstr用的就不是該算法),而BM算法就用的較多,不過BM算法較為復雜,有興趣的小伙伴可以自己去查閱一下資料哦!我們下面就直接使用庫里面的函數(shù)來匹配子串了!????????
//從pos位置開始找字符串s
size_t string::find(const char* s, size_t pos )
{assert(pos < _size);char* ret=strstr(_str + pos, s);if (ret == nullptr){return npos;}return ret - _str;
}
>substr(從pos位置開始取子串)
//從pos位置開始取子串
string string::substr(size_t pos, size_t len)
{if (len > _size-pos){len = _size - pos;}string sub;sub.reserve(len);for (size_t i = pos; i < _size; i++){sub += _str[i];}return sub;
}
測試代碼:?
string s1("hello world");
string s2 = s1.substr(6);
cout << s2.c_str() << endl;
這里我就直接說結(jié)論,我們上面的代碼還是有問題的,我們在vs2022debug版本上測試倒不會出現(xiàn)什么問題,但vs2019或更早一點的版本就會有運行時錯誤。因為我還沒有安裝更早的版本,這里就沒辦法演示了。
那到底是哪里出了問題呢?我們在函數(shù)里面創(chuàng)建了一個局部變量sub來暫時存放取到的子串,并傳值返回,在傳值返回時,函數(shù)還會先調(diào)用拷貝構(gòu)造來拷貝一個臨時的string對象,然后再用臨時的string對象來拷貝構(gòu)造我們在函數(shù)外面用來接收返回值的string對象s2
?不過,編譯器默認的拷貝構(gòu)造是淺拷貝,即將對象一個一個字節(jié)的拷貝構(gòu)造另一個對象
通過調(diào)試我們發(fā)現(xiàn)s2的_str和sub的_str指向的是同一塊空間!當sub出函數(shù)的局部作用域時,sub對象就會調(diào)用析構(gòu)函數(shù)而銷毀,而其所指向的空間也同時被釋放!?
所以,當一個類里面有成員向內(nèi)存申請資源時,我們就不能使用編譯器默認生成的拷貝構(gòu)造了,必須自己顯示生成拷貝構(gòu)造進行深拷貝!
//拷貝構(gòu)造(深拷貝)
string(const string& s)
{_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}
那為什么在vs2022debug版本上測試倒不會出現(xiàn)什么問題呢?這里簡單的說一下,在進行傳值拷貝構(gòu)造時,編譯器可能不會進行sub對象的創(chuàng)建,直接用臨時對象拷貝構(gòu)造s2,而在這里,編譯器的優(yōu)化更為激進直接和三為一,sub和臨時對象都不創(chuàng)建了,直接拷貝構(gòu)造s2!所以也就不存在同一塊空間被多次析構(gòu)的問題了!
8、非成員函數(shù)operator比較系列
這個系列比較簡單,大家看看代碼就明白了!
bool operator<(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator<=(const string& s1, const string& s2)
{return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{return !(s1 < s2);
}
bool operator==(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator!=(const string& s1, const string& s2)
{return !(s1 == s2);
}
9、非成員函數(shù)operator<<、operator>>
> operator<<
ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}
> operator>>
istream& operator>>(istream& in, string& s)
{s.clear();//先清理有效字符char ch;in >> ch;//將流里面的字符插入到ch中while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}s += '\0';//末尾記得加上'\0'return in;
}
上面的代碼看似沒問題,其實這樣寫in讀取不到緩沖區(qū)里面的換行和空格,與scanf一樣,cin在讀取字符時,默認將空格和換行視為字符分割符不進行讀取(記住就行)!所以我們就可以用in.get()來讀取每一個字符,作用和getc一樣!
istream& operator>>(istream& in, string& s)
{s.clear();//先清理有效字符char ch;ch = in.get();//將流里面的字符插入到ch中while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}s += '\0';//末尾記得加上'\0'return in;
}
如果字符串很長,會有頻繁的擴容消耗,可以優(yōu)化一下
istream& operator>>(istream& in, string& s)
{s.clear();//先清理有效字符const size_t N = 256;int i = 0;char buff[N];//用一個buff數(shù)組做我們的緩沖char ch= in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1)//數(shù)組滿了再把字符加進s中,避免頻繁擴容{buff[i] = '\0';i = 0;s += buff;}ch = in.get();}if (i > 0)//把最后一組沒有滿的也加上{buff[i] = '\0';s += buff;}return in;
}
10、完整代碼
>string.h
#pragma once#include<iostream>
#include<assert.h>
using namespace std;namespace my_string
{class string{public://類里面短小頻繁調(diào)用的函數(shù)聲明和定義不用分離//1、無參構(gòu)造函數(shù)//string()// :_str(new char[1]{'\0'})//實際的空間大小要比capacity大1來存放'\0'// , _size (0)// ,_capacity (0)//{}// string()// :_str(nullptr)//有問題,標準庫里面的是可以輸出空字符串的// , _size (0)// ,_capacity (0)//{}//2、有參的構(gòu)造函數(shù)string(const char* str="")//加上缺省值合二為一{_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//記住開空間時多開一個strcpy(_str, str);//會把str的'\0'拷貝進來}/** 不建議走初始化列表,每次都要調(diào)用strlen()!string(const char* str):_str(new char[strlen(str) + 1]), _size(strlen(str)),_capacity(strlen(str)){strcpy(_str, str);}*///3、析構(gòu)函數(shù)~string(){delete[] _str;_str = nullptr;_capacity = _size = 0;}//4、返回C字符串const char* c_str() const{return _str;}//5、返回size和capacitysize_t size() const{return _size;}size_t capacity() const{return _capacity;}//6、[]運算符重載char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//const版本const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}//7、實現(xiàn)兩個簡單的迭代器typedef char* iterator;//正向迭代器iterator begin()const {return _str;}iterator end() const{return _str+_size;}//正向常量迭代器typedef const char* const_iterator;const_iterator cbegin() const{return _str;}const_iterator cend() const{return _str + _size;}//拷貝構(gòu)造(深拷貝)string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}//顯示賦值運算符重載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;}//下面的成員函數(shù)聲明和定義分離//0、預留空間void reserve(size_t n);//1、尾插一個字符void push_back(char c);//2、追加一個字符串string& append(const char* s);//3、+=一個字符string& operator+=(char s);//4、+=一個字符串string& operator+=(const char* s);//5、任意位置插入一個字符string& insert(size_t pos, char c);//6、任意位置插入一個字符串string& insert(size_t pos,const char* s);//7、從任意位置刪除len個字符string& erase(size_t pos, size_t len=npos);//8、從pos位置開始找字符csize_t find(char c, size_t pos = 0);//9、從pos位置開始找字符串ssize_t find(const char* s, size_t pos = 0);//10、從pos位置開始取子串string substr(size_t pos = 0, size_t len = npos);private:char* _str;//指向字符串的指針size_t _size;//有效字符個數(shù)(不包含'\0')size_t _capacity;//空間大小(不包含'\0')static const size_t npos;//static const size_t npos=-1;//特殊的可以在聲明(類內(nèi)部)處定義的static成員變量//而且只有整形可以//static const double d = 1.1;報錯:const double類型不能包含類內(nèi)初始值設定項friend ostream& operator<<(ostream& out, const string& s);};//const size_t string::npos = -1;//非成員函數(shù)!bool operator<(const string& s1, const string& s2);bool operator<=(const string& s1, const string& s2);bool operator>(const string& s1, const string& s2);bool operator>=(const string& s1, const string& s2);bool operator==(const string& s1, const string& s2);bool operator!=(const string& s1, const string& s2);ostream& operator<<(ostream& out, const string& s);istream& operator>>(istream& in, string& s);
}
>string.cpp
#define _CRT_SECURE_NO_WARNINGS#include"string.h"namespace my_string
{const size_t string::npos = -1;void string::reserve(size_t n){//標準庫里面的reserve只有在預留空間大于容量時才會擴容//并且決不改變有效字符if (n > _capacity){char* tmp = new char[n + 1];//開新空間,記得加一strcpy(tmp, _str);//拷貝數(shù)據(jù)到新空間delete[] _str;//釋放舊空間_str = tmp;//_str指向新空間_capacity = n;}return;}void string::push_back(char c){//先判斷是否需要擴容if (_size == _capacity){ //空間為0,則開4個空間,否則二倍擴!reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = c;_str[_size] = '\0';//記得要手動放一個'\0'}string& string::append(const char* s){size_t len = strlen(s);if (len + _size > _capacity){//原字符串與追加的字符串總長度大于2 * _capacity,就開len + _size,否則二倍擴reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);}strcpy(_str + _size, s);_size += len;//不要忘了更新_sizereturn *this;}string& string::operator+=(char c){push_back(c);return *this;}string& string::operator+=(const char* s){append(s);return *this;}//5、任意位置前插入一個字符string& string::insert(size_t pos, char c){assert(pos <=_size);//先判斷是否需要擴容if (_size == _capacity){ //空間為0,則開4個空間,否則二倍擴!reserve(_capacity == 0 ? 4 : _capacity * 2);}//for (size_t i = _size; i >=pos; i--)//{// //把pos位置開始 的字符全部后移一個位置// _str[i + 1] = _str[i];// //這種寫法有bug//}for (size_t i = _size+1; i >pos; i--){//把pos位置開始 的字符全部后移一個位置_str[i] = _str[i-1];}_str[pos] = c;_size++;return *this;}//6、任意位置前插入一個字符串string& string::insert(size_t pos, const char* s){assert(pos <=_size);size_t len = strlen(s);if (len + _size > _capacity){//原字符串與追加的字符串總長度大于2 * _capacity,就開len + _size,否則二倍擴reserve(len + _size > 2 * _capacity ? len + _size : 2 * _capacity);}//把pos位置開始 的字符全部后移len個位置//1、用庫函數(shù)memmove一個一個字節(jié)的拷貝挪動(注意一定要多挪動一個字節(jié),把'\0'也挪動到后面去)memmove(_str + pos + len, _str + pos, (len+1) * sizeof(char));//2、手動挪/*for (size_t i = _size + len; i > pos+len-1; i--){_str[i] = _str[i - len];}*/for (size_t i = 0; i < len ; i++){_str[pos+i] =s[i];}_size += len;return *this;}//7、從任意位置開始刪除len個字符string& string::erase(size_t pos, size_t len){assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{//把pos+len位置開始的字符全部前移len個位置//1、用庫函數(shù)memmove一個一個字節(jié)的挪動(注意一定要多挪動一個字節(jié),把'\0'也挪動到后前面去)//memmove(_str + pos, _str +pos+ len, (_size -pos+1) * sizeof(char));//2、手動挪for (size_t i = pos + len; i <=_size; i++){_str[i-len] = _str[i];}_size -= len;}return *this;}//8、從pos位置開始找字符csize_t string::find(char c, size_t pos ){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}//9、從pos位置開始找字符串ssize_t string::find(const char* s, size_t pos ){assert(pos < _size);char* ret=strstr(_str + pos, s);if (ret == nullptr){return npos;}return ret - _str;}//10、從pos位置開始取子串string string::substr(size_t pos, size_t len){if (len > _size-pos){len = _size - pos;}string sub;sub.reserve(len);for (size_t i = pos; i < _size; i++){sub += _str[i];}//注意一定要自己實現(xiàn)拷貝構(gòu)造,sub是局部的return sub;}bool operator<(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) < 0;}bool operator<=(const string& s1, const string& s2){return s1 < s2 || s1 == s2;}bool operator>(const string& s1, const string& s2){return !(s1 <= s2);}bool operator>=(const string& s1, const string& s2){return !(s1 < s2);}bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}//>1、這樣寫有問題!讀取不到換行和'\0'//istream& operator>>(istream& in, string& s)//{// s.clear();//先清理有效字符// char ch;// in >> ch;//將流里面的字符插入到ch中// while (ch != ' ' && ch != '\n')// {// s += ch;// in >> ch;// }// s += '\0';//末尾記得加上'\0'// return in;//}//>2、如果字符串很長,會有頻繁的擴容消耗,可以優(yōu)化一下//istream& operator>>(istream& in, string& s)//{// s.clear();//先清理有效字符// char ch;// ch = in.get();//將流里面的字符插入到ch中// while (ch != ' ' && ch != '\n')// {// s += ch;// ch = in.get();// }// s += '\0';//末尾記得加上'\0'// return in;//}istream& operator>>(istream& in, string& s){s.clear();//先清理有效字符const size_t N = 256;int i = 0;char buff[N];//用一個buff數(shù)組做我們的緩沖char ch= in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == N - 1)//數(shù)組滿了再把字符加進s中,避免頻繁擴容{buff[i] = '\0';i = 0;s += buff;}ch = in.get();}if (i > 0)//把最后一組沒有滿的也加上{buff[i] = '\0';s += buff;}return in;}
}
>test.cpp
#include"string.h"
using namespace my_string;
namespace my_string
{void test_my_string1(){string s1;cout << s1.c_str() << endl;/*string s2("hello world");cout << s2.c_str() << endl;*/}void test_my_string2(){/* string s1("hello world");for (size_t i = 0; i < s1.size(); i++){cout << s1[i];}cout << endl;for (size_t i = 0; i < s1.size(); i++){s1[i] += 2;cout << s1[i];}cout << endl;*///string::const_iterator it = s1.cbegin();//while (it != s1.cend())//{// //*it += 2;報錯:表達式必須是可修改的左值// cout << *it;// it++;//}//cout << endl;string s1("hello world");for (auto ch :s1 ){cout << ch;}cout<< endl;}void test_my_string3(){//string s1("hello world");//cout << "capacity:" << s1.capacity() << endl;reserve//s1.reserve(10);//cout << "capacity:" <<s1.capacity() << endl << endl;//push_back/*string s1("hello world");cout << s1.c_str() << endl;s1.push_back('x');cout << s1.c_str() << endl<<endl;*/+=//cout << s1.c_str() << endl;//s1 += '&';//cout << s1.c_str() << endl << endl;//append/*string s1("hello world");cout << s1.c_str() << endl;s1.append("test append ");cout << s1.c_str() << endl << endl;*///append//cout << s1.c_str() << endl;//s1+="test+= ";//cout << s1.c_str() << endl << endl;}void test_my_string4() {//Test insert/*string s1("hello world");cout << s1.insert(s1.size(), '*').c_str() << endl << endl;*/string s1("hello world");cout << s1.insert(0, '&').c_str() << endl << endl;//cout << s1.insert(s1.size(), "test insert s").c_str() << endl << endl;/*string s2("hello world");s2.erase(6,2);cout << s2.c_str()<<endl;s2.erase(0,8);cout << s2.c_str() << endl;*/}void test_my_string5(){//Test findstring s1("hello world");/*size_t ret = s1.find("llo");cout << ret << endl;*/string s2 = s1.substr(6);cout << s2.c_str() << endl;}void test_my_string6(){//Test << >>string s1="hello world";cout << s1 << endl;cin >> s1;cout << s1;}
}int main()
{test_my_string5();
}
11. 完結(jié)散花
好了,這期的分享到這里就結(jié)束了~
如果這篇博客對你有幫助的話,可以用你們的小手指點一個免費的贊并收藏起來喲~
如果期待博主下期內(nèi)容的話,可以點點關注,避免找不到我了呢~
我們下期不見不散~~
??
??