加载中...
仓颉文档阅读-语言规约X: 重载

仓颉文档阅读-语言规约X: 重载

周一 10月 13 2025
7871 字 · 34 分钟

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

重载

函数重载

函数重载的定义

在仓颉编程语言中, 如果一个作用域中, 同一个函数名对应多个参数类型不完全相同的函数定义, 这种现象称为函数重载

函数重载定义详见函数重载定义

需要注意的是:

  • classinterfacestruct 类型中的静态成员函数 和 实例成员函数之间不能重载

  • 同一个类型的扩展中的 静态成员函数 和 实例成员函数之间 不能重载, 同一个类型的不同扩展中两个函数都是 private 的除外

  • enum 类型的 constructor、静态成员函数和实例成员函数之间不能重载

下例中, class A 中的实例成员函数 f 和静态成员函数 f 重载, 将编译报错

CANGJIE
class A {
    func f() {}
    
    static func f(a: Int64) {}            // Error, 静态成员函数不能被实例成员函数重载
}

下例中, class A 的扩展中实例成员函数 g 和静态成员函数 g 重载, 将编译报错

CANGJIE
class A {}

extend A {
    func g() {}
    static func g(a: Int64) {}            // Error
}

下例中, 实例成员函数 h 和静态成员函数 hclass A 的不同扩展中, 且都是 private, 编译不报错

CANGJIE
extend A {
    private func h() {}
}

extend A {
    private static func h(a: Int64) {}    // OK
}

下例中, enum Econstructor f, 实例成员函数 f 和静态成员函数 f 重载, 将编译报错

CANGJIE
enum E {
    f(Int64)                              // 构造函数

    // 实例成员函数不能重载构造函数
    func f(a: Float64) {}                 // Error

    // 静态成员函数不能 重载实例成员函数或构造函数
    static func f(a: Bool) {}             // Error
}

在进行函数调用时, 需要根据函数调用表达式中的实参的类型和上下文信息明确是哪一个函数定义被使用, 分为以下几个步骤:

第 1 步, 构建函数候选集

第 2 步, 函数重载决议

第 3 步, 确定实参类型

总结就是, 仓颉中实例成员函数和静态成员函数不能构成重载, 但 实现不同的扩展时, 不同扩展的private互不影响

enum中的构造函数不能重载

重载函数候选集

调用函数时, 首先需要确定哪些函数是可以被调用的函数, 可以被调用的函数集合称为重载函数候选集(以下简称候选集), 用于函数重载决议

构建函数候选集主要是两个步骤:

  • 查找可见函数集, 即根据调用表达式的形式和上下文确定所有可见的函数

  • 对可见函数集中的函数进行函数调用的类型检查, 通过类型检查的函数(即可以被调用的函数)进入候选集

从文档来看, 并不是只有发生重载的函数才会进入候选集, 所有函数应该都会进入候选集

可见函数集

