当前位置: 首页 > news >正文

仓颉编程语言青少年基础教程:Struct(结构)类型

仓颉编程语言青少年基础教程:Struct(结构)类型

Struct(结构) 是一种用户自定义(组织)相关数据的值类型和操作数据的行为的复合数据类型。还是一种 mutable(可变的) 类型。

struct类型的定义与使用

struct 类型的定义以关键字 struct 开头,后跟 struct 的名字,接着是定义在一对花括号中的 struct 定义体。struct 定义体中可以定义一系列的成员变量(member variables)、成员属性(member properties)、静态初始化器(static initializers)、构造函数(constructors)和成员函数(member functions)。

定义 Struct基本结构的语法

struct 类型的定义以关键字 struct 开头,后跟 struct 的名字,接着是定义在一对花括号中的 struct 定义体。struct 定义体中可以定义一系列的成员变量(member variables)、成员属性(member properties)、静态初始化器(static initializers)、构造函数(constructors)和成员函数(member functions)。

定义 Struct基本结构的语法

// 核心语法(必须定义在源文件顶层,不能嵌套在函数/其他结构内)

struct 结构体名 {

    // 1. 成员变量(实例变量/静态变量,需标注类型或赋初值)

    [static] [访问修饰符] [let/var] 变量名: 类型 [= 初始值];

    // 2. 构造相关(静态初始化器/普通构造函数/主构造函数)

    static init() { /* 初始化静态变量,最多1个 */ }

    [访问修饰符] init(参数列表) { /* 普通构造函数,初始化实例变量,可重载 */ }

    [访问修饰符] 结构体名(参数列表) { /* 主构造函数,最多一个,可直接定义实例变量 */ }

    // 3. 成员函数(实例函数/静态函数/mut函数)

    [访问修饰符] [static/mut] func 函数名(参数列表) [-> 返回类型] { /* 函数体 */ }

}

创建 struct 实例的语法:

// 核心语法(var 表示实例可变,let 表示实例不可变)

[var | let] 实例名 = 结构体名(构造函数参数);

// 特殊场景补充

[var | let] 实例名 = 结构体名(); // 无参构造(自动生成或自定义时,无需传参)

[var | let] 实例名 = 结构体名(参数名: 参数值); // 主构造函数/命名参数(如 Rectangle(width: 10, height: 20))

注意,struct 类型的定义以关键字 struct 开头,后跟 struct 的名字,接着是定义在一对花括号中的 struct 定义体。struct 定义体中可以定义一系列的成员变量、成员属性、静态初始化器、构造函数和成员函数。

定义位置:struct 必须直接定义在源文件的最外层(顶层作用域),不能嵌套在函数、循环、条件块等 “内部代码块” 中。

值类型:struct 变量存储的是数据本身(不是引用地址)。这意味着:

当你把一个 struct 变量赋值给另一个变量,或作为参数传给函数时,会完整拷贝一份数据(而不是共享同一份数据)。

可变类型:struct 内部的成员变量可以被修改(只要变量本身用 var 声明),即它的实例状态是可以变化的。

不能继承,但能实现接口:

struct 是 “密封” 的,不能像类(可能的 class 类型)那样被其他类型继承。

例:struct A {}; struct B: A {} 会直接报错(B 不能继承 A)。

可以实现接口,struct 可以遵循接口(Interface),从而拥有接口规定的方法,具备一定的扩展性。

默认不支持 ==,需手动实现:

两个 struct 实例默认不能用 == 或 != 比较(即使成员变量完全相同)。如果需要判等,必须手动 “重载”== 操作符。

struct递归(自己嵌套自己)、互递归非法。
自己嵌套自己(非法),例如:
struct Node {
value: Int64
next: Node          // 错误(error)
}
两个 struct 互相引用(也非法),例如:
struct A {
b: B                //错误(error)
}
struct B {
a: A
}

上面所说,初学者一时看不懂不必着急,下面将逐步示例介绍。

仓颉中的 struct 可以简单理解为一个“自定义数据模板”,用来打包一组相关的 “数据”和 “操作这些数据的方法”。就像现实中 “图纸” 和 “实物” 的关系:struct 是图纸,用它创建的 “实例” 就是按图纸造出来的具体实物。

先看一个简单示例:

