仓颉编程语言的基本概念:标识符、变量、注释
仓颉编程语言的基本概念:标识符、变量、注释
现在介绍仓颉编程语言的基本概念
标识符
在仓颉编程语言中,开发者可以给一些程序元素命名,这些名字被称为“标识符”。
在仓颉语言中,标识符是用户定义的名称,用于变量、函数、类等实体的标记。良好的标识符命名规则是编写可维护代码的基础。
标识符区分大小写(Variable 和 variable 是不同的标识符)。
仓颉编程语言的标识符分为普通标识符和原始标识符两类:
①普通标识符不能和仓颉关键字相同,由 XID_Start字符(包含中文和英文等字符)或_开头开头,后接任意长度的 XID_Continue 字符(包含中文、英文和阿拉伯数字等字符)。合法的标识符
userName
_age
合计
core123
非法的标识符(会导致编译错误)
123user // 不能以数字开头
ab&c //其中&不是 XID_Continue 字符
while // while 是仓颉关键字,普通标识符不能直接使用仓颉关键字
②原始标识符是在普通标识符或仓颉关键字的首尾加上一对反引号,主要用于将仓颉关键字作为标识符的场景。例如,以下每行字符串都是合法的原始标识符:
`abc`
`_abc`
`if`
`while`
以下每行字符串,由于反引号内的部分是不合法的普通标识符,所以它们整体也是不合法的原始标识符:
`ab&c`
`3abc`
变量
仓颉编程语言作为一种静态类型(statically typed)语言,要求每个变量的类型必须在编译时确定。
根据是否可进行修改,可将变量分为 3 类:不可变变量(一旦初始化,值不可改变)、可变变量(值可以改变)、const 变量(必须编译期初始化,不可修改)。
变量定义的具体形式为:
修饰符 变量名: 变量类型 [= 初始值]
可变性修饰符关键字let/var/const说明:
let用于定义不可变变量,let 变量一旦初始化就不能再改变。
var用于定义可变变量。
const用于定义 const 变量。
通俗地说:
一句话:
let = 上锁的盒子,放一次东西后永远不能再换;
var = 普通盒子,随时可以把里面的东西拿出来再放新的;
const = 出厂时就焊死、连盒子带内容一起封装的常量(不可省略初始化表达式)。
【其它常用的修饰符,还有:
• 可见性修饰符:private 与 public 等,影响全局变量和成员变量的可引用范围。https://cangjie-lang.cn/docs?url=%2F0.53.18%2Fuser_manual%2Fsource_zh_cn%2Fpackage%2Ftoplevel_access.html
• 静态性修饰符:static,影响成员变量(写在class / struct / interface / enum内部 的变量)的存储和引用方式。
初学者先不必深究,在此不介绍了。】
变量名应是一个合法的仓颉标识符。
变量类型指定了变量所持有数据的类型。当初始值具有明确类型时,可以省略变量类型标注,此时编译器可以自动推断出变量类型。
初始值是一个仓颉表达式,用于初始化变量,如果标注了变量类型,需要保证初始值类型和变量类型一致。在定义全局变量或静态成员变量时,必须指定初始值。在定义局部变量或实例成员变量时,可以省略初始值,但需要标注变量类型,同时要在此变量被引用前完成初始化,否则编译会报错。
示例
main() {let a: Int64var b: Int64 = 14const c: Int64 = 30 let pi: Float64 = 3.14a = 12 // 使用let修饰的变量只能赋值一次,即只能初始化一次b = 20//pi = 3.14159 //错误,无法对不可变值进行赋值println("a = ${a}, b = ${b}, c = ${c}") //a = 12, b = 20, c = 30println("pi = ${pi}") // pi = 3.140000
}
值类型和引用类型变量
从编译器实现层面看,任何变量总会关联一个值(一般是通过内存地址/寄存器关联),只是在使用时,对有些变量,将直接取用这个值本身,这被称为值类型变量;而对另一些变量,将这个值作为索引、取用这个索引指示的数据,这被称为引用类型变量。
从语言层面看,值类型变量对它所绑定的数据/存储空间是独占的,而引用类型变量所绑定的数据/存储空间可以和其他引用类型变量共享。
在使用值类型变量和引用类型变量时,会存在一些行为差异,以下几点值得注意:
1.在给值类型变量赋值时,一般会产生拷贝操作,且原来绑定的数据/存储空间会被覆盖。在给引用类型变量赋值时,只是改变了引用关系,原来绑定的数据/存储空间不会被覆盖。
2.用 let 定义的变量,要求变量被初始化后都不能再赋值。对于引用类型,这只是限定了引用关系不可改变,但是所引用的数据是可以被修改的。
在仓颉编程语言中,class 和 Array 等类型属于引用类型,其他基础数据类型和 struct 等类型属于值类型。
通俗解释:
值类型和引用类型的核心区别:本质是变量与数据的关联方式不同(独占 vs 共享),这导致了它们在赋值、修改等操作上的行为差异。下面结合具体示例说明:
值类型:变量直接 “持有” 数据本身,数据是 “独占” 的。就像你手里拿着一张纸质照片,给别人时需要复印一份,你改自己的照片不会影响别人的。
引用类型:变量不直接持有数据,而是持有数据的 “地址”(引用),数据可以被多个变量 “共享”。就像你和朋友都知道同一张电子照片的存储路径,你修改这张照片,朋友看到的也会变。
示例说明
1.值类型:拷贝整份数据
struct Point { // 值类型var x: Int64var y: Int64public init(x: Int64, y: Int64) {this.x = xthis.y = y}
}main() {var a = Point(1, 2) // a 独占一块栈内存var b = a // 整份数据拷贝!b 拥有独立副本b.x = 99 // 只改 b 的副本println("a.x = ${a.x}, b.x = ${b.x}") // a.x = 1, b.x = 99
}
2. 引用类型:赋值只复制“地址”
class Counter { // 引用类型public var value: Int64 = 0
}main() {var c1 = Counter() // c1 保存的是“对象地址”var c2 = c1 // 只复制地址,两个变量指向同一块堆内存c2.value = 42println("c1.value = ${c1.value}, c2.value = ${c2.value}") // 都是 42
}
3. let 与可变性:值类型 vs 引用类型
// 值类型
struct Size { var w: Int64 var h: Int64public init(w: Int64, h: Int64) {this.w = wthis.h = h}} // 引用类型
class Box { var width: Int64 = 0
} main() {let s = Size(10, 20) // 值类型 + let → 整个值不可改println("s.w = ${s.w}, s.h = ${s.h}") // s.w = 10, s.h = 20//s.w = 30 // ❌ 编译错误let b = Box() // 引用类型 + let → 引用不可改,但对象内容可改b.width = 100 // ✅ 合法:只是改堆上的对象,引用关系没变println(b.width) // 100
}
4. Array 的拷贝行为
main() {var a: Array<Int64> = [1, 2, 3] // Array 是引用类型var b = a // 只复制引用b[0] = 99println("a[0] = ${a[0]}, b[0] = ${b[0]}") // 都是 99
}
不可变类型和 可变类型
不可变:值一旦设定,内存里的数据永远不变,修改变量=创建新值,保证数据安全性(尤其多线程场景)。
可变:内存里的原数据可以直接被修改(无需频繁创建新对象)。
例如:
main() {// ---------------- 不可变:String ----------------let s1 = "hello" // let用于定义不可变变量println(" s1 = ${s1}") var sA = "Hi" // var用于定义可变变量println(" sA = ${sA}") sA = s1 + ", world" //修改变量=创建新值println(" sA = ${sA}") // ---------------- 可变:Array<Int64> ------------let arr1: Array<Int64> = ([1, 2, 3])let arr2 = arr1 // arr2 与 arr1 指向同一块内存println(" arr2 = ${arr2}") arr1[0] = 10; // 索引0的元素从1变为10println(" arr1 = ${arr1}")
}
输出如下:
s1 = hello
sA = Hi
sA = hello, world
arr2 = [1, 2, 3]
arr1 = [10, 2, 3]
注释
注释内容不影响程序的编译和运行,华为仓颉编程语言的注释有:
1.单行注释
语法:以 // 开头,直到行尾的内容均为注释。
2. 多行注释(块注释)
语法:以 /* 开头,以 */ 结尾,中间的内容均为注释(可跨多行)。多行注释常见的行前加 *”的写法,纯粹是为了让注释块在视觉上对齐,方便阅读,不是语法要求。
示例:
/*
这是多行注释
多行注释可以写多行
* 有人喜欢在多行注释行前加 *的写法,不是语法要求
*/
main() { let a: Bool = true // 声明一个布尔类型(Bool)变量a并赋值为trueif (a) { println(a) // 输出:true}
}