加载中...
仓颉文档阅读-语言规约VI: 类和接口(III)

仓颉文档阅读-语言规约VI: 类和接口(III)

周五 10月 10 2025
5151 字 · 25 分钟

此样式内容, 表示文档原文内容

类和接口

任何面向对象的编程语言, 基本都存在类这个概念, C++不例外, 仓颉当然也不例外

但, C++ 中不存在接口这个东西, 接口这个词, 在业务方面听起来不陌生, 但是语言中的接口具体情况并不是非常了解

接口

接口用来定义一个抽象类型, 它不包含数据, 但可以定义类型的行为

一个类型如果声明实现某接口, 并且实现了该接口中所有的成员, 就被称为实现了该接口

接口的成员可以包含实例成员函数、静态成员函数、操作符重载函数、实例成员属性和静态成员属性, 这些成员都是抽象的, 但函数和属性可以定义默认实现

仓颉中的接口, 像是一个只有成员函数、成员属性、操作符重载函数的抽象类, 不包含成员变量

C++中不严格存在接口的概念

接口定义

接口定义的语法

接口的定义使用interface关键字, 接口定义依次为: 可缺省的修饰符、interface关键字、接口名、可选的类型参数、是否指定父接口、可选的泛型约束、接口体的定义

以下示例是一些接口定义:

CANGJIE
interface I1 {}
public interface I2<T> {}
public interface I3<U> {}
public interface I4<V> <: I2<V> & I3<Int32> {}

接口定义的语法如下:

PLAINTEXT
interfaceDefinition
    : interfaceModifierList? 'interface' identifier
    typeParameters?
    ('<:' superInterfaces)?
    genericConstraints?
    interfaceBody
    ;

接口只可以定义在top level

接口体的{}不允许省略

CANGJIE
interface I {}		// {} 不允许被省略

接口的定义语法, 除了关键词是interface之外, 基本与 类的定义语法保持一致

接口的修饰符

在此小节中, 我们将介绍定义接口可以使用的修饰符

访问修饰符

访问修饰符: 默认, 即不使用访问修饰符, 表示只能在包内部访问

也可以使用public修饰符表示包外部可访问

CANGJIE
public interface I {}     // 可以在包外被访问
interface I2 {}           // 只能在包内访问
继承性修饰符

继承修饰符: 默认, 即不使用继承修饰符即可表示接口具有open修饰的语义, 能在任何可以访问interface的位置继承、实现或扩展interface

当然, 也可以显式地使用open修饰符修饰; 也可以使用sealed修饰符表示只能在interface定义所在的包内继承、实现或扩展该interface

sealed已经蕴含了public的语义, 所以定义sealed interface时的public修饰符是可选的

继承sealed接口的子接口或实现sealed接口的类仍可被sealed修饰或不使用sealed修饰

sealed接口的子接口被public修饰, 且不被sealed修饰, 则其子接口可在包外被继承、实现或扩展;

继承、实现sealed接口的类型可以不被public修饰

CANGJIE
// package A
package A
public sealed interface I1 {}     // OK
sealed interface I2  {}           // OK, 在`sealed`被使用时,`public`是可选的
open interface I3 {}              // OK
interface I4  {}                  // OK,`open`是可选的

class C1 <: I1 {}                 // OK
public open class C2 <: I1 {}     // OK
public sealed class C3 <: I1 {}   // OK
extend Int64 <: I1 {}             // OK

// package B
package B
import A.*

class S1 <: C2 {}                 // OK
class S2 <: C3 {}                 // Error, C3 是 sealed class, 无法在这里继承

再次遇到sealed, 仓颉中, sealed用于限制classinterfacestruct的包外可继承性

public则是赋予他们包外可访问性, 这两个是不冲突的

接口成员

接口的成员包括:

  • 在接口体中声明(即在{}中声明)的成员: 静态成员函数、实例成员函数、操作符重载函数、静态成员属性和实例成员属性

  • 从其他接口继承而来的成员

    如以下示例中, I2的成员将包括I1中允许被继承的成员

    CANGJIE
    interface I1 {
       func f(): Unit
    }
    interface I2 <: I1 {}

接口成员的 BNF 如下:

PLAINTEXT
interfaceMemberDeclaration    
       : (functionDefinition|macroExpression|propertyDefinition) end*   
       ;

接口的成员只包括 函数或属性成员, 不存在成员变量

接口中的函数
接口中函数的声明与定义

接口中可以包含实例成员函数和静态成员函数, 这些函数与普通实例成员函数和静态成员函数的编写方式一样, 但可以没有函数实现, 这些函数被称为抽象函数

对于拥有实现的抽象函数, 我们称该函数拥有默认实现

以下是包含抽象函数的示例:

CANGJIE
interface MyInterface {
    func f1(): Unit               // 不包含默认实现
    static func f2(): Unit        // 不包含默认实现

