仓颉-结构类型

定义 struct 类型

struct 类型的定义以关键字 struct 开头,后跟 struct 的名字,接着是定义在一对花括号中的 struct 定义体。struct 定义体中可以定义一系列的成员变量、成员属性(参见属性)、静态初始化器、构造函数和成员函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Rectangle {
let width: Int64
let height: Int64

public init(width: Int64, height: Int64) {
this.width = width
this.height = height
}

public func area() {
width * height
}
}

上例中定义了名为 Rectanglestruct 类型,它有两个 Int64 类型的成员变量 widthheight,一个有两个 Int64 类型参数的构造函数(使用关键字 init 定义,函数体中通常是对成员变量的初始化),以及一个成员函数 area(返回 widthheight 的乘积)。

注意:

struct 只能定义在源文件的顶层作用域。

struct 成员变量

struct 成员变量分为实例成员变量和静态成员变量(使用 static 修饰符修饰),二者访问上的区别在于实例成员变量只能通过 struct 实例(说 aT 类型的实例,指的是 a 是一个 T 类型的值)访问,静态成员变量只能通过 struct 类型名访问。

实例成员变量定义时可以不设置初值(但必须标注类型,如上例中的 widthheight),也可以设置初值,例如:

1
2
3
4
struct Rectangle {
let width = 10
let height = 20
}

struct 静态初始化器

struct 支持定义静态初始化器,并在静态初始化器中通过赋值表达式来对静态成员变量进行初始化。

静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。

1
2
3
4
5
6
struct Rectangle {
static let degree: Int64
static init() {
degree = 180
}
}

一个 struct最多允许定义一个静态初始化器,否则报重定义错误。

1
2
3
4
5
6
7
8
9
struct Rectangle {
static let degree: Int64
static init() {
degree = 180
}
static init() { // Error, redefinition with the previous static init function
degree = 180
}
}

struct 构造函数

struct 支持两类构造函数:普通构造函数和主构造函数。

普通构造函数以关键字 init 开头,后跟参数列表和函数体,函数体中必须完成对所有未初始化的实例成员变量的初始化(如果参数名和成员变量名无法区分,可以在成员变量前使用 this 加以区分,this 表示 struct 的当前实例),否则编译报错。

1
2
3
4
5
6
7
8
struct Rectangle {
let width: Int64
let height: Int64

public init(width: Int64, height: Int64) { // Error, 'height' is not initialized in the constructor
this.width = width
}
}

一个 struct 中可以定义多个普通构造函数,但它们必须构成重载(参见函数重载),否则报重定义错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Rectangle {
let width: Int64
let height: Int64

public init(width: Int64) {
this.width = width
this.height = width
}

public init(width: Int64, height: Int64) { // Ok: overloading with the first init function
this.width = width
this.height = height
}

public init(height: Int64) { // Error, redefinition with the first init function
this.width = height
this.height = height
}
}

除了可以定义若干普通的以 init 为名字的构造函数外,struct 内还可以定义(最多)一个主构造函数。主构造函数的名字和 struct 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 letvar),成员变量形参同时扮演定义成员变量和构造函数参数的功能。

使用主构造函数通常可以简化 struct 的定义,例如,上述包含一个 init 构造函数的 Rectangle 可以简化为如下定义:

1
2
3
struct Rectangle {
public Rectangle(let width: Int64, let height: Int64) {}
}

主构造函数的参数列表中也可以定义普通形参,例如:

1
2
3
struct Rectangle {
public Rectangle(name: String, let width: Int64, let height: Int64) {}
}

如果 struct 定义中不存在自定义构造函数(包括主构造函数),并且所有实例成员变量都有初始值,则会自动为其生成一个无参构造函数(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象);否则,不会自动生成此无参构造函数。例如,对于如下 struct 定义,注释中给出了自动生成的无参构造函数:

1
2
3
4
5
6
7
8
struct Rectangle {
let width: Int64 = 10
let height: Int64 = 10
/* Auto-generated memberwise constructor:
public init() {
}
*/
}

struct 成员函数

struct 成员函数分为实例成员函数和静态成员函数(使用 static 修饰符修饰),二者的区别在于:实例成员函数只能通过 struct 实例访问,静态成员函数只能通过 struct 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。

下例中,area 是实例成员函数,typeName 是静态成员函数。

1
2
3
4
5
6
7
8
9
10
11
12
struct Rectangle {
let width: Int64 = 10
let height: Int64 = 20

public func area() {
this.width * this.height
}

public static func typeName(): String {
"Rectangle"
}
}

