如何避免if / else如果将一个标题分为8个方向?

我有以下代码:

if (this->_car.getAbsoluteAngle() <= 30 || this->_car.getAbsoluteAngle() >= 330) this->_car.edir = Car::EDirection::RIGHT; else if (this->_car.getAbsoluteAngle() > 30 && this->_car.getAbsoluteAngle() <= 60) this->_car.edir = Car::EDirection::UP_RIGHT; else if (this->_car.getAbsoluteAngle() > 60 && this->_car.getAbsoluteAngle() <= 120) this->_car.edir = Car::EDirection::UP; else if (this->_car.getAbsoluteAngle() > 120 && this->_car.getAbsoluteAngle() <= 150) this->_car.edir = Car::EDirection::UP_LEFT; else if (this->_car.getAbsoluteAngle() > 150 && this->_car.getAbsoluteAngle() <= 210) this->_car.edir = Car::EDirection::LEFT; else if (this->_car.getAbsoluteAngle() > 210 && this->_car.getAbsoluteAngle() <= 240) this->_car.edir = Car::EDirection::DOWN_LEFT; else if (this->_car.getAbsoluteAngle() > 240 && this->_car.getAbsoluteAngle() <= 300) this->_car.edir = Car::EDirection::DOWN; else if (this->_car.getAbsoluteAngle() > 300 && this->_car.getAbsoluteAngle() <= 330) this->_car.edir = Car::EDirection::DOWN_RIGHT; 

我想避免if s链; 这真的很丑。 有没有另外的,可能更干净的方式来写这个?

 #include <iostream> enum Direction { UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT }; Direction GetDirectionForAngle(int angle) { const Direction slices[] = { RIGHT, UP_RIGHT, UP, UP, UP_LEFT, LEFT, LEFT, DOWN_LEFT, DOWN, DOWN, DOWN_RIGHT, RIGHT }; return slices[(((angle % 360) + 360) % 360) / 30]; } int main() { // This is just a test case that covers all the possible directions for (int i = 15; i < 360; i += 30) std::cout << GetDirectionForAngle(i) << ' '; return 0; } 

这是我将如何做到这一点。 (根据我以前的评论)。

您可以使用map::lower_bound并将每个angular度的上限存储在地图中。

下面的工作示例:

 #include <cassert> #include <map> enum Direction { RIGHT, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT, DOWN, DOWN_RIGHT }; using AngleDirMap = std::map<int, Direction>; AngleDirMap map = { { 30, RIGHT }, { 60, UP_RIGHT }, { 120, UP }, { 150, UP_LEFT }, { 210, LEFT }, { 240, DOWN_LEFT }, { 300, DOWN }, { 330, DOWN_RIGHT }, { 360, RIGHT } }; Direction direction(int angle) { assert(angle >= 0 && angle <= 360); auto it = map.lower_bound(angle); return it->second; } int main() { Direction d; d = direction(45); assert(d == UP_RIGHT); d = direction(30); assert(d == RIGHT); d = direction(360); assert(d == RIGHT); return 0; } 

创build一个数组,其中的每个元素都与30度的块关联:

 Car::EDirection dirlist[] = { Car::EDirection::RIGHT, Car::EDirection::UP_RIGHT, Car::EDirection::UP, Car::EDirection::UP, Car::EDirection::UP_LEFT, Car::EDirection::LEFT, Car::EDirection::LEFT, Car::EDirection::DOWN_LEFT, Car::EDirection::DOWN, Car::EDirection::DOWN, Car::EDirection::DOWN_RIGHT, Car::EDirection::RIGHT }; 

然后你可以用angular度/ 30索引数组:

 this->_car.edir = dirlist[(this->_car.getAbsoluteAngle() % 360) / 30]; 

不需要比较或分支。

结果却与原来的略有不同 。 边界上的值,即30,60,120等被放置在下一个类别中。 例如,在原始代码中, UP_RIGHT是31到60.上面的代码将30到59分配给UP_RIGHT

我们可以通过从angular度减去1来解决这个问题:

 this->_car.edir = dirlist[((this->_car.getAbsoluteAngle() - 1) % 360) / 30]; 

这现在给了我们30的UP_RIGHT ,60的RIGHT UP_RIGHT

在0的情况下,expression式变成(-1 % 360) / 30 。 这是有效的,因为-1 % 360 == -1-1 / 30 == 0 ,所以我们仍然得到一个索引0。

C ++标准的 5.6节确认了这个行为:

4二进制/运算符产生商,二进制%运算符产生第一个expression式除以第二个的余数。 如果/%的第二个操作数为零,则行为是未定义的。 对于整型操作数, /运算符会丢弃任何小数部分的代数商。 如果商a/b在结果types中可表示,则(a/b)*b + a%b等于a

编辑:

关于这样的构造的可读性和可维护性提出了许多问题。 motoDrizzt给出的答案是简化更易维护,不太“丑”的原始构造的一个很好的例子。