// 定义 Circle 结构体
struct Circle {// 成员变量let radius: Float32// 构造函数public init(radius: Float32) {this.radius = radius}    // 成员属性:计算面积public prop area: Float32 {get() { 3.14 * radius * radius }}// 成员函数:计算面积public func calculateArea(): Float32 {return 3.14 * radius * radius}
}// 使用示例
main() {let circle = Circle(5.0)// 使用成员属性println("圆的面积(属性)是: ${circle.area}")// 使用成员函数println("圆的面积(函数)是: ${circle.calculateArea()}")
}

编译运行截图:

struct的成员介绍

struct 类型的 “内部定义区域”(即花括号 {} 中的内容),里面可以定义 5 类核心元素:成员变量、成员属性、主构造函数、普通构造函数、成员函数(含操作符函数)。这些哪些是必须的?各有什么作用?

☆成员变量(必须):核心作用是存储数据,而成员变量是数据的载体。

struct Point {
// 显式定义成员变量
let x: Int32
let y: Int32

    public init(x: Int32, y: Int32) {
this.x = x
this.y = y
}
}
其中
public init(x: Int32, y: Int32) {
this.x = x
this.y = y
}
是构造函数(后面介绍)。

成员变量是数据的 “容器”,分为:实例成员变量和静态成员变量

实例成员变量(无 static)

在结构体或类中定义的变量,不使用 static 修饰符。

实例成员变量存储在每个实例中,每个实例都有自己的独立副本。每次创建一个新的实例时,都会为这些变量分配独立的内存空间。

访问方式,通过实例访问,即 对象.变量名。

静态成员变量(有 static)

在结构体或类中定义的变量,使用 static 修饰符。

静态成员变量存储在类或结构体的类型级别上,而不是在每个实例中。静态成员变量在程序启动时初始化一次,所有实例共享同一个变量。

访问方式,通过类型名访问,即 类型名.变量名。

示例:

// 定义 Rectangle 结构体
struct Rectangle {public var width: Int64public var height: Int64public static var count: Int64 = 0public init(width: Int64, height: Int64) {this.width = widththis.height = heightRectangle.count += 1  // 增加实例计数}
}// 使用示例
main() {let r1 = Rectangle(10, 20)let r2 = Rectangle(5, 5)println("r1 的宽度是: ${r1.width}")  // 访问实例成员变量println("r2 的高度是: ${r2.height}")println("Rectangle 的实例数量是: ${Rectangle.count}")  // 访问静态成员变量
}

编译运行,输出如下:

r1 的宽度是: 10
r2 的高度是: 5
Rectangle 的实例数量是: 2

构造函数

仓颉中的struct类型有两种构造函数:

主构造函数(primary constructor)名字 必须 和 struct 同名

普通构造函数(init)名字固定叫 init

构造函数的作用是初始化所有未赋值的成员变量。如果成员变量都有初始值,构造函数可选(编译器可能生成默认无参构造函数);如果存在未赋值的成员变量,则必须通过主构造函数或普通构造函数初始化,否则编译报错。

例:成员变量有初始值,构造函数可选:
struct Square {
let side: Int32 = 10  // 有初始值,无需构造函数
}
let s = Square()  // 编译器生成默认无参构造函数

例:成员变量无初始值,必须有构造函数:
struct Square {
let side: Int32  // 无初始值,必须构造函数初始化
// 必须定义构造函数
init(side: Int32) {
this.side = side
}
}

主构造函数和普通构造函数

主构造函数的名称必须与结构体名称相同。一个结构体最多只能有一个主构造函数。如:
struct Rectangle {
public var width: Int64
public var height: Int64

    public Rectangle(width: Int64, height: Int64) {
this.width = width
this.height = height
}
}

普通构造函数使用 init 关键字定义。可以有多个,但必须构成重载(参数列表不同)。例如
struct Rectangle {
public var width: Int64
public var height: Int64

    public init(side: Int64) {
this.width = side
this.height = side
}
}

关于构造函数的几点说明

①如果构造函数的参数名和成员变量名无法区分,可以在成员变量前使用 this 加以区分,this 表示 struct 的当前实例,否则编译报错。

struct Person {
var name: String  // 成员变量name
var age: Int      // 成员变量age

    // 构造函数参数名也叫name和age,与成员变量同名
func Person(name: String, age: Int) {
this.name = name  // this.name指成员变量,右侧name指参数
this.age = age    // 同理,区分成员变量和参数
}
}

如果不加this,编译器无法区分name是参数还是成员变量,this在这里起到明确标识的作用。否者,编译器会报错。

②一个 struct 中可以定义多个普通构造函数,但它们必须构成重载(参见函数重载),否则报重定义错误。

合法的重载(函数名称相同,参数个数或类型序列不同就合法):

struct Point {
var x: Int
var y: Int

