第8章 字符和字符串

字符类型的深度解析

字符类型的底层实现

C++中的字符类型在底层实现上依赖于目标平台和编译器实现,但其设计遵循明确的标准规范:

类型大小(字节)范围底层表示用途内存对齐
char1-128 到 127 或 0 到 255(取决于实现)单字节整数ASCII字符、UTF-8编码单元1字节
signed char1-128 到 127带符号单字节整数带符号字符值1字节
unsigned char10 到 255无符号单字节整数无符号字符值、原始字节1字节
wchar_t2 或 4取决于实现多字节整数宽字符(平台相关)2或4字节
char16_t20 到 65535(C++11+)16位无符号整数UTF-16编码单元2字节
char32_t40 到 4294967295(C++11+)32位无符号整数UTF-32编码单元、Unicode码点4字节
char8_t10 到 255(C++20+)8位无符号整数UTF-8编码单元1字节

字符类型的内存表示与硬件映射

字符类型在底层直接映射到CPU的寄存器和内存操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符类型的内存表示
char c = 'A'; // 存储ASCII值65,使用AL寄存器
unsigned char uc = 255; // 存储值255,使用AL寄存器
wchar_t wc = L'中'; // 在UTF-16平台存储0x4E2D,使用AX寄存器
char16_t c16 = u'中'; // 存储UTF-16编码单元0x4E2D,使用AX寄存器
char32_t c32 = U'中'; // 存储Unicode码点0x4E2D,使用EAX寄存器
char8_t c8 = u8'A'; // 存储UTF-8编码单元0x41,使用AL寄存器

// 字符类型的大小和对齐
std::cout << "Size of char: " << sizeof(char) << " bytes, Alignment: " << alignof(char) << std::endl;
std::cout << "Size of wchar_t: " << sizeof(wchar_t) << " bytes, Alignment: " << alignof(wchar_t) << std::endl;
std::cout << "Size of char16_t: " << sizeof(char16_t) << " bytes, Alignment: " << alignof(char16_t) << std::endl;
std::cout << "Size of char32_t: " << sizeof(char32_t) << " bytes, Alignment: " << alignof(char32_t) << std::endl;
std::cout << "Size of char8_t: " << sizeof(char8_t) << " bytes, Alignment: " << alignof(char8_t) << std::endl;

字符类型的汇编级实现

字符操作在底层通过CPU的整数指令实现,不同字符类型对应不同的寄存器宽度:

1
2
3
4
5
6
7
8
9
10
11
; char操作示例(x86-64)
mov al, 'A' ; 将字符'A'加载到AL寄存器(8位)
mov byte ptr [c], al ; 存储到内存

; wchar_t操作示例(x86-64,2字节)
mov ax, 0x4E2D ; 将Unicode码点'中'加载到AX寄存器(16位)
mov word ptr [wc], ax ; 存储到内存

; char32_t操作示例(x86-64)
mov eax, 0x4E2D ; 将Unicode码点'中'加载到EAX寄存器(32位)
mov dword ptr [c32], eax ; 存储到内存

字符常量的高级特性

1. 字符常量的类型系统与值表示

字符常量在C++类型系统中具有明确的类型,并且在编译时会被转换为对应类型的整数值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 字符常量的类型推导
static_assert(std::is_same_v<decltype('A'), char>);
static_assert(std::is_same_v<decltype(L'A'), wchar_t>);
static_assert(std::is_same_v<decltype(u'A'), char16_t>);
static_assert(std::is_same_v<decltype(U'A'), char32_t>);
static_assert(std::is_same_v<decltype(u8'A'), char8_t>);

// 字符常量的编译期值
constexpr char c = 'A'; // 编译期常量,值为65
constexpr wchar_t wc = L'\x4E2D'; // 编译期常量,值为0x4E2D
constexpr char16_t c16 = u'\u4E2D'; // 编译期常量,值为0x4E2D
constexpr char32_t c32 = U'\U00004E2D'; // 编译期常量,值为0x4E2D

// 字符常量的类型转换
constexpr int i = 'A'; // 隐式转换为int,值为65
constexpr long long ll = L'中'; // 隐式转换为long long

2. 多字符常量的底层实现

多字符常量在底层通过位打包实现,其值依赖于编译器的字节序:

1
2
3
4
5
6
7
8
9
10
11
// 多字符常量(实现定义)
int multiChar = 'ABCD'; // 在小端机器上通常是0x44434241,在大端机器上是0x41424344
std::cout << "Multi-character constant: " << std::hex << multiChar << std::endl;

// 多字符常量的底层表示
// 'A' (0x41), 'B' (0x42), 'C' (0x43), 'D' (0x44)
// 小端:0x44 0x43 0x42 0x41 → 0x44434241
// 大端:0x41 0x42 0x43 0x44 → 0x41424344

// 注意:多字符常量的类型是int,值是实现定义的
// 不建议在可移植代码中使用

3. Unicode字符常量的编码机制

Unicode字符常量通过转义序列或直接Unicode字符表示,编译器会自动处理编码转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Unicode字符常量(C++11+)
char16_t c16_1 = u'\u0041'; // 'A',直接编码为UTF-16
char16_t c16_2 = u'\u4E2D'; // '中',直接编码为UTF-16
char32_t c32_1 = U'\U00000041'; // 'A',直接编码为UTF-32
char32_t c32_2 = U'\U00004E2D'; // '中',直接编码为UTF-32

// C++17+:直接使用Unicode字符
char16_t c16_3 = u'A'; // 等同于u'\u0041'
char16_t c16_4 = u'中'; // 等同于u'\u4E2D'

// 注意:\u后面跟4位十六进制数字
// \U后面跟8位十六进制数字
// 超出范围的Unicode字符会导致编译错误

4. 字符常量的编译期计算

字符常量可以在编译期进行计算,用于模板元编程和 constexpr 上下文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 编译期字符计算
constexpr char lowercaseA = 'A' + 32; // 'a'
constexpr char digit5 = '0' + 5; // '5'
constexpr bool isDigit = '5' >= '0' && '5' <= '9'; // true

// 编译期字符分类
constexpr bool isAlpha(char c) {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}

static_assert(isAlpha('A')); // 编译期验证
static_assert(!isAlpha('5')); // 编译期验证

// 模板元编程中的字符常量
template<char C> struct CharValue {
static constexpr int value = C;
};

static_assert(CharValue<'A'>::value == 65);

转义序列的深度解析

转义序列的类型与编译期处理

转义序列在编译期被处理并转换为对应的字符值,分为以下几类:

转义序列类型示例字符值编译期处理使用场景
简单转义\n, \t, \r0x0A, 0x09, 0x0D直接替换为对应值文本格式化
引号转义\', \", \\0x27, 0x22, 0x5C转义特殊字符字符串字面量
空字符\00x00生成空终止符字符串结束标记
控制字符\a, \b, \f, \v0x07, 0x08, 0x0C, 0x0B生成控制字符终端控制
十六进制\x41, \xFF0x41, 0xFF解析十六进制值任意字节值
八进制\101, \0400x41, 0x20解析八进制值任意字节值

转义序列的底层实现

转义序列在编译期被词法分析器处理,转换为对应的整数值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 转义序列的编译期处理
char newline = '\n'; // 编译期转换为0x0A
char tab = '\t'; // 编译期转换为0x09
char backslash = '\\'; // 编译期转换为0x5C
char quote = '\''; // 编译期转换为0x27
char null = '\0'; // 编译期转换为0x00

// 十六进制转义序列(变长)
char hexChar1 = '\x41'; // 单字节:0x41
char hexChar2 = '\xFF'; // 单字节:0xFF

// 八进制转义序列(最多3位)
char octChar1 = '\101'; // 0x41
char octChar2 = '\040'; // 0x20
char octChar3 = '\777'; // 实现定义,通常是0xFF

原始字符串字面量的深度解析

原始字符串字面量(C++11+)允许包含未转义的特殊字符,其底层实现使用分隔符机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 基本原始字符串
std::string raw1 = R"(Raw string with \n and ")"; // 不需要转义
std::cout << raw1 << std::endl; // 输出:Raw string with \n and "

// 带分隔符的原始字符串
std::string raw2 = R"(=)
Raw string with
newlines and "quotes"
and backslashes \ \ \
(=)"; // 使用(=)作为分隔符

// 原始字符串与普通字符串的混合使用
std::string mixed = "Normal " + R"(raw)" + " string";

// 原始字符串在模板元编程中的应用
constexpr auto sqlQuery = R"(
SELECT * FROM users
WHERE id = ?
ORDER BY name
)";

转义序列的性能影响

转义序列在编译期处理,对运行时性能无影响,但需要注意以下几点:

  1. 编译期开销:复杂的转义序列会增加编译时间,但通常可以忽略
  2. 字符串长度计算:转义序列被视为单个字符,如”\n”的长度为1
  3. 国际化考虑:在Unicode字符串中,转义序列的处理可能因编码而异
  4. 调试便利性:合理使用转义序列可以提高代码可读性
1
2
3
4
5
6
7
8
// 性能对比:转义序列 vs 原始字符串
// 编译期处理,运行时性能相同
std::string path1 = "C:\\Users\\Name\\File.txt"; // 转义反斜杠
std::string path2 = R"(C:\Users\Name\File.txt)"; // 原始字符串

// 字符串长度计算
static_assert(sizeof("Hello\nWorld") == 11); // 'H' 'e' 'l' 'l' 'o' '\n' 'W' 'o' 'r' 'l' 'd' '\0' → 12字节
static_assert("Hello\nWorld"s.size() == 11); // 运行时长度11

字符类型的类型转换

1. 隐式类型转换的底层机制

字符类型的隐式转换遵循C++的类型提升规则,底层通过CPU的零扩展或符号扩展指令实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 隐式类型转换及其底层实现
char c = 'A'; // 存储为8位值65
int i = c; // char -> int:零扩展或符号扩展(取决于char的有符号性)
double d = c; // char -> double:整数到浮点数转换

unsigned char uc = 255; // 存储为8位无符号值255
signed char sc = uc; // unsigned char -> signed char:可能溢出(变为-1)

// 底层汇编实现(x86-64)
// char -> int(有符号char):
// movsx eax, byte ptr [c] // 符号扩展到32位
//
// char -> int(无符号char):
// movzx eax, byte ptr [uc] // 零扩展到32位

// 注意:char的有符号性是实现定义的
// 为了可移植性,应显式指定signed char或unsigned char

2. 显式类型转换的深度解析

显式类型转换提供了对转换过程的精确控制,特别是在处理边界情况时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 显式类型转换的精确控制
int i = 65;
char c = static_cast<char>(i); // 安全转换,值为'A'

unsigned char uc = static_cast<unsigned char>(i); // 安全转换,值为65

// 处理溢出的显式转换
int large = 300;
unsigned char uc2 = static_cast<unsigned char>(large); // 300 % 256 = 44(模运算)
signed char sc2 = static_cast<signed char>(large); // 实现定义,通常是-12

// 字符编码转换
char8_t utf8Char = u8'A';
char c8 = static_cast<char>(utf8Char); // UTF-8到char的转换

// 宽字符到窄字符的转换
wchar_t wc = L'A';
char cw = static_cast<char>(wc); // 可能溢出(如果wchar_t值>127)

3. 字符与数字的高效转换

字符与数字之间的转换是常见操作,有多种优化技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 字符到数字的高效转换
char digitChar = '5';
int digit = digitChar - '0'; // '5' - '0' = 5(高效,无分支)

