乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

字符串与原始字符串字面量

定义与作用

C++ 支持两种字符串操作方式:

方式来源特点
C 风格 char* / char[]C 语言手动内存管理,易缓冲区溢出
std::stringC++ 标准库自动内存管理,安全便捷

C++11 新增原始字符串字面量 R"(...)" 解决了转义字符地狱问题——在写正则表达式、Windows 路径、多行文本时不再需要层层转义。

核心原理

字符串存储方式对比

原始字符串语法

R"delimiter( 内容 )delimiter"
组件说明
R"(开始标记
delimiter可选的自定义定界符(最多 16 字符),解决内容含 )" 的问题
内容字符串主体,任何字符都不转义
)delimiter"结束标记

完整示例

示例一:转义地狱与原始字符串的救赎

场景说明:在配置解析和正则匹配中,传统字符串需要大量转义,原始字符串让代码可读性大幅提升。

#include <iostream>
#include <string>
#include <regex>

int main() {
    // ---- 场景 1: Windows 文件路径 ----
    std::string escaped_path = "C:\\Users\\张三\\Documents\\论文.docx";
    std::string raw_path = R"(C:\Users\张三\Documents\论文.docx)";

    std::cout << "===== 文件路径 =====" << std::endl;
    std::cout << "转义写法:   " << escaped_path << std::endl;
    std::cout << "原始字符串: " << raw_path << std::endl;

    // ---- 场景 2: 正则表达式 ----
    // 匹配 3 位数字-4 位数字(如 010-1234)
    std::string escaped_regex = "\\d{3}-\\d{4}";
    std::string raw_regex = R"(\d{3}-\d{4})";

    std::cout << "\n===== 正则表达式 =====" << std::endl;
    std::cout << "转义写法:   " << escaped_regex << std::endl;
    std::cout << "原始字符串: " << raw_regex << std::endl;

    std::regex pattern(raw_regex);
    std::string test1 = "电话: 010-5678";
    std::string test2 = "电话: 010-567";

    std::cout << "\"" << test1 << "\" → "
              << std::boolalpha << std::regex_search(test1, pattern) << std::endl;
    std::cout << "\"" << test2 << "\" → "
              << std::regex_search(test2, pattern) << std::endl;

    // ---- 场景 3: JSON / HTML 内嵌 ----
    std::string json = R"({
    "name": "张三",
    "age": 20,
    "courses": ["C++", "数据结构"]
})";

    std::cout << "\n===== 内嵌 JSON =====" << std::endl;
    std::cout << json << std::endl;

    // ---- 场景 4: 多行 SQL 查询 ----
    std::string sql = R"SQL(
SELECT id, name, score
FROM students
WHERE score >= 60
  AND enrollment_year = 2024
ORDER BY score DESC
)SQL";

    std::cout << "===== SQL 查询 =====" << std::endl;
    std::cout << sql << std::endl;

    // ---- 场景 5: 自定义定界符 ----
    // 内容本身包含 )" 时需要自定义定界符
    std::string tricky = R"END(这里的字符串包含)"这个序列,但不会提前终止)END";

    std::cout << "===== 自定义定界符 =====" << std::endl;
    std::cout << tricky << std::endl;

    return 0;
}

预期输出:

===== 文件路径 =====
转义写法:   C:\Users\张三\Documents\论文.docx
原始字符串: C:\Users\张三\Documents\论文.docx

===== 正则表达式 =====
转义写法:   \d{3}-\d{4}
原始字符串: \d{3}-\d{4}

"电话: 010-5678" → true
"电话: 010-567" → false

===== 内嵌 JSON =====
{
    "name": "张三",
    "age": 20,
    "courses": ["C++", "数据结构"]
}

===== SQL 查询 =====

SELECT id, name, score
FROM students
WHERE score >= 60
  AND enrollment_year = 2024
ORDER BY score DESC

===== 自定义定界符 =====
这里的字符串包含)"这个序列,但不会提前终止

逐段分析:

  • 传统写法 "\\d{3}-\\d{4}" 每个 \ 都需要双重转义(C++ 字符串一次,正则引擎一次)
  • R"(\d{3}-\d{4})" 中所有字符原样保留,正则表达式和代码中的写法完全一致
  • JSON 内嵌时,双引号、换行、缩进都不需要转义
  • 自定义定界符 R"END(...)END" 应对"内容本身就包含 )""的极端场景
  • 原始字符串内的空白(包括前导空格)会被保留

示例二:字符串操作与短字符串优化

场景说明:演示 std::string 的常用操作及 SSO(短字符串优化)对性能的影响。

#include <iostream>
#include <string>
#include <cstring>