    func f3(): Unit {             // 包含默认实现
        return
    }
    static func f4(): Unit {      // 包含默认实现
        return
    }
}

抽象函数可以拥有命名参数, 但不能拥有参数默认值

CANGJIE
interface MyInterface {
    func f1(a!: Int64): Unit      // OK
    func f2(a!: Int64 = 1): Unit  // Error, 不能有参数默认值
}

仓颉中, 接口的静态成员函数也可以不实现函数体, 为 抽象静态成员函数

接口的成员函数可以有命名形参, 但不能存在默认值

接口中函数和属性的修饰符

接口中定义的函数或属性已经蕴含public语义, 使用public修饰会产生编译告警

不允许使用protectedinternalprivate修饰符修饰接口中定义的函数或属性

以下是错误的示例:

CANGJIE
interface MyInterface {
    public func f1(): Unit            // 不能使用访问修饰符
    private static func f2(): Unit    // 不能使用访问修饰符
}

使用static修饰的函数被称为静态成员函数, 可以没有函数体

没有函数体的static函数不能直接使用接口类型调用, 拥有函数体的static函数可以使用接口类型调用

当使用interface类型名直接调用其静态成员函数时, 如果这个函数里面直接或间接调用了接口中(自己或其它接口)没有实现的其它静态函数, 则编译报错

CANGJIE
interface I {
    static func f1(): Unit
    static func f2(): Unit {}
    static func f3(): Unit {
        f1()
    }
}

main() {
    I.f1()        // Error, 不能通过接口类型直接调用
    I.f2()        // OK
    I.f3()        // Error, f1 未实现
}

接口中的实例成员函数默认具有open的语义

在接口中定义实例成员函数时, open修饰符是可选的

CANGJIE
interface I {
    open func foo1() {}       // ok
    func foo2() {}            // ok
} 

使用mut修饰的函数是一种特殊的实例成员函数, 可以用于抽象struct类型的可变行为

CANGJIE
interface I {
    mut func f(): Unit
}

仓颉接口中的成员函数, 默认都是public的, 不能用其他访问修饰符修饰

接口的成员函数, 默认都是open的, 即 默认都被实现

接口的成员函数也可以用mut修饰, 接口本身实际是不需要mut的, 因为接口不存在成员变量, 也就不存在成员变量是否可修改

接口的mut成员函数更多是为了给实现了接口的strcut准备的

接口的static成员函数是可以不实现函数体的, 而类中的static成员函数必须拥有函数体的实现

接口中的成员属性

interface中也可以定义成员属性, 定义成员属性的语法参见[属性]

接口的默认实现

interface中的抽象函数和抽象属性都可以拥有默认实现

interface被其它interface或类型继承或实现的时候, 如果该interface的默认实现没有被重新实现, 则这些默认实现会被拷贝子类型中

  • 实例成员函数的默认实现中可以使用this, this的类型是当前interface

  • 默认实现和非抽象的成员函数一样, 可以访问当前作用域中所有可访问的元素

  • 默认实现是一种语法糖, 可以给实现类型提供默认行为, 子接口可以沿用父接口的默认实现

  • 默认实现不属于继承语义, 因此不能使用override/redef, 也不能使用super

  • 继承接口的子类型为类时, 默认实现保留open语义, 可以被类的子类重写

CANGJIE
interface I {
    func f() {            // f: () -> I
        let a = this      // a: I
        return this
    }
}

上述内容中存在一句描述: 默认实现不属于继承语义, 因此不能使用override/redef, 也不能使用super

但经过测试, 在interface的继承链中, overrideredef都是可以使用的, 但super无法被使用

所以, 此描述存在混淆 或 存在描述错误, 可能是在强调 不存在继承或实现关系的interface的默认实现不能使用override/redef, 但这是一定的, 好像并不需要特别强调

或者是在说, 接口的继承关系不是传统的继承语义, 因为super无法被使用

接口继承

接口允许继承一个或多个接口

CANGJIE
interface I1<T> {}
interface I2 {}
interface I3<U> <: I1<U> {}               // 继承泛型接口
interface I4<V> <: I1<Int32> & I2 {}      // 继承多个接口

仓颉中, interface可以多继承, 但class不允许多继承, 其中一个重要原因是interface不存在成员变量, 但不是唯一原因

子接口继承父接口时, 会继承父接口的所有成员:

子接口继承父接口时, 对于非泛型接口不能直接继承多次, 对于泛型接口不能使用相同类型参数直接继承多次

例如:

CANGJIE
interface I1 {}
interface I2 <: I1 & I1 {}                    // error

interface I3<T> {}
interface I4 <: I3<Int32> & I3<Int32> {}      // error
interface I5<T> <: I3<T> & I3<Int32> {}       // ok

