跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ string 使用详解与底层模拟实现

C++ string 类是标准库核心组件。本文详解常用接口如构造、容量管理、遍历及修改操作,涵盖 auto 关键字与范围 for 循环的应用。重点剖析 string 底层模拟实现,包括浅拷贝与深拷贝的区别、析构函数资源释放、运算符重载及内存管理策略。通过手写代码深入理解 C++ 对象生命周期与资源安全机制。

芝士奶盖发布于 2026/3/28更新于 2026/10/2497 浏览
C++ string 使用详解与底层模拟实现

C++ string 的使用与模拟实现

一、标准库中的 string 类

string 是 C++ 中最常用的类之一,但很多人对其底层实现并不熟悉。本文将带你掌握核心用法,并深入模拟实现其关键功能,理解深浅拷贝等概念。

1.1 auto 和范围 for

在深入学习之前,先回顾两个 C++11 的小语法,这对后续遍历容器很有帮助。

auto 关键字 C++11 中,auto 不再仅仅是存储类型指示符,而是作为新的类型指示符,让编译器在编译期推导变量类型。

int main() {
    int a = 10;
    auto b = a;
    auto c = 'a';
    return 0;
}

用 auto 声明指针时,auto 和 auto* 没有区别;但声明引用时必须加 &:

int main() {
    int x = 10;
    auto y = &x;      // 指针
    auto* z = &x;     // 指针
    auto& m = x;      // 引用
    return 0;
}

注意:同一行声明多个变量时,它们必须是相同类型,否则编译器报错。auto 不能直接用来声明数组,也不能作为函数参数(返回值需谨慎)。

范围 for 循环 C++11 引入了基于范围的 for 循环,简化了集合遍历。其底层本质是迭代器的封装。

迭代器简单解释: 迭代器在行为上类似指针,用于统一遍历各种数据结构,是 STL 的基石。

#include <iostream>
#include <string>
using namespace std;

int main() {
    int array[] = {1, 2, 3, 4, 5};
    
    // C++98 遍历
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) {
        array[i] *= 2;
    }
    
    // C++11 遍历
    for (auto& e : array) e *= 2;
    for (auto e : array) cout << e << " ";
    cout << endl;

    string str("hello world");
    for (char ch : str) {
        cout << ch << " ";
    }
    cout << endl;
    return 0;
}

1.2 string 类的常用接口说明

string 类接口众多,这里只讲解最常用的部分。

1. 构造方式
函数名称功能说明
string()构造空字符串
string(const char* s)用 C-string 构造
string(size_t n, char c)包含 n 个字符 c
string(const string& s)拷贝构造
void test() {
    string s1;              // 空串
    string s2("hello world"); // C 格式字符串
    string s3(s2);          // 拷贝构造
}
2. 容量操作
函数名称功能说明
size / length返回有效字符长度
capacity返回空间总大小
empty检测是否为空
clear清空有效字符(不释放内存)
reserve预留空间
resize改变有效字符个数

注意:

  • size() 与 length() 原理相同,建议统一用 size()。
  • clear() 仅重置 _size,不改变底层容量。
  • resize(n) 若增加长度可能扩容,减少则不变。
  • reserve(n) 只是建议保留空间,小于当前容量时是否缩容不确定。
3. 访问及遍历
函数名称功能说明
operator[]访问指定位置字符
begin + end获取迭代器(左闭右开)
rbegin + rend反向迭代器
范围 forC++11 简洁遍历
#include <string>
#include <iostream>
using namespace std;

