---
title: "控制结构 | open.mp"
description: "Pawn语言中的控制结构概述"
canonical: https://omp.itsneufox.com/zh-CN/docs/scripting/language/ControlStructures
language: zh-CN
---

# 控制结构

## 条件语句

### if

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

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

在 `if` 后的括号内的代码称为条件表达式，支持多种测试条件（参见运算符章节）。

上述示例中 `a` 和 5 都是符号，函数也可以作为符号：

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

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

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

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

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

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

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

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

```c
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 ((左表达式 && 右表达式) || !(左表达式 || 右表达式)) |

### 括号

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

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

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

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

或者：

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

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

```c
if (FALSE || c == 1)
```

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

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

```c
if (a == 5 && TRUE)
```

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

```c
if (FALSE && TRUE)
```

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

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

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

### else

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

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

### else if

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

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

可链式使用多个 else if 语句（每组条件判断中只能有一个 if 和一个 else）：

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

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

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

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

### 三元运算符 ?:

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

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

上述条件赋值可简化为：

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

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

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

可以写成:

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

此结构等价于：

```c
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 语句相同：

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

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

### for 循环

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

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

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

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

各要素可选择性省略：

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

**作用域差异**：

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

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

### do-while 循环

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

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

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

### if-goto 结构

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

```c
new
    a = 9;

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

### OBOE（差一错误）

OBOE 是 Off By One Error（差一错误）的缩写，指循环执行次数多一次或少一次的常见编程错误。例如：

```c
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 循环）。通过示例可以最直观地解释其工作原理：

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

这在功能上等价于：

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

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

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

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

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

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

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

```c
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）或数值范围：

```c
case 1, 2, 3, 4:
```

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

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

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

```c
case 1 .. 4:
```

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

```c
if (bla >= 1 && bla <= 4)
```

```c
new
    a = 4;
switch (a)
{
    case 1 .. 3:
    {
    }
    case 5, 8, 11:
    {
    }
    case 4:
    {
    }
    default:
    {
    }
}
```

### default 分支

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

## 单行控制语句

### goto 语句

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

```c
goto my_label;

// 此部分代码将被跳过

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

// 从此处继续正常执行
```

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

### break 语句

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

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

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

### continue 语句

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

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

输出结果为：

```c
a = 0 a = 2
```

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

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

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

### return 语句

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

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

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

输出结果为：

1 3

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

return 也可用于返回值：

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

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

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

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

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

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

## 相关阅读

-   [关键字：语句](https://omp.itsneufox.com/zh-CN/docs/scripting/language/Statements)
-   [关键字：操作符](https://omp.itsneufox.com/zh-CN/docs/scripting/language/Operators)
-   [关键字：预处理器指令](https://omp.itsneufox.com/zh-CN/docs/scripting/language/Directives)
-   [关键字：初始化器](https://omp.itsneufox.com/zh-CN/docs/scripting/language/Initialisers)
