第 13 章

字符与字符串:cctype、cstring 与 std::string

先把单个字符当作整数来分类与转换,再用 cstring 处理以空字符结尾的字符数组,最后引出 std::string——它把长度与空字符的管理藏到类型内部。

Characters and Strings: cctype, cstring, and std::string

Classify and convert single characters as integers, manipulate null-terminated character arrays with cstring, and then meet std::string, which hides length and terminator bookkeeping inside the type.

首发于 2022.02.17中 / EN
系统课程cppstringscstringcctypestd-string
语言
字号
目录
篇内目录

这是 31 小时长视频的第 13 章,对应时间轴 12:46:10–14:12:29。原视频没有分章,这一章是重新划分出来的:从「字符分类」讲到「C 字符串工具函数」,最后引出 std::string。

这一章在讲什么

C++ 里处理文本有三层工具,本章沿着它们由低到高走一遍:

  1. 单个字符:<cctype> 提供判断与大小写转换,函数返回 int,非零表示真。
  2. C 风格字符串:以 '\0' 结尾的字符数组,用 <cstring> 的 strlen、strcmp、strchr、strcat、strcpy 操作。
  3. std::string:把长度、扩容和空字符全部封装起来,不再需要手动维护边界。

三层之间的关系是本条主线:每一层都在替下一层管理麻烦。strlen 替你数到 '\0',std::string 替你保存长度并在需要时重新分配内存。

时间轴

时间内容
12:46:10<cctype> 概览:能做什么、文档在哪
12:47:23std::isalnum:字母或数字
12:50:08std::isalpha:字母
12:50:44std::isblank:空白字符,统计例子
12:51:52std::islower / std::isupper
12:52:49std::isdigit
12:53:16std::toupper / std::tolower
12:54:44VS Code 实机:character manipulation 项目
12:59:09换用 MSVC 编译,观察不同编译器的差别
13:00:51isblank 计数:为什么计数器该用 size_t
13:03:25大小写计数
13:06:38大小写转换演示
13:08:41cctype 小结
13:09:36strlen:长度,不含 '\0'
13:11:43strcmp:字典序比较
13:13:38strncmp:只比较前 n 个字符
13:14:37strchr:找第一次出现,返回指针
13:17:36strrchr:找最后一次出现
13:19:05VS Code 实机:c string manipulation
13:21:39strlen 与 sizeof 的差别
13:24:17指针可改指向、数组名不可改
13:30:17strchr 的循环细节
13:36:46反面例子:直接改原指针导致循环 25 次
13:39:25strrchr 从路径里提取文件名
13:42:30strcat:拼接
13:45:40strncat:只拼前 n 个字符
13:46:37strcpy:复制
13:47:36strncpy:只复制前 n 个字符
13:49:08VS Code 实机:c string concatenation and copy
14:01:10为什么需要 std::string
14:03:34std::string 的声明与六种初始化
14:06:23VS Code 实机:declaring std::string
14:10:51std::string 的自动扩容与内存回收

字符分类:<cctype>

<cctype> 里的函数接收一个字符、返回 int:非零表示真,零表示假。不要假设「真」一定是 1——标准只保证非零。

#include <iostream>
#include <cctype>

int main() {
    char c = 'c';
    char star = '*';
    char seven = '7';

    std::cout << std::isalnum(c)     << "\n";  // 非零
    std::cout << std::isalnum(star)  << "\n";  // 0

    if (std::isalnum(c)) {
        std::cout << c << " is alphanumeric\n";
    }
    // 想得到真正的 bool:
    bool ok = static_cast<bool>(std::isalnum(c));
    return 0;
}

常见成员:

函数判断
isalnum字母或数字
isalpha字母
isblank空格与制表符
islower / isupper小写 / 大写
isdigit数字
toupper / tolower转换大小写

一个计数例子,同时演示两个易错点:范围 for 访问字符数组,以及计数器应使用 std::size_t。

char message[]{"Hello there, nice to meet you!"};
std::size_t blank_count{0};

for (std::size_t i{0}; i < std::size(message); ++i) {
    if (std::isblank(message[i])) {
        std::cout << "Found a blank at index " << i << "\n";
        ++blank_count;
    }
}
std::cout << "Total blanks: " << blank_count << "\n";
  • 为什么用 std::size_t:计数不可能为负,int 允许负数,语义不对,还可能溢出。
  • 为什么用 std::size:这是原生数组,std::size 能拿到元素个数;若数组退化成指针,std::size 就失效了。