可见函数集需要满足以下规则:

  1. 作用域可见

  2. 根据 f 是普通函数 还是 构造函数 确定可见函数集:

    1. 如果 f构造函数, 则根据以下规则确定可见函数集:

      如果 f 是 有构造函数的类型名字, 则 可见函数集仅包括f中定义的构造函数;

      否则, 如果 fenumconstructor 名字, 则可见函数集仅包括指定的 enum 中名为 fconstructor;

      否则, 如果 fsuper, 则表示该调用表达式出现在 class/interface 中, f 仅包含调用表达式所在 class/interface 的直接父类构造函数;

      否则, 如果 f 是无构造函数的类型名, 则可见函数集为空;

    2. 如果 f普通函数, 则根据是否有限定前缀来确定可见函数集:

      • 不带限定前缀直接通过名字调用 f(...), 可见函数集包含以下几种方式引入的名为 f 的函数:

        1. 作用域中可见的局部函数

        2. 如果函数调用在 class/interface/struct/enum/extend 的静态上下文中, 则包含类型的静态成员函数

        3. 如果函数调用在 class/interface/struct/enum/extend 的非静态上下文中, 则包含类型的静态和非静态成员函数

        4. 函数调用所在 package 内定义的全局函数

        5. 函数调用所在 package 中定义的 extend 中声明的函数

        6. 函数调用所在 package 通过import方式导入的函数

      • 对于带限定前缀的函数调用 c.f(...), 可见函数集根据限定前缀来确定:

        1. 如果 c 是包名, 可见函数集仅包含 package c 中定义的全局函数 f, 包括c中通过 public import 重导出的函数, 不包括 c 中仅通过 import 导入的函数

        2. 如果 cclassinterfacestructenum 定义的类型名, 则可见函数集仅包括 c 的静态成员方法 f

        3. 如果 cthis, 即 this.f(...), 表示该调用表达式出现在 classinterfacestructenum 的定义或扩展中

          如果出现在类型定义中, 可见函数集仅包含当前类型中的非静态成员方法 f(包括继承得到的, 但不包括扩展中的)

          如果出现在类型扩展中, 可见函数集包含类型中的非静态成员方法 f, 也包含扩展中的非静态成员方法 f

        4. 如果 csuper, 即 super.f(...), 表示该调用表达式出现在 classinterface 中, 则可见函数集 仅包含 当前类型的父类或父接口的非静态成员方法 f

        5. 如果函数调用的形式是 对象名.f(...) 的形式:

          如果对象的类型中有成员方法f, 则可见函数集仅包含所有名字为f的成员方法

    3. 如果 f类型实例, 则根据其类型或类型扩展中定义的 () 操作符重载函数来确定可见函数集

      假设 f 的类型是 T, 则可见函数集包括:

      1. T 内定义的 () 操作符重载函数

      2. T 的扩展 (当前作用域可见) 中定义的 () 操作符重载函数

      3. T 如果有父类, 还包括从父类中继承的 () 操作符重载函数

      4. T 如果实现了接口, 还包括从接口中获得的带缺省实现的 () 操作符重载函数

      对于泛型函数 f, 进入可见函数集的可能是 部分实例化后的泛型函数 或是 完全实例化的函数

      具体是哪种形式进入可见函数集, 由调用表达式的形式决定(详见, 泛型函数重载)

这一部分感觉暂时不太需要深入了解, 感觉是在描述一些可调用函数的一些情况, 这些具体在开发时可能会有一个更方便深入的了解

可见函数集更多是给编译器看的, 编译器需要在调用上下文中从可见函数集中查找选择可执行的目标函数

类型检查

对于可见函数集中的函数进行类型检查, 只有通过函数调用类型检查的函数才能进入候选集

CANGJIE
open class Base {}
class Sub <: Base {}

func f<X, Y>(a: X, b: Y) {}                           // f1, 类型参数数量不匹配
func f<X>(a: Base, b: X) where X <: Sub {}            // f2 

func test() {
    f<Sub>(Base(), Sub())                             // 候选集: { f2 }
}

需要注意的是:

  1. 如果实参有多个类型, 在类型检查阶段, 如果 实参的多个类型中 有一个能通过类型检查, 则认为该实参能通过候选函数的类型检查
CANGJIE
open class A {}
open class B <: A {}

func g(a: A): B {                     // g1
    B()
}
func g(a: B): B {                     // g2
    B()
}
func g(a: Int64): Unit {}             // g3

// (A)->B <: (B)->B <: (B)->A
func f(a: (A)->B) {}                  // f1: g1 可以通过类型检查
func f(a: (B)->B) {}                  // f2: g1, g2 可以通过类型检查
func f(a: (B)->A) {}                  // f3: g1, g2 可以通过类型检查
func f(a: Bool) {}                    // f4: 没有 g函数 可以通过类型检查

func test() {
    f(g)                              // 候选集: { f1, f2, f3 }
}

文档中的举例, 是根据函数调用进行类型检查, 然后将通过类型检查的加入到候选集中

函数重载决议

如果候选集为空, 即没有匹配项, 编译报错

如果候选集中只有一个函数, 即只有一个匹配项, 选择匹配的函数进行调用;