实例成员函数中可以通过 this 访问实例成员变量,例如:

1
2
3
4
5
6
7
8
struct Rectangle {
let width: Int64 = 1
let height: Int64 = 1

public func area() {
this.width * this.height
}
}

struct 成员的访问修饰符

struct 的成员(包括成员变量、成员属性、构造函数、成员函数、操作符函数(详见操作符重载章节))用 4 种访问修饰符修饰:privateinternalprotectedpublic,缺省的修饰符是 internal

  • private 表示在 struct 定义内可见。
  • internal 表示仅当前包及子包(包括子包的子包,详见章节)内可见。
  • protected 表示当前模块(详见章节)可见。
  • public 表示模块内外均可见。

下面的例子中,widthpublic 修饰的成员,在类外可以访问,height 是缺省访问修饰符的成员,仅在当前包及子包可见,其他包无法访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package a
public struct Rectangle {
public var width: Int64
var height: Int64
private var area: Int64
...
}

func samePkgFunc() {
var r = Rectangle(10, 20)
r.width = 8 // Ok: public 'width' can be accessed here
r.height = 24 // Ok: 'height' has no modifier and can be accessed here
r.area = 30 // Error, private 'area' can't be accessed here
}
package b
import a.*
main() {
var r = Rectangle(10, 20)
r.width = 8 // Ok: public 'width' can be accessed here
r.height = 24 // Error, no modifier 'height' can't be accessed here
r.area = 30 // Error, private 'area' can't be accessed here
}

禁止递归 struct

递归和互递归定义的 struct 均是非法的。例如:

1
2
3
4
5
6
7
8
9
struct R1 { // Error, 'R1' recursively references itself
let other: R1
}
struct R2 { // Error, 'R2' and 'R3' are mutually recursive
let other: R3
}
struct R3 { // Error, 'R2' and 'R3' are mutually recursive
let other: R2
}

创建 struct 实例

定义了 struct 类型后,即可通过调用 struct 的构造函数来创建 struct 实例。在 struct 定义之外,通过 struct 类型名调用构造函数创建该类型实例,并可以通过实例访问满足可见性修饰符(如public )的实例成员变量和实例成员函数。例如,下例中定义了一个 Rectangle 类型的变量 r,通过 r.widthr.height 可分别访问 rwidthheight 的值,通过 r.area() 可以调用 r 的成员函数 area

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct Rectangle {
public var width: Int64
public var height: Int64

public init(width: Int64, height: Int64) {
this.width = width
this.height = height
}

public func area() {
width * height
}
}
let r = Rectangle(10, 20)
let width = r.width // width = 10
let height = r.height // height = 20
let a = r.area() // a = 200

如果希望通过 struct 实例去修改成员变量的值,需要将 struct 类型的变量定义为可变变量,并且被修改的成员变量也必须是可变成员变量(使用 var 定义)。举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Rectangle {
public var width: Int64
public var height: Int64

public init(width: Int64, height: Int64) {
this.width = width
this.height = height
}

public func area() {
width * height
}
}

main() {
var r = Rectangle(10, 20) // r.width = 10, r.height = 20
r.width = 8 // r.width = 8
r.height = 24 // r.height = 24
let a = r.area() // a = 192
}

在赋值或传参时,会对 struct 实例进行复制(成员变量为引用类型时,仅复制引用而不会复制引用的对象),生成新的实例,对其中一个实例的修改并不会影响另外一个实例。以赋值为例,下面的例子中,将 r1 赋值给 r2 之后,修改 r1widthheight 的值,并不会影响 r2widthheight 值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct Rectangle {
public var width: Int64
public var height: Int64

public init(width: Int64, height: Int64) {
this.width = width
this.height = height
}

public func area() {
width * height
}
}

