一. 為什么學(xué)習(xí)string?
C語(yǔ)言中,字符串是以’\0’結(jié)尾的一些字符的集合,為了操作方便,C標(biāo)準(zhǔn)庫(kù)中提供了一些str系列的庫(kù)函數(shù),但是這些庫(kù)函數(shù)與字符串是分離開(kāi)的,不太符合OOP的思想,而且底層空間需要用戶(hù)自己管理,稍不留神可能還會(huì)越界訪(fǎng)問(wèn)。
二、 標(biāo)準(zhǔn)庫(kù)中的string
2.1 string介紹
string的文檔介紹
- 字符串是表示字符序列的類(lèi)。
- 標(biāo)準(zhǔn)的字符串類(lèi)提供了對(duì)此類(lèi)對(duì)象的支持,其接口類(lèi)似于標(biāo)準(zhǔn)字符容器的接口,但添加了專(zhuān)門(mén)用于操作單字節(jié)字符字符串的設(shè)計(jì)特性。
-
string
是使用char
(即作為它的字符類(lèi)型,使用它的默認(rèn)char_traits
和分配器類(lèi)型(關(guān)于模板的更多信息,請(qǐng)參閱basic_string
)。 -
string
類(lèi)是basic_string
模板類(lèi)的一個(gè)實(shí)例,它使用char
來(lái)實(shí)例化basic_string
模板類(lèi),并用char_traits
和allocator
作為basic_string
的默認(rèn)參數(shù)(根于更多的模板信息請(qǐng)參考basic_string
)。 - 注意,這個(gè)類(lèi)獨(dú)立于所使用的編碼來(lái)處理字節(jié):如果用來(lái)處理多字節(jié)或變長(zhǎng)字符(如UTF-8)的序列,這個(gè)類(lèi)的所有成員(如長(zhǎng)度或大小)以及它的迭代器,將仍然按照字節(jié)(而不是實(shí)際編碼的字符)來(lái)操作。
總結(jié):
-
string
是表示字符串的字符串類(lèi) - 該類(lèi)的接口與常規(guī)容器的接口基本相同,再添加了一些專(zhuān)門(mén)用來(lái)操作
string
的常規(guī)操作。 -
string
在底層實(shí)際是:basic_string
模板類(lèi)的別名,typedef basic_string<char, char_traits, allocator>string
; - 不能操作多字節(jié)或者變長(zhǎng)字符的序列。
在使用string
時(shí),必須包含#include
頭文件以及using namespace std
;
2.2 string的常用接口說(shuō)明
2.2.1 string對(duì)象的常見(jiàn)構(gòu)造
2.2.1.1 string() ---- 無(wú)參構(gòu)造函數(shù)
構(gòu)造空的string對(duì)象,即空字符串
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cout << s <<endl;
return 0;
}
2.2.1.2 string(const char* s) ---- 有參構(gòu)造函數(shù)
用C-string來(lái)構(gòu)造string對(duì)象
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
cout << s << endl;
return 0;
}
2.2.1.3 string(size_t n, char c) ---- 有參構(gòu)造函數(shù)
string對(duì)象中包含n個(gè)字符c
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s(10,'z');
cout << s << endl;
return 0;
}
2.2.1.4 string(const string&s) ---- 拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
string ss(s);
cout << s << endl;
cout << ss << endl;
return 0;
}
2.2.2 string對(duì)象的容量操作
2.2.2.1 size 函數(shù)
返回字符串有效字符長(zhǎng)度
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
cout << s.size() << endl;
return 0;
}
2.2.2.2 length 函數(shù)
返回字符串有效字符長(zhǎng)度
size() 與 length() 方法底層實(shí)現(xiàn)原理完全相同,
引入 size() 的原因是為了與其他容器的接口保持一致,
一般情況下基本都是用 size()。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
cout << s.length() << endl;
return 0;
}
2.2.2.3 capacity 函數(shù)
返回空間總大小
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
cout << s.capacity() << endl;
return 0;
}
2.2.2.4 empty 函數(shù)
檢測(cè)字符串釋放為空串,是返回true,否則返回false
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
string ss("chineseprson");
cout << s.empty() << endl;
cout << ss.empty() << endl;
return 0;
}
2.2.2.5 clear 函數(shù)
清空有效字符
clear()只是將string中有效字符清空,不改變底層空間大小。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
cout << s << endl;
s.clear();
cout << s << endl;
return 0;
}
2.2.2.6 reserve 函數(shù)
為字符串預(yù)留空間
reserve(size_t res_arg=0):
為string預(yù)留空間,不改變有效元素個(gè)數(shù),
當(dāng)reserve的參數(shù)小于string的底層空間總大小時(shí),
reserver不會(huì)改變?nèi)萘啃 ?
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
s.reserve(30);
cout << s << endl;
return 0;
}
2.2.2.7 resize 函數(shù)
resize(size_t n) 與 resize(size_t n, char c)都是將字符串中有效字符個(gè)數(shù)改變到n個(gè),
不同的是當(dāng)字符個(gè)數(shù)增多時(shí):resize(n)用 '\0' 來(lái)填充多出的元素空間,
resize(size_t n, char c)用字符c來(lái)填充多出的元素空間。
注意:resize在改變?cè)貍€(gè)數(shù)時(shí),如果是將元素個(gè)數(shù)增多,
可能會(huì)改變底層容量的大小,如果是將元素個(gè)數(shù)減少,底層空間總大小不變。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
cout << s << endl;
s.resize(30,'6');
cout << s << endl;
return 0;
}
2.2.3 string對(duì)象的訪(fǎng)問(wèn)及遍歷操作
2.2.3.1 operator[]
返回pos位置的字符,const string對(duì)象調(diào)用
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
for (int i = 0; i < s.size(); i++)
{
printf("[%d] : %c\n", i, s[i]);
}
cout << endl;
return 0;
}
2.2.3.2 迭代器 begin 、end
begin 獲取第一個(gè)字符的迭代器
end 獲取最后一個(gè)字符下一個(gè)位置的迭代器
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
return 0;
}
2.2.3.3 迭代器 rbegin 、rend
rbegin 獲取最后一個(gè)字符的迭代器
rend 獲取第一個(gè)字符前一個(gè)位置的迭代器
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
string::reverse_iterator it = s.rbegin();
while (it != s.rend())
{
cout << *it << ' ';
it++;
}
cout << endl;
return 0;
}
2.2.3.4 范圍for
范圍for作為C++新出的遍歷方法,相對(duì)于以前的遍歷方式它能夠更加簡(jiǎn)潔的遍歷數(shù)組、容器等數(shù)據(jù)結(jié)構(gòu)中的元素。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
for (auto ch : s)
{
cout << ch << ' ';
}
cout << endl;
return 0;
}
2.2.4. string對(duì)象的增刪查改
2.2.4.1 push_back 函數(shù)
在字符串后面加一個(gè)字符
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
s.push_back('6');
cout << s << endl;
s.push_back('6');
cout << s << endl;
s.push_back('6');
cout << s << endl;
return 0;
}
2.2.4.2 operator+=
string& operator+= (const string& str);
operator+= 能夠在字符串最后追加一個(gè)string對(duì)象內(nèi)的字符串
string& operator+= (const char* s);
string& operator+= (char c);
operator+= 能夠在字符串后面加一個(gè)字符或者字符串
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
s += '6';
cout << s << endl;
s += "666666";
cout << s << endl;
string ss("牛牛牛");
s += ss;
cout << s << endl;
return 0;
}
2.2.4.3 append 函數(shù)
string& append (const string& str);
append 函數(shù)能夠在字符串最后追加一個(gè)string對(duì)象內(nèi)的字符串
string& append (const string& str, size_t subpos, size_t sublen);
append 函數(shù)能夠在字符串最后追加一個(gè)string對(duì)象內(nèi)的字符串的一段字符串
string& append (const char* s);
append 函數(shù)能夠在字符串最后追加一個(gè)string對(duì)象內(nèi)的字符串
string& append (const char* s, size_t n);
append 函數(shù)還能夠在字符串后面追加字符串的前 n 個(gè)
string& append (size_t n, char c);
append 函數(shù)能夠在字符串后面追加 n 個(gè)字符
注意:append
函數(shù)與 operator+=
作用有部分相同
若是單純?cè)谧址竺孀芳右粋€(gè)字符或者字符串,更習(xí)慣使用 operator+=
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
// 追加n個(gè)字符
s.append(1,'6');
cout << s << endl;
// 追加一個(gè)字符串
s.append("666666");
cout << s << endl;
// 追加一個(gè)字符串的前n個(gè)
s.append("888888888", 3);
cout << s << endl;
return 0;
}
2.2.4.4 insert 函數(shù)
string& insert (size_t pos, const string& str);
insert函數(shù)能夠在字符串任意位置插入一個(gè)string容器內(nèi)的字符串
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
insert函數(shù)能夠在字符串任意位置插入一個(gè)string對(duì)象內(nèi)的字符串的一段字符串
string& insert (size_t pos, const char* s);
insert函數(shù)能夠在字符串一段字符串
string& insert (size_t pos, const char* s, size_t n);
insert 函數(shù)還能夠在字符串任意位置插入字符串的前 n 個(gè)
string& insert (size_t pos, size_t n, char c);
insert 函數(shù)還能夠在字符串任意位置插入n個(gè)字符
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("student");
string ss("test");
// 在第三個(gè)位置插入string
s.insert(1, ss);
cout << s << endl;
// 在最后插入string的一部分
s.insert(s.size(), ss , 0 , 2);
cout << s << endl;
// 在第一個(gè)位置插入string
s.insert(0,"666");
cout << s << endl;
// 其他插入方法一樣,這里省略
return 0;
}
2.2.4.5 erase 函數(shù)
string& erase (size_t pos = 0, size_t len = npos);
erase 函數(shù)能夠刪除第 n 個(gè)位置后面長(zhǎng)度為 len 的字符串
如果沒(méi)有傳 len 或是 第 n 個(gè)位置后面的字符數(shù)小于 len ,則n后面的字符全部刪除
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("student");
s.erase(4, 2);
cout << s << endl;
s.erase(1);
cout << s << endl;
return 0;
}
2.2.4.6 npos
npos的值通常是一個(gè)很大的正數(shù),等于-1(當(dāng)作為無(wú)符號(hào)數(shù)解釋時(shí))
或等于string::size_type的最大可能值。
2.2.4.7 c_str 函數(shù)
返回C格式字符串
在C++中,printf 是一個(gè)C語(yǔ)言函數(shù),它不支持直接打印std::string
類(lèi)型的內(nèi)容。這是因?yàn)?printf
是一個(gè)可變參數(shù)函數(shù),而 std::string
不是基本數(shù)據(jù)類(lèi)型,因此需要轉(zhuǎn)換為C風(fēng)格字符串才能由 printf
輸出。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("chineseprson");
printf("%s\n", s);
printf("%s\n", s.c_str());
return 0;
}
2.2.4.8 find 函數(shù)
size_t find (const string& str, size_t pos = 0) const;
find 函數(shù)能夠從第 pos 個(gè)位置開(kāi)始從前往后查找包含 string 對(duì)象 str 的字符串的位置
size_t find (const char* s, size_t pos = 0) const;
find 函數(shù)能夠從第 pos 個(gè)位置開(kāi)始從前往后查找包含字符串 s 的位置
size_t find (char c, size_t pos = 0) const;
find 函數(shù)能夠從第 pos 個(gè)位置開(kāi)始從前往后查找包含字符 c 的位置
find 函數(shù)若是能找到則返回包含需要查找內(nèi)容第一個(gè)字符位置,否則返回 npos。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("student test");
string str("student");
// 查找 str --> 找得到
int pos = s.find(str, 0);
cout << "str pos : " << pos << endl;
// 查找字符串 --> 找得到
pos = s.find("test" , 0);
cout << "test pos : " << pos << endl;
// 查找字符串 --> 找不到
pos = s.find("Test", 0);
cout << "Test pos : " << pos << endl;
// 查找字符 --> 找得到
pos = s.find('s', 0);
cout << "s pos : " << pos << endl;
// 查找字符 --> 找不到
pos = s.find('a', 0);
cout << "a pos : " << pos << endl;
return 0;
}
2.2.4.9 rfind 函數(shù)
size_t rfind (const string& str, size_t pos = npos) const;
find 函數(shù)能夠從第 pos 個(gè)位置開(kāi)始從后往前查找包含 string 對(duì)象 str 的字符串的位置
size_t rfind (const char* s, size_t pos = npos) const;
find 函數(shù)能夠從第 pos 個(gè)位置開(kāi)始從前往后查找包含字符串 s 的位置
size_t rfind (char c, size_t pos = npos) const;
find 函數(shù)能夠從第 pos 個(gè)位置開(kāi)始從前往后查找包含字符 c 的位置
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("student test");
string str("student");
// 查找 str --> 找得到
int pos = s.rfind(str, s.size()-1);
cout << "str pos : " << pos << endl;
// 查找字符串 --> 找得到
pos = s.rfind("test", s.size() - 1);
cout << "test pos : " << pos << endl;
// 查找字符串 --> 找不到
pos = s.rfind("test", s.size() - 5);
cout << "test pos : " << pos << endl;
// 查找字符串 --> 找不到
pos = s.rfind("Test", s.size() - 1);
cout << "Test pos : " << pos << endl;
// 查找字符 --> 找得到
pos = s.rfind('s', s.size() - 1);
cout << "s pos : " << pos << endl;
// 查找字符 --> 找不到
pos = s.rfind('a', s.size() - 1);
cout << "a pos : " << pos << endl;
return 0;
}
2.2.4.10 substr 函數(shù)
string substr (size_t pos = 0, size_t len = npos) const;
在字符串中從第pos個(gè)位置開(kāi)始截取len個(gè)字符返回
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s("student test");
// 在s中從第0個(gè)位置開(kāi)始截取所有字符返回
string ss = s.substr(0);
cout << ss << endl;
// 在s中從第3個(gè)位置開(kāi)始截取3字符返回
ss = s.substr(3,3);
cout << ss << endl;
return 0;
}
三、string拷貝問(wèn)題
3.1 經(jīng)典的string問(wèn)題
上面已經(jīng)對(duì)string進(jìn)行了簡(jiǎn)單的介紹,大家只要能夠正常使用即可。在面試中,面試官總喜歡讓學(xué)生自己來(lái)模擬實(shí)現(xiàn)string,最主要是實(shí)現(xiàn)string的構(gòu)造、拷貝構(gòu)造、賦值運(yùn)算符重載以及析構(gòu)函數(shù)。大家看下以下string的實(shí)現(xiàn)是否有問(wèn)題?
// 為了和標(biāo)準(zhǔn)庫(kù)區(qū)分,此處使用String
class String
{
public:
/*String()
:_str(new char[1])
{*_str = '\0';}
*/
//String(const char* str = "\0") 錯(cuò)誤示范
//String(const char* str = nullptr) 錯(cuò)誤示范
String(const char* str = "")
{
// 構(gòu)造String對(duì)象時(shí),如果傳遞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;
};
// 測(cè)試
void TestString()
{
String s1("hello C++!!!");
String s2(s1);
}
說(shuō)明:上述String沒(méi)有顯式定義其拷貝構(gòu)造函數(shù)與賦值運(yùn)算符重載,此時(shí)編譯器會(huì)合成默認(rèn)的,當(dāng)用s1構(gòu)造s2時(shí),編譯器會(huì)調(diào)用默認(rèn)的拷貝構(gòu)造。最終導(dǎo)致的問(wèn)題是,s1、s2共用同一塊內(nèi)存空間,在釋放時(shí)同一塊空間被釋放多次而引起程序崩潰,這種拷貝方式,稱(chēng)為淺拷貝
3.2 淺拷貝
淺拷貝:也稱(chēng)位拷貝,編譯器只是將對(duì)象中的值拷貝過(guò)來(lái)。如果對(duì)象中管理資源,最后就會(huì)導(dǎo)致多個(gè)對(duì)象共享同一份資源,當(dāng)一個(gè)對(duì)象銷(xiāo)毀時(shí)就會(huì)將該資源釋放掉,而此時(shí)另一些對(duì)象不知道該資源已經(jīng)被釋放,以為還有效,所以當(dāng)繼續(xù)對(duì)資源進(jìn)項(xiàng)操作時(shí),就會(huì)發(fā)生發(fā)生了訪(fǎng)問(wèn)違規(guī)。
可以采用深拷貝解決淺拷貝問(wèn)題,即:每個(gè)對(duì)象都有一份獨(dú)立的資源,不要和其他對(duì)象共享。
3.3 深拷貝
如果一個(gè)類(lèi)中涉及到資源的管理,其拷貝構(gòu)造函數(shù)、賦值運(yùn)算符重載以及析構(gòu)函數(shù)必須要顯式給出。一般情
況都是按照深拷貝方式提供。
3.4 寫(xiě)時(shí)拷貝
寫(xiě)時(shí)拷貝就是一種拖延癥,是在淺拷貝的基礎(chǔ)之上增加了引用計(jì)數(shù)的方式來(lái)實(shí)現(xiàn)的。
引用計(jì)數(shù):用來(lái)記錄資源使用者的個(gè)數(shù)。在構(gòu)造時(shí),將資源的計(jì)數(shù)給成1,每增加一個(gè)對(duì)象使用該資源,就給計(jì)數(shù)增加1,當(dāng)某個(gè)對(duì)象被銷(xiāo)毀時(shí),先給該計(jì)數(shù)減1,然后再檢查是否需要釋放資源,如果計(jì)數(shù)為1,說(shuō)明該對(duì)象時(shí)資源的最后一個(gè)使用者,將該資源釋放;否則就不能釋放,因?yàn)檫€有其他對(duì)象在使用該資源。
四、string的模擬實(shí)現(xiàn)
4.1 string默認(rèn)成員函數(shù)的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
// 這里缺省值給""的原因是空字符串本身就帶一個(gè)'\0'
// 而不是初始化的時(shí)候_str為nullptr
// 初始化列表的順序應(yīng)該與聲明相同
string(const char* str = "")
:_capacity(strlen(str))
, _size(_capacity)
{
// 多開(kāi)一個(gè)空間用來(lái)存放'\0'
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 拷貝構(gòu)造函數(shù)傳統(tǒng)寫(xiě)法:
/*string(const string& s)
{
_capacity = s._capacity;
_str = new char[_capacity + 1];
_size = s._size;
strcpy(_str, s._str);
}*/
// swap函數(shù)
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
// 拷貝構(gòu)造函數(shù)現(xiàn)代寫(xiě)法:
// 構(gòu)造一個(gè)使用s構(gòu)造string對(duì)象tmp
// tmp中的內(nèi)容是this指向的對(duì)象所需要的
// 將兩個(gè)對(duì)象的指針交換
// 即可達(dá)到我們的目的
string(const string& s)
: _str(nullptr)
, _capacity(0)
, _size(0)
{
string tmp(s._str);
swap(tmp);
}
// 賦值重載的傳統(tǒng)寫(xiě)法:
// 這里的傳統(tǒng)寫(xiě)法與上面的拷貝構(gòu)造的內(nèi)容幾乎相同
// 而下面的現(xiàn)代寫(xiě)法復(fù)用了拷貝構(gòu)造
// 使得成員函數(shù)看起來(lái)更加簡(jiǎn)潔
// 賦值重載 傳統(tǒng)寫(xiě)法
/*string& operator=(const string& s)
{
if (this != &s)
{
reserve(s._capacity);
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
return *this;
}*/
// 賦值重載的現(xiàn)代寫(xiě)法:
// 首先判斷是否是自己給自己賦值
// 若是直接返回自己,否則進(jìn)行下面的操作
// 利用拷貝構(gòu)造得來(lái)tmp
// tmp中的數(shù)據(jù)是我們需要的數(shù)據(jù)
// 而this指向的數(shù)據(jù)是我們需要改變的
// tmp是臨時(shí)變量,出了作用域會(huì)自動(dòng)銷(xiāo)毀
// 我們將tmp的內(nèi)容和this指向的內(nèi)容交換
// 實(shí)質(zhì)上是兩個(gè)指針的指向變化
// 交換后this指向的內(nèi)容就是我們需要的,返回
// 而tmp中的內(nèi)容是不需要的,出了作用域自動(dòng)銷(xiāo)毀
// 賦值重載 現(xiàn)代寫(xiě)法
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}*/
// 拷貝構(gòu)造函數(shù)的現(xiàn)代寫(xiě)法:
// 這個(gè)現(xiàn)代寫(xiě)法與上面的本質(zhì)相同
// 這個(gè)是不判斷是否是自己給自己賦值
// 而是傳值傳參時(shí)利用拷貝構(gòu)造直接得來(lái)tmp
// 其他步驟相同
// 賦值重載 現(xiàn)代寫(xiě)法
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
// 析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.2 string 中 c_str 、size 、capacity 和 empty 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
#include <string>
namespace aj
{
class string
{
public:
void clear()
{
_str[0] = '\0';
_size = 0;
}
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.3 string 中 resize 和 reverse 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
// 分三種情況 (n為新字符串的長(zhǎng)度)
// (1) n <= _size
// (2) n > _size && n <= _capacity
// (3) n > _capacity
// 第一種情況為縮短,第二三種情況為增長(zhǎng),
// 但第二種情況不需要擴(kuò)容,第三種情況不需要
// 由于resize內(nèi)部當(dāng)傳入的參數(shù)小于_capacity 時(shí)不會(huì)擴(kuò)容
// 所以將第二三種情況放在一起
void resize(size_t n, char c = '\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
_capacity = n;
while (_size < n)
{
_str[_size] = c;
_size++;
}
// 到這里 _size = n
_str[_size] = '\0';
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
// _capacity 記錄的是需要存儲(chǔ)有效數(shù)據(jù)的個(gè)數(shù)
// 所以我們這里要多開(kāi)一個(gè)空間用來(lái)記錄'\0'
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.4 string 中 push_back 、append 和 operator+= 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
void push_back(char c)
{
if (_size + 1 > _capacity)
{
// 這里不能盲目的開(kāi)二倍,因?yàn)閟tring可能是空字符串,
// _capacity = 0 , 那么這里的二倍就沒(méi)有意義,繼續(xù)下面的操作會(huì)報(bào)錯(cuò)
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
void append(const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
void reserve(size_t n)
{
if (n > _capacity)
{
// _capacity 記錄的是需要存儲(chǔ)有效數(shù)據(jù)的個(gè)數(shù)
// 所以我們這里要多開(kāi)一個(gè)空間用來(lái)記錄'\0'
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.5 string 中 operator[] 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
char& operator[](size_t index)
{
return _str[index];
}
const char& operator[](size_t index)const
{
return _str[index];
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.6 string 中 迭代器 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
public:
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.7 string 中 operator<< 和 operator>> 的實(shí)現(xiàn)
并不是所有的流插入、流提取都需要定義成友元函數(shù),定義成友元函數(shù)的目的是為了訪(fǎng)問(wèn)成員的私有,這里不需要訪(fǎng)問(wèn)私有,則不需要定義成友元函數(shù)。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-844539.html
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& _cout, const string& s)
{
for (auto ch : s)
{
cout << ch;
}
return _cout;
}
// 這個(gè)版本不好在,沒(méi)有提前開(kāi)空間
// 即使開(kāi)空間了,也不知道開(kāi)多少
// 大了浪費(fèi),小了又需要很多次擴(kuò)容
//istream& operator>>(istream& _cin, string& s)
//{
// // 流插入時(shí)需要將string中字符串清除
// s.clear();
// char ch = 0;
// ch = _cin.get();
// while (ch != ' ' && ch != '\n')
// {
// s += ch;
// ch = _cin.get();
// }
// return _cin;
//}
istream& operator>>(istream& _cin, string& s)
{
// 流插入時(shí)需要將string中字符串清除
s.clear();
// 定義一個(gè)buff數(shù)組,作為緩沖
char buff[129] = { 0 };
char ch = 0;
ch = _cin.get();
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i] = ch;
ch = _cin.get();
if (i == 128)
{
s += buff;
i = 0;
}
i++;
}
if (i != 0)
{
s += buff;
}
return _cin;
}
};
4.8 string 中 比較函數(shù) 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 0;
}
bool operator<=(const string& s)
{
return *this == s || *this < s;
}
bool operator>(const string& s)
{
return !(*this <= s);
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.9 string 中 find 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
// 返回c在string中第一次出現(xiàn)的位置
size_t find(char c, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
// 返回子串s在string中第一次出現(xiàn)的位置
size_t find(const char* s, size_t pos = 0) const
{
assert(pos < _size);
char* ret = strstr(_str, s);
if (ret == nullptr)
{
return npos;
}
return ret - _str;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.10 string 中 insert 和 erase 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
// 注意:無(wú)符號(hào)整形比較是用補(bǔ)碼進(jìn)行比較
// 當(dāng)pos = 0,且 i = -1 時(shí) , 0并不比-1大
// 記得將_size位置上的'\0'也向后移動(dòng)
// 版本一 存在問(wèn)題
/*for (size_t i = _size; pos <= i; i--)
{
_str[i + 1] = _str[i];
}*/
// 版本二 將pos轉(zhuǎn)換為有符號(hào)整形進(jìn)行比較
/*for (int i = _size; (int)pos <= i; i--)
{
_str[i + 1] = _str[i];
}*/
// 版本三 將 i 置為_(kāi)size 的后面從后往前移動(dòng),防止了0與-1的比較
for (size_t i = _size + 1; pos < i; i--)
{
_str[i] = _str[i - 1];
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
for (size_t i = _size + len; pos < i; i--)
{
_str[i] = _str[i - len];
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len + pos > _capacity || len == npos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = len + pos;
while (begin <= _size)
{
_str[pos] = _str[begin];
pos++;
begin++;
}
_size -= len;
}
return *this;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.11 string 中 substr 的實(shí)現(xiàn)
#include <iostream>
#include <assert.h>
using namespace std;
namespace aj
{
class string
{
public:
string substr(size_t pos = 0, size_t len = npos) const
{
assert(pos < _size);
string tmp;
int end = pos + len;
if (pos + len > _size || len == npos)
{
len = _size - pos;
end = _size;
}
// 提前開(kāi)空間防止擴(kuò)容
tmp.reserve(len);
for (int i = pos; i < end; i++)
{
tmp += _str[i];
}
return tmp;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
};
4.12 string 實(shí)現(xiàn)匯總及函數(shù)測(cè)試
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;
#include <string>
namespace aj
{
class string
{
// friend ostream& operator<<(ostream& _cout, const aj::string& s);
// friend istream& operator>>(istream& _cin, aj::string& s);
public:
typedef char* iterator;
typedef const char* const_iterator;
public:
// 這里缺省值給""的原因是空字符串本身就帶一個(gè)'\0'
// 而不是初始化的時(shí)候_str為nullptr
// 初始化列表的順序應(yīng)該與聲明相同
string(const char* str = "")
:_capacity(strlen(str))
, _size(_capacity)
{
// 多開(kāi)一個(gè)空間用來(lái)存放'\0'
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 拷貝構(gòu)造函數(shù)傳統(tǒng)寫(xiě)法:
/*string(const string& s)
{
_capacity = s._capacity;
_str = new char[_capacity + 1];
_size = s._size;
strcpy(_str, s._str);
}*/
// swap函數(shù)
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
// 拷貝構(gòu)造函數(shù)現(xiàn)代寫(xiě)法:
string(const string& s)
: _str(nullptr)
, _capacity(0)
, _size(0)
{
string tmp(s._str);
swap(tmp);
}
// 賦值重載 傳統(tǒng)寫(xiě)法
/*string& operator=(const string& s)
{
if (this != &s)
{
reserve(s._capacity);
strcpy(_str, s._str);
_capacity = s._capacity;
_size = s._size;
}
return *this;
}*/
// 賦值重載 現(xiàn)代寫(xiě)法
/*string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s);
swap(tmp);
}
return *this;
}*/
// 賦值重載 現(xiàn)代寫(xiě)法
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
// 析構(gòu)函數(shù)
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
//
// iterator
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
/
// modify
void push_back(char c)
{
if (_size + 1 > _capacity)
{
// 這里不能盲目的開(kāi)二倍,因?yàn)閟tring可能是空字符串,
// _capacity = 0 , 那么這里的二倍就沒(méi)有意義,繼續(xù)下面的操作會(huì)報(bào)錯(cuò)
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = c;
_size++;
_str[_size] = '\0';
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
// 分為兩種情況
// (1) 追加后的字符串沒(méi)有超過(guò)_capacity
// (2) 追加后的字符串超過(guò)_capacity需要擴(kuò)容
void append(const char* str)
{
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
strcpy(_str + _size, str);
_size += len;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_capacity, s._capacity);
std::swap(_size, s._size);
}
const char* c_str()const
{
return _str;
}
/
// capacity
size_t size()const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
bool empty()const
{
return _size == 0;
}
// 分三種情況 (n為新字符串的長(zhǎng)度)
// (1)n <= _size
// (2) n > _size && n <= _capacity
// (3) n > _capacity
// 第一種情況為縮短,第二三種情況為增長(zhǎng),
// 但第二種情況不需要擴(kuò)容,第三種情況不需要
// 由于reserve內(nèi)部當(dāng)傳入的參數(shù)小于_capacity 時(shí)不會(huì)擴(kuò)容
// 所以將第二三種情況放在一起
void resize(size_t n, char c = '\0')
{
if (n <= _size)
{
_str[n] = '\0';
_size = n;
}
else
{
reserve(n);
_capacity = n;
while (_size < n)
{
_str[_size] = c;
_size++;
}
// 到這里 _size = n
_str[_size] = '\0';
}
}
void reserve(size_t n)
{
if (n > _capacity)
{
// _capacity 記錄的是需要存儲(chǔ)有效數(shù)據(jù)的個(gè)數(shù)
// 所以我們這里要多開(kāi)一個(gè)空間用來(lái)記錄'\0'
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
/
// access
char& operator[](size_t index)
{
return _str[index];
}
const char& operator[](size_t index)const
{
return _str[index];
}
/
//relational operators
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 0;
}
bool operator<=(const string& s)
{
return *this == s || *this < s;
}
bool operator>(const string& s)
{
return !(*this <= s);
}
bool operator>=(const string& s)
{
return !(*this < s);
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator!=(const string& s)
{
return !(*this == s);
}
// 返回c在string中第一次出現(xiàn)的位置
size_t find(char c, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
{
return i;
}
}
return npos;
}
// 返回子串s在string中第一次出現(xiàn)的位置
size_t find(const char* s, size_t pos = 0) const
{
assert(pos < _size);
char* ret = strstr(_str, s);
if (ret == nullptr)
{
return npos;
}
return ret - _str;
}
string substr(size_t pos = 0, size_t len = npos) const
{
assert(pos < _size);
string tmp;
int end = pos + len;
if (pos + len > _size || len == npos)
{
len = _size - pos;
end = _size;
}
// 提前開(kāi)空間防止擴(kuò)容
tmp.reserve(len);
for (int i = pos; i < end; i++)
{
tmp += _str[i];
}
return tmp;
}
string& insert(size_t pos, char c)
{
assert(pos <= _size);
if (_size + 1 > _capacity)
{
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
// 注意:無(wú)符號(hào)整形比較是用補(bǔ)碼進(jìn)行比較
// 當(dāng)pos = 0,且 i = -1 時(shí) , 0并不比-1大
// 記得將_size位置上的'\0'也向后移動(dòng)
// 版本一 存在問(wèn)題
/*for (size_t i = _size; pos <= i; i--)
{
_str[i + 1] = _str[i];
}*/
// 版本二 將pos轉(zhuǎn)換為有符號(hào)整形進(jìn)行比較
/*for (int i = _size; (int)pos <= i; i--)
{
_str[i + 1] = _str[i];
}*/
// 版本三 將 i 置為_(kāi)size 的后面從后往前移動(dòng),防止了0與-1的比較
for (size_t i = _size + 1; pos < i; i--)
{
_str[i] = _str[i - 1];
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
int len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len);
}
for (size_t i = _size + len; pos < i; i--)
{
_str[i] = _str[i - len];
}
strncpy(_str + pos, str, len);
_size+=len;
return *this;
}
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
if (len + pos > _capacity || len == npos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = len + pos;
while (begin <= _size)
{
_str[pos] = _str[begin];
pos++;
begin++;
}
_size -= len;
}
return *this;
}
private:
char* _str;
size_t _capacity;
size_t _size;
const static size_t npos;
};
const size_t string::npos = -1;
ostream& operator<<(ostream& _cout, const string& s)
{
for (auto ch : s)
{
cout << ch;
}
return _cout;
}
//istream& operator>>(istream& _cin, string& s)
//{
// // 流插入時(shí)需要將string中字符串清除
// s.clear();
// char ch = 0;
// ch = _cin.get();
// while (ch != ' ' && ch != '\n')
// {
// s += ch;
// ch = _cin.get();
// }
// return _cin;
//}
istream& operator>>(istream& _cin, string& s)
{
// 流插入時(shí)需要將string中字符串清除
s.clear();
char buff[129] = { 0 };
char ch = 0;
ch = _cin.get();
int i = 0;
while (ch != ' ' && ch != '\n')
{
buff[i] = ch;
ch = _cin.get();
if (i == 128)
{
s += buff;
i = 0;
}
i++;
}
if (i != 0)
{
s += buff;
}
return _cin;
}
// 測(cè)試c_str size capacity resize empty
void test_string1()
{
string s("chineseperson");
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s.empty() << endl;
cout << endl;
s.resize(20, 'c');
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
s.clear();
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
string s1;
cout << s1.c_str() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << s1.empty() << endl;
cout << endl;
}
// 測(cè)試 push_back append
void test_string2()
{
string s("chineseperson");
s.push_back('6');
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
s.append(" hellolllllllllll");
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
string s1;
s1.push_back('6');
cout << s1.c_str() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
}
// 測(cè)試 += operator[]
void test_string3()
{
string s("chineseperson");
s += '6';
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
s += " hellolllllllllll";
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
string s1;
s1 += '6';
cout << s1.c_str() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
s1[0]++;
cout << s1.c_str() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
}
// 測(cè)試 迭代器 和 范圍for
void test_string4()
{
string s("chineseperson");
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
for (auto ch : s)
{
cout <<ch << ' ';
}
}
// 測(cè)試流插入流提取
void test_string5()
{
string s;
cin >> s;
cout << s << endl;
}
// 測(cè)試拷貝構(gòu)造 , 賦值
void test_string6()
{
string s("chineseperson");
string s1(s);
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
cout << s1.c_str() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
string s2("hello");
cout << s2.c_str() << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
s2 = s;
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
}
// 測(cè)試string比較
void test_string7()
{
string s("chineseperson");
string s1(s);
s1[0]++;
cout << (s < s1) << endl;
cout << (s <= s1) << endl;
cout << (s > s1) << endl;
cout << (s >= s1) << endl;
cout << (s != s1) << endl;
cout << (s == s1) << endl;
}
// 測(cè)試 find
void test_string8()
{
string s("chineseperson");
cout << s.find('n') << endl;
cout << s.find('n', 10) << endl;
cout << s.find('a') << endl;
cout << endl;
cout << s.find("esepe") << endl;
}
// 測(cè)試insert erase
void test_string9()
{
string s("chineseperson");
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
s.insert(0, '6');
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
s.insert(s.size(), '6');
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
string s1(s);
s.insert(0, "hello ");
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
s.insert(s.size(), " Yeah Yeah Yeah !!");
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
string s2(s);
cout << s2.c_str() << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
s2.erase(0, 2);
cout << s2.c_str() << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
s2.erase(15);
cout << s2.c_str() << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
s2.erase(5);
cout << s2.c_str() << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
}
// 測(cè)試 substr
void test_string10()
{
string s("chineseperson");
cout << s.c_str() << endl;
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << endl;
string s1 = s.substr(2, 5);
cout << s1.c_str() << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
cout << endl;
string s2 = s.substr(2);
cout << s2.c_str() << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
cout << endl;
string tmp("https://legacy.cplusplus.com/reference/string/string/substr/");
int i1 = tmp.find(':', 0);
string ret1 = tmp.substr(0, i1);
cout << ret1.c_str() << endl;
int i2 = tmp.find('/', i1 + 3);
string ret2 = tmp.substr(i1 + 3, i2);
cout << ret2.c_str() << endl;
string ret3 = tmp.substr(i2);
cout << ret3.c_str() << endl;
}
};
結(jié)尾
如果有什么建議和疑問(wèn),或是有什么錯(cuò)誤,大家可以在評(píng)論區(qū)中提出。
希望大家以后也能和我一起進(jìn)步??!????
如果這篇文章對(duì)你有用的話(huà),希望大家給一個(gè)三連支持一下!!????文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-844539.html
到了這里,關(guān)于【C++】一篇文章帶你深入了解string的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!