大小写转换:

char original[]{"Hello World"};
char upper[std::size(original)]{};

for (std::size_t i{0}; i < std::size(original); ++i) {
    upper[i] = std::toupper(original[i]);   // 原数组不变
}

toupper 返回转换后的字符,不修改原字符;要保留结果就得写到另一个数组里。

编译器差异:部分编译器(尤其 MSVC)会把 cctype 中以 c 开头的函数视为不安全并给出警告。课件建议同一段代码在 gcc 与 MSVC 下各编译一次,亲眼看看差异。

C 字符串:以 '\0' 结尾的字符数组

C 字符串不是一种类型,而是一个约定:字符数组的末尾有一个 '\0'。所有 str* 函数都靠它判断结束。

#include <cstring>

char message1[]{"Hello World"};        // 编译期加上 '\0'
const char* message2{"Hello World"};   // 指向字面量

std::cout << std::strlen(message1) << "\n";  // 11
std::cout << sizeof(message1) << "\n";       // 12(含 '\0')

strlen 与 sizeof 的区别是本章最常考的细节:

表达式结果原因
strlen(message1)11数到 '\0' 为止,不含它
sizeof(message1)12整个数组的字节数,含 '\0'
strlen(message2)11解引用指针,逐字节数
sizeof(message2)8指针本身的大小(64 位平台)

strlen 的好处是对退化后的指针仍然有效;sizeof 一旦遇到指针就只能给出指针大小。

比较:strcmp 与 strncmp

strcmp(a, b) 按字典序比较:

  • 返回负数:a 在 b 之前;
  • 返回 0:两串相同;
  • 返回正数:a 在 b 之后。

它不保证返回 -1 或 1,只保证符号。

const char* a{"alabama"};
const char* b{"blabama"};
std::cout << std::strcmp(a, b) << "\n";   // 负数,'a' < 'b'

std::cout << std::strncmp("aaa", "aaanca", 3) << "\n";  // 0,前三个字符相同
char arr3[]{"alabama"};
arr3 = "something else";   // 编译错误:数组名不能重新指向

指针可以改指向(只要不是 const),数组名不行。这个差异在下一节会直接影响内存管理。

查找:strchr 与 strrchr

strchr(s, c) 返回第一次出现 c 的指针;找不到返回 nullptr。strrchr 找最后一次。

const char* message{"Try not. Do or do not. There is no try."};
const char target{'t'};
const char* result{message};
std::size_t iterations{0};

while ((result = std::strchr(result, target)) != nullptr) {
    std::cout << "Found '" << target << "' starting at " << result << "\n";
    ++result;      // 指针算术:从下一个字符继续找
    ++iterations;
}
std::cout << iterations << " iterations\n";

两个容易踩的点:

  1. 必须用独立指针 result 前进,不能直接改原指针 message(下面会看到原因)。
  2. std::cout << result 会一直打印到 '\0',所以输出的是「从匹配位置到结尾」而不是单个字符。

课件特意演示了一个反面写法:

const char* message{"Try not. Do or do not. There is no try."};
const char* result{nullptr};
std::size_t iterations{0};

while ((result = std::strchr(message, 't')) != nullptr) {
    std::cout << result << "\n";
    ++message;          // 问题:只前进一格,没有跳到匹配处
    ++iterations;
}
// 结果循环 25 次,而不是 2 次

原因:每次循环都从整体重新搜索,++message 只挪动一个字符,匹配位置并没有被跳过。正确做法是把返回值赋回搜索起点,或者像上例那样让 result 自己前进。

把原指针声明为 const char* const 可以防止这类意外:

const char* const message{"..."};
// ++message;   // 编译错误,安全

strrchr 的经典用途是从路径中取文件名:

const char path[]{"D:\\projects\\cpp\\hello.cpp"};
const char* filename{std::strrchr(path, '\\')};
if (filename != nullptr) {
    std::cout << (filename + 1) << "\n";   // hello.cpp
}

filename 指向最后一个反斜杠,+1 跳过它,打印到 '\0' 为止。

拼接与复制:strcat / strcpy

char dest[50]{"Hello "};
char source[]{"World"};
std::strcat(dest, source);      // dest = "Hello World"
std::strcat(dest, " and goodbye");