如果候选集中有多个函数, 即有多个匹配项. 先按照[作用域优先级]规则, 选择候选集中作用域级别最高的

如果作用域级别最高的只有一个, 则选择该函数; 否则, 根据[最匹配规则], 选择最匹配项

若无法确定唯一最匹配项, 则编译报错

最终函数调用, 是从候选集中选择的

作用域优先级

作用域级别越高, 函数重载决议时优先级越高, 即: 候选集中两个函数, 选作用域级别高的, 如果作用域级别相同, 则根据[最匹配规则]章节中的规则进行选择

作用域优先级需要注意的是:

  1. 候选集中 父类和子类中定义的函数, 在函数重载时 当成同一作用域优先级处理

  2. 候选集中 类型中定义的函数 和 扩展中定义的函数, 在函数重载时 当成同一作用域优先级处理

  3. 候选集中 定义在同一类型的 不同扩展中的函数, 在函数重载时 当成同一作用域优先级处理

  4. 候选集中 定义在扩展中或类型中的 操作符重载函数 和 内置操作符, 在函数重载时 同一作用域优先级处理

  5. 除以上提到的类型作用域级别之外, 其它的情况根据[作用域级别]章节中的规则, 判断作用域级别

CANGJIE
/* 根据作用域级别优先原则, 候选集中作用域级别不同的两个函数, 作用域级别更高的那个函数优先*/
func outer() {
    func g(a: B) {
        print("1")
    }
    
    func g(a: Int32) {
        print("3")
    }
    
    func inner() {
        func g(a: A) { print("2") }
        func innermost() {
            g(B())                    // Output: 2
            g(1)                      // Output: 3
        }
        g(B())                        // Output: 2
        innermost()
    }
    
    inner()   
    g(B())                            // Output: 1
}

上例中, 函数innermost中调用函数g, 且传入实参B(), 根据作用域级优先原则, 优先选作用域级别高的, 因此, 选第 12 行定义的函数 g

CANGJIE
/* 继承的名称 与 类中定义或声明的名称处于同一范围级别 */
open class Father {
    func f(x: Child) { print("in Father") }
}

class Child <: Father {
    func f(x: Father) { print("in Child") }
}

func display() {
    var obj: Child = Child()
    obj.f(Child()) 	                        // in Father
}

上例中, 函数display调用函数f, 且传入实参Child(), 父类和子类中 构成重载的函数 均属于同一作用域级别, 将根据最匹配规则选择Father类中定义的f(x: Child) {...}

下例中, 类型 C 中定义的函数 fC 的扩展中定义的函数 f, 在函数重载时 当成同一作用域优先级处理

CANGJIE
open class Base {}
class Sub <: Base {}

class C {
    func f(a: Sub): Unit {}  // f1
}

extend C {
    func f(a: Base): Unit {}  // f2
    func g() {
        f(Sub()) // f1
    }
}

var obj = C()
var x = obj.f(Sub()) // f1

这部分内容, 和下面一部分内容, 介绍的是可见作用域中多同名函数调用的匹配

最匹配规则

候选集中最高优先级有多个函数: {f_1, ..., f_n}, 这些函数被称为匹配项, 若其中存在唯一一个匹配项f_m, 比其它所有匹配项都更匹配, 则f_m为最匹配函数

对于两个匹配项谁更匹配的比较是对两个匹配项形参的比较, 分两步进行:

第一步, 需要明确匹配项中用于比较的形参数量和顺序

  • 如果函数参数有缺省值, 未指定实参的可选形参不参与比较

    CANGJIE
    open class Base {}
    class Sub <: Base {}
    
    func f(a!: Sub = Sub(), b!: Int32 = 2) {}         // f1
    func f(a!: Base = Base()) {}                      // f2
    
    var x1 = f(a: Sub())                              // f1 中涉及比较的参数只有一个
  • 如果函数有命名参数, 实参顺序可能会和形参声明的顺序不一致, 那么用于比较的形参顺序需要和实参顺序保持一致, 确保两个候选函数用于比较的形参是对应的

