概述
在仓颉编程语言中,包(Package)是编译的最小单元,也是代码组织和模块化的核心概念。随着项目规模的不断扩大,仅在一个超大文件中管理源代码会变得十分困难。包系统允许我们将源代码根据功能进行分组,并将不同功能的代码分开管理,每组独立管理的代码会生成一个输出文件。
核心概念
包 vs 模块
- 包(Package):编译的最小单元,每个包可以单独输出 AST 文件、静态库文件、动态库文件等产物
- 模块(Module):若干包的集合,是第三方开发者发布的最小单元
包的特性
- 每个包有自己的名字空间
- 在同一个包内不允许有同名的顶层定义或声明(函数重载除外)
- 一个包中可以包含多个源文件
- 模块的程序入口只能在其根目录下
包声明(Package Declaration)
基本语法
包声明以关键字 package
开头,后接 root 包至当前包由 .
分隔路径上所有包的包名:
cangjie
package pkg1 // root 包 pkg1
package pkg1.sub1 // root 包 pkg1 的子包 sub1
重要规则
- 包声明位置:必须在源文件的非空非注释的首行
- 包名一致性:同一个包中的不同源文件的包声明必须保持一致
- 包名限制:当前 Windows 平台版本,包名暂不支持使用 Unicode 字符,必须仅含 ASCII 字符
包名与目录结构的关系
包名需反映当前源文件相对于项目源码根目录 src
的路径,并将其中的路径分隔符替换为小数点。
示例目录结构:
cangjie
src
`-- directory_0
|-- directory_1
| |-- a.cj
| `-- b.cj
`-- c.cj
`-- main.cj
对应的包声明:
cangjie
// a.cj - 位于 src/directory_0/directory_1/
package default.directory_0.directory_1
// 定义一些函数
public func calculate(a: Int32, b: Int32): Int32 {
return a + b
}
public func multiply(a: Int32, b: Int32): Int32 {
return a * b
}
cangjie
// b.cj - 位于 src/directory_0/directory_1/
package default.directory_0.directory_1
import std.math.sqrt
// 定义一些类型
public struct Point {
public let x: Float64
public let y: Float64
public init(x: Float64, y: Float64) {
this.x = x
this.y = y
}
public func distance(other: Point): Float64 {
let dx = this.x - other.x
let dy = this.y - other.y
return sqrt(dx * dx + dy * dy)
}
}
cangjie
// c.cj - 位于 src/directory_0/
package default.directory_0
import std.convert.Formattable
// 定义一些常量
public let PI: Float64 = 3.14159265359
public let E: Float64 = 2.71828182846
public func formatNumber(num: Float64): String {
return "${num.format(".2")}"
}
main() {
println(formatNumber(2.23424))
}
cangjie
// main.cj - 位于 src/ 根目录,可以省略包声明
import default.directory_0.directory_1.calculate
import default.directory_0.directory_1.multiply
import default.directory_0.directory_1.Point
import default.directory_0.PI
import default.directory_0.formatNumber
main(): Int32 {
// 使用导入的函数
let result1 = calculate(10, 20)
let result2 = multiply(5, 6)
// 使用导入的类型
let p1 = Point(x: 0.0, y: 0.0)
let p2 = Point(x: 3.0, y: 4.0)
let distance = p1.distance(to: p2)
// 使用导入的常量
let area = PI * 5.0 * 5.0
// 使用导入的函数
let formatted = formatNumber(area)
println("计算结果:")
println("10 + 20 = \(result1)")
println("5 * 6 = \(result2)")
println("两点距离:\(distance)")
println("圆的面积:\(formatted)")
return 0
}
包名冲突避免
子包不能和当前包的顶层声明同名,避免命名冲突:
cangjie
// ❌ 错误示例 - 包名冲突
package cangjie_blog.math
public class Vector { // Error: 'Vector' 与子包 'math.Vector' 冲突
public static func create(): Vector {
return Vector()
}
}
cangjie
// ✅ 正确示例 - 避免包名冲突
package cangjie_blog.math.Vector
public class MathVector { // 使用不同的名称避免冲突
public static func create(): MathVector {
return MathVector()
}
}
程序入口(Program Entry)
main 函数规则
仓颉程序入口为 main
,源文件根目录下的包的顶层最多只能有一个 main
。
main 函数签名
- 无参数的 main:
cangjie
// main.cj
main(): Int64 {
println("Hello, Cangjie!")
return 0
}
- 带命令行参数的 main:
cangjie
// main.cj
package cangjie_blog
import std.collection.enumerate
main(args: Array<String>): Unit {
println("程序启动,参数数量:${args.size}")
for ((i, arg) in enumerate(args)) {
println("参数 ${i}: ${arg}")
}
// 处理命令行参数
if (args.size > 0) {
let firstArg = args[0]
if (firstArg == "--help") {
println("使用方法:./main [选项]")
println("选项:")
println(" --help 显示帮助信息")
println(" --version 显示版本信息")
} else if (firstArg == "--version") {
println("仓颉语言程序 v1.0.0")
}
}
}
main 函数限制
- 不可被访问修饰符修饰
- 返回值类型必须为
Unit
或整数类型 - 参数类型必须为
Array<String>
或无参数 - 当包被导入时,包中定义的
main
不会被导入
错误示例
cangjie
// ❌ 错误:返回值类型不正确
main(): String {
return "Hello" // Error: return type of 'main' is not 'Integer' or 'Unit'
}
// ❌ 错误:参数类型不正确
main(args: Array<Int32>): Int64 {
return 0 // Error: 'main' cannot be defined with parameter whose type is not Array<String>
}
// ❌ 错误:多个 main 函数
main(args: Array<String>): Int32 {
return 0
}
main(): Int8 { // Error: multiple 'main's are found in source files
return 0
}
包的导入(Package Import)
基本导入语法
1. 导入单个项目
cangjie
package cangjie_blog
import std.math.sqrt
const PI: Float64 = 3.14159
main(): Int32 {
let result = sqrt(16.0)
let area = PI * 5.0 * 5.0
println("√16 = ${result}")
println("圆的面积:${area}")
return 0
}
2. 导入多个项目
cangjie
package cangjie_blog
import std.math.{sqrt, abs}
import std.collection.max
main(): Int32 {
let numbers = [3.14, -2.5, 10.0, -7.8]
for (num in numbers) {
let absValue = abs(num)
println("|${num}| = ${absValue}")
}
let maxValue = max(numbers)
println("最大值:${maxValue}")
return 0
}
3. 导入整个包
cangjie
package cangjie_blog
import std.math.*
const PI = 3.1415926
main(): Int32 {
// 可以直接使用 math 包中的所有公共函数和常量
let result = sqrt(25.0)
let sinValue = sin(PI / 2.0f64)
let cosValue = cos(0.0)
println("√25 = ${result}")
println("sin(π/2) = ${sinValue}")
println("cos(0) = ${cosValue}")
return 0
}
导入规则和限制
1. 导入位置
导入语句必须在包声明之后,其他声明或定义之前:
cangjie
package cangjie_blog
// ✅ 正确:导入语句在包声明之后
import std.math.*
// 其他代码...
public func processData(): Unit {
// 函数实现
}
2. 循环依赖禁止
包之间不能存在循环依赖:
cangjie
// ❌ 错误:循环依赖
package cangjie_blog.pkga
import cangjie_blog.pkgb.* // Error: packages pkga pkgb are in circular dependencies
public class ClassA {
public func method(): Unit {}
}
package cangjie_blog.pkgb
import cangjie_blog.pkga.* // Error: packages pkga pkgb are in circular dependencies
public class ClassB {
public func method(): Unit {}
}
3. 不能导入当前包
cangjie
// ❌ 错误:不能导入当前包
package cangjie_blog
import cangjie_blog.someFunction // Error: packages cangjie_blog are in circular dependencies
public func someFunction(): Unit {}
main() {
}
导入冲突解决
1. 使用 import as 重命名
cangjie
2. 使用包名作为命名空间
cangjie
// 解决不同包中同名类型的冲突
package cangjie_blog
// 从不同包导入同名的类,这里还有一个问题,包名不规范,包名最好都是小写
import cangjie_blog.geometry.Vector as GeoVector
import cangjie_blog.math.Vector as MathVector
main(): Int32 {
// 使用重命名后的类型
let geoVec = GeoVector()
let mathVec = MathVector()
return 0
}
隐式导入
编译器会自动为源码隐式导入 core
包中所有的 public
修饰的声明,这就是为什么 String
、Range
等类型能直接使用的原因。
重导出(Re-export)
访问修饰符修饰的 import
import
可以被访问修饰符修饰,实现重导出功能:
cangjie
// 包 a 重导出包 b 中的函数
package cangjie_blog.a
public import cangjie_blog.a.b.{calculate, multiply}
public let x = 0
cangjie
// 包 a.b 中定义函数
internal package a.b
public func calculate(a: Int32, b: Int32): Int32 {
return a + b
}
public func multiply(a: Int32, b: Int32): Int32 {
return a * b
}
cangjie
// 其他包可以直接导入 a 中的函数,无需从 a.b 导入
package cangjie_blog.imp
import cangjie_blog.a.{calculate, multiply}
main(): Int32 {
let result1 = calculate(10, 20)
let result2 = multiply(5, 6)
println("10 + 20 = ${result1}")
println("5 * 6 = ${result2}")
return 0
}
访问修饰符级别
修饰符 | 含义 | 默认值 |
---|---|---|
private |
仅当前文件内可访问 | import 的默认值 |
internal |
当前包及其子包可访问 | 其他声明的默认值 |
protected |
当前模块内可访问 | - |
public |
模块内外均可访问 | package 的默认值 |
重导出限制
包不可以被重导出:
cangjie
// ❌ 错误:不能重导出包
// imported package name 'cangjie_blog.a.b' cannot be modified by 'public'
public import cangjie_blog.a.b
顶层声明的可见性(Top-level Access Control)
访问修饰符详解
1. private - 文件级可见性
cangjie
package cangjie_blog.visibility
// private 修饰的函数仅在当前文件内可见
private func internalHelper(): String {
return "这是内部辅助函数"
}
// 其他函数可以调用 private 函数
public func publicFunction(): String {
return "公共函数调用:" + internalHelper()
}
// 但是其他文件无法访问 internalHelper
2. internal - 包级可见性
cangjie
package cangjie_blog.visibility
// internal 修饰的函数在当前包及子包内可见
internal func packageHelper(): String {
return "这是包级辅助函数"
}
// 同一包内的其他函数可以调用
public func anotherFunction(): String {
return "另一个函数:" + packageHelper()
}
cangjie
// 子包中可以导入并使用
package cangjie_blog.visibility.sub
import cangjie_blog.visibility.packageHelper
public func subPackageFunction(): String {
return "子包函数:" + packageHelper() // ✅ 可以访问
}
3. protected - 模块级可见性
cangjie
package cangjie_blog.visibility
// protected 修饰的函数在当前模块内可见
protected func moduleHelper(): String {
return "这是模块级辅助函数"
}
// 同一模块内的其他包可以导入使用
cangjie
// 同一模块内的其他包
package cangjie_blog.visibilitytest
import cangjie_blog.visibility.moduleHelper
// 'packageHelper' is not accessible in package 'cangjie_blog.visibility'
// import cangjie_blog.visibility.packageHelper
// 'internalHelper' is not accessible in package 'cangjie_blog.visibility'
// import cangjie_blog.visibility.internalHelper
public func otherFunction(): String {
return "其他包函数:" + moduleHelper() // ✅ 可以访问
}
4. public - 全局可见性
cangjie
package cangjie_blog.visibility
// public 修饰的函数在所有地方都可见
public func globalFunction(): String {
return "这是全局可见的函数"
}
// 可以被任何包导入使用
cangjie
// 任何其他包都可以导入
package cangjie_blog.visibilitytest
// import cangjie_blog.visibility.moduleHelper
// 'packageHelper' is not accessible in package 'cangjie_blog.visibility'
// import cangjie_blog.visibility.packageHelper
// 'internalHelper' is not accessible in package 'cangjie_blog.visibility'
// import cangjie_blog.visibility.internalHelper
import cangjie_blog.visibility.globalFunction
// public func otherFunction(): String {
// return "其他包函数:" + moduleHelper() // ✅ 可以访问
// }
public func externalFunction(): String {
return "外部包函数:" + globalFunction() // ✅ 可以访问
}
访问级别规则
1. 访问级别排序
public > protected > internal > private
2. 声明访问级别限制
一个声明的访问修饰符不得高于该声明中用到的类型的访问修饰符的级别:
cangjie
package cangjie_blog.access
// internal 类
class InternalClass {
public func method(): Unit {}
}
// ❌ 错误:public 函数不能使用 internal 类型
// 'public' declaration uses lower access level types
// public func publicFunction(param: InternalClass): InternalClass {
// return param // Error: public declaration cannot use internal type
// }
// ✅ 正确:internal 函数可以使用 internal 类型
internal func internalFunction(param: InternalClass): InternalClass {
return param
}
// ✅ 正确:public 函数可以使用 public 类型
public func anotherPublicFunction(param: String): String {
return param
}
3. 泛型类型约束
cangjie
package cangjie_blog.access
// internal 接口
interface InternalInterface {
func method(): Unit
}
// ❌ 错误:public 泛型类不能使用 internal 接口作为约束
// 'public' declaration uses 'internal' types
// public class PublicGeneric<T> where T <: InternalInterface {
// // Error: public declaration cannot use internal type
// }
// ✅ 正确:internal 泛型类可以使用 internal 接口
internal class InternalGeneric<T> where T <: InternalInterface {
// 实现...
}
特殊情况
1. public 声明的内部实现
public
修饰的声明在其初始化表达式或者函数体里面可以使用本包可见的任意类型:
cangjie
package access
// internal 类
class InternalClass {
public func method(): String {
return "internal method"
}
}
// internal 函数
internal func internalFunction(): String {
return "internal function"
}
// ✅ public 函数内部可以使用 internal 类型
public func publicFunction(): String {
let obj = InternalClass() // ✅ 可以创建 internal 类型实例
let result = internalFunction() // ✅ 可以调用 internal 函数
return obj.method() + " + " + result
}
// ✅ public 变量可以使用 internal 类型
public let publicVariable: String = internalFunction() // ✅ 可以调用 internal 函数
// ✅ public 类内部可以使用 internal 类型
public class PublicClass {
private let internalObj = InternalClass() // ✅ 可以创建 internal 类型实例
public func method(): String {
return internalObj.method() // ✅ 可以调用 internal 方法
}
}
2. 内置类型默认访问级别
内置类型诸如 Rune
和 Int64
等默认的修饰符是 public
:
cangjie
package cangjie_blog.access
// ✅ 正确:public 变量可以使用内置类型
public let publicNumber: Int64 = 42
public let publicString: String = "Hello"
public let publicRune: Rune = 'A'
// ✅ 正确:public 函数可以使用内置类型
public func publicFunction2(): Int64 {
let localNumber: Int32 = 100
return Int64(localNumber)
}
实际项目示例
让我创建一个完整的项目示例来展示包系统的使用:
项目结构
css
src/
├── main.cj
├── math/
│ ├── arithmetic.cj
│ ├── geometry.cj
│ └── constants.cj
├── utils/
│ ├── formatter.cj
│ └── validator.cj
└── models/
├── point.cj
└── rectangle.cj
具体实现
1. 数学包 - 算术运算
cangjie
// src/math/arithmetic.cj
package cangjie_blog.math
public func add(a: Int32, b: Int32): Int32 {
return a + b
}
public func subtract(a: Int32, b: Int32): Int32 {
return a - b
}
public func multiply(a: Int32, b: Int32): Int32 {
return a * b
}
public func divide(a: Int32, b: Int32): Float64 {
if (b == 0) {
throw Exception("除数不能为零")
}
return Float64(a) / Float64(b)
}
public func power(base: Int32, exponent: Int32): Int64 {
if (exponent < 0) {
throw Exception("指数不能为负数")
}
var result: Int64 = 1
var base64 = Int64(base)
var exp = exponent
while (exp > 0) {
if (exp % 2 == 1) {
result = result * base64
}
base64 = base64 * base64
exp = exp / 2
}
return result
}
2. 数学包 - 几何运算
cangjie
// src/math/geometry.cj
package cangjie_blog.math
public func circleArea(radius: Float64): Float64 {
return PI * radius * radius
}
public func circleCircumference(radius: Float64): Float64 {
return 2.0 * PI * radius
}
public func rectangleArea(width: Float64, height: Float64): Float64 {
return width * height
}
public func rectanglePerimeter(width: Float64, height: Float64): Float64 {
return 2.0 * (width + height)
}
public func triangleArea(base: Float64, height: Float64): Float64 {
return 0.5 * base * height
}
public func trianglePerimeter(a: Float64, b: Float64, c: Float64): Float64 {
return a + b + c
}
3. 数学包 - 常量
cangjie
// src/math/constants.cj
package cangjie_blog.math
public let PI: Float64 = 3.14159265359
public let E: Float64 = 2.71828182846
public let GOLDEN_RATIO: Float64 = 1.61803398875
public let SQRT2: Float64 = 1.41421356237
public let SQRT3: Float64 = 1.73205080757
4. 工具包 - 格式化
cangjie
// src/utils/formatter.cj
package cangjie_blog.utils
import cangjie_blog.math.PI
import std.math.{round, pow}
public func formatFloat(value: Float64, precision: Int32): String {
let factor = pow(10.0f64, Float64(precision))
let rounded = round((value * factor)) / factor
return "${rounded}"
}
public func formatPercentage(value: Float64): String {
return "${value * Float64(100)}%"
}
public func formatCurrency(amount: Float64, currency: String): String {
return "${currency}\\${formatFloat(amount, 2)}"
}
5. 工具包 - 验证器
cangjie
// src/utils/validator.cj
package cangjie_blog.utils
public func isValidEmail(email: String): Bool {
// 简单的邮箱验证
return email.contains("@") && email.contains(".")
}
public func isValidPhone(phone: String): Bool {
// 简单的手机号验证(假设11位数字)
if (phone.size != 11) {
return false
}
for (char in phone.toRuneArray()) {
if (char < r'0' || char > r'9') {
return false
}
}
return true
}
public func isValidAge(age: Int64): Bool {
return age >= 0 && age <= 150
}
public func isValidPassword(password: String): Bool {
// 密码至少8位,包含字母和数字
if (password.size < 8) {
return false
}
var hasLetter = false
var hasDigit = false
for (char in password.toRuneArray()) {
if ((char >= r'a' && char <= r'z') || (char >= r'A' && char <= r'Z')) {
hasLetter = true
} else if (char >= r'0' && char <= r'9') {
hasDigit = true
}
}
return hasLetter && hasDigit
}
6. 模型包 - 点
cangjie
// src/models/point.cj
package cangjie_blog.models
import std.math.sqrt
public struct Point <: ToString {
public let x: Float64
public let y: Float64
public init(x!: Float64, y!: Float64) {
this.x = x
this.y = y
}
public func distance(to!: Point): Float64 {
let dx = this.x - to.x
let dy = this.y - to.y
return sqrt(dx * dx + dy * dy)
}
public func add(other: Point): Point {
return Point(x: this.x + other.x, y: this.y + other.y)
}
public func subtract(other: Point): Point {
return Point(x: this.x - other.x, y: this.y - other.y)
}
public func multiply(scalar: Float64): Point {
return Point(x: this.x * scalar, y: this.y * scalar)
}
public func toString(): String {
return "(${x}, ${y})"
}
}
7. 模型包 - 矩形
cangjie
// src/models/rectangle.cj
package cangjie_blog.models
import cangjie_blog.math.{rectangleArea, rectanglePerimeter}
public struct Rectangle <: ToString {
public let topLeft: Point
public let bottomRight: Point
public init(topLeft: Point, bottomRight: Point) {
this.topLeft = topLeft
this.bottomRight = bottomRight
}
public init(x1!: Float64, y1!: Float64, x2!: Float64, y2!: Float64) {
this.topLeft = Point(x: x1, y: y1)
this.bottomRight = Point(x: x2, y: y2)
}
public func width(): Float64 {
return bottomRight.x - topLeft.x
}
public func height(): Float64 {
return bottomRight.y - topLeft.y
}
public func area(): Float64 {
return rectangleArea(width(), height())
}
public func perimeter(): Float64 {
return rectanglePerimeter(width(), height())
}
public func contains(point: Point): Bool {
return point.x >= topLeft.x && point.x <= bottomRight.x && point.y <= topLeft.y && point.y >= bottomRight.y
}
public func toString(): String {
return "Rectangle(${topLeft} to ${bottomRight})"
}
}
8. 主程序
cangjie
package cangjie_blog
// cjlint-ignore -start !G.OTH.03 !G.OTH.02
// 主程序,可以省略包声明
import cangjie_blog.math.*
import cangjie_blog.utils.*
import cangjie_blog.models.*
main(): Int32 {
println("=== 仓颉语言包系统演示 ===\n")
// 测试算术运算
println("1. 算术运算测试:")
let sum = add(15, 25)
let product = multiply(6, 7)
let quotient = divide(100, 3)
println(" 15 + 25 = ${sum}")
println(" 6 × 7 = ${product}")
println(" 100 ÷ 3 = ${formatFloat(quotient, 2)}")
println(" 2^8 = ${power(2, 8)}\n")
// 测试几何运算
println("2. 几何运算测试:")
let radius = 5.0
let circleArea = circleArea(radius)
let circleCirc = circleCircumference(radius)
println(" 半径为 ${radius} 的圆:")
println(" 面积:${formatFloat(circleArea, 2)}")
println(" 周长:${formatFloat(circleCirc, 2)}\n")
// 测试模型
println("3. 模型测试:")
let p1 = Point(x: 0.0, y: 0.0)
let p2 = Point(x: 3.0, y: 4.0)
let distance = p1.distance(to: p2)
println(" 点 ${p1} 到点 ${p2} 的距离:${formatFloat(distance, 2)}")
let rect = Rectangle(x1: 0.0, y1: 0.0, x2: 5.0, y2: 3.0)
println(" 矩形:${rect}")
println(" 面积:${formatFloat(rect.area(), 2)}")
println(" 周长:${formatFloat(rect.perimeter(), 2)}\n")
// 测试工具函数
println("4. 工具函数测试:")
let email = "test@example.com"
let phone = "13800138000"
let age = 25
let password = "SecurePass123"
println(" 邮箱 ${email} 是否有效:${isValidEmail(email)}")
println(" 手机号 ${phone} 是否有效:${isValidPhone(phone)}")
println(" 年龄 ${age} 是否有效:${isValidAge(age)}")
println(" 密码是否有效:${isValidPassword(password)}\n")
// 测试格式化
println("5. 格式化测试:")
let percentage = 0.856
let currency = 1234.56
println(" 百分比:${formatPercentage(percentage)}")
println(" 货币:${formatCurrency(currency, "¥")}")
println(" 精确到3位小数:${formatFloat(PI, 3)}\n")
println("=== 演示完成 ===")
return 0
}
// cjlint-ignore -end
最佳实践
1. 包命名规范
- 使用小写字母和下划线
- 包名要反映功能模块
- 避免与标准库冲突
2. 导入组织
- 按标准库、第三方库、本地包的顺序组织导入
- 使用具体的导入而不是通配符导入(除非必要)
- 及时清理未使用的导入
3. 访问控制
- 默认使用
internal
访问级别 - 只在必要时使用
public
- 合理使用
private
隐藏内部实现
4. 包结构设计
- 按功能模块组织包
- 避免过深的包层次
- 保持包的职责单一
总结
仓颉语言的包系统提供了强大的代码组织和模块化能力。通过合理使用包声明、导入、访问控制和重导出,我们可以:
- 提高代码可维护性:将相关功能组织在一起
- 增强代码复用性:通过导入和重导出共享功能
- 控制代码可见性:使用访问修饰符保护内部实现
- 避免命名冲突:通过包名空间隔离不同模块