仓颉语言学习笔记(2)重载、const
函数重载
一个作用域中,一个函数名对应多个函数定义,成为函数重载。
- 函数名相同,函数参数不同
func f(a: Int64) {
println("Int64:" + a.toString())
}
func f(a: Float64) {
println("Float64:" + a.toString())
}
func f(a: Int64, b: Float64) {
println("Int64 and Float64:" + a.toString() + ", " + b.toString())
}
main() {
f(42)
f(3.14)
f(42, 3.14)
}
- 同一个类内的两个构造函数参数不同
class C {
var a: Int64
var b: Float64
public init(a: Int64, b: Float64) {
this.a = a
this.b = b
}
public init(a: Int64) {
b = 0.0
this.a = a
}
public func display() {
println("a: ${a}, b: ${b}")
}
}
main() {
let c1 = C(10, 20.5)
let c2 = C(15)
c1.display()
c2.display()
}
- 同一个类内的主构造函数和 init 构造函数参数不同(认为主构造函数和 init 函数具有相同的名字,主构造函数只能有一个)
class C {
var a: Int64
var b: Float64
C(a: Int64, b: Float64) {
this.a = a
this.b = b
}
init(a: Int64) {
this.a = a
this.b = 0.0
}
func display(): String {
return a.toString() + ", " + b.toString()
}
}
main() {
let c = C(42)
println(c.display())
}
- 两个函数名相同,参数不同的函数定义在不同的作用域,在两个函数都可见的作用域中构成重载。
func f(a: Int64) {
return "Int64: " + a.toString()
}
func g() {
func f(a: Float64) {
return "In g Float64: " + a.toString()
}
func display() {
println(f(100))
println(f(3.14))
}
display()
}
main() {
g()
}
// Int64: 100
// In g Float64: 3.140000
- 如果子类中存在与父类同名的函数,并且函数的参数类型不同
open class Base {
public func f(a: Int64) {
println("In Base Int64: ${a}")
}
}
class Sub <: Base {
public func f(a: Float64) {
println("In Sub Float64: ${a}")
}
}
main() {
var b: Base = Sub()
b.f(100)
var s: Sub = Sub()
s.f(3.14)
}
// In Base Int64: 100
// In Sub Float64: 3.140000
函数重载决议
函数调用时,所有可被调用的函数(是指当前作用域可见且能通过类型检查的函数)构成候选集,候选集中有多个函数,究竟选择候选集中哪个函数,需要进行函数重载决议。
- 优先选择作用域级别高的作用域内的函数。在嵌套的表达式或函数中,越是内层作用域级别越高。
open class Base {}
class Sub <: Base {}
func outer() {
func g(a: Sub) {
print("1")
}
func inner() {
func g(a: Int64) {
print("2")
}
g(Sub())
}
inner()
}
main() {
outer()
}
- 如果作用域级别相对最高的仍有多个函数,则需要选择最匹配的函数(对于函数 f 和 g 以及给定的实参,如果 f 可以被调用时 g 也总是可以被调用的,但反之不然,则称 f 比 g 更匹配)。如果不存在唯一最匹配的函数,则报错。
- 子类和父类认为是同一作用域。
操作符重载
- 定义操作符函数时需要在 func 关键字前面添加 operator 修饰符;
- 操作符函数的参数个数需要匹配对应操作符的要求(详见附录操作符);
- 操作符函数只能定义在 class、interface、struct、enum 和 extend 中;
- 操作符函数具有实例成员函数的语义,所以禁止使用 static 修饰符;
- 操作符函数不能为泛型函数。
定义操作符函数有两种方式:
-
对于可以直接包含函数定义的类型 (包括 struct、enum、class 和 interface ),可以直接在其内部定义操作符函数的方式实现操作符的重载。
-
使用 extend 的方式为其添加操作符函数,从而实现操作符在这些类型上的重载。对于无法直接包含函数定义的类型(是指除 struct、class、enum 和 interface 之外其他的类型)或无法改变其实现的类型,比如第三方定义的 struct、class、enum 和 interface,只能采用这种方式(参见扩展);
-
一元操作符重载,没有参数
class C { var a: Int64 = 10 public operator func !() { return "!" + a.toString() } } main() { var c = C() print(!c) } -
二元操作符重载,操作符有一个参数
class C { var str: String C(str!: String = "hello") { this.str = str } operator func + (right: C): C { return C(str: this.str + right.str) } func display() { println(this.str) } } main() { let c1 = C(str: "Cangjie ") let c2 = C(str: "Demo") (c1 + c2).display() } -
索引操作符([])分为取值 let a = arr[i] 和赋值 arr[i] = a 两种形式,它们通过是否存在特殊的命名参数 value 来区分不同的重载。索引操作符重载不要求同时重载两种形式,可以只重载赋值不重载取值,反之亦可。
索引操作符取值形式 [] 内的参数序列对应操作符重载的非命名参数,可以是 1 个或多个,可以是任意类型。不可以有其它命名参数。返回类型可以是任意类型。
赋值形式 [] 内的参数序列对应操作符重载的非命名参数,可以是 1 个或多个,可以是任意类型。= 右侧的表达式对应操作符重载的命名参数,有且只能有一个命名参数,该命名参数的名称必须是 value, 不能有默认值,value 可以是任意类型。返回类型必须是 Unit 类型。需要注意的是,value 只是一种特殊的标记,在索引操作符赋值时并不需要使用命名参数的形式调用。
class C { var a = 1 var b = 2 var c = 3 // 赋值形式 operator func [](index: Int64, value!: Int64){ if(index == 0){ a = value } else if(index == 1){ b = value } else if(index == 2){ c = value } } // 取值形式 operator func [](index: Int64): Int64 { if(index == 0){ return a } else if(index == 1){ return b } else if(index == 2){ return c } return -1 } } main() { var obj = C() println("${obj[1]}") // 输出2 obj[1] = 20 println("${obj[1]}") // 输出20 } -
函数调用操作符(())重载函数,输入参数和返回值类型可以是任意类型。不能使用 this 或 super 调用 () 操作符重载函数。
class C { operator func ()() { return 1 } } main() { let c = C() println(c()) }
const变量
const 变量是一种特殊的变量,它以关键字 const 修饰,定义在编译时完成求值,并且在运行时不可改变的变量。
let c: Int64 = 10 // 顶层let变量必须初始化
main() {
const a: Int64 = 20 // const变量必须初始化
let b: Int64
b = 20 // let变量可以先声明后赋值
}
const上下文与const表达式
const 上下文是指 const 变量初始化表达式,这些表达式始终在编译时求值。因此需要对 const 上下文中允许的表达式加以限制,避免修改全局状态、I/O 等副作用,确保其可以在编译时求值。
const 表达式具备了可以在编译时求值的能力。满足如下规则的表达式是 const 表达式:
- 数值类型、Bool、Unit、Rune、String 类型的字面量(不包含插值字符串)。
- 所有元素都是 const 表达式的 Array 字面量(不能是 Array 类型,可以使用 VArray 类型),tuple 字面量。
- const 变量,const 函数形参,const 函数中的局部变量。
- const 函数,包含使用 const 声明的函数名、符合 const 函数要求的 lambda、以及这些函数返回的函数表达式。
- const 函数调用(包含 const 构造函数),该函数的表达式必须是 const 表达式,所有实参必须都是 const 表达式。
- 所有参数都是 const 表达式的 enum 构造器调用,和无参数的 enum 构造器。
- 数值类型、Bool、Unit、Rune、String 类型的算术表达式、关系表达式、位运算表达式,所有操作数都必须是 const 表达式。
- if、match、try、控制转移表达式(包含 return、break、continue、throw)、is、as。这些表达式内的表达式必须都是 const 表达式。
- const 表达式的成员访问(不包含属性的访问),tuple 的索引访问。
- const init 和 const 函数中的 this 和 super 表达式。
- const 表达式的 const 实例成员函数调用,且所有实参必须都是 const 表达式。
const函数
const 函数是一类特殊的函数,这些函数具备了可以在编译时求值的能力。在 const 上下文中调用这种函数时,这些函数会在编译时执行计算。而在其它非 const 上下文,const 函数会和普通函数一样在运行时执行。
const init
如果一个 struct 或 class 定义了 const 构造器,那么这个 struct/class 实例可以用在 const 表达式中。
- 如果当前类型是 class,则不能具有 var 声明的实例成员变量,否则不允许定义 const init 。如果当前类型具有父类,当前的 const init 必须调用父类的 const init(可以显式调用或者隐式调用无参const init),如果父类没有 const init 则报错。
- 当前类型的实例成员变量如果有初始值,初始值必须要是 const 表达式,否则不允许定义 const init。
- const init 内可以使用赋值表达式对实例成员变量赋值,除此以外不能有其它赋值表达式。
const init 与 const 函数的区别是 const init 内允许对实例成员变量进行赋值(需要使用赋值表达式)。
class C {
let name: String
var num: Int64 = 0 // 错误:定义了const构造器后,不能具有var声明的实例成员变量
const C(str: String) {
name = str
}
}

被折叠的 条评论
为什么被折叠?



