MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C++中switch语句的参数限制

2021-08-215.0k 阅读

switch 语句基础回顾

在 C++ 中,switch 语句是一种多分支选择结构,它允许根据一个表达式的值来选择执行多个代码块中的一个。其基本语法如下:

switch (expression) {
    case constant1:
        // 执行代码块1
        break;
    case constant2:
        // 执行代码块2
        break;
    default:
        // 当 expression 的值与所有 case 常量都不匹配时执行
        break;
}

其中,expression 是要进行求值的表达式,constant1constant2 等是常量表达式,break 语句用于跳出 switch 结构,避免继续执行下一个 case 分支。如果没有 break,程序会“穿透”到下一个 case 分支继续执行,直到遇到 break 或者 switch 语句结束。

switch 语句参数限制概述

switch 语句的参数,也就是 switch 关键字后面括号内的 expression,有着特定的限制。这些限制主要围绕表达式的类型、求值的确定性以及与 case 常量的匹配规则等方面。理解这些限制对于正确使用 switch 语句至关重要,否则可能会导致编译错误或者未定义行为。

表达式类型限制

  1. 整型类型
    • 基本整型switch 语句的表达式最常见的类型是整型。这包括 charshortintlong 及其对应的无符号版本(unsigned charunsigned shortunsigned intunsigned long)。例如:
char ch = 'a';
switch (ch) {
    case 'a':
        std::cout << "It's a" << std::endl;
        break;
    case 'b':
        std::cout << "It's b" << std::endl;
        break;
    default:
        std::cout << "Other character" << std::endl;
        break;
}

在这个例子中,switch 的表达式是 char 类型,case 常量也是 char 类型,这种匹配是符合要求的。 - 枚举类型:枚举类型本质上也是整型的一种,因此也可以作为 switch 语句的表达式。例如:

enum class Weekday {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
};

Weekday today = Weekday::Tuesday;
switch (today) {
    case Weekday::Monday:
        std::cout << "Start of the week" << std::endl;
        break;
    case Weekday::Tuesday:
        std::cout << "It's Tuesday" << std::endl;
        break;
    // 其他 case 分支省略
    default:
        std::cout << "Another day" << std::endl;
        break;
}

这里使用了 enum class 定义的枚举类型,在 switch 语句中使用时,case 常量需要使用枚举成员的完整限定名。如果是普通的 enum,在 case 中可以直接使用枚举成员名。 2. 不允许的类型 - 浮点类型floatdouble 等浮点类型不能作为 switch 语句的表达式。这是因为浮点类型的精度问题,两个看似相等的浮点数在内存中的表示可能存在细微差异,这会导致 switch 语句在比较时出现不可预测的结果。例如:

// 以下代码会导致编译错误
float num = 3.14f;
switch (num) {
    case 3.14f:
        std::cout << "It's 3.14" << std::endl;
        break;
    default:
        std::cout << "Other value" << std::endl;
        break;
}
- **自定义类类型**:一般情况下,自定义类类型也不能直接作为 `switch` 语句的表达式。这是因为 `switch` 语句要求表达式的值能够直接与 `case` 常量进行比较,而自定义类类型的比较通常需要定义复杂的比较运算符,这与 `switch` 语句的设计初衷不符。例如:
class MyClass {
public:
    int value;
    MyClass(int val) : value(val) {}
};

// 以下代码会导致编译错误
MyClass obj(10);
switch (obj) {
    case MyClass(10):
        std::cout << "Matched" << std::endl;
        break;
    default:
        std::cout << "Not matched" << std::endl;
        break;
}

不过,如果自定义类重载了 operator int() 这样的类型转换运算符,将类对象转换为整型,那么在一定程度上可以间接用于 switch 语句,但这种做法并不常见且可能带来代码可读性问题。

表达式求值的确定性

  1. 常量表达式求值 switch 语句的表达式必须在编译时能够确定其值。这意味着表达式中只能包含常量、常量表达式以及在编译时可求值的函数调用。例如:
const int num1 = 10;
const int num2 = 20;
int result = num1 + num2; // 这是运行时求值
constexpr int constexpr_result = num1 + num2; // 这是编译时求值

// 以下代码中,使用 result 会导致编译错误
// switch (result) { 
//     case 30:
//         std::cout << "Matched" << std::endl;
//         break;
//     default:
//         std::cout << "Not matched" << std::endl;
//         break;
// }

// 使用 constexpr_result 是正确的
switch (constexpr_result) { 
    case 30:
        std::cout << "Matched" << std::endl;
        break;
    default:
        std::cout << "Not matched" << std::endl;
        break;
}