第二步, 比较两个匹配项的形参确定哪个匹配项更匹配

对于两个匹配项: f_if_j, 如果满足以下条件, 则称f_if_j更匹配:

对于一个函数调用表达式: e<T1, ..., T_p>(e_1, ...,e_m, a_m+1:e_m+1, a_k:e_k), 实参个数为 k 个, 则参与用于比较的形参个数为 k

f_if_j参与比较的形参分别为(a_i1, ..., a_ik)(a_j1, ..., a_jk), 对应的形参类型分别为 (A_i1, ..., A_ik), (A_j1, ..., A_jk)

若将f_i的形参(a_i1, ..., a_ik)作为实参传递给f_j能通过f_j的函数调用类型检查, 且将f_j的形参(a_j1, ..., a_jk)作为参数传递给f_i不能通过函数调用类型检查, 则称f_if_j更匹配

如下所示:

CANGJIE
func f_i<X1,..., Xp>(a_i1: A_i1,..., a_ik: A_ik) {            // f_i 可能没有类型参数
         f_j(a_i1, ..., a_ik)                                 // 如果这个调用表达式可以通过类型检查
}

func f_j<X1,..., Xq>(a_j1: A_j1,..., a_jk: A_jk) {            // f_j 可能没有类型参数
         f_i(a_j1, ..., a_jk)                                 // 并且这个表达式无法通过类型检查
}

以下列举了一些函数重载决议的例子:

CANGJIE
interface I3 {}
interface I1 <: I2 & I3 {}
interface I2 <: I4 {}
interface I4 <: I3 {}

func f(x: I4) {}                          // f1
func f(x: I3) {}                          // f2

class C1 <: I1 {}
var obj = C1()
var result = f(obj)	                    // 选择 f1, 因为 I4 <: I3
CANGJIE
open class C1 {}
open class C2 <: C1 {}
class C3 <: C2 {}

func f(a: C1, b: C2, c: C1) {}            // f1
func f(a: C3, b: C3, c: C2) {}            // f2
func f(a: C3, b: C2, c: C1) {}            // f3

// 函数调用
var x = f(C3(), C3(), C3())               // f2
CANGJIE
open class A {}
class B <: A {}
func foo<X>(a: X, b: X): Int32 {}         // foo1
func foo(a: A, b: B): Int32 {}            // foo2
func foo<X>(a: A, b: X): Int32 {}         // foo3

foo(A(), A())                             // Error: 无法解析
foo(A(), 1)                               // foo3, foo3 是候选集中唯一的函数

长篇大论的文档内容, 解释起来很简单

如果候选集中存在多个匹配项, 那么如何决定执行哪个呢?

  1. 先确定需要比较的匹配项的形参顺序和数量

  2. 再进行形参类型比较哪个更匹配

最终选择出来的就是最佳匹配项

确定实参类型

如果实参有多个类型:

确定最匹配函数后, 如果实参的多个类型中有且只有一个类型T能通过该函数的类型检查, 则确定实参的类型为T; 否则, 编译报错

CANGJIE
// Sub <: Base
func f(a: (Base)->Int64, b : Sub) {}      // f1
func f(a: (Sub)->Int64, b: Base) {}       // f2
func g(a: Base): Int64 { 0 }              // g1
func g(a: Sub): Int64 { 0 }               // g2

func test() {
    f(g, Base())                          // Error, 两个 g 都可以通过 f2 的类型检查

    f(g, Sub())                           // OK, 只有 g1 可以通过 f2 的类型检查
}

当传入的实参类型, 同时可以匹配多个匹配项时, 编译报错

操作符重载

仓颉编程语言中定义了一系列使用特殊符号表示的操作符(详见第 4 章表达式), 例如, 算术操作符(+, -, *, /等), 逻辑操作符(!, &&, ||), 函数调用操作符()

默认情况下, 这些操作符的操作数只能是特定的类型, 例如, 算术操作符的操作数只能是数值类型, 逻辑操作符的操作数只能是Bool类型

