【仓颉语言入门 · 第3课】
【仓颉语言入门 · 第3课】运算符与标准输入输出
上节课我们学会了用
let/var把数据存进变量,也在调试器里亲眼看到了变量的值。但程序目前只会"顺序往下走":数据是代码里写死的,输出自然也是固定的。本文是系列第 3 课:先学会仓颉的各类运算符——让数据真正参与计算;再打通程序与外界的通道——标准输入,让程序接收你的键盘输入。学完之后,同一个程序每次运行都能根据不同的输入给出不同的结果,程序第一次真正"活"起来。
目录(系列导航)
整套路线共 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 数据处理程序
一、算术运算符:加减乘除、取余与求幂
最常用的算术运算符有 7 个:
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
+ | 加(也可拼接字符串) | 2 + 3 | 5 |
- | 减(也可作一元负号) | 3 - 1 | 2 |
* | 乘 | 3 * 4 | 12 |
/ | 除 | 7 / 3 | 2(整数相除) |
% | 取余(仅支持整数) | 7 % 3 | 1 |
** | 求幂 | 2 ** 3 | 8 |
-x | 一元负号 | -8 | -8 |
先跑一段整体感受一下:
main(): Int64 {
println(2 + 3) // 5
println(7 - 4) // 3
println(3 * 4) // 12
println(7 / 3) // 2
println(7 % 3) // 1
println(2 ** 3) // 8,2 的 3 次方
return 0
}
1.1 整数除法:向 0 截断
两个整数相除,结果仍然是整数,小数部分会被向 0 的方向直接砍掉(截断,不是四舍五入):
println(7 / 3) // 2
println(-7 / 3) // -2,向 0 截断,而不是 -3
println(7 / -3) // -2
想要带小数的真实结果,先把操作数转成 Float64(第 2 课学过的显式转换):
println(7.0 / 3.0) // 2.333333
println(Float64(7) / Float64(3)) // 2.333333
⚠️ 这也呼应第 2 课的铁律:
/两侧类型必须一致。7 / 3.0会直接编译报错,要么都是整数、要么都是小数。
1.2 取余 %:先整除,再求剩
仓颉里 a % b 的值严格定义为 a - b * (a / b),所以余数的正负号跟随被除数 a:
println(4 % 3) // 1
println(-4 % 3) // -1
println(4 % -3) // 1
println(-4 % -3) // -1
% 只支持整数操作数。一个非常实用的场景是取某一位数字:一个整数对 10 取余得到个位,除以 10 再取余得到十位,课后练习会用到。
💡 另外,
+除了做加法,还能拼接两个字符串("abc" + "ABC"得到"abcABC"),字符串第 6 课细讲。
1.3 求幂 **:注意类型配对
** 计算幂,类型规则比其他运算符严格:
- 左操作数是
Int64时,右操作数必须是UInt64(写整数字面量时编译器会自动适配,不用手写转换),结果为Int64; - 左操作数是
Float64时,右操作数可以是Int64或Float64,结果为Float64。
let n: Int64 = 5
println(2 ** 3) // 8
println(n ** 2) // 25,字面量 2 自动当作 UInt64
println(2.0 ** 3) // 8.000000
println(2.0 ** 0.5) // 1.414214,开平方就是 0.5 次幂
⚠️ 注意求幂是
**,不是很多其他语言里的^——仓颉里的^是按位异或,千万别搞混。
1.4 溢出和除零:仓颉会"大声报错"
仓颉对算术错误采取零容忍策略:整数运算一旦溢出,运行时会直接抛出错误,而不是像某些语言那样悄悄回绕成一个奇怪的负数;除以 0 同样会立即终止并报错。这意味着问题暴露得早、定位得快,是在帮我们避坑。
二、赋值运算符:=、复合赋值、++ / --
2.1 普通赋值:把右边装进左边
第 2 课已经反复用过 =:它的含义是"把右侧的值赋给左侧的变量"。有两个初学者必知的限制:
限制一:赋值表达式没有值(类型是 Unit),不能连等、不能塞进条件里。
var a: Int64 = 0
var b: Int64 = 0
// a = b = 0 // 编译错误:不支持链式赋值
// if (a = 5) { } // 编译错误:赋值不是 true/false,不能当条件
a = 5 // 单独成句,这才是正确姿势
这其实是一道保险:避免把"判断相等 =="误写成"赋值 ="这类经典 bug。
2.2 复合赋值:运算 + 赋值一步到位
+=、-=、*=、/=、%=、**= 等复合赋值,等价于"先运算、再赋值":
var a: Int64 = 10
a += 2 // 等价于 a = a + 2,a = 12
a -= 2 // a = 10
a **= 2 // a = 100
a *= 2 // a = 200
a /= 10 // a = 20
a %= 6 // a = 2
println(a)
位运算也有对应的 <<=、>>=、&=、^=、|=,逻辑运算有 &&=、||=。
2.3 自增自减:只能写在变量后面
仓颉支持 ++ 和 --,但规则比其他语言严格:只能作后缀(只有 i++,没有 ++i),且只能用于可变的整数变量(var):
var i: Int64 = 0
i++ // 等价于 i += 1,i = 1
i-- // i = 0
println(i)
let n = 3
// n++ // 编译错误:let 常量不能自增
// ++i // 编译错误:仓颉没有前缀写法
2.4 小技巧:元组多赋值与一行交换
借助元组(Tuple,一对按顺序打包的值,用圆括号表示),可以同时给多个变量赋值:
var a = 3
var b = 5
(a, b) = (b, a)
println("a=${a}, b=${b}") // a=5, b=3,一行完成交换!
传统语言交换两个变量需要第三个"临时变量"中转,仓颉直接 (a, b) = (b, a) 即可,右边会先整体算好再赋给左边。不想要的位置可以用下划线 _ 忽略。
三、比较运算符与逻辑运算符
3.1 比较运算:结果一定是 Bool
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
== | 相等 | 3 == 3 | true |
!= | 不等 | 3 != 4 | true |
< | 小于 | 3 < 4 | true |
<= | 小于等于 | 3 <= 3 | true |
> | 大于 | 3 > 4 | false |
>= | 大于等于 | 3 >= 4 | false |
比较表达式的结果一定是 Bool(true / false),这正是第 4 课 if 分支的判断依据。同样要求两侧类型一致:
println(3.14 == 3.15) // false
println(3 < 3.0) // 编译错误:Int64 不能直接和 Float64 比较
字符串也可以直接用 == 比较内容是否相同(第 6 课细讲)。
3.2 逻辑运算:与、或、非
| 运算符 | 含义 | 规则 |
|---|---|---|
&& | 逻辑与(并且) | 两边都为 true,结果才为 true |
| ` | ` | |
! | 逻辑非(取反) | !true 为 false,!false 为 true |
let score = 95
println(score >= 0 && score <= 100) // true:成绩在合法区间内
println(score < 60 || score > 100) // false:既不是不及格也不是越界值
println(!(score < 60)) // true:取反,成绩"不是不及格"
3.3 短路求值:能不算就不算
&& 和 || 都采用短路求值:
expr1 && expr2:如果expr1已经是false,结果注定为假,expr2根本不会执行;expr1 || expr2:如果expr1已经是true,结果注定为真,expr2根本不会执行。
这不仅是省一次计算,还能用来"挡刀"——把危险判断放在前面:
let a = 10
let b = 0
println(b != 0 && a / b > 2) // false:b != 0 为假,后面的除零运算被短路,不会执行
如果把两个条件顺序写反,除零错误就会先发生。记住口诀:把把关条件放在 && 最左边。
3.4 扩展视野:位运算符
仓颉还提供直接操作二进制位的位运算符,操作数只能是整数,入门阶段了解即可:
| 运算符 | 含义 | 示例 | 结果 |
|---|---|---|---|
& | 按位与 | 10 & 15 | 10 |
| ` | ` | 按位或 | `10 |
^ | 按位异或 | 10 ^ 15 | 5 |
<< | 左移(相当于乘 2 的幂) | 10 << 1 | 20 |
>> | 右移(相当于除 2 的幂) | 10 >> 1 | 5 |
! | 按位求反(操作数为整数时) | !10 | -11 |
注意 ! 是个"两面派":作用在 Bool 上是逻辑非,作用在整数上是按位求反,编译器按操作数类型自动区分。
四、三元条件运算符与运算优先级
4.1 三元运算符 ? ::一行二选一
当你想"根据一个布尔条件,在两个值里挑一个"时,可以用三元条件运算符:
条件 ? 条件为真时取这个值 : 条件为假时取这个值
let age = 20
let result = age >= 18 ? "成年" : "未成年"
println(result) // 成年
它和 &&、|| 一样有类型要求:: 两侧两个分支的结果类型必须一致(上例两边都是字符串)。更复杂的分支逻辑第 4 课会用 if / when 来写,可读性更好;简单的二选一,三元运算符最紧凑。
4.2 运算优先级:拿不准,就加括号
一个表达式里出现多个运算符时,编译器按固定的优先级决定先算谁。简化版从高到低大致是:
一元运算符(- ! ++ --)
↓
求幂 **
↓
乘除余 * / %
↓
加减 + -
↓
移位 << >>
↓
比较 < > <= >=
↓
判等 == !=
↓
位运算 & ^ |
↓
逻辑与 &&
↓
逻辑或 ||
↓
三元 ? :
↓
赋值 = 与复合赋值
比如 2 + 3 * 4 得 14 而不是 20,因为 * 优先于 +。不需要死记这张表——实际开发中一条通用建议:拿不准优先级,或者想让运算顺序一目了然时,果断加括号。括号不产生任何运行时开销,却能让读代码的人(包括三个月后的你自己)立刻明白意图:
println((2 + 3) * 4) // 20,括号强制先算加法
println(2 + 3 * 4) // 14
五、标准输出回顾:print 与 println
前两课一直在用 println,这里正式做个对比:
println(x):输出内容后自动换行,光标移到下一行开头;print(x):输出内容后不换行,光标紧跟在内容后面。
print 最典型的用途就是做输入提示语——让光标停在提示文字后面等用户输入:
print("请输入:") // 运行效果:请输入:| (光标停在冒号后面)
println("第一行")
println("第二行")
输出多个值时依然推荐用第 2 课的字符串插值 ${...},它比一连串 + 拼接清晰得多:
let a = 3
let b = 5
println("${a} + ${b} = ${a + b}") // 3 + 5 = 8
六、标准输入:让程序听你说话
6.1 三步走:import、getStdIn、readln
仓颉的标准输入通过标准库 std.env 包提供,固定写法分三步:
package helloCangjie
import std.env.* // ① 引入标准输入输出环境包
main(): Int64 {
print("请输入你的名字:")
let name = getStdIn().readln().getOrThrow() // ② 读取一行输入,③ 取出字符串
println("你好,${name}!欢迎学习仓颉。")
return 0
}
逐段拆解第②③步这一长串:
getStdIn():拿到标准输入对象(默认对应键盘);.readln():读取一整行,直到你按下回车。它的返回类型是?String——表示"可能读到字符串,也可能读不到"(比如输入流被关闭)。这是仓颉的Option可空类型,第 9 课专门讲;.getOrThrow():在确认能读到内容时,把里面真正的String取出来。学习阶段照抄这个固定搭配即可,第 10 课讲错误处理时再学更稳妥的处理方式。
运行后程序会停在 readln() 处等待输入,用键盘打字、回车,输入的那一整行(不含末尾回车换行)就成了字符串变量 name 的值。
请输入你的名字:小明
你好,小明!欢迎学习仓颉。
💡 如果担心用户输入时前后误带空格,可以在末尾再接一个
.trim()去掉首尾空白:getStdIn().readln().getOrThrow().trim()。
6.2 读入数字:Int64.parse 转换
readln() 读进来的永远是字符串。想让输入参与算术运算,要用 Int64.parse(...) 把数字字符串转成 Int64。使用 parse 需要再引入一个 std.convert 包:
package helloCangjie
import std.env.*
import std.convert.* // Int64.parse 所在的转换包
main(): Int64 {
print("请输入你的年龄:")
let age = Int64.parse(getStdIn().readln().getOrThrow())
println("再过十年,你就 ${age + 10} 岁了。")
return 0
}
运行效果:
请输入你的年龄:18
再过十年,你就 28 岁了。
⚠️
parse是"严格"的:输入18能正常转换,但如果输入abc之类不是数字的内容,转换会失败并抛出异常、程序终止。第 10 课学完错误处理后,可以改用Int64.tryParse(...)——它不抛异常,而是返回一个"可能有、可能没有"的Option,到时我们再给输入程序加上容错。
七、CIDE 实操:交互式两数计算器(综合实战)
把今天的知识点串起来:读入用户输入的两个整数,输出和、差、积、整除、余数、精确商和平方。除数为 0 时还用刚学的三元运算符给出提示。
7.1 编写程序
继续使用 helloCangjie 项目(也可 Ctrl + N 新建),把 src/main.cj 替换为:
package helloCangjie
import std.env.*
import std.convert.*
main(): Int64 {
print("请输入第一个整数:")
let a = Int64.parse(getStdIn().readln().getOrThrow())
print("请输入第二个整数:")
let b = Int64.parse(getStdIn().readln().getOrThrow())
println("====== 计算结果 ======")
println("${a} + ${b} = ${a + b}")
println("${a} - ${b} = ${a - b}")
println("${a} * ${b} = ${a * b}")
// b 为 0 时除法无意义:用三元运算给一句提示
// 第 4 课学完 if,会有更自然的写法
let divResult = b == 0 ? "除数不能为 0" : "${a / b}"
let modResult = b == 0 ? "除数不能为 0" : "${a % b}"
let realResult = b == 0 ? "除数不能为 0" : "${Float64(a) / Float64(b)}"
println("${a} / ${b}(整除)= ${divResult}")
println("${a} % ${b}(余数)= ${modResult}")
println("${a} / ${b}(精确值)= ${realResult}")
println("${a} 的 2 次方 = ${a ** 2}")
return 0
}
写完可以按 Ctrl + Alt + L 格式化,保持缩进整齐。
7.2 运行与交互
按 Ctrl + R 运行,在下方 Result(运行结果) 面板中直接用键盘输入:
请输入第一个整数:17
请输入第二个整数:5
====== 计算结果 ======
17 + 5 = 22
17 - 5 = 12
17 * 5 = 85
17 / 5(整除)= 3
17 % 5(余数)= 2
17 / 5(精确值)= 3.400000
17 的 2 次方 = 289
对照输出体会三个除法的区别:17 / 5 整除得 3,17 % 5 取余得 2,转成 Float64 后得到精确商 3.400000。
再跑一次,第二个数输入 0,可以看到程序没有崩溃,而是打印三行"除数不能为 0"——这就是比较运算 + 三元运算符的功劳。
📸 配图建议:Result 面板中显示输入提示、键盘输入 17 / 5 以及完整计算结果的截图。
7.3 用调试器观察"输入 → 计算"全过程
第 2 课我们已经玩过单步调试,这次加上输入场景,观察程序如何"停下来等你打字":
- 在
let a = Int64.parse(...)这一行左侧行号沟槽点一下,打上断点; - 按 Shift + F9 启动调试,程序在断点处暂停;
- 按 F8 单步执行这一行——程序会停住等待,此时在调试控制台用键盘输入
17并回车; - 继续 F8 走过读取
b的一行,输入5; - 观察 Variables(变量)面板:
a = 17、b = 5已就位;再单步走过每一行println,行尾会内联显示a + b、a * b等表达式的真实结果; - 把鼠标悬停在代码里的
divResult、realResult上,悬浮框直接显示它们当前的字符串内容。
📸 配图建议:调试停在
println行时,Variables 面板中 a/b 的值 + 行尾内联表达式结果 + 悬停 realResult 浮出 “3.400000” 的截图。
小实验:调试运行时第二位数故意输入 0,单步走到三元表达式那一行,逐行看 divResult 怎样被赋成"除数不能为 0",直观感受"条件选择值"的过程——这正是下节课 if 分支的雏形。
调试结束点停止按钮,结束调试会话。
五、常见问题 FAQ
Q1:为什么 7 / 2 等于 3 而不是 3.5?
两个整数相除走的是整数除法,结果仍是整数,小数部分向 0 截断。写 Float64(7) / Float64(2) 或 7.0 / 2.0 才能得到 3.5。
Q2:为什么 ++i 报编译错误?
仓颉只支持后缀自增自减(i++、i--),没有前缀写法;且只能用于 var 声明的可变整数变量,let 常量和其他类型都不行。
Q3:为什么不能写 a = b = 0 连等?
仓颉中赋值表达式的类型是 Unit(没有值),不能再被赋给另一个变量;if (a = 5) 同理不合法。这也从语法上杜绝了把 == 误写成 = 的经典错误。
Q4:readln() 后面为什么要跟 getOrThrow()?
readln() 的返回类型是 ?String(Option 类型),表示"可能读到、也可能读不到";getOrThrow() 负责把里面真实的字符串取出来。Option 的完整处理方式是第 9 课的内容,目前照抄固定搭配即可。
Q5:输入了字母,程序直接崩了怎么办?
Int64.parse("abc") 转换失败会抛出异常。入门阶段保证输入合法数字即可;学完第 10 课错误处理后,可以用 Int64.tryParse(...) 不抛异常地判断输入是否合法,并提示用户重新输入。
Q6:&& 和 &、|| 和 | 有什么区别?
&& / || 是逻辑运算符,操作数是 Bool,用于"并且/或者"的条件组合,且会短路;& / | 是位运算符,操作数是整数,按二进制位逐位运算。写条件判断时永远用双符号的那组。
Q7:求幂为什么不能写 2 ^ 3?
仓颉里 ^ 是按位异或(10 ^ 15 得 5),求幂必须写 2 ** 3。这是从其他语言转过来最容易踩的符号坑。
六、课后练习
- 输入一个整数表示圆的半径(π 取
3.14),输出圆的周长(2 × π × r)和面积(π × r²)。提示:注意把整数显式转成Float64,求幂用**。 - 输入一个三位整数,用
/和%拆出它的百位、十位、个位并分别输出;再计算"各位数字的立方和"(如 153 →1**3 + 5**3 + 3**3),直接用==比较它与原数是否相等并输出true/false。 - 声明
var a = 3、var b = 5,先用普通方式输出交换前的值,再用元组写法(a, b) = (b, a)一行完成交换并输出结果;思考为什么不需要临时变量。 - 在本课计算器程序中打断点,Shift + F9 启动调试,输入
17和0,F8 单步走完全程,观察divResult、modResult、realResult三个变量如何被三元表达式赋成"除数不能为 0",并在 Variables 面板确认它们的类型都是String。
下节预告
今天我们的程序已经能"听懂"输入、也能根据条件(比如除数是否为 0)在两个值之间做选择了。但三元运算符只能处理简单的二选一,真实程序里常有"大于 85 优秀、大于 60 及格、否则不及格"这种多分支逻辑。第 4 课我们就学习分支结构:if 与 when 表达式,让程序学会"看情况办事"。我们下节课见!
系列说明:本系列基于 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)