
【仓颉语言入门 · 第2课】变量与常量let / var 与基本数据类型上节课我们已经在 CIDE 里跑通了第一个 Hello World看到了第一行输出。但那个程序只会背台词——每次运行输出都一模一样。真正的程序要能处理数据年龄、分数、价格、开关……这些值在运行过程中可能变化就需要先找个地方把它们存起来。本文是系列第 2 课学会仓颉里怎么声明变量和常量、仓颉有哪些基本数据类型、类型之间怎么转换最后兑现上节课的约定——用 CIDE 的调试器亲眼看着变量值一步步变化。目录系列导航整套路线共7 个模块、30 课按每周 23 课的节奏大约 23 个月可以走完一遍模块课次内容一、环境与入门0105环境搭建与 Hello World、变量与基本类型、运算符与输入输出、分支、循环二、常用类型与数据组织0610字符串、数组与区间、ArrayList/HashMap/HashSet、可空类型、错误处理三、函数与函数式1114函数、Lambda 与高阶函数、闭包、迭代器与惰性序列四、面向对象与类型系统1520struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展五、工程化与标准库2125cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试六、并发编程2628线程、Channel 通道与同步原语、并发实战七、项目实战2930命令行小工具、GeoJSON 数据处理实战环境搭建与第一个仓颉程序变量与常量let / var 与基本数据类型本文运算符与标准输入输出分支结构if 与 when 表达式循环结构while / for / Range字符串详解与字符串插值数组 Array 与区间 Range集合框架ArrayList、HashMap、HashSet可空类型?与 Option错误处理异常机制与 Result函数定义、参数与返回值Lambda 表达式与高阶函数闭包、作用域与函数类型迭代器 Iterator 与 Sequence结构体 struct 与类 class构造函数、属性与方法接口 interface 与实现枚举 enum、代数数据类型与 match 模式匹配泛型编程扩展、类型别名与可见性控制cjpm 包管理与多文件项目组织文件与目录 IOJSON 处理结合 stdx 扩展库网络编程入门单元测试并发基础线程的创建与等待Channel 通道与同步原语并发实战多线程任务处理实战一带文件持久化的命令行小工具实战二GeoJSON 数据处理程序一、为什么需要变量先看一个没有变量的程序println(张三今年 18 岁)如果我想让年龄加 1就得手动改代码、重新编译——这显然不是计算。有了变量我们可以给一个值起名字之后用名字引用它、修改它var age: Int64 18 println(张三今年 ${age} 岁) age 19 println(一年后张三 ${age} 岁)你可以把变量理解成一个贴了标签的盒子标签是变量名age盒子里装的是值18类型标注: Int64则规定了这个盒子只能装 64 位整数。 代码里的${age}是字符串插值把变量的值拼进字符串里一起输出。字符串本身第 6 课才系统讲今天照抄即可。二、let 与 var常量和变量仓颉用两个关键字声明绑定关键字含义赋值后能否再改使用场景let常量绑定不可变不能重新赋值整个生命周期值不变如 π、配置项var变量绑定可变可以反复重新赋值值会随程序运行变化如计数器、年龄2.1 基本写法let pi: Float64 3.14159 // 常量圆周率不允许再改 var score: Int64 95 // 变量考试分数后面可以改 score 98 // 重新赋值合法 println(pi${pi}, score${score})输出pi3.141590, score98声明语句的三个部分var score : Int64 95 │ │ │ │ 关键字 变量名 类型标注 初始值2.2 类型标注可以省略类型推导如果声明时就给了初始值编译器能根据值自动推断类型这时: 类型可以不写let age 18 // 整数字面量64 位平台上推导为 Int64 var price 9.9 // 小数字面量推导为 Float64 let passed true // 推导为 Bool这叫类型推导——类型并没有消失只是编译器替你写了。我们可以做个实验验证默认类型let n 42 let x: Int32 n // 故意把 Int64 的值放进 Int32 的盒子编译器直接报错error: mismatched types main.cj:10:20 | 10 | let x: Int32 n | ^ expected Int32, found Int64这证明了整数字面量默认被推导成Int64。2.3 let 赋值后不能改let绑定再次赋值会编译失败而且报错信息非常直白let pi: Float64 3.14 pi 3.15 // 错误不能给不可变的值赋值error: cannot assign to immutable value main.cj:5:5 | 5 | pi 3.15 | ^^^^^^^^^ | note: variable pi is immutable main.cj:4:9编码建议默认优先用let。只有当这个值确实需要被修改时才用var。不可变的值更安全——读代码的人不用时刻担心它在别处被改掉编译器也能帮你拦住误修改。这是现代语言共同的推荐风格。2.4 先声明后赋值var允许声明时先不给初始值但必须写上类型并且在第一次使用之前完成赋值var total: Int64 // 先声明盒子先准备好 total 100 // 再赋值 println(${total}) // 使用时已有值合法编译器会做明确赋值检查——如果用的时候还没赋值照样编译不过var total: Int64 println(${total}) // 错误使用前未初始化error: variable total is used before initialization2.5 命名规则与约定仓颉标识符的规则很宽松以**字母、下划线或其他 Unicode 字符包括中文**开头后面可以跟数字大小写敏感age和Age是两个不同的名字不能使用let、var、if等关键字社区约定变量/函数名用小驼峰userName、totalScore类型名用大驼峰Int64、ArrayList。let userName 张三 var totalScore 0 let 分数 100 // 中文标识符合法但工程上不推荐⚠️ 中文变量名虽然编译器支持但为了团队协作和避免编码问题正式项目还是老老实实用英文。三、仓颉的基本数据类型仓颉是强类型静态语言每个值都有确定的类型类型不同不能随便混用。基本内置类型如下3.1 整数类型类型位宽取值范围Int88 位-128 127Int1616 位-32,768 32,767Int3232 位约 -21 亿 21 亿Int6464 位约 -9.2×10¹⁸ 9.2×10¹⁸UInt8UInt64同上无符号没有负数从 0 开始Int/UInt平台相关主流 64 位平台上等价于Int64/UInt64日常写业务代码直接用Int64或省略标注让编译器推导即可处理二进制数据、网络协议时才需要精确控制位宽用UInt8字节等类型。整数字面量的花样写法编译器实测全部合法let population 1_000_000 // 下划线分隔长数字一眼看清值就是 1000000 let bin 0b1010 // 0b 开头二进制等于十进制 10 let oct 0o17 // 0o 开头八进制等于十进制 15 let hex 0xFF // 0x 开头十六进制等于十进制 255 println(${population} ${bin} ${oct} ${hex})输出1000000 10 15 2553.2 浮点类型小数类型位宽精度Float3232 位单精度约 7 位有效数字Float6464 位双精度约 15 位有效数字最常用let pi: Float64 3.14159 // 小数字面量默认推导为 Float64 let f: Float32 Float32(2.5) // 需要 32 位时显式转换 println(${pi} ${f})输出3.141590 2.500000Float32(2.5)这种把类型名当函数调用的写法就是仓颉的显式类型转换3.7 节细讲。3.3 布尔类型 Bool布尔类型Bool只有两个值true真和false假专门用于表示是/否这种二元状态第 4 课if分支会大量用到let isStudent: Bool true let vip false println(${isStudent} ${vip})输出true false3.4 字符类型 Rune仓颉用Rune表示单个字符字面量用r 加单引号rA、r好。它本质是一个 32 位的 Unicode 码点可以表示世界上几乎所有文字let grade: Rune rA let hanzi r好 println(${grade} ${hanzi}) // 直接插值输出字符本身 println(好 的 Unicode 码点${UInt32(hanzi)}) // 转成整数看码点值输出A 好 好 的 Unicode 码点22909注意区分rA是Rune 字符单引号、带 r 前缀A是String 字符串双引号哪怕只有一个字符也是字符串。字符串第 6 课详讲。常用转义字符和字符串里一样r\t制表符、r\n换行、r\\反斜杠、r\单引号。3.5 Unit 类型Unit类型只有一个值()表示没有有意义的返回值类似其他语言里的void。目前先混个脸熟第 11 课讲无返回值函数时会再见面let u () println(${u}) // 输出 ()3.6 一张图记住类型选择需要整数 → Int6499% 的场景 需要小数 → Float64 表示是非开关 → Bool 处理单个字符 → Rune 一段文本 → String第 6 课 与字节/协议打交道→ UInt8 等定宽类型3.7 显式类型转换仓颉不会偷偷帮你转强类型意味着整数和小数之间不会自动转换。比如下面这行就编译不过var count: Int64 3.5error: cannot convert a floating-point literal to type Int64想转换必须在代码里显式写出来用类型名(值)的形式let i: Int64 10 let d: Float64 Float64(i) // 整数 - 小数安全无损10.000000 let back: Int64 Int64(3.99) // 小数 - 整数小数部分直接截断3 println(${d} ${back})输出10.000000 3⚠️ 注意Int64(3.99)的结果是3不是 4——浮点转整数是截断直接砍掉小数部分不做四舍五入。想要四舍五入以后用标准库的取整函数。同样Int32和Int64这种不同位宽的整数之间赋值也需要显式转换。看似麻烦但它杜绝了一大批精度悄悄丢失的隐蔽 bug。四、在 CIDE 调试器里看见变量上节课说过学完变量要正式玩调试器。现在正是时候——调试器最擅长的就是让你暂停程序逐个检查变量的当前值。4.1 准备程序继续用上节课的helloCangjie项目或者新建一个把src/main.cj替换为下面这份个人档案代码它把今天学的每种类型都用上了package helloCangjie main(): Int64 { // 个人档案每种基本类型亮个相 let name 张三 // String 字符串第 6 课详讲 var age: Int64 18 // 64 位整数var 表示后面可以改 let height: Float64 1.75 // 64 位小数单位米 let isStudent: Bool true // 布尔只有 true / false let level: Rune rA // 字符一个 Unicode 码点 age 19 // 过了一年长一岁var 变量允许重新赋值 println( 个人档案 ) println(姓名${name}) println(年龄${age}) println(身高${height} 米) println(是否学生${isStudent}) println(等级${level}) println(字符 好 的 Unicode 码点${UInt32(r好)}) // 身高换算成厘米Float64 - Int64 必须显式转换小数部分直接截断 let heightCm Int64(height * 100.0) println(身高取整${heightCm} 厘米) return 0 }4.2 调试三步走在第一行println( 个人档案 )左侧行号沟槽点一下打上红点断点菜单Run → DebugShift F9启动调试程序在断点处暂停当前行高亮观察调试工具窗口Variables变量面板当前作用域内的所有局部变量一览无余——名字、值、类型三列。你会看到name 张三、age 19赋值语句已执行过、height 1.75、isStudent true、level A鼠标悬停把光标移到编辑器里任意变量名上悬停会浮出一个小提示框显示它的当前值不用来回切面板按F8单步跳过逐行执行每按一下看 Variables 面板和 Result 面板的变化——特别是走到let heightCm ...那一行之前和之后看着heightCm从不存在变成175。 配图建议断点命中后 Variables 面板中各变量名/值/类型三列 编辑器里鼠标悬停 age 浮出值的截图。两个小实验把断点打在age 19那一行F8 执行前后对比age的值18 → 19试试把鼠标悬停在heightCm上——声明语句执行前它还不存在提示框里是找不到它的这能帮你直观理解变量的作用域从声明处开始。调试结束点停止按钮结束调试会话。五、常见问题 FAQQ1到底什么时候用let什么时候用var默认一律用let当你确实需要在后面给它重新赋值比如累加、状态翻转时再改成var。拿不准就先写let编译器报错了再改成本极低。Q2Int和Int64有什么区别写哪个Int的位宽跟随平台在主流 64 位平台上就是Int64。学习期和普通业务直接用Int64最省心写需要跨平台/跨语言交换的底层数据时定宽类型语义更明确。Q3为什么var count: Int64 3.5会报错3.5 不就是个数吗仓颉是强类型语言整数盒子不能直接装小数编译器不会替你做隐式转换。写Float64(3.5)放进浮点变量或写Int64(3.5)显式取整。Q4Int64(3.99)是四舍五入得到 4 吗不是是截断结果为 3。需要四舍五入时使用标准库提供的取整函数后面课程会讲。Q5rA和A有什么区别rA是Rune单字符32 位码点A是String字符串。即使看起来只有一个字符双引号的也是字符串二者类型不同。Q6变量声明时可以先不赋值吗var可以必须带类型标注但编译器保证你在第一次读取它之前完成赋值否则报used before initializationlet声明时必须直接给值。Q7调试暂停了但 Variables 面板是空的先确认程序确实停在断点当前行高亮再看面板里选中的栈帧是不是main。另外面板只显示当前作用域、已经声明过的局部变量作用域外和声明语句执行前的变量自然看不到。六、课后练习声明 5 个变量描述你自己姓名字符串、年龄Int64、身高Float64、是否学生Bool、等级Rune全部输出然后把其中会变化的项改成var并重新赋值一次对比哪些必须是let、哪些必须是var。故意把一个let常量重新赋值编译并阅读 Build 面板的报错cannot assign to immutable value把报错文案和 2.3 节对照一下。先心算Int64(7.8)、Int64(-2.3)的结果分别是几写程序验证你的答案体会截断和四舍五入的区别。在练习 1 的程序里打断点用 Shift F9 启动调试F8 单步走完全程在 Variables 面板里确认每个变量的类型列显示的正是你预期的类型。下节预告现在我们会存数据了但还不会算数据。第 3 课进入运算符与标准输入输出算术、比较、逻辑运算符以及让程序接收用户键盘输入——到那时程序就能根据不同的输入产生不同的输出真正活起来。我们下节课见系列说明本系列基于 Windows 平台 CIDE 仓颉 SDK1.1.3编写所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入以你本地版本为准欢迎评论区交流。 工具下载本系列全程使用的仓颉 IDE ——CIDE免费开源、社区版GitCode 仓库 / 安装包下载https://gitcode.com/wp_upala/cide打开页面后进入发行版Releases下载CIDE-版本-x64-Setup.exe安装即可仓颉 SDK 请前往仓颉编程语言官网下载https://cangjie-lang.cn