如果希望扩展某些操作符支持的操作数类型, 或者允许自定义类型也能使用这些操作符, 可以使用操作符重载(operator overloading)来实现

如果需要在某个类型Type上重载某个操作符opSymbol, 可以通过为Type定义一个函数名为opSymbol的操作符函数(operator function)的方式实现, 这样, 在Type的实例使用opSymbol时, 就会自动调用名为opSymbol的操作符函数

操作符函数定义的语法如下:

PLAINTEXT
operatorFunctionDefinition
    	: functionModifierList? 'operator' 'func'
    	  overloadedOperators typeParameters?
          functionParameters (':' type)?
    	  (genericConstraints)? ('=' expression | block)? 
        ;

操作符重载, 仓颉中当然也存在, 语法与普通的函数定义还是比较相似的

操作符函数定义与普通函数定义相似, 区别如下:

  • 定义操作符函数时需要在func关键字前面添加operator修饰符

  • overloadedOperators是操作符函数名, 它必须是一个操作符, 且只有固定的操作符可以被重载

    哪些操作符可重载将在[可以被重载的操作符]章节中详细介绍

  • 操作符函数的参数个数 和 操作符要求的操作数个数必须相同

  • 操作符函数只能定义在 classinterfacestructenumextend

  • 操作符函数具有实例成员函数的语义, 所以禁止使用 static 修饰符

  • 操作符函数不能为泛型函数

另外, 需要注意:

  • 被重载后的操作符不改变它们固有的优先级和结合性(各操作符的优先级和结合律详见第 4 章表达式)

  • 一元操作符是作为前缀操作符使用还是后缀操作符使用, 与此操作符默认的用法保持一致

    由于仓颉编程语言中不存在既可以作为前缀使用又可以作为后缀使用的操作符, 所以不会产生歧义

  • 操作符函数的调用方式遵循操作符固有的使用方式(根据操作数的数量和类型决定调用哪个操作符函数)

如果要在一个类型 Type 之上重载某个操作符 opSymbol, 需要在类型上定义名为opSymbol的操作符函数

在类型上定义操作符函数有两种方式:

  1. 使用 extend 的方式为其添加操作符函数, 从而实现操作符在这些类型上的重载

    对于无法直接包含函数定义的类型(是指除 structclassenuminterface 之外其他的类型), 只能采用这种方式

  2. 对于可以直接包含函数定义的类型 (包括 classinterfaceenumstruct ), 可以直接在其内部定义操作符函数的方式实现操作符的重载

仓颉中, 操作符重载的语法是在func前加operator, 然后函数名是目标操作符, 参数列表需要与操作符原义的操作个数对应

且, 操作符重载函数不能是静态的, 也不能是泛型的, 只能给特定类型实现

定义操作符函数

因为操作符函数实现的是特定类型之上的操作符, 所以定义操作符函数与定义普通实例成员函数的差别在于对参数类型的约定:

  1. 对于一元操作符, 操作符函数没有参数, 对返回值的类型没有要求

    例如, 假设opSymbol1是一元操作符, 那么操作符函数opSymbol1定义为:

    PLAINTEXT
    operator func opSymbol1(): returnType1 {
        functionBody1
    }
  2. 对于二元操作符, 操作符函数只有一个参数 right, 对返回值的类型没有要求

    例如, 假设 opSymbol2 是二元操作符, 那么操作符函数 opSymbol2 可以定义为:

    CANGJIE
    operator func opSymbol2(right: anyType): returnType2 {
        functionBody2
    }

仓颉中操作符重载与C++中有一点保持一致, 即 实例本身作为操作数, 一元操作符重载不需要显式声明参数, 二元操作符重载只需要声明右操作数

同样地, 对于定义了操作符函数的类型TypeName, 就可以像使用普通的一元或二元操作符一样在 TypeName 类型的实例上使用这些操作符(保持各操作符固有的使用方式)

另外, 因为操作符函数是实例函数, 所以在TypeName 类型的实例 A 上使用重载操作符 opSymbol, 其实是函数调用 A.opSymbol(arguments) 的语法糖(根据操作符的类型, 参数的个数和类型, 调用不同的操作符函数)

