用中文写代码(三):一小时读完 CN 语法基础
前两篇我们认识了 CN 语言、跑通了第一个程序。今天把过程式编程的全部基础一次讲完:变量、类型、运算符、控制流、函数、数组、字符串、结构体、枚举。读完这篇,你就能动手写正经的 CN 程序了。
老规矩:以下所有示例都来自官方文档,逐个经过编译器编译、运行、输出比对,标注的输出就是真实运行结果。
一、程序的骨架
CN 源文件 UTF-8 编码、.cn 扩展名,入口是名为「主」的函数:
文件:骨架.cn
// 单行注释
/* 块注释,支持嵌套 /* 像这样 */ */
静态 整32 全局计数 = 0; // 全局静态变量(零初始化)
不安全 函数 主() -> 整32 { // 静态变量写=不安全操作(进阶篇细讲)
全局计数 = 全局计数 + 1;
打印("计数:", 全局计数);
返回 0; // 0 = 成功
}
运行输出:计数:1
- 每条语句必须以分号结束------和 C/Rust 一样,没有 Python 那种「自动补分号」;
- 顶层由函数、结构体、枚举、类、导入、全局变量声明组成。
二、变量:不初始化就不让用
文件:变量.cn
函数 主() -> 整32 {
整32 甲 = 10; // 显式类型
变量 乙 = 20; // 类型推断(= 整32)
浮64 单价 = 9.5;
布尔 通过 = 真;
字符 等级 = '甲'; // Unicode 字符(4 字节码点)
甲 = 甲 + 乙; // 赋值
打印(甲, " ", 单价, " ", 通过, " ", 整32(等级)); // 字符打印=码点
返回 0;
}
运行输出:30 9.500000 真 30002
- 声明即初始化 是 CN 的默认姿势:未初始化的局部变量读取是编译错误------垃圾值根本没机会出现(编译期数据流检查,零运行时成本);
变量 乙 = 20;让编译器自己推断类型;- 字符是 4 字节 Unicode 码点,「甲」的码点就是 30002;
常量 尺寸 = 4;定义编译期常量,还能写算术表达式(常量 尺寸2 = 尺寸 * 2;);静态变量零初始化、生存期=整个进程。
三、类型全景:从整8到整128
- 整数 10 种:有符号 整8/整16/整32/整64/整128,无符号 正8/正16/正32/正64/正128;
- 浮点 2 种:浮32/浮64(IEEE 754 单/双精度);
- 其他:布尔(真/假)、字符(4 字节码点)、字符串(UTF-8)、空类型;
- 还有两个中文习惯别名:整数=整32、小数=浮64。
文件:字面量.cn
函数 主() -> 整32 {
整32 十进制 = 42;
整32 十六进制 = 0xFF; // 255
整32 二进制 = 0b1010; // 10
整32 八进制 = 0o777; // 511
整64 长值 = 100L; // L 后缀 → 整64
浮32 单精度 = 1.5f; // f 后缀 → 浮32
浮64 科学 = 1.5e10;
打印(十进制, " ", 十六进制, " ", 二进制, " ", 八进制);
打印(长值, " ", 单精度, " ", 科学);
返回 0;
}
运行输出:42 255 10 511
100 1.500000 15000000000.000000
给追求细节的读者:同宽窄整数算术保持窄域 (整8 + 整8 = 整8,127+1 按两补码回绕成 -128);字面量超范围直接编译拒绝(整16 甲 = 40000; 不放行)。
四、类型转换:变窄必须明说
宽化(小类型转大类型)隐式进行;窄化、跨符号、浮点转整数,一律显式写 类型名(表达式),语义是位模式变换(Rust 的 as 同款):
文件:转换.cn
函数 主() -> 整32 {
整32 小 = 7;
整64 大 = 小; // 宽化:隐式放行
浮64 比例 = 浮64(小) / 4; // 显式转浮点 → 1.75
整32 截断 = 整32(3.99); // 浮转整:截断 → 3
正64 位模式 = 正64(整32(-1)); // 升宽:符号扩展 → 2^64-1
打印(大, " ", 比例, " ", 截断, " ", 位模式 == 18446744073709551615);
返回 0;
}
运行输出:7 1.750000 3 真
五、运算符:C 风格全套
文件:运算符.cn
函数 正数(整32 x) -> 布尔 { 返回 x > 0; }
函数 主() -> 整32 {
整32 a = 7;
整32 b = 2;
打印(a + b, a - b, a * b, a / b, a % b); // 9 5 14 3 1
打印(a << 1, a >> 1, a & 3, a | 8, a ^ 1); // 14 3 3 15 6
打印(a > b, a == 7, a != 7, !正数(-1)); // 布尔族
整32 c = a > b ? a : b; // 三元(右结合)
a += 10; // 复合赋值族 += -= *= /= %=
a++; // 后缀自增:表达式值=原值
打印(c, a);
返回 0;
}
/对整数是截断除;%取余;除零=运行时错误(常量除零=编译错误);- 逻辑
&&||短路求值;位运算只对整型开放; - 后缀
a++的表达式值是自增前的原值,前缀是自增后的新值; - 顺带一个细节:打印的多个参数是紧挨着输出的 (所以上面 交换 后输出「21」),想要空格得自己打
打印(p, " ", q)。
六、控制流:如果/当/循环/选择/遍历
条件的小括号可以省略------「如果 分 >= 90」直接写:
文件:条件与循环.cn
函数 分级(整32 分) -> 字符串 {
如果 分 >= 90 {
返回 "优";
} 否则 如果 分 >= 60 {
返回 "及格";
} 否则 {
返回 "不及格";
}
}
函数 主() -> 整32 {
打印(分级(95), " ", 分级(70), " ", 分级(30));
整32 i = 0;
当 i < 3 { // 当循环(前测)
打印行(i, " ");
i++;
}
打印("");
循环 (整32 j = 10; j >= 0; j -= 5) { // for 风格
打印行(j, " ");
}
打印("");
返回 0;
}
运行输出:优 及格 不及格
0 1 2
10 5 0
注意 打印行 输出不换行 ,打印 输出完自动换行。
更惊喜的是:字符串也能当选择条件,枚举成员还能裸写:
文件:选择.cn
枚举 命令 { 启动, 停止, 查询 }
函数 主() -> 整32 {
整32 键 = 2;
选择 (键) {
情况 0:
打印("零");
中断;
情况 1, 2: // 多值分组
打印("一或二");
中断;
默认:
打印("其他"); // 不写 中断 会贯穿下一分支(C 语义)
}
字符串 指令 = "开始";
选择 (指令) { // 字符串选择
情况 "开始", "继续":
打印("执行");
中断;
默认:
打印("未知");
}
命令 c = 命令.查询;
选择 (c) { // 裸枚举成员
情况 启动:
打印("启动中");
中断;
情况 查询:
打印("查询中");
中断;
默认:
打印("...");
}
返回 0;
}
运行输出:一或二
执行
查询中
最后是遍历和循环控制:
文件:遍历与中断继续.cn
函数 主() -> 整32 {
整32[5] 数据 = {10, 20, 30, 40, 50};
遍历 数据 中 每个 x { // 数组遍历:x=元素副本
打印行(x, " ");
}
打印("");
循环 (整32 k = 0; k < 100; k++) {
如果 k % 2 == 0 { 继续; } // 跳过偶数
如果 k > 10 { 中断; } // 终止循环
打印行(k, " ");
}
打印("");
返回 0;
}
运行输出:10 20 30 40 50
1 3 5 7 9
- 「遍历 数据 中 每个 x」------几乎就是中文句子;
中断/继续对应 break/continue;中断绑定最近一层循环或选择(与 C 的 break 一致);- 选择默认贯穿(fall-through),想停住必须显式写
中断。
七、函数:重载、默认参数、引用传递
文件:函数.cn
// 重载:同名按参数类型与个数区分(仅返回类型不同不构成重载)
函数 最大(整32 a, 整32 b) -> 整32 { 如果 a > b { 返回 a; } 返回 b; }
函数 最大(浮64 a, 浮64 b) -> 浮64 { 如果 a > b { 返回 a; } 返回 b; }
// 默认参数:从右向左连续声明
函数 问候(字符串 名字 = "世界") -> 空类型 {
打印("你好," + 名字);
}
// 引用参数:直写调用方
函数 交换(整32& a, 整32& b) -> 空类型 {
整32 临时 = a;
a = b;
b = 临时;
}
// 常量引用参数:只读借用(体内赋值=编译错误)
函数 求和(常量 整64& x, 常量 整64& y) -> 整64 { 返回 x + y; }
函数 主() -> 整32 {
打印(最大(3, 9), " ", 最大(2.5, 1.8));
问候();
问候("张三");
整32 p = 1;
整32 q = 2;
交换(p, q); // 引用实参:直传左值(不加 &)
打印(p, q);
整64 x = 100;
整64 y = 23;
打印(求和(x, y)); // 常量引用实参须左值变量
返回 0;
}
运行输出:9 2.500000
你好,世界
你好,张三
21
123
还有泛型函数,编译期单态化,零运行时开销:
文件:泛型函数.cn
泛型 <类型 T>
函数 交换泛型(T& a, T& b) -> 空类型 {
T 临时 = a;
a = b;
b = 临时;
}
函数 主() -> 整32 {
整32 甲 = 1;
整32 乙 = 2;
交换泛型<整32>(甲, 乙); // 显式单态化<类型>;引用实参直传
打印(甲, 乙);
返回 0;
}
运行输出:21
八、数组:赋值即整块拷贝
数组写法是 类型[长度],长度可以是编译期常量表达式:
文件:数组.cn
常量 行数 = 3;
函数 主() -> 整32 {
整32[5] 表 = {10, 20, 30, 40, 50}; // 聚合初始化
整32[行数] 零表; // 长度=常量表达式
零表[0] = 7;
打印(表[0], " ", 表[4], " ", 零表[0]);
整32[3] 甲 = {1, 2, 3};
整32[3] 乙 = 甲; // 整块值拷贝
乙[0] = 99;
打印(甲[0], " ", 乙[0]); // 1 99(两态独立)
整32 和 = 0;
遍历 表 中 每个 x { 和 = 和 + x; }
打印(和);
返回 0;
}
运行输出:10 50 7
1 99
150
- 下标从 0 起;越界读/写=运行时错误(错误码 2)直接终止;
乙 = 甲是整块值拷贝,改 乙 不影响 甲------两态独立;- 数组传给函数也是按值整块拷贝;要可变长度的动态容器,用标准库的 向量<T>。
九、字符串:拥有型 UTF-8
文件:字符串.cn
函数 主() -> 整32 {
字符串 名 = "CN 语言";
打印(字符串长度(名)); // 9(UTF-8 字节:CN=2+空格1+语言=6)
字符串 拼接 = "你好, " + 名 + "!" + 1;
打印(拼接); // 你好, CN 语言!1
打印(字符串包含(名, "语言")); // 真
打印(字符串子串(名, 3, 6)); // 语言(字节切片)
打印(名[0]); // 67('C' 的 UTF-8 首字节)
打印(字符串大写("cn")); // CN
返回 0;
}
运行输出:9
你好, CN 语言!1
真
语言
67
CN
- 字符串是拥有型:数据在堆上,作用域结束自动释放(RAII),不用手动管内存;
- 长度、下标、切片全部按 UTF-8 字节 口径------一个汉字 3 字节,
字符串长度("中")等于 3; +拼接时右侧数值自动转字符串("编号-" + 7合法);- 字符串不可原地下标写(编译拒绝)------修改就是重建新串,杜绝改一半的中间态。
十、结构体与枚举
文件:结构体.cn
结构体 点 {
浮64 x;
浮64 y;
}
结构体 线段 {
点 起; // 嵌套结构体字段
点 止;
}
函数 主() -> 整32 {
点 a = 点{x = 1.0, y = 2.0};
点 b = a; // 整体值拷贝
b.x = 9.0;
打印(a.x, " ", b.x); // 1 9(两态独立)
线段 线 = 线段{起 = 点{x = 0.0, y = 0.0}, 止 = 点{x = 3.0, y = 4.0}};
浮64 dx = 线.止.x - 线.起.x;
浮64 dy = 线.止.y - 线.起.y;
打印(dx * dx + dy * dy); // 25(3-4-5 直角三角形)
返回 0;
}
运行输出:1.000000 9.000000
25.000000
点{x = 1.0, y = 2.0}是指定初始化器,带穷举纪律:缺字段、多写字段、重复字段都是编译错误------静默零填充这种坑在这里不存在;- 成员访问统一用点号;指针左侧自动解引用一级(
p.x等价(*p).x),C 里的->被废除了; - 结构体里有字符串、容器这类字段时,释放随作用域自动级联(RAII)。
枚举是命名常量族,成员可显式赋值(含负数),引用形态是 枚举名.成员,在选择的条件位可以裸写成员名(前面「选择.cn」已演示);枚举名::成员 是非法的------:: 保留给模块路径。
写在最后
到这里,过程式编程的地基打完了:变量、类型、转换、运算符、控制流、函数、数组、字符串、结构体、枚举。下一篇上强度:类与泛型------构造析构、继承与接口、泛型类、运算符重载,CN 面向对象的全套家伙什。
官方文档(本文整理自官方教程「语法基础」):www.cn-language.com/docs
源码仓库:gitcode.com/ChenChao_GitCode/CN_Language
这套中文语法读起来习惯吗?评论区聊聊你最意外的一个设计。