跟老卫学仓颉编程语言开发:结构类型
struct类型的定义以关键字struct开头,后跟struct的名字,接着是定义在一对花括号中的struct定义体。struct定义体中可以定义一系列的成员变量、成员属性、静态初始化器、构造函数和成员函数。
定义struct类型
以下是定义struct类型的一个示例:
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
}
}
上例中定义了名为Rectangle的struct类型,它有两个Int64类型的成员变量width和height,一个有两个Int64类型参数的构造函数init,以及一个成员函数area,用于返回width和height的乘积。
1. struct成员变量
struct成员变量分为实例成员变量和静态成员变量(使用static修饰符修饰,且必须有初值),二者访问上的区别在于实例成员变量只能通过struct实例访问,静态成员变量只能通过struct类型名访问。
实例成员变量定义时可以不设置初值(但必须标注类型),如上例中的width和height。也可以设置初值,例如:
struct Rectangle {
let width = 10
let height = 20
}
2. struct静态初始化器
struct支持定义静态初始化器,并在静态初始化器中通过赋值表达式来对静态成员变量进行初始化。
静态初始化器以关键字组合static init开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。
struct Rectangle {
static let degree: Int64
static init() {
degree = 180
}
}
一个struct中最多允许定义一个静态初始化器,否则报重定义错误。
struct Rectangle {
static let degree: Int64
static init() {
degree = 180
}
static init() { // 错误!用前面的静态init函数重新定义
degree = 180
}
}
3. struct构造函数
struct支持两类构造函数:普通构造函数和主构造函数。
普通构造函数以关键字init开头,后跟参数列表和函数体,函数体中必须完成对所有未初始化的实例成员变量的初始化,否则编译报错。
struct Rectangle {
let width: Int64
let height: Int64
public init(width: Int64, height: Int64) { // 错误! 'height'未在构造函数中初始化
this.width = width
}
}
一个struct中可以定义多个普通构造函数,但它们必须构成重载,否则报重定义错误。
struct Rectangle {
let width: Int64
let height: Int64
public init(width: Int64) {
this.width = width
this.height = width
}
public init(width: Int64, height: Int64) { // 正确!用第一个init函数重载
this.width = width
this.height = height
}
public init(height: Int64) { // 错误!使用第一个init函数重新定义
this.width = height
this.height = height
}
}
除了可以定义若干普通的以init为名字的构造函数外,struct内还可以定义(最多)一个主构造函数。主构造函数的名字和struct类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上let或var),成员变量形参同时扮演定义成员变量和构造函数参数的功能。
使用主构造函数通常可以简化struct的定义,例如,上述包含一个init构造函数的Rectangle可以简化为如下定义:
struct Rectangle {
public Rectangle(let width: Int64, let height: Int64) {}
}
主构造函数的参数列表中也可以定义普通形参,例如:
struct Rectangle {
public Rectangle(name: String, let width: Int64, let height: Int64) {}
}
如果struct定义中不存在自定义构造函数(包括主构造函数),并且所有实例成员变量都有初始值,则会自动为其生成一个无参构造函数(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象);否则,不会自动生成此无参构造函数。例如,对于如下struct定义,注释中给出了自动生成的无参构造函数:
struct Rectangle {
let width: Int64 = 10
let height: Int64 = 10
/* Auto-generated memberwise constructor:
public init() {
}
*/
}
4. struct成员函数
struct成员函数分为实例成员函数和静态成员函数(使用static修饰符修饰),二者的区别在于:实例成员函数只能通过struct实例访问,静态成员函数只能通过struct类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。
下例中,area是实例成员函数,typeName是静态成员函数。
struct Rectangle {
let width: Int64 = 10
let height: Int64 = 20
public func area() {
this.width * this.height
}
public static func typeName(): String {
"Rectangle"
}
}
实例成员函数中可以通过this访问实例成员变量,例如:
struct Rectangle {
let width: Int64 = 1
let height: Int64 = 1
public func area() {
this.width * this.height
}
}
5. struct成员的访问修饰符
struct的成员,包括成员变量、成员属性、构造函数、成员函数、操作符函数,可以用4种访问修饰符修饰:private、internal、protected和public,缺省的修饰符是internal。
- private表示在struct定义内可见。
- internal表示仅当前包及子包内可见。
- protected表示当前模块可见。
- public表示模块内外均可见。
下面的例子中,width是public修饰的成员,在类外可以访问,height是缺省访问修饰符的成员,仅在当前包及子包可见,其他包无法访问。
package a
publicstructRectangle {
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 // 错误!, 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 // 错误!, no modifier 'height' can't be accessed here
r.area = 30 // 错误!, private 'area' can't be accessed here
}
6. 禁止递归struct
递归和互递归定义的struct均是非法的。例如:
struct R1 { // 错误!'R1' 递归引用自身
let other: R1
}
struct R2 { // 错误!'R2' 和 'R3' 递归引用自身
let other: R3
}
struct R3 { // 错误!'R2' 和 'R3' 递归引用自身
let other: R2
}

创建struct实例
定义了struct类型后,即可通过调用struct的构造函数来创建struct实例。在struct定义之外,通过struct类型名调用构造函数。例如,下例中定义了一个Rectangle类型的变量r。
let r = Rectangle(10, 20)
创建了struct实例之后,可以通过实例访问它的(public修饰的)实例成员变量和实例成员函数。例如,下例中通过r.width和r.height可分别访问r中width和height的值,通过r.area()可以调用r的成员函数area。
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定义)。举例如下:
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之后,修改r1的width和height的值,并不会影响r2的width和height值。
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的值。
struct Foo {
var i = 0
public func g() {
i += 1 // 错误!无法在实例成员函数中修改实例成员变量的值
}
}
mut函数是一种可以修改struct实例本身的特殊的实例成员函数。在mut函数内部,this的语义是特殊的,这种this拥有原地修改字段的能力。
注:只允许在interface、struct和struct的扩展内定义mut函数,禁止在class中定义mut函数。
mut函数与普通的实例成员函数相比,多一个mut关键字来修饰。
例如,下例中在函数g之前增加mut修饰符之后,即可在函数体内修改成员变量i的值。
struct Foo {
var i = 0
public mut func g() {
i += 1 // 正确
}
}
参考引用
- 示例源码,见免费开源书《跟老卫学仓颉编程语言开发》
- 免费开源书《跟老卫学HarmonyOS开发》
- HarmonyOS NEXT+AI大模型打造智能助手APP(仓颉版)(视频)
- 仓颉编程从入门到实践(北京大学出版社)