    // 构造函数1:两个参数
func Point(x: Int, y: Int) {
this.x = x
this.y = y
}

    // 构造函数2:一个参数(参数个数不同,构成重载)
func Point(xy: Int) {
this.x = xy
this.y = xy
}

    // 构造函数3:参数类型不同(构成重载)
func Point(x: Float, y: Float) {
this.x = Int(x)
this.y = Int(y)
}
}

如果定义两个参数列表完全相同的构造函数,就会报错(不合法):
struct Point {
var x: Int
var y: Int

    func Point(x: Int, y: Int) { ... }
func Point(x: Int, y: Int) { ... }  // 错误:参数列表相同,未构成重载
}

下面这样就不行,仅靠参数名不同,不构成重载:
public init(a: Int64) { ... }
public init(b: Int64) { ... }   // 报错:redefinition of init(Int64)

③主构造函数的名字和 struct 类型名相同,它的参数列表中可以有两种形式的形参:普通形参和成员变量形参(需要在参数名前加上 let 或 var),成员变量形参同时扮演定义成员变量和构造函数参数的功能。

换句话说,主构造函数是struct中名字与 struct 类型名相同的构造函数,其参数有两种形式:

普通形参:仅作为构造函数的参数,需手动关联到成员变量(需提前定义成员变量)。

成员变量形参:在参数名前加let或var,此时参数既是构造函数的输入,也会自动成为 struct 的成员变量(无需提前定义)。

示例:

// ① 主构造函数:参数带 let → 成员变量形参
//    编译器自动干三件事:
//      1. 给 Point 增加不可变成员 x: Int64
//      2. 给 Point 增加不可变成员 y: Int64
//      3. 把实参值赋进去
public struct Point {public Point(let x: Int64, let y: Int64) {println("通过主构造函数创建的点: (${x}, ${y})")}
}// ② 传统写法:先手动声明成员,再用普通形参赋值
public struct PointVerbose {var x: Int64var y: Int64public init(x: Int64, y: Int64) {   // 普通形参,无 let/varthis.x = xthis.y = yprintln("传统普通构造创建的点: (${x}, ${y})")}
}main(): Int64 {// 主构造:一句搞定声明+赋值let p1 = Point(10, 20)println("p1.x = ${p1.x}, p1.y = ${p1.y}")   // 证明成员变量已存在// 普通构造:先声明再赋值let p2 = PointVerbose(30, 40)println("p2.x = ${p2.x}, p2.y = ${p2.y}")return 0
}

输出:

通过主构造函数创建的点: (10, 20)
p1.x = 10, p1.y = 20
传统普通构造创建的点: (30, 40)
p2.x = 30, p2.y = 40

成员属性、成员函数(可选)

这两类元素是 “增强功能”,不是 struct 的必备要素。

成员属性:

成员属性通过 prop 关键字定义,包含 get 和可选的 set 方法。

通过 get(读取)和 set(赋值)逻辑,对成员变量的访问进行 “包装”,实现更灵活的数据处理(如计算、验证、联动修改)。成员属性(Properties) 不能接受外部参数,主要作用是提供对成员变量的封装和间接访问,而不是实现复杂的逻辑或处理外部参数。

访问方式,直接访问属性名(对象.属性名)

示例:

// 定义 Circle 结构体
struct Circle {let radius: Float32  // 成员变量// 构造函数public init(radius: Float32) {this.radius = radius}// 成员属性(计算面积,依赖 radius)public prop area: Float32 {get() { 3.14 * radius * radius }  // 读取时计算}
}// 使用示例
main() {let circle = Circle(5.0)  // 创建一个半径为 5.0 的圆println("圆的面积是: ${circle.area}")  // 访问 area 属性,输出:圆的面积是: 78.500000
}

成员函数

成员函数通过 func 关键字定义

成员函数可以接受外部参数,从而实现更灵活的操作。

访问方式,通过方法调用(对象.函数名())

示例:

// 定义 Rectangle 结构体
struct Rectangle {public var width: Int64public var height: Int64// 构造函数public init(width: Int64, height: Int64) {this.width = widththis.height = height}// 成员函数:计算面积public func calculateArea(): Int64 {return width * height}// 成员函数:计算周长public func calculatePerimeter(): Int64 {return 2 * (width + height)}// 成员函数:缩放矩形public mut func scale(factor: Int64) {width *= factorheight *= factor}
}// 使用示例
main() {let rec1 = Rectangle(10, 20)// 调用成员函数println("rec1 的面积是: ${rec1.calculateArea()}")  // 输出:200println("rec1 的周长是: ${rec1.calculatePerimeter()}")  // 输出:60// 缩放矩形var rec2 = rec1rec2.scale(2)println("缩放后的 rec2 的面积是: ${rec2.calculateArea()}")  // 输出:800println("缩放后的 rec2 的周长是: ${rec2.calculatePerimeter()}")  // 输出:120
}

运行上述代码后,输出如下:

运行上述代码后,输出如下:
rec1 的面积是: 200
rec1 的周长是: 60
缩放后的 rec2 的面积是: 800
缩放后的 rec2 的周长是: 120

struct成员的访问修饰符:控制成员的可见范围

struct 的所有成员(变量、方法、构造函数)都可以用访问修饰符控制谁能访问,默认是 internal。

private     仅在当前 struct 内部可见(外部无法访问)

internal    默认值,仅当前包及子包可见(跨包无法访问)

protected        仅当前模块可见(模块内任意包可访问,模块外无法访问)

public       模块内外都可见(外部包导入后可访问)

示例

假设有一个项目目录如下:

(在包shape 定义 struct, 在入口文件main.cj导入并使用它)
demo24

src/ 
├── shape/     // shape包
│      └── rectangle.cj    // shape包的rectangle.cj文件
└──  main.cj    // 入口文件  

shape包的rectangle.cj 文件源码如下:

package demo24.shape// 公开的 struct(需加 public,否则跨包无法访问)
public struct Rectangle {public var width: Int64    // public:跨包可访问var height: Int64          // 默认 internal:仅 shape 包及子包可访问private var area: Int64    // private:仅 Rectangle 内部可访问// 公开的构造函数(跨包需调用)public init(width: Int64, height: Int64) {this.width = widththis.height = heightthis.area = width * height // 内部计算面积}// 公开方法:间接访问 private 成员public func getArea(): Int64 {this.area}
}

入口main.cj文件源码如下:

package demo24import demo24.shape.* // 导入 shape 包的所有内容main() {// 1. 调用 shape 包的 struct 和构造函数var rect = Rectangle(10,20)// 2. 访问成员的差异rect.width = 15    // Ok:public(跨包可访问)// rect.height = 25 // Error:internal(跨包不可访问)// rect.area = 300  // Error:private(完全不可访问)// 3. 通过 public 方法访问 private 成员print("跨包访问面积:${rect.getArea()}") // 输出:300(15*20)
}

编译运行截图:

mut函数

为什么需要 Mut 函数?
struct 是值类型。默认情况下,它的实例成员函数不能修改实例的成员变量。如果你希望一个函数能修改实例本身,必须用 mut 关键字来标记它。

核心规则:

