『 C++入門到放棄 』- string

发布于:2025-07-07 ⋅ 阅读:(18) ⋅ 点赞:(0)

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;
  }
}


网站公告

今日签到

点亮在社区的每一天
去签到