一、子类型关系基础

仓颉语言中的子类型关系是类型系统的核心概念之一,它定义了类型之间的兼容性规则。子类型关系主要通过以下几种方式建立:

1.1 继承与接口实现

当类通过继承或接口实现建立关系时,会形成明确的子类型关系:

 
open class Super { }
class Sub <: Super { }  // Sub是Super的子类型

interface I1 { }
class C <: I1 { }       // C是I1的子类型

这种关系是面向对象编程的基础,子类实例可以直接赋值给父类变量:

 
let a: Super = Sub()  // 合法,子类可替代父类

1.2 元组类型的子类型

元组类型存在协变子类型关系:

 
(C2, C4) <: (C1, C3)  // 当C2<:C1且C4<:C3时成立

这种关系允许元组元素进行安全替换:

 
let t1: (C1, C3) = (C2(), C4())  // 合法

1.3 函数类型的子类型

函数类型子类型关系具有逆变和协变的特性:

 
(U1) -> S2 <: (U2) -> S1  // 当U2<:U1且S2<:S1时成立

这种设计确保了函数调用的安全性:

 
func h(lam: (U2) -> S1) { ... }
h(f)  // 当f: (U1)->S2且满足条件时合法

1.4 预设子类型关系

仓颉语言预设了以下永远成立的子类型关系:

  • T <: T(自反性)
  • Nothing <: T(底类型)
  • T <: Any(顶类型)
  • C <: Object(所有类都是Object子类)

1.5 传递性

子类型关系具有传递性:

 
interface I1 { }
interface I2 <: I1 { }
class C <: I2 { }
// 隐含C <: I1

二、接口与子类型

2.1 接口定义与实现

接口定义使用interface关键字:

 
interface I {
    func f(): Unit
}

类实现接口时需满足:

 
class Foo <: I {
    public func f(): Unit { ... }  // 必须实现接口所有成员
}

2.2 接口继承

接口支持多继承:

 
interface Calculable <: Addable & Subtractable {
    func mul(other: Int64): Int64
    func div(other: Int64): Int64  // 继承并扩展新成员
}

2.3 实现规则

实现接口需遵守严格规则:

  • 成员函数必须名称、参数、返回类型匹配
  • 成员属性需保持mut修饰一致
  • 返回值类型允许协变(返回子类型)

2.4 默认实现

接口可提供默认实现:

 
interface SayHi {
    func say() { "hi" }  // 默认实现
}

2.5 Any类型

Any是所有类型的顶级接口:

 
interface Any {}  // 所有类型都隐式实现
var any: Any = "hello"  // 可接受任意类型

三、泛型子类型关系

3.1 泛型子类型基础

泛型类型存在子类型关系:

 
class C<Z> <: I<Z, Z> { }
// 隐含C<Bool> <: I<Bool, Bool>

3.2 型变规则

仓颉采用不变(invariant)规则:

 
I<D> <: I<C>  // 不成立,即使D<:C

用户定义的泛型类型在所有类型参数处都是不变的。

 

3.3 内建类型的型变

内建类型有特殊型变规则:

  • 元组类型:元素类型协变
  • 函数类型:参数逆变,返回类型协变

四、类型转换机制

4.1 数值类型转换

仓颉要求显式类型转换:

 
let a: Int8 = 10
let b: Int16 = Int16(a)  // 显式转换

支持所有数值类型间的转换:

 
let e: Int64 = 1024
let r5 = Float64(e)  // Int64转Float64

4.2 Rune类型转换

Rune与整数间可转换:

 
let x: Rune = 'a'
let r1 = UInt32(x)  // 获取Unicode值

let y: UInt32 = 65
let r2 = Rune(y)  // 转回Rune

4.3 is和as操作符

运行时类型检查:

 
e is T  // 检查e的运行时类型是否为T的子类型

典型应用场景:

 
if obj is MyClass {
    let myObj = obj as MyClass  // 安全转换
}

五、C互操作中的类型映射

5.1 基础类型映射

仓颉与C语言的基础类型映射:

 
仓颉类型     C类型
Int32      int32_t
Float64    double
Bool       bool

5.2 结构体映射

使用@C修饰的结构体:

 
@C
struct Point3D {
    var x: Int64 = 0
    var y: Int64 = 0  // 内存布局与C相同
}

5.3 指针类型转换

支持指针类型安全转换:

 
var pInt8 = CPointer<Int8>()
var pUInt8 = CPointer<UInt8>(pInt8)  // 强制转换

5.4 C字符串处理

专用CString类型:

 
func toString(): String  // C风格转仓颉字符串
func subCString(start: UInt64, len: UInt64): CString  // 截取子串

六、类型系统高级特性

6.1 This类型

类内部使用This类型:

 
open class C1 {
    func f(): This { return this }  // 返回当前类型
}
class C2 <: C1 {
    // f()返回类型自动变为C2
}

6.2 类继承限制

类继承有严格限制:

 
class C <: A & B { }  // 错误!仓颉仅支持单继承

6.3 覆盖与重定义

子类可覆盖父类成员:

 
class B <: A {
    public override func f(): Unit { ... }  // 覆盖父类方法
}

七、最佳实践与注意事项

7.1 子类型使用建议

  • 优先在类型定义处声明接口实现
  • 避免同时声明父接口和子接口
  • 尽量通过泛型约束使用接口

7.2 类型转换注意事项

  • 数值转换需注意精度损失
  • Rune转换需确保值在有效Unicode范围内
  • as转换前应始终使用is检查

7.3 C互操作陷阱

  • 结构体字段顺序必须严格匹配
  • 指针转换需了解内存布局
  • 资源需及时释放防止泄漏

结论

仓颉语言的类型系统通过精心设计的子类型关系和类型转换机制,在保持静态类型安全的同时提供了足够的灵活性。其核心特点包括:

  1. 基于继承和接口实现的面向对象类型层次
  2. 元组和函数类型的协变/逆变支持
  3. 泛型不变性保证类型安全
  4. 显式类型转换避免隐式错误
  5. 完善的C互操作类型映射

这些特性使仓颉既能满足系统编程的精确控制需求,又能支持高级抽象和跨语言交互。开发者应深入理解子类型关系的各种形式及其转换规则,特别是泛型不变性和函数类型逆变规则等关键概念,才能充分发挥仓颉类型系统的优势,构建健壮高效的应用程序。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