返回列表

帖子详情

经验分享

Y++ 自写类及继承语法说明

作者:管理员发布:2026-08-11 01:50:04浏览:0
# 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. 调用说明不显示时检查 `///` 注释是否紧邻方法、参数名是否一致。

评论