playbook/docs/tsl/syntax/12_pitfalls.md

272 lines
7.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pitfalls
文档类型:反例索引页
是否可直接用于生成代码:否
是否含已验证可执行示例:否
是否含已验证反例:是
遇到不确定时跳转到:[03_core_model.md](03_core_model.md)、[06_functions_and_calls.md](06_functions_and_calls.md)、[09_objects_and_classes.md](09_objects_and_classes.md)
手册位置:第 12 篇,共 32 篇。上一篇:[11_runtime_context_and_with.md](11_runtime_context_and_with.md)。下一篇:[13_matrix_and_collections.md](13_matrix_and_collections.md)。
这一篇不讲新知识,只做反例索引。
## 这一篇解决什么问题
回答“哪些写法最容易凭直觉写出来,但在 TSL 里会编译失败、运行出错,或语义并不可靠”。
## 这页怎么用
- 先按主题扫一遍,再回到对应正文看正确写法。
- 这里不重复讲完整规则,只保留“错法 -> 正确页”的索引。
- 只有已经单独验证过的误写,才会列在这里。
## 已验证反例索引
### 文件模型与基础值
#### 1. 把裸 `class Name` 当成顶层类声明
代码块身份:反例 / 不可照写
```text
class Person
end;
```
这会编译失败。正确页:见 [09_objects_and_classes.md](09_objects_and_classes.md)
#### 2. 把 `=` 当成赋值
代码块身份:反例 / 不可照写
```text
a = 1;
```
这会编译失败。正确页:见 [07_expressions_and_operators.md](07_expressions_and_operators.md)
#### 3. 把字符串当成 0 基下标
代码块身份:反例 / 不可照写
```text
s := "ABC";
WriteLn(s[0]);
```
这会在运行时报字符串下标越界。正确页:见 [04_values_and_literals.md](04_values_and_literals.md)
#### 4. 把普通字符串里的 `"\uXXXX"` 直接当成和 `L"\uXXXX"` 一样的单字符宽串
代码块身份:反例 / 不可照写
```text
WriteLn(Length("\u0041"));
WriteLn(Length(L"\u0041"));
```
这不是同一件事。当前解释器里前者输出 `2`,后者输出 `1`。正确页:见 [04_values_and_literals.md](04_values_and_literals.md)
#### 5. 把 `U""` 误当成和 `L""` 一样的宽串
代码块身份:反例 / 不可照写
```text
WriteLn(IfWString(U"\u5929\u8F6F"));
WriteLn(IfWString(L"\u5929\u8F6F"));
```
这不是同一回事。当前解释器里前者输出 `0`,后者输出 `1`。正确页:见 [04_values_and_literals.md](04_values_and_literals.md)
#### 6. 把带 `#0` / `\0` 的字符串当成会自动截断的零结尾串
代码块身份:反例 / 不可照写
```text
s := "A"#0"B";
WriteLn(Length(s));
```
这不要按 C 风格字符串去理解。当前解释器里这里输出 `3`,说明 `#0` 仍是字符串内容的一部分。正确页:见 [04_values_and_literals.md](04_values_and_literals.md)
#### 7. 把顶层函数定义和松散语句混写
代码块身份:反例 / 不可照写
```text
function Add(a, b);
begin
return a + b;
end;
value := Add(1, 2);
```
这会编译失败。正确页:见 [03_core_model.md](03_core_model.md)
#### 8. 顶层单独写 `const Name = value;`
代码块身份:反例 / 不可照写
```text
const value = 1;
```
这在当前解释器里会编译失败。正确页:见 [05_variables_and_constants.md](05_variables_and_constants.md)
### 函数与调用
#### 9. 把 `a = 1` 当成命名参数
代码块身份:反例 / 不可照写
```text
Pack(a = 1, b = 2)
```
这不要当成可靠的命名参数写法。它虽然可能编译通过,但实测返回结果不对。正确页:见 [06_functions_and_calls.md](06_functions_and_calls.md)
#### 10. 把 `like` 当成 SQL `%` / `_` 通配
代码块身份:反例 / 不可照写
```text
WriteLn("abc" like "a%");
```
这在当前解释器里输出 `0`,不要按 SQL `LIKE` 去理解。当前手册里,`like` 的右侧按正则模式解释。正确页:见 [07_expressions_and_operators.md](07_expressions_and_operators.md)
#### 11. 把普通函数默认参数规则直接套到 `unit interface` 的 `const` 默认参数
代码块身份:反例 / 不可照写
```text
unit UnitConst;
interface
const CS = 888;
function F(a, b = 100, c = CS);
```
这不要直接当成已经等价于普通函数默认参数规则的写法。在当前解释器里,对应的 `F(1)` 实测输出是 `101`,不是按 `CS = 888` 补值。正确页:见 [06_functions_and_calls.md](06_functions_and_calls.md) 和 [10_units_and_scope.md](10_units_and_scope.md)
#### 12. 把 `external` 后面的 DLL 名直接写成字符串拼接表达式
代码块身份:反例 / 不可照写
```text
function TickFromExpr(): int64; stdcall; external "kernel32"$"."$"dll" name "GetTickCount64";
```
这会编译失败,错误是 `dll filename const string not found after external`。正确页:见 [06_functions_and_calls.md](06_functions_and_calls.md)
### Unit 与查找路径
#### 13. 把“文件就在当前目录”当成当前命令行解释器已经能找到对应 `unit` / `.tsf`
代码块身份:反例 / 不可照写
```text
program test;
uses DemoUnit;
begin
WriteLn(Ping());
end.
```
如果 `DemoUnit.tsf` 所在目录没有进入 `-LIBPATH`、`tsl.conf` 的 `Libpath=` 或运行时查找路径,上面这种写法虽然语法能过,但运行时仍然找不到 `Ping()`。正确页:见 [10_units_and_scope.md](10_units_and_scope.md)
#### 14. 把函数体或类定义体里的 `uses` 写在第一条语句之后,或在同一作用域里重复写第二个 `uses`
代码块身份:反例 / 不可照写
```text
function Run();
begin
a := 1;
uses DemoUnit;
return Ping();
end;
type Worker = class
uses UnitA;
uses UnitB;
end;
```
上面这两类写法都没有通过。函数里的错法会报 `invalid statement`,类里的错法会报 `invalid class definition`。正确页:见 [10_units_and_scope.md](10_units_and_scope.md)
#### 15. 把限定读取 `DemoUnit.VarName` 误当成也支持限定赋值
代码块身份:反例 / 不可照写
```text
DemoUnit.UnitCounter := 13;
```
这在当前解释器里会编译失败,错误是左值不可赋值。正确页:见 [10_units_and_scope.md](10_units_and_scope.md)
### 类与对象
#### 16. 把裸类名当成 `class function` 或静态字段的直接访问入口
代码块身份:反例 / 不可照写
```text
MathBox.Add(1, 2);
THuman.mCount := 7;
```
这两种写法在当前解释器里都没有通过。当前已验证可用的是先拿到类类型,再用 `class(MathBox).Add(...)` / `FindClass("MathBox").Add(...)` 调类方法,以及 `class(THuman).mCount` 访问静态字段。正确页:见 [09_objects_and_classes.md](09_objects_and_classes.md)
#### 17. 把 `private` / `protected create` 当成一定会执行的构造函数
代码块身份:反例 / 不可照写
```text
program test;
type A = class
public
value;
private
function create(v);
begin
self.value := v;
end;
end;
begin
a := new A(111);
WriteLn(a.value);
end.
```
这不会报“不能创建对象”,但 `create` 不会执行;上面的 `a.value` 实测输出 `<NIL>`。正确页:见 [09_objects_and_classes.md](09_objects_and_classes.md)
#### 18. 在子类里把父类 `private` 成员当成 `protected` 成员用
代码块身份:反例 / 不可照写
```text
program test;
type A = class
private
secret;
end;
type B = class(A)
public
function SetSecret(v);
begin
self.secret := v;
end;
end;
```
这会在执行时报对象成员访问错误。正确页:见 [09_objects_and_classes.md](09_objects_and_classes.md)
## 跳转指引
- 整体读法:见 [01_introduction.md](01_introduction.md)
- 函数写法:见 [06_functions_and_calls.md](06_functions_and_calls.md)
- unit / uses见 [10_units_and_scope.md](10_units_and_scope.md)
- 类写法:见 [09_objects_and_classes.md](09_objects_and_classes.md)