strcat 从 dest 的 '\0' 处开始覆盖,把 source 接上去。调用者必须保证 dest 足够大,否则会越界写内存,这正是这些函数被许多编译器标记为不安全的原因。

char dest[30]{"Hello "};
std::strncat(dest, "there is a bird on my window", 6);  // 只接 "there "
char source[]{"C++ is a multi-purpose language"};
char dest[std::strlen(source) + 1];     // +1 给 '\0'
std::strcpy(dest, source);

strcpy 从目标数组的开头覆盖整个数组;strncpy(dest, src, n) 只复制前 n 个字符。课件用一个例子说明覆盖行为:

char dest[]{"abcde f"};
std::strncpy(dest, "hello", 5);   // "hellof":前五个字符被覆盖

为什么 strcpy 的目标要单独留 +1:strlen 不数 '\0',而复制需要把 '\0' 一起搬过去。

std::string:把细节藏起来

字符数组的两个日常负担:

  1. 必须自己记录容量,越界会崩;
  2. 必须自己保证 '\0' 存在。

std::string 把这两件事交给类型本身处理。

#include <string>

std::string empty;                                  // 空串
std::string planet{"Where the sky is blue"};
std::string copy{planet};                           // 拷贝
std::string message{"Hello world", 5};              // "Hello"
std::string repeated(4, 'e');                       // "eeee"
std::string saying{"Where the sky is blue", 6, 5};  // 从下标 6 取 5 个:"the s"

第六种写法最容易看错:第一个数字是起始下标,第二个是长度。

std::string planet{"Where the sky is blue"};
planet = "A much longer string than before";

赋值会自动扩容:旧内存被释放,新内存按需分配。若用 const char* 做同样的事,旧字面量只是被丢在原地,指针换了指向,内存并没有归还——std::string 没有这个浪费。

本章结论

  • <cctype> 的谓词返回 int,非零为真;不要依赖具体数值。
  • 字符数组是原生数组:std::size 可用,一旦退化成指针就失效。
  • strlen 不含 '\0',sizeof 含;sizeof(指针) 只是指针大小。
  • strcmp 只保证返回值的符号:负、零、正。
  • strchr / strrchr 返回指针,找不到返回 nullptr;搜索时应让返回指针自己前进。
  • strcat / strcpy 不检查目标容量,调用者负责保证足够大,并给 '\0' 留位置。
  • std::string 自动管理长度、空字符与扩容,是本章推荐的默认选择。

理解检验

1. 为什么 std::isalnum('7') 在 if 里能直接用,却不该写成 == 1?

参考答案:标准只规定「是字母或数字时返回非零值,否则返回零」,没有规定非零值必须是 1。写 if (std::isalnum('7')) 依赖的是「零为假、非零为真」,这是正确的;写 if (std::isalnum('7') == 1) 则假设了具体返回值,换编译器或标准库可能失效。

2. 对 char s[]{"Hello"},strlen(s)、sizeof(s) 分别是多少?若 const char* p = s;,sizeof(p) 又是多少?

参考答案:strlen(s) 是 5,数到 '\0' 为止且不含它;sizeof(s) 是 6,包含结尾的 '\0';sizeof(p) 是指针本身的大小(64 位平台通常为 8),与它指向的字符串长度无关。

3. strchr 的循环里,为什么课件要用一个单独的 result 指针,而不能直接对原字符串指针做 ++?

参考答案:strchr 每次都会从头搜索传入的指针所指的字符串。若直接 ++message,只是把搜索起点后移一个字符,并没有跳到匹配位置,下一次搜索仍会在几乎整段里重新扫描,于是循环次数变成字符数(25 次)。用 result = strchr(result, target) 把返回值赋回搜索起点,才能让下一轮从匹配处之后继续,同时保持原指针不变。把原指针声明为 const char* const 可以从编译期阻止这种误改。

4. 下面这段代码为什么危险?写出一种修复方式。

char dest[]{"Hello"};
std::strcat(dest, " World");

参考答案:dest 由字面量初始化为长度 6(含 '\0'),strcat 会把 " World" 追加到末尾,需要至少 12 个字节,越界写入属于未定义行为。修复:显式声明足够容量,例如 char dest[32]{"Hello"};,或在 C++ 里改用 std::string dest{"Hello"}; dest += " World";。

