NOTE阅读文档版本:
语言规约 Cangjie-0.53.18-Spec
具体开发指南 Cangjie-LTS-1.0.4
在阅读 了解仓颉的语言规约时, 难免会涉及到一些仓颉的示例代码, 但 我们对仓颉并不熟悉, 所以可以用 仓颉在线体验 快速验证
有条件当然可以直接 配置 Canjie-SDK
WARNING博主在此之前, 基本只接触过 C/C++语言, 对大多现代语言都没有了解, 所以在阅读过程中遇到相似的概念, 难免会与 C/C++中的相似概念作类比, 见谅
且, 本系列是文档阅读, 而不是仓颉的零基础教学, 所以如果要跟着阅读的话最好有一门编程语言的开发经验
WARNING在阅读仓颉编程语言的开发指南之前, 已经大概阅读了一遍 仓颉编程语言的语言规约, 已经对仓颉编程语言有了一个大概的了解
所以在阅读开发指南时, 不会对类似: 类、函数、结构体、接口等解释起来较为复杂名称 做出解释
此样式内容, 表示文档原文内容
包
包的概述
随着项目规模的不断扩大,仅在一个超大文件中管理源代码会变得十分困难
这时可以将源代码根据功能进行分组,并将不同功能的代码分开管理,每组独立管理的代码会生成一个输出文件
在使用时,通过导入对应的输出文件使用相应的功能,或者通过不同功能的交互与组合实现更加复杂的特性,使得项目管理更加高效
在仓颉编程语言中,包是编译的最小单元,每个包可以单独输出
AST文件、静态库文件、动态库文件等产物每个包有自己的名字空间,在同一个包内不允许有同名的顶层定义或声明(函数重载除外)
一个包中可以包含多个源文件
模块是若干包的集合,是第三方开发者发布的最小单元
一个模块的程序入口只能在其根目录下,它的顶层最多只能有一个作为程序入口的
main,该main没有参数或参数类型为Array<String>,返回类型为整数类型或Unit类型
包和模块, 对于没有接触过相关概念的博主来说, 编译的最小单元和发布的最小单元, 两个含义有一些难区分
仓颉中每个包都可以单独输出库文件, 但不能发布的原因是什么呢? 是仓颉自己做了限制吗? 毕竟库发布也是为了导入使用
模块可以存在程序入口, 不过只能在根目录下, 有且只能有一个main函数作为模块的程序入口
包和模块管理
在仓颉编程语言中,包由一个或多个源码文件组成,同一个包的源码文件必须在同一个目录,并且同一个目录里的源码文件只能属于同一个包
包可以定义子包从而构成树形结构, 子包的目录是其父包目录的子目录
没有父包的包称为
root包,root包及其子包(包括子包的子包)构成的整棵树称为模块仓颉程序常见的组织结构如下:
demo├── src│ ├── main.cj│ └── pkg0│ ├── pkg0.cj│ ├── aoo│ │ └── aoo.cj│ └── boo│ └── boo.cj└── cjpm.toml
cjpm.toml是当前模块所在工作空间的配置文件,用于定义基础信息、依赖项、编译选项等内容该文件由仓颉语言的官方包管理工具
cjpm解析和执行注意:
对于同一个模块,如果需要为其配置一个有效的包,则该包所在目录必须直接包含至少一个仓颉代码文件,并且其上游目录都需要是有效包
对文档中举例的模块目录结构来说:
-
此模块为
demo, 即root包为demo -
src目录默认为模块的代码根目录这个代码根目录, 是可以在
cjpm.toml中配置的, 默认为src-dir = "", 此时 模块以src目录为模块的代码根目录所以,
src内的所有子目录都是demo的子包 -
src/main.cj中, 如果存在main函数, 那么就编译可执行程序, 否则可以编译发布库
cjpm.toml是管理 模块的各种信息、依赖、编译选项等的, 编译目标也是由cjpm.tmol管理的
这也意味着, 虽然每个包都可以单独编译, 但是cjpm配置的是整个模块, 而不是模块内的子包, 所以 发布也应该是模块
因为项目依赖、结构等是由cjpm管理的
CAUTION一个有效的包, 所在目录至少包含一个仓颉代码文件, 且至少要声明包名
这意味着, 如果你要声明一个有效包, 就需要在包目录下创建至少一个
.cj文件, 且必须当前声明包名举个例子:
Second/├── cjpm.lock├── cjpm.toml└── src├── main.cj└── test└── test.cj如果想要成功声明有效的
Second.test子包, 就必须在src/test下创建至少一个.cj文件, 且内容至少为:package Second.test注意: 包名就是目录名,
.cj文件命名不需要与包名保持一致
包的声明
在仓颉编程语言中,包声明以关键字
package开头,后接root包至当前包由.分隔路径上所有包的包名包名必须是合法的普通标识符(不含原始标识符)
例如:
package pkg1 // root 包 pkg1package pkg1.sub1 // root 包 pkg1 的子包 sub1注意:
当前
Windows平台版本,包名暂不支持使用Unicode字符,包名必须是一个仅含ASCII字符的合法的普通标识符包声明必须在源文件的非空非注释的首行,且同一个包中的不同源文件的包声明必须保持一致
// file 1// 注释是允许的package test// 声明 ...// file 2let a = 1 // Error, 包声明必须出现在文件的第一位package test// 声明 ...仓颉的包名需反映当前源文件相对于项目源码根目录
src的路径,并将其中的路径分隔符替换为小数点例如包的源代码位于
src/directory_0/directory_1下,root包名为pkg则其源代码中的包声明应为package pkg.directory_0.directory_1需要注意的是:
包所在的文件夹名必须与包名一致
源码根目录默认名为
src源码根目录下的包可以没有包声明,此时编译器将默认为其指定包名
default假设源代码目录结构如下:
// 目录结构如下:src├-- directory_0│ |-- directory_1│ | |-- a.cj│ | └-- b.cj│ └-- c.cj└-- main.cj则
a.cj、b.cj、c.cj、main.cj中的包声明可以为:a.cj // 在文件 a.cj 中, 声明的包名必须与相对路径对应 directory_0/directory_1package default.directory_0.directory_1b.cj // 在文件 b.cj 中, 声明的包名必须与相对路径对应 directory_0/directory_1package default.directory_0.directory_1c.cj // 在文件 c.cj 中, 声明的包名必须与相对路径对应 directory_0package default.directory_0main.cj // 文件 main.cj 位于模块根目录, 可以省略包声明main() {return 0}另外,包声明不能引起命名冲突:子包不能和当前包的顶层声明同名
以下是一些错误示例:
a.cj package apublic class B { // Error, 'B' 与子包 'a.B' 冲突public static func f() {}}b.cj package a.Bpublic func f {}main.cj import a.B // 'a.B'的用法不明确main() {a.B.f()return 0}
仓颉中, 包声明必须处于源代码文件的非注释首行
包名必须与所在的目录名保持一致
仓颉的包名, 需反映当前源文件相对于项目源码根目录的路径, 项目的源码根目录默认为src
那么, src下的子目录就是项目root包的子包, 那么在子包的源文件中声明包名是, 就需要这样声明:
如果存在Project/src/pkg1/pkg2/pkg3, 且没有使用cjpm项目管理:
-
如果在
src下声明了root包名为Project那么, 子包就需要声明为:
Project.pkg1、Project.pkg1.pkg2和Project.pkg1.pkg2.pkg3 -
如果在
src下没有声明root包名那么, 子包就需要声明为:
default.pkg1、default.pkg1.pkg2和default.pkg1.pkg2.pkg3
如果项目的源码根目录下的源文件, 没有声明包名, 那么包名默认为default, 如果需要声明包名, 那么包名推荐与项目路径名相同
但, 声明子包时必须以root包名开头
TIP如果不使用
cjpm管理项目, 尝试直接使用cjc编译项目通过指定父包和子包的源文件一起进行编译是不允许的: 即
cjc src/main.cj src/pkg1/c.cj是不被允许的需要先编译子包, 将子包编译为静态或动态库, 然后使用
cjc src/main.cj lib进行编译