下面举例说明如何在class类型中定义操作符函数, 进而实现在class类型上重载特定的操作符

假设我们希望在一个名为Point(包含两个Int32类型的成员变量xy)的class类型上实现一元负号(-)和二元加法(+)两个操作

其中, -实现对一个Point实例中两个成员变量xy取负值, 然后返回一个新的Point对象, +实现对两个Point实例中两个成员变量xy分别求和, 然后返回一个新的Point对象

首先, 定义名为Pointclass, 并在其中分别定义函数名为-+的操作符函数, 如下所示:

CANGJIE
class Point {
    var x: Int32 = 0
    var y: Int32 = 0
    init (a: Int32, b: Int32) {
        x = a
        y = b
    }
    operator func -(): Point {
        return Point(-x, -y)
    }
    operator func +(right: Point): Point {
        return Point(x + right.x, y + right.y)
    }
}

接下来, 就可以在Point的实例上直接使用一元-操作符和二元+操作符:

CANGJIE
main(): Int64 {
    let p1 = Point(8, 24)
    let p2 = -p1              // p2 = Point(-8, -24)
    let p3 = p1 + p2          // p3 = Point(0, 0)
    return 0
}

操作符重载之后, 就可以像普通操作符一样 操作 类型的实例

C++中存在前后置++/--, 所以还有其他标志用以区分前后置, 但 仓颉中不存在前后置的差别, 只有后置

操作符函数的作用域以及调用时的搜索策略

本节介绍操作符函数的作用域(名字和作用域请参考第 3 章)以及调用操作符函数时的搜索策略

操作符函数的作用域

操作符函数和相同位置定义或声明的普通函数具备相同的作用域级别

操作符重载函数, 与实例普通成员函数保持一致的作用域级别

调用操作符函数时的搜索策略

这里介绍调用操作符函数(即使用操作符)时的搜索策略, 因为 一元操作符函数的搜索 是 二元操作符函数搜索的一个子情况, 所以这里只介绍二元操作符(记为op)的搜索策略(一元操作符遵循一样的策略):

  1. 确定左操作数lOperand和右操作数rOperand的类型(假设分别为lTyperType)

  2. 在调用表达式lOperand op rOperand的当前作用域内, 搜索和 lType 关联的所有名字为op, 右操作数类型为rType的操作符函数

    如果有且仅有一个这样的操作符函数, 则将表达式调用转换成此操作符函数的调用; 如果没有找到这样的函数, 则继续执行第 3 步;

  3. 在更低优先级的作用域内重复第 2 步

    如果在最低优先级的作用域内仍然没有找到匹配的操作符函数, 则终止搜索, 并产生一个编译错误(“函数未定义”错误)

搜索, 是根据操作数类型 以及 操作符 进行的, 先从最高优先级的作用域, 逐渐向低优先级作用域搜索, 直到搜索到或搜索不到

可以被重载的操作符

下表列出了所有可以被重载的操作符(优先级从高到低):

OperatorDescription
()函数调用
[]索引
!逻辑非: 一元
-负: 一元
**求幂: 二元
*乘: 二元
/除以: 二元
%求余: 二元
+加: 二元
-减: 二元
<<按位左移: 二元
>>按位右移: 二元
<小于: 二元
<=小于等于: 二元
>大于: 二元
>=大于等于: 二元
==判等: 二元
!=判不等: 二元
&按位与: 二元
^按位或: 二元

