← 回算法课 · 看不懂?问龟龟

仓颉(Cangjie)语法速成 · 竞赛够用版

目标:12 月华为 ICT 大赛编程赛,用仓颉做题。你已经有 C++/Python 底子,所以这份不讲编程概念,只讲"这件事在仓颉里怎么写"。
写法来源:仓颉官方文档(程序结构、Console 示例、排序示例)+ 实际编译过的入门教程(第 3 课)。
⚠️ 仓颉 SDK 版本迭代快(社区版 1.0.x / 1.1.x),标了「以本地为准」的地方,赛前一定在 CIDE 里编译一次。

0. 先跑通这三段,比赛就不会慌

0.1 最小程序


main() {
    println("Hello, 仓颉")
}

0.2 读一行输入


import std.env.*

main() {
    let line = getStdIn().readln().getOrThrow()
    println("你输入了:${line}")
}

0.3 读一个整数 / 一行多个整数


import std.env.*
import std.convert.*

main() {
    let n = Int64.parse(getStdIn().readln().getOrThrow().trim())
    println(n * 2)
}

⚠️ Int64.parse 是严格的:输入里有非数字就抛异常。比赛保证输入合法,所以够用;要容错用 Int64.tryParse。

读一行多个数(比如 3 5 7)用 split:


import std.env.*
import std.convert.*

main() {
    let parts = getStdIn().readln().getOrThrow().trim().split(" ")
    let a = Int64.parse(parts[0])
    let b = Int64.parse(parts[1])
    println(a + b)
}
「以本地为准」:split 的参数和返回类型(Array<String>)在不同版本里有细微差别;如果编译报错,试试 split(" ") 改成 split(' ')(单字符),或者用 String 的 split 重载。赛前把这 10 行在 CIDE 里跑通一次,比收藏十篇博客都管用。

1. 变量与类型


let a = 2023          // 不可变:只能赋值一次
var b = 14            // 可变
var b2: Int64 = 14    // 显式标类型
const PI = 3.14159    // 编译期常量
b = 12                // OK
// a = 1              // 编译错误:let 不能再赋值
类型说明字面量
Int64默认整数(还有 Int8/16/32)42
Float64默认小数3.14
Bool只有 true/false,不能当 0/1 用true
String字符串"你好"
Rune单个字符(Unicode)'中'
Array<T>定长数组[1, 2, 3]
ArrayList<T>可变长ArrayList<Int64>()
HashMap<K,V>哈希表HashMap<String, Int64>()
Unit无返回值()

和 C++ 最大的不同:


println(7 / 3)              // 2  (整数除法,向 0 截断)
println(-7 / 3)             // -2
println(7 % 3)              // 1
println(2 ** 10)            // 1024 —— 求幂是 **,不是 ^
println(7.0 / 3.0)          // 2.333333
println(Float64(7) / 3.0)   // 2.333333(必须先转)

⚠️ ^ 在仓颉是按位异或,不是幂。这是从 C++/Python 转过来最容易踩的坑。


2. 分支与循环


// if 也是表达式,能直接赋值
let level = if (score >= 85) { "优秀" } else if (score >= 60) { "及格" } else { "不及格" }

// if 的条件必须是 Bool,不能写 if (1)
if (n % 2 == 0) {
    println("偶数")
}

// while
var i = 0
while (i < n) {
    i++
}

// for + Range:0..n 是左闭右开,0..=n 是闭区间
for (j in 0..n) {          // j = 0,1,...,n-1
    println(j)
}
for (j in 1..=n) {         // j = 1,...,n
    println(j)
}

// 遍历数组/集合
let arr = [10, 20, 30]
for (x in arr) {
    println(x)
}

⚠️ 只有 i++,没有 ++i;而且 let 变量不能自增。赋值表达式没有值,不能写 a = b = 0。

match(比 switch 强,能解构):


let s = match (x) {
    case 0 => "零"
    case 1 => "一"
    case _ => "很多"
}

3. 数组与集合

