.
💓 博客主頁:倔強的石頭的CSDN主頁
📝Gitee主頁:倔強的石頭的gitee主頁
? 文章專欄:《C++指南》
期待您的關注
文章目錄
- 引言
- 一、成員變量與內存管理
- 1.1 核心成員變量
- 1.2 內存分配策略
- 二、默認成員函數的實現與優化
- 2.1 拷貝構造函數
- 2.2 賦值運算符重載
- 2.3 析構函數
- 三、迭代器與元素訪問
- 3.1 迭代器實現
- 3.2 運算符重載
- 四、容量管理
- 4.1 reserve:預分配內存
- 4.2 resize:調整字符串長度
- 五、修改操作
- 5.1 清空字符串:`clear`
- 5.2 push_back與append
- 5.3 insert與erase
- 六、其他關鍵函數實現
- 6.1 查找函數:`find`
- 查找字符
- 查找子串
- 6.2 子串生成:`substr`
- 6.3 流運算符重載
- 流插入(`operator<<`)
- 流提取(`operator>>`)
- 6.4 比較運算符重載
- 等于與不等于
- 大小比較
- 6.5 交換函數:`swap`
- 七、性能優化與注意事項
- 結語
引言
在前文中,我們深入探討了C++標準庫中
basic_string
的成員變量、默認成員函數及常用操作。
本文作為系列第三篇,將結合模擬實現的代碼,逐行解析basic_string
的底層原理,涵蓋構造函數、拷貝控制、容量管理、修改操作等核心功能的實現細節與優化技巧。
通過手寫一個簡化版string
類,幫助讀者徹底理解std::string
的內部工作機制。
一、成員變量與內存管理
1.1 核心成員變量
標準庫的basic_string
通過三個核心變量管理字符串:
- 字符指針
_str
:指向動態分配的字符數組。 - 當前長度
_size
:字符串有效字符個數(不含\0
)。 - 總容量
_capacity
:當前內存可容納的最大字符數(含\0
)。
模擬實現代碼:
namespace xc {
class string {
private:char* _str; // 字符存儲指針size_t _size; // 有效字符數size_t _capacity; // 總容量(含\0)
public:static const size_t npos = -1; // 特殊標記
};
}
1.2 內存分配策略
- 默認構造:初始化為空字符串(
_str
指向\0
)。 注意不能初始化為nullptr,否則調用c_str時,就會對空指針解引用 - 動態擴容:當
_size
達到_capacity
時,按2倍或需求大小擴容,避免頻繁內存分配。
構造函數實現:
// 默認構造(支持傳入C字符串)
string::string(const char* str) : _size(strlen(str)) {_str = new char[_size + 1]; // 多分配1字節存放\0strcpy(_str, str);_capacity = _size; // 初始容量等于長度
}
二、默認成員函數的實現與優化
2.1 拷貝構造函數
傳統寫法需要手動分配內存并拷貝數據,而現代C++寫法通過“構造臨時對象 + 交換資源”簡化代碼:
(關于swap函數的實現可跳轉6.5查找)
// 傳統寫法(易錯且冗余)
string::string(const string& s) {_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;
}// 現代寫法(利用臨時對象)
string::string(const string& s) {string tmp(s._str); // 調用構造函數swap(tmp); // 交換資源
}
2.2 賦值運算符重載
通過**“拷貝構造臨時對象 + 交換”**避免自賦值問題,同時減少重復代碼:
//傳統寫法string& 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;}
// 優化版賦值重載
string& string::operator=(const string& s) {if (this != &s) { // 防止自賦值string tmp(s); // 調用拷貝構造swap(tmp); // 交換資源}return *this;
}
2.3 析構函數
釋放動態內存并將成員變量歸零:
string::~string() {delete[] _str; // 釋放堆內存_size = 0;_capacity = 0;
}
三、迭代器與元素訪問
3.1 迭代器實現
模擬原生指針的行為,提供begin()
和end()
:
using iterator = char*;
iterator begin() { return _str; }
iterator end() { return _str + _size; }
3.2 運算符重載
通過operator[]
提供隨機訪問,并使用assert
檢查越界:
char& operator[](size_t i) {assert(i < _size); // 越界檢查return _str[i];
}
四、容量管理
4.1 reserve:預分配內存
若需求容量大于當前容量,重新分配內存并拷貝數據:
void string::reserve(size_t n) {if (n > _capacity) {char* tmp = new char[n + 1]; strcpy(tmp, _str);delete[] _str; // 釋放舊內存_str = tmp;_capacity = n; // 更新容量}
}
4.2 resize:調整字符串長度
根據新長度截斷或填充字符:
void string::resize(size_t n, char c) {if (n < _size) {_str[n] = '\0'; // 截斷_size = n;} else {reserve(n); // 確保容量足夠for (size_t i = _size; i < n; ++i) {_str[i] = c; // 填充字符}_size = n;_str[_size] = '\0';}
}
五、修改操作
5.1 清空字符串:clear
清空字符串內容但不釋放內存(保留容量):
void string::clear() {_str[0] = '\0'; // 首字符置為結束符_size = 0; // 長度歸零
}
5.2 push_back與append
- 尾插字符:檢查擴容后直接寫入:
void string::push_back(char c) {if (_size == _capacity) {reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = c;_str[_size] = '\0';
}
- 追加字符串:計算長度后擴容并拷貝:
void string::append(const char* str) {size_t len = strlen(str);if (_size + len > _capacity) {reserve(_size + len); // 按需擴容}strcpy(_str + _size, str); // 直接拷貝_size += len;
}
5.3 insert與erase
- 插入字符:移動后續字符騰出位置:
string& string::insert(size_t pos, char c) {assert(pos <= _size);if (_size == _capacity) reserve(2 * _capacity);size_t end = _size + 1;while (end > pos) { // 從后向前移動_str[end] = _str[end - 1];end--;}_str[pos] = c;_size++;return *this;
}
- 刪除字符:覆蓋后續字符并更新長度:
string& string::erase(size_t pos, size_t len) {assert(pos < _size);if (len == npos || len > _size - pos) {_str[pos] = '\0';_size = pos;} else {strcpy(_str + pos, _str + pos + len); // 覆蓋刪除區域_size -= len;}return *this;
}
六、其他關鍵函數實現
6.1 查找函數:find
查找字符
size_t string::find(char c, size_t pos) const {assert(pos < _size);for (size_t i = pos; i < _size; ++i) {if (_str[i] == c) return i;}return npos; // 未找到返回特殊標記
}
查找子串
利用標準庫的strstr
函數優化子串查找:
size_t string::find(const char* s, size_t pos) const {assert(pos < _size);const char* ptr = strstr(_str + pos, s); // 直接調用C庫函數return ptr ? ptr - _str : npos;
}
6.2 子串生成:substr
截取從pos
開始的len
個字符生成新字符串:
string string::substr(size_t pos, size_t len) const {assert(pos <= _size);len = (len == npos) ? _size - pos : len; // 默認取到末尾len = std::min(len, _size - pos); // 防止越界string result;result.reserve(len); // 預分配內存for (size_t i = 0; i < len; ++i) {result += _str[pos + i]; // 逐字符追加}return result;
}
6.3 流運算符重載
流插入(operator<<
)
直接遍歷輸出有效字符:
ostream& operator<<(ostream& os, const xc::string& s) {for (size_t i = 0; i < s.size(); ++i) {os << s[i]; // 支持鏈式調用}return os;
}
流提取(operator>>
)
優化版輸入,通過緩沖區減少擴容次數:
istream& operator>>(istream& is, xc::string& s) {s.clear(); // 清空原內容char buff[256]; // 局部緩沖區char ch;int idx = 0;while (is.get(ch) && !isspace(ch)) {buff[idx++] = ch;if (idx == 255) { // 緩沖區滿時批量追加buff[idx] = '\0';s += buff;idx = 0;}}if (idx > 0) { // 處理剩余字符buff[idx] = '\0';s += buff;}return is;
}
6.4 比較運算符重載
等于與不等于
bool string::operator==(const string& s) const {return strcmp(_str, s._str) == 0; // 直接比較C字符串
}bool string::operator!=(const string& s) const {return !(*this == s); // 復用等于運算符
}
大小比較
bool string::operator<(const string& s) const {return strcmp(_str, s._str) < 0; // 字典序比較
}bool string::operator<=(const string& s) const {return (*this < s) || (*this == s); // 組合邏輯
}bool string::operator>(const string& s) const {return !(*this <= s);
}bool string::operator>=(const string& s) const {return !(*this < s);
}
6.5 交換函數:swap
高效交換兩個字符串的資源(避免深拷貝):
void string::swap(string& s) {std::swap(_str, s._str); // 交換指針std::swap(_size, s._size); // 交換長度std::swap(_capacity, s._capacity); // 交換容量
}
七、性能優化與注意事項
-
substr
的優化:- 避免直接使用
new
和strcpy
,通過reserve
預分配內存減少擴容次數。 - 若需要高性能,可實現“淺拷貝+引用計數”(需處理寫時復制邏輯)。
- 避免直接使用
-
find
的局限性:- 當前實現為暴力匹配,標準庫可能使用更高效的算法(如KMP)。
-
流提取的安全性:
- 緩沖區大小固定為256,若輸入過長可能丟失數據,可動態調整緩沖區大小。
-
swap
的優勢:- 僅交換指針和元數據,時間復雜度為
O(1)
,適合頻繁交換場景。
- 僅交換指針和元數據,時間復雜度為
結語
通過手寫string
類,我們深入理解了basic_string
的底層機制。標準庫的實現在此基礎上進行了大量優化(如SSO、內存池),但核心邏輯與本文的模擬實現高度一致。掌握這些原理后,讀者可以更高效地使用std::string
,并能在需要時定制自己的字符串類。
相關閱讀
- 【C++指南】string(一):string從入門到掌握
- 【C++指南】string(二):深入探究 C++
basic_string
:成員變量、函數全解析
關注博主,第一時間獲取更新!