仓颉编程语言青少年基础教程:数组类型

数组本质上是有序、同类型数据的集合容器,其核心作用是高效组织、访问和处理批量数据,同时结合语言特性,为开发者提供简洁、高性能的数据管理方式。例如:

main() {
    let v1: Array<String> = ["a1", "a2", "a3"] // 使用 Array<String>
    println(v1[0]) // 成功输出 a1
}

在仓颉语言中,“数组” 相关的类型主要包括 Array、VArray 和 ArrayList 三种,它们虽名称或特性不同,但都用于组织有序的元素集合,只是在可变性、存储方式和适用场景上有显著差异。

三种类型的核心区别与选择

类型

长度特性

类型性质

核心能力

整体拷贝成本

适用场景

Array<T>

固定

引用类型(结构体包装)

不可增删,可修改元素

仅复制引用

元素数量固定的场景

VArray<T,$N>

固定

值类型

不可增删

按字节全复制

需减少堆内存、元素类型简单场景

ArrayList<T>

动态

引用类型

可增删改,支持扩容

仅复制引用

元素数量动态变化的场景

引用类型的数组Array

Array<T> ,其中T 表示 Array 的元素类型。引用类型(对象在堆上),放 同一种类型 T 的元素,顺序固定,长度 创建后就不可变。用来构造单一元素类型,有序序列的数据。
可以轻松使用字面量来初始化一个 Array,只需要使用方括号将逗号分隔的值列表括起来即可。如:
let numbers: Array<Int64> = [1, 2, 3, 4]
// 也可省类型:let numbers = [1, 2, 3, 4]
也可以使用构造函数的方式构造一个指定元素类型的 Array。其中,repeat 属于 Array 构造函数中的一个命名参数。如:
// 1. 指定长度 + 重复值
let zeros = Array<Int64>(10, repeat: 0)   // [0,0,0,0,0,0,0,0,0,0]
// 2. 长度 +  lambda 表达式
let squares = Array<Int64>(5, { i => i * i })  // [0,1,4,9,16]
需要注意的是,当通过 repeat 指定的初始值初始化 Array 时,该构造函数不会拷贝 repeat,如果 repeat 是一个引用类型,构造后数组的每一个元素都将指向相同的引用。如:
let d = Array<Int64>(3, repeat: 0) // repeat创建一个元素类型为Int64,长度为3,初始化为0

元素类型相同的 Array之间,可以互相赋值。元素类型不相同的 Array 是不相同的类型,不可以互相赋值。如:
let a: Array<Int64>  = [1, 2]
let b: Array<UInt8>  = [1, 2]
// a = b  // ❌ 类型不匹配

可以通过索引(从 0 开始)访问如 numbers[0]。例如:
let arr = [0,1,2,3,4,5]
println("第一个元素是${arr[0]}")  //第一个元素是0
arr [0] = 3
println("现在第一个元素是${arr[0]}") //现在第一个元素是3

可以使用 for-in 循环遍历 Array 的所有元素。如:
let numbers: Array<Int64> = [1, 2, 3, 4, 5]
for (i in numbers) { println(i) }
遍历元素常规写法:
for (i in 0.. numbers.size) {
    println("v[${i}] = ${v[i]}")
}

简单而完整的示例:

main() {let v: Array<Int64> = [10, 20, 30, 40]// for-in 循环遍历for (i in v) {println(i)}// 遍历元素,常规写法for (i in 0..v.size) {println("v[${i}] = ${v[i]}")}
}

编译运行截图:

可以使用 size 属性获得 Array 包含的元素个数。如:
main() {
    let arr = [0, 1, 2]
    println("数组的大小为 ${arr.size}")  // 数组的大小为 3
}

综合示例