// 范围检查的字符到数字转换
int safeDigitToInt(char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
throw std::invalid_argument("Not a digit");
}

// 数字到字符的高效转换
int num = 5;
char numChar = static_cast<char>('0' + num); // '0' + 5 = '5'

// 多位数的字符串表示
std::string intToString(int value) {
if (value == 0) return "0";

bool negative = false;
if (value < 0) {
negative = true;
value = -value;
}

std::string result;
while (value > 0) {
result += static_cast<char>('0' + (value % 10));
value /= 10;
}

if (negative) {
result += '-';
}

std::reverse(result.begin(), result.end());
return result;
}

4. 类型转换的性能影响

不同类型转换的性能特性差异显著:

转换类型底层实现性能特性适用场景
char → int零扩展/符号扩展O(1),单指令字符处理
char → double整数到浮点数转换O(1),多条指令数值计算
int → char截断O(1),单指令范围受限场景
wchar_t → char截断或转换O(1),可能有分支窄字符输出
char → bool零值检查O(1),单指令条件判断
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 性能优化:避免不必要的类型转换
void processCharacters(const std::vector<char>& chars) {
// 避免在循环中重复转换
for (char c : chars) {
// 直接使用char类型进行计算
if (c >= 'A' && c <= 'Z') {
// 处理大写字母
}
}
}

// 性能优化:批量类型转换
void batchConvert(const std::vector<char>& input, std::vector<int>& output) {
output.resize(input.size());
// 可以使用SIMD指令优化批量转换
for (size_t i = 0; i < input.size(); i++) {
output[i] = static_cast<int>(input[i]);
}
}

字符类型的最佳实践

1. 字符类型的选择策略

选择合适的字符类型需要考虑编码需求、平台兼容性和性能特性:

使用场景推荐类型底层实现性能特性兼容性
ASCII字符char单字节整数最高效全平台
原始字节unsigned char无符号单字节整数缓存友好全平台
UTF-8编码char8_t (C++20+) 或 char单字节编码单元缓存友好现代编译器
UTF-16编码char16_t16位无符号整数中等C++11+
UTF-32编码char32_t32位无符号整数较低C++11+
宽字符(Windows)wchar_t2字节UTF-16中等Windows API
宽字符(Unix)char32_t4字节UTF-32较低Unix系统

2. 字符处理的性能优化技术

字符处理是高频操作,需要使用高效的实现技术:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 位掩码优化:快速字符分类
bool isVowel(char c) {
// 位掩码优化:快速判断元音字母
static constexpr unsigned int vowelMask = (
1u << ('a' - 'a') | 1u << ('e' - 'a') | 1u << ('i' - 'a') |
1u << ('o' - 'a') | 1u << ('u' - 'a') |
1u << ('A' - 'a') | 1u << ('E' - 'a') | 1u << ('I' - 'a') |
1u << ('O' - 'a') | 1u << ('U' - 'a')
);

// 范围检查避免越界访问
if (c < 'A' || (c > 'Z' && c < 'a') || c > 'z') {
return false;
}

// 无分支位操作
return (vowelMask & (1u << (c - 'a'))) != 0;
}

// 查找表优化:字符转换
char toUpper(char c) {
// 预计算的大写转换表(编译期初始化)
static constexpr char upperTable[256] = []() {
char table[256] = {0};
for (int i = 0; i < 256; i++) {
if (i >= 'a' && i <= 'z') {
table[i] = static_cast<char>(i - 32);
} else {
table[i] = static_cast<char>(i);
}
}
return table;
}();

return upperTable[static_cast<unsigned char>(c)];
}

// SIMD优化:批量字符处理
#include <immintrin.h>

void batchToUpper(char* data, size_t size) {
size_t i = 0;

// 使用AVX2指令批量处理(32字节/次)
for (; i + 31 < size; i += 32) {
__m256i vec = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(data + i));
// 大写转换:a-z → A-Z
__m256i mask = _mm256_set1_epi8(0x20); // 32字节的0x20
__m256i upper = _mm256_andnot_si256(mask, vec); // 清除第5位
_mm256_storeu_si256(reinterpret_cast<__m256i*>(data + i), upper);
}

// 处理剩余字节
for (; i < size; i++) {
if (data[i] >= 'a' && data[i] <= 'z') {
data[i] -= 32;
}
}
}

3. 字符处理的安全性考虑

字符处理中的安全性主要涉及边界检查、符号扩展和编码验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 安全的字符处理:避免符号扩展
void processRawBytes(const unsigned char* data, size_t size) {
// 使用unsigned char确保无符号处理
for (size_t i = 0; i < size; i++) {
unsigned char byte = data[i];
// 安全处理,无符号值在0-255范围内
processByte(byte);
}
}

// 安全的字符输入:处理EOF
char safeGetChar() {
int c = getchar();
if (c == EOF) {
return '\0'; // 或抛出异常
}
// 转换为unsigned char避免符号扩展,再转回char
return static_cast<char>(static_cast<unsigned char>(c));
}

// 安全的字符编码验证
bool isValidUTF8LeadByte(unsigned char c) {
// UTF-8前导字节验证
if (c <= 0x7F) {
return true; // 单字节
} else if (c >= 0xC0 && c <= 0xF7) {
return true; // 多字节前导字节
}
return false;
}

// 安全的字符范围检查
bool isPrintableASCII(char c) {
unsigned char uc = static_cast<unsigned char>(c);
return uc >= 0x20 && uc <= 0x7E;
}

4. 字符类型的现代C++特性

现代C++提供了更安全、更灵活的字符处理机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 字符类型概念(C++20+)
#include <concepts>

// 字符类型概念
template<typename T>
concept NarrowCharType = std::integral<T> && sizeof(T) == 1;

template<typename T>
concept WideCharType = std::integral<T> && sizeof(T) > 1;

// 约束函数模板
template<NarrowCharType T>
void processNarrowChar(T c) {
// 处理窄字符
std::cout << "Narrow character: " << static_cast<int>(c) << std::endl;
}

template<WideCharType T>
void processWideChar(T c) {
// 处理宽字符
std::cout << "Wide character: " << static_cast<int>(c) << std::endl;
}

// 字符类型的类型特征
#include <type_traits>

// 编译期字符类型检查
static_assert(std::is_trivial_v<char>);
static_assert(std::is_standard_layout_v<char>);
static_assert(std::is_integral_v<char>);
static_assert(!std::is_floating_point_v<char>);

// 字符类型的编译期分类
template<typename T>
constexpr bool isUnicodeCharType() {
return std::is_same_v<T, char16_t> || std::is_same_v<T, char32_t> || std::is_same_v<T, char8_t>;
}

static_assert(isUnicodeCharType<char16_t>());
static_assert(isUnicodeCharType<char32_t>());
static_assert(isUnicodeCharType<char8_t>());
static_assert(!isUnicodeCharType<char>());

字符类型的现代C++特性

1. 字符类型的概念(C++20+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <concepts>

// 字符类型概念
template<typename T>
concept CharType = std::integral<T> && sizeof(T) == 1;

template<typename T>
concept WideCharType = std::integral<T> && sizeof(T) > 1;

// 字符类型约束的函数
template<CharType T>
void processNarrowChar(T c) {
std::cout << "Narrow character: " << static_cast<int>(c) << std::endl;
}

template<WideCharType T>
void processWideChar(T c) {
std::cout << "Wide character: " << static_cast<int>(c) << std::endl;
}

// 使用
processNarrowChar('A'); // 调用processNarrowChar
processWideChar(L'A'); // 调用processWideChar
processWideChar(u'A'); // 调用processWideChar
processWideChar(U'A'); // 调用processWideChar

2. 字符类型的类型特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <type_traits>

// 字符类型的类型特征
static_assert(std::is_integral_v<char>);
static_assert(std::is_trivial_v<char>);
static_assert(std::is_standard_layout_v<char>);
static_assert(std::is_pod_v<char>); // C++20中已弃用

// 字符类型的比较
static_assert(sizeof(char) == 1);
static_assert(sizeof(wchar_t) >= sizeof(char));
static_assert(sizeof(char16_t) == 2);
static_assert(sizeof(char32_t) == 4);
static_assert(sizeof(char8_t) == 1);

// 字符类型的有符号性
static_assert(std::is_signed_v<signed char>);
static_assert(!std::is_signed_v<unsigned char>);
// char的有符号性是实现定义的

字符类型的底层实现细节

1. 字符类型的存储

  • char:通常存储在一个字节中,其有符号性由编译器决定
  • wchar_t:在Windows上通常为2字节(UTF-16),在Unix上通常为4字节(UTF-32)
  • char16_t:固定为2字节,用于UTF-16编码
  • char32_t:固定为4字节,用于UTF-32编码
  • char8_t:固定为1字节,用于UTF-8编码(C++20+)

2. 字符类型的对齐

1
2
3
4
5
6
7
8
9
// 字符类型的对齐要求
std::cout << "Alignment of char: " << alignof(char) << " bytes" << std::endl;
std::cout << "Alignment of wchar_t: " << alignof(wchar_t) << " bytes" << std::endl;
std::cout << "Alignment of char16_t: " << alignof(char16_t) << " bytes" << std::endl;
std::cout << "Alignment of char32_t: " << alignof(char32_t) << " bytes" << std::endl;
std::cout << "Alignment of char8_t: " << alignof(char8_t) << " bytes" << std::endl;

// 注意:对齐要求通常等于类型大小
// 但在某些平台上可能不同

字符类型的性能分析

1. 字符操作的性能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <benchmark/benchmark.h>

// 基准测试:字符比较
static void BM_CharComparison(benchmark::State& state) {
char c1 = 'A', c2 = 'B';
for (auto _ : state) {
bool result = c1 == c2;
benchmark::DoNotOptimize(result);
}
}

// 基准测试:字符转换
static void BM_CharConversion(benchmark::State& state) {
char c = 'A';
for (auto _ : state) {
int i = static_cast<int>(c);
benchmark::DoNotOptimize(i);
}
}

BENCHMARK(BM_CharComparison);
BENCHMARK(BM_CharConversion);
BENCHMARK_MAIN();

2. 字符类型的内存访问模式

  • char:单字节访问,缓存友好
  • wchar_t:多字节访问,可能导致对齐问题
  • char16_t:2字节访问,需要2字节对齐
  • char32_t:4字节访问,需要4字节对齐
  • char8_t:单字节访问,缓存友好

总结

字符类型是C++中最基本的数据类型之一,选择合适的字符类型对于字符串处理、编码转换和性能优化至关重要。现代C++提供了多种字符类型以支持不同的编码方案,特别是Unicode编码。

在实际编程中,应根据具体的使用场景选择合适的字符类型:

  • 对于ASCII字符和UTF-8编码,使用charchar8_t(C++20+)
  • 对于UTF-16编码,使用char16_t
  • 对于UTF-32编码,使用char32_t
  • 对于原始字节数据,使用unsigned char
  • 对于与平台API交互,使用wchar_t(特别是Windows)

通过理解字符类型的底层实现、内存表示和性能特性,可以编写更加高效、可靠的字符处理代码。

C风格字符串的深度解析

字符串字面量的底层实现

C风格字符串是由字符组成的数组,以空字符('\0')结尾。其底层实现涉及编译期处理、内存布局和运行时操作:

1. 字符串字面量的编译期处理

字符串字面量在编译期被处理并存储在只读内存段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 字符串字面量的编译期处理
const char* str1 = "Hello, world!"; // 指向只读内存中的字符串字面量
const char str2[] = "Hello, world!"; // 复制到栈中的字符数组

// 字符串字面量的类型推导
static_assert(std::is_same_v<decltype("Hello"), const char[6]>);

// 字符串字面量的长度计算(编译期)
static_assert(sizeof("Hello") == 6); // 'H','e','l','l','o','\0'
static_assert(sizeof("Hello"s) == sizeof(std::string)); // std::string对象大小

// 字符串字面量的常量表达式属性
constexpr auto strLen = sizeof("Hello") - 1;
static_assert(strLen == 5);

2. 字符串字面量的内存布局与链接

字符串字面量的内存布局受编译器和链接器的影响:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 字符串字面量的内存布局
const char* s1 = "Hello"; // 存储在.rodata段
const char* s2 = "Hello"; // 可能与s1指向同一内存地址(字符串池化)

// 字符串池化(String Pooling)
std::cout << "s1: " << static_cast<const void*>(s1) << std::endl;
std::cout << "s2: " << static_cast<const void*>(s2) << std::endl;
std::cout << "s1 == s2: " << (s1 == s2) << std::endl; // 可能为1(池化)

// 底层实现细节
// 1. 编译期:字符串字面量被存储在汇编的.data或.rodata段
// 2. 链接期:相同的字符串字面量可能被合并(字符串池化)
// 3. 运行期:通过指针访问只读内存中的字符串数据

// 汇编表示(x86-64)
// .section .rodata
// .LC0:
// .string "Hello"
// .text
// mov eax, OFFSET FLAT:.LC0
// mov qword ptr [s1], rax

3. 多行字符串和原始字符串的深度解析

C++11+支持原始字符串字面量,其底层实现使用分隔符机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 多行字符串的编译期拼接
const char* multiLine = "Line 1\n"
"Line 2\n"
"Line 3"; // 编译期拼接为单个字符串

// 原始字符串的底层实现
const char* raw = R"(Raw string with \n and ")"; // 不需要转义