3.1 Array(定长)


var a = [1, 3, 5, 2, 4]      // 字面量,类型自动推断为 Array<Int64>
println(a.size)              // 5
println(a[0])                // 1
a[0] = 99                    // 可以改元素

// 全 0 数组(长度 n)
var b = Array<Int64>(n, repeat: 0)
「以本地为准」:Array<Int64>(n, repeat: 0) 是常见构造;如果你的 SDK 不认,用下面的 ArrayList 顶替,或写 Array<Int64>(n) { _ => 0 }。

3.2 ArrayList(可变长,最常用)


import std.collection.*

var list = ArrayList<Int64>()        // 空
list.add(3)                          // 追加
list.add(1)
println(list.size)
println(list[0])                     // 也能下标访问
list[0] = 8                          // 下标赋值

也可以用字面量初始化:ArrayList<Int64>([1, 3, 5])。

3.3 HashMap(计数 / 去重 / 映射)


import std.collection.*

var cnt = HashMap<String, Int64>()
cnt.add("apple", 1)                    // 新增
let v = cnt.get("apple")               // Option<Int64>
let x = cnt.get("apple") ?? 0          // 取不到就当 0(?? 是空值合并)
cnt["apple"] = 2                       // 下标赋值(更常用)
if (cnt.contains("apple")) { ... }

计数模板(比赛天天用):


import std.collection.*
import std.env.*

main() {
    let words = getStdIn().readln().getOrThrow().trim().split(" ")
    var cnt = HashMap<String, Int64>()
    for (w in words) {
        let c = cnt.get(w) ?? 0
        cnt[w] = c + 1
    }
    println(cnt.get("apple") ?? 0)
}
「以本地为准」:HashMap 的方法名在 1.0/1.1 之间调整过(add / put / [])。先试 cnt[k] = v 和 cnt.get(k),报错再试 cnt.add(k, v)。

3.4 排序


import std.sort.*

var a = [1, 3, 5, 2, 4]
sort(a)                                  // 升序,原地
println(a)                               // [1, 2, 3, 4, 5]

// 自定义比较:按 score 升序
let cmp = {l: Student, r: Student => l.score.compare(r.score)}
sort(arr, by: cmp)

// 降序
let lt = {l: Int64, r: Int64 => l < r}
sort(b, lessThan: lt, descending: true)

4. 字符串


let s = "hello 仓颉"
println(s.size)                  // 长度
println("${a} + ${b} = ${a + b}") // 插值
println(s.trim())                // 去首尾空白

数字 ↔ 字符串:


import std.convert.*
let n = Int64.parse("123")       // 字符串 → 整数
let t = n.toString()             // 整数 → 字符串

逐字符处理(判断回文、统计字符):


for (ch in s.toRuneArray()) {    // 按字符遍历(中文也不会被劈开)
    println(ch)
}
「以本地为准」:取字符数组的方法名可能是 toRuneArray() / toArray(),报错就换另一个。

5. 函数


func add(a: Int64, b: Int64): Int64 {
    return a + b
}

// 单表达式可以省略 return 和花括号
func square(x: Int64): Int64 {
    x * x
}

// 默认参数
func greet(name: String, prefix: String = "你好"): String {
    "${prefix},${name}"
}

注意:参数要逐个标类型;返回值类型写在 ): 后面,不写则推断为 Unit。


6. 结构体、类、枚举


// struct:值类型(赋值会拷贝)
struct Point {
    var x: Int64
    var y: Int64
}

// class:引用类型(赋值共享同一个对象)
class Node {
    public var val: Int64
    public var next: ?Node = None
    public init(v: Int64) {
        this.val = v
    }
}

enum Color {
    | Red
    | Green
    | Blue
}

练习赛常用:结构体数组 + 排序。


import std.sort.*

struct Student {
    var name: String
    var score: Int64
}

main() {
    var arr = [Student("张三", 88), Student("李四", 95)]
    sort(arr, by: {l: Student, r: Student => l.score.compare(r.score)})
    println(arr[0].name)
}

