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

C++ 标准库 string 类详解与模拟实现

C++ string 类详解涵盖构造、常用接口、底层结构及模拟实现。对比 C 风格字符串,阐述 OOP 优势。解析 auto 关键字与范围 for 循环在 string 遍历中的应用。区分 VS 与 g++ 下 string 内存布局差异。重点讲解浅拷贝与深拷贝原理,通过传统与现代写法演示规则三的实现,辅助理解资源管理。

ServerBase发布于 2026/3/29更新于 2026/6/717 浏览
C++ 标准库 string 类详解与模拟实现

C++ 标准库 string 类详解与模拟实现

为什么学习 string 类

在 C 语言中,字符串是以 \0 结尾的字符集合。虽然标准库提供了一些 str 系列函数,但这些函数与字符串本身是分离的,不太符合面向对象(OOP)的思想。更重要的是,底层空间需要用户自己管理,容易出现越界访问等安全隐患。

C++ 引入 string 类正是为了解决这些问题,它封装了内存管理、大小计算等操作,让开发者能更安全、高效地处理文本。

基础语法与工具

auto 关键字与范围 for

auto 关键字 在 C++11 之前,auto 表示自动存储期,意义不大。C++11 重新定义了它:auto 不再是一个存储类型指示符,而是作为类型推导指示符。编译器会在编译期根据初始化表达式推导变量类型。

  • 指针与引用:用 auto 声明指针时,auto 和 auto* 没有区别;但声明引用时必须加 &。
  • 多变量声明:同一行声明多个变量时,它们必须是相同的类型,否则编译器会报错。
  • 限制:auto 不能作为函数参数,可以做返回值但需谨慎使用;不能直接用来声明数组。
#include <iostream>
#include <typeinfo>
using namespace std;

int fun1() {
    return 1;
}

void func2(auto a) {} // 编译报错,auto 不能做参数

auto func3() {
    return 3; // 可以返回 auto,但建议谨慎使用
}

int main() {
    int a = 1;
    auto b = a;
    auto c = 'a';
    auto d = fun1();
    
    // auto e; // 编译报错,必须具有初始值设定项
    
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    
    int x = 1;
    auto y = &x;
    auto *z = &x;
    auto &m = x;
    
    // auto cc = 3, dd = 4.0; // 编译报错,类型不一致
    
    return 0;
}

范围 for 循环 C++11 引入了基于范围的 for 循环,简化了集合遍历。括号由冒号分为两部分:迭代变量和被迭代范围。它会自动处理迭代器,无需手动判断结束条件。

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

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    
    // C++98 写法
    for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        arr[i]++;
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // C++11 写法
    for (auto &e : arr) { // 引用修改原数组
        e++;
        cout << e << " ";
    }
    cout << endl;
    
    string str("hello world");
    for (auto c : str) {
        cout << c << " ";
    }
    cout << endl;
    
    return 0;
}

string 类常用接口

1. 构造方式

函数名称功能说明
string()构造空的 string 对象
string(const char* s)用 C 风格字符串构造
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() 只清空内容,底层空间不变。
  • resize(n) 扩容时用 \0 填充,resize(n, c) 用字符 c 填充。
  • reserve() 若参数小于当前容量,则不改变容量。

3. 访问及遍历

函数名称功能说明
operator[]返回指定位置字符
begin() + end()获取首尾迭代器
rbegin() + rend()获取反向迭代器
范围 forC++11 简洁遍历方式

4. 修改操作

函数名称功能说明
push_back()尾部插入字符
append()追加字符串
operator+=追加字符串或字符
c_str()返回 C 风格字符串指针
find() / rfind()查找字符/子串位置
substr()截取子串

提示:

  • 尾部追加字符时,s.push_back(c)、s.append(1, c) 和 s += 'c' 效果类似,+= 最常用。
  • 若能预估字符数量,先用 reserve() 预留空间可减少内存重分配开销。

5. 非成员函数

函数名称功能说明
operator+连接字符串(传值效率低,尽量少用)
operator>>输入运算符重载
operator<<输出运算符重载
getline()获取一行字符串
关系运算符大小比较

底层结构差异

不同编译器下 string 的内部实现有所不同,以 32 位平台为例:

VS 下的结构

VS 的 string 占用 28 字节。采用短字符串优化(SSO):

  • 当字符串长度小于 16 时,使用内部固定字符数组存放,无需堆分配。
  • 当长度大于等于 16 时,从堆上开辟空间。
  • 结构体包含联合体、长度字段、容量字段及其他指针信息。

g++ 下的结构

g++ 的 string 通常采用写时拷贝(Copy-On-Write),对象本身仅占 4 字节(一个指针)。

  • 指针指向堆上的控制块,包含:空间总大小、有效长度、引用计数。
  • 这种设计在多线程环境下需注意线程安全问题。

