# Y++ 自写类及继承语法说明
自写类使用 `.ypp` 文件,适合封装状态、属性、方法、构造逻辑和继承关系。事件入口仍写在 `.yc`,纯数据布局使用 `.yt`。
## 1. 最小类定义
`source/classes/计数器.ypp`:
```ypp
类 计数器 {
属性 整数 当前值 = 0
方法 增加() {
当前值 = 当前值 + 1
}
方法 取值() 整数 {
返回(当前值)
}
}
```
调用:
```yc
函数 主窗口.创建完毕() {
计数器 c
c.增加()
c.增加()
输出(c.取值())
}
```
## 2. 属性
```ypp
类 用户 {
属性 整数 编号 = 0
属性 文本 姓名 = ""
属性 逻辑 启用 = 真
属性 文本 标签[] = []
}
```
类外访问:
```yc
用户 u
u.编号 = 1001
u.姓名 = "张三"
数组添加(u.标签, "管理员")
输出(u.姓名)
```
属性类型也可以是 `.yt` 自定义数据类型:
```ypp
类 客户档案 {
属性 学生 基本信息
属性 地址 联系地址
}
```
```yc
客户档案 档案
档案.基本信息.姓名 = "李四"
档案.联系地址.市 = "杭州"
```
## 3. 方法参数和返回值
```ypp
类 数学工具 {
方法 相加(整数 a, 整数 b) 整数 {
返回(a + b)
}
方法 格式化(文本 名称, 整数 数量) 文本 {
返回(名称 + " × " + 到文本(数量))
}
方法 是否有效(整数 数值) 逻辑 {
返回(数值 >= 0)
}
}
```
```yc
数学工具 工具
整数 合计 = 工具.相加(10, 20)
文本 标题 = 工具.格式化("苹果", 3)
如果 (工具.是否有效(合计)) {
输出(标题)
}
```
## 4. 构造方法
无参构造:
```ypp
类 日志器 {
属性 文本 前缀
方法 构造() {
前缀 = "[程序]"
}
方法 写入(文本 内容) {
输出(前缀 + 内容)
}
}
```
```yc
日志器 日志
日志.写入("启动完成")
```
需要参数的初始化:
```ypp
类 矩形模型 {
属性 整数 宽 = 0
属性 整数 高 = 0
方法 初始化(整数 初始宽, 整数 初始高) {
宽 = 初始宽
高 = 初始高
}
方法 面积() 整数 {
返回(宽 * 高)
}
}
```
```yc
矩形模型 r
r.初始化(10, 20)
输出(r.面积())
```
类实例使用“类名 对象名”声明。需要传入初始数据时,定义普通初始化方法并在声明后调用;不要写成 `矩形模型 r(10, 20)`,也不要混用其它语言的 `new` 语法。
## 5. 类中使用 `.yt` 数据
`source/types/学生.yt`:
```yt
学生 {
整数 学号 = 0
文本 姓名
整数 分数[] = []
}
```
`source/classes/学籍服务.ypp`:
```ypp
类 学籍服务 {
属性 学生 学生列表[] = []
方法 添加(学生 新学生) {
数组添加(学生列表, 新学生)
}
方法 取学生(整数 下标) 学生 {
返回(学生列表[下标])
}
方法 修改姓名(整数 下标, 文本 新姓名) {
学生 s = 学生列表[下标]
s.姓名 = 新姓名
学生列表[下标] = s
}
}
```
```yc
学籍服务 服务
学生 s
s.学号 = 1001
s.姓名 = "张三"
服务.添加(s)
服务.修改姓名(0, "张小三")
输出(服务.取学生(0).姓名)
```
## 6. 方法文档注释
紧邻方法写说明,IDE 调用提示可读取:
```ypp
类 人员服务 {
/// @说明 根据编号查找人员,找不到时返回空名称人员
/// 参数 @编号 要查找的人员编号
方法 查找(整数 编号) 学生 {
学生 空结果
返回(空结果)
}
/// @说明 修改人员姓名
/// 参数 @人员 原人员数据
/// 参数 @新姓名 修改后的姓名
方法 改名(学生 人员, 文本 新姓名) 学生 {
人员.姓名 = 新姓名
返回(人员)
}
}
```
注释参数名必须与方法形参一致,注释与方法之间不要插入其它声明。
## 7. 基本继承
```ypp
类 动物 {
属性 文本 名称
方法 显示名称() {
输出("名称:" + 名称)
}
方法 发声() {
输出("动物声音")
}
}
类 狗 : 动物 {
属性 文本 品种
方法 发声() {
输出("汪汪")
}
方法 显示资料() {
显示名称()
输出("品种:" + 品种)
}
}
```
```yc
狗 小狗
小狗.名称 = "旺财"
小狗.品种 = "中华田园犬"
小狗.显示名称()
小狗.显示资料()
小狗.发声()
```
子类可访问继承得到的可见属性和方法。子类声明同名方法时,调用子类对象会使用子类方法。
## 8. 多层继承
```ypp
类 设备 {
属性 文本 名称
方法 启动() {
输出(名称 + " 启动")
}
}
类 网络设备 : 设备 {
属性 文本 IP
方法 显示网络() {
输出(IP)
}
}
类 摄像机 : 网络设备 {
属性 整数 通道 = 1
方法 拍照() {
输出(名称 + " 通道 " + 到文本(通道) + " 拍照")
}
}
```
```yc
摄像机 相机
相机.名称 = "入口相机"
相机.IP = "192.168.1.20"
相机.通道 = 2
相机.启动()
相机.显示网络()
相机.拍照()
```
继承层级不要过深;业务组合能解决的问题可优先使用属性持有其它对象。
## 9. 组合优于继承的场景
```ypp
类 日志服务 {
方法 写(文本 内容) {
输出("[日志] " + 内容)
}
}
类 用户服务 {
属性 日志服务 日志
方法 登录(文本 用户名) 逻辑 {
日志.写("登录:" + 用户名)
返回(用户名 != "")
}
}
```
```yc
用户服务 服务
如果 (服务.登录("admin")) {
输出("登录成功")
}
```
“用户服务使用日志服务”是组合关系,不需要让用户服务继承日志服务。
## 10. 类数组和对象数组
```yc
用户 用户列表[] = []
用户 a
a.编号 = 1
a.姓名 = "张三"
数组添加(用户列表, a)
用户 b
b.编号 = 2
b.姓名 = "李四"
数组添加(用户列表, b)
整数 i = 0
循环 (数组长度(用户列表), i) {
输出(用户列表[i].姓名)
}
```
类对象作为另一类属性:
```ypp
类 订单服务 {
属性 用户 当前用户
属性 订单 当前订单
方法 显示() {
输出(当前用户.姓名)
输出(当前订单.编号)
}
}
```
## 11. 需要共享状态时使用全局类对象
当前自写类语法不要套用 C++ 的 `static` 或 `类名::成员`。需要全工程共享一个服务状态时,在 `source/globals.yc` 声明一个类对象:
```yc
编号生成器 全局编号器
```
类本身使用普通属性和方法:
```ypp
类 编号生成器 {
属性 整数 下一个编号 = 1
方法 取新编号() 整数 {
整数 当前 = 下一个编号
下一个编号 = 下一个编号 + 1
返回(当前)
}
}
```
其它文件调用同一个全局对象:
```yc
整数 新编号 = 全局编号器.取新编号()
```
## 12. 类封装支持库对象
```ypp
类 配置服务 {
属性 JSON类 文档
属性 文本 文件路径
方法 构造() {
文件路径 = 系统_取运行目录() + "\\config.json"
}
方法 设置服务器(文本 地址, 整数 端口) {
文档.置文本("地址", 地址)
文档.置整数("端口", 端口)
}
方法 保存() {
写文件(文件路径, 文档.到文本())
}
}
```
```yc
配置服务 配置
配置.设置服务器("127.0.0.1", 8080)
配置.保存()
```
## 13. 类与窗口事件组合
```ypp
类 登录服务 {
属性 文本 最后错误
方法 验证(文本 用户名, 文本 密码) 逻辑 {
如果 (用户名 == "") {
最后错误 = "用户名不能为空"
返回(假)
}
如果 (密码 == "") {
最后错误 = "密码不能为空"
返回(假)
}
最后错误 = ""
返回(真)
}
}
```
```yc
登录服务 登录逻辑
函数 登录按钮.单击(整数 X, 整数 Y) {
如果 (登录逻辑.验证(用户名框.标题, 密码框.标题)) {
状态标签.标题 = "登录成功"
} 否则 {
状态标签.标题 = 登录逻辑.最后错误
}
}
```
## 14. 错误写法与正确写法
### 错误:把类写在 `.yt`
```yt
// 错误
类 用户服务 {
方法 登录() { }
}
```
正确:类写在 `.ypp`。
### 错误:套用其它语言的 `new` 或构造赋值
```yc
// 错误
用户 a = new 用户()
// 正确
用户 a
```
带参构造按 `用户 a(参数)` 形式,并以 IDE 补全为准。
### 错误:套用 C++ 静态成员语法
```yc
// 错误
整数 编号 = 编号生成器::取新编号()
```
正确:在 `globals.yc` 声明共享对象,调用 `全局编号器.取新编号()`。
### 错误:调用方法漏对象
```yc
用户服务 服务
// 错误
登录("admin")
// 正确
服务.登录("admin")
```
### 错误:把属性当方法
```yc
// 错误
输出(用户.姓名())
// 正确
输出(用户.姓名)
```
### 错误:把方法当属性
```yc
// 错误
整数 值 = 计数器.取值
// 正确
整数 值 = 计数器.取值()
```
### 错误:方法返回类型与返回值不一致
```ypp
// 错误
方法 取数量() 整数 {
返回("10")
}
// 正确
方法 取数量() 整数 {
返回(10)
}
```
### 错误:声明继承但父类不存在
```ypp
// 错误
类 狗 : 未定义动物 { }
```
正确:先定义父类并确保父类 `.ypp` 已加入项目。
### 错误:循环继承
```ypp
// 错误
类 A : B { }
类 B : A { }
```
正确:继承关系必须单向且无环。
### 错误:用继承表达“拥有”
```ypp
// 不合理:汽车不是发动机
类 汽车 : 发动机 { }
```
正确:使用组合:
```ypp
类 汽车 {
属性 发动机 动力
}
```
### 错误:文档参数名和方法形参不一致
```ypp
/// 参数 @用户名 用户名
方法 登录(文本 账号) { }
```
正确:注释写 `@账号`,与形参一致。
### 错误:在函数外直接执行方法
```yc
用户服务 服务
// 错误:顶层不能写运行语句
服务.登录("admin")
```
正确:放入事件或函数:
```yc
函数 主窗口.创建完毕() {
服务.登录("admin")
}
```
## 15. 排查顺序
1. 类文件扩展名必须是 `.ypp`,并已出现在项目树。
2. 检查类名、对象名、属性名和方法名是否混淆。
3. 属性不用括号,方法必须写括号。
4. 方法参数、返回类型和 `返回(...)` 的值要一致。
5. 父类必须先可解析,继承链不能循环。
6. 调用说明不显示时检查 `///` 注释是否紧邻方法、参数名是否一致。
评论