int main() {
    string str = "hello world";
    
    // 1. operator[]
    for (size_t i = 0; i < str.size(); ++i) {
        cout << str[i] << " ";
    }
    cout << endl;
    
    // 2. 迭代器
    for (auto it = str.begin(); it != str.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 3. 反向迭代器
    for (auto rit = str.rbegin(); rit != str.rend(); ++rit) {
        cout << *rit << " ";
    }
    cout << endl;
    
    // 4. 范围 for
    for (char ch : str) {
        cout << ch << " ";
    }
    cout << endl;
    return 0;
}
4. 修改操作
函数名称功能说明
push_back尾插字符
append追加字符串
operator+=追加字符串(常用)
c_str返回 C 格式字符串
find / rfind查找字符/子串
substr截取子串

关于 npos: npos 表示无效或未找到的位置。由于 size_t 是无符号类型,-1 会变成最大值,因此用它作为查找失败的标志。

std::string str = "hello";
str.push_back('!');           // hello!
str.append(" world");         // hello! world
str += " C++";                // hello! world C++
const char* cstr = str.c_str();
size_t pos = str.find('o');   // 查找位置
if (pos != std::string::npos) {
    std::cout << "Found at: " << pos << std::endl;
}
std::string sub = str.substr(0, 5);
5. 非成员函数
函数名称功能说明
operator+拼接(慎用,效率低)
operator>>输入流提取
operator<<输出流插入
getline读取整行
关系运算符比较大小
#include <iostream>
#include <string>
using namespace std;

int main() {
    string s1 = "Hello";
    string s2 = "World";
    
    // 拼接(产生临时对象)
    string s3 = s1 + " " + s2;
    cout << s3 << endl;
    
    // 输入(遇到空格停止)
    string input;
    cin >> input;
    
    // 输出
    cout << s1 << " " << s2 << endl;
    
    // getline(推荐,可含空格)
    cin.ignore();
    getline(cin, input);
    cout << "You entered: " << input << endl;
    
    // 比较
    if (s1 == s2) cout << "Equal" << endl;
    else if (s1 < s2) cout << "Less" << endl;
    return 0;
}

二、string 类的模拟实现

面试中常要求手写 string 类,重点在于构造、析构、拷贝构造及赋值重载。

2.1 头文件设计

我们可以将 string 的底层视为一个动态字符数组,配合 _size 和 _capacity 管理。

class string {
    friend std::ostream& operator<<(std::ostream& _out, const sxn::string& s);
    friend std::istream& operator>>(std::istream& _in, sxn::string& s);
public:
    typedef char* iterator;

    string(const char* str = "");
    string(const string& s);
    string& operator=(string s);
    ~string();

    iterator begin();
    iterator end();

    void swap(string& s);
    void push_back(char c);
    string& operator+=(char c);
    void append(const char* str);
    string& operator+=(const char* str);
    void clear();
    const char* c_str() const;

    size_t size() const;
    size_t capacity() const;
    bool empty() const;
    void resize(size_t n, char c = '\0');
    void reserve(size_t n);

    char& operator[](size_t index);
    const char& operator[](size_t index) const;

    bool operator<(const string& s);
    bool operator==(const string& s);
    bool operator<=(const string& s);
    bool operator>(const string& s);
    bool operator>=(const string& s);
    bool operator!=(const string& s);

    size_t find(char c, size_t pos = 0) const;
    size_t find(const char* s, size_t pos = 0) const;
    string& insert(size_t pos, char c);
    string& insert(size_t pos, const char* str);
    string& erase(size_t pos, size_t len);

private:
    char* _str = nullptr;
    size_t _capacity = 0;
    size_t _size = 0;
};

2.2 浅拷贝与深拷贝

编译器生成的默认拷贝构造和赋值是浅拷贝,会导致两个对象指向同一块内存。析构时重复释放会引发崩溃,因此必须实现深拷贝。

2.3 核心代码实现

#include "string.h"
#include <cstring>
#include <cassert>

const size_t string::npos = static_cast<size_t>(-1);

// 构造函数
string::string(const char* str) : _str(new char[strlen(str) + 1]) {
    _size = strlen(str);
    strcpy(_str, str);
    _capacity = _size;
}

// 拷贝构造(现代写法:利用交换法)
string::string(const string& s) {
    string tmp(s._str);
    swap(tmp);
}

// 析构函数
string::~string() {
    delete[] _str;
    _str = nullptr;
    _size = _capacity = 0;
}

// 迭代器
string::iterator string::begin() { return _str; }
string::iterator string::end() { return _str + _size; }

// 赋值运算符重载(现代写法:取传值 + 交换)
string& string::operator=(string s) {
    swap(s);
    return *this;
}

// 尾插
void string::push_back(char c) {
    reserve(_size + 1);
    _str[_size++] = c;
}

string& string::operator+=(char c) {
    push_back(c);
    return *this;
}

// 追加
void string::append(const char* str) {
    int len = strlen(str);
    reserve(_size + len);
    strcpy(_str + _size, str);
    _size += len;
}

string& string::operator+=(const char* str) {
    append(str);
    return *this;
}

void string::clear() {
    _size = 0;
    _str[0] = '\0';
}

void string::swap(string& s) {
    std::swap(_str, s._str);
    std::swap(_size, s._size);
    std::swap(_capacity, s._capacity);
}

const char* string::c_str() const {
    return _str;
}

// 容量相关
size_t string::size() const { return _size; }
size_t string::capacity() const { return _capacity; }
bool string::empty() const { return _size == 0; }

void string::resize(size_t n, char c) {
    if (n <= _size) {
        _str[n] = '\0';
        _size = n;
    } else {
        reserve(n);
        while (_size < n) {
            _str[_size++] = c;
        }
        _str[_size] = '\0';
    }
}

void string::reserve(size_t n) {
    if (n > _capacity) {
        char* tmp = new char[n + 1];
        strcpy(tmp, _str);
        delete[] _str;
        _str = tmp;
        _capacity = n;
    }
}

// 访问
char& string::operator[](size_t index) {
    assert(index < _size);
    return _str[index];
}

const char& string::operator[](size_t index) const {
    assert(index < _size);
    return _str[index];
}

// 关系运算
bool string::operator<(const string& s) {
    int i = 0, j = 0;
    while (i < _size && j < s._size) {
        if (_str[i] >= s._str[j]) return false;
        ++i; ++j;
    }
    if (i < _size && j == s._size) return false;
    return true;
}

bool string::operator==(const string& s) {
    int i = 0, j = 0;
    while (i < _size && j < s._size) {
        if (_str[i] != s._str[j]) return false;
        ++i; ++j;
    }
    return i == _size && j == s._size;
}

bool string::operator<=(const string& s) { return *this == s || *this < s; }
bool string::operator>(const string& s) { return !(*this <= s); }
bool string::operator>=(const string& s) { return *this == s || *this > s; }
bool string::operator!=(const string& s) { return !(*this == s); }

// 查找
size_t string::find(char c, size_t pos) const {
    if (pos >= _size) return npos;
    while (pos < _size) {
        if (_str[pos] == c) return pos;
        ++pos;
    }
    return npos;
}

size_t string::find(const char* s, size_t pos) const {
    char* str = strstr(_str + pos, s);
    if (str == nullptr) return npos;
    return str - _str;
}

// 插入
string& string::insert(size_t pos, char c) {
    assert(pos <= _size);
    reserve(_size + 1);
    size_t i = _size + 1;
    while (i > pos) {
        _str[i] = _str[i - 1];
        --i;
    }
    _str[pos] = c;
    ++_size;
    return *this;
}

string& string::insert(size_t pos, const char* str) {
    assert(pos <= _size);
    size_t len = strlen(str);
    if (len == 0) return *this;
    reserve(_size + len);
    size_t i = _size + len;
    while (i >= pos + len) {
        _str[i] = _str[i - len];
        --i;
    }
    strncpy(_str + pos, str, len);
    _size += len;
    return *this;
}

// 删除
string& string::erase(size_t pos, size_t len) {
    assert(pos < _size);
    if (pos + len >= _size) {
        _str[pos] = '\0';
        _size = pos;
    } else {
        size_t i = 0;
        while (pos + i < _size) {
            _str[pos + i] = _str[pos + len + i];
            ++i;
        }
        _size -= len;
    }
    return *this;
}

// 输入输出
std::ostream& operator<<(std::ostream& _out, const sxn::string& s) {
    int n = 0;
    while (n < s._size) {
        _out << s._str[n++];
    }
    return _out;
}

std::istream& operator>>(std::istream& _in, sxn::string& s) {
    s.clear();
    const int N = 5;
    char buffer[N];
    int ch = _in.get();
    int i = 0;
    while (ch != '\n') {
        if (i < N) {
            buffer[i++] = ch;
        } else {
            s.reserve(s._size + N);
            strncpy(s._str + s._size, buffer, N);
            s._size += N;
            s._str[s._size] = '\0';
            i = 0;
        }
        ch = _in.get();
    }
    if (i > 0) {
        s.reserve(s._size + i + 1);
        strncpy(s._str + s._size, buffer, i + 1);
        s._size += i + 1;
        s._str[s._size] = '\0';
    }
    return _in;
}

通过上述实现,我们不仅掌握了 string 的基本用法,更理解了 C++ 中资源管理的核心机制。真正掌握一个类,不仅要会用,更要知其所以然。

目录

  1. C++ string 的使用与模拟实现
  2. 一、标准库中的 string 类
  3. 1.1 auto 和范围 for
  4. 1.2 string 类的常用接口说明
  5. 1. 构造方式
  6. 2. 容量操作
  7. 3. 访问及遍历
  8. 4. 修改操作
  9. 5. 非成员函数
  10. 二、string 类的模拟实现
  11. 2.1 头文件设计
  12. 2.2 浅拷贝与深拷贝
  13. 2.3 核心代码实现

更多推荐文章

查看全部
  • Mac mini M4 部署 OpenClaw + Ollama 本地大模型接入飞书机器人
  • 大模型转行指南:四大方向解析与入行建议
  • C++ 二叉搜索树原理与实现
  • Ubuntu 24.04 更换阿里云镜像源指南
  • PostgreSQL 18 默认密码修改与安全加固指南
  • 鸿蒙金融理财全栈项目:风险控制、合规审计与产品创新
  • C++11 右值引用与移动语义详解:从性能瓶颈到零拷贝优化
  • 鸿蒙金融理财全栈项目:运维监控、性能优化与安全加固
  • Clang Power Tools:在 Visual Studio 里做 C++ 静态分析
  • 大模型基本概念详解:定义、发展、特点与分类
  • RAG 实战:利用 Langchain 和 Milvus 构建本地知识库
  • RTX 4090 本地部署腾讯混元与阿里通义万相视频生成模型
  • 快速排序核心原理与多版本实现详解
  • C++ 模板编程基础:泛型编程入门与实践
  • 大模型技术从基础入门到实战应用指南
  • 文心一言 4.5 开源版本地化部署实测与性能分析
  • 使用 cpolar 实现 Open-Lovable 远程访问与网页克隆
  • C++ 结构体基础:定义、成员函数与排序
  • ThreadLocal 原理、使用场景及内存泄漏问题详解
  • Git 基础教程:从零理解版本控制与团队协作

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online