跳到主要内容

控制结构

条件语句

if

if 语句检查某个条件是否为真,若为真则执行相应操作。

new
a = 5;
if (a == 5)
{
print("a 是 5");
}

if 后的括号内的代码称为条件表达式,支持多种测试条件(参见运算符章节)。

上述示例中 a 和 5 都是符号,函数也可以作为符号:

if (SomeFunction() == 5)
{
print("SomeFunction() 返回 5");
}

这会检测 SomeFunction 的返回值(见下文)是否等于 5。

还可以组合多个条件进行复合检查:

new
a = 5,
b = 3;
if (a == 5 && b != 3)
{
print("此信息不会被输出");
}

该示例检查 a 等于 5 且 b 不等于 3,但 b 实际为 3 导致条件不满足。

new
a = 5,
b = 3;
if (a == 5 || b != 3)
{
print("此信息将会被输出");
}

该示例检查 a 等于 5 或 b 不等于 3。虽然 b 等于 3 导致后半部分不成立*,但由于 a 等于 5 且我们使用 ||(或)运算符,只要任一条件为真即可(若两个条件都为真语句仍成立,这与自然语言中"或"仅指两者其一的含义略有不同),因此整体条件成立。

此外还可以直接链式比较而无需显式使用与运算符:

new
idx = 3;

if (0 < idx < 5)
{
print("idx 大于 0 且小于 5!");
}

运算符

下列符号可用于条件判断及其说明,部分已在示例中使用。

运算符含义用法
==左表达式等于右表达式if (左表达式 == 右表达式)
!=左表达式不等于右表达式if (左表达式 != 右表达式)
>左表达式大于右表达式if (左表达式 > 右表达式)
>=左表达式大于等于右表达式if (左表达式 >= 右表达式)
<左表达式小于右表达式if (左表达式 < 右表达式)
<=左表达式小于等于右表达式if (左表达式 <= 右表达式)
&&if (左表达式 && 右表达式)
||if (左表达式 || 右表达式)
!if (!变量)
或非if (!(左表达式 || 右表达式))
与非if (!(左表达式 && 右表达式))
异或(xor, eor) - 仅一个条件为真时成立,两者同真则不成立if (!(左表达式 && 右表达式) && (左表达式 || 右表达式))
同或(nxor, neor) - 两者同真或同假时成立if ((左表达式 && 右表达式) || !(左表达式 || 右表达式))

括号

条件语句中另一个重要方面是括号的使用,括号控制着运算顺序:

new
a = 3,
b = 3,
c = 1;
if (a == 5 && b == 3 || c == 1)
{
print("这段会被执行吗?");
}

上述语句有两种解析方式:

if ((a == 5 && b == 3) || c == 1)

或者:

if (a == 5 && (b == 3 || c == 1))

第一种解析会先检查 a 是否等于 5 且 b 是否等于 3,若该条件不成立(即其中任一或两个值不符),则检查 c 是否等于 1。由于 (a == 5 && b == 3) 显然不成立(根据之前知识),我们可用 FALSE 代替该组合:

if (FALSE || c == 1)

已知 FALSE 不成立,但 c 等于 1 使得后半部分成立,因此整个条件语句通过或运算成立。

第二种解析会先检查 a 是否等于 5,若成立则检查 b 是否等于 3 或 c 是否等于 1。虽然引擎实际会先处理 a==5 的判定,但为便于理解我们反向分析。(b ==3 || c ==1) 结果为真(两个子条件均成立),因此整体条件变为:

if (a == 5 && TRUE)

由于 a 实际为 3,(a ==5) 结果为假,故整体表达式简化为:

if (FALSE && TRUE)

显然该条件不成立,因此判定失败。

此案例展示了括号如何改变逻辑判定结果。若无括号,编译器会默认采用第一种解析方式,但为确保明确性应始终使用括号——即使仅为提升代码可读性。

  • 注:在或运算示例中 (b !=3) 实际上并未执行判定(因编译器采用短路求值机制,当首个条件已成立时不再评估后续条件)。若强行评估该条件则会失败。

else

else 语句用于当 if 条件不成立时执行操作:

new
a = 5;
if (a == 3) // 条件不成立
{
print("此信息不会被输出");
}
else
{
print("因主条件失败,此信息将被输出");
}

else if

else if 语句在主条件失败时进行次级条件判定:

new
a = 5;
if (a == 1)
{
print("当 a=1 时输出");
}
else if (a == 5)
{
print("当 a=5 时输出");
}
else
{
print("其他数值时输出");
}

可链式使用多个 else if 语句(每组条件判断中只能有一个 if 和一个 else):