// 带分隔符的原始字符串
const char* rawWithDelimiter = R"(=)
Raw string with
newlines and "quotes"
(=)"; // 使用(=)作为分隔符

// 原始字符串的编译期处理
// 1. 识别起始分隔符:R"(
// 2. 扫描内容直到找到匹配的结束分隔符:)"
// 3. 生成对应的字符串字面量,保留所有字符(包括换行符)

// 原始字符串与普通字符串的比较
const char* normal = "Raw string with \\n and \"";
const char* rawEq = R"(Raw string with \n and ")";
// normal 和 rawEq 在运行时指向相同的字符序列

4. 字符串字面量的安全性考虑

字符串字面量存储在只读内存中,修改它们会导致未定义行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 危险:尝试修改字符串字面量
char* str = "Hello"; // 弃用:应使用const char*
str[0] = 'h'; // 未定义行为:修改只读内存

// 安全:使用可修改的字符数组
char str[] = "Hello"; // 存储在栈中,可修改
str[0] = 'h'; // 安全:修改栈内存

// 安全:动态分配可修改的字符串
char* dynamicStr = new char[6];
std::strcpy(dynamicStr, "Hello");
dynamicStr[0] = 'h'; // 安全:修改堆内存
delete[] dynamicStr;

// 编译器警告
// GCC: warning: ISO C++11 does not allow conversion from string literal to 'char*'
// Clang: warning: converting string literal to 'char *' is deprecated

字符串操作函数的深度分析

C标准库提供了一系列字符串操作函数,声明在<cstring>头文件中。这些函数的底层实现涉及内存操作、算法设计和性能优化:

1. 字符串长度计算的底层实现

strlen函数通过线性扫描计算字符串长度,底层使用字节级比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cstring>

// 字符串长度计算
const char* str = "Hello";
size_t length = strlen(str); // 返回5(不包括空字符)

// 底层实现原理(简化版)
size_t my_strlen(const char* s) {
const char* p = s;
while (*p) {
++p;
}
return p - s;
}

// 时间复杂度:O(n),其中n是字符串长度
// 空间复杂度:O(1)

// 安全的字符串长度计算(处理可能没有空字符的情况)
size_t safeStrlen(const char* str, size_t maxLength) {
if (!str) return 0;

const char* end = str;
while (*end && (end - str) < maxLength) {
++end;
}
return end - str;
}

// SIMD优化的字符串长度计算
// 现代编译器会使用向量指令批量比较字节,提高长字符串的处理速度
// 例如:使用SSE/AVX指令一次比较16/32个字节

2. 字符串复制的内存操作

strcpystrncpy函数的底层实现涉及内存复制和边界检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 字符串复制
char dest[20];

// 不安全:可能缓冲区溢出
strcpy(dest, "Hello");

// 安全的字符串复制
strncpy(dest, "Hello", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // 确保 null 终止

// 底层实现原理(简化版)
char* my_strcpy(char* dest, const char* src) {
char* p = dest;
while ((*p++ = *src++)) {
// 空循环体
}
return dest;
}

// 注意:strncpy 的特性
// 1. 当源字符串长度小于指定长度时,会用空字符填充剩余空间
// 2. 当源字符串长度大于等于指定长度时,不会添加空字符
// 3. 时间复杂度:O(n),其中n是指定的复制长度

// 自定义安全复制函数
char* safeStrcpy(char* dest, size_t destSize, const char* src) {
if (!dest || !src || destSize == 0) return nullptr;

size_t srcLen = strlen(src);
size_t copyLen = (srcLen < destSize - 1) ? srcLen : (destSize - 1);

memcpy(dest, src, copyLen);
dest[copyLen] = '\0';

return dest;
}

// 性能优化:使用memcpy替代strcpy(当已知字符串长度时)
// memcpy是按字节块复制,比逐字符复制更快

3. 字符串连接的内存管理

strcatstrncat函数的底层实现涉及长度计算和内存复制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 字符串连接
char dest[20] = "Hello";

// 不安全:可能缓冲区溢出
strcat(dest, " world");

// 安全的字符串连接
size_t destLen = strlen(dest);
strncat(dest, " world", sizeof(dest) - destLen - 1);

// 底层实现原理(简化版)
char* my_strcat(char* dest, const char* src) {
char* p = dest;
while (*p) {
++p;
}
while ((*p++ = *src++)) {
// 空循环体
}
return dest;
}

// 时间复杂度:O(n + m),其中n是目标字符串长度,m是源字符串长度
// 空间复杂度:O(1)(使用现有缓冲区)

// 自定义安全连接函数
char* safeStrcat(char* dest, size_t destSize, const char* src) {
if (!dest || !src || destSize == 0) return nullptr;

size_t destLen = strlen(dest);
if (destLen >= destSize - 1) return dest; // 目标已满

size_t srcLen = strlen(src);
size_t copyLen = (srcLen < destSize - destLen - 1) ? srcLen : (destSize - destLen - 1);

memcpy(dest + destLen, src, copyLen);
dest[destLen + copyLen] = '\0';

return dest;
}

// 性能优化:预分配足够空间
// 避免多次拼接导致的重复内存操作

4. 字符串比较的算法实现

strcmpstrncmp函数的底层实现使用逐字节比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 字符串比较
int result = strcmp("Hello", "World"); // 返回负数
result = strcmp("Hello", "Hello"); // 返回0
result = strcmp("World", "Hello"); // 返回正数

// 底层实现原理(简化版)
int my_strcmp(const char* s1, const char* s2) {
while (*s1 && *s1 == *s2) {
++s1;
++s2;
}
return static_cast<unsigned char>(*s1) - static_cast<unsigned char>(*s2);
}

// 时间复杂度:
// - 最好情况:O(1)(第一个字符不同)
// - 最坏情况:O(n)(所有字符都相同,直到空字符)
// - 平均情况:取决于字符串的相似程度

// 安全的字符串比较
result = strncmp("Hello", "World", 3); // 比较前3个字符

// 自定义大小写不敏感比较
int strcmpIgnoreCase(const char* s1, const char* s2) {
if (!s1 || !s2) return (s1 == s2) ? 0 : (s1 ? 1 : -1);

while (*s1 && *s2) {
unsigned char c1 = static_cast<unsigned char>(tolower(*s1));
unsigned char c2 = static_cast<unsigned char>(tolower(*s2));

if (c1 != c2) return (c1 < c2) ? -1 : 1;
++s1;
++s2;
}

return (s1 && !s2) ? 1 : (!s1 && s2) ? -1 : 0;
}

// 性能优化:短字符串比较
// 对于短字符串,现代编译器会内联strcmp并使用寄存器操作

5. 字符串搜索的算法分析

strchrstrrchrstrstr函数的底层实现涉及搜索算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// 字符串搜索
const char* found = strchr("Hello", 'l'); // 查找第一个 'l'
found = strrchr("Hello", 'l'); // 查找最后一个 'l'
found = strstr("Hello world", "world"); // 查找子字符串

// 底层实现原理(strchr简化版)
const char* my_strchr(const char* s, int c) {
while (*s && *s != static_cast<char>(c)) {
++s;
}
return *s == static_cast<char>(c) ? s : nullptr;
}

// 底层实现原理(strstr简化版)
const char* my_strstr(const char* haystack, const char* needle) {
if (!*needle) {
return haystack;
}

const char* h = haystack;
while (*h) {
const char* h2 = h;
const char* n = needle;
while (*h2 && *n && *h2 == *n) {
++h2;
++n;
}
if (!*n) {
return h;
}
++h;
}
return nullptr;
}

// 时间复杂度:
// - strchr: O(n),其中n是字符串长度
// - strstr: O(n*m),其中n是 haystack 长度,m是 needle 长度
// (现代实现可能使用更高效的算法如KMP)

// 自定义高效搜索函数(Boyer-Moore 算法思想)
const char* fastStrStr(const char* haystack, const char* needle) {
if (!haystack || !needle || !*needle) return haystack;

const char* haystackEnd = haystack;
while (*haystackEnd) ++haystackEnd;

size_t needleLen = strlen(needle);
if (needleLen > static_cast<size_t>(haystackEnd - haystack)) {
return nullptr;
}

// 简化的Boyer-Moore算法
const char* haystackPtr = haystack;
while (haystackPtr <= haystackEnd - needleLen) {
const char* needlePtr = needle;
const char* haystackCheck = haystackPtr;

while (*needlePtr && *haystackCheck == *needlePtr) {
++needlePtr;
++haystackCheck;
}

if (!*needlePtr) {
return haystackPtr;
}

++haystackPtr;
}

return nullptr;
}

// 性能优化:
// 1. 对于频繁查找的模式,考虑使用预处理(如构建有限自动机)
// 2. 对于长字符串,考虑使用更高效的字符串匹配算法
// 3. 利用现代CPU的SIMD指令加速字符串查找

字符串输入和输出的最佳实践

1. 字符串输入的安全性与性能

字符串输入操作涉及缓冲区管理、错误处理和性能优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 字符串输入
char name[50];

// 不安全:可能缓冲区溢出
std::cin >> name; // 读取到空格为止

// 安全的字符串输入
std::cin.getline(name, sizeof(name)); // 读取整行,自动处理缓冲区大小

// 处理输入溢出
if (std::cin.fail()) {
std::cin.clear(); // 清除错误状态
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略剩余输入
std::cout << "Input too long, truncated." << std::endl;
}

// 自定义安全输入函数
bool safeGetLine(char* buffer, size_t bufferSize) {
if (!buffer || bufferSize == 0) return false;

std::cin.getline(buffer, bufferSize);

if (std::cin.fail()) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return false;
}

return true;
}