7. 竞赛模板(直接背,改内容)

模板 1:读 n,再读 n 个数


import std.env.*
import std.convert.*

main() {
    let n = Int64.parse(getStdIn().readln().getOrThrow().trim())
    let nums = ArrayList<Int64>()
    let parts = getStdIn().readln().getOrThrow().trim().split(" ")
    for (p in parts) {
        nums.add(Int64.parse(p))
    }
    var sum = 0
    for (x in nums) {
        sum += x
    }
    println(sum)
    return 0
}

模板 2:多组测试数据(第一行是 T)


import std.env.*
import std.convert.*

main() {
    let t = Int64.parse(getStdIn().readln().getOrThrow().trim())
    for (k in 0..t) {
        let parts = getStdIn().readln().getOrThrow().trim().split(" ")
        let a = Int64.parse(parts[0])
        let b = Int64.parse(parts[1])
        println(a + b)
    }
    return 0
}

模板 3:读 n×m 矩阵


import std.env.*
import std.convert.*

main() {
    let head = getStdIn().readln().getOrThrow().trim().split(" ")
    let n = Int64.parse(head[0])
    let m = Int64.parse(head[1])
    var grid = ArrayList<ArrayList<Int64>>()
    for (i in 0..n) {
        let row = ArrayList<Int64>()
        for (p in getStdIn().readln().getOrThrow().trim().split(" ")) {
            row.add(Int64.parse(p))
        }
        grid.add(row)
    }
    println(grid[0][0])
    return 0
}

模板 4:图(邻接表 BFS)


import std.collection.*

main() {
    let n = 5
    var g = ArrayList<ArrayList<Int64>>()
    for (i in 0..n) { g.add(ArrayList<Int64>()) }
    g[0].add(1); g[1].add(0)     // 无向边 0-1
    var dist = ArrayList<Int64>()
    for (i in 0..n) { dist.add(-1) }
    var q = ArrayList<Int64>()
    q.add(0); dist[0] = 0
    var head = 0
    while (head < q.size) {
        let u = q[head]; head++
        for (v in g[u]) {
            if (dist[v] == -1) {
                dist[v] = dist[u] + 1
                q.add(v)
            }
        }
    }
    println(dist[0])
}
「以本地为准」:ArrayList 没有内置队列,用 ArrayList + 头指针模拟队列(上面就是),或者用 ArrayDeque。别用 q.remove(0) 当出队,那是 O(n)。

模板 5:二分查找


func lowerBound(a: Array<Int64>, target: Int64): Int64 {
    var lo = 0
    var hi = a.size
    while (lo < hi) {
        let mid = (lo + hi) / 2
        if (a[mid] < target) {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    lo
}

8. 从 C++ / Python 迁过来:对照表

想干的事C++仓颉
打印cout << xprintln(x) / println("${x}")
读一行getline(cin, s)getStdIn().readln().getOrThrow()
字符串转数stoi(s)Int64.parse(s)
变长数组vector<int> vArrayList<Int64>() + add
哈希表unordered_map<string,int> mHashMap<String, Int64>()
集合set<int>HashSet<Int64> / TreeSet
排序sort(v.begin(), v.end())sort(a)(要 import std.sort.*)
求幂pow(2,10)2 ** 10
取余a % ba % b
空值指针 / -1 哨兵?T + Option + ??
常量const / #defineconst
结构体structstruct(值类型)/ class(引用类型)

9. 踩坑清单(都是从 C++ 过来必犯的)


10. 赛前 checklist(15 分钟)

💡 比赛现场别硬刚语法:先写注释和思路,再填代码。仓颉的报错信息还算友好,编译不过时先看是不是"类型不匹配"(90% 是这个)。

11. 下一步

语法这关过了,下一步是题目。另开一份《ICT 编程赛 · 题型与题目讲解》,按题型把典型题拆开讲(模拟 / 贪心 / 二分 / 前缀和 / BFS / DP / 并查集)。