【仓颉语言入门 · 第2课】
【仓颉语言入门 · 第2课】变量与常量:let / var 与基本数据类型
上节课我们已经在 CIDE 里跑通了第一个 Hello World,看到了第一行输出。但那个程序只会"背台词"——每次运行输出都一模一样。真正的程序要能处理数据:年龄、分数、价格、开关……这些值在运行过程中可能变化,就需要先找个地方把它们存起来。
本文是系列第 2 课:学会仓颉里怎么声明变量和常量、仓颉有哪些基本数据类型、类型之间怎么转换,最后兑现上节课的约定——用 CIDE 的调试器亲眼看着变量值一步步变化。
目录(系列导航)
整套路线共 7 个模块、30 课,按每周 2~3 课的节奏,大约 2~3 个月可以走完一遍:
| 模块 | 课次 | 内容 |
|---|---|---|
| 一、环境与入门 | 01~05 | 环境搭建与 Hello World、变量与基本类型、运算符与输入输出、分支、循环 |
| 二、常用类型与数据组织 | 06~10 | 字符串、数组与区间、ArrayList/HashMap/HashSet、可空类型、错误处理 |
| 三、函数与函数式 | 11~14 | 函数、Lambda 与高阶函数、闭包、迭代器与惰性序列 |
| 四、面向对象与类型系统 | 15~20 | struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展 |
| 五、工程化与标准库 | 21~25 | cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试 |
| 六、并发编程 | 26~28 | 线程、Channel 通道与同步原语、并发实战 |
| 七、项目实战 | 29~30 | 命令行小工具、GeoJSON 数据处理实战 |
- 环境搭建与第一个仓颉程序
- 变量与常量:let / var 与基本数据类型(本文)
- 运算符与标准输入输出
- 分支结构:if 与 when 表达式
- 循环结构:while / for / Range
- 字符串详解与字符串插值
- 数组 Array 与区间 Range
- 集合框架:ArrayList、HashMap、HashSet
- 可空类型
?与 Option - 错误处理:异常机制与 Result
- 函数定义、参数与返回值
- Lambda 表达式与高阶函数
- 闭包、作用域与函数类型
- 迭代器 Iterator 与 Sequence
- 结构体 struct 与类 class
- 构造函数、属性与方法
- 接口 interface 与实现
- 枚举 enum、代数数据类型与 match 模式匹配
- 泛型编程
- 扩展、类型别名与可见性控制
- cjpm 包管理与多文件项目组织
- 文件与目录 IO
- JSON 处理(结合 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}")
输出:
pi=3.141590, score=98
声明语句的三个部分:
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 initialization
2.5 命名规则与约定
仓颉标识符的规则很宽松:
- 以**字母、下划线或其他 Unicode 字符(包括中文)**开头,后面可以跟数字;
- 大小写敏感:
age和Age是两个不同的名字; - 不能使用
let、var、if等关键字; - 社区约定:变量/函数名用小驼峰(
userName、totalScore),类型名用大驼峰(Int64、ArrayList)。
let userName = "张三"
var totalScore = 0
let 分数 = 100 // 中文标识符合法,但工程上不推荐
⚠️ 中文变量名虽然编译器支持,但为了团队协作和避免编码问题,正式项目还是老老实实用英文。
三、仓颉的基本数据类型
仓颉是强类型静态语言:每个值都有确定的类型,类型不同不能随便混用。基本(内置)类型如下:
3.1 整数类型
| 类型 | 位宽 | 取值范围 |
|---|---|---|
Int8 | 8 位 | -128 ~ 127 |
Int16 | 16 位 | -32,768 ~ 32,767 |
Int32 | 32 位 | 约 -21 亿 ~ 21 亿 |
Int64 | 64 位 | 约 -9.2×10¹⁸ ~ 9.2×10¹⁸ |
UInt8~UInt64 | 同上 | 无符号(没有负数,从 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 255
3.2 浮点类型(小数)
| 类型 | 位宽 | 精度 |
|---|---|---|
Float32 | 32 位 | 单精度,约 7 位有效数字 |
Float64 | 64 位 | 双精度,约 15 位有效数字,最常用 |
let pi: Float64 = 3.14159 // 小数字面量默认推导为 Float64
let f: Float32 = Float32(2.5) // 需要 32 位时显式转换
println("${pi} ${f}")
输出:
3.141590 2.500000
💡
Float32(2.5)这种"把类型名当函数调用"的写法,就是仓颉的显式类型转换,3.7 节细讲。
3.3 布尔类型 Bool
布尔类型 Bool 只有两个值:true(真)和 false(假),专门用于表示"是/否"这种二元状态,第 4 课 if 分支会大量用到:
let isStudent: Bool = true
let vip = false
println("${isStudent} ${vip}")
输出:
true false
3.4 字符类型 Rune
仓颉用 Rune 表示单个字符,字面量用 r 加单引号:r'A'、r'好'。它本质是一个 32 位的 Unicode 码点,可以表示世界上几乎所有文字:
let grade: Rune = r'A'
let hanzi = r'好'
println("${grade} ${hanzi}") // 直接插值输出字符本身
println("好 的 Unicode 码点:${UInt32(hanzi)}") // 转成整数看码点值
输出:
A 好
好 的 Unicode 码点:22909
注意区分:
r'A'是 Rune 字符(单引号、带 r 前缀);"A"是 String 字符串(双引号),哪怕只有一个字符也是字符串。字符串第 6 课详讲。
常用转义字符和字符串里一样:r'\t'(制表符)、r'\n'(换行)、r'\\'(反斜杠)、r'\''(单引号)。
3.5 Unit 类型
Unit 类型只有一个值 (),表示"没有有意义的返回值",类似其他语言里的 void。目前先混个脸熟,第 11 课讲无返回值函数时会再见面:
let u = ()
println("${u}") // 输出 ()
3.6 一张图记住类型选择
需要整数? → Int64(99% 的场景)
需要小数? → Float64
表示是非开关? → Bool
处理单个字符? → Rune
一段文本? → String(第 6 课)
与字节/协议打交道?→ UInt8 等定宽类型
3.7 显式类型转换:仓颉不会"偷偷"帮你转
强类型意味着整数和小数之间不会自动转换。比如下面这行就编译不过:
var count: Int64 = 3.5
error: 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 = r'A' // 字符:一个 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 → Debug(Shift + 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上——声明语句执行前它还不存在,提示框里是找不到它的,这能帮你直观理解"变量的作用域从声明处开始"。
调试结束点停止按钮,结束调试会话。
五、常见问题 FAQ
Q1:到底什么时候用 let,什么时候用 var?
默认一律用 let;当你确实需要在后面给它重新赋值(比如累加、状态翻转)时,再改成 var。拿不准就先写 let,编译器报错了再改,成本极低。
Q2:Int 和 Int64 有什么区别?写哪个?
Int 的位宽跟随平台,在主流 64 位平台上就是 Int64。学习期和普通业务直接用 Int64 最省心;写需要跨平台/跨语言交换的底层数据时,定宽类型语义更明确。
Q3:为什么 var count: Int64 = 3.5 会报错?3.5 不就是个数吗?
仓颉是强类型语言,整数盒子不能直接装小数,编译器不会替你做隐式转换。写 Float64(3.5) 放进浮点变量,或写 Int64(3.5) 显式取整。
Q4:Int64(3.99) 是四舍五入得到 4 吗?
不是,是截断,结果为 3。需要四舍五入时使用标准库提供的取整函数,后面课程会讲。
Q5:r'A' 和 "A" 有什么区别?
r'A' 是 Rune(单字符,32 位码点);"A" 是 String(字符串)。即使看起来只有一个字符,双引号的也是字符串,二者类型不同。
Q6:变量声明时可以先不赋值吗?
var 可以(必须带类型标注),但编译器保证你在第一次读取它之前完成赋值,否则报 used before initialization;let 声明时必须直接给值。
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 + 仓颉 SDK(1.1.3)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入,以你本地版本为准,欢迎评论区交流。
📥 工具下载
本系列全程使用的仓颉 IDE —— CIDE(免费开源、社区版):
- GitCode 仓库 / 安装包下载:https://gitcode.com/wp_upala/cide
- 打开页面后进入 发行版(Releases),下载
CIDE-<版本>-x64-Setup.exe安装即可 - 仓颉 SDK 请前往仓颉编程语言官网下载:https://cangjie-lang.cn
更多推荐




所有评论(0)