1 概述
1.1 背景介绍
仓颉编程语言作为一款面向全场景应用开发的现代编程语言,通过现代语言特性的集成、全方位的编译优化和运行时实现、以及开箱即用的 IDE工具链支持,为开发者打造友好开发体验和卓越程序性能。
案例结合代码体验,让大家更直观的了解仓颉语言中的结构体、类和接口。
1.2 适用对象
- 个人开发者
- 高校学生
1.3 案例时间
本案例总时长预计60分钟。
1.4 案例流程

说明:
① 进入华为开发者空间,登录云主机; ② 使用CodeArts IDE for Cangjie编程和运行仓颉代码。
1.5 资源总览
资源名称 | 规格 | 单价(元) | 时长(分钟) |
开发者空间 - 云主机 | 鲲鹏通用计算增强型 kc2 | 4vCPUs | 8G | Ubuntu | 免费 | 60 |
最新案例动态,请查阅 《仓颉之结构体、类与接口的奇幻乐园》。小伙伴快来领取华为开发者空间进行实操体验吧!
2 运行测试环境准备
2.1 开发者空间配置
面向广大开发者群体,华为开发者空间提供一个随时访问的“开发桌面云主机”、丰富的“预配置工具集合”和灵活使用的“场景化资源池”,开发者开箱即用,快速体验华为根技术和资源。
领取云主机后可以直接进入华为开发者空间工作台界面,点击打开云主机 \> 进入桌面连接云主机。没有领取在开发者空间根据指引领取配置云主机即可,云主机配置参考1.5资源总览。


2.2 创建仓颉程序
点击桌面CodeArts IDE for Cangjie,打开编辑器,点击新建工程,保持默认配置,点击创建。
产物类型说明:
- executable,可执行文件;
- static,静态库,是一组预先编译好的目标文件的集合;
- dynamic,动态库,是一种在程序运行时才被加载到内存中的库文件,多个程序共享一个动态库副本,而不是像静态库那样每个程序都包含一份完整的副本。