5. 用 strcpy 复制一个长度为 n 的字符串时,目标数组至少要多大?为什么不是 n?

参考答案:至少要 n + 1 个字符。strlen 返回的 n 不含结尾的 '\0',而 strcpy 会把 '\0' 一并复制过去,所以目标必须额外留一格存放它。

6. std::string s{"Where the sky is blue", 6, 5}; 得到的 s 是什么?

参考答案:"the s"。第一个数字 6 是起始下标(W 为 0,t 为 6),第二个数字 5 是取多少个字符:t、h、e、空格、s。

与前后章的关系

上一章处理引用与 const,为本章的「指针只读」打下基础;本章最后引出的 std::string 在第 14 章之后会继续使用。下一章转向单一定义规则、链接与声明/定义的区别——那是把多个源文件组织成程序的前提,也是理解 std::string 这类类型如何跨翻译单元工作的基础。

This is chapter 13 of a 31-hour single video, covering 12:46:10–14:12:29. The original video has no chapters; this one is a section of the re-divided timeline, running from character classification through the C-string utilities and finally to std::string.

What this chapter covers

C++ offers three layers for working with text, and the chapter climbs them in order:

  1. Individual characters: <cctype> classifies and converts them; the functions return int, with nonzero meaning true.
  2. C-style strings: null-terminated character arrays, manipulated by strlen, strcmp, strchr, strcat, and strcpy from <cstring>.
  3. std::string: length, growth, and the terminator are all encapsulated, so bounds never have to be tracked by hand.

The through-line is that each layer absorbs bookkeeping the layer below makes you do. strlen counts up to '\0' for you; std::string stores the length and reallocates when needed.

Timeline

TimeTopic
12:46:10<cctype> overview: what it offers and where the docs are
12:47:23std::isalnum: letter or digit
12:50:08std::isalpha: letters
12:50:44std::isblank: blanks, with a counting example
12:51:52std::islower / std::isupper
12:52:49std::isdigit
12:53:16std::toupper / std::tolower
12:54:44In VS Code: the character-manipulation project
12:59:09Building with MSVC to compare compilers
13:00:51Counting blanks, and why the counter should be size_t
13:03:25Counting upper- and lowercase letters
13:06:38Case conversion demo
13:08:41cctype wrap-up
13:09:36strlen: length without '\0'
13:11:43strcmp: lexicographic comparison
13:13:38strncmp: compare only the first n characters
13:14:37strchr: first occurrence, returns a pointer
13:17:36strrchr: last occurrence
13:19:05In VS Code: the C-string-manipulation project
13:21:39strlen versus sizeof
13:24:17Pointers can be repointed; array names cannot
13:30:17The strchr loop, step by step
13:36:46A counterexample: advancing the original pointer loops 25 times
13:39:25Pulling a filename out of a path with strrchr
13:42:30strcat: concatenation
13:45:40strncat: concatenate only n characters
13:46:37strcpy: copying
13:47:36strncpy: copy only n characters
13:49:08In VS Code: concatenation and copy
14:01:10Why std::string exists
14:03:34Declaring and initializing std::string six ways
14:06:23In VS Code: declaring std::string
14:10:51Automatic growth and memory release

Character classification: <cctype>

Functions in <cctype> take a character and return an int: nonzero is true, zero is false. Do not assume the true value is exactly 1; the standard only promises nonzero.

#include <iostream>
#include <cctype>

int main() {
    char c = 'c';
    char star = '*';
    char seven = '7';

    std::cout << std::isalnum(c)     << "\n";  // nonzero
    std::cout << std::isalnum(star)  << "\n";  // 0

    if (std::isalnum(c)) {
        std::cout << c << " is alphanumeric\n";
    }
    // To get an actual bool:
    bool ok = static_cast<bool>(std::isalnum(c));
    return 0;
}

The common predicates:

FunctionTests
isalnumletter or digit
isalphaletter
isblankspace or tab
islower / isupperlowercase / uppercase
isdigitdigit
toupper / tolowercase conversion

A counting example that also illustrates two common mistakes: ranging over a character array, and using std::size_t for a counter.

char message[]{"Hello there, nice to meet you!"};
std::size_t blank_count{0};

