【问题标题】:Nested if-statements C++嵌套 if 语句 C++
【发布时间】:2010-12-15 15:00:10
【问题描述】:

我正在用 C++ 编写一个 Snake 程序(使用 JNI 进行可视化),有效的移动按钮是 left(逆时针移动 90°)或 right(移动90°顺时针)。

在游戏循环的每个循环中,我从 GUI 界面检索一个键事件并根据该键事件移动我的蛇,我就是这样做的:

if(key_event != NULL){
    if(key_event == LEFT){
        if(moveDirection == UP || moveDirection == DOWN){
            moveDirection = LEFT;
            (moveDirection == UP) ? /*change turn after head*/ : /*change turn after head*/;
        } //else if (moveDir == LEFT || RIGHT)
    } //else if (key_event == RIGHT)
    //...
}

if with: /*change turn after head*/ 是因为如果蛇在向下移动并向左移动,那么当它向上移动时,会有另一个图形用于转弯向左

这会导致大量的 if 语句并且不是很可读,所以我想知道是否有一种通用的方法来解决这样的嵌套 if 语句。

编辑:
key_event 和 moveDirection 是枚举。

【问题讨论】:

    标签: c++ if-statement nested


    【解决方案1】:

    感觉Finite State Machine 就是您在这里所需要的。您可以将键定义为事件,将蛇位置定义为状态,并根据事件(按下的键)描述从一种状态到另一种状态的转换。这几乎可以解决问题并使用转换表而不是嵌套的if 语句,并使您的实现不易出错。 Boost Statechart Library可以帮你快速实现。

    【讨论】:

    • 我以前从未听说过这个,它看起来很有趣,绝对值得一试,对于这个项目来说可能有点矫枉过正。问题是这是作业的一部分,我们只被允许使用标准库和已经给定的头文件。我一定会为其他项目检查这个!
    【解决方案2】:

    我个人会有另一个功能来应用这个动作。

    if(key_event != NULL){
        if(key_event == LEFT){
           moveLeft();
        }
        ...
    }
    
    void moveLeft()
    {
      switch(moveDirection)
      {
        case UP:
        case DOWN:
          moveDirection = LEFT;
          break;
        case ...
      }
    }
    

    我使用 switch case,我认为这个例子比 if.. else if.. else if... 更易读

    关键是,当你有嵌套循环时,看看你是否可以将其中的一些分解成一个函数来简化问题。

    我希望这会有所帮助。

    【讨论】:

    • 基本上这是walkingTarget和Mark B的答案的组合,这肯定会提高可读性,谢谢!
    【解决方案3】:

    如果您将 if 检查的各个部分放入函数中,则代码很可能更具可读性。如果没有看到正在完成的工作的完整背景,很难确定,但像get_new_direction(event, direction) 这样的东西可能是一个好的开始。当 if 检查的组件是命名良好的函数时,它将有助于提高可读性和嵌套级别。

    【讨论】:

    • 我想知道为什么我没有想到这一点!一定会使用这个,结合 raRaRa 的回答,谢谢!
    【解决方案4】:

    一个简单的查找表会有所帮助:

    enum dir { UP, RIGHT, DOWN, LEFT };
    
    dir lturn[] = {LEFT, UP, RIGHT, DOWN};
    dir rturn[] = {RIGHT, DOWN, LEFT, UP};
    

    这样你就可以写

    dir curr_dir;
    if (moveDirection == LEFT)
        curr_dir = lturn[curr_dir];
    if (moveDirection == RIGHT)
        curr_dir = rturn[curr_dir];
    

    【讨论】:

      【解决方案5】:

      如果您的 key_event 是 int 或 char,您可以使用可能更具可读性的 switch 语句。

      if (moveDirection == UP || moveDirection == DOWN)
      {
          switch (key_event)
          {
              case LEFT:
                  //Turn snake left
                  break;
              case RIGHT:
                  //Turn snake right
                  break;
              default:
                  //Invalid turn, do nothing
                  break;
          }
      }
      else if (moveDirection == LEFT || moveDirection == RIGHT)
      {
          switch (key_event)
          {
              case UP:
                  //Turn snake up
                  break;
              case DOWN:
                  //Turn snake down
                  break;
              default:
                  //Invalid turn, do nothing
                  break;
          }
      }
      

      【讨论】:

      • 确实是int,我很惊讶,因为我的教授说枚举是 C++ 中的独立类型,不像 C 中它们基本上是整数。将与 raRaRa 的答案结合使用,谢谢!
      【解决方案6】:

      嗯,显然你可以用这样的方式消除第一个“if”:

      if( keyEvent == null )
      return;
      

      接下来,使用如下内容:

      Action defaultAction = TurnLeftAction;
      if( keyEvent == Up )
        defaultAction = TurnUpAction;
      else if( keyEvent == Down)
       defaultAction = TurnDownAction;
      else
       defaultAction = TurnRightAction;
      
      defaultAction.Execute();
      

      我希望你明白这个想法:)

      【讨论】:

      • 我喜欢如何消除第一个如果,但我不能这样做,因为它下面有一大段代码,会被跳过。 (我可以把所有的 if 语句放在一个新的方法中,然后我就能做到这一点)不过好主意!
      【解决方案7】:

      你可以用对象做到这一点:

      class MoveState {
          public:
              MoveState* getNextState(MoveDirection change);
              GraphicsStuff* getDirectionChangeGraphics(MoveDirection change);
              void setNextState(MoveDirection change, MoveState* nextState, GraphicsStuff* graphics);
      }
      

      然后你有MoveState 的任一实例代表每个移动方向。您的外部代码可以是:

      MoveState STATES[4];
      
      void init() {
          STATES[UP].setNextState(LEFT, &STATES[LEFT], whatever);
          STATES[UP].setNextState(RIGHT, &STATES[RIGHT], whatever);
          STATES[DOWN].setNextState(LEFT, &STATES[RIGHT], whatever);
          STATES[DOWN].setNextState(RIGHT, &STATES[LEFT], whatever);
          ...
      }
      
      void handleInput() {
          ...
          if(key_event != NULL){
              MoveDirection change = convertEventToDirection(key_event);
              MoveState* nextState = currentState->getNextState(change);
              if (nextState != NULL) {
                  drawCoolGraphics(currentState->getDirectionChangeGraphics(change);
                  currentState = nextState;
              }
          }
      }
      

      您也可以通过为每个移动方向子类化 MoveState 来实现。这取决于你想把代码放在哪里以及它需要有多大的可扩展性。子类化更灵活,但在这种情况下可能有点过头了。

      这应该更不容易出错,因为与大型 if-then-else 或 switch 语句相比,您可以更轻松地对 MoveState 的每个实例进行单元测试。

      编辑:呵呵。这基本上是@Vlad 建议的不太简洁的版本。

      【讨论】:

      • 我认为这是解决此类问题的一种非常通用的方法,我需要进一步研究它,但我可能会尝试按照您所说的方式实现它,作为我自己的练习.感谢您的贡献!
      【解决方案8】:

      我建议将蛇的方向表示为向量,四个方向是 (1 0) - 右,(-1 0) - 左,(0 1) - 上和 (0 -1) 下。这将消除程序中的大多数开关,因为使蛇移动是通过简单的数学处理的,而不是通过将任意枚举值映射到移动。

      struct Vector2D 
      { 
          int x, y; 
          Vector2D(int x, int y); 
          ... 
      };
      

      然后左右转变成:

      Vector2D turn_left(Vector2D direction)
      {
          return Vector2D(-direction.y, direction.x);
      }
      
      Vector2D turn_right(Vector2D direction)
      {
          return Vector2D(direction.y, -direction.x);
      }
      

      密钥将由以下人员处理:

      if (key_event == LEFT) snake_direction = turn_left(snake_direction);
      else if (key_event == RIGHT) snake_direction = turn_right(snake_direction);
      

      【讨论】:

      • 虽然这也是我的方法,但我无法实现,因为这是作业的一部分。在这个任务中,我们得到了固定的头文件和限制(IMO 消除了我们为解决这个问题而必须经历的跟踪和错误和逻辑)我们必须实现。我的蛇可以工作,如果我想在空闲时间优化它,我会尝试这种方法:)。
      【解决方案9】:

      您可以使用跳转表,假设关键事件从零开始并且是整数类型,并且该函数是非静态的。然而,它的语义开销以及时间和空间都相当多。一个好的开关盒可能是真正最简单的选择。

      【讨论】:

      • 我现在将使用 switch-case,因为 - 正如你所提到的 - 它是最简单的选项,它给了我足够的可读性。
      【解决方案10】:

      这个问题有点老了,但我想贡献一下。我个人更喜欢提前返回以避免嵌套 if。同样对于您的情况,我使用映射。我会编写如下代码。

      if(key_event == NULL)
          return;
      
      static const map<pair<int , int> , int> directionMapping = {
          make_pair(make_pair(LEFT , UP) , 180) ,
          make_pair(make_pair(LEFT , DOWN) , 0) ,
          make_pair(make_pair(LEFT , RIGHT) , 90) ,
          make_pair(make_pair(LEFT , LEFT) , 270) ,
          make_pair(make_pair(RIGHT , UP) , 0) ,
          make_pair(make_pair(RIGHT , DOWN) , 180) ,
          make_pair(make_pair(RIGHT , RIGHT) , 270) ,
          make_pair(make_pair(RIGHT , LEFT) , 90)
      };
      
      auto newDirection = directionMapping.at(make_pair(key_event , current_direction));
      
      cout << "New direction is " << newDirection << endl;
      

      它更具可读性,没有嵌套,没有深度,更易于维护。例如,如果您想同时支持 UP 和 DOWN 箭头键,您只需在映射中添加一些对。

      映射可以用来执行不同的程序。看看这个

      if(type == "Foo")
      //Do something
      else if(type == "Bar")
      //Do another thing
      else if(type == "Nothing")
      //Do nothing
      

      this的映射方式,c++11后支持。

      static const map<string , function<void()>> fMapping = {
              make_pair("Foo" , [](){ cout << "I am doing something"; }),
              make_pair("Bar" , [](){ cout << "I am doing another thing"; }) ,
              make_pair("Nothing" , [](){ cout << "I am doing nothig"; })
      };
      
      string type = "Foo";
      fMapping.at(type)();
      

      请注意,这种类型的映射在工厂类中非常有用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-01
        • 2016-04-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多