Skip to content

std::string 详解

本篇把 STL 中关于字符串的考点补齐:string 存储模型、常用方法复杂度、手写 String,并在文末附 STL 分层总结与 tuple/variant/any 辨析(optional 见 optional.md)。

一、std::string

重点考点

  1. 存储模型(SSO 小字符串优化):多数实现(libstdc++/libc++/MSVC)对小字符串(通常 ≤15/22 字节)直接存在对象内部的 buffer 中,不堆分配;超过才用动态内存。这使小字符串拷贝/构造很快,是面试常问「string 是怎么存」的答案。
  2. 底层是一个 char* + 长度/容量,与 vector<char> 类似,连续存储(C++11 保证 c_str()/data() 连续且以 '\0' 结尾)。
  3. 常见复杂度:+/append/find 的复杂度说明(find 通常 O(n))。
  4. 高频题
    • 手写 String(拷贝构造/赋值/析构/operator== /operator+);
    • find vs rfind vs substr substr 底层;
    • reserve/resize 与容量。

手写简易 String(老八股)

cpp
#include <cstring>
#include <utility>

class String {
public:
    String() : buf_(nullptr), len_(0) {}
    String(const char* s)
        : len_(s ? std::strlen(s) : 0) {
        buf_ = new char[len_ + 1];
        std::memcpy(buf_, s ? s : "", len_ + 1);
    }
    // 拷贝构造
    String(const String& o) : String(o.buf_) {}
    // 拷贝赋值(copy-and-swap)
    String& operator=(String o) {
        std::swap(buf_, o.buf_);
        std::swap(len_, o.len_);
        return *this;
    }
    // 移动
    String(String&& o) noexcept : buf_(o.buf_), len_(o.len_) {
        o.buf_ = nullptr; o.len_ = 0;
    }
    ~String() { delete[] buf_; }

    size_t size() const { return len_; }
    const char* c_str() const { return buf_ ? buf_ : ""; }
    char& operator[](size_t i) { return buf_[i]; }
    const char& operator[](size_t i) const { return buf_[i]; }

    bool operator==(const String& o) const {
        return len_ == o.len_ && std::memcmp(c_str(), o.c_str(), len_) == 0;
    }
    String& operator+=(const String& o) {
        char* nb = new char[len_ + o.len_ + 1];
        std::memcpy(nb, c_str(), len_);
        std::memcpy(nb + len_, o.c_str(), o.len_);
        nb[len_ + o.len_] = '\0';
        delete[] buf_;
        buf_ = nb; len_ += o.len_;
        return *this;
    }
private:
    char* buf_;
    size_t len_;
};

要点new char[len+1] 要多留 1 字节放 '\0';拷贝赋值传值 + swap 天然异常安全;记得 delete[]。


二、std::optional(C++17)

已单独成篇,详见 optional.md(含「为什么需要、底层实现、值访问 API、手写思路」)。

一句话记忆:optional = 一个可能没值的 T,底层就地存储 + has_value 标志,不堆分配。

  • std::nullopt 表示空;value() 无值抛 bad_optional_accessvalue_or(fallback) 给默认。
  • 不支持 optional<T&>

三、std::tuple / std::variant / std::any(了解)

  • std::tuple(C++11):异构定长容器,std::get<i>std::tie、结构化绑定(C++17)。底层类似「成员依次排列」+ 空基类优化(EBCO)。
  • std::variant(C++17):类型安全的联合体(sum type),同一时刻只存一个类型,std::visit 访问。无指针风险,比裸 union 安全。
  • std::any(C++17):类型擦除的可存任意类型容器,底层常是虚函数表 + 堆对象。

三者区分

组件语义底层
tuple同时存多个不同类型的值嵌套结构体
variant同一时刻存「其中一种」类型判别式联合
any存「任意一种」类型(运行期才知道)类型擦除 + 虚表
optional存「有或没有」一个 T(详见 optional.md值 + bool

四、STL 分层总结记忆卡

  1. 序列容器选型:
    • 大多时候 → vector
    • 频繁中间插入删、不需要随机 → list
    • 频繁头尾操作 → deque
  2. 有序字典 → map(红黑树 O(logn),键有序)
  3. 无序字典/复杂键查字典 → unordered_map(哈希 O(1),需 hash+==)
  4. 去重/集合 → set / unordered_set
  5. 优先级 → priority_queue(堆)
  6. 可变长字符串 → string
  7. 键不可重复 / 需要多个值 → multimap / 或用 map<K, vector<V>>

面试追问「为什么不用 / 改用」,多基于以上底层差异作答。

C++ 面试八股 · VitePress 版