for (std::size_t i{0}; i < std::size(message); ++i) {
    if (std::isblank(message[i])) {
        std::cout << "Found a blank at index " << i << "\n";
        ++blank_count;
    }
}
std::cout << "Total blanks: " << blank_count << "\n";
  • Why std::size_t: a count cannot be negative, int allows negatives, and it can overflow.
  • Why std::size: this is a native array, so std::size reports the element count; once the array decays to a pointer, std::size stops working.

Case conversion:

char original[]{"Hello World"};
char upper[std::size(original)]{};

for (std::size_t i{0}; i < std::size(original); ++i) {
    upper[i] = std::toupper(original[i]);   // original is untouched
}

toupper returns the converted character; it does not modify the input, so the result must be stored somewhere.

Compiler differences: some compilers, MSVC in particular, flag the c-prefixed cctype functions as unsafe and emit warnings. The lecture recommends compiling the same code under both gcc and MSVC to see the difference firsthand.

C strings: null-terminated character arrays

A C string is not a type but a convention: a character array whose last character is '\0'. Every str* function relies on it to find the end.

#include <cstring>

char message1[]{"Hello World"};        // the compiler appends '\0'
const char* message2{"Hello World"};   // points at a string literal

std::cout << std::strlen(message1) << "\n";  // 11
std::cout << sizeof(message1) << "\n";       // 12, including '\0'

The strlen versus sizeof distinction is the detail most likely to be tested:

ExpressionResultWhy
strlen(message1)11counts up to '\0', excluding it
sizeof(message1)12byte size of the array, including '\0'
strlen(message2)11dereferences the pointer and counts bytes
sizeof(message2)8size of the pointer itself (64-bit)

The value of strlen is that it still works on a decayed pointer; sizeof on a pointer only reports the pointer's size.

Comparison: strcmp and strncmp

strcmp(a, b) compares lexicographically:

  • negative when a comes before b;
  • zero when they are equal;
  • positive when a comes after b.

It does not promise -1 or 1, only the sign.

const char* a{"alabama"};
const char* b{"blabama"};
std::cout << std::strcmp(a, b) << "\n";   // negative, 'a' < 'b'

std::cout << std::strncmp("aaa", "aaanca", 3) << "\n";  // 0, first three match
char arr3[]{"alabama"};
arr3 = "something else";   // compile error: an array name cannot be repointed

Pointers can be repointed (unless const); array names cannot. That difference matters directly for the memory discussion below.

Searching: strchr and strrchr

strchr(s, c) returns a pointer to the first occurrence of c, or nullptr if absent. strrchr returns the last.

const char* message{"Try not. Do or do not. There is no try."};
const char target{'t'};
const char* result{message};
std::size_t iterations{0};

while ((result = std::strchr(result, target)) != nullptr) {
    std::cout << "Found '" << target << "' starting at " << result << "\n";
    ++result;      // pointer arithmetic: continue after the match
    ++iterations;
}
std::cout << iterations << " iterations\n";

Two traps:

  1. Advance a separate pointer result, never the original message.
  2. std::cout << result prints through to '\0', so the output is "from the match to the end", not the single character.

The lecture deliberately shows the wrong version:

const char* message{"Try not. Do or do not. There is no try."};
const char* result{nullptr};
std::size_t iterations{0};

while ((result = std::strchr(message, 't')) != nullptr) {
    std::cout << result << "\n";
    ++message;          // problem: moves one character instead of jumping to the match
    ++iterations;
}
// loops 25 times instead of 2

The reason: each iteration searches from the start of the pointer passed in, and ++message only shifts the start by one character, so the match is never skipped. The fix is to feed the returned pointer back in, or let a separate pointer walk forward. Declaring the original as const char* const makes the mistake a compile error:

const char* const message{"..."};
// ++message;   // compile error, safe

A classic strrchr use is extracting a filename:

const char path[]{"D:\\projects\\cpp\\hello.cpp"};
const char* filename{std::strrchr(path, '\\')};
if (filename != nullptr) {
    std::cout << (filename + 1) << "\n";   // hello.cpp
}

filename points at the last backslash; +1 skips it and prints to '\0'.

Concatenation and copy: strcat / strcpy

char dest[50]{"Hello "};
char source[]{"World"};
std::strcat(dest, source);      // dest = "Hello World"
std::strcat(dest, " and goodbye");

strcat starts at dest's '\0', overwrites it, and appends source. The caller must ensure dest is large enough; otherwise it writes out of bounds — exactly why many compilers mark these functions unsafe.