在上述例子中,result 是在运行时求值的变量,不能用于 switch 语句。而 constexpr_resultconstexpr 修饰的常量表达式,在编译时就可以确定值,因此可以用于 switch 语句。 2. 函数调用的限制 如果在 switch 表达式中调用函数,该函数必须是 constexpr 函数,以便在编译时求值。例如:

constexpr int add(int a, int b) {
    return a + b;
}

int main() {
    const int num1 = 5;
    const int num2 = 7;
    switch (add(num1, num2)) { 
        case 12:
            std::cout << "Matched" << std::endl;
            break;
        default:
            std::cout << "Not matched" << std::endl;
            break;
    }
    return 0;
}

这里的 add 函数是 constexpr 函数,因此在 switch 表达式中调用它是合法的,因为在编译时就能确定 add(num1, num2) 的值。

case 常量与表达式的匹配规则

  1. 类型一致性 case 常量的类型必须与 switch 表达式的类型完全一致(对于 enum 类型,遵循枚举类型的比较规则)。例如,如果 switch 表达式是 unsigned int 类型,那么 case 常量也必须是 unsigned int 类型。
unsigned int num = 5u;
switch (num) {
    case 5u:
        std::cout << "Matched unsigned int" << std::endl;
        break;
    // 如果写成 case 5: (这里 5 是 int 类型)会导致编译错误
    default:
        std::cout << "Not matched" << std::endl;
        break;
}
  1. 唯一性 case 常量的值在同一个 switch 语句中必须是唯一的。不能有两个或多个 case 常量具有相同的值,否则会导致编译错误。例如:
int value = 10;
switch (value) {
    case 10:
        std::cout << "First 10" << std::endl;
        break;
    case 10: // 编译错误:重复的 case 常量值
        std::cout << "Second 10" << std::endl;
        break;
    default:
        std::cout << "Not 10" << std::endl;
        break;
}
  1. 整型提升规则switch 语句中,如果 switch 表达式是比 int 小的整型(如 charshort 等),会发生整型提升。case 常量也会进行相应的整型提升,然后再与提升后的 switch 表达式值进行比较。例如:
char ch = 'A';
switch (ch) {
    case 'A': // 'A' 会提升为 int 类型与 ch 提升后的 int 值比较
        std::cout << "It's A" << std::endl;
        break;
    default:
        std::cout << "Not A" << std::endl;
        break;
}

这里 chchar 类型,在 switch 语句中会提升为 int 类型,'A' 也会提升为 int 类型,然后进行比较。

特殊情况与注意事项

  1. fall - through 行为 虽然通常情况下每个 case 分支后都应该有 break 语句,但有时故意不写 break 可以利用 fall - through 行为。例如,在处理相似的情况时:
int num = 2;
switch (num) {
    case 1:
    case 2:
        std::cout << "It's 1 or 2" << std::endl;
        break;
    case 3:
        std::cout << "It's 3" << std::endl;
        break;
    default:
        std::cout << "Other number" << std::endl;
        break;
}

在这个例子中,当 num 为 1 或 2 时,都会执行 std::cout << "It's 1 or 2" << std::endl; 这行代码,因为没有 break 导致 fall - through。但这种行为需要谨慎使用,否则可能导致逻辑错误,尤其是在大型代码库中。 2. default 分支的作用 default 分支不是必需的,但它提供了一种处理 switch 表达式值与所有 case 常量都不匹配的情况的方式。合理使用 default 分支可以增强程序的健壮性。例如:

int code = 4;
switch (code) {
    case 1:
        std::cout << "Code 1" << std::endl;
        break;
    case 2:
        std::cout << "Code 2" << std::endl;
        break;
    default:
        std::cout << "Unknown code" << std::endl;
        break;
}

在这个例子中,如果 code 不是 1 或 2,就会执行 default 分支,输出“Unknown code”。如果没有 default 分支,当 code 与所有 case 常量都不匹配时,switch 语句将不执行任何分支。 3. 嵌套 switch 语句 switch 语句可以嵌套使用,即一个 switch 语句可以包含在另一个 switch 语句的 case 分支中。但嵌套层次过多可能会导致代码可读性变差。例如:

int outerValue = 1;
int innerValue = 2;
switch (outerValue) {
    case 1:
        switch (innerValue) {
            case 1:
                std::cout << "Inner 1" << std::endl;
                break;
            case 2:
                std::cout << "Inner 2" << std::endl;
                break;
            default:
                std::cout << "Inner other" << std::endl;
                break;
        }
        break;
    case 2:
        std::cout << "Outer 2" << std::endl;
        break;
    default:
        std::cout << "Outer other" << std::endl;
        break;
}

在这个例子中,外层 switch 根据 outerValue 的值决定执行哪个分支,而内层 switch 又根据 innerValue 的值进一步选择执行的代码块。