  • mut 函数使用 mut 关键字修饰
  • 只能修饰实例成员函数,不能修饰静态成员函数
  • mut 函数中的 this 不能被捕获,也不能作为表达式
  • 只有 mut func 可以修改实例的成员变量。
  • 通过 let 声明的结构体常量,不能调用 mut func。

struct 类型是值类型,其实例成员函数无法修改实例本身。例如,下例中,成员函数 g 中不能修改成员变量 i 的值。

struct Foo {
var i = 0

    public func g() {
i += 1  // Error
}
}

mut 函数是一种可以修改 struct 实例本身的特殊的实例成员函数。在 mut 函数内部,this 的语义是特殊的,这种 this 拥有原地修改字段的能力。

注意:只允许在 interface、struct 和 struct 的扩展内定义 mut 函数(class 是引用类型,实例成员函数不需要加 mut 也可以修改实例成员变量,所以禁止在 class 中定义 mut 函数)。

mut 函数定义

mut 函数与普通的实例成员函数相比,多一个 mut 关键字来修饰。

例如,下例中在函数 g 之前增加 mut 修饰符之后,即可在函数体内修改成员变量 i 的值。

struct Foo {
var i = 0

    public mut func g() {
i += 1  // Ok
}
}

mut 只能修饰实例成员函数,不能修饰静态成员函数。

Mut函数使用完整示例

struct Counter {var count: Int64 = 0// 这是一个 mut 函数,它可以修改 countpublic mut func increment() {count += 1 // 允许修改}// 这是一个普通函数,它不能修改 countpublic func getCount(): Int64 {return count// count += 1 // 这里如果取消注释,会编译错误!}
}main() {// 必须用 var 声明变量,才能调用 mut 函数var myCounter = Counter()myCounter.increment() // OKprintln(myCounter.getCount()) // 输出: 1// 用 let 声明的常量,即使实例本身是 var,也不能调用 mut 函数let constantCounter = Counter()// constantCounter.increment() // 错误!常量不能调用 mut 函数
}

接口中的 mut 函数:struct 实现接口时,必须保持 mut 修饰一致;且接口类型变量调用 mut 函数时,会复制实例(不影响原 struct 实例)。示例:

interface IIncrement {mut func add(step: Int64) {}func getValue(): Int64 { 0 }
}struct MyCounter <: IIncrement {private var value: Int64 = 0// 函数参数定义时保留 `step: Int64`(声明参数名)public mut func add(step: Int64) {value += step}public func getValue(): Int64 {value}
}main() {var counter = MyCounter()//直接传值(位置参数)counter.add(3)println("原实例值:${counter.getValue()}") // 输出:3var inc: IIncrement = counter//传值(位置参数)inc.add(2)println("原实例值:${counter.getValue()}") // 输出:3println("接口实例值:${inc.getValue()}")   // 输出:5
}


文章转载自:

http://St164Tvj.tmxtr.cn
http://XUwNcha1.tmxtr.cn
http://SZgpia1h.tmxtr.cn
http://e3mu94Vv.tmxtr.cn
http://vTOT4bH6.tmxtr.cn
http://P5ElDY7X.tmxtr.cn
http://GFh6R5wD.tmxtr.cn
http://YwLwHcVW.tmxtr.cn
http://Z0K7kohU.tmxtr.cn
http://DJKCPC8o.tmxtr.cn
http://iOVI2vlt.tmxtr.cn
http://bOF6ZExW.tmxtr.cn
http://Emy31QHS.tmxtr.cn
http://5c9bhAxe.tmxtr.cn
http://MIB9KVRL.tmxtr.cn
http://dbelHN7D.tmxtr.cn
http://uoMQ0Clj.tmxtr.cn
http://BQ3FrEB9.tmxtr.cn
http://CCOmMrIa.tmxtr.cn
http://Ha0s1oop.tmxtr.cn
http://m9rklkzU.tmxtr.cn
http://gKscjceX.tmxtr.cn
http://Opr3f0Yg.tmxtr.cn
http://GIWrCNLJ.tmxtr.cn
http://4Kj9ANcp.tmxtr.cn
http://hC3c30CA.tmxtr.cn
http://wsePRB52.tmxtr.cn
http://Vpfj5R9E.tmxtr.cn
http://QFSJltPf.tmxtr.cn
http://Q9z90V0e.tmxtr.cn
http://www.dtcms.com/a/382288.html

相关文章:

