# Strings And Text 文档类型:语法主线 是否可直接用于生成代码:是 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:[04_values_and_literals.md](04_values_and_literals.md)、[19_types_and_conversions.md](19_types_and_conversions.md)、[07_expressions_and_operators.md](07_expressions_and_operators.md) 手册位置:第 20 篇,共 32 篇。上一篇:[19_types_and_conversions.md](19_types_and_conversions.md)。下一篇:[21_external_calls_and_threads.md](21_external_calls_and_threads.md)。 这一篇吸收字符串专题里较深的部分:字符串表达、编码边界、转义、非转义表达、字符串拼装和子串操作。 ## 这一篇解决什么问题 回答“当任务进入字符串和编码细节,而不是只写普通字面量时,应该进入哪一篇”。 ## 必须记住的规则 - 普通字符串、`L""` 宽串、`U""` UTF8 前缀串,以及 `%%` 原始字符串,当前解释器都接受。 - `%%` 原始字符串开头后必须先跟一个空白分隔符;可以带标识符,也支持多行。 - `L%% ...%%` 与 `U%% ...%%` 这两种带前缀的原始字符串当前也可用。 - 普通字符串里的 `\uXXXX` 不要直接按“单字符宽串”理解;当前 `Length("\u0041") = 2`,而 `Length(L"\u0041") = 1`、`Length(U"\u0041") = 1`。 - `U""` 不是宽串;需要在 UTF8、宽串、普通串之间显式转换时,继续用 `UTF8ToUnicode(...)`、`UTF8ToAnsi(...)`、`AnsiToUTF8(...)`、`UnicodetoUTF8(...)`、`String(...)`、`WideString(...)`。 - `#number` 可以直接把字符码拼进字符串。 - `\0` 和 `#0` 都能把 ASCII `0` 放进字符串,并且不会把字符串截断。 - 字符串下标当前已验证从 `1` 开始,不从 `0` 开始;访问 `s[0]` 会运行报 `String index out of bounds`。 - 字符串切片 `s[start:end]`、替换 `s[start:end] := sub`、删除 `s[start:end] := ""`、插入 `s[index:0] := sub` 当前都可用。 - 基础字符串索引、切片、插入、删除和替换规则,阅读主线仍以 [04_values_and_literals.md](04_values_and_literals.md) 为准;这一篇只补已经单独实测过的深水边界。 ## 已验证语法 原始字符串 `%%` 的空白分隔、标识符和多行: 代码块身份:已验证可执行示例 ```tsl program test; begin s1 := %% ABC%%; s2 := %%tag A"B'C%%tag; s3 := %%m A B%%m; WriteLn(s1); WriteLn(s2); WriteLn(s3); end. ``` 已验证运行结果: - `s1` 输出 `ABC` - `s2` 输出 `A"B'C` - `s3` 输出两行 `A`、`B` - 说明 `%%` 开头后的第一个空白只是分隔符,不属于正文 - 说明 `%%tag ...%%tag` 可以用标识符配对 - 也说明 `%%` 原始字符串支持直接跨行 带前缀的原始字符串: 代码块身份:已验证可执行示例 ```tsl program test; begin WriteLn(L%% ABC%% = L"ABC"); WriteLn(U%% ABC%% = "ABC"); end. ``` 已验证运行结果: - 依次输出 `1`、`1` - 说明 `L%% ...%%` 和 `U%% ...%%` 在当前解释器里都可用 `\uXXXX` 在普通串、宽串和 `U` 串里的边界: 代码块身份:已验证可执行示例 ```tsl program test; begin WriteLn(Length("\u0041")); WriteLn(Length(L"\u0041")); WriteLn(Length(U"\u0041")); end. ``` 已验证运行结果: - 依次输出 `2`、`1`、`1` - 因此不要把普通字符串里的 `"\u0041"` 直接理解成和 `L"\u0041"` 一样的“单字符”写法 字符串显式编码转换: 代码块身份:已验证可执行示例 ```tsl program test; begin utf8_s := U"\u5929\u8F6F"; utf8_s2 := UnicodetoUTF8(L"\u5929\u8F6F"); wide_s := UTF8ToUnicode(utf8_s); ansi_s := UTF8ToAnsi(utf8_s); WriteLn(IfWString(utf8_s)); WriteLn(utf8_s = utf8_s2); WriteLn(wide_s = L"\u5929\u8F6F"); WriteLn(ansi_s = String(L"\u5929\u8F6F")); end. ``` 已验证运行结果: - 依次输出 `0`、`1`、`1`、`1` - 说明 `U""` 在当前解释器里不是宽串 - 说明 UTF8、宽串、普通串之间的显式转换链已经拿到正向最小结果 ASCII `0` 字符不会截断字符串: 代码块身份:已验证可执行示例 ```tsl program test; begin s1 := "A\0B"; s2 := "A"#0"B"; WriteLn(Length(s1)); WriteLn(Length(s2)); WriteLn(s1 = s2); WriteLn(s1[2] = #0); end. ``` 已验证运行结果: - 依次输出 `3`、`3`、`1`、`1` - 说明 `\0` 和 `#0` 都可以把 ASCII `0` 放进字符串 - 也说明当前字符串不是遇到 `#0` 就自动截断的 C 风格零结尾串 字符码拼接: 代码块身份:已验证可执行示例 ```tsl program test; begin WriteLn("A"#48"B"); WriteLn("A"#13#10"B"); end. ``` 已验证运行结果: - 第一行输出 `A0B` - 第二组输出会分成两行 `A`、`B` - 说明 `#48` 这类写法会按字符码直接参与字符串拼接,`#13#10` 可以直接表示换行 字符串下标、切片和区间改写: 代码块身份:已验证可执行示例 ```tsl program test; begin s := "ABCD"; WriteLn(s[1]); WriteLn(s[2]); s := "AAABBB"; WriteLn(s[3:4]); end. ``` 已验证运行结果: - 依次输出 `A`、`B`、`AB` - 说明字符串下标当前从 `1` 开始 - 也说明 `s[3:4]` 会取到起止位置都包含在内的子串 替换、删除和插入: 代码块身份:已验证可执行示例 ```tsl program test; begin s1 := "AAABBB"; s1[3:4] := "111"; WriteLn(s1); s2 := "AAABBB"; s2[3:4] := ""; WriteLn(s2); s3 := "AAABBB"; s3[4:0] := "111"; WriteLn(s3); end. ``` 已验证运行结果: - 三行依次输出 `AA111BB`、`AABB`、`AAA111BBB` - 说明 `s[start:end] := sub` 可以直接替换区间 - 说明右值设为空串时,会删除该区间 - 说明 `s[index:0] := sub` 会在指定位置前插入子串 当前还额外验证到: 代码块身份:反例 / 不可照写 ```text s := "ABCD"; WriteLn(s[0]); ``` 上面这种写法会运行报错,错误信息包含 `String index out of bounds`。不要把字符串下标按数组的 `0` 起始规则来写。 ## 跳转指引 - 回看最常用字符串字面量:见 [04_values_and_literals.md](04_values_and_literals.md) - 回看字符串上的表达式行为:见 [07_expressions_and_operators.md](07_expressions_and_operators.md) - 看类型与转换:见 [19_types_and_conversions.md](19_types_and_conversions.md) - 看值与字面量:见 [04_values_and_literals.md](04_values_and_literals.md)