仓颉之包 2024-09-02 周一

包的概述

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

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

包的声明

  • 在仓颉编程语言中,包声明以关键字 package 开头,后接 root 包至当前包由 . 分隔路径上所有包的包名。包名必须是合法的普通标识符(不含原始标识符)。
package pkg1      // root 包 pkg1
package pkg1.sub1 // root 包 pkg1 的子包 sub1
  • 包声明必须在源文件的非空非注释的首行,且同一个包中的不同源文件的包声明必须保持一致。
// 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。
  1. 包所在的文件夹名必须与包名一致。
  2. 源码根目录默认名为 src。
  3. 源码根目录下的包可以没有包声明,此时编译器将默认为其指定包名 default。
// The directory structure is as follows:
src
`-- directory_0
    |-- directory_1
    |    |-- 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,在修饰顶层元素时不同访问修饰符的语义如下。
  1. private 表示仅当前文件内可见。不同的文件无法访问这类成员。

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

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

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

  • 不同顶层声明支持的访问修饰符和默认修饰符(默认修饰符是指在省略情况下的修饰符语义,这些默认修饰符也允许显式写出)规定如下:
  1. pacakge 支持使用 internal、protected、public,默认修饰符为 public。

  2. import 支持使用全部访问修饰符,默认修饰符为 private。

  3. 其他顶层声明支持使用全部访问修饰符,默认修饰符为 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。一个声明的访问修饰符不得高于该声明中用到的类型的访问修饰符的级别
  1. 函数声明中的参数与返回值
// 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()
}
  1. 变量声明
// 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.
  1. 类声明中继承的类
// a.cj
package a
open class C1 {}
public class C2 <: C1 {} // Error, public declaration C2 cannot use internal type C1.
  1. 类型实现的接口
// a.cj
package a
interface I {}
public enum E <: I { A } // Error, public declaration uses internal types.
  1. 泛型类型的类型实参
// a.cj
package a
public class C1<T> {}
class C2 {}
public let v1 = C1<C2>() // Error, public declaration v1 cannot use internal type C2.
  1. 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.

包的导入

使用 import 语句导入其它包中的声明或定义

  • 在仓颉编程语言中,可以通过 import fullPackageName.itemName 的语法导入其他包中的一个顶层声明或定义,fullPackageName 为完整路径包名,itemName 为声明的名字。导入语句在源文件中的位置必须在包声明之后,其他声明或定义之前。
package a
import std.math.*
import package1.foo
import {package1.foo, package2.bar}
  • 如果要导入的多个 itemName 同属于一个 fullPackageName,可以使用 import fullPackageName.{itemName[, itemName]*} 语法
import package1.{foo, bar, fuzz}

等价于

import package1.foo
import package1.bar
import package1.fuzz
  • 除了通过 import fullPackagename.itemName 语法导入一个特定的顶层声明或定义外,还可以使用 import packageName.* 语法将 packageName 包中所有可见的顶层声明或定义全部导入。
import package1.*
import {package1.*, package2.*}
  1. import 可以被 private、internal、protected、public 访问修饰符修饰。不写访问修饰符的 import 等价于 private import。

  2. 导入的成员的作用域级别低于当前包声明的成员。

  3. 当已导出的包的模块名或者包名被篡改,使其与导出时指定的模块名或包名不一致,在导入时会报错。

  4. 只允许导入当前文件可见的顶层声明或定义,导入不可见的声明或定义将会在导入处报错。

  5. 禁止通过 import 导入当前源文件所在包的声明或定义。

  6. 禁止包间的循环依赖导入,如果包之间存在循环依赖,编译器会报错。

// pkga/a.cj
package pkga    // Error, packages pkga pkgb are in circular dependencies.
import pkgb.*

class C {}
public struct R {}

// pkgb/b.cj
package pkgb

import pkga.*

// pkgc/c1.cj
package pkgc

import pkga.C // Error, 'C' is not accessible in package 'pkga'.
import pkga.R // OK, R is an external top-level declaration of package pkga.
import pkgc.f1 // Error, package 'pkgc' should not import itself.

public func f1() {}

// pkgc/c2.cj
package pkgc

func f2() {
    /* OK, the imported declaration is visible to all source files of the same package
     * and accessing import declaration by its name is supported.
     */
    R()

    // OK, accessing imported declaration by fully qualified name is supported.
    pkga.R()

    // OK, the declaration of current package can be accessed directly.
    f1()

    // OK, accessing declaration of current package by fully qualified name is supported.
    pkgc.f1()
}
  • 在仓颉编程语言中,导入的声明或定义如果和当前包中的顶层声明或定义重名且不构成函数重载,则导入的声明和定义会被遮盖;导入的声明或定义如果和当前包中的顶层声明或定义重名且构成函数重载,函数调用时将会根据函数重载的规则进行函数决议。
// pkga/a.cj
package pkga

public struct R {}            // R1
public func f(a: Int32) {}    // f1
public func f(a: Bool) {} // f2

// pkgb/b.cj
package pkgb
import pkga.*

func f(a: Int32) {}         // f3
struct R {}                 // R2

func bar() {
    R()     // OK, R2 shadows R1.
    f(1)    // OK, invoke f3 in current package.
    f(true) // OK, invoke f2 in the imported package
}

隐式导入 core 包

诸如 String、Range 等类型能直接使用,并不是因为这些类型是内置类型,而是因为编译器会自动为源码隐式的导入 core 包中所有的 public 修饰的声明。

使用 import as 对导入的名字重命名

  • 不同包的名字空间是分隔的,因此在不同的包之间可能存在同名的顶层声明。在导入不同包的同名顶层声明时,我们支持使用 import packageName.name as newName 的方式进行重命名来避免冲突。没有名字冲突的情况下仍然可以通过 import as 来重命名导入的内容。
  1. 使用 import as 对导入的声明进行重命名后,当前包只能使用重命名后的新名字,原名无法使用。

  2. 如果重命名后的名字与当前包顶层作用域的其它名字存在冲突,且这些名字对应的声明均为函数类型,则参与函数重载,否则报重定义的错误。

  3. 支持 import pkg as newPkgName 的形式对包名进行重命名,以解决不同模块中同名包的命名冲突问题。

// a.cj
package p1
public func f1() {}

// d.cj
package p2
public func f3() {}

// b.cj
package p1
public func f2() {}

// c.cj
package pkgc
public func f1() {}

// main.cj
import p1 as A
import p1 as B
import p2.f3 as f  // OK
import pkgc.f1 as a
import pkgc.f1 as b // OK

func f(a: Int32) {}

main() {
    A.f1()  // OK, package name conflict is resolved by renaming package name.
    B.f2()  // OK, package name conflict is resolved by renaming package name.
    p1.f1() // Error, the original package name cannot be used.
    a()     // Ok.
    b()     // Ok.
    pkgc.f1()    // Error, the original name cannot be used.
}
  • 如果没有对导入的存在冲突的名字进行重命名,在 import 语句处不报错;在使用处,会因为无法导入唯一的名字而报错。这种情况可以通过 import as 定义别名或者 import fullPackageName 导入包作为命名空间。
// a.cj
package p1
public class C {}

// b.cj
package p2
public class C {}

// main1.cj
package pkga
import p1.C
import p2.C

main() {
    let _ = C() // Error
}

// main2.cj
package pkgb
import p1.C as C1
import p2.C as C2

main() {
    let _ = C1() // Ok
    let _ = C2() // Ok
}

// main3.cj
package pkgc
import p1
import p2

main() {
    let _ = p1.C() // Ok
    let _ = p2.C() // Ok
}

重导出一个导入的名字

在仓颉编程语言中,import 可以被 private、internal、protected、public 访问修饰符修饰。其中,被 public、protected 或者 internal 修饰的 import 可以把导入的成员重导出(如果这些导入的成员没有因为名称冲突或者被遮盖导致在本包中不可用)。其它包可以根据可见性直接导入并使用本包中用重导出的内容,无需从原包中导入这些内容。

  • private import 表示导入的内容仅当前文件内可访问,private 是 import 的默认修饰符,不写访问修饰符的 import 等价于 private import。

  • internal import 表示导入的内容在当前包及其子包(包括子包的子包)均可访问。非当前包访问需要显式 import。

  • protected import 表示导入的内容在当前 module 内都可访问。非当前包访问需要显式 import。

  • public import 表示导入的内容外部都可访问。非当前包访问需要显式 import。

package a

public let x = 0
public import a.b.f
internal package a.b

public func f() { 0 }
import a.f  // Ok
let _ = f() // Ok
  • 包不可以被重导出:如果被 import 导入的是包,那么该 import 不允许被 public、protected 或者 internal 修饰。
public import a.b // Error, cannot re-export package

程序入口

  • 仓颉程序入口为 main,源文件根目录下的包的顶层最多只能有一个 main。

  • 如果模块采用生成可执行文件的编译方式,编译器只在源文件根目录下的顶层查找 main。如果没有找到,编译器将会报错;如果找到 main,编译器会进一步对其参数和返回值类型进行检查。需要注意的是,main 不可被访问修饰符修饰,当一个包被导入时,包中定义的 main 不会被导入。

  • 作为程序入口的 main 可以没有参数或参数类型为 Array<String>,返回值类型为 Unit 或整数类型。

// main.cj
main(): Int64 { // Ok.
    return 0
}
// main.cj
main(args: Array<String>): Unit { // Ok.
    for (arg in args) {
        println(arg)
    }
}

/* 使用 cjc main.cj 编译完成后,通过命令行执行:./main Hello, World,将会得到如下输出:
Hello,
World
*/
// main.cj
main(): String { // Error, return type of 'main' is not 'Integer' or 'Unit'.
    return ""
}
// main.cj
main(args: Array<Int8>): Int64 { // Error, 'main' cannot be defined with parameter whose type is not Array<String>.
    return 0
}
// main.cj
// Error, multiple 'main's are found in source files.
main(args: Array<String>): Int32 {
    return 0
}

main(): Int8 {
    return 0
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容