2.3 运行仓颉工程
创建完成后,打开src/main.cj,参考下面代码简单修改后,点击编辑器右上角运行按钮直接运行,终端窗口可以看到打印内容。
(\* 注意:后续替换main.cj文件代码时,package demo保留)
(\* 仓颉注释语法:// 符号之后写单行注释,也可以在一对 /\* 和 \*/ 符号之间写多行注释)

到这里,我们第一个仓颉程序就运行成功啦!后面案例中的示例代码都可以放到main.cj文件中进行执行,接下来我们继续探索仓颉语言。
3 仓颉语言结构类型
3.1 定义struct类型
仓颉编程语言中定义结构类型使用struct关键字,后跟结构体的名字,接着是定义在一对花括号中的结构体。struct结构体中可以定义一系列成员变量、成员属性、静态初始化器、构造函数和成员函数。
结构类型初识,具体代码如下:
注意:struct只能定义在源文件的顶层作用域。
struct成员变量:
struct成员变量分为实例成员变量和静态成员变量(使用static修饰符修饰)。
两者访问区别:
实例成员变量只能通过struct实例访问;静态成员变量只能通过struct类型名访问。
定义struct类型Rectangle,通过struct访问实例成员变量和静态成员变量。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

struct静态初始化器
struct 支持定义静态初始化器,并在静态初始化器中通过赋值表达式来对静态成员变量进行初始化。
struct静态初始化器要点:
- 静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体;
- 关键字组合 static init不能被访问修饰符访问;
- 函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错;
- 一个 struct 中最多允许定义一个静态初始化器,否则报重定义错误。
关于struct静态初始化器使用,具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:代码复制完成后,编译器直接提示报错。

struct构造函数:
struct构造函数分为普通构造函数和主构造函数。
普通构造函数:普通构造函数以关键字 init 开头,后跟参数列表和函数体,函数体中必须完成对所有未初始化的实例成员变量的初始化。
主构造函数:struct内最多可以定义一个主构造函数。主构造函数的名字和 struct 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时扮演定义成员变量和构造函数参数的功能。
关于struct普通构造函数使用,具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:代码复制完成后,编译器直接提示报错。

关于struct主构造函数使用,具体代码如下:
struct成员函数:
struct 成员函数分为实例成员函数和静态成员函数(使用 static 修饰符修饰)
二者区别:
实例成员函数只能通过 struct 实例访问,静态成员函数只能通过 struct 类型名访问;
静态成员函数中不能访问实例成员变量,也不能调用实例成员函数,但在实例成员函数中可以访问静态成员变量以及静态成员函数。
定义struct类型Rectangle,通过struct访问实例成员函数和静态成员函数。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

struct成员访问修饰符
访问修饰符修饰:private、internal、protected 和 public。
- private:在 struct 定义内可见。
- internal:当前包及子包内可见。
- protected:当前模块可见。
- public:模块内外均可见。
3.2 创建struct实例
定义了 struct 类型后,即可通过调用 struct 的构造函数来创建 struct 实例。在 struct 定义之外,通过 struct 类型名调用构造函数。
创建struct实例,具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

3.3 mut函数
mut 函数是一种可以修改 struct 实例本身的特殊的实例成员函数。mut 函数与普通的实例成员函数相比,多一个 mut 关键字来修饰。
mut函数使用,具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

4 仓颉语言中的类
class 类型是面向对象编程中的经典概念,仓颉中同样支持使用 class 来实现面向对象编程。class 与 struct 的主要区别:
- class 是引用类型,struct 是值类型;
- class 之间可以继承,但 struct 之间不能继承;
4.1 Class定义
class 类型的定义以关键字 class 开头,后跟 class 的名字,接着是定义在一对花括号中的 class 定义体。class 定义体中可以定义一系列的成员变量、成员属性、静态初始化器、构造函数、成员函数等。
class类型初识,具体代码如下:
抽象类定义,具体代码如下:
注意:
- 抽象类中禁止定义 private 的抽象函数;
- 不能为抽象类创建实例;
- 抽象类的非抽象子类必须实现父类中的所有抽象函数。
成员变量:
class 成员变量分为实例成员变量和静态成员变量,静态成员变量使用 static 修饰符修饰,没有静态初始化器时必须有初值,只能通过类型名访问。
静态成员变量通过类名访问,具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。
实例成员变量定义时可以不设置初值,也可以设置初值,只能类的实例访问。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。
class静态初始化器:
静态初始化器以关键字组合 static init 开头,后跟无参参数列表和函数体,且不能被访问修饰符修饰。函数体中必须完成对所有未初始化的静态成员变量的初始化,否则编译报错。
一个 class 中最多允许定义一个静态初始化器,否则报重定义错误。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:代码复制完成后,编译器直接提示报错。

class构造函数:
和 struct 一样,class 中也支持定义普通构造函数和主构造函数。
普通构造函数以关键字 init 开头,后跟参数列表和函数体,函数体中必须完成所有未初始化实例成员变量的初始化,否则编译报错。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:代码复制完成后,编译器直接提示报错。

主构造函数的名字和 class 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时具有定义成员变量和构造函数参数的功能。class 内最多可定义一个主构造函数。
主构造函数定义如下:
class成员函数:
class 成员函数同样分为实例成员函数和静态成员函数(使用 static 修饰符修饰),实例成员函数只能通过对象访问,静态成员函数只能通过 class 类型名访问。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。
class成员的访问修饰符:
对于 class 的成员(包括成员变量、成员属性、构造函数、成员函数),可以使用的访问修饰符有 4 种访问修饰符修饰:private、internal、protected 和 public。
- private 表示在 class 定义内可见。
- internal 表示仅当前包及子包(包括子包的子包)内可见。
- protected 表示当前模块及当前类的子类可见。
- public 表示模块内外均可见。
4.2 创建对象
定义了 class 类型后,即可通过调用其构造函数来创建对象。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

4.3 Class继承
像大多数支持 class 的编程语言一样,仓颉中的 class 同样支持继承。如果类 B 继承类 A,则称 A 为父类,B 为子类。子类将继承父类中除 private 成员和构造函数以外的所有成员。
open修饰符修饰的类是可被继承;
class类仅支持单继承;
抽象类总是可被继承的,故抽象类定义时的 open 修饰符是可选的;
可以使用 sealed修饰符修饰抽象类,表示该抽象类只能在本包被继承;
可以在子类定义处通过 \<: 指定其继承的父类,但要求父类必须是可继承的。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:代码复制完成后,编译器直接提示报错。

父类构造函数调用:
子类的 init 构造函数可以使用 super(args) 的形式调用父类构造函数,或使用 this(args) 的形式调用本类其它构造函数,但两者之间只能调用一个。如果调用,必须在构造函数体内的第一个表达式处。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

覆盖和重定义:
子类中可以覆盖(override)父类中的同名非抽象实例成员函数,即在子类中为父类中的某个实例成员函数定义新的实现。
覆盖时,要求父类中的成员函数使用 open 修饰,子类中的同名函数使用 override 修饰,其中 override 是可选的。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

5 仓颉语言中的接口
接口用来定义一个抽象类型,它不包含数据,但可以定义类型的行为。一个类型如果声明实现某接口,并且实现了该接口中所有的成员,就被称为实现了该接口。
接口的成员可以包含:
- 成员函数;
- 操作符重载函数;
- 成员属性。
这些成员都是抽象的,要求实现类型必须拥有对应的成员实现。
5.1 接口定义
接口定义:
接口使用关键字 interface 声明,其后是接口的标识符 I 和接口的成员。接口成员可被 open 修饰符修饰,并且 open 修饰符是可选的。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

静态成员函数和实例成员函数类似,都要求实现类型提供实现。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

5.2 接口继承
接口可以继承一个或多个接口,多个接口使用 & 分隔,多个接口之间没有顺序要求。
接口继承时可以添加新的接口成员。
具体代码如下:
5.3 接口实现
仓颉除 Tuple、VArray 和函数外的其它类型都可以实现接口。
如果接口中的成员函数或操作符重载函数的返回值类型是 class 类型,那么允许实现函数的返回类型是其子类型。
例如下面这个例子,I 中的 f 返回类型是一个 class 类型 Base,因此 C 中实现的 f 返回类型可以是 Base 的子类型 Sub。
具体代码如下:
接口的成员可以提供默认实现。
例如下面的代码中,SayHi 中的 say 拥有默认实现,因此 A 实现 SayHi 时可以继承 say 的实现,而 B 也可以选择提供自己的 say 实现。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

如果一个类型在实现多个接口时,多个接口中包含同一个成员的默认实现,这时会发生多重继承的冲突,语言无法选择最适合的实现,因此这时接口中的默认实现也会失效,需要实现类型提供自己的实现。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

struct、enum 和 class 在实现接口时,函数或属性定义前的 override 修饰符是可选的,无论接口中的函数或属性是否存在默认实现。
具体代码操作如下:
Step1:复制以下代码,替换main.cj文件中的代码。(保留package)
Step2:点击右上角“运行”按钮,终端控制台打印出内容。

5.4 Any类型
Any 类型是一个内置的接口,定义如下:
仓颉中所有接口都默认继承它,所有非接口类型都默认实现它,因此所有类型都可以作为 Any 类型的子类型使用。
具体代码如下:
至此,仓颉之结构体、类与接口的奇幻乐园案例内容已全部完成。
如果想了解更多仓颉编程语言知识可以访问:https://cangjie-lang.cn/

被折叠的 条评论
为什么被折叠?



