【仓颉语言入门 · 第3课】运算符与标准输入输出

上节课我们学会了用 let / var 把数据存进变量,也在调试器里亲眼看到了变量的值。但程序目前只会"顺序往下走":数据是代码里写死的,输出自然也是固定的。

本文是系列第 3 课:先学会仓颉的各类运算符——让数据真正参与计算;再打通程序与外界的通道——标准输入,让程序接收你的键盘输入。学完之后,同一个程序每次运行都能根据不同的输入给出不同的结果,程序第一次真正"活"起来。


目录(系列导航)

整套路线共 7 个模块、30 课,按每周 2~3 课的节奏,大约 2~3 个月可以走完一遍:

模块课次内容
一、环境与入门01~05环境搭建与 Hello World、变量与基本类型、运算符与输入输出、分支、循环
二、常用类型与数据组织06~10字符串、数组与区间、ArrayList/HashMap/HashSet、可空类型、错误处理
三、函数与函数式11~14函数、Lambda 与高阶函数、闭包、迭代器与惰性序列
四、面向对象与类型系统15~20struct/class、构造与属性、接口、枚举与 match 模式匹配、泛型、扩展
五、工程化与标准库21~25cjpm 包管理与多文件、文件 IO、JSON 处理、网络编程、单元测试
六、并发编程26~28线程、Channel 通道与同步原语、并发实战
七、项目实战29~30命令行小工具、GeoJSON 数据处理实战
  1. 环境搭建与第一个仓颉程序
  2. 变量与常量:let / var 与基本数据类型
  3. 运算符与标准输入输出(本文)
  4. 分支结构:if 与 when 表达式
  5. 循环结构:while / for / Range
  6. 字符串详解与字符串插值
  7. 数组 Array 与区间 Range
  8. 集合框架:ArrayList、HashMap、HashSet
  9. 可空类型 ? 与 Option
  10. 错误处理:异常机制与 Result
  11. 函数定义、参数与返回值
  12. Lambda 表达式与高阶函数
  13. 闭包、作用域与函数类型
  14. 迭代器 Iterator 与 Sequence
  15. 结构体 struct 与类 class
  16. 构造函数、属性与方法
  17. 接口 interface 与实现
  18. 枚举 enum、代数数据类型与 match 模式匹配
  19. 泛型编程
  20. 扩展、类型别名与可见性控制
  21. cjpm 包管理与多文件项目组织
  22. 文件与目录 IO
  23. JSON 处理(结合 stdx 扩展库)
  24. 网络编程入门
  25. 单元测试
  26. 并发基础:线程的创建与等待
  27. Channel 通道与同步原语
  28. 并发实战:多线程任务处理
  29. 实战一:带文件持久化的命令行小工具
  30. 实战二:GeoJSON 数据处理程序

一、算术运算符:加减乘除、取余与求幂

最常用的算术运算符有 7 个:

运算符含义示例结果
+加(也可拼接字符串)2 + 35
-减(也可作一元负号)3 - 12
*3 * 412
/7 / 32(整数相除)
%取余(仅支持整数)7 % 31
**求幂2 ** 38
-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 时,右操作数可以是 Int64Float64,结果为 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 == 3true
!=不等3 != 4true
<小于3 < 4true
<=小于等于3 <= 3true
>大于3 > 4false
>=大于等于3 >= 4false

比较表达式的结果一定是 Booltrue / false),这正是第 4 课 if 分支的判断依据。同样要求两侧类型一致

println(3.14 == 3.15)    // false
println(3 < 3.0)         // 编译错误:Int64 不能直接和 Float64 比较

字符串也可以直接用 == 比较内容是否相同(第 6 课细讲)。

3.2 逻辑运算:与、或、非

