C++ 標準庫中的 std::string 提供了豐富的字符串操作功能,底層封裝了動態內存管理、容量擴展、拷貝控制等核心機制。為了深入理解其運作原理,本文將手把手帶你從零開始模擬實現一個簡易的 String 類,涵蓋構造/析構、賦值運算符、下標訪問、尾部追加、插入操作等核心功能。
模擬 std::string 的第一步是確定核心數據成員。我們需要一個字符指針 _str 指向動態分配的字符數組,以及兩個整型成員 _size(當前有效字符長度)和 _capacity(當前分配的總容量,含結尾的 \0)。
#include <iostream>
#include <cstring>
using namespace std;
class String {
friend ostream& operator<<(ostream& os, const String& s);
public:
// 構造函數:支持默認構造和 C 風格字符串構造
String(const char* str = "")
: _size(strlen(str))
, _capacity(strlen(str) + 1)
, _str(new char[_capacity]) {
strcpy(_str, str);
cout << "String(const char* str)" << endl;
}
// 拷貝構造函數
String(const String& s)
: _size(s._size)
, _capacity(s._capacity)
, _str(new char[_capacity]) {
strcpy(_str, s._str);
cout << "String(const String&)" << endl;
}
// 析構函數
~String() {
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
cout << "~String()" << endl;
}
// 運算符重載聲明
String& operator=(const String& s);
char& operator[](size_t index);
const char& operator[](size_t index) const;
// 核心功能函數
void PushBack(char c);
String& operator+=(const String& str);
String& operator+=(const char* s);
String& operator+=(char c);
// insert 多載族
String& insert(size_t pos, const String& str);
String& insert(size_t pos, const String& str, size_t subPos, size_t n);
String& insert(size_t pos, const char* s, size_t n);
String& insert(size_t pos, const char* s);
String& insert(size_t pos, size_t n, char c);
private:
// 容量檢查與擴容(私有,內部自動調用)
void CheckCapacity(size_t count);
char* _str; // 動態字符數組
size_t _size; // 有效字符長度
size_t _capacity;// 總容量
};
CheckCapacity 是所有修改性操作的基石。當現有容量不足以容納新增字符時,按 2 倍策略或按需分配更大空間,並將舊數據拷貝至新內存。
void String::CheckCapacity(size_t count) {
if (_size + count >= _capacity) {
size_t newCapacity = max(2 * _capacity, _capacity + count);
char* tmp = new char[newCapacity];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = newCapacity;
}
}
賦值運算符必須處理自我賦值、釋放舊內存、深拷貝新數據。這裡展示兩種經典實現:
String& String::operator=(const String& s) {
if (this != &s) { // 防止自我賦值
delete[] _str;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
利用拷貝構造函數創建臨時對象,再交換內部指針,代碼簡潔且異常安全。
String& String::operator=(String s) { // 參數按值傳遞,自動調用拷貝構造
swap(_str, s._str);
swap(_size, s._size);
swap(_capacity, s._capacity);
return *this; // 臨時對象 s 析構時釋放舊內存
}
operator[]返回字符引用,支持讀寫。注意區分 const 與非 const 版本。
char& String::operator[](size_t index) {
return _str[index];
}
const char& String::operator[](size_t index) const {
return _str[index];
}
PushBack 與 operator+=PushBack 單字符追加,內部調用 CheckCapacity(1) 確保空間。operator+= 提供三種重載,分別處理 String、C 字符串、單字符,核心邏輯均為:擴容 → strcat 或直接賦值 → 更新 _size → 補 \0。
void String::PushBack(char c) {
CheckCapacity(1);
_str[_size++] = c;
_str[_size] = '\0';
}
String& String::operator+=(const String& str) {
CheckCapacity(str._size);
strcat(_str, str._str);
_size += str._size;
return *this;
}
String& String::operator+=(const char* s) {
size_t len = strlen(s);
CheckCapacity(len);
strcat(_str, s);
_size += len;
return *this;
}
String& String::operator+=(char c) {
CheckCapacity(1);
_str[_size++] = c;
_str[_size] = '\0';
return *this;
}
insert 族函數詳解insert 的核心難點在於「在指定位置騰出空間並拼接字符串」。所有重載統一採用「臨時緩衝區保存尾部 → 截斷原字符串 → 追加新內容 → 拼回尾部」的策略。
String 對象String& String::insert(size_t pos, const String& str) {
if (pos > _size) return *this; // 位置非法直接返回
CheckCapacity(str._size);
char* tmp = new char[_size - pos + 1];
strcpy(tmp, _str + pos); // 保存 pos 及之後的內容
_str[pos] = '\0'; // 截斷
strcat(_str, str._str); // 追加新串
strcat(_str, tmp); // 拼回尾部
_size += str._size;
_str[_size] = '\0';
delete[] tmp;
return *this;
}
String 的子串String& String::insert(size_t pos, const String& str, size_t subPos, size_t n) {
if (pos > _size || subPos > str._size || n > str._size - subPos) return *this;
CheckCapacity(n);
char* tmp = new char[_size - pos + 1];
strcpy(tmp, _str + pos);
_str[pos] = '\0';
strncat(_str, str._str + subPos, n);
strcat(_str, tmp);
_size += n;
_str[_size] = '\0';
delete[] tmp;
return *this;
}
String& String::insert(size_t pos, const char* s, size_t n) {
if (pos > _size) return *this;
CheckCapacity(n);
char* tmp = new char[_size - pos + 1];
strcpy(tmp, _str + pos);
_str[pos] = '\0';
strncat(_str, s, n);
strcat(_str, tmp);
_size += n;
_str[_size] = '\0';
delete[] tmp;
return *this;
}
String& String::insert(size_t pos, const char* s) {
return insert(pos, s, strlen(s)); // 復用上一個重載
}
String& String::insert(size_t pos, size_t n, char c) {
if (pos > _size) return *this;
CheckCapacity(n);
char* tmp = new char[_size - pos + 1];
strcpy(tmp, _str + pos);
_str[pos] = '\0';
for (size_t i = 0; i < n; ++i) _str[_size + i] = c;
_size += n;
_str[_size] = '\0';
strcat(_str, tmp);
delete[] tmp;
return *this;
}
ostream& operator<<(ostream& os, const String& s) {
os << s._str;
return os;
}
const 與非 const 重載。insert 族函數盡量相互調用,減少重複邏輯。通過這個模擬實現,你應該能清晰掌握 std::string 底層的動態內存管理、容量增長、拷貝控制語義以及各類修改操作的核心邏輯。實際工程中請直接使用標準庫 std::string,它經過高度優化並處理了大量邊界情況。