NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.4
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
结构类型
定义struct类型
struct类型的定义以关键字struct开头, 后跟struct的名字, 接着是定义在一对花括号中的struct定义体
struct定义体中可以定义一系列的成员变量、成员属性(参见属性)、静态初始化器、构造函数和成员函数struct Rectangle {let width: Int64let height: Int64public init(width: Int64, height: Int64) {this.width = widththis.height = height}public func area() {width * height}}上例中定义了名为
Rectangle的struct类型, 它有两个Int64类型的成员变量width和height, 一个有两个Int64类型参数的构造函数(使用关键字init定义, 函数体中通常是对成员变量的初始化), 以及一个成员函数area(返回width和height的乘积)注意:
struct只能定义在源文件的顶层作用域
仓颉也可以struct类型, 是值类型的
只能定义在顶层作用域
struct Ident {}可以拥有成员变量、成员属性、构造函数、成员函数等
struct成员变量
struct成员变量分为实例成员变量和静态成员变量(使用static修饰符修饰)二者访问上的区别在于实例成员变量只能通过
struct实例(说a是T类型的实例, 指的是a是一个T类型的值)访问, 静态成员变量只能通过struct类型名访问实例成员变量定义时可以不设置初值(但必须标注类型, 如上例中的
width和height), 也可以设置初值, 例如:struct Rectangle {let width = 10let height = 20}
仓颉中, strcut也可以定义静态成员变量, 但与C++不同的是, 仓颉中的静态成员变量只能通过struct名访问
struct静态初始化器
struct支持定义静态初始化器, 并在静态初始化器中 通过赋值表达式来对静态成员变量进行初始化静态初始化器以关键字组合**
static init开头, 后跟无参参数列表和函数体, 且不能被访问修饰符修饰**函数体中必须完成对所有未初始化的静态成员变量的初始化, 否则编译报错
struct Rectangle {static let degree: Int64static init() {degree = 180}}一个
struct中最多允许定义一个静态初始化器, 否则报重定义错误struct Rectangle {static let degree: Int64static init() {degree = 180}static init() { // Error, 静态初始化函数重定义degree = 180}}
仓颉中, 使用静态初始化器: static init() {}对静态成员变量进行初始化
且, 需要在静态初始化器中 对所有静态成员变量完成初始化, 否则编译报错
且struct中只能定义一个静态初始化器
struct构造函数
struct支持两类构造函数: 普通构造函数和主构造函数普通构造函数以关键字
init开头, 后跟参数列表和函数体函数体中必须完成对所有未初始化的实例成员变量的初始化(如果参数名和成员变量名无法区分, 可以在成员变量前使用
this加以区分,this表示struct的当前实例), 否则编译报错struct Rectangle {let width: Int64let height: Int64public init(width: Int64, height: Int64) { // Error, 'height' 在构造函数中未初始化this.width = width}}
仓颉struct, 使用init作为构造函数的函数名
函数体内必须 完成所有未初始化成员变量的初始化
构造函数的形参可以与已存在的成员变量同名, 此时在函数体内可以使用this.变量名访问成员变量, 否则就是形参
一个
struct中可以定义多个普通构造函数, 但它们必须构成重载(参见函数重载), 否则报重定义错误struct Rectangle {let width: Int64let height: Int64public init(width: Int64) {this.width = widththis.height = width}public init(width: Int64, height: Int64) { // Ok, 与第一个 init 函数构成重载this.width = widththis.height = height}public init(height: Int64) { // Error, 与第一个 init 函数重定义this.width = heightthis.height = height}}
仓颉struct的init函数之间可以构成重载
除了可以定义若干普通的以
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的主构造函数中, 可以通过使用let或var修饰形参, 实现在参数列表中定义成员变量
成员变量形参与普通形参的区别, 就只在是否使用let和var修饰
NOTE仓颉中, 主构造函数会在编译时 自动转换为对应的
init构造函数同时要保证 主构造函数对应的
init构造函数, 需要与其他显式定义的init函数构成重载
如果
struct定义中不存在自定义构造函数(包括主构造函数), 并且所有实例成员变量都有初始值, 则会自动为其生成一个无参构造函数(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象); 否则, 不会自动生成此无参构造函数例如, 对于如下
struct定义, 注释中给出了自动生成的无参构造函数:struct Rectangle {let width: Int64 = 10let height: Int64 = 10/* : 自动生成的无参构造函数public init() {}*/}
仓颉struct也可以不显式定义构造函数
仓颉规定, 只要struct的所有成员变量在定义时均有初始值, 那么编译器就会生成一个无参构造函数
struct成员函数
struct成员函数分为实例成员函数和静态成员函数(使用static修饰符修饰), 二者的区别在于:实例成员函数只能通过
struct实例访问, 静态成员函数只能通过struct类型名访问静态成员函数中不能访问实例成员变量, 也不能调用实例成员函数, 但在实例成员函数中可以访问静态成员变量以及静态成员函数
下例中,
area是实例成员函数,typeName是静态成员函数struct Rectangle {let width: Int64 = 10let height: Int64 = 20public func area() {this.width * this.height}public static func typeName(): String {"Rectangle"}}实例成员函数中可以通过
this访问实例成员变量, 例如:struct Rectangle {let width: Int64 = 1let height: Int64 = 1public func area() {this.width * this.height}}
仓颉的成员函数, 拥有实例成员函数和静态成员函数
静态成员函数内, 不能调用实例成员函数和实例成员变量, 且在struct外 只能通过struct名访问
struct成员的访问修饰符
struct的成员(包括成员变量、成员属性、构造函数、成员函数、操作符函数(详见操作符重载章节))用4种访问修饰符修饰:private、internal、protected和public, 缺省的修饰符是internal
private表示在struct定义内可见
internal表示仅当前包及子包(包括子包的子包, 详见包章节)内可见
protected表示当前模块(详见包章节)可见
public表示模块内外均可见下面的例子中,
width是public修饰的成员, 在类外可以访问,height是缺省访问修饰符的成员, 仅在当前包及子包可见, 其他包无法访问package apublic struct Rectangle {public var width: Int64var height: Int64private var area: Int64...}func samePkgFunc() {var r = Rectangle(10, 20)r.width = 8 // Ok, public 'width' 可以在这里访问r.height = 24 // Ok, 'height' 没有显式修饰符, 可以在此处访问r.area = 30 // Error, private 'area' 在此处不可访问}package bimport a.*main() {var r = Rectangle(10, 20)r.width = 8 // Ok, public 'width' 可以在这里访问r.height = 24 // Error, 'height' 没有显式修饰符 在此处不可访问r.area = 30 // Error, private 'area' 在此处不可访问}
仓颉中, 成员默认是被internal修饰的
除此之外, 成员还可以被private、protected和public修饰, 分别表示不同的可访问性:
禁止递归struct
递归和互递归定义的
struct均是非法的例如:
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类型后, 即可通过调用struct的构造函数来创建struct实例在
struct定义之外, 通过struct类型名调用构造函数创建该类型实例, 并可以通过实例访问满足可见性修饰符(如public)的实例成员变量和实例成员函数例如, 下例中定义了一个
Rectangle类型的变量r, 通过r.width和r.height可分别访问r中width和height的值, 通过r.area()可以调用r的成员函数areastruct Rectangle {public var width: Int64public var height: Int64public init(width: Int64, height: Int64) {this.width = widththis.height = height}public func area() {width * height}}let r = Rectangle(10, 20)let width = r.width // width = 10let height = r.height // height = 20let a = r.area() // a = 200
仓颉struct实例, 都是通过对应的构造函数创建的
通过实例, 可以访问对应struct的成员, 通过实例访问成员, 就不是struct内部访问了, 就要满足可访问性修饰符的限制
如果希望通过
struct实例去修改成员变量的值, 需要将struct类型的变量定义为可变变量, 并且被修改的成员变量也必须是可变成员变量(使用var定义)举例如下:
struct Rectangle {public var width: Int64public var height: Int64public init(width: Int64, height: Int64) {this.width = widththis.height = height}public func area() {width * height}}main() {var r = Rectangle(10, 20) // r.width = 10, r.height = 20r.width = 8 // r.width = 8r.height = 24 // r.height = 24let a = r.area() // a = 192}
仓颉struct是值类型数据
对于struct实例, 如果要修改成员变量的值, 需要将struct变量定义为var的, 即 可变变量
在赋值或传参时, 会对
struct实例进行复制(成员变量为引用类型时, 仅复制引用而不会复制引用的对象), 生成新的实例, 对其中一个实例的修改并不会影响另外一个实例以赋值为例, 下面的例子中, 将
r1赋值给r2之后, 修改r1的width和height的值, 并不会影响r2的width和height值struct Rectangle {public var width: Int64public var height: Int64public init(width: Int64, height: Int64) {this.width = widththis.height = height}public func area() {width * height}}main() {var r1 = Rectangle(10, 20) // r1.width = 10, r1.height = 20var r2 = r1 // r2.width = 10, r2.height = 20r1.width = 8 // r1.width = 8r1.height = 24 // r1.height = 24let a1 = r1.area() // a1 = 192let a2 = r2.area() // a2 = 200}
仓颉的struct是值类型的, 在传参或赋值时 会对struct实例进行拷贝
但, 如果成员变量是引用类型, 就只拷贝引用, 而不会拷贝完整的成员变量实例
下面这段代码可以演示:
class RefTest { var num = 0
init(num!: Int64 = 0) { this.num = num }
}
struct ValueTest { let ref: RefTest var num: Int64
init(num: Int64) { this.num = num this.ref = RefTest(num: num) }}
main() { var test1 = ValueTest(20)
let test2 = test1
println("test1: ref.num: ${test1.ref.num}, num: ${test1.num}") println("test2: ref.num: ${test2.ref.num}, num: ${test2.num}")
test1.ref.num = 40 test1.num = 30
println("test1: ref.num: ${test1.ref.num}, num: ${test1.num}") println("test2: ref.num: ${test2.ref.num}, num: ${test2.num}")}这段代码最终会输出:
test1: ref.num: 20, num: 20test2: ref.num: 20, num: 20test1: ref.num: 40, num: 30test2: ref.num: 40, num: 20从结果可以看出, 用struct实例进行赋值, 会发生拷贝, 即let test2 = test1发生拷贝
但, 此时, 如果修改成员变量ref的成员变量, 你会看到两个struct实例同步发生了改变
即 如果struct的成员变量是引用类型的, 那么 此成员变量只会拷贝引用而不是拷贝完整的变量实例
可以认为 struct默认是浅拷贝
mut函数
struct类型是值类型, 其实例成员函数无法修改实例本身例如, 下例中, 成员函数
g中不能修改成员变量i的值struct Foo {var i = 0public func g() {i += 1 // Error, 实例成员函数中不能修改实例成员变量的值}}
mut函数是一种可以修改struct实例本身的特殊的实例成员函数在
mut函数内部,this的语义是特殊的, 这种this拥有原地修改字段的能力注意:
只允许在
interface、struct和struct的扩展内定义mut函数(class是引用类型, 实例成员函数不需要加mut也可以修改实例成员变量, 所以禁止在class中定义mut函数)
仓颉struct是值类型的, 实例成员函数无法修改实例本身
即, 如果在实例成员函数内 尝试修改成员变量, 此行为是被禁止的, 即使目标成员变量是var的
但sturct中可以定义mut函数, 声明函数的可变性
仓颉struct, 被mut修饰的函数, 才可以修改实例成员变量
mut函数定义
mut函数与普通的实例成员函数相比, 多一个mut关键字来修饰例如, 下例中在函数
g之前增加mut修饰符之后, 即可在函数体内修改成员变量i的值struct Foo {var i = 0public mut func g() {i += 1 // Ok}}
mut只能修饰实例成员函数, 不能修饰静态成员函数struct A {public mut func f(): Unit {} // Okpublic mut operator func +(rhs: A): A { // OkA()}public mut static func g(): Unit {} // Error, 静态成员函数不能用 'mut' 修饰}
mut函数中的this不能被捕获, 也不能作为表达式
mut函数中的lambda或嵌套函数, 不能对struct的实例成员变量进行捕获示例:
struct Foo {var i = 0public mut func f(): Foo {let f1 = { => this } // Error, mut 函数中的 'this' 无法被捕获let f2 = { => this.i = 2 } // Error, mut 函数中的 实例成员变量 无法被捕获let f3 = { => this.i } // Error, mut 函数中的 实例成员变量 无法被捕获let f4 = { => i } // Error, mut 函数中的 实例成员变量 无法被捕获this // Error, mut 函数中的 'this' 不能用作表达式}}
mut函数定义时要使用mut关键字修饰
但, 静态成员变量禁止被mut修饰
仓颉规定mut函数中的this以及实例成员变量, 不能被捕获, this也不能被捕获
mut函数内可以修改var成员变量
接口中的mut函数
接口中的实例成员函数, 也可以使用
mut修饰
struct类型在实现interface的函数时必须保持一样的mut修饰
struct以外的类型实现interface的函数时不能使用mut修饰示例:
interface I {mut func f1(): Unitfunc f2(): Unit}struct A <: I {public mut func f1(): Unit {} // Ok: 在interface中, 使用了 'mut' 修饰符public func f2(): Unit {} // Ok: 在interface中, 没有使用 'mut' 修饰符}struct B <: I {public func f1(): Unit {} // Error, 'f1' 在interface中用 'mut' 修饰, 但在struct中没有public mut func f2(): Unit {} // Error, 'f2' 在interface中没有被 'mut' 修饰, 但在struct中被修饰}class C <: I {public func f1(): Unit {} // Okpublic func f2(): Unit {} // Ok}
当interface中定义有mut函数时:
-
如果
struct实现此interface, 那么 实现接口函数也需要使用mut修饰 -
如果
class实现此interface, 那么 实现接口函数不能使用mut修饰
当
struct的实例赋值给interface类型时是拷贝语义, 因此interface的mut函数并不能修改struct实例的值示例:
interface I {mut func f(): Unit}struct Foo <: I {public var v = 0public mut func f(): Unit {v += 1}}main() {var a = Foo()var b: I = ab.f() // 通过'b'调用'f()'不能修改'a'的值println(a.v) // 0}程序输出结果为:
0
拿struct实例 赋值给interface变量, 是拷贝语义
此时, 如果使用interface变量调用mut函数, 也无法修改原struct实例成员
mut函数的使用限制
因为
struct是值类型, 所以如果一个变量是struct类型且使用let声明, 那么不能通过这个变量访问该类型的mut函数示例:
interface I {mut func f(): Unit}struct Foo <: I {public var i = 0public mut func f(): Unit {i += 1}}main() {let a = Foo()a.f() // Error, 'a' 是struct, 并且使用 'let' 声明, 因此不能通过 'a' 访问 'mut' 函数var b = Foo()b.f() // Oklet c: I = Foo()c.f() // Ok, 变量 c 为接口 I 类型, 非 struct 类型, 此处允许访问}
let修饰的struct变量, 无法访问mut成员函数
为避免逃逸, 如果一个变量的类型是
struct类型, 那么这个变量不能将该类型使用mut修饰的函数作为一等公民来使用, 只能调用这些mut函数示例:
interface I {mut func f(): Unit}struct Foo <: I {var i = 0public mut func f(): Unit {i += 1}}main() {var a = Foo()var fn = a.f // Error, mut 函数 'f' 不能作为 'a' 的一等公民使用var b: I = Foo()fn = b.f // Ok}
struct变量只能调用mut成员函数, 不能尝试将mut成员函数作为一等公民使用
为避免逃逸, 非
mut的实例成员函数(包括lambda表达式)不能直接访问所在类型的mut函数, 反之可以示例:
struct Foo {var i = 0public mut func f(): Unit {i += 1g() // Ok}public func g(): Unit {f() // Error, mut 函数不能在非 mut 函数中调用}}interface I {mut func f(): Unit {g() // Ok}func g(): Unit {f() // Error, mut 函数不能在非 mut 函数中调用}}
非mut实例成员函数 禁止直接访问同类型的mut实例成员函数
这是为了防止mut函数逃逸