Iterable 和 Collections

前面已经了解过 Range、Array、ArrayList,它们都可以使用 for-in 进行遍历操作。对于开发者自定义的类型,也能实现类似的遍历操作。

Range、Array、ArrayList 都是通过 Iterable 来支持 for-in 语法的。

Iterable 是如下形式(只展示了核心代码)的一个内置 interface。

interface Iterable<T> {
    func iterator(): Iterator<T>
    ...
}

iterator 函数要求返回的 Iterator 类型是如下形式(只展示了核心代码)的另一个内置 interface。

interface Iterator<T> <: Iterable<T> {
    mut func next(): Option<T>
    ...
}

可以使用 for-in 语法来遍历任何一个实现了 Iterable 接口类型的实例。

假设有这样一段 for-in 代码,如下所示。

let list = [1, 2, 3]
for (i in list) {
    println(i)
}

它等价于如下形式的 while 代码。

let list = [1, 2, 3]
var it = list.iterator()
while (true) {
    match (it.next()) {
        case Some(i) => println(i)
        case None => break
    }
}

另外一种常见的遍历 Iterable 类型的方法是在 while 表达式的条件中使用模式匹配,比如上面 while 代码的另一种等价写法是:

let list = [1, 2, 3]
var it = list.iterator()
while (let Some(i) <- it.next()) {
    println(i)
}

Array、ArrayList、HashSet、HashMap 类型都实现了 Iterable,因此可以将其用在 for-in 或者 while 中。

包的概述

随着项目规模的不断扩大,仅在一个超大文件中管理源代码会变得十分困难。这时可以将源代码根据功能进行分组,并将不同功能的代码分开管理,每组独立管理的代码会生成一个输出文件。在使用时,通过导入对应的输出文件使用相应的功能,或者通过不同功能的交互与组合实现更加复杂的特性,使得项目管理更加高效。

在仓颉编程语言中,包是编译的最小单元,每个包可以单独输出 AST 文件、静态库文件、动态库文件等产物。每个包有自己的名字空间,在同一个包内不允许有同名的顶层定义或声明(函数重载除外)。一个包中可以包含多个源文件。

模块是若干包的集合,是第三方开发者发布的最小单元。一个模块的程序入口只能在其根目录下,它的顶层最多只能有一个作为程序入口的 main ,该 main 没有参数或参数类型为 Array,返回类型为整数类型或 Unit 类型。

包和模块管理

在仓颉编程语言中,包由一个或多个源码文件组成,同一个包的源码文件必须在同一个目录,并且同一个目录里的源码文件只能属于同一个包。包可以定义子包从而构成树形结构。子包的目录是其父包目录的子目录。没有父包的包称为 root 包,root 包及其子包(包括子包的子包)构成的整棵树称为模块。

仓颉程序常见的组织结构如下:

在这里插入图片描述
cjpm.toml 是当前模块所在工作空间的配置文件,用于定义基础信息、依赖项、编译选项等内容。该文件由仓颉语言的官方包管理工具 cjpm 解析和执行。

注意:

对于同一个模块,如果需要为其配置一个有效的包,则该包所在目录必须直接包含至少一个仓颉代码文件,并且其上游目录都需要是有效包。

包的声明

在仓颉编程语言中,包声明以关键字 package 开头,后接 root 包至当前包由 . 分隔路径上所有包的包名。包名必须是合法的普通标识符(不含原始标识符)。例如:

package pkg1      // root 包 pkg1
package pkg1.sub1 // root 包 pkg1 的子包 sub1

注意:

当前 Windows 平台版本,包名暂不支持使用 Unicode 字符,包名必须是一个仅含 ASCII 字符的合法的普通标识符。

包声明必须在源文件的非空非注释的首行,且同一个包中的不同源文件的包声明必须保持一致。

// file 1
// Comments are accepted
package test
// declarations...

// file 2
let a = 1 // Error, package declaration must appear first in a file
package test
// declarations...

仓颉的包名需反映当前源文件相对于项目源码根目录 src 的路径,并将其中的路径分隔符替换为小数点。例如包的源代码位于 src/directory_0/directory_1 下,root 包名为 pkg 则其源代码中的包声明应为 package pkg.directory_0.directory_1。

需要注意的是:

包所在的文件夹名必须与包名一致。
源码根目录默认名为 src。
源码根目录下的包可以没有包声明,此时编译器将默认为其指定包名 default。
假设源代码目录结构如下:

// The directory structure is as follows:
src
`-- directory_0
    |-- directory_1
    |    |-- a.cj
    |    `-- b.cj
    `-- c.cj
`-- main.cj

则 a.cj、b.cj、c.cj、main.cj 中的包声明可以为:

// a.cj
// in file a.cj, the declared package name must correspond to relative path directory_0/directory_1.

package default.directory_0.directory_1
// b.cj
// in file b.cj, the declared package name must correspond to relative path directory_0/directory_1.