main() {// Array示例:存储固定的3个月份let months: Array<String> = ["Jan", "Feb", "Mar"]println(months[1])  // 输出:Janmonths[1] = "February"  // 允许修改元素println(months[1])  // 输出:Janlet d = Array<Int64>(3, repeat: 0) // repeat创建一个元素类型为Int64,长度为3,所有元素初始化为0的数组 for (n in d) { println(n)} var a: Array<Int64> = [0, 0, 0, 0] // 元素类型为Int64的数组var b: Array<String> = ["a1", "a2", "a3"] // 元素类型为String的数组var c: Array<String> = b  //元素类型相同的 Array之间可以互相赋值println("a的元素个数${a.size}")//元素个数//for-in 遍历for (n in c) { println(n)}//编译器会根据上下文自动推断 Array 字面量的类型。var x: Array<String> =[] //创建一个元素类型为String的空数组x = ["bb1","bb2"]for (n in x) { println(n)}let y =[1,2,3] //创建元素类型为Int64的数组,包含元素1,2,3for (n in y) { println(n)}// x = y // 类型不匹配   
}

输出:

Feb
February    
0
0
0
a的元素个数4
a1
a2
a3
bb1
bb2
1
2
3

注意,Array 是一种长度不变的 Collection(集合) 类型,因此 Array 没有提供添加和删除元素的成员函数

注意,Array 是一种长度不变的 Collection(集合) 类型,因此 Array 没有提供添加和删除元素的成员函数

数组切片(返回新 Array)

从 数组 numbers 中截取(切片)出下标 1 到下标 3(左闭右开区间)的所有元素,组成一个新的 Array,然后把这个新数组赋值给常量 slice。

示例:

main() {let numbers: Array<Int64> = [10, 99, 3, 7]let slice = numbers[1..3]  // [99, 3] for (n in slice) { println(n)}
}

编译运行截图:

值类型的数组VArray

VArray<T, $N>,不能省略 <T, $N>,其中 T 表示该值类型数组的元素类型(如Int64Float32Bool 等【注】),$N 是一个固定的语法。通过 $ 加上一个 Int64 类型的数值字面量表示这个值类型数组的长度( $ 开头后接数字)。

【注】:由于运行时后端限制,当前 VArray<T, $N> 的元素类型 T 或 T 的成员不能包含引用类型(class 、 Array 、String等)、枚举类型、Lambda 表达式(CFunc 除外)以及未实例化的泛型类型。如:let v1: VArray<String, $3> = ["a1", "a2", "a3"] 是错误的——String 做不了 VArray 元素。


VArray 可以由一个数组的字面量来进行初始化。如:
let rgb: VArray<UInt8, $3> = [255, 128, 0]
也可以用构造函数进行初始化。其中,repeat 属于 Array 构造函数中的一个命名参数。如:
let c = VArray<Int64, $5>(repeat: 0)  // 生成 [0, 0, 0, 0, 0](5 个 0)。
let b = VArray<Int64, $5>({ i => i }) // lambda 表达式,生成 [0, 1, 2, 3, 4]。

用下标[] 操作符访问和修改元素:用 [] 加索引(索引必须是整数,从 0 开始),例如:
var a: VArray<Int64, $3> = [1, 2, 3]
let second = a[1]  // 取第2个元素(值为2)
a[2] = 4           // 修改第3个元素,现在数组是 [1, 2, 4]

用 size 获取 VArray 长度。例如:
var a: VArray<Int64, $3> = [1, 2, 3]
let s = a.size // 3

VArray<T, $N> 和 Array<T> 作为仓颉中两种固定长度的数组类型,基础操作有一定相似性,但也有不同,如遍历元素,目前版本(Cangjie语言首个LTS版本1.0.0)的 VArray 不支持 for-in遍历元素,可用常规写法:

main() {let v: VArray<Int64,$4> = [10, 20, 30, 40]// // 不支持for-in 循环遍历// for (i in v) {//     println(i)// }// 遍历元素,常规写法for (i in 0..v.size) {println("v[${i}] = ${v[i]}")}
}

输出:

v[0] = 10
v[1] = 20
v[2] = 30
v[3] = 40

与频繁使用引用类型 Array 相比,使用值类型 VArray 可以减少堆上内存分配和垃圾回收的压力。但是需要注意的是,由于值类型本身在传递和赋值时的拷贝,会产生额外的性能开销,因此建议不要在性能敏感场景使用较大长度的 VArray。

【——如何理解仓颉编程语言官方文档这句话?

VArray 和 Array 各有性能优劣,需要根据场景选择 —— 前者能减轻内存管理压力,但拷贝成本高;后者传递成本低,但会增加内存回收负担。