new
a = 4;
if (a == 1)
{
// 条件不成立
}
else if (a == 2)
{
// 条件不成立
}
else if (a == 3)
{
// 条件不成立
}
else if (a == 4)
{
// 条件成立
}
else
{
// 条件不成立
}

需注意:else if 的条件判定基于初始变量值,无法捕获动态变化。例如:

new
a = 5;
if (a == 5)
{
// 条件成立被执行
a = 4; // 修改变量值
}
else if (a == 4)
{
// 不会执行,因首个条件已成立(即使此时 a=4)
}

若需实现动态追踪,需将 else if 改为独立 if 语句。

三元运算符 ?:

'?' 与 ':' 组合称为三元运算符,可在其他语句中实现条件判断功能:

new
a,
b = 3;
if (b == 3)
{
a = 5;
}
else
{
a = 7;
}

上述条件赋值可简化为:

new
a,
b = 3;
a = (b == 3) ? (5) : (7); // 条件成立返回5,否则返回7

问号前为条件表达式,与常规条件语句逻辑相同。问号后为条件成立时返回值,冒号后为条件失败时返回值。支持链式嵌套实现多级条件:

new
a,
b = 3;
if (b == 1)
{
a = 2;
}
else if (b == 2)
{
a = 3;
}
else if (b == 3)
{
a = 4;
}
else
{
a = 5;
}

可以写成:

new
a,
b = 3;
a = (b == 1) ? (2) : ((b == 2) ? (3) : ((b == 3) ? (4) : (5)));

此结构等价于:

new
a,
b = 3;
if (b == 1)
{
a = 2;
}
else
{
if (b == 2)
{
a = 3;
}
else
{
if (b == 3)
{
a = 4;
}
else
{
a = 5;
}
}
}

循环结构

while 循环

"while"循环在条件成立时重复执行代码块,条件检查机制与 if 语句相同:

new
a = 9;
while (a < 10) // 当a<10时持续执行
{
// 循环体内代码
a++; // 每次循环递增a
}
// 循环结束后执行

当 a 初始值为 8 时,循环将执行两次直至 a 达到 10。

for 循环

"for"循环是紧凑型循环结构,包含三个要素:初始化、条件判断、迭代操作:

for (new a = 9; a < 10; a++) // 初始化a=9,条件a<10,每次循环a递增
{
// 循环体内代码
}
// 循环结束后执行

典型应用场景——遍历玩家列表:

for(new i, maxPlayers = GetMaxPlayers(); i < maxPlayers; i++) // 初始化最大玩家数
{
if (IsPlayerConnected(i)) // 检查玩家是否在线
{
// 执行相关操作
}
}

各要素可选择性省略:

new
a = 9;
for ( ; a < 10; ) // 省略初始化和迭代操作
{
// 循环体内代码
a++; // 手动递增
}

作用域差异

  1. 第一个示例中变量 a 仅在循环内有效
  2. 第二个示例中变量 a 在循环外声明,作用域更广
  3. 使用continue语句时,标准 for 循环会执行迭代操作,而省略迭代操作的版本会跳过

执行顺序说明
完整 for 循环中,迭代操作(如 a++)在每次循环结束前执行。当使用continue跳过当前循环时,仍会执行迭代操作,保证循环控制变量正确更新。

do-while 循环

do-while 循环是一种将条件判断置于循环体代码之后(而非之前)的 while 循环结构。这意味着循环内部的代码始终会至少执行一次,因为代码执行先于条件检查:

new
a = 10;
do
{
// 循环内部代码
a++;
}
while (a < 10); // 注意结尾的分号
// 循环后续代码

若将此结构替换为标准 while 循环,变量 a 将不会自增,因为 (a < 10) 的条件初始即为 false。但在此结构中,自增操作发生于条件检查之前。假设 a 的初始值为 9,该循环仍会执行一次;初始值为 8 则执行两次,以此类推。

if-goto 结构

这本质上是上述循环结构经编译后的底层实现。尽管 goto 语句通常不推荐使用,但观察其实现方式有助于理解循环的实际运作机制:

new
a = 9;

loop_start:
if (a < 10)
{
// 循环内部代码
a++;
goto loop_start;
}
// 循环后续代码

OBOE(差一错误)

OBOE 是 Off By One Error(差一错误)的缩写,指循环执行次数多一次或少一次的常见编程错误。例如:

new
a = 0,
b[10];
while (a <= sizeof (b))
{
b[a] = 0;
}