运算符含义规则
&&逻辑与(并且)两边都为 true,结果才为 true
``
!逻辑非(取反)!truefalse!falsetrue
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 & 1510
``按位或`10
^按位异或10 ^ 155
<<左移(相当于乘 2 的幂)10 << 120
>>右移(相当于除 2 的幂)10 >> 15
!按位求反(操作数为整数时)!10-11

注意 ! 是个"两面派":作用在 Bool 上是逻辑非,作用在整数上是按位求反,编译器按操作数类型自动区分。


四、三元条件运算符与运算优先级

4.1 三元运算符 ? ::一行二选一

当你想"根据一个布尔条件,在两个值里挑一个"时,可以用三元条件运算符:

条件 ? 条件为真时取这个值 : 条件为假时取这个值
let age = 20
let result = age >= 18 ? "成年" : "未成年"
println(result)   // 成年

它和 &&|| 一样有类型要求:: 两侧两个分支的结果类型必须一致(上例两边都是字符串)。更复杂的分支逻辑第 4 课会用 if / when 来写,可读性更好;简单的二选一,三元运算符最紧凑。

4.2 运算优先级:拿不准,就加括号

一个表达式里出现多个运算符时,编译器按固定的优先级决定先算谁。简化版从高到低大致是:

一元运算符(-  !  ++  --)
    ↓
求幂 **
    ↓
乘除余 *  /  %
    ↓
加减 +  -
    ↓
移位 <<  >>
    ↓
比较 <  >  <=  >=
    ↓
判等 ==  !=
    ↓
位运算 &  ^  |
    ↓
逻辑与 &&
    ↓
逻辑或 ||
    ↓
三元 ? :
    ↓
赋值 = 与复合赋值

比如 2 + 3 * 414 而不是 20,因为 * 优先于 +不需要死记这张表——实际开发中一条通用建议:拿不准优先级,或者想让运算顺序一目了然时,果断加括号。括号不产生任何运行时开销,却能让读代码的人(包括三个月后的你自己)立刻明白意图:

println((2 + 3) * 4)     // 20,括号强制先算加法
println(2 + 3 * 4)       // 14

五、标准输出回顾:printprintln

前两课一直在用 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
}

逐段拆解第②③步这一长串:

  1. getStdIn():拿到标准输入对象(默认对应键盘);
  2. .readln()读取一整行,直到你按下回车。它的返回类型是 ?String——表示"可能读到字符串,也可能读不到"(比如输入流被关闭)。这是仓颉的 Option 可空类型,第 9 课专门讲;
  3. .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 整除得 317 % 5 取余得 2,转成 Float64 后得到精确商 3.400000

再跑一次,第二个数输入 0,可以看到程序没有崩溃,而是打印三行"除数不能为 0"——这就是比较运算 + 三元运算符的功劳。

📸 配图建议:Result 面板中显示输入提示、键盘输入 17 / 5 以及完整计算结果的截图。

7.3 用调试器观察"输入 → 计算"全过程

第 2 课我们已经玩过单步调试,这次加上输入场景,观察程序如何"停下来等你打字":

  1. let a = Int64.parse(...) 这一行左侧行号沟槽点一下,打上断点;
  2. Shift + F9 启动调试,程序在断点处暂停;
  3. F8 单步执行这一行——程序会停住等待,此时在调试控制台用键盘输入 17 并回车;
  4. 继续 F8 走过读取 b 的一行,输入 5
  5. 观察 Variables(变量)面板a = 17b = 5 已就位;再单步走过每一行 println,行尾会内联显示 a + ba * b 等表达式的真实结果;
  6. 把鼠标悬停在代码里的 divResultrealResult 上,悬浮框直接显示它们当前的字符串内容。

📸 配图建议:调试停在 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 ^ 155),求幂必须写 2 ** 3。这是从其他语言转过来最容易踩的符号坑。


六、课后练习

  1. 输入一个整数表示圆的半径(π 取 3.14),输出圆的周长(2 × π × r)和面积(π × r²)。提示:注意把整数显式转成 Float64,求幂用 **
  2. 输入一个三位整数,用 /% 拆出它的百位、十位、个位并分别输出;再计算"各位数字的立方和"(如 153 → 1**3 + 5**3 + 3**3),直接用 == 比较它与原数是否相等并输出 true / false
  3. 声明 var a = 3var b = 5,先用普通方式输出交换前的值,再用元组写法 (a, b) = (b, a) 一行完成交换并输出结果;思考为什么不需要临时变量。
  4. 在本课计算器程序中打断点,Shift + F9 启动调试,输入 170,F8 单步走完全程,观察 divResultmodResultrealResult 三个变量如何被三元表达式赋成"除数不能为 0",并在 Variables 面板确认它们的类型都是 String

下节预告

今天我们的程序已经能"听懂"输入、也能根据条件(比如除数是否为 0)在两个值之间做选择了。但三元运算符只能处理简单的二选一,真实程序里常有"大于 85 优秀、大于 60 及格、否则不及格"这种多分支逻辑。第 4 课我们就学习分支结构:if 与 when 表达式,让程序学会"看情况办事"。我们下节课见!


系列说明:本系列基于 Windows 平台 + CIDE + 仓颉 SDK(1.1.3)编写,所有代码均已实际编译运行通过。如遇 SDK 版本差异导致的细节出入,以你本地版本为准,欢迎评论区交流。


📥 工具下载

本系列全程使用的仓颉 IDE —— CIDE(免费开源、社区版):

Logo

一站式 AI 云服务平台

更多推荐