1、类
class 类型是面向对象编程中的经典概念,仓颉中同样支持使用 class 来实现面向对象编程。class 与 struct 的主要区别
在于:class 是引用类型,struct 是值类型,它们在赋值或传参时行为是不同的;class 之间可以继承,但 struct 之间不能继承。
本节依次介绍如何定义 class 类型,如何创建对象,以及 class 的继承。
1.1 class 定义
class
类型的定义以关键字 class 开头
,后跟 class 的名字
,接着是定义在一对花括号中的 class 定义体
。class 定义体
中可以定义一系列的成员变量
、成员属性
(参见属性)、静态初始化器
、构造函数
、成员函数
和操作符函数
(详见操作符重载章节))。
class 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 的 class 类型,它有两个 Int64 类型的成员变量 width 和 height,一个有两个 Int64 类型参数的构造函数,以及一个成员函数 area(返回 width 和 height 的乘积)。
class 只能定义在源文件顶层。
1.1.1 class 成员变量
class 成员变量分为实例成员变量
和静态成员变量
,静态成员变量使用 static 修饰符修饰,必须有初值,只能通过类型名访问,参考如下示例:
class Rectangle {
let width = 10
static let height = 20
}
let l = Rectangle.height // l = 20
实例成员变量定义时可以不设置初值(但必须标注类型),也可以设置初值,只能通过对象(即类的实例)访问,参考如下示例:
class Rectangle {
let width = 10
let height: Int64
init(h: Int64){
height = h
}
}
let rec = Rectangle(20)
let l = rec.height // l = 20
1.1.2 class 静态初始化器
class 支持定义静态初始化器
,并在静态初始化器中通过赋值表达式来对静态成员变量进行初始化
。
静态初始化器以关键字组合 static init
开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。
class Rectangle {
static let degree: Int64
static init() {
degree = 180
}
}
一个 class 中最多允许定义一个静态初始化器,否则报重定义错误。
class Rectangle {
static let degree: Int64
static init() {
degree = 180
}
static init() {
// Error, redefinition with the previous static init function
degree = 180
}
}
1.1.3 class 构造函数
和 struct 一样,class 中也支持定义普通构造函数和主构造函数。
普通构造函数以关键字 init
开头,后跟参数列表和函数体,函数体中必须完成所有未初始化实例成员变量的初始化
,否则编译报错。
class Rectangle {
let width: Int64
let height: Int64
public init(width: Int64, height: Int64) {
// Error, 'height' is not initialized in the constructor
this.width = width
}
}
一个类中可以定义多个普通构造函数
,但它们必须构成重载
(参见函数重载),否则报重定义错误。
class 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 为名字的构造函数外,class 内还可以定义(最多)一个主构造函数
。主构造函数的名字
和 class 类型名相同,它的参数列表中可以有两种形式的形参
:普通形参和成员变量形参(需要在参数名前加上 let
或 var
),成员变量形参同时具有定义成员变量和构造函数参数的功能。
使用主构造函数通常可以简化 class 的定义,例如,上述包含一个 init 构造函数的 Rectangle 可以简化为如下定义:
class Rectangle {
public Rectangle(let width: Int64, let height: Int64) {
}
}
主构造函数的参数列表中也可以定义普通形参,例如:
class Rectangle {
public Rectangle(name: String, let width: Int64, let height: Int64) {
}
}
如果 class 定义中不存在自定义构造函数(包括主构造函数)
,并且所有实例成员变量都有初值,则会自动为其生成一个无参构造函数
(调用此无参构造函数会创建一个所有实例成员变量的值均等于其初值的对象);否则,不会自动生成此无参构造函数。例如,对于如下 class 定义,编译器会为其自动生成一个无参构造函数:
class Rectangle {
let width = 10
let height = 20
/* Auto-generated parameterless constructor:
public init() {
}
*/
}
// Invoke the auto-generated parameterless constructor
let r = Rectangle() // r.width = 10,r.height = 20
1.1.4 class 终结器
class 支持定义终结器
,这个函数在类的实例被垃圾回收的时候被调用
。终结器的函数名固定为 ~init
。终结器一般被用于释放系统资源:
class C {
var p: CString
init(s: String) {
p = unsafe {
LibC.mallocCString(s) }
println(s)
}
~init() {
unsafe {
LibC.free(p) }
}
}
使用终结器有些限制条件,需要开发者注意:
- 终结器没有参数,没有返回类型,没有泛型类型参数,没有任何修饰符,也不可以被显式调用。
- 带有终结器的类不可被 open 修饰,只有非 open 的类可以拥有终结器。
- 一个类最多只能定义一个终结器。
- 终结器不可以定义在扩展中。
- 终结器被触发的时机是不确定的。
- 终结器可能在任意一个线程上执行。
- 多个终结器的执行顺序是不确定的。
- 终结器向外抛出未捕获异常属于未定义行为。
- 终结器中创建线程或者使用线程同步功能属于未定义行为。
- 终结器执行结束之后,如果这个对象还可以被继续访问,则属于未定义行为。
1.1.5 class 成员函数
class 成员函数同样分为实例成员函数
和静态成员函数
(使用 static
修饰符修饰),实例成员函数只能通过对象访问,静态成员函数只能通过 class 类型名访问;静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。
下例中,area 是实例成员函数,typeName 是静态成员函数。
class Rectangle {
let width: Int64 = 10
let height: Int64 = 20
public func area() {
this.width * this.height
}
public static func typeName(): String {
"Rectangle"
}
}
根据有没有函数体,实例成员函数又可以分为抽象成员函数
和非抽象成员函数
。抽象成员函数没有函数体,只能定义在抽象类或接口(详见接口章节)中。例如,下例中在抽象类 AbRectangle(使用关键字 abstract
修饰)中定义了抽象函数 foo。
abstract class AbRectangle {
public func foo(): Unit
}
需要注意的是,抽象实例成员函数默认具有 open
的语义,open
修饰符是可选的,且必须使用 public
或 protected
进行修饰。
非抽象函数必须有函数体,在函数体中可以通过 this 访问实例成员变量
,例如:
class Rectangle {
let width: Int64 = 10
let height: Int64 = 20
public func area() {
this.width * this.height
}
}
1.1.6 class 成员的访问修饰符
对于 class 的成员(包括成员变量
、成员属性
、构造函数
、成员函数
),可以使用的访问修饰符有 4 种访问修饰符修饰:private
、internal
、protected
和 public
,缺省的含义是 internal
。
private
表示在 class 定义内可见。internal
表示仅当前包及子包(包括子包的子包,详见包章节)内可见。protected
表示当前模块(详见包章节)及当前类的子类可见。public
表示模块内外均可见。
package a
public open class Rectangle {
public var width: Int64
protected var height: Int64
private var area: Int64
public init(width: Int64, height: Int64) {
this.width = width
this.height = height
this.area = this.width * this.height
}
init(width: Int64, height: Int64, multiple: Int64) {
this.width = width
this.height = height
this.area = width * height * multiple
}
}
func samePkgFunc() {
var r = Rectangle(10, 20) // Ok: constructor 'Rectangle' can be accessed here
r.width = 8 // Ok: public 'width' can be accessed here
r.height = 24 // Ok: protected 'height' can be accessed here
r.area = 30 // Error, private 'area' cannot be accessed here
}
package b
import a.*
public class Cuboid <: Rectangle {
private var length: Int64
public init(width: Int64, height: Int64, length: Int64) {
super(width, height)
this.length = length
}
public func volume() {
this.width * this.height * this.length // Ok: protected 'height' can be accessed here
}
}
main() {
var r = Rectangle(10, 20, 2) // Error, Rectangle has no `public` constructor with three parameters
var c = Cuboid(20, 20, 20)
c.width = 8 // Ok: public 'width' can be accessed here
c.height = 24 // Error, protected 'height' cannot be accessed here
c.area = 30 // Error, private 'area' cannot be accessed here
}
1.2 This 类型
在类内部,我们支持 This
类型占位符,代指当前类的类型。它只能被作为实例成员函数的返回类型来使用
,当使用子类对象调用在父类中定义的返回 This 类型的函数时,该函数调用的类型会被识别为子类类型,而非定义所在的父类类型。
如果实例成员函数没有声明返回类型,并且只存在返回 This
类型表达式时,当前函数的返回类型会推断为 This
。示例如下:
open class C1 {
func f(): This {
// its type is `() -> C1`
return this
}
func f2() {
// its type is `() -> C1`
return this
}
public open func f3(): C1 {
return this
}
}
class C2 <: C1 {
// member function f is inherited from C1, and its type is `() -> C2` now
public override func f3(): This {
// ok
return this
}
}
var obj1: C2 = C2()
var obj2: C1 = C2()
var x = obj1.f() // During compilation, the type of x is C2
var y = obj2.f() // During compilation, the type of y is C1
1.3 创建对象
定义了 class 类型后,即可通过调用其构造函数来创建对象(通过 class 类型名调用构造函数
)。例如,下例中通过 Rectangle(10, 20) 创建 Rectangle 类型的对象并赋值给变量 r。
let r = Rectangle(10, 20)
创建对象之后,可以通过对象访问(public
修饰的)实例成员变量和实例成员函数。例如,下例中通过 r.width 和 r.height 可分别访问 r 中 width 和 height 的值,通过 r.area() 可以调用成员函数 area。
let r = Rectangle(10, 20) // r.width = 10, r.height = 20
let width = r.width // width = 10
let height = r.height // height = 20
let a = r.area() // a = 200
如果希望通过对象去修改成员变量的值(不鼓励这种方式,最好还是通过成员函数去修改
),需要将 class 类型中的成员变量定义为可变成员变量(即使用 var
定义)。举例如下:
lass Rectangle {
public var width: Int64
public var height: Int64
...
}
main() {
let r = Rectangle(10, 20) // r.width = 10, r.height =