需要注意的是:

  1. 除了上表中列出的操作符, 其他操作符不支持被重载(完整的操作符列表见 1.4 节)

  2. 一旦在某个类型上重载了除关系操作符(<<=>>===!=)之外的其他二元操作符, 并且操作符函数的返回类型与左操作数的类型一致或是其子类型, 那么自然也就可以在此类型上使用对应的复合赋值符号

    当操作符函数的返回类型与左操作数的类型不一致且不是其子类型时, 在使用对应的复合赋值符号时将报类型不匹配错误

  3. 仓颉编程语言不支持自定义操作符, 即不允许定义除上表中所列operator之外的其他操作符函数

  4. 函数调用操作符(())重载函数对输入参数和返回值类型没有要求

  5. 对于类型 T, 如果 T 已经默认支持了上述若干可重载操作符, 那么通过扩展的方式再次为其实现同签名的操作符函数时将报重定义错误

    例如, 为数值类型重载其已支持的同签名算术操作符、位操作符或关系操作符等操作符时, 为 Rune 重载同签名的关系操作符时, 为 Bool 类型重载同签名的逻辑操作符、判等或不等操作符时, 等等这些情况, 均会报重定义错

操作符重载, 参数的类型不做限制, 但重载的实际操作要符合原操作符语义, 禁止重载+实现*

重载可以复合赋值的操作符之后, 如果重载符合条件, 复合赋值操作符也可以直接使用, 详情请阅读上例第2条

存在以下特殊场景:

  • 不能使用thissuper调用()操作符重载函数

  • 对于枚举类型, 当构造器形式和()操作符重载函数形式都满足时, 优先匹配构造器形式

CANGJIE
// this 或 super 的使用场景
open class A {
    init(x: Int64) {
        this()                            // Error, 调用时缺少参数列表中的参数: (Int64)
    }
    operator func ()(): Unit {}
}

class B <: A {
    init() {
        super()                           // Error, 调用时缺少参数列表中的参数: (Int64)
    }
}

// enum 构造函数的使用场景
enum E {
    Y | X | X(Int64)
    operator func ()(a: Int64) { a }
    operator func ()(a: Float64) { a }
}

main() {
    let e = X(1)                          // ok, X(1) 是要调用构造函数 X(Int64)
    X(1.0)                                // ok, X(1.0) 是要调用 操作符() 重载函数
    let e1 = X
    e1(1)                                 // ok, e1(1) 是要调用 操作符() 重载函数
    Y(1)                                  // oK, Y(1) 是要调用 操作符() 重载函数
}

索引操作符重载

索引操作符([])分为取值 let a = arr[i] 和赋值 arr[i] = a 两种形式, 它们通过是否存在特殊的命名参数 value 来区分不同的重载

索引操作符重载不要求同时重载两种形式, 可以只重载赋值 不重载取值, 反之亦可

索引操作符取值形式 [] 内的参数序列 对应 操作符重载的非命名参数, 可以是 1 个或多个, 可以是任意类型, 不可以有其它命名参数, 返回类型可以是任意类型

CANGJIE
class A {
    operator func [](arg1: Int64, arg2: String): Int64 {
        return 0
    }
}

func f() {
    let a = A()
    let b: Int64 = a[1, "2"]
    // b == 0
}

索引操作符赋值形式 [] 内的参数序列 对应 操作符重载的命名参数

= 右侧的表达式对应操作符重载的命名参数, 有且只能有一个命名参数, 该命名参数的名称必须是 value, 不能有默认值, value 可以是任意类型, 返回类型必须是 Unit 类型

需要注意的是, value 只是一种特殊的标记, 在索引操作符赋值时并不需要使用命名参数的形式调用

CANGJIE
class A {
    operator func [](arg1: Int64, arg2: String, value!: Int64): Unit {
        return
    }
}

func f() {
    let a = A()
    a[1, "2"] = 0
}

特别的, 数值类型、BoolUnitNothingRuneStringRangeFunctionTuple 类型不支持重载索引操作符赋值形式

索引操作符存在两种操作: 取值 和 赋值

仓颉通过是否存在命名参数value进行区分, 不存在则是取值, 存在则是赋值, value不能有默认值, 且value只是一个标识, 所以使用[]时只需要正常使用就可以

如果存在, 即 赋值时, 重载函数的返回值类型必须是Unit

索引操作符的参数可以有若干个, 但命名参数只能存在一个, 且必须是value


Thanks for reading!

仓颉文档阅读-语言规约X: 重载

周一 10月 13 2025
7871 字 · 34 分钟