int main() {
    // ---- 常用操作 ----
    std::string s1 = "Hello";
    std::string s2 = "World";

    // 拼接
    std::string s3 = s1 + ", " + s2 + "!";
    std::cout << "拼接: " << s3 << std::endl;

    // 查找
    size_t pos = s3.find("World");
    if (pos != std::string::npos) {
        std::cout << "\"World\" 在位置 " << pos << std::endl;
    }

    // 子串
    std::string sub = s3.substr(7, 5);  // 从位置 7 取 5 个字符
    std::cout << "子串: " << sub << std::endl;

    // 替换
    std::string s4 = "C++ is hard";
    s4.replace(7, 4, "fun");
    std::cout << "替换: " << s4 << std::endl;

    // ---- C 风格字符串处理 ----
    char buf[50];
    // strcpy(buf, s3.c_str());  // 不安全:buf 可能不够大
    strncpy(buf, s3.c_str(), sizeof(buf) - 1);
    buf[sizeof(buf) - 1] = '\0';
    std::cout << "\nC 风格拷贝: " << buf << std::endl;

    // ---- 短字符串优化 (SSO) 观察 ----
    std::string short_str = "hi";
    std::string long_str = "this is a very long string that exceeds the SSO buffer";

    std::cout << "\n===== 字符串内存布局 =====" << std::endl;
    std::cout << "短字符串 '" << short_str << "':" << std::endl;
    std::cout << "  sizeof: " << sizeof(short_str) << " 字节" << std::endl;
    std::cout << "  data() == 栈? " << std::boolalpha
              << (short_str.data() == reinterpret_cast<const char*>(&short_str))
              << " (可能由实现决定)" << std::endl;

    std::cout << "长字符串: sizeof " << sizeof(long_str)
              << " 字节" << std::endl;
    // 长字符串的 data() 肯定不在栈上

    // ---- 字符类型 ----
    std::cout << "\n===== 字符类型 =====" << std::endl;
    const wchar_t* wide = L"宽字符";
    const char16_t* u16 = u"UTF-16 字符串";
    const char32_t* u32 = U"UTF-32 字符串";

    std::wcout << L"宽字符输出: " << wide << std::endl;
    std::cout << "char16_t 字符串长度: " << std::char_traits<char16_t>::length(u16) << std::endl;
    std::cout << "char32_t 字符串长度: " << std::char_traits<char32_t>::length(u32) << std::endl;

    return 0;
}

预期输出:

拼接: Hello, World!
"World" 在位置 7
子串: World
替换: C++ is fun

C 风格拷贝: Hello, World!

===== 字符串内存布局 =====
短字符串 'hi':
  sizeof: 32 字节
  data() == 栈? false (可能由实现决定)
长字符串: sizeof 32 字节

===== 字符类型 =====
宽字符输出: 宽字符
char16_t 字符串长度: 8
char32_t 字符串长度: 6

逐段分析:

  • std::string 的 sizeof 在 64 位 libstdc++ 上通常为 32 字节,不随字符串长度变化——因为它内部存储指针 + size + capacity
  • SSO(Short String Optimization)让短字符串直接存储在 std::string 对象内部,避免堆分配
  • C 风格字符串操作(strncpy/strcat 等)需要手动管理缓冲区大小,容易出错——优先用 std::string
  • C++11 新增 char16_t/char32_t + u/U 前缀,支持 UTF-16/UTF-32 字符串字面量

易错场景与面试考点

易错场景

场景错误表现正确做法
用 strcpy 拷贝不可信来源的字符串缓冲区溢出用 std::string 或 strncpy
std::string::data() 返回的指针当 C 字符串可能不以 \0 结尾(C++11 前)用 .c_str()
原始字符串中嵌套 )"提前终止使用自定义定界符 R"DELIM(...)DELIM"
原始字符串前导空白被意外保留注意原始字符串会保留所有字符,包括缩进
char 有无符号不确定位运算结果不一致明确用 signed char / unsigned char

常见面试问题

  1. std::string 的 SSO 是什么?——短字符串优化,短字符串直接存储在对象内部(栈上)而非堆上,避免动态内存分配。SSO 阈值通常为 15 字符左右(取决于实现)。

  2. c_str() 和 data() 的区别?——C++11 起两者等价,都返回以 \0 结尾的字符串。C++11 之前 data() 不保证 \0 结尾。

  3. 原始字符串字面量解决了什么问题?——避免正则表达式、文件路径、JSON、HTML 中的大量反斜杠转义。核心原理是"所见即所得"。

  4. 如何安全地将 std::string 传给 C 函数?——使用 .c_str() 获取 const char*。如果 C 函数需要修改字符串,先拷贝到 std::vector<char> 再传 &vec[0]。

  5. char、wchar_t、char16_t、char32_t 的区别?——char 8 位(ASCII/UTF-8)、wchar_t 平台相关(Windows 16 位/Linux 32 位)、char16_t 16 位(UTF-16)、char32_t 32 位(UTF-32)。跨平台项目应明确选择。

小结

  • C 风格字符串易出错,日常开发统一使用 std::string
  • 原始字符串 R"(...)" 解决转义地狱,配合自定义定界符应对极端场景
  • SSO 让短字符串零堆分配,是 std::string 的重要性能优化
  • 涉及 C API 时用 .c_str() 获取 const char*,不要直接修改
  • C++11 新增 char16_t/char32_t 提供 UTF-16/UTF-32 的标准化支持
上一页
数组与 stdarray
下一页
const 与 constexpr