扩展他的答案,这是使用三元运算符的另一个例子。 由于原始文章中的每个案例都分配给相同的variables,因此使用此操作符可以进一步提高可读性。

 int angle = ((this->_car.getAbsoluteAngle() % 360) + 360) % 360; this->_car.edir = (angle <= 30) ? Car::EDirection::RIGHT : (angle <= 60) ? Car::EDirection::UP_RIGHT : (angle <= 120) ? Car::EDirection::UP : (angle <= 150) ? Car::EDirection::UP_LEFT : (angle <= 210) ? Car::EDirection::LEFT : (angle <= 240) ? Car::EDirection::DOWN_LEFT : (angle <= 300) ? Car::EDirection::DOWN: (angle <= 330) ? Car::EDirection::DOWN_RIGHT : Car::EDirection::RIGHT; 

这个代码并不难看,它很简单,实用,易读,易于理解。 它会以自己的方式孤立,所以没有人会在日常生活中处理它。 而且万一有人要检查它 – 可能是因为他正在debugging你的应用程序,在其他地方出现问题 – 这很容易,需要两秒钟的时间来理解代码以及它的function。

如果我正在进行这样的debugging,我很乐意不必花费五分钟的时间来了解你的function。 在这方面,所有其他function完全失败,因为它们改变了一个简单的,忘了它,没有错误的程序,在一个复杂的混乱,人们在debugging时将被迫深入分析和testing。 作为一名项目经理,我会非常担心开发者采取简单的任务,而不是以一种简单,无害的方式实施它,浪费时间将其实施为复杂的方式。 想想你总是在想这个问题,然后来问这个问题,所有这些都是为了恶化维护和可读性。

也就是说,在你的代码中有一个常见的错误,使得它的可读性差,而且你可以很容易地做一些改进:

 int angle = this->_car.getAbsoluteAngle(); if (angle <= 30 || angle >= 330) return Car::EDirection::RIGHT; else if (angle <= 60) return Car::EDirection::UP_RIGHT; else if (angle <= 120) return Car::EDirection::UP; else if (angle <= 150) return Car::EDirection::UP_LEFT; else if (angle <= 210) return Car::EDirection::LEFT; else if (angle <= 240) return Car::EDirection::DOWN_LEFT; else if (angle <= 300) return Car::EDirection::DOWN; else if (angle <= 330) return Car::EDirection::DOWN_RIGHT; 

把它放到一个方法中,把返回的值赋给对象,折叠方法,然后忘记永恒的剩余时间。

PS还有另外一个超过330的门槛,但我不知道你想怎么处理它,所以我没有修复它。


稍后更新

根据评论,你甚至可以摆脱其他:

 int angle = this->_car.getAbsoluteAngle(); if (angle <= 30 || angle >= 330) return Car::EDirection::RIGHT; if (angle <= 60) return Car::EDirection::UP_RIGHT; if (angle <= 120) return Car::EDirection::UP; if (angle <= 150) return Car::EDirection::UP_LEFT; if (angle <= 210) return Car::EDirection::LEFT; if (angle <= 240) return Car::EDirection::DOWN_LEFT; if (angle <= 300) return Car::EDirection::DOWN; if (angle <= 330) return Car::EDirection::DOWN_RIGHT; 

我没有这样做,因为我觉得有一点,它只是一个自己喜好的问题,我的回答的范围是(而且是)给你一个不同的观点,你对“代码的丑陋”的关注。 无论如何,正如我所说,有人在评论中指出,我认为这是有道理的。

在伪代码中:

 angle = (angle + 30) %360; // Offset by 30. 

所以,我们有90-150 ,…作为分类。 在90度的每个象限中,一个部分有60个,一个部分有30个。所以,现在:

 i = angle / 90; // Figure out the quadrant. Could be 0, 1, 2, 3 j = (angle - 90 * i) >= 60? 1: 0; // In the quardrant is it perfect (eg: RIGHT) or imperfect (eg: UP_RIGHT)? index = i * 2 + j; 

按照适当的顺序在包含枚举的数组中使用索引。

 switch (this->_car.getAbsoluteAngle() / 30) // integer division { case 0: case 11: this->_car.edir = Car::EDirection::RIGHT; break; case 1: this->_car.edir = Car::EDirection::UP_RIGHT; break; ... case 10: this->_car.edir = Car::EDirection::DOWN_RIGHT; break; } 

忽略你的第一个, if这是一个特殊的情况,其余的都遵循完全相同的模式:最小,最大和方向; 伪代码:

 if (angle > min && angle <= max) _car.edir = direction; 

让这个真正的C ++看起来像这样:

 enum class EDirection { NONE, RIGHT, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT, DOWN, DOWN_RIGHT }; struct AngleRange { int min, max; EDirection direction; }; 

现在,而不是写一堆if s,只是循环你的各种可能性:

 EDirection direction_from_angle(int angle, const std::vector<AngleRange>& angleRanges) { for (auto&& angleRange : angleRanges) { if ((angle > angleRange.min) && (angle <= angleRange.max)) return angleRange.direction; } return EDirection::NONE; } 