package default.directory_0.directory_1
// c.cj
// in file c.cj, the declared package name must correspond to relative path directory_0.

package default.directory_0
// main.cj
// file main.cj is in the module root directory and may omit package declaration.

main() {
    return 0
}

另外,包声明不能引起命名冲突:子包不能和当前包的顶层声明同名。

以下是一些错误示例:

// a.cj
package a
public class B { // Error, 'B' is conflicted with sub-package 'a.B'
    public static func f() {}
}
// b.cj
package a.B
public func f {}

// main.cj
import a.B // ambiguous use of 'a.B'

main() {
    a.B.f()
    return 0
}


顶层声明的可见性

仓颉编程语言中,可以使用访问修饰符来控制对类型、变量、函数等顶层声明的可见性。仓颉语言有 4 种访问修饰符:private、internal、protected、public,在修饰顶层元素时不同访问修饰符的语义如下:

private 表示仅当前文件内可见。不同的文件无法访问这类成员。
internal 表示仅当前包及子包(包括子包的子包)内可见。同一个包内可以不导入就访问这类成员,当前包的子包(包括子包的子包)内可以通过导入来访问这类成员。

protected 表示仅当前模块内可见。同一个包的文件可以不导入就访问这类成员,不同包但是在同一个模块内的其他包可以通过导入访问这些成员,不同模块的包无法访问这些成员。

public 表示模块内外均可见。同一个包的文件可以不导入就访问这类成员,其他包可以通过导入访问这些成员。

在这里插入图片描述

不同顶层声明支持的访问修饰符和默认修饰符(默认修饰符是指在省略情况下的修饰符语义,这些默认修饰符也允许显式写出)规定如下:

package 支持使用 internal、protected、public,默认修饰符为 public。
import 支持使用全部访问修饰符,默认修饰符为 private。
其他顶层声明支持使用全部访问修饰符,默认修饰符为 internal。
package a

private func f1() { 1 }   // f1 仅在当前文件内可见
func f2() { 2 }           // f2 仅当前包及子包内可见
protected func f3() { 3 } // f3 仅当前模块内可见
public func f4() { 4 }    // f4 当前模块内外均可见

仓颉的访问级别排序为 public > protected > internal > private。一个声明的访问修饰符不得高于该声明中用到的类型的访问修饰符的级别,参考如下示例:

函数声明中的参数与返回值

// a.cj
package a
class C {}
public func f1(a1: C) // Error, public declaration f1 cannot use internal type C.
{
    return 0
}
public func f2(a1: Int8): C // Error, public declaration f2 cannot use internal type C.
{
    return C()
}
public func f3 (a1: Int8) // Error, public declaration f3 cannot use internal type C.
{
    return C()
}

变量声明

// a.cj
package a
class C {}
public let v1: C = C() // Error, public declaration v1 cannot use internal type C.
public let v2 = C() // Error, public declaration v2 cannot use internal type C.

泛型类型的类型实参

// a.cj
package a
public class C1<T> {}
class C2 {}
public let v1 = C1<C2>() // Error, public declaration v1 cannot use internal type C2.

where 约束中的类型上界

// a.cj
package a
interface I {}
public class B<T> where T <: I {}  // Error, public declaration B cannot use internal type I.

值得注意的是:

public 修饰的声明在其初始化表达式或者函数体里面可以使用本包可见的任意类型,包括被 public 修饰的类型和不被 public 修饰的类型。

// a.cj
package a
class C1 {}
func f1(a1: C1)
{
  return 0
}
public func f2(a1: Int8) // Ok.
{
  var v1 = C1()
  return 0
}
public let v1 = f1(C1()) // Ok.
public class C2 // Ok.
{
 var v2 = C1()
}

public 修饰的顶层声明能使用匿名函数,或者任意顶层函数,包括被 public 修饰的类型和不被 public 修饰的顶层函数。

public var t1: () -> Unit = { => } // Ok.
func f1(): Unit {}
public let t2 = f1 // Ok.

public func f2() // Ok.
{
  return f1
}

内置类型诸如 Rune 和 Int64 等默认的修饰符是 public。

var num = 5
public var t3 = num // Ok.

注意:

同一个包内,private 修饰的同名自定义类型(如 struct、class、enum 和 interface 等),在某些场景下不支持,不支持场景由编译器进行报错。

例如在以下程序中,example1.cj 与 example2.cj文件包名相同,在 example1.cj 文件中定义了 private 修饰的类 A, 在 example2.cj 文件中定义了 private 修饰的结构体 A。

// example1.cj
package test

private class A {}

public class D<T> {
    private let a: A = A()
}
// example2.cj
package test

private struct A {}

public class C<T> {
    private let a: A = A()
}

运行以上程序,将输出:

error: currently, it is not possible to export two private declarations with the same name

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