15 KiB
Basic Types And Values
文档类型:语法主线 是否可直接用于生成代码:是 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:05_variables_and_constants.md、07_expressions_and_operators.md、20_strings_and_text.md
手册位置:第 4 篇,共 32 篇。上一篇:03_core_model.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这类写法可直接形成换行。
已验证语法
最基础的值写法:
代码块身份:已验证可执行示例
count := 1;
price := 12.5;
name := "ABC";
flag1 := true;
flag2 := false;
items := array(1, 2, 3);
其中已经验证:
true可以直接写成布尔值。false可以直接写成布尔值。- 运行输出里,
true/false分别表现为1/0。
普通字符串字面量:
代码块身份:已验证可执行示例
WriteLn("ABC");
WriteLn('XYZ');
已验证运行结果:
- 依次输出
ABC、XYZ
字符串内引号与基础转义:
代码块身份:已验证可执行示例
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
更多基础转义:
代码块身份:已验证可执行示例
WriteLn("A\tB" = "A"#9"B");
WriteLn("A\x30B" = "A0B");
已验证运行结果:
- 两行都输出
1 - 说明
\t可以表示制表符,\x30这类写法可以按 ANSI 字节值插入字符
经典控制字符转义:
代码块身份:已验证可执行示例
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"" 前缀的最小已验证事实:
代码块身份:已验证可执行示例
WriteLn(L"A" = L"\u0041");
WriteLn(U"Hello" = "Hello");
已验证运行结果:
- 依次输出
1、1 - 说明当前解释器接受
L""、U""和 ASCII 范围的\u表达 - 但在当前控制台里直接
WriteLn(L"Hello")会显示成带空字节的宽串输出,因此当前手册不把L""作为默认示例写法
非 ASCII 宽串的最小已验证事实:
代码块身份:已验证可执行示例
WriteLn(L"\u5929\u8F6F" = L""#0x5929#0x8F6F);
已验证运行结果:
- 输出
1 - 说明当前解释器接受非 ASCII 宽串的
\uXXXX与#0xXXXX这两种构造方式 - 但当前手册仍优先把它们当作边界写法,而不是默认入门写法
原始字符串 %%:
代码块身份:已验证可执行示例
s := %% ABC%%;
WriteLn(s);
已验证运行结果:
- 输出
ABC - 说明
%%开头后面的第一个空白只是分隔符,不计入最终字符串内容
原始字符串不会处理转义:
代码块身份:已验证可执行示例
s := %% a\r\nb%%;
WriteLn(s = "a\\r\\nb");
已验证运行结果:
- 输出
1 - 说明
%%原始字符串里的\r、\n只是普通字符,不会变成回车换行
带标识符的原始字符串:
代码块身份:已验证可执行示例
s := %%tag A"B'C%%tag;
WriteLn(s);
已验证运行结果:
- 输出
A"B'C - 说明
%%tag ...%%tag的首尾标识符可以配对使用,正文里可直接放单双引号
多行原始字符串:
代码块身份:已验证可执行示例
s := %%m
A
B%%m;
WriteLn(s);
已验证运行结果:
- 输出两行
A、B - 说明
%%原始字符串支持直接跨行
带前缀的原始字符串:
代码块身份:已验证可执行示例
WriteLn(L%% ABC%% = L"ABC");
WriteLn(U%% ABC%% = "ABC");
已验证运行结果:
- 依次输出
1、1 - 说明
L%% ...%%和U%% ...%%在当前解释器里都可用
前缀字符串与混合拼接的最小类型观察:
代码块身份:已验证可执行示例
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""
显式字符串类型转换:
代码块身份:已验证可执行示例
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 显式转换:
代码块身份:已验证可执行示例
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 / 宽串的最小往返:
代码块身份:已验证可执行示例
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 串里的最小边界:
代码块身份:已验证可执行示例
program test;
begin
WriteLn(Length("\u0041"));
WriteLn(Length(L"\u0041"));
WriteLn(Length(U"\u0041"));
end.
已验证运行结果:
- 依次输出
2、1、1 - 因此不要把普通字符串里的
"\u0041"直接理解成和L"\u0041"一样的“单字符宽串”语义
ASCII 0 字符不会截断字符串:
代码块身份:已验证可执行示例
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都可以把 ASCII0放进字符串 - 也说明当前字符串不是遇到
#0就自动截断的 C 风格零结尾串
带 ASCII 0 的字符串切片仍然保留该字符:
代码块身份:已验证可执行示例
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仍然稳定,不会在中间被截断
字符码拼接:
代码块身份:已验证可执行示例
WriteLn("A"#48"B");
WriteLn("A"#13#10"B");
已验证运行结果:
- 第一行输出
A0B - 第二组输出会分成两行
A、B - 说明
#48这类写法会按字符码直接参与字符串拼接,#13#10可直接表示换行
代码块身份:已验证可执行示例
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] = 10arr[1] = 20hash["Code"] = "0001"s[1] = "A"s[2] = "B"s[3] = "C"
二进制缓冲区:
代码块身份:已验证可执行示例
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开始 - 也说明二进制缓冲区项可以直接按单字节读写
字符串子串读取:
代码块身份:已验证可执行示例
s := "ABCDE";
WriteLn(s[2:4]);
已验证运行结果:
- 输出
BCD - 这说明字符串区间
2:4会包含结束位4
字符串替换子串:
代码块身份:已验证可执行示例
s := "ABCDE";
s[2:4] := "XYZ";
WriteLn(s);
已验证运行结果:
- 输出
AXYZE
字符串删除子串:
代码块身份:已验证可执行示例
s := "ABCDE";
s[2:4] := "";
WriteLn(s);
已验证运行结果:
- 输出
AE
字符串插入子串:
代码块身份:已验证可执行示例
s := "AAABBB";
s[4:0] := "111";
WriteLn(s);
已验证运行结果:
- 输出
AAA111BBB - 这说明
s[index:0] := ...是在指定位置插入,而不是删除或替换区间
最小可编译示例
如果你只是要抓住“基本类型 + array”的第一层,用这个最短例子:
代码块身份:已验证可执行示例
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%%当成合法的原始字符串起手式。
代码块身份:反例 / 不可照写
s := "ABC";
WriteLn(s[0]);
上面这类写法在运行时会报字符串下标越界。
代码块身份:反例 / 不可照写
s := "ABCDE";
WriteLn(s[1:3]);
不要把上面这个区间访问类比成数组切片的半开区间。当前解释器里它输出的是 ABC,不是 AB;字符串区间的结束位会被包含进结果。
代码块身份:反例 / 不可照写
s := %%ABC%%;
上面这种写法在当前解释器里会报 missing or invalid characters。%% 原始字符串开头后面必须先跟一个空白分隔符,或者写成 %%tag 这种“标识符 + 空白”形式。
代码块身份:反例 / 不可照写
WriteLn(Length("\u0041"));
WriteLn(Length(L"\u0041"));
不要把上面这两行想成同一件事。当前解释器里前者输出 2,后者输出 1;普通字符串里的 \uXXXX 不能直接按宽串单字符去理解。
代码块身份:反例 / 不可照写
WriteLn(IfWString(U"\u5929\u8F6F"));
WriteLn(IfWString(L"\u5929\u8F6F"));
不要把 U"" 和 L"" 当成同一种“宽串前缀”。当前解释器里前者输出 0,后者输出 1。
代码块身份:反例 / 不可照写
s := "A"#0"B";
WriteLn(Length(s));
不要把上面这类字符串想成遇到 #0 就结束。当前解释器里这里输出 3,说明 ASCII 0 是字符串内容的一部分。
跳转指引
- 值有了名字以后怎么看:见 05_variables_and_constants.md
- 值怎样组成表达式:见 07_expressions_and_operators.md
- 看字符串与编码深水专题:见 20_strings_and_text.md