第三节 LPC语言常量和变量
本文节选自《LPC语言基础教程:从零学习游戏开发》,版权归@mudren,欢迎转载,但必须注明来源(mud.ren)。
数据有类型,也有常量和变量之分,下面我们具体介绍。
变量
变量(variable)可以理解成一块内存区域的名字。通过变量名,可以引用这块内存区域,获取里面存储的值。由于值可能发生变化,所以称为变量,否则就是常量了。
在LPC语言中变量的定义和初始化方式和C语言完全相同:
数据类型符 变量名1[,变量名2,...];
数据类型符 变量名1=初值1[,变量名2=初值2,...];
FluffOS 说明: 在 FluffOS 中,局部变量可以出现在函数体内的任何位置(C99 风格),不必强制在函数开头声明。推荐在文件开头使用
#pragma strict_types启用严格类型检查。
下面详细介绍变量相关知识,这些LPC语言和C语言差别不大,这里重点关注和C语言的不同点。
变量名
变量名在 C 语言里面属于标识符(identifier),命名有严格的规范。
- 只能由字母(包括大写和小写)、数字和下划线(
_)组成。 - 不能以数字开头。
- 长度不能超过63个字符。
下面是一些无效变量名的例子:
$zj
j**p
2cat
Hot-tab
tax rate
don't
上面示例中,每一行的变量名都是无效的。
变量名区分大小写,star、Star、STAR都是不同的变量。
并非所有的词都能用作变量名,有些词在 C 语言里面有特殊含义(比如int),另一些词是命令(比如continue),它们都称为关键字,不能用作变量名。另外,C 语言还保留了一些词,供未来使用,这些保留字也不能用作变量名。
在C语言中一共有32个关键字,LPC语言中的关键字和C语言有一些差别,LPC语言中的关键字具体如下(以fluffos为标准):
| LPC语言关键字 | 说明 | 备注 |
|---|---|---|
| __TREE__ | #ifdef DEBUG | |
| array | 声明数组变量或函数 | #ifdef ARRAY_RESERVED_WORD |
| asm | ||
| break | 跳出当前循环 | |
| buffer | 声明缓冲类型变量或函数 | |
| case | 开关语句分支 | |
| catch | ||
| class | 声明结构体变量或函数 | #ifdef STRUCT_CLASS |
| closure | #ifdef COMPAT_32 | |
| continue | 结束当前循环,开始下一轮循环 | |
| default | 开关语句中的"其他"分支 | |
| do | 循环语句的循环体(与 while 连用) | |
| efun | 调用外部函数 | |
| else | 条件语句否定分支(与 if 连用) | |
| float | 声明浮点型变量或函数 | |
| for | 一种循环语句 | |
| foreach | 一种循环语句 | |
| function | 声明函数指针类型变量 | |
| if | 条件语句 | |
| in | 用于 foreach 循环语句 | |
| inherit | 继承对象的方法 | |
| int | 声明整型变量或函数 | |
| mapping | 声明映射类型变量或函数 | |
| mixed | 声明混合类型变量或函数 | |
| new | 声明结构体变量,LPC语言有同名函数 | |
| nomask | 声明方法类型为不可被重写的(类似其它面向对象语言中的final) | |
| nosave | 声明变量类型为nosave | #ifdef SENSIBLE_MODIFIERS |
| object | 声明对象类型变量或函数 | |
| parse_command | ||
| private | 声明方法类型为私有 | |
| protected | 声明方法类型为被保护的 | |
| public | 声明方法类型为公开的(默认类型) | |
| ref | #ifdef REF_RESERVED_WORD | |
| return | 子程序返回语句(可以带参数,也可不带参数)循环条件 | |
| sscanf | ||
| static | 声明静态变量 | #ifndef SENSIBLE_MODIFIERS |
| string | 声明字符串变量或函数 | |
| struct | 声明结构体变量或函数 | #ifdef STRUCT_STRUCT |
| switch | 用于开关语句 | |
| time_expression | ||
| varargs | 声明函数参数为可变长度 | |
| void | 声明函数无返回值或无参数 | |
| while | 循环语句的循环条件 |
关键字是不可以做变量和函数名的,因为LPC和C的关键字的不同,定义变量和函数时要注意了。
另外,驱动预定义的常量不可以做变量名,如:__DIR__(表示当前目录)、__FILE__(表示当前文件)、__LINE__(表示当前行)、__VERSION__(当前驱动版本)等等。
变量的声明
C 语言的变量,必须先声明后使用。如果一个变量没有声明,就直接使用,会报错。
每个变量都有自己的类型(type)。声明变量时,必须把变量的类型告诉编译器。
int height;
上面代码声明了变量height,并且指定类型为int(整数)。
如果几个变量具有相同类型,可以在同一行声明。
int height, width;
// 等同于
int height;
int width;
注意,声明变量的语句必须以分号结尾。
一旦声明,变量的类型就不能在运行时修改。
FluffOS 说明: 在 FluffOS 中,可以在函数体内任何位置声明变量,支持 C99 风格。例如:
void example() { int x = 1; write("x = " + x + "\n"); string msg = "hello"; // 在函数中间声明 int a, b, c; // 同一类型多个变量 int *arr = ({ 1, 2, 3 }); // 带初始化 write(msg + "\n"); }
变量的赋值
C 语言会在变量声明时,就为它分配内存空间,但是不会清除内存里面原来的值。这导致声明变量以后,变量会是一个随机的值。所以,变量一定要赋值以后才能使用。
赋值操作通过赋值运算符(=)完成。
int num;
num = 42;
上面示例中,第一行声明了一个整数变量num,第二行给这个变量赋值。
变量的值应该与类型一致,不应该赋予不是同一个类型的值,比如num的类型是整数,就不应该赋值为小数。虽然 C 语言会自动转换类型,但是应该避免赋值运算符两侧的类型不一致。
变量的声明和赋值,也可以写在一行。
int num = 42;
多个相同类型变量的赋值,可以写在同一行。
int x = 1, y = 2;
注意,赋值表达式有返回值,等于等号右边的值。
int x, y;
x = 1;
y = (x = 2 * x);
上面代码中,变量y的值就是赋值表达式(x = 2 * x)的返回值2。
由于赋值表达式有返回值,所以 C 语言可以写出多重赋值表达式。
int x, y, z, m, n;
x = y = z = m = n = 3;
上面的代码是合法代码,一次为多个变量赋值。赋值运算符是从右到左执行,所以先为n赋值,然后依次为m、z、y和x赋值。
C 语言有左值(left value)和右值(right value)的概念。左值是可以放在赋值运算符左边的值,一般是变量;右值是可以放在赋值运算符右边的值,一般是一个具体的值。这是为了强调有些值不能放在赋值运算符的左边,比如x = 1是合法的表达式,但是1 = x就会报错。
变量的作用域
作用域(scope)指的是变量生效的范围。C 语言的变量作用域主要有两种:文件作用域(file scope)和块作用域(block scope)。
文件作用域(file scope)指的是,在源码文件顶层声明的变量,从声明的位置到文件结束都有效。这种变量被称为全局变量。
int x = 1;
void main() {
printf("%i\n", x);
}
上面示例中,变量x是在文件顶层声明的,从声明位置开始的整个当前文件都是它的作用域,可以在这个范围的任何地方读取这个变量,比如函数main()内部就可以读取这个变量。
块作用域(block scope)指的是由大括号({})组成的代码块,它形成一个单独的作用域。凡是在块作用域里面声明的变量,只在当前代码块有效,代码块外部不可见。这种变量被称为局部变量。
int a = 12;
if (a == 12) {
int b = 99;
printf("%d %d\n", a, b); // 12 99
}
printf("%d\n", a); // 12
printf("%d\n", b); // 出错
上面例子中,变量b是在if代码块里面声明的,所以对于大括号外面的代码,这个变量是不存在的。
代码块可以嵌套,即代码块内部还有代码块,这时就形成了多层的块作用域。它的规则是:内层代码块可以使用外层声明的变量,但外层不可以使用内层声明的变量。如果内层的变量与外层同名,那么会在当前作用域覆盖外层变量。
{
int i = 10;
{
int i = 20;
printf("%d\n", i); // 20
}
printf("%d\n", i); // 10
}
上面示例中,内层和外层都有一个变量i,每个作用域都会优先使用当前作用域声明的i。
LPC 语言重要区别:在LPC语言中作用域内的局部变量不可以重复声明,这是和C语言不同的。例如以下代码在 LPC 中会报错:
void func() { int x = 1; // ... 一些代码 ... int x = 2; // 错误!不能重复声明 }
最常见的块作用域就是函数,函数内部声明的变量,对于函数外部是不可见的。for循环也是一个块作用域,循环变量只对循环体内部可见,外部是不可见的。
for (int i = 0; i < 10; i++)
printf("%d\n", i);
printf("%d\n", i); // 出错
上面示例中,for循环省略了大括号,但依然是一个块作用域,在外部读取循环变量i,编译器就会报错。
LPC语言中局部变量除了不能重复声明外,作用域和C语言完全一致。而全局变量的用法在LPC中比较特别,除了作用域的变化外还有存档相关,具体在后续章节会介绍。
LPC语言中的不同点
在C语言中整型变量分有符号型、无符号型、基本整型、短整型、长整型,但LPC中只有基本整型变量。
在C语言中可以使用 const 关键字定义有名常量,但LPC语言中,没有这种用法,也没有 const 关键字。
另外,在C语言中有取地址指针运算符 & 获取变量的地址,在LPC中没有,LPC游戏开发不需要和底层交互,没有相关功能。
这里强调一点,LPC语言中任何变量类型如果只定义没有赋初值,默认初值都是0,而且对这种没有初始化的变量做类型判断时返回的都是 int 类型。
int x; // 默认初始化为 0
string s; // 默认初始化为 0 (undefined)
mixed *arr; // 默认初始化为 0
FluffOS 说明: 强烈推荐在每个 LPC 文件开头启用严格类型检查:
#pragma strict_types启用后,函数必须声明返回类型和参数类型,调用和赋值会进行类型检查。使用
mixed可以关闭特定位置的检查。
常量
数据有类型,也有常量和变量之分,和C语言一样,常量和变量也是严格区分大小写的,这里重点备注和C语言的不同点。
整型常量
在C语言中整型常量有八进制、十进制、十六进制三种书写形式,而且可以赋值给整型变量。但是LPC中,无法用八进制赋值,具体在第二章第一节的示例 2.1.2.c 中有演示。另外LPC中整型常量不像C语言中分基本整型、短整型和长整型,只用一种基本整型,取值范围为-263 ~ 263-1。
FluffOS 说明: FluffOS 支持以下整数字面量格式:
42 // 十进制 0xFF // 十六进制(0x 或 0X 前缀) 0777 // 八进制(0 前缀) 0b1010 // 二进制(0b 或 0B 前缀) 1_000_000 // 下划线分隔符(提高可读性)整数为 64 位有符号整数。溢出行为:
+、-、*、一元-的溢出为有定义的二进制补码回绕(不是未定义行为),但INT_MIN / -1和INT_MIN % -1仍为未定义行为。
浮点型常量
在C语言中实型(即浮点型)常量有二种书写形式,但在LPC语言中只有十进制小数一种书写形式,取值范围为 ±1.7E-308 和 ±1.7E308 之间。
FluffOS 说明: FluffOS 支持以下浮点字面量格式:
3.14 // 标准形式 1. // 等价于 1.0 2.5e6 // 科学记数法:2500000.0 2.5e-3 // 科学记数法:0.0025 1_000.25 // 带下划线分隔浮点数使用 C 的
double类型,约 15 位有效数字。
字符常量
在C语言中有字符类型变量和常量,但在LPC语言中,只有字符常量,没有字符变量。字符常量使用单引号包裹,返回整数值:
'A' // 返回整数值 65
'\n' // 换行符
'\x41' // 十六进制
'\101' // 八进制
字符常量只能赋值给整型变量。
字符串常量
在C语言中没有字符串变量,只有字符串常量,但在LPC语言中,既有字符串常量,也有字符串变量。
"hello world" // 双引号字符串
"hello" " world" // 相邻字符串编译时自动拼接
"line1\nline2" // 转义序列
"€ 3,50" // 直接写 UTF-8 字符
"\u20ac 3,50" // \uXXXX Unicode 转义
"\x41" // \x 十六进制转义:'A'
"\101" // 八进制转义:'A'
FluffOS 说明: FluffOS 支持模板字面量(反引号)和 Heredoc 文本块,详见后续字符串操作章节。
符号常量
在LPC语言中的符号常量的用法和C语言一样,使用以下方式定义:
#define 符号常量 常量
在LPC中,驱动预定义了很多常量,具体会在后续第七章宏定义中讲解。