// 底层实现原理:
// 1. getline函数会读取字符直到遇到换行符或达到缓冲区大小
// 2. 当输入长度超过缓冲区大小时,会设置failbit
// 3. 忽略剩余输入需要使用ignore函数清空输入流

// 性能优化:
// 对于频繁输入操作,考虑使用预分配的缓冲区
// 避免每次输入都重新分配内存

// 现代C++风格的安全输入
std::string safeInput;
std::getline(std::cin, safeInput); // 自动管理内存,无缓冲区溢出风险

2. 字符串输出的优化与底层实现

字符串输出操作涉及格式化处理、缓冲区管理和性能优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 字符串输出
const char* str = "Hello, world!";

// 标准输出
std::cout << str << std::endl;

// 优化的字符串输出(避免重复计算长度)
void fastPrint(const char* str) {
if (!str) return;

// 计算长度
size_t len = strlen(str);

// 一次性输出
std::cout.write(str, len);
std::cout << std::endl;
}

// 使用puts(自动添加换行符)
puts(str); // 等同于 printf("%s\n", str);

// 注意:puts 只能输出以空字符结尾的字符串

// 底层实现原理:
// 1. std::cout << str:调用operator<<(const char*),内部处理字符串输出
// 2. std::cout.write:直接写入指定长度的字符,避免多次计算长度
// 3. puts:C标准库函数,直接将字符串写入标准输出并添加换行符

// 性能比较:
// - std::cout << str:最灵活,但可能有多次函数调用开销
// - std::cout.write:适合已知长度的字符串,减少函数调用
// - puts:C风格输出,对于简单字符串可能更快
// - printf:适合复杂格式化,但类型不安全

// 现代C++风格的字符串输出
std::string message = "Hello, C++!";
std::cout << message << std::endl;

// C++20+格式化输出
#include <format>
std::string formatted = std::format("Hello, {}!", "World");
std::cout << formatted << std::endl;

3. 字符串输入输出的性能优化策略

针对不同场景的性能优化策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 批量输入优化
void batchInput(std::vector<std::string>& lines, size_t maxLines) {
lines.reserve(maxLines); // 预分配空间
std::string line;
for (size_t i = 0; i < maxLines && std::getline(std::cin, line); ++i) {
lines.push_back(std::move(line)); // 使用移动语义避免复制
}
}

// 批量输出优化
void batchOutput(const std::vector<std::string>& lines) {
// 合并输出减少I/O操作
std::stringstream ss;
for (const auto& line : lines) {
ss << line << '\n';
}
std::cout << ss.str(); // 一次性输出
}

// 内存映射文件(处理大文件)
#include <fstream>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void processLargeFile(const char* filename) {
int fd = open(filename, O_RDONLY);
if (fd == -1) return;

struct stat sb;
if (fstat(fd, &sb) == -1) {
close(fd);
return;
}

char* addr = static_cast<char*>(mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0));
if (addr != MAP_FAILED) {
// 直接访问内存映射区域
// 避免多次I/O操作
munmap(addr, sb.st_size);
}

close(fd);
}

// 最佳实践总结:
// 1. 对于小量输入输出:使用标准流操作
// 2. 对于大量数据:考虑批量处理和内存映射
// 3. 对于格式化需求:优先使用std::format(C++20+)
// 4. 对于性能关键场景:使用底层I/O函数和内存管理技术

C风格字符串的性能优化

1. 内存布局优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 内存布局优化
// 1. 避免频繁的字符串复制
// 2. 预分配足够的空间
// 3. 使用栈上的缓冲区处理小字符串