如果泛型接口I3在使用时被给了类型参数为Int32, 那么编译器会在类型使用的位置报错:

CANGJIE
interface I1<T> {}
interface I2<T> <: I1<T> & I1<Int32> {}       // ok
interface I3 <: I2<Int32> {}                  // error

main() {
    var a: I2<Int32>                          // error 
}

接口的继承关系中, 不允许存在直接继承多个相同接口类型的情况

即, 如果存在I1, 那么不允许存在interface I2 <: I1 & I1 {}, 如果是泛型接口类型, 类型参数也不允许相同, 因为相同类型参数的泛型接口类型实际属于相同类型

子接口中的默认实现

子接口如果继承了 父接口中 没有默认实现的函数或属性, 则在子接口中 允许仅写此函数或属性的声明(当然也允许定义默认实现), 并且函数声明或定义前的overrideredef修饰符是可选的

示例如下:

CANGJIE
interface I1 {
    func f1(): Unit
    static func f2(): Unit
}
interface I2 <: I1 {
    func f1(): Unit                   // ok
    static func f2(): Unit            // ok
}
interface I3 <: I1 {
    override func f1(): Unit          // ok
    redef static func f2(): Unit      // ok
}
interface I4 <: I1 {
    func f1(): Unit {}                // ok
    static func f2(): Unit {}         // ok
}
interface I5 <: I1 {
    override func f1(): Unit {}       // ok
    redef static func f2(): Unit {}   // ok
}

子接口中的成员函数是可以使用overrideredef修饰的

子接口如果继承了 父接口中 有默认实现的函数或属性, 则在子接口中 不允许 仅写此函数或属性的声明而 没有实现, 如果子接口中给出新的默认实现, 那么定义前的overrideredef修饰符是可选的

示例如下:

CANGJIE
interface I1 {
    func f1(): Unit {}
    static func f2(): Unit {}
}
interface I2 <: I1 {
    func f1(): Unit                   // Error, 'f1' 必须有一个新的实现
    static func f2(): Unit            // Error, 'f2' 必须有一个新的实现
}
interface I3 <: I1 {
    override func f1(): Unit {}       // ok
    redef static func f2(): Unit {}   // ok
}
interface I4 <: I1 {
    func f1(): Unit {}                // ok
    static func f2(): Unit {}         // ok
}

父接口的成员函数或属性, 如果存在默认实现, 则子接口中不允许只声明不实现, 要不就不声明, 要不就要重新实现

如果子接口继承的多个父接口中拥有相同签名成员的默认实现, 子接口必须提供自己版本的新默认实现, 否则会编译报错

CANGJIE
interface I1 {
    func f() {}
}
interface I2 {
    func f() {}
}
interface I3 <: I1 & I2 {}        // error, I3 必须实现 f: () -> Unit

子接口如果是多继承, 且不同父接口中存在同签名成员函数且 都有默认实现 或 都无默认实现

子接口就会出现冲突, 子接口实现一个自己的同签名成员函数 可以解决冲突

但, 如果不同父类接口中 存在同函数名、同参数列表 但返回值类型毫无关系的成员函数, 那么冲突无法解决

实现接口

实现接口时的覆盖与重载

一个类型实现一个或多个接口时规则如下:

  • 抽象类以外的类型实现接口时, 必须实现所有的函数、属性

  • 抽象类实现接口时, 允许不实现接口中的函数和属性

  • 实现函数的函数名、参数列表必须与接口中对应函数的相同

  • 实现函数的返回类型 应该与 接口中对应函数的返回类型相同 或者 为其子类型

  • 如果接口中的函数为泛型函数, 则要求实现函数的类型变元约束 比 接口中对应函数更宽松或相同

  • 实现属性的mut修饰符必须与接口中相应的属性相同

  • 实现属性的类型, 必须与接口中对应的属性相同

  • 如果多个接口中 只有同一函数或属性的 一个默认实现, 则实现类型可以 不实现该函数或属性, 使用默认实现

  • 如果多个接口中 包含同一函数或属性的 多个默认实现, 则实现类型 必须要实现该函数或属性, 无法使用默认实现

  • 如果实现类型已经存在 (从父类继承或本类定义) 接口中同一函数或属性的实现, 则不会再使用任何接口中的默认实现

  • 类型在实现接口时, 函数或属性定义前的override修饰符(或redef修饰符)是可选的, 无论接口中的函数或属性是否存在默认实现

仓颉中, 抽象类可以只实现接口的部分函数或属性, 非抽象类 需要实现接口的所有的函数或属性

抽象类以外的类型实现接口时:

必须对接口中的抽象函数和抽象属性进行实现, 如下示例中的f1, f3

允许不使用接口中的函数默认实现, 如下示例中的f2

抽象类允许不实现接口中的实例成员函数, 如下示例中抽象类C1并没有实现接口I中的f1

