目錄
1 -> 為什么學(xué)習(xí)string類?
1.1 -> C語言中的字符串
2 -> 標(biāo)準(zhǔn)庫中的string類
2.1 -> string類
2.2 -> string類的常用接口
3 -> string類的模擬實(shí)現(xiàn)
3.1 -> 經(jīng)典的string類問題
3.2 -> 淺拷貝?
3.3 -> 深拷貝
3.3.1 -> 傳統(tǒng)寫法的string類
3.3.2 -> 現(xiàn)代寫法的string類
3.4 -> 寫時(shí)拷貝
3.5 -> string類的模擬實(shí)現(xiàn)
1 -> 為什么學(xué)習(xí)string類?
1.1 -> C語言中的字符串
在C語言中,字符串是以‘\0’結(jié)尾的一些字符的集合,為了操作方便,C標(biāo)準(zhǔn)庫中提供了一些str系列的庫函數(shù),但是這些庫函數(shù)與字符串時(shí)分離開的,不太符合OOP的思想,而且底層空間需要用戶自己管理,稍不留神可能還會越界訪問。
OOP ->??Object Oriented Programming(面向?qū)ο蟪绦蛟O(shè)計(jì))是一種計(jì)算機(jī)編程架構(gòu)。OOP 的一條基本原則是計(jì)算機(jī)程序是由單個(gè)能夠起到子程序作用的單元或?qū)ο蠼M合而成。
核心思想:封裝、繼承、多態(tài)。
使用OOP的好處:
- 易維護(hù)
- 質(zhì)量高
- 效率高
- 易擴(kuò)展
2 -> 標(biāo)準(zhǔn)庫中的string類
2.1 -> string類
- 字符串是表示字符序列的類;
- 標(biāo)準(zhǔn)的字符串類提供了對此類對象的支持,其接口類似于標(biāo)準(zhǔn)字符容器的接口,但添加了專門用于操作單字節(jié)字符字符串的設(shè)計(jì)特性;
- string類是使用char(即作為它的字符類型,使用它的默認(rèn)char_traits和分配器類型);
- string類是basic_string模板類的一個(gè)實(shí)例,它使用char來實(shí)例化basic_string模板類,并用char_traits和allocator作為basic_string的默認(rèn)參數(shù);
- 注意,這個(gè)類獨(dú)立于所使用的編碼來處理字節(jié):如果用來處理多字節(jié)或變長字符(如UTF-8)的序列,這個(gè)類的所有成員(如長度或大小)以及它的迭代器,將仍然按照字節(jié)(而不是實(shí)際編碼的字符)來操作。
總結(jié):
- string是表示字符串的字符串類;
- 該類的接口與常規(guī)容器的接口基本相同,再添加了一些專門用來操作string的常規(guī)操作;
- string在底層實(shí)際是:basic_string模板類的別名,typedef basic_string<char, char_traits, allocator>string;
- 不能操作多字節(jié)或者變長字符的序列。
在使用string類時(shí),必須包括#include頭文件以及using namespace std;
2.2 -> string類的常用接口
?
函數(shù)名稱 | 功能說明 |
string() |
構(gòu)造空的string類對象,即空字符串
|
string(const char* s) |
用C-string來構(gòu)造string類對象
|
string(size_t n, char c) |
string類對象中包含n個(gè)字符c
|
string(const string& s) |
拷貝構(gòu)造函數(shù)
|
#include <iostream>
using namespace std;
void Teststring()
{
string s1; // 構(gòu)造空的string類對象s1
string s2("hello bit"); // 用C格式字符串構(gòu)造string類對象s2
string s3(s2); // 拷貝構(gòu)造s3
}
int main()
{
return 0;
}
2. string類對象的容量操作
函數(shù)名稱 | 功能說明 |
size | 返回字符串有效字符長度 |
length | 返回字符串有效字符長度 |
capacity | 返回空間總大小 |
empty | 檢測字符串釋放為空串,是返回true,否則返回false |
clear | 清空有效字符 |
reserve | 為字符串預(yù)留空間 |
resize | 將有效字符的個(gè)數(shù)改成n個(gè),多出空間用字符c填充 |
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
// 測試string容量相關(guān)的接口
// size/clear/resize
void Teststring1()
{
// 注意:string類對象支持直接用cin和cout進(jìn)行輸入和輸出
string s("hello, world");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 將s中的字符串清空,注意清空時(shí)只是將size清0,不改變底層空間的大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
// 將s中有效字符個(gè)數(shù)增加到10個(gè),多出位置用'a'進(jìn)行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;
cout << s.capacity() << endl;
// 將s中有效字符個(gè)數(shù)增加到15個(gè),多出位置用缺省值'\0'進(jìn)行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此時(shí)s中有效字符個(gè)數(shù)已經(jīng)增加到15個(gè)
s.resize(15);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
// 將s中有效字符個(gè)數(shù)縮小到5個(gè)
s.resize(5);
cout << s.size() << endl;
cout << s.capacity() << endl;
cout << s << endl;
}
//====================================================================================
void Teststring2()
{
string s;
// 測試reserve是否會改變string中有效元素個(gè)數(shù)
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
// 測試reserve參數(shù)小于string的底層空間大小時(shí),是否會將空間縮小
s.reserve(50);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
// 利用reserve提高插入數(shù)據(jù)的效率,避免增容帶來的開銷
//====================================================================================
void TestPushBack()
{
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
// 構(gòu)建vector時(shí),如果提前已經(jīng)知道string中大概要放多少個(gè)元素,可以提前將string中空間設(shè)置好
void TestPushBackReserve()
{
string s;
s.reserve(100);
size_t sz = s.capacity();
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
if (sz != s.capacity())
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
int main()
{
return 0;
}
注意:
- size()與length()方法底層實(shí)現(xiàn)原理完全相同,引入size()的原因是為了與其他容器的接口保持一致,一般情況下基本都是用size();
- clear()只是將string中有效字符清空,不改變底層空間大?。?/span>
- resize(size_t n)與resize(size_t n, char c)都是將字符串中有效字符個(gè)數(shù)改變到n個(gè),不同的是當(dāng)字符個(gè)數(shù)增多時(shí):resize(n)用0來填充多出的元素空間,resize(size_t n, char c)用字符c來填充多出的元素空間。注意:resize在改變元素個(gè)數(shù)時(shí),如果是將元素個(gè)數(shù)增多,可能會改變底層容量的大小,如果是將元素個(gè)數(shù)減少,底層空間總大小不變;
- reserve(size_t res_arg = 0):為string預(yù)留空間,不改變有效元素個(gè)數(shù),當(dāng)reserve的參數(shù)小于string的底層空間總大小時(shí),reserve不會改變?nèi)萘看笮 ?/span>
3. string類對象的訪問及遍歷操作?
?函數(shù)名稱 | 功能說明 |
operator[] | 返回pos位置的字符,const string類對象調(diào)用 |
begin + end | huoqbegin獲取一個(gè)字符的迭代器 + end獲取最后一個(gè)字符下一個(gè)位置的迭代器 |
rbegin + rend | huoqbegin獲取一個(gè)字符的迭代器 + end獲取最后一個(gè)字符下一個(gè)位置的迭代器 |
范圍for | C++11支持更簡潔的范圍for的新遍歷方式 |
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
// string的遍歷
// begin()+end() for+[] 范圍for
// 注意:string遍歷時(shí)使用最多的還是for+下標(biāo) 或者 范圍for(C++11后才支持)
// begin()+end()大多數(shù)使用在需要使用STL提供的算法操作string時(shí),比如:采用reverse逆置string
void Teststring3()
{
string s1("hello world");
const string s2("Hello world");
cout << s1 << " " << s2 << endl;
cout << s1[0] << " " << s2[0] << endl;
s1[0] = 'H';
cout << s1 << endl;
// s2[0] = 'h'; 代碼編譯失敗,因?yàn)閏onst類型對象不能修改
}
void Teststring4()
{
string s("hello world");
// 3種遍歷方式:
// 需要注意的以下三種方式除了遍歷string對象,還可以遍歷是修改string中的字符,
// 另外以下三種方式對于string而言,第一種使用最多
// 1. for+operator[]
for (size_t i = 0; i < s.size(); ++i)
cout << s[i] << endl;
// 2.迭代器
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << endl;
++it;
}
// string::reverse_iterator rit = s.rbegin();
// C++11之后,直接使用auto定義迭代器,讓編譯器推到迭代器的類型
auto rit = s.rbegin();
while (rit != s.rend())
cout << *rit << endl;
// 3.范圍for
for (auto ch : s)
cout << ch << endl;
}
int main()
{
return 0;
}
4. string類對象的修改操作
函數(shù)名稱 | 功能說明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一個(gè)字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回c格式字符串 |
find + npos | 從字符串pos位置開始往后找字符c,返回該字符在字符串中的位置 |
rfind | 從字符串pos位置開始往前找字符c,返回該字符在字符串中的位置 |
substr | 在str中從pos位置開始,截取n個(gè)字符,然后將其返回 |
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
// 測試string:
// 1. 插入(拼接)方式:push_back append operator+=
// 2. 正向和反向查找:find() + rfind()
// 3. 截取子串:substr()
// 4. 刪除:erase
void Teststring5()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一個(gè)字符"hello"
str += 'b'; // 在str后追加一個(gè)字符'b'
str += "it"; // 在str后追加一個(gè)字符串"it"
cout << str << endl;
cout << str.c_str() << endl; // 以C語言的方式打印字符串
// 獲取file的后綴
string file("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size() - pos));
cout << suffix << endl;
// npos是string里面的一個(gè)靜態(tài)成員變量
// static const size_t npos = -1;
// 取出url中的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t start = url.find("://");
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
start += 3;
size_t finish = url.find('/', start);
string address = url.substr(start, finish - start);
cout << address << endl;
// 刪除url的協(xié)議前綴
pos = url.find("://");
url.erase(0, pos + 3);
cout << url << endl;
}
int main()
{
return 0;
}
注意:
- 在string尾部追加字符時(shí),s.push_back(c) / s.append(1, c) / s += 'c'三種的實(shí)現(xiàn)方式差不多,一般情況下string類的+=操作用的比較多,+=操作不僅可以連接單個(gè)字符,還可以連接字符串;
- 對sting操作時(shí),如果能夠大概預(yù)估到放多少字符,可以先通過reserve把空間預(yù)留好。
5. string類非成員函數(shù)
函數(shù) | 功能說明 |
operator+ | 盡量少用,因?yàn)閭髦捣祷?,?dǎo)致深拷貝效率低 |
operator>> | 輸入運(yùn)算符重載 |
operator<< | 輸出運(yùn)算符重載 |
getline | 獲取一行字符串 |
relational operators | 大小比較 |
6. vs和g++下string結(jié)構(gòu)的說明
注意:下述結(jié)構(gòu)是在32位平臺下進(jìn)行,32位平臺下指針占4個(gè)字節(jié)。
- vs下string的結(jié)構(gòu)
string總共占28個(gè)字節(jié),內(nèi)部結(jié)構(gòu)稍微復(fù)雜一點(diǎn),先是有一個(gè)聯(lián)合體,聯(lián)合體用來定義string中字符串的存儲空間:
- 當(dāng)字符串長度小于16時(shí),使用內(nèi)部固定的字符數(shù)組來存放;
- 當(dāng)字符串長度大于等于16時(shí),從堆上開辟空間。
union _Bxty
{ // storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
這種設(shè)計(jì)也是有一定道理的,大多數(shù)情況下字符串的長度都小于16,那string對象創(chuàng)建好后,內(nèi)部已經(jīng)有了16個(gè)字符數(shù)組的固定空間,不需要通過堆創(chuàng)建,效率高。
其次:還有一個(gè)size_t字段保存字符串長度,一個(gè)size_t字段保存從堆上開辟空間總的容量。
最后:還有一個(gè)指針做一些其他事情。
故總共占16 + 4 + 4 + 4 = 28個(gè)字節(jié)。
- g++下string的結(jié)構(gòu)
g++下,string是通過寫時(shí)拷貝實(shí)現(xiàn)的,string對象總共占4個(gè)字節(jié),內(nèi)部只包含了一個(gè)指針,該指針將來指向一塊堆空間,內(nèi)部包含了如下字段:
- 空間總大小
- 字符串有效長度
- 引用計(jì)數(shù)
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
- 指向堆空間的指針,用來存儲字符串
3 -> string類的模擬實(shí)現(xiàn)
3.1 -> 經(jīng)典的string類問題
模擬實(shí)現(xiàn)string類,最主要是實(shí)現(xiàn)string類的構(gòu)造、拷貝構(gòu)造、賦值運(yùn)算符重載以及析構(gòu)函數(shù)。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class String
{
public:
String(const char* str = "")
{
// 構(gòu)造String類對象時(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;
};
void TestString()
{
String s1("hello world");
String s2(s1);
}
?
3.2 -> 淺拷貝?
淺拷貝:
也稱位拷貝,編譯器只是將對象中的值拷貝過來。如果對象中管理資源,最后就會導(dǎo)致多個(gè)對象共享同一份資源,當(dāng)一個(gè)對象銷毀時(shí)就會將該資源釋放,而此時(shí)另一些對象不知道該資源已經(jīng)被釋放,以為還有效,所以當(dāng)繼續(xù)對資源進(jìn)項(xiàng)操作時(shí),就會發(fā)生訪問違規(guī)。
可以采用深拷貝解決淺拷貝問題,即:每個(gè)對象都有一份獨(dú)立的資源,不要和其他對象共享。
3.3 -> 深拷貝
如果一個(gè)類中涉及到資源的管理,其拷貝構(gòu)造函數(shù),賦值運(yùn)算符重載以及構(gòu)造函數(shù)必須要顯式給出。一般情況都是按照深拷貝方式提供。
3.3.1 -> 傳統(tǒng)寫法的string類
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
class String
{
public:
String(const char* str = "")
{
// 構(gòu)造String類對象時(shí),如果傳遞nullptr指針,可以認(rèn)為程序非
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;
};
3.3.2 -> 現(xiàn)代寫法的string類
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cassert>
using namespace std;
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(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;
};
3.4 -> 寫時(shí)拷貝
寫時(shí)拷貝就是一種拖延癥,實(shí)在淺拷貝的基礎(chǔ)之上增加了引用計(jì)數(shù)的方式來實(shí)現(xiàn)的。
引用計(jì)數(shù):用來記錄資源使用者的個(gè)數(shù)。在構(gòu)造時(shí),將資源的計(jì)數(shù)給成1,每增加一個(gè)對象使用該資源,就給計(jì)數(shù)增加1,當(dāng)某個(gè)對象被銷毀時(shí),先給該計(jì)數(shù)減1,然后再檢查是否需要釋放資源,如果計(jì)數(shù)為1,說明該對象是資源的最后一個(gè)使用者,將該資源釋放;否則就不能釋放,因?yàn)檫€有其他對象在使用該資源。
3.5 -> string類的模擬實(shí)現(xiàn)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <assert.h>
using namespace std;
namespace fyd
{
class string
{
public:
typedef char* iterator;
public:
string(const char* str = "")
{
_size = strlen(str);
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
string(const string& s)
: _str(nullptr)
, _size(0)
, _capacity(0)
{
string tmp(s._str);
this->swap(tmp);
}
string& operator=(string s)
{
this->swap(s);
return *this;
}
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;
}
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
void push_back(char c)
{
if (_size == _capacity)
reserve(_capacity * 2);
_str[_size++] = c;
_str[_size] = '\0';
}
string& operator+=(char c)
{
push_back(c);
return *this;
}
void append(const char* str)
{
size_t 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()
{
_size = 0;
_str[_size] = '\0';
}
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
const char* c_str()const
{
return _str;
}
size_t size()const
{
return _size;
}
size_t capacity()const
{
return _capacity;
}
bool empty()const
{
return 0 == _size;
}
void resize(size_t newSize, char c = '\0')
{
if (newSize > _size)
{
// 如果newSize大于底層空間大小,則需要重新開辟空間
if (newSize > _capacity)
{
reserve(newSize);
}
memset(_str + _size, c, newSize - _size);
}
_size = newSize;
_str[newSize] = '\0';
}
void reserve(size_t newCapacity)
{
// 如果新容量大于舊容量,則開辟空間
if (newCapacity > _capacity)
{
char* str = new char[newCapacity + 1];
strcpy(str, _str);
// 釋放原來舊空間,然后使用新空間
delete[] _str;
_str = str;
_capacity = newCapacity;
}
}
// access
char& operator[](size_t index)
{
assert(index < _size);
return _str[index];
}
const char& operator[](size_t index)const
{
assert(index < _size);
return _str[index];
}
bool operator<(const string& s)
{
return strcmp(_str, s._str) < 0;
}
bool operator==(const string& s)
{
return strcmp(_str, s._str) == 0;
}
bool operator<=(const string& s)
{
return ((_str < s._str) || (_str == s._str));
}
bool operator>(const string& s)
{
return !((_str < s._str) || (_str == s._str));
}
bool operator>=(const string& s)
{
return !(strcmp(_str, s._str) < 0);
}
bool operator!=(const string& s)
{
return !(strcmp(_str, s._str) == 0);
}
// 返回c在string中第一次出現(xiàn)的位置
size_t find(char c, size_t pos = 0) const
{
for (size_t i = pos; i < _size; i++)
{
if (_str[i] == c)
return i;
}
return -1;
}
// 返回子串s在string中第一次出現(xiàn)的位置
size_t find(const char* s, size_t pos = 0) const
{
const char* ret = strstr(_str + pos, s);
if (ret)
return ret - _str;
return -1;
}
// 在pos位置上插入字符c/字符串str,并返回該字符的位置
string& insert(size_t pos, char c)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
char* end = _str + _size;
while (end > _str + pos)
{
*(end + 1) = *end;
--end;
}
_str[pos] = c;
_size++;
return *this;
}
string& insert(size_t pos, const char* str)
{
size_t len = strlen(str);
if ((_size + len) > _capacity)
{
reserve(_size + len);
}
char* end = _str + _size;
while (end > (_str + pos))
{
*(end + len) = *end;
--end;
}
strncpy(_str + pos, str, len);
_size += len;
return *this;
}
// 刪除pos位置上的元素,并返回該元素的下一個(gè)位置
string& erase(size_t pos, size_t len)
{
size_t leftNum = _size - pos;
if (len >= leftNum)
{
_str[pos] = '\0';
_size = pos;
}
else
{
strcpy(_str + pos, _str + pos + len);
_size -= len;
}
return *this;
}
private:
friend ostream& operator<<(ostream& _cout, const fyd::string& s);
friend istream& operator>>(istream& _cin, fyd::string& s);
private:
char* _str;
size_t _capacity;
size_t _size;
};
ostream& operator<<(ostream& _cout, const fyd::string& s)
{
for (size_t i = 0; i < s.size(); ++i)
{
_cout << s[i];
}
return _cout;
}
istream& operator>>(istream& _cin, string& s)
{
s.clear();
char ch;
ch = _cin.get();
while (ch != ' ' && ch != '\n')
{
s += ch;
ch = _cin.get();
}
return _cin;
}
}
///對自定義的string類進(jìn)行測試
void TestString()
{
fyd::string s1("hello");
s1.push_back(' ');
s1.push_back('w');
s1.push_back('o');
s1.push_back('r');
s1.push_back('l');
s1 += 'd';
cout << s1 << endl;
cout << s1.size() << endl;
cout << s1.capacity() << endl;
// 利用迭代器打印string中的元素
fyd::string::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it;
++it;
}
cout << endl;
// 這里可以看到一個(gè)類只要支持的基本的iterator,就支持范圍for
for (auto ch : s1)
cout << ch;
cout << endl;
}
int main()
{
TestString();
return 0;
}
感謝大佬們的支持?。?!文章來源:http://www.zghlxwxcb.cn/news/detail-831848.html
互三啦!?。?/strong>文章來源地址http://www.zghlxwxcb.cn/news/detail-831848.html
到了這里,關(guān)于【C++航海王:追尋羅杰的編程之路】string類的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!