  • C语言数据结构实战:从零构建一个高性能的顺序栈
  • 数据链路层总结
  • Linux线程:基于环形队列的生产消费模型
  • 【Ambari监控】高版本 DataGrip 无法使用 Phoenix 驱动
  • 1.架构师——大纲
  • 粒子群算法模型深度解析与实战应用
  • JDK 新特性
  • 数据库可视化面板下载
  • 深入解析:preload与prefetch的区别及最佳实践
  • 【层面一】C#语言基础和核心语法-01(类型系统/面向对象/异常处理)
  • Python核心技术开发指南(061)——初始化方法__init__
  • 用 Go 采集服务器资源指标:从原理到实践
  • MySQL-day2_02
  • 基于springboot+vue开发的会议预约管理系统【50906】
  • 【Ubuntu】sudo apt update出现E :仓库***没有Release文件
  • JavaWeb--day3--AjaxElement路由打包部署
  • 阿里云国际代理:怎么保障数据库在凭据变更过程中的安全与稳定?
  • 关于子空间流形的认识
  • SQL注入漏洞手动测试详细过程
  • 【Linux】gcc/g++工具篇
  • libxl写到xls
  • 关键点(c++,Linux)
  • IO进程——进程引入、进程函数接口
  • Java 面向对象设计的六大原则
  • 今日分享:C++ deque与priority_queue
  • Vue3 通过json配置生成查询表单
  • spring 声明式事务
  • [硬件电路-190]:三极管的电流放大特性看男女关系3:过渡的投入,输出进入不安全区、疲惫期,反而双方系统造成伤害
  • json文件转excel
  • ros2获取topic信息解析