欢迎光临
我们一直在努力

【仓颉语言入门 · 第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)
    未经允许不得转载:171主机测评 » 【仓颉语言入门 · 第2课】
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址