在C语言,C++中都有struct和class两个关键字,但是struct的使用在不同语言中的限制也有一定差异,这篇看一下struct在仓颉中的使用。
定义 struct 类型
struct 类型的定义以关键字 struct 开头,后跟 struct 的名字,接着是定义在一对花括号中的 struct 定义体。struct 定义体中可以定义一系列的成员变量、成员属性、静态初始化器、构造函数和成员函数。
class 与 struct 的主要区别在于:class 是引用类型,struct 是值类型,它们在赋值或传参时行为是不同的;class 之间可以继承,但 struct 之间不能继承。
struct 成员变量
struct 成员变量分为实例成员变量和静态成员变量,实例成员变量只能通过 struct 实例访问,静态成员变量只能通过 struct 类型名访问。
实例成员变量定义时可以不设置初值,也可以设置初值。
struct Person{
static let sex:Rune = '男'
let name:String
let age:Int64 = 18
public Person(name:String){
this.name = name
}
public func show(){
println('${this.name} ${sex} ${this.age} 岁')
}
}
main() {
let person = Person('张三')
person.show() //张三 男 18 岁
}
struct 静态初始化器
静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。
一个 struct 中最多允许定义一个静态初始化器
struct Person{
static let sex:Rune = '男'
static var height:Int64
static let weight:Int64
static init(){
height = 180
weight = 65
}
let name:String
let age:Int64 = 18
public Person(name:String){
this.name = name
}
public func show(){
println('${this.name} ${sex} ${this.age} 岁')
}
}
struct 构造函数
普通构造函数
普通构造函数以关键字init 开头,后跟参数列表和函数体,函数体中必须完成对所有未初始化的实例成员变量的初始化
一个 struct 中可以定义多个普通构造函数,但它们必须构成重载。
struct Rectangle {
let width: Int64
let height: String
public init(){
this.width=1
this.height=''
}
public init(width: Int64) {
this.width = width
this.height = ''
}
public init(height: String) {
this.width = 1
this.height = height
}
public init(width: Int64, height: String) {
this.width = width
this.height = height
}
}
主构造函数
可以定义(最多)一个主构造函数。主构造函数的名字和 struct 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时扮演定义成员变量和构造函数参数的功能。
struct Location{
let tag:String
// 主构造参数的成员变量形参,定义了 lat 和 lon 2个成员变量,并且赋值
public Location(tag:String,public var lat:Int64,private var lon:Int64){
this.tag = tag
}
public func show(){
println('location lat:${this.lat} lon:${this.lon}')
}
}
main() {
let location = Location('tag',110,119)
println(location.lat) //110
println(location.tag) //tag
location.show() //location lat:110 lon:119
}
struct 成员函数
实例成员函数
实例成员函数只能通过 struct 实例访问,在实例成员函数中可以访问静态成员变量以及静态成员函数。
静态成员函数
静态成员函数使用 static 修饰符修饰,只能通过 struct 类型名访问。静态成员函数中不能访问实例成员变量,也不能调用实例成员函数
struct Rectangle {
let width: Int64 = 10
let height: Int64 = 20
public func area() { //实例成员函数
typeName() // 可以访问静态成员函数
return this.width * this.height
}
public static func typeName(): String {
// "${this.height}" //non-static variable 'this' cannot be referenced from a static context
'Rectangle'
}
}
main() {
let rec = Rectangle()
println(rec.area()) //通过 struct 实例访问
println(Rectangle.typeName()) // 通过 struct 类型名访问
}
struct 成员的访问修饰符
struct的成员(包括成员变量、成员属性、构造函数、成员函数、操作符函数)用 4 种访问修饰符修饰:
private 表示在 struct 定义内可见。
internal表示仅当前包及子包内可见。
protected 表示当前模块可见。
public 表示模块内外均可见。
禁止递归 struct
//正确
struct R0{
}
struct R{
let r:R0=R0()
}
// 错误
struct R1 { // Error, 'R1'递归地引用自身
let other: R1
}
struct R2 { // Error, 'R2' 和 'R3' 是相互递归的
let other: R3
}
struct R3 { // Error, 'R2' 和 'R3' 是相互递归的
let other: R2
}
创建 struct 实例
在 struct 定义之外,通过 struct 类型名调用构造函数创建该类型实例,并可以通过实例访问满足可见性修饰符(如public )的实例成员变量和实例成员函数。
如果希望通过 struct 实例去修改成员变量的值,需要将 struct 类型的变量定义为可变变量,并且被修改的成员变量也必须是可变成员变量(使用 var 定义)。
struct Person{
private static let sex:Rune = '男'
static var height:Int64
static let weight:Int64
static init(){
height = 180
weight = 65
}
var name:String
var age:Int64 = 18
public Person(name:String){
this.name = name
}
public func show(){
println('${this.name} ${sex} ${this.age} 岁')
}
}
main() {
let p1 = Person('无名')
p1.name = '张三' //variable 'p1' is immutable
// 实例化的变量如果需要赋值修改,也需要用var定义
var person = Person('张三')
person.show() //张三 男 18 岁
person.age = 20
person.name = '李四'
person.show() //李四 男 20 岁
println(Person.sex) //无法访问字段 “sex”
}
mut 函数
mut 函数的定义
mut 函数与普通的实例成员函数相比,多一个 mut 关键字来修饰。
在函数之前增加 mut 修饰符之后,即可在函数体内修改成员变量的值。
struct Point{
public Point(var x:Int64,var y:Int64){
}
func show(){
println('坐标为(${x},${y})')
}
// func setX(x:Int64){ //实例成员变量 “x” 无法在不可变函数中修改
// this.x =x
// }
mut func setY(y:Int64){
this.y=y
}
func copy(){
return this // this变量是当前实例的拷贝
}
}
main() {
var point = Point(3,4)
var p2 = point.copy()
point.show() //坐标为(3,4)
point.setY(6)
point.show() //坐标为(3,6)
p2.show() //坐标为(3,4)
}
mut 函数中的 this 不能作为表达式。
mut 函数中的 lambda 或嵌套函数不能对 struct 的实例成员变量进行捕获。
struct Point{
public Point(var x:Int64,var y:Int64){
}
func copy(){
return this
}
// mut func copy2(){
// return this //在可变函数 “copy2” 中,“this” 不能用作表达式
// }
mut func setX(a:Int64){
this.x ={ =>a }()
this.x ={=>a+this.x }() //'this' cannot be captured in the mutable function 'setX'
}
}
接口中的 mut 函数
struct 类型在实现 interface 的函数时必须保持一样的 mut 修饰。struct 以外的类型实现 interface 的函数时不能使用 mut 修饰。
interface I {
mut func f1(): Unit
func f2(): Unit
}
struct A <: I {
public mut func f1(): Unit {} // 正确使用
public func f2(): Unit {} //正确使用
}
// struct B <: I {
// public func f1(): Unit {} // Error,在接口中,“f1” 用 “mut” 修饰,但在结构体中没有
// public mut func f2(): Unit {} // Error, 在接口中,'f2' 没有用 'mut' 修饰,但在结构体中却用了
// }
// class C <: I {
// mut func f1(): Unit {} // Error,类体中函数声明上出现意外的修饰符 “mut”
// func f2(): Unit {} // Error, “f2” 的可见性为 “internal”
// }
class D <: I {
public func f1(): Unit {}//正确使用
public func f2(): Unit {} //正确使用
}
当 struct 的实例赋值给 interface 类型时是拷贝语义,因此 interface 的 mut 函数并不能修改 struct 实例的值。
interface In {
mut func f(): Unit
}
struct Foo <: In {
public var v = 0
public mut func f(): Unit {
v += 1
}
}
main() {
var a = Foo()
var b: In = a
b.f() // Calling 'f' via 'b' cannot modify the value of 'a'
println(a.v) // 0
a.f()
println(a.v) //1
var c:In = Foo()
c.f()
// println(c.v) //error: 'v' is not a member of interface 'In'
}
mut 函数的使用限制
1.因为 struct 是值类型,所以如果一个变量是 struct 类型且使用 let 声明,那么不能通过这个变量访问该类型的 mut 函数。
2.如果一个变量的类型是 struct 类型,那么这个变量不能将该类型使用 mut 修饰的函数作为一等公民来使用,只能调用这些 mut 函数。
3.非 mut 的实例成员函数(包括 lambda 表达式)不能直接访问所在类型的 mut 函数,反之可以。
struct People{
mut func fun1(){
println('fun1')
fun2() // 可以访问
}
func fun2(){
println('fun2')
fun1() //错误:不可变函数 “fun2” 无法访问可变函数 “fun1”
}
}
main() {
var p1 = People()
p1.fun1() // fun1
let p2 = People()
// p2.fun1() //错误:不能在不可变值上使用可变函数
// var f = p1.fun1 //错误:可变函数 “fun1” 不能单独作为引用使用
}