1. 为什么 VArray 能 “减少堆上内存分配和垃圾回收的压力”?

内存存储位置不同:

引用类型的 Array 数据通常存在“堆”里,每次创建 Array 都要在堆里申请一块空间;而堆里的空间不会自动释放,需要 “垃圾回收器”(Garbage Collector)定期来清理不用的空间。如果频繁创建 Array,堆里会堆积大量临时空间,垃圾回收器就需要频繁工作(压力大),甚至可能影响程序运行流畅度。

值类型的 VArray 数据通常存在“栈”里,栈的空间会随着变量的生命周期自动释放(比如函数执行完,栈上的 VArray 就自动消失),不需要垃圾回收器操心。因此,用 VArray 可以减少堆的使用,自然就减轻了垃圾回收的压力。

2. 为什么“不要在性能敏感场景使用较大长度的 VArray”?

值类型的“拷贝成本”问题:

值类型的特点是“赋值或传递时会完整拷贝数据”。比如一个长度为 1000 的 VArray,每次把它传给函数、或者赋值给另一个变量时,都要复制 1000 个元素(相当于把小盒子里的东西全倒出来,再一个个装进新盒子)。

如果 VArray 很小(比如长度 3),拷贝成本可以忽略;但如果是大长度(比如长度 10000),每次拷贝都会消耗大量时间和内存带宽。在“性能敏感场景”比如游戏的帧循环、高频数据处理)中,这种频繁的大拷贝会明显拖慢速度,反而不如用 Array(引用类型传递时只拷贝一个“地址”成本极低)。

总结:是 “内存管理压力” 和 “拷贝成本” 的权衡

小长度数组:用 VArray 更合适 —— 既减少堆内存和垃圾回收的麻烦,拷贝成本又低。

大长度数组:尤其在频繁传递 / 赋值的性能敏感场景,用 Array 更合适 —— 虽然有堆内存和垃圾回收的压力,但传递成本低,避免了大拷贝的性能损耗。

值类型的特点是 “赋值或传递时会完整拷贝数据”,引用类型的特点:赋值或传递时通常不会拷贝数据本身,而是拷贝 “引用”(即数据的内存地址)。这意味着多个引用类型变量可以共享同一份数据,修改其中一个变量指向的数据,会影响所有指向该数据的变量。

“通常不会拷贝”≠“永远不会拷贝”。

仓颉的 Array、String 等引用类型在写时复制(COW :copy-on-write) 优化下,第一次真正修改时仍可能触发一次惰性拷贝,从而把共享拆成两份数据。

因此:

• 日常代码层面:

“赋值/传参只拷引用,修改共享数据会互相可见”这句话成立。

• 底层实现细节:

如果对象内部做 COW,则真实的物理拷贝会延迟到第一次写操作。】

ArrayList 类型

这个是动态数组(也叫顺序表)。使用 ArrayList 类型需要导入 collection 包:

import std.collection.*

【ArrayList相关官方文档:https://cangjie-lang.cn/docs?url=%2F1.0.0%2Fuser_manual%2Fsource_zh_cn%2Fcollections%2Fcollection_arraylist.html 】

Array和ArrayList的异同点
Array:如果不需要增加和删除元素,但需要修改元素,就应该使用它。
ArrayList:如果需要频繁对元素增删查改,就应该使用它。相比 Array,ArrayList 既可以原地修改元素,也可以原地增加和删除元素。
使用 ArrayList 类型需要导入 collection 包:import std.collection.*
ArrayList 的可变性是一个非常有用的特征,可以让同一个 ArrayList 实例的所有引用都共享同样的元素,并且对它们统一进行修改。
不同元素类型的ArrayList是不同类型,不能互相赋值。如:
var intList: ArrayList<Int64> = ...
var strList: ArrayList<String> = ...
strList = intList  // 不合法报错,类型不匹配

仓颉提供了多种构造ArrayList的方式
// 1. 创建空的ArrayList
let emptyList = ArrayList<String>()

// 2. 指定初始容量创建
let preAllocated = ArrayList<String>(100)  // 预分配100个元素的空间

// 3. 从数组初始化
let fromArray = ArrayList<Int64>([0, 1, 2])

