# Y++ 自定义数据类型语法说明
自定义数据类型使用 `.yt` 文件,适合保存一组字段、嵌套数据和与 DLL 对应的结构体布局。`.yt` 只定义数据,不写事件、执行语句、方法或继承。
## 1. 文件和基本定义
文件放在项目 `source/` 或其子目录:
```text
source/types/学生.yt
```
简写语法:
```yt
学生 {
整数 学号 = 0
文本 姓名
逻辑 启用 = 真
}
```
结构体写法:
```yt
结构体 矩形 {
整数 左 = 0
整数 上 = 0
整数 右 = 0
整数 下 = 0
}
```
两种写法都表示纯字段类型。加入项目后在 `.yc`、`.ypp` 和其它 `.yt` 中直接使用,无需 `#include`。
## 2. 字段类型和默认值
```yt
任务信息 {
整数 编号 = 0
长整数 文件大小 = 0
小数 进度 = 0
文本 标题 = ""
逻辑 已完成 = 假
字节集 原始数据
}
```
基础字段可给默认值。字段名在同一类型中不能重复。
使用:
```yc
任务信息 任务
任务.编号 = 1001
任务.标题 = "导入数据"
任务.进度 = 0.5
输出(任务.标题)
```
## 3. 嵌套自定义类型
```yt
坐标 {
小数 X = 0
小数 Y = 0
}
地址 {
文本 省
文本 市
文本 街道
坐标 门牌坐标
}
人员 {
文本 姓名
地址 家庭住址
}
```
逐层赋值和读取:
```yc
人员 张三
张三.姓名 = "张三"
张三.家庭住址.省 = "广东"
张三.家庭住址.市 = "深圳"
张三.家庭住址.门牌坐标.X = 113.9
张三.家庭住址.门牌坐标.Y = 22.5
输出(张三.家庭住址.市)
输出(张三.家庭住址.门牌坐标.X)
```
也可以先构造内层对象再整体赋值:
```yc
地址 新地址
新地址.省 = "浙江"
新地址.市 = "杭州"
人员 李四
李四.姓名 = "李四"
李四.家庭住址 = 新地址
```
## 4. 动态数组和限长数组
类型数组:
```yc
人员 成员[] = []
人员 张三
张三.姓名 = "张三"
数组添加(成员, 张三)
人员 李四
李四.姓名 = "李四"
数组添加(成员, 李四)
输出(成员[0].姓名)
输出(成员[1].姓名)
```
限制最大数量:
```yc
人员 成员[40] = []
```
预分配元素:
```yc
人员 成员[40]
成员[0].姓名 = "张三"
```
下标从 0 开始。访问前应确认数组长度:
```yc
如果 (数组长度(成员) > 0) {
输出(成员[0].姓名)
}
```
## 5. 类型内部的数组字段
```yt
学生 {
文本 姓名
整数 成绩[] = []
}
班级 {
文本 名称
学生 成员[] = []
}
```
```yc
学生 张三
张三.姓名 = "张三"
数组添加(张三.成绩, 96)
数组添加(张三.成绩, 88)
班级 一班
一班.名称 = "软件一班"
数组添加(一班.成员, 张三)
输出(一班.成员[0].姓名)
输出(一班.成员[0].成绩[1])
```
## 6. 多层数组嵌套完整示例
```yt
坐标 {
小数 X = 0
小数 Y = 0
}
家庭地址 {
文本 市
坐标 门牌坐标
}
学生 {
文本 姓名
家庭地址 家庭住址
}
班级 {
学生 成员[] = []
}
学校 {
文本 校名
班级 班级列表[] = []
}
```
```yc
函数 创建学校() 学校 {
学生 新学生
新学生.姓名 = "张三"
新学生.家庭住址.市 = "深圳"
新学生.家庭住址.门牌坐标.X = 100.5
班级 新班级
数组添加(新班级.成员, 新学生)
学校 我校
我校.校名 = "示例学校"
数组添加(我校.班级列表, 新班级)
返回(我校)
}
函数 主窗口.创建完毕() {
学校 我校 = 创建学校()
输出(我校.班级列表[0].成员[0].姓名)
输出(我校.班级列表[0].成员[0].家庭住址.市)
输出(我校.班级列表[0].成员[0].家庭住址.门牌坐标.X)
}
```
## 7. 按添加时的变量名访问
通过 `数组添加(数组, 变量名)` 加入的对象可按下标访问;支持命名访问的数组也可写:
```yc
学生 学生列表[] = []
学生 张三
张三.姓名 = "张三"
数组添加(学生列表, 张三)
输出(学生列表[0].姓名)
输出(学生列表.张三.姓名)
```
动态下标适合循环,命名访问适合固定配置成员。
## 8. 作为函数参数
值参数:
```yc
函数 打印学生(学生 s) {
输出(s.姓名)
}
学生 张三
张三.姓名 = "张三"
打印学生(张三)
```
多个自定义类型参数:
```yc
函数 是否同城(学生 a, 学生 b) 逻辑 {
返回(a.家庭住址.市 == b.家庭住址.市)
}
```
## 9. 作为函数返回值
```yc
函数 新建学生(文本 姓名, 文本 城市) 学生 {
学生 s
s.姓名 = 姓名
s.家庭住址.市 = 城市
返回(s)
}
学生 新同学 = 新建学生("王五", "广州")
输出(新同学.家庭住址.市)
```
修改后返回并回写:
```yc
函数 修改城市(学生 s, 文本 城市) 学生 {
s.家庭住址.市 = 城市
返回(s)
}
新同学 = 修改城市(新同学, "佛山")
```
## 10. 全局自定义类型变量
`source/globals.yc`:
```yc
学校 当前学校
学生 在线学生[] = []
```
其它文件直接使用:
```yc
函数 主窗口.创建完毕() {
当前学校.校名 = "第一中学"
输出(当前学校.校名)
}
```
不要在多个文件重复声明同名全局对象。
## 11. 与自写类组合
`.yt` 负责数据,`.ypp` 负责行为:
```ypp
类 学生服务 {
方法 修改姓名(学生 s, 文本 新姓名) 学生 {
s.姓名 = 新姓名
返回(s)
}
方法 显示(学生 s) {
输出(s.姓名 + " / " + s.家庭住址.市)
}
}
```
```yc
学生服务 服务
学生 s = 新建学生("赵六", "东莞")
s = 服务.修改姓名(s, "赵小六")
服务.显示(s)
```
## 12. 与 `.ypi` API 组合
结构体字段顺序、类型宽度必须与第三方 DLL 头文件一致:
```yt
结构体 原生矩形 {
整数 left
整数 top
整数 right
整数 bottom
}
```
```ypi
DLL("user32.dll", "stdcall")
逻辑 GetWindowRect(长整数 窗口句柄, 原生矩形 结果) 读取原生窗口矩形
```
```yc
原生矩形 r
如果 (读取原生窗口矩形(窗口句柄, r)) {
输出("宽=" + 到文本(r.right - r.left))
}
```
## 13. 错误写法与正确写法
### 错误:在 `.yc` 中临时定义类型
```yc
// 错误
学生 {
文本 姓名
}
```
正确做法:放到 `.yt` 文件。
### 错误:在 `.yt` 中写方法
```yt
// 错误
学生 {
文本 姓名
方法 打印() { }
}
```
正确做法:数据放 `.yt`,方法放 `.ypp` 类。
### 错误:类型和变量顺序写反
```yc
// 错误
张三 学生
// 正确
学生 张三
```
### 错误:漏写数组下标
```yc
// 错误:成员是数组
我校.班级列表.成员.姓名
// 正确
我校.班级列表[0].成员[0].姓名
```
### 错误:数组越界
```yc
学生 成员[] = []
// 错误:数组仍为空
输出(成员[0].姓名)
```
正确做法:先 `数组添加`,或检查 `数组长度`。
### 错误:字段不存在
```yc
// 学生类型未定义“年龄”
张三.年龄 = 20
```
正确做法:在 `.yt` 增加字段并保存,或使用真实字段名。
### 错误:把自定义类型当类方法调用
```yc
// 错误:纯 .yt 类型没有方法
张三.保存()
```
正确做法:定义 `学生服务` 类并写 `服务.保存(张三)`。
### 错误:原生结构体字段宽度错误
```yt
// x64 指针字段错误使用整数,地址可能被截断
结构体 原生数据 {
整数 地址
}
```
正确做法:x64 地址、指针、句柄使用 `长整数`,并逐字段核对 DLL 文档。
### 错误:修改值参数后认为原对象一定变化
```yc
函数 改名(学生 s) {
s.姓名 = "新名字"
}
```
稳妥写法是返回并回写:
```yc
函数 改名(学生 s) 学生 {
s.姓名 = "新名字"
返回(s)
}
张三 = 改名(张三)
```
## 14. 排查顺序
1. 确认 `.yt` 已出现在项目树并已保存。
2. 检查类型名、变量名、字段名有没有写反或拼错。
3. 点号链每遇到数组都先写 `[下标]`。
4. 如果点号出现不相关成员,先检查链条前一段的类型是否可解析。
5. 原生 API 结构体逐字段核对顺序、宽度和 x86/x64 差异。
评论