目錄:
- 一.STL
- 二.string類
- 一、創建對象的6種構造方式
- 二、常用接口解析
- 1. 容量操作
- 2. 元素訪問
- 3. 修改操作
- 4. 字符串操作
- 三.string模擬實現
- 一、設計基礎:類結構與資源管理
- 二、拷貝控制:深拷貝的三種實現
- 1. 傳統深拷貝
- 2. 現代寫法(推薦)
- 三、關鍵功能實現
- 1. 容量管理
- 2. 訪問操作
- 3. 數據修改操作
- 4. 字符串操作
- 四、運算符重載與IO
- 1. 關系運算符
- 2. 流操作符
- 四.總結
一.STL
STL(standard template libaray-標準模板庫):是C++標準庫的重要組成部分,不僅是一個可復用的組件庫,而且是一個包羅數據結構與算法的軟件框架。
1. STL核心架構
STL(標準模板庫)由三大核心組件構成:
- 容器(數據結構):存儲數據的模板類(如vector/list/map)
- 算法:通用算法(如sort/find),可適配不同容器
- 迭代器:連接容器與算法的橋梁,支持統一遍歷方式
2. 重要版本對比
- SGI版:GCC采用,可讀性最佳,適合源碼學習
- P.J.版:VS采用,閉源但性能優異
- HP原始版:所有版本始祖,開源自由(沒錯就是你知道的那個的惠普)
3. 六大組件應用
在這里推薦一本書: 侯捷老師的《STL源碼剖析》,里面系統的講解STL的源碼
二.string類
一、創建對象的6種構造方式
構造方式 | 語法示例 | 功能描述 |
---|---|---|
默認構造 | std::string s1; | 創建空字符串(長度為0) |
拷貝構造 | std::string s2(s1); | 復制另一個字符串的內容 |
C字符串構造 | std::string s3("Hello"); | 用C風格字符串初始化(需以\0 結尾) |
子串構造 | std::string s4(s3, 1, 3); | 從s3 的第1個字符開始取3個字符("ell" ) |
部分構造 | std::string s5("Hello", 4); | 取C字符串的前4個字符("Hell" ) |
填充構造 | std::string s6(5, 'A'); | 生成5個重復字符("AAAAA" ) |
? 注意:
- 子串構造中
s4(s3, pos, len)
:
pos
超出范圍會拋out_of_range
異常,len
超長時自動截斷。- 所有構造支持鏈式操作:
std::string s = std::string("Hi") + " there!";
二、常用接口解析
1. 容量操作
接口 | 參數 | 返回值 | 功能 | 示例 |
---|---|---|---|---|
resize(len) | len :新長度 | void | 調整字符串長度(默認用\0 填充) | s.resize(3); // "Hel" |
resize(len, c) | c :填充字符 | void | 指定填充字符擴展/縮短 | s.resize(5, '!'); // "Hello!!" |
reserve(size) | size :預留容量 | void | 預分配內存避免多次擴容 | s.reserve(100); |
size() | 無 | size_t | 返回字符串的有效長度 | s.size(); |
capacity() | 無 | size_t | 返回空間總大小 | `s.capacity(); |
? 性能提示:
reserve()
減少動態分配次數,適合已知大小時提前調用,當reserve的參數小于string的底層空間總大小時,reserver不會改變容量大小。resize()
擴展時未指定字符則填充空字符(\0
),如果resize在改變元素個數時,如果是將元素個數增多,可能會改變底層容量的大小,如果是將元素個數減少,底層空間總大小不變`。
注意在vs底下reserve會可能多開一些空間,而Linux下你要多少它開多少
2. 元素訪問
接口 | 參數 | 返回值 | 行為說明 |
---|---|---|---|
operator[](i) | i :索引位置 | char& | 不檢查越界(訪問越界=未定義行為) |
at(i) (不常用) | i :索引位置 | char& | 檢查越界(越界拋out_of_range 異常) |
范圍for
- for循環后的括號由冒號“ :”分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍,自動迭代,自動取數據,自動判斷結束。
- 范圍for可以作用到數組和容器對象上進行遍歷
- 范圍for的底層很簡單,容器遍歷實際就是替換為迭代器,這個從匯編層也可以看到。
//范圍for 底層是迭代器,只能正序遍歷for (auto e : str2)//值拷貝{cout << e << " ";}cout << endl;for (auto& e : str2)//引用會修改原字符串{e++;cout << e << " ";}
auto關鍵字
作為一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期
推導而得
用auto聲明指針類型時,用auto和auto*沒有任何區別,但用auto聲明引用類型時則必須加&
當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際
只對第一個類型進行推導,然后用推導出來的類型定義其他變量。
auto不能作為函數的參數,可以做返回值,但是建議謹慎使用
auto不能直接用來聲明數組
typeid().name()可以查看數據的類型
迭代器
迭代器(Iterator)是C++ STL中至關重要的抽象機制,其意義遠超出簡單的遍歷容器元素。
- 迭代器為不同類型容器(如vector、list、map)提供了統一的元素訪問方式,隱藏了底層存儲結構的差異。
//迭代器
void func(const string& str)
{string::const_iterator it = str.begin();//從前向后打印,不可修改值while (it != str.end()){cout << *it << " ";++it;}cout << endl;//string::const_reverse_iterator itr = str.rbegin();//從后向前打印,且不可修改值auto itr = str.rbegin();while (itr != str.rend()){cout << *itr << " ";++itr;}}
int main()
{
string str1;
string str2("hello world");cout << endl;
string::iterator it = str2.begin();
while (it != str2.end())
{cout << *it << " ";++it;
}cout << endl;
it = str2.begin();
while (it != str2.end())
{(*it)-- ;cout << (*it) << " ";++it;
}cout << endl;
string::reverse_iterator it1 = str2.rbegin();
while (it1 != str2.rend())
{cout << *it1 << " ";++it1;
}cout << endl;
func(str2);
}
暫且先記住是這樣的,以后會講,而且string類不喜歡使用迭代器
it 可能是指針也可能不是
begin+ end: begin獲取一個字符的迭代器 + end獲取最后一個字符下一個位置的迭代器
rbegin + rend :begin獲取一個字符的迭代器 + end獲取最后一個字符下一個位置的迭代器
3. 修改操作
接口 | 參數 | 返回值 | 功能 |
---|---|---|---|
append(str) | str :追加的字符串 | string& | 尾部添加字符串 |
push_back(c) | c :單個字符 | void | 尾部添加字符(等價于+= ) |
insert(pos, str) | pos :插入位置,str :內容 | string& | 在pos 處插入字符串 |
erase(pos, len) | pos :起始位置,len :長度 | string& | 刪除子串(默認刪到結尾) |
operator+=(str) | str :追加內容 | string& | 追加字符串或字符(最常用) |
clear() | 無 | void | 清空內容(長度=0,容量不變) |
示例:
std::string s = "Hello";
s.append(" World"); // "Hello World"
s.insert(5, " C++"); // "Hello C++ World"
s.erase(5, 4); // "Hello World"
s += '!'; // "Hello World!"
避免循環中反復調用
str += ch
,改用reserve()
+ 循環更高效
頻繁拼接字符串時,優先用+=
或append()
而非+
(減少臨時對象)。
4. 字符串操作
接口 | 參數 | 返回值 | 功能 |
---|---|---|---|
find(str, pos) | str :查找內容,pos :起始位置 | size_t | 返回首次出現的位置(未找到返回npos ) |
substr(pos, len) | pos :起始位置,len :長度 | string | 提取子串(默認到結尾) |
c_str() | 無 | const char* | 返回C風格字符串(只讀) |
c_str()
返回的指針在字符串修改后可能失效,需立即使用。
查詢操作(如find
)時間復雜度為O(n),避免在大循環中調用。
示例:
std::string s = "Hello World";
size_t pos = s.find("World"); // pos = 6
std::string sub = s.substr(6, 5); // "World"
const char* cstr = s.c_str(); // 用于C接口函數
場景:從路徑中提取文件名(不含擴展名)
#include <iostream>
#include <string>int main() {std::string path = "/home/user/file.txt";// 1. 從末尾找到最后一個'.'size_t dot_pos = path.rfind('.');// 2. 從開頭到'.'之前截取std::filename = path.substr(0, dot_pos);// 3. 從末尾找到最后一個'/'size_t slash_pos = path.rfind('/');// 4. 從'/'后截取到'.'前std::string name = path.substr(slash_pos + 1, dot_pos - slash_pos - 1);std::cout << name; // 輸出 "file"
}
完整接口列表見C++ Reference(非官方)。
三.string模擬實現
一、設計基礎:類結構與資源管理
核心成員變量:
class string {
private:size_t _size; // 當前字符串長度size_t _capacity; // 當前內存容量char* _str; // 動態內存指針
};
構造與析構關鍵點:
// 默認構造(支持空字符串)
string(const char* str = "") {_size = strlen(str);//計算字符串的大小_capacity = _size;_str = new char[_capacity + 1]; // +1存儲'\0'memcpy(_str, str, _size + 1); // 高效內存拷貝
}// 析構函數
~string() {delete[] _str; // 釋放動態內存_str = nullptr;_size = _capacity = 0;
}
設計陷阱:若使用編譯器生成的默認拷貝構造,會導致淺拷貝問題——多個對象共享同一內存,析構時重復釋放。這也是為什么必須手動實現深拷貝。
二、拷貝控制:深拷貝的三種實現
1. 傳統深拷貝
// 拷貝構造
string(const string& str) {_str = new char[str._capacity + 1];memcpy(_str, str._str, str._size + 1);_size = str._size;_capacity = str._capacity;
}// 賦值重載
string& operator=(const string& s) {if(this != &s) { // 防止自賦值char* tmp = new char[s._capacity + 1];memcpy(tmp, s._str, s._size + 1);delete[] _str; // 釋放舊內存_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;
}
2. 現代寫法(推薦)
void swap(string& tmp) {std::swap(_str, tmp._str);std::swap(_size, tmp._size);std::swap(_capacity, tmp._capacity);
}// 拷貝構造
string(const string& str): _str(nullptr) {string tmp(str._str); // 調用構造函數string(const char* str),創建一個內容與str完全相同的臨時對象tmpswap(tmp); //調用成員函數swap()交換當前對象和臨時對象的內容 //交換后,當前對象獲得tmp的資源(即拷貝的數據)//tmp獲得當前對象初始時的空狀態(nullptr) //當函數結束時,臨時對象tmp離開作用域//調用tmp的析構函數,釋放其持有的資源(此時是nullptr,安全釋放)
}// 賦值重載
string& operator=(string tmp)
{ // 傳值產生臨時副本swap(tmp); // 交換資源return *this; // tmp離開作用域自動析構舊資源
}
方法對比:
實現方式 | 內存安全 | 異常安全 | 代碼簡潔度 |
---|---|---|---|
傳統深拷貝 | ? | ?? | 中等 |
現代寫法 | ? | ? | 高 |
現代寫法的核心優勢:異常安全與自賦值安全。傳參過程自動完成拷貝,避免手動檢查。
三、關鍵功能實現
1. 容量管理
size_t size()const
{return _size;//返回個數大小
}size_t capacity()const
{return _capacity;//返回容量大小
}// 擴容(不影響數據)
void reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1];memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n; // 更新容量}
}// 調整大小(影響數據)
void resize(size_t n, char ch = '\0') {if (n < _size) { // 縮小,刪數據_size = n;_str[_size] = '\0';//直接在_size位置截斷} else { // 擴大reserve(n); // 確保容量for (size_t i = _size; i < n; i++)_str[i] = ch; // 填充字符_size = n;_str[_size] = '\0';}
}
容量操作對比:
方法 | 功能 | 時間復雜度 | 是否影響數據 |
---|---|---|---|
reserve() | 預分配內存 | O(N) | ? |
resize() | 調整字符串長度 | O(N) | ? |
2. 訪問操作
//[]運算符重載
char& operator[](size_t pos)
{assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const//不可修改
{assert(pos < _size);return _str[pos];
}//迭代器
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{return _str;
}iterator end()
{return _str + _size;
}const_iterator begin()const
{return _str;
}const_iterator end()const
{return _str + _size;
}
我們自己實現的迭代器可以用原生指針完成,但是并不是所有的迭代器都是指針(雖然底層依舊還是指針)
這是vs底下string迭代器的類型,你會發現他用一個類封裝了(考慮到各種原因)
typeid().name()可以看到變量的類型
我們屏蔽掉end(),發現編譯器報錯“此基于范圍的"for“語句需要適合的"end"函數,但未找到”,間接證明范圍for容器遍歷實際就是替換為迭代器
3. 數據修改操作
// 尾部追加字符
void push_back(char c) {if (_size == _capacity)reserve(_capacity ? _capacity*2 : 4); // 指數擴容_str[_size++] = c;_str[_size] = '\0';
}void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}memcpy(_str + _size, str, len + 1);_size += len;}
// 插入字符串
void insert(size_t pos, const char* str) {assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity)reserve(_size + len); // 精確擴容// 移動現有字符(從后向前)size_t end = _size;//要注意end是size_t類型,當end = -1 ->42以多的數字恒大于pos//把end改為int也不行,關系運算符兩邊類型不同會出現整型提升while (end >= pos && end != npos)//nops是公共成員變量 = -1{_str[end + len] = _str[end];end--;}// 插入新內容memcpy(_str + pos, str, len); // 避免循環_size += len;
}// 刪除操作
void erase(size_t pos = 0, size_t len = npos) {assert(pos < _size);if (len == npos || pos + len >= _size) { _str[pos] = '\0'; // 截斷至pos處_size = pos;} else {memmove(_str + pos, _str + pos + len, _size - pos - len + 1);_size -= len;}
}string& operator+=(char ch)
{push_back(ch);return *this;
}string& operator+=(const char* str)
{append(str);return *this;
}
性能技巧:使用
memmove
而非循環移位,處理內存重疊更安全。
4. 字符串操作
// 查找字符
size_t find(char ch, size_t pos = 0) const {for (; pos < _size; pos++){if (_str[pos] == ch){ return pos;}} return npos;
}// 查找子串
size_t find(const char* s, size_t pos = 0) const {const char* ptr = strstr(_str + pos, s); // 庫函數優化return ptr ? ptr - _str : npos;
}// 獲取子串
string substr(size_t pos = 0, size_t len = npos) const {if (len == npos || pos + len > _size)len = _size - pos; // 自動調整長度string result;result.reserve(len);for (size_t i = 0; i < len; i++)result += _str[pos + i]; // 支持鏈式追加return result; // 返回值優化(RVO)
}
四、運算符重載與IO
1. 關系運算符
// 字典序比較
bool operator<(const string& str) const {size_t i = 0;while (i < _size && i < str._size) {if (_str[i] != str[i]) return _str[i] < str[i];i++;}return _size < str._size; // 前綴相同比長度
}// 基于<和==實現其他運算符
bool operator==(const string& str) const { return _size == str._size && memcmp(_str, str._str, _size) == 0;
}bool operator!=(const string& str) const { return !(*this == str);
}
// <=, >, >= 類似實現...
bool string::operator<=(const string& str)const
{return (*this < str) || (*this == str);
}bool string::operator>(const string& str)const
{return !(*this <= str);
}
bool string:: operator>=(const string& str)const
{return !(*this < str);
}
operator<()
“hello” “hello” --false
“hellox” “hello” – false
“hello” "hellox’ --true
2. 流操作符
// 輸出(支持含'\0'的字符串)ostream& operator<<(ostream& out, const string& str) {for (size_t i = 0; i < str.size(); i++) out << str[i]; // 避免c_str()的'\0'截斷return out;
}// 輸入(帶緩沖區優化)istream& operator>>(istream& in, string& str) {str.clear(); // 清空目標char buff[128]; // 減少擴容次數size_t i = 0;char ch = in.get();while (isspace(ch)) ch = in.get(); // 跳過空白while (!isspace(ch)) {buff[i++] = ch;if (i == 127) { // 緩沖區滿buff[i] = '\0';str += buff; // 批量追加i = 0; // 重置}ch = in.get();}if (i > 0) { // 處理剩余buff[i] = '\0';str += buff;}return in;
}
<<和>> 不要在類里重載,因為會有個隱藏的this指針占據第一個位置
輸入優化:緩沖區減少+=
操作次數,降低擴容開銷。
四.總結
通過系統的學習string類,我們學會了使用,在面對字符串或字符的時候就可以用string類,提高效率,并且通過完整的模擬實現可深入理解:動態內存管理、深/淺拷貝、操作符重載、迭代器設計等C++核心概念。需知我們模擬實現不是為了造一個更好的輪子,已經有人給我們造出來了,我們直接使用就行了,關鍵在于學習人家的思路和想發,以及了解底層是如何工作的。