592 lines
15 KiB
Markdown
592 lines
15 KiB
Markdown
# Basic Types And Values
|
||
|
||
文档类型:语法主线
|
||
是否可直接用于生成代码:是
|
||
是否含已验证可执行示例:是
|
||
是否含已验证反例:是
|
||
遇到不确定时跳转到:[05_variables_and_constants.md](05_variables_and_constants.md)、[07_expressions_and_operators.md](07_expressions_and_operators.md)、[20_strings_and_text.md](20_strings_and_text.md)
|
||
|
||
手册位置:第 4 篇,共 32 篇。上一篇:[03_core_model.md](03_core_model.md)。下一篇:[05_variables_and_constants.md](05_variables_and_constants.md)。
|
||
|
||
这一篇整理基本类型、字面量、数组、字符串与基础值模型,避免把值规则分散在函数或金融示例里。
|
||
|
||
## 这一篇解决什么问题
|
||
|
||
回答“基本类型怎么写、数组和字符串怎么索引、哪些值规则属于语言级事实”。
|
||
|
||
## 必须记住的规则
|
||
|
||
- 最先掌握的几类值是:整数、实数、字符串、布尔和 `array`。
|
||
- 普通字符串既可以用双引号,也可以用单引号。
|
||
- 同类引号本身可以通过连续写两个同类引号放进字符串里。
|
||
- `\\`、`\"`、`\n` 这类基础转义已验证可用。
|
||
- `\t` 和 `\xNN` 这类转义也已验证可用。
|
||
- `\r`、`\r\n`、`\a`、`\b`、`\f`、`\v` 这些经典转义也已验证可用。
|
||
- 当前解释器接受 `%%` 原始字符串;开头的 `%%` 或 `%%tag` 后面必须先跟一个空白分隔符,正文才开始。
|
||
- `%%` 原始字符串里的反斜杠不做转义处理,并且支持多行内容。
|
||
- `array(...)` 既可以写顺序数组,也可以写字符串键表。
|
||
- 顺序数组下标从 `0` 开始。
|
||
- `Binary(...)` 创建的二进制缓冲区,当前也用 `[]` 访问,并且下标从 `0` 开始。
|
||
- 字符串下标从 `1` 开始。
|
||
- `s[0]` 在运行时会越界,不要把字符串当成 0 基下标。
|
||
- 字符串取子串用 `s[start:end]`,并且 `end` 是包含在结果里的。
|
||
- 字符串替换子串用 `s[start:end] := "..."`。
|
||
- 字符串删除子串,本质上就是 `s[start:end] := ""`。
|
||
- 字符串插入子串用 `s[index:0] := "..."`。
|
||
- 当前解释器接受 `L""` 与 `U""` 前缀字符串,但当前手册默认仍优先普通字符串。
|
||
- 当前解释器也接受 `L%% ...%%` 与 `U%% ...%%` 前缀原始字符串。
|
||
- 当前最小验证表明:从 `IfWString(...)` 观察,混合拼接时结果是否为宽串,取决于左操作数是否为宽串;`U""` 在这里仍按非宽串处理。
|
||
- 当前最小验证表明:可以用 `String(...)` 把宽串显式转回普通串,也可以用 `WideString(...)` 把普通串显式转成宽串。
|
||
- 当前最小验证表明:`U""` 可以和 `UnicodetoUTF8(...)`、`UTF8ToUnicode(...)`、`UTF8ToAnsi(...)`、`AnsiToUTF8(...)` 组成一条清晰的 UTF8 转换链;`U""` 本身不等同于宽串。
|
||
- 当前最小验证表明:`\uXXXX` 在普通字符串里不要直接当成“单个字符宽串”;例如 `Length("\u0041")` 为 `2`,而 `Length(L"\u0041")` 和 `Length(U"\u0041")` 都为 `1`。
|
||
- 当前最小验证表明:字符串可以包含 ASCII `0` 字符;`\0` 和 `#0` 都能构造这个字符,并且不会把字符串截断。
|
||
- `#number` 可以按字符码直接拼进字符串里;`#13#10` 这类写法可直接形成换行。
|
||
|
||
## 已验证语法
|
||
|
||
最基础的值写法:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
count := 1;
|
||
price := 12.5;
|
||
name := "ABC";
|
||
flag1 := true;
|
||
flag2 := false;
|
||
items := array(1, 2, 3);
|
||
```
|
||
|
||
其中已经验证:
|
||
|
||
- `true` 可以直接写成布尔值。
|
||
- `false` 可以直接写成布尔值。
|
||
- 运行输出里,`true` / `false` 分别表现为 `1` / `0`。
|
||
|
||
普通字符串字面量:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn("ABC");
|
||
WriteLn('XYZ');
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `ABC`、`XYZ`
|
||
|
||
字符串内引号与基础转义:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn("A""B");
|
||
WriteLn('A''B');
|
||
WriteLn("A\\B");
|
||
WriteLn("A\"B");
|
||
WriteLn("A\nB");
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `A"B`、`A'B`、`A\B`
|
||
- `"A\nB"` 会分成两行输出 `A` 和 `B`
|
||
|
||
更多基础转义:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn("A\tB" = "A"#9"B");
|
||
WriteLn("A\x30B" = "A0B");
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 两行都输出 `1`
|
||
- 说明 `\t` 可以表示制表符,`\x30` 这类写法可以按 ANSI 字节值插入字符
|
||
|
||
经典控制字符转义:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
WriteLn("\r" = #13);
|
||
WriteLn("\r\n" = #13#10);
|
||
WriteLn(Length("\r\n"));
|
||
WriteLn("\a" = #7);
|
||
WriteLn("\b" = #8);
|
||
WriteLn("\f" = #12);
|
||
WriteLn("\v" = #11);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `1`、`1`、`2`、`1`、`1`、`1`、`1`
|
||
- 说明 `\r` / `\r\n` 可以直接表示回车和回车换行
|
||
- 说明 `\a`、`\b`、`\f`、`\v` 也已经在当前解释器里拿到正向最小结果
|
||
|
||
`L""` / `U""` 前缀的最小已验证事实:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn(L"A" = L"\u0041");
|
||
WriteLn(U"Hello" = "Hello");
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `1`、`1`
|
||
- 说明当前解释器接受 `L""`、`U""` 和 ASCII 范围的 `\u` 表达
|
||
- 但在当前控制台里直接 `WriteLn(L"Hello")` 会显示成带空字节的宽串输出,因此当前手册不把 `L""` 作为默认示例写法
|
||
|
||
非 ASCII 宽串的最小已验证事实:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn(L"\u5929\u8F6F" = L""#0x5929#0x8F6F);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `1`
|
||
- 说明当前解释器接受非 ASCII 宽串的 `\uXXXX` 与 `#0xXXXX` 这两种构造方式
|
||
- 但当前手册仍优先把它们当作边界写法,而不是默认入门写法
|
||
|
||
原始字符串 `%%`:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := %% ABC%%;
|
||
WriteLn(s);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `ABC`
|
||
- 说明 `%%` 开头后面的第一个空白只是分隔符,不计入最终字符串内容
|
||
|
||
原始字符串不会处理转义:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := %% a\r\nb%%;
|
||
WriteLn(s = "a\\r\\nb");
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `1`
|
||
- 说明 `%%` 原始字符串里的 `\r`、`\n` 只是普通字符,不会变成回车换行
|
||
|
||
带标识符的原始字符串:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := %%tag A"B'C%%tag;
|
||
WriteLn(s);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `A"B'C`
|
||
- 说明 `%%tag ...%%tag` 的首尾标识符可以配对使用,正文里可直接放单双引号
|
||
|
||
多行原始字符串:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := %%m
|
||
A
|
||
B%%m;
|
||
WriteLn(s);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出两行 `A`、`B`
|
||
- 说明 `%%` 原始字符串支持直接跨行
|
||
|
||
带前缀的原始字符串:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn(L%% ABC%% = L"ABC");
|
||
WriteLn(U%% ABC%% = "ABC");
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `1`、`1`
|
||
- 说明 `L%% ...%%` 和 `U%% ...%%` 在当前解释器里都可用
|
||
|
||
前缀字符串与混合拼接的最小类型观察:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
WriteLn(IfWString("A" + L"B"));
|
||
WriteLn(IfWString(L"A" + "B"));
|
||
WriteLn(IfWString(U"A"));
|
||
WriteLn(IfWString(U"A" + L"B"));
|
||
WriteLn(IfWString(L"A" + U"B"));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `0`、`1`、`0`、`0`、`1`
|
||
- 说明从 `IfWString(...)` 的观察角度看,混合拼接是否得到宽串,取决于左操作数是否为宽串
|
||
- 也说明 `U""` 前缀字符串在这里仍按非宽串处理,不等同于 `L""`
|
||
|
||
显式字符串类型转换:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
a := String(L"abcd");
|
||
b := WideString("abcd");
|
||
WriteLn(a = "abcd");
|
||
WriteLn(IfWString(a));
|
||
WriteLn(b = L"abcd");
|
||
WriteLn(IfWString(b));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `1`、`0`、`1`、`1`
|
||
- 说明 `String(...)` 可以把宽串显式转回普通串
|
||
- 说明 `WideString(...)` 可以把普通串显式转成宽串
|
||
|
||
`U""` 与 UTF8 显式转换:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```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""` 在当前解释器里不是宽串
|
||
- 说明 `U"\u5929\u8F6F"` 和 `UnicodetoUTF8(L"\u5929\u8F6F")` 可以对应到同一个 UTF8 串
|
||
- 说明 `UTF8ToUnicode(...)` 可以把 UTF8 串转回宽串,`UTF8ToAnsi(...)` 可以把 UTF8 串转回普通串
|
||
|
||
ANSI / UTF8 / 宽串的最小往返:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
ansi_s := String(L"\u5929\u8F6F");
|
||
utf8_s := AnsiToUTF8(ansi_s);
|
||
ansi_back := UTF8ToAnsi(utf8_s);
|
||
wide_s := UTF8ToUnicode(utf8_s);
|
||
WriteLn(IfWString(ansi_s));
|
||
WriteLn(IfWString(utf8_s));
|
||
WriteLn(IfWString(wide_s));
|
||
WriteLn(ansi_back = ansi_s);
|
||
WriteLn(wide_s = L"\u5929\u8F6F");
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `0`、`0`、`1`、`1`、`1`
|
||
- 说明 `AnsiToUTF8(...)` 产物在这里仍按非宽串处理
|
||
- 说明 `UTF8ToAnsi(...)` 和 `UTF8ToUnicode(...)` 都已经在当前解释器里拿到了正向最小结果
|
||
|
||
`\uXXXX` 在普通字符串、宽串和 `U` 串里的最小边界:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
WriteLn(Length("\u0041"));
|
||
WriteLn(Length(L"\u0041"));
|
||
WriteLn(Length(U"\u0041"));
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `2`、`1`、`1`
|
||
- 因此不要把普通字符串里的 `"\u0041"` 直接理解成和 `L"\u0041"` 一样的“单字符宽串”语义
|
||
|
||
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 风格零结尾串
|
||
|
||
带 ASCII `0` 的字符串切片仍然保留该字符:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
s := "A"#0"B";
|
||
t := s[1:3];
|
||
WriteLn(Length(t));
|
||
WriteLn(t = s);
|
||
u := s[2:2];
|
||
WriteLn(Length(u));
|
||
WriteLn(u = #0);
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `3`、`1`、`1`、`1`
|
||
- 说明字符串切片和单字符读取对 ASCII `0` 仍然稳定,不会在中间被截断
|
||
|
||
字符码拼接:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
WriteLn("A"#48"B");
|
||
WriteLn("A"#13#10"B");
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 第一行输出 `A0B`
|
||
- 第二组输出会分成两行 `A`、`B`
|
||
- 说明 `#48` 这类写法会按字符码直接参与字符串拼接,`#13#10` 可直接表示换行
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
arr := array(10, 20, 30);
|
||
hash := array("Code": "0001", "Price": 12.3);
|
||
s := "ABC";
|
||
WriteLn("ARR0=", arr[0]);
|
||
WriteLn("ARR1=", arr[1]);
|
||
WriteLn("HASH=", hash["Code"]);
|
||
WriteLn("STR1=", s[1]);
|
||
WriteLn("STR2=", s[2]);
|
||
WriteLn("STR3=", s[3]);
|
||
```
|
||
|
||
已验证运行结果对应关系:
|
||
|
||
- `arr[0] = 10`
|
||
- `arr[1] = 20`
|
||
- `hash["Code"] = "0001"`
|
||
- `s[1] = "A"`
|
||
- `s[2] = "B"`
|
||
- `s[3] = "C"`
|
||
|
||
二进制缓冲区:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
program test;
|
||
begin
|
||
b := Binary("30");
|
||
WriteLn(IfBinary(b));
|
||
WriteLn(b[0] = "3");
|
||
WriteLn(b[1] = "0");
|
||
b[1] := 0x31;
|
||
WriteLn(b[1] = "1");
|
||
end.
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 依次输出 `1`、`1`、`1`、`1`
|
||
- 说明 `Binary(...)` 当前确实得到二进制缓冲区
|
||
- 说明二进制缓冲区的 `[]` 下标从 `0` 开始
|
||
- 也说明二进制缓冲区项可以直接按单字节读写
|
||
|
||
字符串子串读取:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := "ABCDE";
|
||
WriteLn(s[2:4]);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `BCD`
|
||
- 这说明字符串区间 `2:4` 会包含结束位 `4`
|
||
|
||
字符串替换子串:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := "ABCDE";
|
||
s[2:4] := "XYZ";
|
||
WriteLn(s);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `AXYZE`
|
||
|
||
字符串删除子串:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := "ABCDE";
|
||
s[2:4] := "";
|
||
WriteLn(s);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `AE`
|
||
|
||
字符串插入子串:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
s := "AAABBB";
|
||
s[4:0] := "111";
|
||
WriteLn(s);
|
||
```
|
||
|
||
已验证运行结果:
|
||
|
||
- 输出 `AAA111BBB`
|
||
- 这说明 `s[index:0] := ...` 是在指定位置插入,而不是删除或替换区间
|
||
|
||
## 最小可编译示例
|
||
|
||
如果你只是要抓住“基本类型 + array”的第一层,用这个最短例子:
|
||
|
||
代码块身份:已验证可执行示例
|
||
|
||
```tsl
|
||
count := 1;
|
||
price := 12.5;
|
||
name := "ABC";
|
||
flag := true;
|
||
items := array(1, 2, 3);
|
||
```
|
||
|
||
## 常见误写
|
||
|
||
- 用 `s[0]` 访问字符串首字符。
|
||
- 误以为 `array(...)` 只能写顺序数组,不能写字符串键表。
|
||
- 误以为字符串区间和数组一样从 `0` 开始。
|
||
- 把 `s[index:0] := ...` 误解成“替换到第 0 位”。
|
||
- 把二进制缓冲区也按字符串的 `1` 基下标去理解。
|
||
- 在普通字符串示例里默认切到 `L""`,却没有先确认下游场景真的需要宽串。
|
||
- 把 `U""` 误解成和 `L""` 一样的宽串。
|
||
- 把普通字符串里的 `"\uXXXX"` 直接当成和 `L"\uXXXX"` 一样的单字符写法。
|
||
- 把带 `#0` / `\0` 的字符串当成会自动截断的 C 风格零结尾串。
|
||
- 把 `%%ABC%%` 当成合法的原始字符串起手式。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
s := "ABC";
|
||
WriteLn(s[0]);
|
||
```
|
||
|
||
上面这类写法在运行时会报字符串下标越界。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
s := "ABCDE";
|
||
WriteLn(s[1:3]);
|
||
```
|
||
|
||
不要把上面这个区间访问类比成数组切片的半开区间。当前解释器里它输出的是 `ABC`,不是 `AB`;字符串区间的结束位会被包含进结果。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
s := %%ABC%%;
|
||
```
|
||
|
||
上面这种写法在当前解释器里会报 `missing or invalid characters`。`%%` 原始字符串开头后面必须先跟一个空白分隔符,或者写成 `%%tag ` 这种“标识符 + 空白”形式。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
WriteLn(Length("\u0041"));
|
||
WriteLn(Length(L"\u0041"));
|
||
```
|
||
|
||
不要把上面这两行想成同一件事。当前解释器里前者输出 `2`,后者输出 `1`;普通字符串里的 `\uXXXX` 不能直接按宽串单字符去理解。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
WriteLn(IfWString(U"\u5929\u8F6F"));
|
||
WriteLn(IfWString(L"\u5929\u8F6F"));
|
||
```
|
||
|
||
不要把 `U""` 和 `L""` 当成同一种“宽串前缀”。当前解释器里前者输出 `0`,后者输出 `1`。
|
||
|
||
代码块身份:反例 / 不可照写
|
||
|
||
```text
|
||
s := "A"#0"B";
|
||
WriteLn(Length(s));
|
||
```
|
||
|
||
不要把上面这类字符串想成遇到 `#0` 就结束。当前解释器里这里输出 `3`,说明 ASCII `0` 是字符串内容的一部分。
|
||
|
||
## 跳转指引
|
||
|
||
- 值有了名字以后怎么看:见 [05_variables_and_constants.md](05_variables_and_constants.md)
|
||
- 值怎样组成表达式:见 [07_expressions_and_operators.md](07_expressions_and_operators.md)
|
||
- 看字符串与编码深水专题:见 [20_strings_and_text.md](20_strings_and_text.md)
|