CANGJIE
interface I {
    func f1(): Unit
    func f2(): Unit {
        return
    }
    static func f3(): Int64
}
class C <: I {
    public func f1(): Unit {}
    public func f2(): Unit {
        return
    }
    public static func f3(): Int64 {
        return 0
    }
}

abstract class C1 <: I {
    public static func f3(): Int64 {
        return 1
    }
}

示例: 接口I中的函数fg为泛型函数, class EF满足实现函数的类型变元约束 比 被实现函数的更宽松或相同的要求, 编译成功;class D不满足要求, 则编译报错

CANGJIE
// C <: B <: A
interface I {
    static func f<T>(a: T): Unit where  T <: B
    static func g<T>(): Unit where T <: B
}

class D <: I  {
    public static func f<T>(a: T) where T <: C {}         // Error, 约束过于严格
    public static func g<T>() where T <: C {}             // Error, 约束过于严格
}

class E <: I  {
    public static func f<T>(a: T) where T <: A {}         // OK, 约束更宽松
    public static func g<T>() where T <: A {}             // OK, 约束更宽松
}

class F <: I  {
    public static func f<T>(a: T) where T <: B {}         // OK, 相同的约束
    public static func g<T>() where T <: B {}             // OK, 相同的约束
}

更多例子:

CANGJIE
// case 1
interface I1 {
    func f(): Unit
}
interface I2 {
    func f(): Unit
}
class A <: I1 & I2 {
    public func f(): Unit {}                  // ok
}

// case 2
interface I1 {
    func f(): Unit
}
interface I2 {
    func f(): Unit {}
}
open class A {
    public open func f(): Unit {}             // ok
}
class B <: A & I1 & I2 {
    public override func f(): Unit {}         // ok
}

// case 3
interface I1 {
    func f(): Unit
}
interface I2 {
    func f(): Unit {}
}
class A <: I1 & I2 {}                         // ok, f from I2

// case 4
interface I1 {
    func f(): Unit {}
}
interface I2 {
    func f(): Unit {}
}
class A <: I1 & I2 {}                         // error
class B <: I1 & I2 {                          // ok,
    public func f(): Unit {}
}

// case 5
interface I1 {
    func f(a: Int): Unit {}
}
interface I2  {
    func f(a: Int): Unit {}
}

open class A  {
    public open func f(a: Int): Unit {}
}
open class B <: A & I1 & I2 {                 // ok, f from A
}

对于泛型interface的成员函数, 在子类或子接口中实现时, 类型变元约束不能变得更严格

实现接口时函数重载的规则

父作用域函数与子作用域函数的函数名必须相同, 但参数列表必须不同

以下几个例子中给出了类型实现接口时构成函数重载的一些情形, 需要注意的是 当类型实现接口时 需要为被重载的函数声明提供实现

示例一: 分别在I1I2声明了函数名相同、参数列表不同的函数f

需要在实现类C中同时实现参数类型为Unit和参数类型为Int32的f

CANGJIE
interface I1 {
    func f(): Unit
}
interface I2 {
    func f(a: Int32): Unit
}
class C <: I1 & I2 {
    public func f(): Unit {}              // I1 中的 f 需要实现
    public func f(a: Int32): Unit {}      // I2 中的 f 需要实现
}

示例二: 分别在I1I2定义了函数相同、参数列表不同的默认函数f

不需要在C中实现f

CANGJIE
interface I1 {
    func f() {}
}
interface I2 {
    func f(a: Int32) {}
}
class C <: I1 & I2 {
}

示例三: 在I1声明了函数名为f, 参数类型为Unit的函数; 在I2定义了函数名为f, 参数类型为Int32的函数

C中必须实现参数类型为Unit的函数f

CANGJIE
interface I1 {
    func f(): Unit
}
interface I2 {
    func f(a: Int32):Unit {
        return
    }
}
class C <: I1 & I2 {
    public func f(): Unit {
        return
    }
}

Any接口

Any接口是一个语言内置的空接口, 所有interface类型都默认继承它, 所有非interface类型都默认实现它, 因此所有类型都可以作为Any类型的子类型使用

CANGJIE
class A {}

struct B {}

enum C { D }

main() {
    var i: Any = A()      // ok
    i = B()               // ok
    i = C.D               // ok
    i = (1, 2)            // ok
    i = { => 123 }        // ok

    return 0
}

在类型定义处可以显式声明实现Any接口, 如果没有则会由编译器隐式实现, 但不能使用扩展重新实现Any接口

CANGJIE
class A <: Any {}     // ok

class B {}            // 隐式实现 Any

extend B <: Any {}    // error

Any之于接口, 类似Object之于类

不过Any也是被所有类型实现的接口


Thanks for reading!

仓颉文档阅读-语言规约VI: 类和接口(III)

周五 10月 10 2025
5151 字 · 25 分钟