第三节 LPC语言常量和变量

第三节 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

上面示例中,每一行的变量名都是无效的。

变量名区分大小写,starStarSTAR都是不同的变量。

并非所有的词都能用作变量名,有些词在 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赋值,然后依次为mzyx赋值。

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 / -1INT_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中,驱动预定义了很多常量,具体会在后续第七章宏定义中讲解。

京ICP备13031296号-4