string 类的模拟实现

面试中常要求手写 string 类,核心在于掌握资源管理规则(Rule of Three/Five)。

浅拷贝问题

如果未显式定义拷贝构造函数和赋值运算符,编译器会生成默认的浅拷贝。这会导致两个对象共享同一块堆内存,析构时发生重复释放,引发程序崩溃。

class String {
public:
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    
    ~String() {
        if (_str) {
            delete[] _str;
            _str = nullptr;
        }
    }
private:
    char* _str;
};
// 测试代码中若进行 String s2(s1),将导致浅拷贝错误

深拷贝解决方案

每个对象应拥有独立的资源副本。

传统写法

显式实现拷贝构造函数、赋值运算符重载和析构函数。

class String {
public:
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    
    // 拷贝构造
    String(const String& s) : _str(new char[strlen(s._str) + 1]) {
        strcpy(_str, s._str);
    }
    
    // 赋值运算符重载
    String& operator=(const String& s) {
        if (this != &s) {
            char* pstr = new char[strlen(s._str) + 1];
            strcpy(pstr, s._str);
            delete[] _str;
            _str = pstr;
        }
        return *this;
    }
    
    ~String() {
        if (_str) {
            delete[] _str;
            _str = nullptr;
        }
    }
private:
    char* _str;
};
现代写法(异常安全)

利用临时对象和 swap 提高效率,减少内存泄漏风险。

class String {
public:
    String(const char* str = "") {
        if (nullptr == str) {
            assert(false);
            return;
        }
        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }
    
    // 拷贝构造
    String(const String& s) : _str(new char[strlen(s._str) + 1]) {
        strcpy(_str, s._str);
    }
    
    // 赋值运算符重载 (右值引用版本)
    String& operator=(String& s) {
        swap(_str, s._str);
        return *this;
    }
    
    // 赋值运算符重载 (常量引用版本)
    String& operator=(const String& s) {
        if (this != &s) {
            String temp(s);
            swap(_str, temp._str);
        }
        return *this;
    }
    
    ~String() {
        if (_str) {
            delete[] _str;
            _str = nullptr;
        }
    }
private:
    char* _str;
};

扩展阅读与练习

  • 写时拷贝原理
  • STL string 相关问题
  • LeetCode 练习题:反转字母
  • LeetCode 练习题:第一个唯一字符
  • 牛客网练习题:字符串最后一个单词的长度
  • LeetCode 练习题:验证回文串
  • LeetCode 练习题:字符串相加

目录

  1. C++ 标准库 string 类详解与模拟实现
  2. 为什么学习 string 类
  3. 基础语法与工具
  4. auto 关键字与范围 for
  5. string 类常用接口
  6. 1. 构造方式
  7. 2. 容量操作
  8. 3. 访问及遍历
  9. 4. 修改操作
  10. 5. 非成员函数
  11. 底层结构差异
  12. VS 下的结构
  13. g++ 下的结构
  14. string 类的模拟实现
  15. 浅拷贝问题
  16. 深拷贝解决方案
  17. 传统写法
  18. 现代写法(异常安全)
  19. 扩展阅读与练习
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Neo4j Desktop 2 安装与图数据库实战指南
  • Meson 构建系统入门:Python C++ 扩展开发实战
  • C++ 继承机制详解:从基础到多继承模型
  • AI 办公实战指南:7 套书籍助你精准提效与职场进阶
  • 从 Alpaca 到 Vicuna:使用 Llama Factory 切换对话模板
  • Stack-Chan 机器人开发指南:从入门到进阶
  • OpenClaw Secure DM Pairing 机制解析与安全私信访问配置
  • VSCode 中使用 CMake 构建 C/C++ 多文件项目
  • 基于 DeepSeek 和 Cursor 构建智能代码审查工具
  • OpenClaw 部署指南:环境搭建、模型接入与飞书机器人配置
  • MySQL 表基础语法:增删查与函数核心技巧
  • GLM-4.7-Flash 本地 Copilot 工具构建实战教程
  • VSCode Copilot 登录失败问题排查与解决方案
  • YOLOv8 旋转框角度回归优化:CSL 与 DCL 编码实战
  • Docker 安装及基础操作指南
  • 应对高 AIGC 率检测:学术论文写作与优化的技术解析
  • Microi 吾码:开源低代码平台架构与实战指南
  • 火山引擎 API 与 GLM-4.6V-Flash-WEB 本地部署成本对比
  • Spring Cloud LoadBalancer 负载均衡实战与原理
  • Stable Diffusion 模型加载报错:CheckpointLoaderSimple 错误修复

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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