// 4. 从其他Collection初始化
let copyList = ArrayList<Int64>(fromArray)

// 5. 通过函数规则初始化
let funcInit = ArrayList<String>(2, {x: Int64 => x.toString()})

ArrayList 的基本用法
1.使用size属性获取大小:
let list = ArrayList<Int64>([0, 1, 2])
println("list大小: ${list.size}")
2.访问单个元素:使用下标语法。示例:

main(){let list = ArrayList<Int64>([0, 1, 2])let first = list[0]      // 正确,访问第一个元素let last = list[list.size - 1]  // 正确,访问最后一个元素  println(first)  // 0println(last)   // 2
}

3. 遍历元素:使用for-in循环。示例:

main(){let list = ArrayList<Int64>([0, 1, 2])for (i in list) {println("元素: ${i}")}
}

4.范围访问:支持Range语法(与 Array 相同)。示例:

main() {// 创建一个包含 0-9 的 ArrayListlet list = ArrayList<Int64>([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])// 1. 获取从索引 2 到 5(包含 2,不包含 5)的元素let sub1 = list[2..5]println("子序列 [2..5]: ${sub1}")  // 输出: [2, 3, 4]// 2. 获取从索引 0 到 3(包含 0,不包含 3)的元素let sub2 = list[0..3]println("子序列 [0..3]: ${sub2}")  // 输出: [0, 1, 2]// 3. 获取从索引 6 到末尾的元素let sub3 = list[6..list.size]println("子序列 [6..end]: ${sub3}")  // 输出: [6, 7, 8, 9]   
}

重要特性

1.引用类型特性:

    赋值时不拷贝数据,仅传递引用

    所有引用共享同一数据,一处修改处处可见

示例:

main() {let list1 = ArrayList<Int64>([0, 1, 2])let list2 = list1list2[0] = 3// list1 和 list2 现在都为 [3, 1, 2] for (i in list1) {println("list1元素: ${i}")}for (i in list2) {println("list2元素: ${i}")}    
}

2.ArrayList自动扩容机制:

    当元素数量超过当前容量时,会自动分配更大的内存。扩容操作有性能成本。

    可通过初始化时指定容量或使用reserve()方法预分配空间

示例:

import std.collection.*
import std.time.*  // 用于计时MonoTime.now()main() {// 创建未指定初始容量的空ArrayList(初始容量较小,假设为10)let list = ArrayList<Int64>(10)let start = MonoTime.now()  // 记录开始时间// 循环添加10000个元素,会多次触发自动扩容for (i in 0..10000) {list.add(i)}let end = MonoTime.now()  // 记录结束时间println("未预分配容量时,添加10000个元素耗时: ${end - start}毫秒")
}

说明:

每次扩容都需要申请新内存并复制现有元素,多次扩容会累积性能成本,导致总耗时较长。

也可以使用reserve()方法预分配。示例:

import std.collection.*
import std.time.*  // 用于计时,MonoTime.now()main() {let list = ArrayList<Int64>(10)list.reserve(10000)  // 手动预分配足够容量let start = MonoTime.now()for (i in 0..10000) {list.add(i)}let end = MonoTime.now()println("reserve预分配容量时,添加10000个元素耗时: ${end - start}毫秒")
}

运行对比,后面的优化方案的耗时会显著低于“无预分配”的场景。

最后给出一个ArrayList综合示例