与其他条件语句的比较

  1. 与 if - else if 语句的比较
    • 可读性:在处理多个离散值的情况时,switch 语句通常比 if - else if 语句更具可读性。例如:
// if - else if 语句
int num = 3;
if (num == 1) {
    std::cout << "It's 1" << std::endl;
} else if (num == 2) {
    std::cout << "It's 2" << std::endl;
} else if (num == 3) {
    std::cout << "It's 3" << std::endl;
} else {
    std::cout << "Other number" << std::endl;
}

// switch 语句
switch (num) {
    case 1:
        std::cout << "It's 1" << std::endl;
        break;
    case 2:
        std::cout << "It's 2" << std::endl;
        break;
    case 3:
        std::cout << "It's 3" << std::endl;
        break;
    default:
        std::cout << "Other number" << std::endl;
        break;
}

switch 语句的结构更加清晰,每个 case 分支对应一个特定的值,而 if - else if 语句在值较多时可能显得比较冗长。 - 效率:在现代编译器优化下,switch 语句和 if - else if 语句在效率上差异不大。不过,对于有大量离散值的情况,switch 语句可以使用跳转表(jump table)等优化技术,在某些情况下可能会比 if - else if 语句执行得更快。 2. 与三元运算符的比较 三元运算符 (condition? value1 : value2) 适用于简单的二选一情况,而 switch 语句用于处理多个可能值的情况。例如:

// 三元运算符
int a = 5;
int result = (a > 10? 100 : 200);

// switch 语句不适用于这种简单二选一情况
// 但对于多值选择,如:
int num = 2;
int output;
switch (num) {
    case 1:
        output = 10;
        break;
    case 2:
        output = 20;
        break;
    default:
        output = 30;
        break;
}

三元运算符简洁明了,适用于简单的条件求值,而 switch 语句功能更强大,用于处理多分支逻辑。

在实际项目中的应用场景

  1. 状态机实现 在实现状态机时,switch 语句非常有用。例如,一个简单的游戏角色状态机:
enum class CharacterState {
    Idle,
    Running,
    Jumping,
    Attacking
};

CharacterState currentState = CharacterState::Idle;

// 假设根据用户输入更新状态
void updateState(CharacterState newState) {
    switch (currentState) {
        case CharacterState::Idle:
            if (newState == CharacterState::Running) {
                std::cout << "Character starts running" << std::endl;
            }
            break;
        case CharacterState::Running:
            if (newState == CharacterState::Jumping) {
                std::cout << "Character starts jumping while running" << std::endl;
            }
            break;
        // 其他状态处理省略
        default:
            break;
    }
    currentState = newState;
}

在这个例子中,switch 语句根据当前角色状态和新的状态输入,处理状态转换逻辑。 2. 命令解析 在命令行程序或者一些需要解析用户输入命令的场景中,switch 语句可以方便地根据不同的命令执行相应的操作。例如:

#include <iostream>
#include <string>

int main() {
    std::string command;
    std::cout << "Enter a command (start, stop, status): ";
    std::cin >> command;

    if (command == "start") {
        std::cout << "Starting the process..." << std::endl;
    } else if (command == "stop") {
        std::cout << "Stopping the process..." << std::endl;
    } else if (command == "status") {
        std::cout << "Process status: running" << std::endl;
    } else {
        std::cout << "Unknown command" << std::endl;
    }

    // 使用 switch - case 结合字符串哈希值实现更高效的命令解析(简化示例)
    // 假设我们有一个简单的字符串哈希函数
    unsigned long hash(const std::string& str) {
        unsigned long hash = 5381;
        for (char c : str) {
            hash = ((hash << 5) + hash) + c;
        }
        return hash;
    }

    unsigned long commandHash = hash(command);
    switch (commandHash) {
        case hash("start"):
            std::cout << "Starting the process..." << std::endl;
            break;
        case hash("stop"):
            std::cout << "Stopping the process..." << std::endl;
            break;
        case hash("status"):
            std::cout << "Process status: running" << std::endl;
            break;
        default:
            std::cout << "Unknown command" << std::endl;
            break;
    }

    return 0;
}

在这个例子中,最初使用 if - else if 来解析命令,后面展示了一种通过字符串哈希值结合 switch 语句进行命令解析的方式,这种方式在命令较多时可能会更高效。

总结

switch 语句在 C++ 编程中是一个强大的多分支选择结构,但它的参数有着严格的限制。从表达式类型的限定,到求值的确定性,再到 case 常量与表达式的匹配规则,每一个方面都需要开发者严格遵守,以确保程序的正确性和稳定性。同时,在实际应用中,要根据具体的需求合理选择 switch 语句与其他条件语句,充分发挥其优势,提高代码的可读性和效率。无论是实现状态机还是解析命令,switch 语句都能在合适的场景中发挥重要作用。