char dest[30]{"Hello "};
std::strncat(dest, "there is a bird on my window", 6);  // appends "there "
char source[]{"C++ is a multi-purpose language"};
char dest[std::strlen(source) + 1];     // +1 for '\0'
std::strcpy(dest, source);

strcpy overwrites the target from its first character; strncpy(dest, src, n) copies only the first n. The overwrite behaviour is easy to see:

char dest[]{"abcde f"};
std::strncpy(dest, "hello", 5);   // "hellof": first five characters replaced

Why the target needs +1: strlen does not count '\0', but the copy must carry the terminator along.

std::string: hiding the details

Character arrays impose two daily burdens:

  1. You must track capacity; overruns crash.
  2. You must maintain '\0'.

std::string puts both under the type's control.

#include <string>

std::string empty;                                  // empty
std::string planet{"Where the sky is blue"};
std::string copy{planet};                           // copy
std::string message{"Hello world", 5};              // "Hello"
std::string repeated(4, 'e');                       // "eeee"
std::string saying{"Where the sky is blue", 6, 5};  // from index 6, length 5: "the s"

The last form is the easiest to misread: the first number is the start index, the second is the length.

std::string planet{"Where the sky is blue"};
planet = "A much longer string than before";

Assignment grows automatically: the old buffer is freed and a new one allocated as needed. With a const char* the old literal is simply abandoned in place while the pointer is repointed — std::string avoids that waste.

Takeaways

  • <cctype> predicates return int; nonzero is true, and the exact value is unspecified.
  • Character arrays are native arrays: std::size works until they decay to pointers.
  • strlen excludes '\0'; sizeof includes it; sizeof(pointer) is just the pointer.
  • strcmp promises only the sign: negative, zero, or positive.
  • strchr / strrchr return pointers, or nullptr when not found; let the returned pointer advance.
  • strcat / strcpy never check capacity; the caller must reserve room, including for '\0'.
  • std::string manages length, terminator, and growth, and is the default choice.

Comprehension check

1. Why can std::isalnum('7') be used directly in an if, while writing == 1 would be wrong?

Reference answer: the standard only specifies "nonzero when the character is alphanumeric, zero otherwise", not that the nonzero value is 1. if (std::isalnum('7')) relies on zero being false and nonzero true, which is guaranteed. if (std::isalnum('7') == 1) assumes a particular return value that another library implementation may not produce.

2. For char s[]{"Hello"}, what are strlen(s) and sizeof(s)? If const char* p = s;, what is sizeof(p)?

Reference answer: strlen(s) is 5 (counts up to but excludes '\0'); sizeof(s) is 6 (includes '\0'); sizeof(p) is the pointer's own size — commonly 8 on 64-bit platforms — independent of the string length.

3. In the strchr loop, why does the lecture use a separate result pointer instead of advancing the original string pointer?

Reference answer: strchr always searches from the pointer it is given. ++message merely shifts the search start by one character; it never jumps past the match, so the next search rescans almost the whole string and the loop runs once per character (25 times). Assigning the return value back — result = strchr(result, target) — makes the next search begin after the match while leaving the original pointer intact. Declaring the original as const char* const turns such accidental modification into a compile error.

4. Why is the following dangerous, and how would you fix it?

char dest[]{"Hello"};
std::strcat(dest, " World");

Reference answer: dest is initialized from a literal, so it holds 6 characters including '\0'. strcat appends " World", which needs at least 12 bytes, so it writes past the array — undefined behaviour. Fix it by giving explicit capacity, e.g. char dest[32]{"Hello"};, or by using std::string dest{"Hello"}; dest += " World";.

5. When copying a string of length n with strcpy, how large must the destination be, and why is n not enough?

Reference answer: it must hold n + 1 characters. strlen returns n without the terminating '\0', but strcpy copies the terminator too, so one extra cell is required.

6. What does std::string s{"Where the sky is blue", 6, 5}; contain?

Reference answer: "the s". The 6 is the starting index (W is 0, t is 6) and the 5 is the count: t, h, e, space, s.

Relation to neighbouring chapters

The previous chapter covered references and const, which underpin the read-only pointer discipline used here; std::string, introduced at the end, is used from here on. The next chapter turns to the one definition rule, linkage, and declarations versus definitions — the groundwork for organizing multiple source files, and for understanding how a type such as std::string works across translation units.

来源