// 小字符串优化
void processSmallString(const char* input) {
char buffer[256]; // 栈上的小缓冲区
size_t len = strlen(input);

if (len < sizeof(buffer) - 1) {
// 使用栈缓冲区
strncpy(buffer, input, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
// 处理buffer
} else {
// 使用动态分配
char* largeBuffer = new char[len + 1];
strcpy(largeBuffer, input);
// 处理largeBuffer
delete[] largeBuffer;
}
}

2. 字符串操作的性能分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <benchmark/benchmark.h>

// 基准测试:字符串长度计算
static void BM_Strlen(benchmark::State& state) {
const char* str = "Hello, world! This is a test string.";
for (auto _ : state) {
size_t len = strlen(str);
benchmark::DoNotOptimize(len);
}
}

// 基准测试:字符串复制
static void BM_Strcpy(benchmark::State& state) {
const char* src = "Hello, world!";
char dest[20];
for (auto _ : state) {
strcpy(dest, src);
benchmark::DoNotOptimize(dest);
}
}

// 基准测试:字符串比较
static void BM_Strcmp(benchmark::State& state) {
const char* s1 = "Hello, world!";
const char* s2 = "Hello, world!";
for (auto _ : state) {
int result = strcmp(s1, s2);
benchmark::DoNotOptimize(result);
}
}

BENCHMARK(BM_Strlen);
BENCHMARK(BM_Strcpy);
BENCHMARK(BM_Strcmp);
BENCHMARK_MAIN();

3. 字符串操作的高级优化技巧

  • 避免重复计算长度:缓存 strlen 的结果,减少重复扫描
  • 使用 memcpy 替代 strcpy:对于已知长度的字符串,提高复制速度
  • 使用 memcmp 替代 strcmp:对于已知长度的字符串,减少函数调用开销
  • 预分配足够空间:避免频繁的重新分配和复制
  • 使用栈上缓冲区:对于小字符串,利用栈内存的快速访问特性
  • 内存对齐:提高SIMD指令的访问效率
  • 内存池:避免频繁的动态内存分配和释放
  • 缓存友好:分块处理大字符串,减少缓存未命中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 优化的字符串操作
void optimizedStringOperations() {
const char* src = "Hello, world!";
size_t srcLen = strlen(src);

// 1. 避免重复计算长度
char dest[20];
strncpy(dest, src, sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';

// 2. 使用 memcpy 替代 strcpy
char dest2[20];
memcpy(dest2, src, srcLen);
dest2[srcLen] = '\0';

// 3. 使用 memcmp 替代 strcmp
const char* s1 = "Hello";
const char* s2 = "Hello";
bool equal = (memcmp(s1, s2, 5) == 0);
}

// 内存对齐优化
void alignedStringProcessing() {
// 对齐到16字节边界的缓冲区
alignas(16) char alignedBuffer[256];

const char* input = "Aligned memory test string";
size_t inputLen = strlen(input);

// 复制到对齐缓冲区
memcpy(alignedBuffer, input, inputLen + 1);

// 对齐内存可以提高SIMD指令的访问速度
// 例如:使用SSE/AVX指令进行字符串处理
}

// 内存池优化
class StringMemoryPool {
private:
static constexpr size_t BLOCK_SIZE = 4096;
std::vector<char*> blocks;
size_t currentBlock = 0;
size_t currentPos = 0;

public:
~StringMemoryPool() {
for (char* block : blocks) {
delete[] block;
}
}

char* allocate(size_t size) {
if (currentBlock >= blocks.size() || currentPos + size > BLOCK_SIZE) {
// 分配新块
char* newBlock = new char[BLOCK_SIZE];
blocks.push_back(newBlock);
currentBlock = blocks.size() - 1;
currentPos = 0;
}

char* result = blocks[currentBlock] + currentPos;
currentPos += size;
return result;
}

char* copyString(const char* str) {
size_t len = strlen(str) + 1;
char* buffer = allocate(len);
memcpy(buffer, str, len);
return buffer;
}
};

// 缓存友好的字符串处理
void cacheFriendlyStringProcess(const char* str) {
size_t len = strlen(str);
const size_t CACHE_LINE_SIZE = 64;

// 分块处理,利用缓存行
for (size_t i = 0; i < len; i += CACHE_LINE_SIZE) {
size_t blockSize = std::min(CACHE_LINE_SIZE, len - i);
// 处理当前块
processStringBlock(str + i, blockSize);
}
}

4. 字符串操作的SIMD优化

利用现代CPU的SIMD指令加速字符串操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// SIMD优化的字符串长度计算
#include <immintrin.h>

size_t simd_strlen(const char* s) {
const char* p = s;

// 对齐到16字节边界
while (reinterpret_cast<uintptr_t>(p) % 16 != 0) {
if (!*p) return p - s;
++p;
}

// 使用SSE2指令批量比较
const __m128i zero = _mm_setzero_si128();
while (true) {
__m128i chunk = _mm_load_si128(reinterpret_cast<const __m128i*>(p));
__m128i cmp = _mm_cmpeq_epi8(chunk, zero);
int mask = _mm_movemask_epi8(cmp);

if (mask != 0) {
// 找到空字符
return p - s + __builtin_ctz(mask);
}

p += 16;
}
}

// SIMD优化的字符串比较
int simd_strcmp(const char* s1, const char* s2) {
const char* p1 = s1;
const char* p2 = s2;

// 对齐到16字节边界
while (reinterpret_cast<uintptr_t>(p1) % 16 != 0 && *p1 && *p1 == *p2) {
++p1;
++p2;
}

if (!*p1 || *p1 != *p2) {
return static_cast<unsigned char>(*p1) - static_cast<unsigned char>(*p2);
}

// 使用SSE2指令批量比较
const __m128i zero = _mm_setzero_si128();
while (true) {
__m128i chunk1 = _mm_load_si128(reinterpret_cast<const __m128i*>(p1));
__m128i chunk2 = _mm_load_si128(reinterpret_cast<const __m128i*>(p2));
__m128i cmp_eq = _mm_cmpeq_epi8(chunk1, chunk2);
__m128i cmp_zero1 = _mm_cmpeq_epi8(chunk1, zero);

int eq_mask = _mm_movemask_epi8(cmp_eq);
int zero_mask = _mm_movemask_epi8(cmp_zero1);

if (eq_mask != 0xFFFF) {
// 找到不同的字符
int pos = __builtin_ctz(~eq_mask);
return static_cast<unsigned char>(p1[pos]) - static_cast<unsigned char>(p2[pos]);
}

if (zero_mask != 0) {
// 找到空字符
return 0;
}

p1 += 16;
p2 += 16;
}
}

// 混合实现:根据字符串长度选择最优算法
size_t optimized_strlen(const char* s) {
// 快速长度检查
if (!s[0]) return 0;
if (!s[1]) return 1;
if (!s[2]) return 2;
if (!s[3]) return 3;

// 估算长度
size_t len = 0;
while (s[len]) {
len += 4;
}

// 精确计算
while (len > 0 && !s[len-1]) {
len--;
}

return len;
}

// 性能提升:
// - 对于长字符串,SIMD优化可以提供2-4倍的性能提升
// - 对于短字符串,快速路径可以减少函数调用开销
// - 混合实现可以适应不同长度的字符串

5. 字符串操作的最佳实践

综合各种优化策略,总结字符串操作的最佳实践:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 字符串操作的最佳实践
void stringOperationBestPractices() {
// 1. 小字符串处理(< 64字节)
const char* smallStr = "Hello, world!";
char smallBuffer[128]; // 栈上缓冲区
size_t smallLen = strlen(smallStr);
memcpy(smallBuffer, smallStr, smallLen + 1);

// 2. 大字符串处理(> 64字节)
const char* largeStr = "This is a very long string that exceeds the cache line size...";
size_t largeLen = strlen(largeStr);

// 使用智能指针管理内存
std::unique_ptr<char[]> largeBuffer(new char[largeLen + 1]);
memcpy(largeBuffer.get(), largeStr, largeLen + 1);

// 3. 字符串比较
const char* s1 = "Test string for comparison";
const char* s2 = "Test string for comparison";
size_t compareLen = strlen(s1);
bool equal = (memcmp(s1, s2, compareLen) == 0);

// 4. 字符串查找
const char* haystack = "Hello, world! This is a test string.";
const char* needle = "test";
const char* found = strstr(haystack, needle);

// 5. 字符串连接
char concatBuffer[256] = "Hello, ";
const char* suffix = "world!";
size_t prefixLen = strlen(concatBuffer);
size_t suffixLen = strlen(suffix);

if (prefixLen + suffixLen < sizeof(concatBuffer) - 1) {
memcpy(concatBuffer + prefixLen, suffix, suffixLen + 1);
}

// 6. 内存池使用
StringMemoryPool pool;
char* pooledStr = pool.copyString("Memory pool allocated string");
// 使用pooledStr
// 不需要手动释放,内存池析构时会自动清理
}

// 性能优化总结:
// 1. 选择合适的内存分配策略(栈 vs 堆 vs 内存池)
// 2. 利用内存对齐提高SIMD指令效率
// 3. 减少缓存未命中(分块处理)
// 4. 对于已知长度的字符串,使用memcpy/memcmp替代strcpy/strcmp
// 5. 对于长字符串,使用SIMD指令加速处理
// 6. 对于短字符串,使用快速路径减少开销
// 7. 避免重复计算字符串长度
// 8. 预分配足够空间,避免频繁重新分配

C风格字符串的安全性

1. 常见安全问题的深度分析

安全问题技术原因风险后果解决方案
缓冲区溢出输入字符串长度超过缓冲区大小,导致覆盖相邻内存程序崩溃、代码执行、数据泄露使用 strncpystrncat 等带长度限制的函数
空指针解引用对 nullptr 调用字符串函数,导致内存访问异常程序崩溃、拒绝服务检查指针是否为 nullptr 再调用函数
未终止的字符串缺少空字符,导致字符串函数扫描越界未定义行为、内存访问错误确保所有字符串都以 '\0' 结尾
字符串字面量修改尝试修改只读内存中的字符串,违反内存保护程序崩溃、段错误使用 const char* 并避免修改
整数溢出字符串长度计算时发生溢出,导致逻辑错误缓冲区溢出、内存损坏使用 size_t 类型并检查边界条件
格式化字符串漏洞用户输入直接作为格式字符串,执行未预期操作代码执行、内存泄露使用固定格式字符串,用户输入作为参数
时间-of-check 到 time-of-use 漏洞检查和使用之间字符串状态发生变化缓冲区溢出、安全绕过原子操作或复制到临时缓冲区

2. 安全的字符串处理实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// 安全的字符串处理
bool safeStringCopy(char* dest, size_t destSize, const char* src) {
// 检查参数有效性
if (!dest || !src || destSize == 0) {
return false;
}

// 计算源字符串长度(安全计算)
size_t srcLen = 0;
const char* p = src;
while (*p && srcLen < SIZE_MAX) {
++p;
++srcLen;
}

// 检查整数溢出
if (srcLen == SIZE_MAX) {
return false;
}

// 检查是否有足够空间
if (srcLen >= destSize) {
// 截断并添加终止符
memcpy(dest, src, destSize - 1);
dest[destSize - 1] = '\0';
return false; // 表示发生了截断
}

// 复制字符串(包括空字符)
memcpy(dest, src, srcLen + 1);
return true; // 成功复制
}

// 安全的字符串连接
bool safeStringConcat(char* dest, size_t destSize, const char* src) {
// 检查参数有效性
if (!dest || !src || destSize == 0) {
return false;
}

// 计算当前长度(安全计算)
size_t destLen = 0;
char* q = dest;
while (*q && destLen < destSize - 1) {
++q;
++destLen;
}

// 检查目标是否已满
if (destLen >= destSize - 1) {
return false; // 目标已满
}

// 计算剩余空间
size_t remaining = destSize - destLen;

// 计算源字符串长度(安全计算)
size_t srcLen = 0;
const char* p = src;
while (*p && srcLen < remaining - 1) {
++p;
++srcLen;
}

// 检查是否有足够空间
if (srcLen >= remaining) {
// 截断并添加终止符
memcpy(dest + destLen, src, remaining - 1);
dest[destLen + remaining - 1] = '\0';
return false; // 表示发生了截断
}

// 连接字符串(包括空字符)
memcpy(dest + destLen, src, srcLen + 1);
return true; // 成功连接
}

// 安全的字符串长度计算
size_t safeStringLength(const char* str, size_t maxLength) {
if (!str || maxLength == 0) {
return 0;
}

size_t len = 0;
const char* p = str;
while (*p && len < maxLength) {
++p;
++len;
}

return len;
}

// 安全的字符串比较
int safeStringCompare(const char* s1, const char* s2, size_t maxLength) {
if (!s1 && !s2) return 0;
if (!s1) return -1;
if (!s2) return 1;

size_t i = 0;
while (i < maxLength && *s1 && *s2 && *s1 == *s2) {
++s1;
++s2;
++i;
}

if (i >= maxLength) return 0;
if (!*s1 && !*s2) return 0;
if (!*s1) return -1;
if (!*s2) return 1;
return static_cast<unsigned char>(*s1) - static_cast<unsigned char>(*s2);
}

3. 安全的字符串操作库实现

实现一个完整的安全字符串操作库,提供类型安全和边界检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// 安全字符串操作库
class SafeString {
private:
char* buffer;
size_t capacity;
size_t length;

// 确保容量
bool ensureCapacity(size_t required) {
if (required <= capacity) {
return true;
}

// 计算新容量(指数增长)
size_t newCapacity = capacity * 2;
if (newCapacity < required) {
newCapacity = required;
}

// 检查整数溢出
if (newCapacity < required || newCapacity > SIZE_MAX - 1) {
return false;
}

// 分配新内存
char* newBuffer = new char[newCapacity + 1];
if (!newBuffer) {
return false;
}

// 复制现有内容
if (buffer) {
memcpy(newBuffer, buffer, length + 1);
delete[] buffer;
} else {
newBuffer[0] = '\0';
}

buffer = newBuffer;
capacity = newCapacity;
return true;
}

public:
SafeString(size_t initialCapacity = 64)
: buffer(nullptr), capacity(0), length(0) {
ensureCapacity(initialCapacity);
}

~SafeString() {
delete[] buffer;
}

// 复制构造函数
SafeString(const SafeString& other)
: buffer(nullptr), capacity(0), length(0) {
if (other.buffer) {
ensureCapacity(other.length);
memcpy(buffer, other.buffer, other.length + 1);
length = other.length;
}
}

// 移动构造函数
SafeString(SafeString&& other) noexcept
: buffer(other.buffer), capacity(other.capacity), length(other.length) {
other.buffer = nullptr;
other.capacity = 0;
other.length = 0;
}

// 从 C 风格字符串构造
SafeString(const char* str)
: buffer(nullptr), capacity(0), length(0) {
if (str) {
size_t strLen = strlen(str);
ensureCapacity(strLen);
memcpy(buffer, str, strLen + 1);
length = strLen;
}
}

// 赋值运算符
SafeString& operator=(const SafeString& other) {
if (this != &other) {
if (other.buffer) {
ensureCapacity(other.length);
memcpy(buffer, other.buffer, other.length + 1);
length = other.length;
} else {
clear();
}
}
return *this;
}

// 移动赋值运算符
SafeString& operator=(SafeString&& other) noexcept {
if (this != &other) {
delete[] buffer;
buffer = other.buffer;
capacity = other.capacity;
length = other.length;
other.buffer = nullptr;
other.capacity = 0;
other.length = 0;
}
return *this;
}

// 添加字符串
bool append(const char* str) {
if (!str) {
return false;
}

size_t strLen = strlen(str);
if (!ensureCapacity(length + strLen)) {
return false;
}

memcpy(buffer + length, str, strLen + 1);
length += strLen;
return true;
}

// 添加单个字符
bool append(char c) {
if (!ensureCapacity(length + 1)) {
return false;
}

buffer[length] = c;
buffer[length + 1] = '\0';
++length;
return true;
}

// 清空字符串
void clear() {
if (buffer) {
buffer[0] = '\0';
}
length = 0;
}

// 获取长度
size_t size() const {
return length;
}

// 获取容量
size_t getCapacity() const {
return capacity;
}

// 转换为 C 风格字符串
const char* c_str() const {
return buffer ? buffer : "";
}

// 比较运算符
bool operator==(const SafeString& other) const {
return strcmp(c_str(), other.c_str()) == 0;
}

bool operator!=(const SafeString& other) const {
return !(*this == other);
}

bool operator<(const SafeString& other) const {
return strcmp(c_str(), other.c_str()) < 0;
}

bool operator<=(const SafeString& other) const {
return strcmp(c_str(), other.c_str()) <= 0;
}

bool operator>(const SafeString& other) const {
return strcmp(c_str(), other.c_str()) > 0;
}

bool operator>=(const SafeString& other) const {
return strcmp(c_str(), other.c_str()) >= 0;
}
};

// 使用示例
void safeStringUsage() {
SafeString s1(100);
s1.append("Hello");
s1.append(", ");
s1.append("world!");

std::cout << "String: " << s1.c_str() << std::endl;
std::cout << "Length: " << s1.size() << std::endl;
std::cout << "Capacity: " << s1.getCapacity() << std::endl;

SafeString s2 = s1;
std::cout << "s2: " << s2.c_str() << std::endl;

SafeString s3 = "Test";
s3.append(s1.c_str());
std::cout << "s3: " << s3.c_str() << std::endl;
}

4. 安全编码实践指南

  1. 输入验证:对所有用户输入进行长度和内容验证
  2. 边界检查:在所有字符串操作中检查边界条件
  3. 内存安全:使用安全的内存分配和释放函数
  4. 类型安全:使用适当的类型(如 size_t 表示长度)
  5. 错误处理:检查所有函数调用的返回值
  6. 代码审查:定期审查字符串操作代码的安全性
  7. 工具检测:使用静态分析工具检测安全漏洞
  8. 最小权限:限制字符串操作的权限范围
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 安全编码示例
void secureInputHandling() {
constexpr size_t BUFFER_SIZE = 256;
char buffer[BUFFER_SIZE];

// 安全读取输入
std::cout << "Enter your name: " << std::endl;
std::cin.getline(buffer, BUFFER_SIZE);

// 处理输入溢出
if (std::cin.fail()) {
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
std::cout << "Input too long, truncated." << std::endl;
}

// 验证输入内容
bool valid = true;
for (size_t i = 0; buffer[i] && valid; ++i) {
if (!std::isprint(static_cast<unsigned char>(buffer[i])) && !std::isspace(static_cast<unsigned char>(buffer[i]))) {
valid = false;
}
}

if (valid) {
std::cout << "Hello, " << buffer << "!" << std::endl;
} else {
std::cout << "Invalid input." << std::endl;
}
}

// 安全的格式化输出
void securePrintf(const char* format, const char* input) {
// 固定格式字符串,用户输入作为参数
printf("%s: %s\n", format, input);
}

// 避免格式化字符串漏洞
void avoidFormatVulnerability(const char* userInput) {
// 不安全:用户输入直接作为格式字符串
// printf(userInput); // 危险!

// 安全:固定格式字符串
printf("User input: %s\n", userInput); // 安全
}

C风格字符串与现代C++的集成

1. 与 std::string 的互操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 与 std::string 的互操作
std::string cppStr = "Hello, world!";

// 转换为 C 风格字符串
const char* cStr = cppStr.c_str(); // C++11 前返回 const char*
const char* data = cppStr.data(); // C++17 后返回 char*

// 从 C 风格字符串创建 std::string
const char* cStr2 = "Hello";
std::string cppStr2(cStr2);

// 混合使用
void processString(const char* cStr) {
std::string cppStr(cStr);
// 使用 std::string 的成员函数
cppStr += "!";
// 转换回 C 风格字符串
std::cout << cppStr.c_str() << std::endl;
}

2. 与 std::string_view 的集成(C++17+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <string_view>

// 与 std::string_view 的集成
std::string_view sv1 = "Hello, world!"; // 直接从字符串字面量创建
std::string cppStr = "Hello";
std::string_view sv2(cppStr); // 从 std::string 创建

// 转换为 C 风格字符串
const char* cStr = sv1.data();

// 注意:std::string_view 不拥有字符串的所有权
// 因此在使用 data() 时,需要确保底层字符串仍然有效

// 安全使用
void processStringView(std::string_view sv) {
// 使用 string_view 的成员函数
std::cout << "Length: " << sv.length() << std::endl;
std::cout << "Substring: " << sv.substr(0, 5) << std::endl;

// 转换为 std::string(如果需要修改)
std::string copy = std::string(sv);
copy += "!";
std::cout << copy << std::endl;
}

C风格字符串的最佳实践

1. 何时使用C风格字符串

  • 与C库交互:当需要调用C语言库函数时
  • 性能关键路径:对于非常注重性能的场景
  • 内存受限环境:在内存受限的嵌入式系统中
  • 底层系统编程:操作系统内核、驱动程序等

2. 最佳实践总结

实践原因示例
使用 const char*避免修改字符串字面量const char* str = "Hello";
检查空指针避免空指针解引用if (str) { /* 处理 */ }
确保空字符终止避免未定义行为buffer[sizeof(buffer)-1] = '\0';
使用安全的字符串函数避免缓冲区溢出strncpy(dest, src, size);
缓存字符串长度提高性能size_t len = strlen(str);
优先使用 std::string现代C++推荐std::string str = "Hello";
使用 std::string_view高效字符串视图std::string_view sv = str;

总结

C风格字符串是C++中最基本的字符串表示形式,虽然在现代C++中推荐使用 std::stringstd::string_view,但C风格字符串仍然在许多场景中发挥着重要作用,特别是与C库交互、性能关键路径和底层系统编程。

掌握C风格字符串的底层实现、操作技巧和安全实践,对于编写高效、可靠的C++代码至关重要。通过合理使用安全的字符串函数、性能优化技巧以及与现代C++特性的集成,可以充分发挥C风格字符串的优势,同时避免其潜在的安全问题。

在实际编程中,应根据具体的使用场景选择合适的字符串表示形式:对于大多数应用场景,推荐使用 std::string;对于需要高效字符串视图的场景,使用 std::string_view;对于与C库交互或性能关键的场景,使用C风格字符串。

string 类的深度解析

std::string的底层实现

std::string是C++标准库提供的字符串类,其底层实现具有以下特点:

1. 内存布局

  • 小字符串优化(SSO):对于短字符串,直接存储在栈上,避免堆分配
  • 动态内存管理:对于长字符串,使用堆内存分配
  • 引用计数(某些实现):早期实现使用引用计数,现代实现通常不使用
1
2
3
4
5
6
7
8
9
// std::string的内存布局(简化)
// 小字符串:[大小][容量][字符数据...]
// 长字符串:[大小][容量][指向堆的指针]

std::string shortStr = "Hello"; // 存储在栈上(SSO)
std::string longStr = "Hello, world! This is a long string that exceeds SSO buffer."; // 存储在堆上

// 检查SSO阈值
std::cout << "Size of std::string: " << sizeof(std::string) << " bytes" << std::endl;

2. 容量管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 容量管理
std::string s = "Hello";
std::cout << "Size: " << s.size() << std::endl; // 5
std::cout << "Capacity: " << s.capacity() << std::endl; // 至少5

// 预留空间
s.reserve(100); // 预留100个字符的空间
std::cout << "Capacity after reserve: " << s.capacity() << std::endl; // 至少100

// 收缩空间
s.shrink_to_fit(); // 减少容量以匹配大小
std::cout << "Capacity after shrink_to_fit: " << s.capacity() << std::endl; // 5

// 注意:shrink_to_fit只是请求,编译器可能忽略

std::string的高级用法

1. 字符串初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <string>
#include <initializer_list>

// 字符串初始化
std::string s1; // 空字符串
std::string s2 = "Hello"; // 从C风格字符串初始化
std::string s3("Hello"); // 从C风格字符串初始化
std::string s4(5, 'a'); // 重复字符初始化:"aaaaa"
std::string s5(s2); // 复制构造
std::string s6(s2, 1, 3); // 从索引1开始,长度3:"ell"
std::string s7({ 'H', 'e', 'l', 'l', 'o' }); // 初始化列表
std::string s8(s2.begin(), s2.end()); // 迭代器范围
std::string s9("Hello", 5); // 从C风格字符串初始化,指定长度

// C++17:从string_view初始化
#include <string_view>
std::string_view sv = "Hello";
std::string s10(sv); // 从string_view初始化

2. 字符串赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 字符串赋值
std::string s;

// 从C风格字符串赋值
s = "World";

// 从另一个string赋值
s = s2;

// 使用assign成员函数
s.assign("Hello");
s.assign("Hello", 2, 3); // 从索引2开始,长度3:"llo"
s.assign(5, 'x'); // 重复字符:"xxxxx"
s.assign(s2.begin(), s2.end()); // 迭代器范围
s.assign({ 'W', 'o', 'r', 'l', 'd' }); // 初始化列表

// C++17:从string_view赋值
s.assign(sv);

3. 字符串拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 字符串拼接
std::string s = "Hello";

// 使用运算符+=
s += " world";
s += '!';

// 使用append成员函数
s.append("!");
s.append("abc", 2); // 添加前2个字符:"ab"
s.append(3, '?'); // 添加3个'?'
s.append(s2); // 添加另一个字符串
s.append(s2, 0, 2); // 添加另一个字符串的子串
s.append(s2.begin(), s2.end()); // 添加迭代器范围

// C++17:从string_view拼接
s.append(sv);

// 高效拼接:使用reserve避免多次分配
s.reserve(s.size() + 10); // 预留空间
s += " world!";

4. 字符串访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 字符串访问
std::string s = "Hello";

// 下标访问,无边界检查
char c1 = s[0]; // 'H'

// 带边界检查的访问
char c2 = s.at(0); // 'H'
try {
char c3 = s.at(10); // 抛出std::out_of_range异常
} catch (const std::out_of_range& e) {
std::cout << "Out of range: " << e.what() << std::endl;
}

// 首字符和尾字符
char front = s.front(); // 'H'
char back = s.back(); // 'o'

// 数据访问
const char* data = s.data(); // C++17前返回const char*
char* mutableData = s.data(); // C++17后返回char*
const char* cStr = s.c_str(); // 返回C风格字符串

// 迭代器访问
for (std::string::iterator it = s.begin(); it != s.end(); ++it) {
std::cout << *it;
}

// 范围for循环(C++11+)
for (char ch : s) {
std::cout << ch;
}

// 反向迭代器
for (std::string::reverse_iterator rit = s.rbegin(); rit != s.rend(); ++rit) {
std::cout << *rit;
}

5. 字符串修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 字符串修改
std::string s = "Hello, world!";

// 插入操作
s.insert(5, " "); // 在索引5处插入空格
s.insert(5, 3, '!'); // 在索引5处插入3个'!'
s.insert(5, "abc", 2); // 在索引5处插入前2个字符
s.insert(5, s2); // 在索引5处插入另一个字符串
s.insert(s.begin() + 5, 'x'); // 使用迭代器插入

// 删除操作
s.erase(5, 1); // 从索引5开始删除1个字符
s.erase(s.begin() + 5); // 删除迭代器指向的字符
s.erase(s.begin() + 5, s.end()); // 删除迭代器范围

// 替换操作
s.replace(0, 5, "Hi"); // 替换子字符串
s.replace(s.begin(), s.begin() + 5, "Hello"); // 使用迭代器替换
s.replace(0, 5, 3, 'x'); // 替换为重复字符
s.replace(0, 5, "abc", 2); // 替换为C风格字符串的子串

// 清空字符串
s.clear(); // 清空字符串
bool isEmpty = s.empty(); // 检查是否为空

// 调整大小
s.resize(10); // 调整大小为10,不足部分用''填充
s.resize(5, 'x'); // 调整大小为5,截断多余部分

6. 字符串搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 字符串搜索
std::string s = "Hello, world!";

// 查找单个字符
size_t pos1 = s.find('l'); // 查找第一个 'l',返回2
size_t pos2 = s.rfind('l'); // 查找最后一个 'l',返回10
size_t pos3 = s.find_first_of("aeiou"); // 查找第一个元音字母,返回1
size_t pos4 = s.find_last_of("aeiou"); // 查找最后一个元音字母,返回8
size_t pos5 = s.find_first_not_of("Helo,"); // 查找第一个不在集合中的字符,返回7

// 查找子字符串
size_t pos6 = s.find("world"); // 查找子字符串,返回7

// 指定起始位置
size_t pos7 = s.find('l', 3); // 从索引3开始查找 'l',返回3

// 查找失败返回std::string::npos
if (s.find("test") == std::string::npos) {
std::cout << "Substring not found" << std::endl;
}

// 自定义搜索函数(使用Boyer-Moore算法)
size_t boyerMooreSearch(const std::string& haystack, const std::string& needle) {
if (needle.empty()) return 0;
if (haystack.size() < needle.size()) return std::string::npos;

// 简化的Boyer-Moore算法
// ... 实现细节 ...
return haystack.find(needle); // 这里使用标准库实现
}

std::string的性能优化

1. 内存管理优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 内存管理优化
// 1. 预分配空间
std::string s;
s.reserve(1000); // 预分配1000个字符的空间

// 2. 避免不必要的复制
std::string createString() {
std::string s = "Hello, world!"; // 返回值优化(RVO)
return s; // 无复制
}

// 3. 使用移动语义
std::string s1 = "Hello";
std::string s2 = std::move(s1); // 移动构造,s1变为空

// 4. 小字符串优化(SSO)
std::string shortStr = "Hello"; // 存储在栈上
std::string longStr = "This is a long string that exceeds the SSO buffer size.";

// 5. 避免频繁的重新分配
std::string buildString(const std::vector<std::string>& parts) {
// 计算总长度
size_t totalLength = 0;
for (const auto& part : parts) {
totalLength += part.size();
}

// 预分配空间
std::string result;
result.reserve(totalLength);

// 拼接
for (const auto& part : parts) {
result += part;
}

return result;
}

2. 字符串操作优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// 字符串操作优化
// 1. 高效拼接:使用ostringstream
#include <sstream>
std::string buildString(int x, double y, const std::string& z) {
std::ostringstream oss;
oss << "x: " << x << ", y: " << y << ", z: " << z;
return oss.str();
}

// 2. 避免重复计算长度
std::string s = "Hello, world!";
size_t len = s.size(); // 缓存长度
for (size_t i = 0; i < len; ++i) {
// 处理s[i]
}

// 3. 使用std::move避免复制
void processString(std::string s) { // 值传递,允许移动
// 处理s
}

std::string s = "Hello";
processString(std::move(s)); // 移动,避免复制

// 4. 选择合适的字符串比较方法
std::string s1 = "Hello";
std::string s2 = "World";

// 对于前缀比较,使用compare效率更高
if (s1.compare(0, 2, s2, 0, 2) == 0) {
// 前2个字符相同
}

// 5. 利用字符串的连续性
// std::string的存储是连续的(C++11+)
std::string s = "Hello";
char* data = &s[0]; // 指向连续存储的首字符

std::string的现代C++特性

1. 移动语义(C++11+)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 移动语义
std::string createLargeString() {
std::string s(1000000, 'x'); // 大字符串
return s; // 移动返回,无复制
}

std::string s1 = createLargeString(); // 移动构造
std::string s2;
s2 = createLargeString(); // 移动赋值

// 显式移动
std::string s3 = "Hello";
std::string s4 = std::move(s3); // s3变为空

2. 字符串视图(C++17+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <string_view>

// 字符串视图
std::string s = "Hello, world!";
std::string_view sv(s); // 从string创建
std::string_view sv2 = "Hello";

// 传递string_view而非const string&
void processStringView(std::string_view sv) {
std::cout << "Length: " << sv.length() << std::endl;
std::cout << "Substring: " << sv.substr(0, 5) << std::endl;
}

// 从string_view转换为string
std::string s5(sv);

// 注意:string_view不拥有字符串的所有权
// 确保底层字符串在string_view使用期间有效

3. 字符串转换(C++11+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 字符串转换
// 数字转字符串
int i = 42;
double d = 3.14;
std::string s1 = std::to_string(i);
std::string s2 = std::to_string(d);

// 字符串转数字
std::string s3 = "42";
std::string s4 = "3.14";
int i2 = std::stoi(s3);
double d2 = std::stod(s4);

// 带错误处理的转换
try {
int i3 = std::stoi("abc"); // 抛出std::invalid_argument
int i4 = std::stoi("12345678901234567890"); // 抛出std::out_of_range
} catch (const std::exception& e) {
std::cout << "Conversion error: " << e.what() << std::endl;
}

// 自定义进制转换
std::string hexStr = "4A";
int hexVal = std::stoi(hexStr, nullptr, 16); // 16进制转换

// C++17:from_chars(更高效)
#include <charconv>
std::string numStr = "42";
int value;
auto [ptr, ec] = std::from_chars(numStr.data(), numStr.data() + numStr.size(), value);
if (ec == std::errc()) {
std::cout << "Value: " << value << std::endl;
}

4. 字符串格式化(C++20+)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <format>

// 字符串格式化
std::string s1 = std::format("Hello, {}!", "world");
std::string s2 = std::format("The answer is {}.", 42);
std::string s3 = std::format("Pi is approximately {:.2f}.", 3.14159);

// 格式化选项
std::string s4 = std::format("{:<10} {:>10}", "Left", "Right");
std::string s5 = std::format("{:^10}", "Centered");
std::string s6 = std::format("Decimal: {}, Hex: {:x}, Octal: {:o}", 42, 42, 42);

// 自定义类型的格式化(C++20+)
struct Point {
int x, y;
};

// 为Point类型特化std::formatter
template<>
struct std::formatter<Point> {
constexpr auto parse(std::format_parse_context& ctx) {
return ctx.begin();
}

template<typename FormatContext>
auto format(const Point& p, FormatContext& ctx) {
return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};

Point p = {1, 2};
std::string s7 = std::format("Point: {}", p);

std::string的最佳实践

1. 避免常见错误

错误原因解决方案
缓冲区溢出无边界检查的访问使用at()或检查索引
空指针解引用使用data()或c_str()返回的空字符串指针检查字符串是否为空
内存泄漏无(string自动管理内存)-
性能问题频繁的重新分配使用reserve()预分配空间
不必要的复制按值传递大字符串按const&传递或使用string_view

2. 性能最佳实践

实践原因示例
预分配空间避免频繁的重新分配s.reserve(1000);
使用移动语义避免不必要的复制std::string s2 = std::move(s1);
利用返回值优化避免函数返回时的复制return std::string("Hello");
使用string_view避免小字符串的复制void process(std::string_view sv);
避免频繁的拼接减少内存分配使用reserve()后拼接
选择合适的比较方法提高比较效率使用compare()进行前缀比较

3. 代码风格最佳实践

实践原因示例
使用std::string现代C++推荐std::string s = "Hello";
避免使用C风格字符串安全、便捷使用std::string而非char*
使用auto推断类型代码简洁auto s = std::string("Hello");
合理使用异常处理错误使用try-catch处理at()的异常
遵守命名约定代码可读性使用驼峰命名法

std::string的输入和输出

1. 字符串输入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 字符串输入
std::string name;

// 读取到空格为止
std::cout << "Enter your name: ";
std::cin >> name;
std::cout << "Your name is: " << name << std::endl;

// 读取整行
std::cout << "Enter a line: ";
std::cin.ignore(); // 忽略之前的换行符
std::getline(std::cin, name); // 读取整行
std::cout << "You entered: " << name << std::endl;

// 读取多行
std::vector<std::string> lines;
std::string line;
std::cout << "Enter lines (Ctrl+Z to end): " << std::endl;
while (std::getline(std::cin, line)) {
lines.push_back(line);
}

// 自定义输入函数
bool readLine(std::string& line) {
return static_cast<bool>(std::getline(std::cin, line));
}

2. 字符串输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 字符串输出
std::string s = "Hello, world!";

// 标准输出
std::cout << s << std::endl;

// 输出到文件
std::ofstream file("output.txt");
if (file.is_open()) {
file << s << std::endl;
file.close();
}

// 输出到字符串流
std::ostringstream oss;
oss << "Name: " << s << ", Length: " << s.size();
std::string output = oss.str();
std::cout << output << std::endl;

// 格式化输出(C++20+)
std::cout << std::format("String: '{}', Length: {}", s, s.size()) << std::endl;

// 高效输出大字符串
void printLargeString(const std::string& s) {
std::cout.write(s.data(), s.size());
std::cout << std::endl;
}

std::string的性能分析

1. 基准测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <benchmark/benchmark.h>

// 基准测试:字符串创建
static void BM_StringCreation(benchmark::State& state) {
for (auto _ : state) {
std::string s(100, 'x');
benchmark::DoNotOptimize(s);
}
}

// 基准测试:字符串拼接
static void BM_StringConcatenation(benchmark::State& state) {
std::string s;
s.reserve(1000);
for (auto _ : state) {
s += "Hello, world!";
benchmark::DoNotOptimize(s);
}
}

// 基准测试:字符串复制
static void BM_StringCopy(benchmark::State& state) {
std::string s(1000, 'x');
for (auto _ : state) {
std::string copy = s;
benchmark::DoNotOptimize(copy);
}
}

BENCHMARK(BM_StringCreation);
BENCHMARK(BM_StringConcatenation);
BENCHMARK(BM_StringCopy);
BENCHMARK_MAIN();

2. 性能对比

操作std::stringC风格字符串备注
创建O(n)O(n)std::string有SSO优化
复制O(n)O(n)std::string使用深复制
拼接O(n)O(n)std::string自动管理内存
查找O(n)O(n)实现相似
访问O(1)O(1)相同
比较O(n)O(n)实现相似

总结

std::string是C++标准库提供的功能强大、安全可靠的字符串类。它具有以下优点:

  1. 安全:自动管理内存,避免缓冲区溢出
  2. 便捷:丰富的成员函数,支持各种字符串操作
  3. 高效:小字符串优化(SSO)、移动语义等性能特性
  4. 现代:支持C++11+的各种特性,如移动语义、字符串视图等
  5. 兼容:可以与C风格字符串互操作

在现代C++编程中,应优先使用std::string而非C风格字符串,除非有特殊的性能要求或需要与C库交互。通过合理使用std::string的各种特性和最佳实践,可以编写更加安全、高效、可维护的字符串处理代码。

字符串流

字符串输入流(istringstream)

1
2
3
4
5
6
7
8
9
10
11
#include <sstream>

std::string data = "123 45.67 Hello";
std::istringstream iss(data);

int i;
double d;
std::string s;

iss >> i >> d >> s; // 从字符串流中读取数据
std::cout << "i: " << i << ", d: " << d << ", s: " << s << std::endl;

字符串输出流(ostringstream)

1
2
3
4
5
6
7
8
9
10
#include <sstream>

std::ostringstream oss;
int i = 123;
double d = 45.67;
std::string s = "Hello";

oss << "i: " << i << ", d: " << d << ", s: " << s;
std::string result = oss.str(); // 获取生成的字符串
std::cout << result << std::endl;

字符串流的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 数字转字符串
int number = 123;
std::ostringstream oss;
oss << number;
std::string numberStr = oss.str();

// 字符串转数字
std::string numberStr = "123";
std::istringstream iss(numberStr);
int number;
iss >> number;

// 格式化输出
std::ostringstream oss;
oss << std::fixed << std::setprecision(2);
oss << "Pi is approximately " << 3.14159;
std::string message = oss.str();

宽字符串

宽字符和宽字符串

1
2
3
4
5
6
7
8
9
10
11
// 宽字符
wchar_t wc = L'A';

// 宽字符串字面量
const wchar_t* wstr = L"Hello, world!";

// 宽字符串输入输出
std::wcout << L"Enter your name: ";
std::wstring wname;
std::wcin >> wname;
std::wcout << L"Hello, " << wname << L"!" << std::endl;

wstring 类

1
2
3
4
5
6
7
8
// wstring 类
std::wstring ws1 = L"Hello";
std::wstring ws2(5, L'a');

// 基本操作与 string 类似
ws1 += L" world";
size_t len = ws1.length();
std::wcout << ws1 << std::endl;

Unicode 字符串

UTF-8 字符串

1
2
3
4
5
6
7
8
// UTF-8 字符串字面量(C++11+)
const char* utf8Str = u8"Hello, 世界!";

// UTF-8 字符串
std::string utf8String = u8"Hello, 世界!";

// 输出 UTF-8 字符串
std::cout << utf8String << std::endl;

UTF-16 字符串

1
2
3
4
5
// UTF-16 字符串字面量(C++11+)
const char16_t* utf16Str = u"Hello, 世界!";

// UTF-16 字符串
std::u16string utf16String = u"Hello, 世界!";

UTF-32 字符串

1
2
3
4
5
// UTF-32 字符串字面量(C++11+)
const char32_t* utf32Str = U"Hello, 世界!";

// UTF-32 字符串
std::u32string utf32String = U"Hello, 世界!";

字符串的最佳实践

1. 优先使用 std::string

  • 安全性:std::string 自动管理内存,避免缓冲区溢出
  • 便捷性:std::string 提供了丰富的成员函数
  • 可读性:std::string 的代码更易读、易维护
  • 兼容性:std::string 可以与 C 风格字符串互操作

2. 避免缓冲区溢出

1
2
3
4
5
6
7
// 错误:可能导致缓冲区溢出
char buffer[10];
std::cin >> buffer; // 如果输入超过9个字符,会导致缓冲区溢出

// 正确:使用 std::string
std::string buffer;
std::cin >> buffer; // 自动处理任意长度的输入

3. 字符串连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 低效:多次字符串连接
std::string result;
result = "Hello";
result += " ";
result += "world";
result += "!";

// 高效:使用单个表达式
std::string result = "Hello" + std::string(" ") + "world" + "!";

// 更高效:使用 ostringstream
std::ostringstream oss;
oss << "Hello" << " " << "world" << "!";
std::string result = oss.str();

4. 字符串比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 错误:使用 == 比较 C 风格字符串
const char* str1 = "Hello";
const char* str2 = "Hello";
if (str1 == str2) { // 比较的是指针,不是内容
// 可能不执行
}

// 正确:使用 strcmp 比较 C 风格字符串
if (strcmp(str1, str2) == 0) {
// 执行
}

// 正确:使用 == 比较 std::string
std::string s1 = "Hello";
std::string s2 = "Hello";
if (s1 == s2) { // 比较的是内容
// 执行
}

5. 字符串转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 数字转字符串
int number = 123;

// C++11+
std::string str = std::to_string(number);

// 旧版本 C++
std::ostringstream oss;
oss << number;
std::string str = oss.str();

// 字符串转数字
std::string str = "123";

// C++11+
int number = std::stoi(str);

// 旧版本 C++
std::istringstream iss(str);
int number;
iss >> number;

C++11+字符串处理新特性

字符串视图(std::string_view,C++17+)

std::string_view是C++17引入的一个非所有权字符串视图,用于提供对字符串的高效访问,避免不必要的字符串复制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <string_view>
#include <string>

// 基本使用
std::string s = "Hello, world!";
std::string_view sv(s);
std::cout << sv << std::endl; // 输出:Hello, world!

// 从C风格字符串创建
const char* cstr = "Hello";
std::string_view sv2(cstr);

// 从子字符串创建
std::string_view sv3(s, 0, 5); // 从索引0开始,长度5:"Hello"

// 基本操作
std::cout << "Length: " << sv.length() << std::endl;
std::cout << "Empty: " << sv.empty() << std::endl;
std::cout << "Substring: " << sv.substr(7, 5) << std::endl; // "world"

// 查找操作
size_t pos = sv.find("world");
if (pos != std::string_view::npos) {
std::cout << "Found 'world' at position: " << pos << std::endl;
}

// 比较操作
if (sv.starts_with("Hello")) {
std::cout << "Starts with 'Hello'" << std::endl;
}

if (sv.ends_with("!")) {
std::cout << "Ends with '!'" << std::endl;
}

std::string的新方法(C++11+)

C++11新方法

1
2
3
4
5
6
7
8
// 移动语义
std::string s1 = "Hello";
std::string s2 = std::move(s1); // 移动构造,s1变为空

// emplace_back
std::string s;
s.emplace_back('H');
s.append("ello");

C++14新方法

1
2
3
4
5
6
// 字符串字面量操作符
using namespace std::string_literals;
std::string s = "Hello"s; // 等同于std::string("Hello")

// 原始字符串字面量与后缀
std::string raw = R"(Raw string with "quotes" and \backslashes)"s;

C++20新方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// starts_with和ends_with
std::string s = "Hello, world!";
if (s.starts_with("Hello")) {
std::cout << "Starts with 'Hello'" << std::endl;
}

if (s.ends_with("!")) {
std::cout << "Ends with '!'" << std::endl;
}

// 支持多种参数类型
if (s.starts_with({'H', 'e'})) {
std::cout << "Starts with 'He'" << std::endl;
}

// 范围构造
std::vector<char> chars = {'H', 'e', 'l', 'l', 'o'};
std::string s2(chars.begin(), chars.end());

C++23新方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// contains
std::string s = "Hello, world!";
if (s.contains("world")) {
std::cout << "Contains 'world'" << std::endl;
}

if (s.contains('o')) {
std::cout << "Contains 'o'" << std::endl;
}

// resize_and_overwrite
std::string s;
s.resize_and_overwrite(10, [](char* buffer, size_t size) -> size_t {
std::memcpy(buffer, "Hello", 5);
return 5; // 返回实际写入的字符数
});
std::cout << s << std::endl; // 输出:Hello

正则表达式(C++11+)

C++11引入了std::regex库,用于字符串的模式匹配和替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <regex>
#include <string>

// 基本匹配
std::string s = "Hello, world!";
std::regex pattern("world");
if (std::regex_search(s, pattern)) {
std::cout << "Found 'world'" << std::endl;
}

// 捕获组
std::string date = "2023-12-25";
std::regex datePattern(R"((\d{4})-(\d{2})-(\d{2}))");
std::smatch matches;
if (std::regex_search(date, matches, datePattern)) {
std::cout << "Year: " << matches[1] << std::endl;
std::cout << "Month: " << matches[2] << std::endl;
std::cout << "Day: " << matches[3] << std::endl;
}

// 替换
std::string text = "Hello, world! Hello, C++!";
std::regex replacePattern("Hello");
std::string result = std::regex_replace(text, replacePattern, "Hi");
std::cout << result << std::endl; // 输出:Hi, world! Hi, C++!

// 正则表达式标志
std::regex caseInsensitivePattern("hello", std::regex::icase);
if (std::regex_search(s, caseInsensitivePattern)) {
std::cout << "Found 'hello' (case insensitive)" << std::endl;
}

C++20新特性:format库

C++20引入了std::format库,提供了一种类型安全、灵活的字符串格式化方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <format>
#include <string>

// 基本格式化
std::string message = std::format("Hello, {}!", "world");
std::cout << message << std::endl; // 输出:Hello, world!

// 多个参数
std::string info = std::format("Name: {}, Age: {}", "Alice", 30);
std::cout << info << std::endl; // 输出:Name: Alice, Age: 30

// 格式化数字
std::string number = std::format("Pi is approximately {:.2f}", 3.14159);
std::cout << number << std::endl; // 输出:Pi is approximately 3.14

// 格式化宽度和对齐
std::string aligned = std::format("{:<10} {:>10}", "Left", "Right");
std::cout << aligned << std::endl; // 输出:Left Right

// 格式化进制
std::string hex = std::format("Decimal: {}, Hex: {:x}, Octal: {:o}", 42, 42, 42);
std::cout << hex << std::endl; // 输出:Decimal: 42, Hex: 2a, Octal: 52

format库的优点

  1. 类型安全:相比printfstd::format是类型安全的
  2. 灵活性:支持位置参数和命名参数
  3. 可读性:格式化字符串更清晰易读
  4. 性能:性能与printf相当或更好
  5. 扩展性:支持自定义类型的格式化

C++23新特性:print库

C++23引入了std::printstd::println函数,提供了一种更方便的字符串输出方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <print>

// 基本输出
std::print("Hello, {}}!", "world");

// 带换行
std::println("Hello, {}!", "world");

// 多个参数
std::println("Name: {}, Age: {}", "Alice", 30);

// 格式化数字
std::println("Pi is approximately {:.2f}", 3.14159);

Unicode字符串处理进阶

Unicode码点和代码单元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// Unicode码点是字符的数字表示
// UTF-8使用1-4个代码单元(字节)表示一个码点
// UTF-16使用1-2个代码单元(16位)表示一个码点
// UTF-32使用1个代码单元(32位)表示一个码点

// 遍历UTF-8字符串的码点
#include <cuchar>
#include <string>

void printUtf8CodePoints(const std::string& utf8Str) {
const char* p = utf8Str.data();
const char* end = p + utf8Str.size();

while (p < end) {
char32_t codePoint;
size_t len = mbrtoc32(&codePoint, p, end - p, nullptr);
if (len == static_cast<size_t>(-1) || len == static_cast<size_t>(-2)) {
break; // 无效的UTF-8序列
}
std::cout << "Code point: U+" << std::hex << codePoint << std::endl;
p += len;
}
}

// 使用
std::string utf8Str = u8"Hello, 世界!";
printUtf8CodePoints(utf8Str);

Unicode字符串的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
// UTF-8与UTF-16之间的转换
#include <codecvt>
#include <locale>

// UTF-8到UTF-16
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
std::string utf8Str = u8"Hello, 世界!";
std::wstring utf16Str = converter.from_bytes(utf8Str);

// UTF-16到UTF-8
std::string utf8Str2 = converter.to_bytes(utf16Str);

// 注意:C++17已弃用std::wstring_convert,推荐使用第三方库如ICU或Boost.Locale

常见错误和陷阱

1. 空指针解引用

1
2
3
4
5
6
7
8
// 错误:空指针解引用
const char* str = nullptr;
size_t len = strlen(str); // 未定义行为

// 正确:检查空指针
if (str != nullptr) {
size_t len = strlen(str);
}

2. 缓冲区溢出

1
2
3
4
5
6
7
8
9
10
// 错误:缓冲区溢出
char buffer[10];
strcpy(buffer, "This string is too long"); // 缓冲区溢出

// 正确:使用安全的函数
strncpy(buffer, "This string is too long", sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0'; // 确保 null 终止

// 更好:使用 std::string
std::string buffer = "This string is too long";

3. 忘记 null 终止符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误:忘记 null 终止符
char buffer[10];
for (int i = 0; i < 10; i++) {
buffer[i] = 'a';
}
std::cout << buffer << std::endl; // 未定义行为,缺少 null 终止符

// 正确:添加 null 终止符
char buffer[11]; // 多留一个位置
for (int i = 0; i < 10; i++) {
buffer[i] = 'a';
}
buffer[10] = '\0'; // 添加 null 终止符
std::cout << buffer << std::endl;

4. 字符串字面量的修改

1
2
3
4
5
6
7
// 错误:修改字符串字面量
char* str = "Hello";
str[0] = 'h'; // 未定义行为,字符串字面量是常量

// 正确:使用可修改的数组
char str[] = "Hello";
str[0] = 'h'; // 合法

5. 混合使用 C 风格字符串和 std::string

1
2
3
4
5
6
7
8
9
// 潜在问题:混合使用
std::string s = "Hello";
const char* cstr = s.c_str();
// 不要在 s 被修改后使用 cstr,因为 cstr 可能失效

// 安全使用
std::string s = "Hello";
std::string copy = s; // 创建副本
const char* cstr = copy.c_str(); // 使用副本的 c_str()

小结

本章介绍了C++中的字符和字符串处理,包括:

  1. 字符类型:char、wchar_t、char16_t、char32_t
  2. C风格字符串:字符数组、字符串字面量、字符串操作函数
  3. std::string 类:C++标准库提供的字符串类,具有丰富的成员函数
  4. 字符串流:istringstream 和 ostringstream,用于字符串的输入输出
  5. 宽字符串:wchar_t 和 std::wstring
  6. Unicode 字符串:UTF-8、UTF-16、UTF-32 字符串
  7. 字符串的最佳实践:优先使用 std::string,避免缓冲区溢出等
  8. 常见错误和陷阱:空指针解引用、缓冲区溢出、忘记 null 终止符等

字符串是C++程序中最常用的数据类型之一,掌握好字符串的处理方法对于编写高效、可靠的程序至关重要。在实际编程中,应优先使用 std::string 类,它提供了更安全、更便捷的字符串操作方式。同时,也要了解 C 风格字符串的基本概念和操作函数,因为在一些遗留代码或与 C 库交互的场景中仍然会用到。

在后续章节中,我们将学习更高级的C++特性,如内存模型、面向对象编程、模板等,这些特性将与字符串处理结合使用,帮助我们构建更复杂、更强大的程序。