main() {
var r1 = Rectangle(10, 20) // r1.width = 10, r1.height = 20
var r2 = r1 // r2.width = 10, r2.height = 20
r1.width = 8 // r1.width = 8
r1.height = 24 // r1.height = 24
let a1 = r1.area() // a1 = 192
let a2 = r2.area() // a2 = 200

mut 函数

struct 类型是值类型,其实例成员函数无法修改实例本身。例如,下例中,成员函数 g 中不能修改成员变量 i 的值。

1
2
3
4
5
6
7
struct Foo {
var i = 0

public func g() {
i += 1 // Error, the value of a instance member variable cannot be modified in an instance member function
}
}

mut 函数是一种可以修改 struct 实例本身的特殊的实例成员函数。在 mut 函数内部,this 的语义是特殊的,这种 this 拥有原地修改字段的能力。

注意:

只允许在 interface、struct 和 struct 的扩展内定义 mut 函数(class 是引用类型,实例成员函数不需要加 mut 也可以修改实例成员变量,所以禁止在 class 中定义 mut 函数)。

mut 函数定义

mut 函数与普通的实例成员函数相比,多一个 mut 关键字来修饰。

例如,下例中在函数 g 之前增加 mut 修饰符之后,即可在函数体内修改成员变量 i 的值。

1
2
3
4
5
6
7
struct Foo {
var i = 0

public mut func g() {
i += 1 // Ok
}
}

mut 只能修饰实例成员函数,不能修饰静态成员函数。

1
2
3
4
5
6
7
struct A {
public mut func f(): Unit {} // Ok
public mut operator func +(rhs: A): A { // Ok
A()
}
public mut static func g(): Unit {} // Error, static member functions cannot be modified with 'mut'
}

mut 函数中的 this 不能被捕获,也不能作为表达式。mut 函数中的 lambda 或嵌套函数不能对 struct 的实例成员变量进行捕获。

示例:

1
2
3
4
5
6
7
8
9
10
11
struct Foo {
var i = 0

public mut func f(): Foo {
let f1 = { => this } // Error, 'this' in mut functions cannot be captured
let f2 = { => this.i = 2 } // Error, instance member variables in mut functions cannot be captured
let f3 = { => this.i } // Error, instance member variables in mut functions cannot be captured
let f4 = { => i } // Error, instance member variables in mut functions cannot be captured
this // Error, 'this' in mut functions cannot be used as expressions
}
}

接口中的 mut 函数

接口中的实例成员函数,也可以使用 mut 修饰。

struct 类型在实现 interface 的函数时必须保持一样的 mut 修饰。struct 以外的类型实现 interface 的函数时不能使用 mut 修饰。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface I {
mut func f1(): Unit
func f2(): Unit
}

struct A <: I {
public mut func f1(): Unit {} // Ok: as in the interface, the 'mut' modifier is used
public func f2(): Unit {} // Ok: as in the interface, the 'mut' modifier is not used
}

struct B <: I {
public func f1(): Unit {} // Error, 'f1' is modified with 'mut' in interface, but not in struct
public mut func f2(): Unit {} // Error, 'f2' is not modified with 'mut' in interface, but did in struct
}

class C <: I {
public func f1(): Unit {} // Ok
public func f2(): Unit {} // Ok
}

struct 的实例赋值给 interface 类型时是拷贝语义,因此 interfacemut 函数并不能修改 struct 实例的值。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface I {
mut func f(): Unit
}
struct Foo <: I {
public var v = 0
public mut func f(): Unit {
v += 1
}
}
main() {
var a = Foo()
var b: I = a
b.f() // Calling 'f' via 'b' cannot modify the value of 'a'
println(a.v) // 0
}

程序输出结果为:

1
0

mut 函数的使用限制

因为 struct 是值类型,所以如果一个变量是 struct 类型且使用 let 声明,那么不能通过这个变量访问该类型的 mut 函数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface I {
mut func f(): Unit
}
struct Foo <: I {
public var i = 0
public mut func f(): Unit {
i += 1
}
}
main() {
let a = Foo()
a.f() // Error, 'a' is of type struct and is declared with 'let', the 'mut' function cannot be accessed via 'a'
var b = Foo()
b.f() // Ok
let c: I = Foo()
c.f() // Ok, 变量 c 为接口 I 类型,非 struct 类型,此处允许访问。
}

为避免逃逸,如果一个变量的类型是 struct 类型,那么这个变量不能将该类型使用 mut 修饰的函数作为一等公民来使用,只能调用这些 mut 函数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface I {
mut func f(): Unit
}

struct Foo <: I {
var i = 0

public mut func f(): Unit {
i += 1
}
}

main() {
var a = Foo()
var fn = a.f // Error, mut function 'f' of 'a' cannot be used as a first class citizen.
var b: I = Foo()
fn = b.f // Ok
}

为避免逃逸,非 mut 的实例成员函数(包括 lambda 表达式)不能直接访问所在类型的 mut 函数,反之可以。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Foo {
var i = 0

public mut func f(): Unit {
i += 1
g() // Ok
}

public func g(): Unit {
f() // Error, mut functions cannot be invoked in non-mut functions
}
}

interface I {
mut func f(): Unit {
g() // Ok
}

func g(): Unit {
f() // Error, mut functions cannot be invoked in non-mut functions
}
}