throwexception而不是return NONE是另一种select)。

然后你会打电话给:

 _car.edir = direction_from_angle(_car.getAbsoluteAngle(), { {30, 60, EDirection::UP_RIGHT}, {60, 120, EDirection::UP}, // ... etc. }); 

这种技术被称为数据驱动编程 。 除了摆脱一堆if s,它将允许您轻松地添加更多的方向(例如,NNW)或减less数量(左,右,上,下),而无需重新工作其他代码。


(处理你的第一个特例就是“给读者一个练习”:-))

尽pipe基于angle / 30的查找表提议的变体可能是优选的,但是这是使用硬编码二进制search来最小化比较次数的替代scheme。

 static Car::EDirection directionFromAngle( int angle ) { if( angle <= 210 ) { if( angle > 120 ) return angle > 150 ? Car::EDirection::LEFT : Car::EDirection::UP_LEFT; if( angle > 30 ) return angle > 60 ? Car::EDirection::UP : Car::EDirection::UP_RIGHT; } else // > 210 { if( angle <= 300 ) return angle > 240 ? Car::EDirection::DOWN : Car::EDirection::DOWN_LEFT; if( angle <= 330 ) return Car::EDirection::DOWN_RIGHT; } return Car::EDirection::RIGHT; // <= 30 || > 330 } 

如果你真的想避免重复,你可以将其expression为一个math公式。

首先,假设我们正在使用@ Geek的Enum

 Enum EDirection { RIGHT =0, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT,DOWN, DOWN_RIGHT} 

现在我们可以使用整数math计算枚举(不需要数组)。

 EDirection angle2dir(int angle) { int d = ( ((angle%360)+360)%360-1)/30; d-=d/3; //some directions cover a 60 degree arc d%=8; //printf ("assert(angle2dir(%3d)==%-10s);\n",angle,dir2str[d]); return (EDirection) d; } 

正如@motoDrizzt指出的,简洁的代码不一定是可读的代码。 它有一个很小的优势,那就是用mathexpression它,表明有些方向覆盖更宽的弧度。 如果你想要这个方向,你可以添加一些断言来帮助理解代码。

 assert(angle2dir( 0)==RIGHT ); assert(angle2dir( 30)==RIGHT ); assert(angle2dir( 31)==UP_RIGHT ); assert(angle2dir( 60)==UP_RIGHT ); assert(angle2dir( 61)==UP ); assert(angle2dir(120)==UP ); assert(angle2dir(121)==UP_LEFT ); assert(angle2dir(150)==UP_LEFT ); assert(angle2dir(151)==LEFT ); assert(angle2dir(210)==LEFT ); assert(angle2dir(211)==DOWN_LEFT ); assert(angle2dir(240)==DOWN_LEFT ); assert(angle2dir(241)==DOWN ); assert(angle2dir(300)==DOWN ); assert(angle2dir(301)==DOWN_RIGHT); assert(angle2dir(330)==DOWN_RIGHT); assert(angle2dir(331)==RIGHT ); assert(angle2dir(360)==RIGHT ); 

添加了断言之后,您添加了重复,但声明中的重复并不是那么糟糕。 如果你有一个不一致的断言,你会很快发现。 断言可以从发行版本以外编译,以免膨胀你分发的可执行文件。 尽pipe如此,如果你想优化代码,而不是让它变得不那么丑,这种方法可能是最适用的。

我迟到了,但是我们可以使用枚举标志和范围检查来做一些简洁的事情。

 enum EDirection { RIGHT = 0x01, LEFT = 0x02, UP = 0x04, DOWN = 0x08, DOWN_RIGHT = DOWN | RIGHT, DOWN_LEFT = DOWN | LEFT, UP_RIGHT = UP | RIGHT, UP_LEFT = UP | LEFT, // just so we be clear, these won't have much use though IMPOSSIBLE_H = RIGHT | LEFT, IMPOSSIBLE_V = UP | DOWN }; 

检查(伪代码),假设angular度是绝对的(在0和360之间):

 int up = (angle > 30 && angle < 150) * EDirection.UP; int down = (angle > 210 && angle < 330) * EDirection.DOWN; int right = (angle <= 60 || angle >= 330) * EDirection.Right; int left = (angle >= 120 && angle <= 240) * EDirection.LEFT; EDirection direction = (Direction)(up | down | right | left); switch(direction){ case RIGHT: // do right break; case UP_RIGHT: // be honest break; case UP: // whats up break; case UP_LEFT: // do you even left break; case LEFT: // 5 done 3 to go break; case DOWN_LEFT: // your're driving me to a corner here break; case DOWN: // :( break; case DOWN_RIGHT: // completion break; // hey, we mustn't let these slide case IMPOSSIBLE_H: case IMPOSSIBLE_V: // treat your impossible case here! break; }