import std.collection.*main() {// 1. 创建let list = ArrayList<String>()      // 空列表// let mut list = ArrayList<String>(100) // 预分配 100 容量// let list = ArrayList<Int64>([0, 1, 2]) // 用 Collection 初始化// 2. 增list.add("Apple")list.add("Banana")list.add(all: ["Orange", "Pear"])       // 批量追加  [Apple Banana Orange Pear]for (item in list) {println(item)}// 3. 插list.add("Grape", at: 1)                // 索引 1 处插入// 4. 删list.remove(at: 2)                      // 删除索引 2 的元素// 5. 改list[0] = "Pineapple"// 6. 查println("size = ${list.size}")          // size = 3println("first = ${list[0]}")           // first = Pineapple// 7. 遍历 [Pineapple Grape Orange Pear]for (item in list) {println(item)}    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/922152.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/922152.shtml
英文地址,请注明出处:http://en.pswp.cn/news/922152.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++微基础蓝桥杯之旅9.9-9.12

这里主要还是强制类型转换的使用//打印字符ASCII码值 //输入一个除空格以外的可见字符 //输出其ASCII值--十进制整数 #include <iostream> using namespace std;int main() {char ch;cin >> ch;//字符cout << (int)ch << endl; return 0; }//打印字符…

逻辑漏洞(上)- 突破功能限制漏洞、用户信息泄露(逻辑漏洞入门)

漏洞介绍&#xff1a; 在网络攻防实战中&#xff0c;常会遇到各种前端限制&#xff0c;绕过限制的方法大多是改包或者修改前端代码来实现的。 漏洞环境&#xff1a;docker docker-compose up -d 启动环境后&#xff1a;访问 http://127.0.0.1:8983/web/# 发现查询按钮是无法使用…

tsv文件简介

初步了解tsv文件在很多 OCR&#xff08;光学字符识别&#xff09;项目中&#xff0c;.tsv文件是标准的训练数据标注文件&#xff0c;主要用于存储 “图像路径 - 对应文本标签” 的映射关系&#xff0c;同时可能包含图像尺寸、文本长度等辅助信息&#xff0c;方便模型读取训练数…

apache poi 导出复杂的excel表格

如何导出复杂的excel 表格 如图表格&#xff0c;存在行和列的合并&#xff0c;边框&#xff0c;样式&#xff0c;颜色等。依赖<!-- https://mvnrepository.com/artifact/org.apache.poi/poi --><dependency><groupId>org.apache.poi</groupId><arti…

下载 Eclipse Temurin 的 OpenJDK 提示 “无法访问此网站 github.com 的响应时间过长”

打开 Eclipse Temurin 的 OpenJDK 的官网下载地址&#xff1a; https://adoptium.net/zh-CN/temurin/releases 问 deepseek&#xff1a; 国内网络&#xff0c;打不开github.com网页&#xff0c;提示github.com 的响应时间过长。 国内无法访问 GitHub 或访问缓慢&#xff0c;通…

C/C++类型转换

C/C类型转换 1. C类型转换 C 语言中的类型转换主要分为两种&#xff1a;隐式类型转换 (Implicit Conversion) - 由编译器自动完成。显式类型转换 (Explicit Conversion) - 由程序员强制指定&#xff0c;也称为强制类型转换。1.2 隐式类型转换 编译器在编译时自动进行的转换&…

【Java】Windows切换Java8和Java11

现在有些项目要升级到Java17, 所以需要切换不同的java版本。 如何安装Java8 由于已经安装了jJava8, 之前的安装文章&#xff1a;【Java】jdk8安装——英文版 如何安装Java17 Java17下载地址 https://www.oracle.com/java/technologies/downloads/#java17-windows 下载到电…

SQLite 数据库核心知识与 C 语言编程

一、数据库基础概念1.1 数据库分类根据规模和应用场景&#xff0c;数据库可分为以下几类&#xff1a;大型数据库&#xff1a;Oracle&#xff08;适用于企业级高并发、大容量场景&#xff09;中型数据库&#xff1a;MySQL、MSSQL&#xff08;适用于中小型系统、Web 应用&#xf…

Netty 调优篇:实战配置、性能监控与常见坑

&#x1f680; Netty 调优篇&#xff1a;实战配置、性能监控与常见坑前面我们已经深入了 Netty 的 线程模型、Pipeline、EventLoop、内存池、零拷贝和背压机制。 但在实际工作中&#xff0c;很多人踩坑的地方不是“源码没看懂”&#xff0c;而是 调优没做好。 今天我们就从三个…

Linux Node.js 安装及环境配置详细教程

如果您喜欢此文章&#xff0c;请收藏、点赞、评论&#xff0c;谢谢&#xff0c;祝您快乐每一天。 一、Node.js是什么 Node.js是一个基于Chrome V8引擎的[JavaScript运行环境]。 Node.js使用了一个事件驱动、非阻塞式I/O 的模型。 Node.js是一个让JavaScript运行在服务端的开…

呼叫中心系统IVR流程设计的心理学

呼叫中心的 IVR&#xff08;交互式语音应答&#xff09;系统看似是 “机器与用户的对话”&#xff0c;实则暗藏对用户心理的精准把握。其设计需围绕降低焦虑、提升效率、强化信任三大核心目标&#xff0c;背后依托认知心理学、行为心理学、情感心理学等理论支撑。一、认知负荷理…

一些开源或免费的网络管理工具

整理开源及免费网络管理工具推荐,涵盖监控、配置、安全、流量分析等场景,适用于不同规模的网络环境: ​一、网络监控与性能分析​ 1. ​Zabbix​ ​特点​:企业级监控方案,支持SNMP、IPMI、JMX等多种协议,提供实时仪表盘、告警通知和自动化发现功能。 ​适用场景​:服…

谷粒商城项目-P16快速开发-人人开源搭建后台管理系统

1.对脚手架工程进行改造 此项目选用的脚手架工程是人人开源 地址&#xff1a;人人开源 选择的是下图标红的renren-fast作为后端&#xff0c;renren-fast-vue作为前端 克隆上述两个项目 2.后端改造 2.1将renrenfast项目的git文件夹删除后&#xff0c;拖进后端代码文件夹中 2…

V少JS基础班之第八弹:this

文章目录一、 前言二、本节涉及知识点三、重点内容1、从新的角度认识this2、this是函数的参数3、this的值4、函数的调用1- 裸函数调用2- 函数作为构造函数调用3- 函数作为对象的方法调用4- 函数显示调用5- 箭头函数一、 前言 第八弹内容是this。this相对来说难度不大&#xff…

《堆的详解:结构、操作及堆排序算法》

目录 一.堆的概念与结构 1.1 堆的概念 1.2 堆性质&#xff1a; 1.3 堆的结构定义 二.堆的初始化和销毁 2.1 堆的初始化&#xff1a; 2.2 堆的销毁&#xff1a; 三.堆的插入数据(含向上调整算法的实现) 3.1 插入逻辑 3.2 插入函数 3.3 向上调整算法 三. 堆的删除数…

深入解析 Kubernetes 中的 Service 资源:为应用提供稳定的网络访问

什么是 Kubernetes 中的 Service&#xff1f; 在现代微服务架构中&#xff0c;服务之间的通信和负载均衡是至关重要的。尤其是在 Kubernetes 环境中&#xff0c;由于 Pod 是动态创建和销毁的&#xff0c;如何为一组 Pod 提供稳定的访问入口&#xff0c;成为了架构设计中的一个关…

使用Samba网络磁盘作为MacOS时间机器的远程备份磁盘

最近考虑MacOS系统升级&#xff0c;所以需要做磁盘备份&#xff0c;MacOS里有个备份磁盘很方便的工具&#xff1a;时间机器&#xff0c;可以自动定期备份磁盘&#xff0c;但是一般需要一个大点的移动硬盘插在macbook上选择其为备份磁盘&#xff0c;可惜我并没有移动硬盘&#x…

智能头盔实时监控系统设计与实现

智能头盔实时监控系统设计与实现 源码 https://gitee.com/intostars/csdn-demo/tree/master/src/views/smartHelmet 预览 一、功能概述 智能头盔实时监控系统是基于Vue 3和TypeScript开发的一套用于远程监控和控制智能头盔设备的前端应用模块。该系统通过WebSocket与后端服务…

Docker 学习笔记(八):容器运行时工具实践及 OpenStack 部署基础

容器管理工具Containerd nerdctl 实践 nerdctl管理存储 nerdctl命令创建容器的时候&#xff0c;可以使用-v选项将本地目录挂载给容器实现数据持久化 示例&#xff1a; [rootlocalhost ~]# mkdir /data [rootlocalhost ~]# nerdctl run -d -v /data:/data busybox -- sleep infi…

Unity键盘控制角色运动

以下是一个完整的Unity角色移动和跳跃脚本,支持WASD或方向键移动: 使用说明 确保组件设置正确: 确保您的游戏对象有一个CharacterController组件 如果没有,可以通过菜单 "Component -> Physics -> Character Controller" 添加 相机设置: 确保场景中有一…