C++ 學習筆記 - string
一、什麼是string ?
string 是 C++ 中標準函數庫中的一個類,其包含在 中
該類封裝了C語言中字符串操作,提供內存管理自動化與更多的操作
支持複製、比較、插入、刪除、查找等功能
二、常用接口整理
類別 | 常用方法 / 說明 |
---|---|
建立與指定 | std::string s = "hello"; |
長度 | s.size() 、s.length() |
存取 | s[0] 、s.at(0) (有邊界檢查) |
新增 | s.push_back('!') 、s.append(" world") |
插入 | s.insert(5, "123") |
刪除 | s.erase(3, 2) |
擷取 | s.substr(0, 5) |
查找 | s.find("abc") 、s.rfind("abc") |
清空 | s.clear() |
比較 | ==, !=, <, >, compare() |
C 字串轉換 | s.c_str() |
三、與 C-Style 字符串比較
項目 | C-style (char* ) |
C++ string (std::string ) |
---|---|---|
結尾 | 需手動補上 \0 |
自動管理 |
長度 | 需用 strlen() |
size() 即可 |
記憶體管理 | 自行分配與釋放 | 自動處理 |
易出錯 | 高(溢位、未終止) | 低(有邊界檢查) |
與 C API 相容 | 原生 | 透過 c_str() 支援 |
c_str()的使用範例
std::string s = "data.txt";
FILE* fp = fopen(s.c_str(), "r"); // 將 string 轉為 const char*
// c_str() 回傳 const char*,供 C 語言函數(如 fopen, printf, strcpy)使用
*注意事項
s[999]
→ 未檢查邊界會造成未定行為(undefined behavior)c_str()
回傳值不可修改erase()
或insert()
修改後會讓舊的指標失效(如 iterator 或c_str()
)
四、String 類模擬實現
1. 成員變量和 typedef
size_t _size; // 實際字串長度
size_t _capacity; // 分配空間大小
char* _str; // C-style 字串
typedef char* iterator;
typedef const char* const_iterator;
static size_t npos; // 用來表示無法找到的位置(類似 STL 的 string::npos)
// 靜態變量 類內聲明 類外定義
size_t string::npos = -1;
2. 構造、析構、拷貝
// 構造函數
string(const char *str = "") // 带参
{
_size = strlen(str);
_capacity = strlen(str);
_str = new char[_capacity + 1];
// strcpy(_str, str);
memcpy(_str, str, _size + 1);
/*
之所以使用memcpy而不是strcpy是因爲strcpy只會複製'/0'之前的的字符 如果字符串是一個沒有'/0'的字符串則會有越界的問題
*/
}
// 析構函數
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
// 拷貝構造函數
string(const string& s)
: _str(new char[strlen(s._str) + 1]) {
strcpy(_str, s._str);
}
3. 迭代器
// iterator
iterator begin()
{
return _str; // 返回首元素的地址
}
iterator end()
{
return _str + _size; // 返回首元素的地址 + 有多少個元素
}
// const_iterator
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
4. 內存管理
void reserve(size_t n) // 多少有效字符的空間 假設要10個有效字符會開11個空間
{
if (n > _capacity)
{
char *tmp = new char[n + 1];
// strcpy(tmp, _str);
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';
}
else
{
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
5. 增刪查改 ( 函數接口 )
void push_back(char ch)
{
if (_capacity == _size)
{
// 2倍擴容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
// ------------------------------------------------------------------------------------------
void append(const char *str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 至少擴容至 _size + len
reserve(_size + len);
}
// strcpy(_str + _size, str); // strcpy 會把字符串的'\0'一起拷貝過去
memcpy(_str + _size, str, len + 1);
_size += len;
}
// ------------------------------------------------------------------------------------------
// insert pos位置插入n個字元
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
// 至少擴容至 _size + n
if (_size + n > _capacity)
{
reserve(_size + n);
}
// 挪動數據
size_t end = _size; // end or pos 類型為 size_t 時 當 pos = 0 會發生問題
while (end >= pos && end != npos) /* 可以嘗試(int) 強轉pos類型 => 要轉是因爲 end 為int pos為size_t 時
會發生類型提升(提升至較大的類型 : size_t) 會陷入死循環
*/
{
_str[end + n] = _str[end];
--end; // size_t 不會小於 0 所以如果 end 類型為 size_t 會陷入死循環
}
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = ch;
}
_size += n;
}
// ------------------------------------------------------------------------------------------
// insert pos位置插入字符串
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;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
--end;
}
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
_size += len;
}
// 刪除
void erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
// _str[_size] = '\0';
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
// ------------------------------------------------------------------------------------------
void clear()
{
_str[0] = '\0';
_size = 0;
}
// 查
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
// ------------------------------------------------------------------------------------------
size_t find(const char *str, size_t pos = 0) const
{
assert(pos < _size);
const char *ptr = strstr(_str + pos, str);
if (ptr)
{
return ptr - _str;
}
else
{
return npos;
}
}
// ------------------------------------------------------------------------------------------
string substr(size_t pos = 0, size_t len = npos) const
{
assert(pos < _size);
size_t n = len;
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < n; ++i)
{
tmp += _str[i];
}
return tmp;
}
6. 運算符重載
char &operator[](size_t pos) // 重載運算符 讓string也可以像C char 一樣用下標訪問
{
assert(pos < _size);
return _str[pos];
}
const char &operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
// ------------------------------------------------------------------------------------------
string &operator+=(char ch)
{
push_back(ch);
return *this;
}
string &operator+=(const char *str)
{
append(str);
return *this;
}
// ------------------------------------------------------------------------------------------
string &operator=(string tmp)
{
swap(tmp);
return *this;
}
// swap函數
void swap(string &str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
// ------------------------------------------------------------------------------------------
bool operator<(const string &str) const
{
// return strcmp(this->_str, str._str) < 0;
size_t i1 = 0;
size_t i2 = 0;
while (i1 < _size && i2 < str._size)
{
if (_str[i1] < str._str[i2])
{
return true;
}
else if (_str[i1] > str._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
if (i1 == _size && i2 != str._size)
{
return true;
}
else
{
return false;
}
}
// ------------------------------------------------------------------------------------------
bool operator==(const string &s) const
{
return _size == s._size && memcmp(_str, s._str, _size) == 0;
}
// ------------------------------------------------------------------------------------------
bool operator<=(const string &s) const
{
return *this < s || *this == s;
}
// ------------------------------------------------------------------------------------------
bool operator>(const string &s) const
{
return !(*this <= s);
}
// ------------------------------------------------------------------------------------------
bool operator>=(const string &s) const
{
return !(*this < s);
}
// ------------------------------------------------------------------------------------------
bool operator!=(const string &s) const
{
return !(*this == s);
}
ostream &operator<<(ostream &out, const bit::string &s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
// ------------------------------------------------------------------------------------------
istream &operator>>(istream &in, bit::string &s)
{
char ch = in.get();
char buf[128];
int i = 0;
// 處理緩衝區之前的空格或換行
while (ch == ' ' || ch == '\0')
{
ch = in.get();
}
while (ch != ' ' && ch != '\0')
{
buf[i++] = ch;
if (i == 127)
{
buf[i] = '\0';
s += buf;
i = 0;
}
}
if (i != 0)
{
buf[i] = '\0';
s += buf;
}
return in;
}
}
7. 其他
char *c_str() const // C的string
{
return _str;
}
size_t size() //元素個數
{
return _size;
}
五、完整代碼
#pragma once
#include <iostream>
#include <string>
#include <assert.h>
// 因爲library中已有string 所以以命名空間做區分
using namespace std;
namespace bit
{
class string
{
public:
typedef char *iterator;
typedef const char *const_iterator;
// iterator
iterator begin()
{
return _str; // 返回首元素的地址
}
iterator end()
{
return _str + _size; // 返回首元素的地址 + 有多少個元素
}
// const_iterator
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
string(const char *str = "") // 带参
{
_size = strlen(str);
_capacity = strlen(str);
_str = new char[_capacity + 1];
memcpy(_str, str, _size + 1);
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
char *c_str() const // C的string
{
return _str;
}
size_t size()
{
return _size;
}
char &operator[](size_t pos) // 重載運算符 讓string也可以像C char 一樣用下標訪問
{
assert(pos < _size);
return _str[pos];
}
const char &operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n) // 多少有效字符的空間 假設要10個有效字符會開11個空間
{
if (n > _capacity)
{
char *tmp = new char[n + 1];
// strcpy(tmp, _str);
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';
}
else
{
reserve(n);
for (size_t i = _size; i < n; ++i)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
// 增刪查改
// 增
void push_back(char ch)
{
if (_capacity == _size)
{
// 2倍擴容
reserve(_capacity == 0 ? 4 : 2 * _capacity);
}
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
void append(const char *str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 至少擴容至 _size + len
reserve(_size + len);
}
// strcpy(_str + _size, str); // strcpy 會把字符串的'\0'一起拷貝過去
memcpy(_str + _size, str, len + 1);
_size += len;
}
string &operator+=(char ch)
{
push_back(ch);
return *this;
}
string &operator+=(const char *str)
{
append(str);
return *this;
}
void insert(size_t pos, size_t n, char ch)
{
assert(pos <= _size);
// 至少擴容至 _size + n
if (_size + n > _capacity)
{
reserve(_size + n);
}
// 挪動數據
size_t end = _size;
while (end >= pos && end != npos)
{
_str[end + n] = _str[end];
--end; // size_t 不會小於 0 所以如果 end 類型為 size_t 會陷入死循環
}
for (size_t i = 0; i < n; ++i)
{
_str[pos + i] = ch;
}
_size += n;
}
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;
while (end >= pos && end != npos)
{
_str[end + len] = _str[end];
--end;
}
for (size_t i = 0; i < len; ++i)
{
_str[pos + i] = str[i];
}
_size += len;
}
// 刪除
void erase(size_t pos = 0, size_t len = npos)
{
assert(pos <= _size);
if (len == npos || pos + len >= _size)
{
_str[pos] = '\0';
_size = pos;
// _str[_size] = '\0';
}
else
{
size_t end = pos + len;
while (end <= _size)
{
_str[pos++] = _str[end++];
}
_size -= len;
}
}
size_t find(char ch, size_t pos = 0) const
{
assert(pos < _size);
for (size_t i = pos; i < _size; ++i)
{
if (_str[i] == ch)
{
return i;
}
}
return npos;
}
size_t find(const char *str, size_t pos = 0) const
{
assert(pos < _size);
const char *ptr = strstr(_str + pos, str);
if (ptr)
{
return ptr - _str;
}
else
{
return npos;
}
}
string substr(size_t pos = 0, size_t len = npos) const
{
assert(pos < _size);
size_t n = len;
if (len == npos || pos + len > _size)
{
n = _size - pos;
}
string tmp;
tmp.reserve(n);
for (size_t i = pos; i < n; ++i)
{
tmp += _str[i];
}
return tmp;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
void swap(string &str)
{
std::swap(_str, str._str);
std::swap(_size, str._size);
std::swap(_capacity, str._capacity);
}
string &operator=(string tmp)
{
swap(tmp);
return *this;
}
bool operator<(const string &str) const
{
// return strcmp(this->_str, str._str) < 0;
size_t i1 = 0;
size_t i2 = 0;
while (i1 < _size && i2 < str._size)
{
if (_str[i1] < str._str[i2])
{
return true;
}
else if (_str[i1] > str._str[i2])
{
return false;
}
else
{
i1++;
i2++;
}
}
if (i1 == _size && i2 != str._size)
{
return true;
}
else
{
return false;
}
}
bool operator==(const string &s) const
{
return _size == s._size && memcmp(_str, s._str, _size) == 0;
}
bool operator<=(const string &s) const
{
return *this < s || *this == s;
}
bool operator>(const string &s) const
{
return !(*this <= s);
}
bool operator>=(const string &s) const
{
return !(*this < s);
}
bool operator!=(const string &s) const
{
return !(*this == s);
}
private:
size_t _size;
size_t _capacity;
char *_str;
static size_t npos; // 第二種insert pos = 0 死循環解決方法 : 設定 npos
};
// 靜態變量 類內聲明 類外定義
size_t string::npos = -1;
ostream &operator<<(ostream &out, const bit::string &s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
istream &operator>>(istream &in, bit::string &s)
{
char ch = in.get();
// in >> ch;
char buf[128];
int i = 0;
// 處理緩衝區之前的空格或換行
while (ch == ' ' || ch == '\0')
{
ch = in.get();
}
while (ch != ' ' && ch != '\0')
{
buf[i++] = ch;
if (i == 127)
{
buf[i] = '\0';
s += buf;
i = 0;
}
}
if (i != 0)
{
buf[i] = '\0';
s += buf;
}
return in;
}
}