这个简单示例演示了最常见的差一错误场景。乍看之下可能认为该循环会遍历数组 b 的所有元素并置零,但实际上循环会执行 11 次并试图访问不存在的 b[10](即数组中从 0 开始的第 11 个位置),这将导致各种问题。这种现象被称为越界错误(Out Of Bounds,OOB)。

使用 do-while 循环时需特别警惕差一错误,因为此类循环必定会至少执行一次。

Switch 语句

switch 语句

switch 语句本质上是结构化的 if/else if/else 系统(类似于 for 循环是结构化的 while 循环)。通过示例可以最直观地解释其工作原理:

new
a = 5;
switch (a)
{
case 1:
{
// 不会执行
}
case 2:
{
// 不会执行
}
case 5:
{
// 将会执行
}
default:
{
// 不会执行
}
}

这在功能上等价于:

new
a = 5;
if (a == 1)
{
// 不会执行
}
else if (a == 2)
{
// 不会执行
}
else if (a == 5)
{
// 将会执行
}
else
{
// 不会执行
}

但 switch 语句的代码结构更为清晰。

需要特别注意 if 语句和 switch 语句的处理方式差异:

switch (SomeFunction())
{
case 1: {}
case 2: {}
case 3: {}
}

上述代码会调用 SomeFunction() 一次并进行三次结果比对。

if (SomeFunction() == 1) {}
else if (SomeFunction() == 2) {}
else if (SomeFunction() == 3) {}

而此代码会调用 SomeFunction() 三次,效率较低。switch 语句等效于:

new
result = SomeFunction();
if (result == 1) {}
else if (result == 2) {}
else if (result == 3) {}

对于熟悉 C 语言的开发者需注意:PAWN 的 switch 语句不支持 fall-through 机制,每个 case 分支都有独立的作用域,因此不需要 break 语句。

case 分支

case 语句(即 switch 中的 "case X:" 部分)支持多种数值匹配方式。除了单个数值外,还可以匹配数值列表(替代 C 语言的 fall-through)或数值范围:

case 1, 2, 3, 4:

此分支会在检测值为 1、2、3 或 4 时触发,等价于:

if (bla == 1 || bla == 2 || bla == 3 || bla == 4)

但语法更为简洁。当需要匹配连续数值时,推荐使用范围语法:

case 1 .. 4:

此分支效果与前述列表相同,但通过范围检查实现,等价于:

if (bla >= 1 && bla <= 4)
new
a = 4;
switch (a)
{
case 1 .. 3:
{
}
case 5, 8, 11:
{
}
case 4:
{
}
default:
{
}
}

default 分支

default 分支等效于 if 语句中的 else,当所有 case 分支匹配失败时执行。

单行控制语句

goto 语句

goto 语句实现无条件跳转,直接跳转到指定标签位置。if-goto 循环示例已展示其基本用法:

goto my_label;

// 此部分代码将被跳过

my_label: // 标签以冒号结尾且独占一行

// 从此处继续正常执行

由于可能破坏程序流程,goto 语句通常不建议使用。

break 语句

break 语句用于提前终止循环:

for (new a = 0; a < 10; a++)
{
if (a == 5) break;
}

该循环将执行 6 次迭代,但 break 之后的代码仅执行 5 次。

continue 语句

continue 语句用于跳过当前循环迭代:

for (new a = 0; a < 3; a++)
{
if (a == 1) continue;
printf("a = %d", a);
}

输出结果为:

a = 0 a = 2

需特别注意 continue 在某些循环中的使用:

new
a = 0;
while (a < 3)
{
if (a == 1) continue;
printf("a = %d", a);
a++;
}

此例中 continue 会跳过 a++ 语句,导致循环变量 a 始终为 1,形成无限循环。

return 语句

return 语句终止函数执行并返回调用点:

main()
{
print("1");
MyFunction(1);
print("3");
}

MyFunction(num)
{
if (num == 1)
{
return;
}
print("2");
}

输出结果为:

1 3

因为 print("2"); 语句永远不会执行。

return 也可用于返回值:

main()
{
print("1");
if (MyFunction(1) == 27)
{
print("3");
}
}

MyFunction(num)
{
if (num == 1)
{
return 27;
}
print("2");
return 0;
}

注意函数末尾必须显式返回数值(即使函数逻辑上不会执行到该处),因为 PAWN 不允许混合有返回值和无返回值的返回方式。返回值可以是数字、变量或另一个函数的返回值(此时会先执行该函数,其必须返回有效值)。

返回值可存储供后续使用:

main()
{
print("1");
new
ret = MyFunction(1);
if (ret == 27)
{
print("3");
}
}

MyFunction(num)
{
if (num == 1)
{
return 27;
}
print("2");
return 0;
}