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