摘要:本文介紹了類的常用接口的使用,并對其進(jìn)行了模擬實現(xiàn),對模擬實現(xiàn)中涉及到的深淺拷貝問題進(jìn)行了解析。在此之前,必須提到一個經(jīng)典問題。為了解決淺拷貝問題,所以中引入了深拷貝。但是實際使用中需要是第一個形參對象,才能正常使用。
本文介紹了string類的常用接口的使用,并對其進(jìn)行了模擬實現(xiàn),對模擬實現(xiàn)中涉及到的深淺拷貝問題進(jìn)行了解析。
目錄
?在C語言中,字符串是以"/0"結(jié)尾的一些字符的集合,C標(biāo)準(zhǔn)庫還提供了str系列的庫函數(shù),但是這些庫函數(shù)與字符串不太符合OOP的思想,底層空間需要用戶自己管理,可能會造成越界訪問。
C++ 大大增強(qiáng)了對字符串的支持,除了可以使用C風(fēng)格的字符串,還可以使用內(nèi)置的 string 類。string 類處理起字符串來會方便很多,完全可以代替C語言中的字符數(shù)組或字符串指針。
string是表示字符串的字符串類,該類的接口與常規(guī)容器的接口基本相同,再添加了一些專門用來操作string的常規(guī)操作。不能操作多字節(jié)或者變長字符的序列。在底層實際是:basic_string模板類的別名,typedef basic_string
(constructor)函數(shù)名 | 功能說明 |
---|---|
string() | 構(gòu)造空的string類對象,即空字符串 |
string(const char* s) | 用C-string來構(gòu)造string類對象 |
string(size_t n, char c) | string類對象中包含n個字符c |
string(const string&s) | 拷貝構(gòu)造函數(shù) |
void Teststring(){ string s1; // 構(gòu)造空的string類對象s1 string s2("abcdef"); // 用C格式字符串構(gòu)造string類對象s2 string s3(s2); // 拷貝構(gòu)造s3}
函數(shù)名 | 功能說明 |
---|---|
size | 返回字符串有效字符長度,一般用作返回容器大小的方法 |
length | 返回字符串有效字符長度,一般用作返回一個序列的長度 |
capacity | 返回空間總大小 |
empty | 檢測字符串釋放為空串,是返回true,否則返回false |
clear | 清空有效字符 |
reserve | 為字符串預(yù)留空間 |
resize | 將有效字符的個數(shù)該成n個,多出的空間用字符c填充 |
這里的size()與length()方法底層實現(xiàn)原理完全相同,引入size()的原因是為了與其他容器的接口保持一致。
clear()只是將string中有效字符清空,不改變底層空間大小。
resize(size_t n) 與 resize(size_t n, char c)都是將字符串中有效字符個數(shù)改變到n個,不同的是當(dāng)字符個數(shù)增多時:resize(n)用0來填充多出的元素空間,resize(size_t n, char c)用字符c來填充多出的元素空間。
reserve(size_t res_arg=0):為string預(yù)留空間,不改變有效元素個數(shù),當(dāng)reserve的參數(shù)小于string的底層空間總大小時,reserver不會改變?nèi)萘看笮 ?/p>
函數(shù)名 | 功能說明 |
---|---|
operator[] | 返回pos位置的字符,const string類對象調(diào)用 |
begin+ end | begin獲取一個字符的迭代器 + end獲取最后一個字符下一個位置的迭代器 |
rbegin + rend | begin獲取一個字符的迭代器 + end獲取最后一個字符下一個位置的迭代器 |
范圍for | C++11支持更簡潔的范圍for的新遍歷方式 |
三種迭代
void Teststring(){ string s("hello world"); // 3種遍歷方式: // 1. for+operator[] for(size_t i = 0; i < s.size(); ++i) cout<
函數(shù)名 | 功能說明 |
---|---|
push_back | 在字符串后尾插字符 |
append | 在字符串后追加一個字符串 |
operator+= | 在字符串后追加字符串 |
c_str | 返回C格式字符串 |
find + npos | 從字符串pos位置開始往后找字符c,返回該字符在字符串中的位置 |
rfind | 從字符串pos位置開始往前找字符c,返回該字符在字符串中的位置 |
substr | 在str中從pos位置開始,截取n個字符,然后將其返回 |
在string尾部追加字符時,s.push_back(c) / s.append(1, c) / s += "c"三種的實現(xiàn)方式差不多,一般
情況下string類的+=操作用的比較多,+=操作不僅可以連接單個字符,還可以連接字符串。
對string操作時,如果能夠大概預(yù)估到放多少字符,可以先通過reserve把空間預(yù)留好。?
函數(shù)名 | 功能說明 |
---|---|
operator+ | 盡量少用,因為傳值返回,導(dǎo)致深拷貝效率低 |
operator>> | 輸入運算符重載 |
operator<< | 輸出運算符重載 |
getline | 獲取一行字符串 |
relational operators | 大小比較 |
int main(){ /*****************構(gòu)造**********************/ string s1; //無參 string s2("zhtzhtzht"); //帶參 string s3(s2); //拷貝構(gòu)造 string s4 = "zhtzhtzhtzht"; //substring ,給多了或者給string::npos 都是走到尾 string s5(s4, 3, 5); //從3開始5個 cout << s5 << endl; string s6("123456", 3); //取前三個構(gòu)造 cout << s6 << endl; /*************三種遍歷***************/ //1.下標(biāo)+【】 for (size_t i = 0; i < s2.size(); i++) { cout << s2[i] << " "; } cout <
上文對string類進(jìn)行了簡單的介紹,接下來模擬實現(xiàn)string類的主要函數(shù)。在此之前,必須提到一個經(jīng)典問題。
class string{public: string(const char* str = "") { // 構(gòu)造string類對象時,如果傳遞nullptr指針,認(rèn)為程序非法 if(nullptr == str) { assert(false); return; } _str = new char[strlen(str) + 1]; strcpy(_str, str);}~string(){ if(_str) { delete[] _str; _str = nullptr; }}private: char* _str;};void Teststring(){ string s1("hello"); string s2(s1);}
上述代碼會崩潰,string類沒有顯式定義其拷貝構(gòu)造函數(shù)與賦值運算符重載,此時編譯器會合成默認(rèn)的,當(dāng)用s1構(gòu)造s2時,編譯器會調(diào)用默認(rèn)的拷貝構(gòu)造。最終導(dǎo)致的問題是,s1、s2共用同一塊內(nèi)存空間,在釋放時同一塊空間被釋放多次而引起程序崩潰,這種拷貝方式,稱為淺拷貝。
?淺拷貝:也稱位拷貝,編譯器只是將對象中的值拷貝過來。如果對象中管理資源,最后就會導(dǎo)致多個對象共享同一份資源,當(dāng)一個對象銷毀時就會將該資源釋放掉,而此時另一些對象不知道該資源已經(jīng)被釋放,所以當(dāng)繼續(xù)對資源進(jìn)項操作時,就會發(fā)生發(fā)生了訪問違規(guī)。
為了解決淺拷貝問題,所以C++中引入了深拷貝。
如果一個類中涉及到資源的管理,其拷貝構(gòu)造函數(shù)、賦值運算符重載以及析構(gòu)函數(shù)必須要顯式給出。
顯式地定義拷貝構(gòu)造函數(shù),它除了會將原有對象的所有成員變量拷貝給新對象,還會為新對象再分配一塊內(nèi)存,并將原有對象所持有的內(nèi)存也拷貝過來。這樣做的結(jié)果是,原有對象和新對象所持有的動態(tài)內(nèi)存是相互獨立的,更改一個對象的數(shù)據(jù)不會影響另外一個對象。
class string{public: string(const char* str = "") { if(nullptr == str) { assert(false); return; } _str = new char[strlen(str) + 1]; strcpy(_str, str); } string(const string& s) : _str(new char[strlen(s._str)+1]) { strcpy(_str, s._str); } string& operator=(const string& s) { if(this != &s) { char* pStr = new char[strlen(s._str) + 1]; strcpy(pStr, s._str); delete[] _str; _str = pStr; } return *this; } ~string() { if(_str) { delete[] _str; _str = nullptr; } }private: char* _str;};
class string{public: string(const char* str = "") { if(nullptr == str) str = ""; _str = new char[strlen(str) + 1]; strcpy(_str, str); } string(const string& s) : _str(nullptr) { string strTmp(s._str); swap(_str, strTmp._str); } string& operator=(string s) { swap(_str, s._str); return *this; } ~string() { if(_str) { delete[] _str; _str = nullptr; } }private: char* _str;};
寫時拷貝是在淺拷貝的基礎(chǔ)之上增加了引用計數(shù)的方式來實現(xiàn)的。
引用計數(shù):用來記錄資源使用者的個數(shù)。在構(gòu)造時,將資源的計數(shù)給成1,每增加一個對象使用該資源,就給計數(shù)增加1,當(dāng)某個對象被銷毀時,先給該計數(shù)減1,然后再檢查是否需要釋放資源,如果計數(shù)為1,說明該對象時資源的最后一個使用者,將該資源釋放;否則就不能釋放,因為還有其他對象在使用該資源。
下面給出模擬實現(xiàn)的完整代碼以及需要注意的點
#include#include#include#includeusing std::cout;using std::endl;namespace zht{ class string { public: typedef char* iterator; //容器迭代器本質(zhì)上是指針,通過typedef給char*重定義關(guān)鍵字 typedef const char* const_iterator;//迭代器需要提供const型,const 迭代器與普通迭代器在編譯器處理時會進(jìn)行修飾,構(gòu)成了函數(shù)重載 friend std::ostream& operator<<(std::ostream& out, const string& s); //為了方便內(nèi)部引用,所以要設(shè)置為友元 friend std::istream& operator>>(std::istream& in, string& s); iterator begin() // 開始 { return _str; } const_iterator begin() const //需要提供const類型迭代器,權(quán)限只能縮小不能放大,所以在處理const類型的問題時需要使用const類型的迭代器 { return _str; } iterator end() //結(jié)束 { return _str + _size; //迭代器結(jié)束實在空間的最后一位的后一個 } const_iterator end() const { return _str + _size; } // operator& string(const char* str = "") //構(gòu)造函數(shù),現(xiàn)代寫法,減少創(chuàng)建的臨時對象的個數(shù) :_str(new char[strlen(str) + 1]) { _size = strlen(str); _capacity = _size; strcpy(_str,str); } //void swap(string& s) //{ // ::swap(_str,s._str); //::swap(_size,s._size); //::swap(_capacity,s._capacity); //} //開空間 void reserve(std::size_t n) { if(n > _capacity) //當(dāng)N大于最大容量時擴(kuò)容 { char* tmp = new char[n + 1]; //創(chuàng)建N+1個空間,需要保存/0. strncpy(tmp, _str, _size + 1); //將原空間中的數(shù)據(jù)拷貝到新的中 delete []_str; _str = tmp; //更新 _capacity = n; } } //開空間 + 初始化,重置capacity void resize(std::size_t n, char ch = "/0") { //三情況,1.小于當(dāng)前的字符串長度,2.大于字符串長度但是小于空間大小;3.大于空間大小 if(n < _size) //1.直接在n處加/0 { _size = n; _str[n] = "/0"; } else { if(n > _capacity) //3.擴(kuò)容,然后與2.合并 { reserve(n); } for(std::size_t i = _size; i < _capacity; i++) //從當(dāng)前字符串向后覆蓋 { _str[i] = ch; } _str[_capacity] = "/0"; _size = n; } } void swap(string& s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } string(const string& s) //拷貝構(gòu)造函數(shù),現(xiàn)代寫法,通過創(chuàng)建一個新對象,交換,達(dá)到拷貝構(gòu)造的目的 :_str(NULL) ,_size(0) ,_capacity(0) { string tmp(s._str); swap(tmp); } //binstring& operator+= (char ch) //{ //} string& operator=(string s) // = 運算符重載 { swap(s); return *this; } ~string() { delete [] _str; _str = NULL; _size = 0; _capacity = 0; } void clear() { _size = 0; _str[0] = "/0"; } //可讀可寫 char& operator[](std::size_t i) { assert(i < _size); ///0,所以閉區(qū)間 return _str[i]; } //只讀 const char& operator[](std::size_t i) const { assert(i < _size); return _str[i]; } ///返回對象中的字符串,用const const char* c_str() const { return _str; } //pos位置插入 string& insert(std::size_t pos, char ch) { assert(pos <= _size); //可以尾插,所以可以等于 //先判斷是否需要擴(kuò)容 if(_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } //將數(shù)據(jù)后移 char* end = _size + _str; //從/0開始挪 while (end >= _str + pos)//pos位需要挪 { *(end + 1) = *end; //end向后挪也就是end-1 --end; //再向前 } *(_str + pos) = ch; _size++; return *this; } //插入字符串 string& insert(std::size_t pos,const char* str) { assert(pos <= _size); std::size_t len = strlen(str); if(_size + len > _capacity)//可能會直接大于 { reserve(_size + len); } char* end = _size + _str; while(end >= pos + _str) { *(end + len) = *end; --end; } strncpy(_str + pos, str, len); _size += len; return *this; } void push_back(char ch) //尾插字符 { insert(_size,ch); } void append(const char* str) //尾插字符串 { insert(_size, str); } string& operator+=(char ch) //重載+=字符 { push_back(ch); return *this; } string& operator+=(const char* str) //重載+=字符串 { append(str); return *this; } string& erase(std::size_t pos,std::size_t len = -1) { assert(pos < _size); //兩種情況: //1.剩余長度小于需要刪除的 //2.剩余長度大于需要刪除的 std::size_t LeftLen = _size - pos; if(LeftLen <= len) // 小于,全刪除 { _str[pos] = "/0"; _size = pos; } else //大于,len位向前補(bǔ)。 { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; } std::size_t find (char ch, std::size_t pos = 0) { assert(pos < _size); for(std::size_t i = pos; i < _size; ++i) { if(_str[i] == ch) { return i; } } return -1; } std::size_t find (const char* str, std::size_t pos = 0) { assert(pos < _size); const char* ret = strstr(_str + pos, str); //函數(shù)返回在 haystack 中第一次出現(xiàn) needle 字符串的位置,如果未找到則返回 null。 if(ret) { return ret - _str; } else{ return -1; } } std::size_t size() const { return _size; } private: char* _str; //字符串指針 std::size_t _size; //使用的空間大小 std::size_t _capacity; //空間大小 }; inline bool operator<(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) < 0; //strcmp(str1,str2),若str1=str2,則返回零;若str1str2,則返回正數(shù) } inline bool operator==(const string& s1, const string& s2) { return strcmp(s1.c_str(), s2.c_str()) == 0; } inline bool operator<=(const string& s1, const string& s2) { return s1 < s2 || s1 == s2; } inline bool operator!=(const string& s1, const string& s2) { return !(s1 == s2); } inline bool operator>(const string& s1, const string& s2) { return !(s1 <= s2); } inline bool operator>=(const string& s1, const string& s2) { return !(s1 < s2); } std::ostream& operator<<(std::ostream& out, const string& s) //因為cout的輸出流對象和隱含的this指針在搶占第一個參數(shù)的位置。this指針默認(rèn)是第一個參數(shù)也就是左操作數(shù)了。 //但是實際使用中cout需要是第一個形參對象,才能正常使用。 //友元函數(shù)可以訪問 { for(auto ch : s) //使用范圍for遍歷字符串 { out << ch; //輸出到輸出流 } return out; } std::istream& operator>>(std::istream& in,string& s) { s.clear(); char ch; ch = in.get(); while(ch != " " && ch != "/n") { s += ch; ch = in.get(); } return in; }}
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/120816.html
摘要:本文介紹了的常用接口的使用,并對其進(jìn)行了模擬實現(xiàn),包括迭代器的實現(xiàn)。與為反向迭代器,對迭代器執(zhí)行操作,迭代器向前移動。 本文介紹了list的常用接口的使用,并對其進(jìn)行了模擬實現(xiàn),包括list迭代器的實現(xiàn)。 目錄 一、list的介紹 二、list的常用接口的使用 1. list的構(gòu)造 2. l...
摘要:函數(shù)底層實際上是對指針的操作隸書向,范圍內(nèi)比較等于的第一個元素返回迭代器。指定位置元素的刪除操作使用查找所在位置的刪除位置的數(shù)據(jù),導(dǎo)致迭代器失效。因此刪除中任意位置上元素時,就認(rèn)為該位置迭代器失效了。 ...
摘要:并且由于的連續(xù)性,且循環(huán)中有迭代器的自加,所以在刪除一個元素后,迭代器需要減。隸書方案二與方案一在迭代器的處理上是類似的,不過對元素的訪問采用了迭代器的方法。 一、...
摘要:注意當(dāng)中的和屬于容器適配器,它們默認(rèn)使用的基礎(chǔ)容器分別是和。拷貝構(gòu)造類型容器的復(fù)制品方式三使用迭代器拷貝構(gòu)造某一段內(nèi)容。若待插入元素的鍵值在當(dāng)中已經(jīng)存在,則函數(shù)插入失敗,并返回當(dāng)中鍵值為的元素的迭代器和。返回該迭代器位置元素的值。 ...
摘要:對類采用三個模板來實現(xiàn)迭代器。楷體類中和模板定義分別對應(yīng)迭代器中模板定義的楷體采用向上傳值的方式,傳入不同值來采用不同迭代器。首先是迭代器的,分為前置和后置。迭代器的和都是獲取迭代器所對應(yīng)的值。唯一的差別就是就是用到了迭代器。 ...
閱讀 2022·2023-04-25 23:30
閱讀 1451·2021-11-24 10:18
閱讀 3068·2021-10-09 09:54
閱讀 2016·2021-10-08 10:05
閱讀 3431·2021-09-23 11:21
閱讀 3161·2019-08-30 15:52
閱讀 1560·2019-08-30 13:05
閱讀 1056·2019-08-30 13:02