目标:12 月华为 ICT 大赛编程赛,用仓颉做题。你已经有 C++/Python 底子,所以这份不讲编程概念,只讲"这件事在仓颉里怎么写"。
写法来源:仓颉官方文档(程序结构、Console 示例、排序示例)+ 实际编译过的入门教程(第 3 课)。
⚠️ 仓颉 SDK 版本迭代快(社区版 1.0.x / 1.1.x),标了「以本地为准」的地方,赛前一定在 CIDE 里编译一次。
main() {
println("Hello, 仓颉")
}
.cj,入口是 mainfunc(和普通函数不一样)main(): Int64 { ... return 0 } 也可以,返回值是退出码
import std.env.*
main() {
let line = getStdIn().readln().getOrThrow()
println("你输入了:${line}")
}
readln() 返回的是 ?String(可能读不到),.getOrThrow() 把值取出来 —— 这是固定搭配,先照抄import std.console.* 然后 Console.stdIn.readln().getOrThrow(),两种都行
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 里跑通一次,比收藏十篇博客都管用。
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++ 最大的不同:
Int64 和 Float64 不能直接比大小,要显式 Float64(a)
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 转过来最容易踩的坑。
// 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 _ => "很多"
}
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 }。
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])。
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)。
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)
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(),报错就换另一个。
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。
// 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)
}
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
}
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
}
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
}
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)。
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
}
| 想干的事 | C++ | 仓颉 |
|---|---|---|
| 打印 | cout << x | println(x) / println("${x}") |
| 读一行 | getline(cin, s) | getStdIn().readln().getOrThrow() |
| 字符串转数 | stoi(s) | Int64.parse(s) |
| 变长数组 | vector<int> v | ArrayList<Int64>() + add |
| 哈希表 | unordered_map<string,int> m | HashMap<String, Int64>() |
| 集合 | set<int> | HashSet<Int64> / TreeSet |
| 排序 | sort(v.begin(), v.end()) | sort(a)(要 import std.sort.*) |
| 求幂 | pow(2,10) | 2 ** 10 |
| 取余 | a % b | a % b |
| 空值 | 指针 / -1 哨兵 | ?T + Option + ?? |
| 常量 | const / #define | const |
| 结构体 | struct | struct(值类型)/ class(引用类型) |
不是 ^**;^ 是异或++i,只有 i++;let 变量不能自增Int64 和 Float64 混算直接编译错,要 Float64(x)if 条件必须是 Bool,if (1) 非法readln() 返回 Option,必须 .getOrThrow()(或 ??)main 不写 func;普通函数要写struct 是值类型(赋值拷贝),class 是引用类型——想共享就用 classsort 要 import std.sort.\\*,不 import 会报找不到toRuneArray(),直接按下标取可能是字节.cjgetStdIn().readln().getOrThrow() 这一串(肌肉记忆)Int64.parse、sort(a)、ArrayList、HashMap 三件套💡 比赛现场别硬刚语法:先写注释和思路,再填代码。仓颉的报错信息还算友好,编译不过时先看是不是"类型不匹配"(90% 是这个)。
语法这关过了,下一步是题目。另开一份《ICT 编程赛 · 题型与题目讲解》,按题型把典型题拆开讲(模拟 / 贪心 / 二分 / 前缀和 / BFS / DP / 并查集)。