由于您是 C++ 新手(正如您所说),我想向您展示一个不错的替代方案,它需要一些不同的想法,但是一旦您习惯了它,您就会喜欢它。
基本思想
您正在寻找一个元素(或者您希望对该元素的引用可能稍后对其进行修改),它是相对于某种比较方法的最小元素。C++ 允许您选择如何比较元素,分别针对特定查询(在这种情况下,不要将比较视为定义明确的“小于”/“大于”运算符,但这是一个类似的概念)。
您可以为这个特定场景在本地定义这样的比较。比较方法可以实现为独立函数、仿函数(实现所谓的“调用运算符”的函数对象)或 lambda 函数,您应该更喜欢它们。
Lambda 函数语法
Lambda 函数是匿名函数,通常在您按字面意思编写它们的地方使用。lambda 函数语法是您必须习惯的东西,但是一旦您习惯了,它就是一个强大的东西!
[](Entity a, Entity b) { return a.X < b.X; }
这是一个 lambda 函数,它接受两个Entity
实例并简单地比较它们的 X 坐标。当然这不是你想要的,但我想先向你展示语法。
现在我们要实现一个比较函数,它比较相对于原点(你的玩家位置)的坐标,所以这是从函数外部传递的东西,而不是作为参数(因为比较函数只能接受两个值进行比较)。这是通过捕获一些上下文变量来完成的。看看这里:
int captured = ...
...
[captured](Entity a, Entity b) { return a.X + captured < b.X + captured; }
仍然没有任何意义,它展示了如何从 lambda 函数内部引用在 lambda 函数外部定义的变量。
针对您的特定问题的 Lambda 函数
现在我们可以正确编写比较方法了:
[fb](Entity a, Entity b) {
double ax = fb.PlayerLocation.X - a.X;
double ay = fb.PlayerLocation.Y - a.Y;
double a = ax * ax + ay * ay; // we can save the sqrt()
double bx = fb.PlayerLocation.X - b.X;
double by = fb.PlayerLocation.Y - b.Y;
double b = bx * bx + by * by; // we can save the sqrt()
return a < b;
}
所以我们捕获fb
,我们计算两个实体的相对坐标a
,b
计算长度的平方(这样我们可以保存 sqrt)并比较这些距离。代码看起来有点臃肿;我们稍后会对此进行改进。
在 STL 算法中使用 Lambda 函数
一旦你了解了如何编写这样的比较函数,最后一步就变得微不足道了,因为STL 提供了许多可以使用的算法,可以使用这样的函数:
Entity closest = *std::min_element(fb.Entities.begin(), fb.Entities.end(),
[fb](Entity a, Entity b) {
double ax = fb.PlayerLocation.X - a.X;
double ay = fb.PlayerLocation.Y - a.Y;
double a = ax * ax + ay * ay; // we can save the sqrt()
double bx = fb.PlayerLocation.X - b.X;
double by = fb.PlayerLocation.Y - b.Y;
double b = bx * bx + by * by; // we can save the sqrt()
return a < b;
}
);
如您所见,我只是将 lambda 函数直接传递给另一个函数调用(没有先定义具有特定名称的函数,因为它是一个匿名函数)。此函数调用 is std::min_element
,它查找两个迭代器之间的最小元素(如果要在整个容器中搜索,请使用开始/结束迭代器对)。它返回另一个迭代器。使用*
前缀运算符,您可以访问迭代器指向的元素。
请注意,一旦将其存储为Entity
值,它就会被复制,因此修改不再直接写入向量,而是写入本地副本。为避免这种情况,请使用Entity&
,它是对向量中元素的(可修改)引用,您的函数可以毫无问题地返回它(只要引用的值在您的函数之外有效,在您的情况下就是这样)。
改进
如果您编写一个比较两个实体的距离函数,这会变得更简单:(注意缺少abs
,这是因为我们无论如何都要对值进行平方,任何负号都会消失)
double distance(Entity p, Entity q) {
double delta_x = p.X - q.X;
double delta_y = p.Y - q.Y;
return sqrt(delta_x * delta_x + delta_y * delta_y);
}
或者,再次保存 sqrt:
double distanceSquare(Entity p, Entity q) {
double delta_x = p.X - q.X;
double delta_y = p.Y - q.Y;
return (delta_x * delta_x + delta_y * delta_y);
}
所以代码变成了:
Entity closest = *std::min_element(fb.Entities.begin(), fb.Entities.end(),
[fb](Entity a, Entity b) {
return distanceSquare(a, fb.PlayerLocation) <
distanceSquare(b, fb.PlayerLocation);
}
);
另一个改进是通过(不可修改的)引用传递变量,而不是通过值传递它们。这意味着不需要复制变量。将代码放在您在问题中编写的方法中,代码变为(应用引用调用概念并返回可修改引用):
double distanceSquare(const Entity & p, const Entity & q) {
double delta_x = p.X - q.X;
double delta_y = p.Y - q.Y;
return (delta_x * delta_x + delta_y * delta_y);
}
Entity & GetNearestEntity()
{
return *std::min_element(fb.Entities.begin(), fb.Entities.end(),
[fb](const Entity & a, const Entity & b) {
return distanceSquare(a, fb.PlayerLocation) <
distanceSquare(b, fb.PlayerLocation);
}
);
}
(注意嵌套的 return 语句。内层是 lambda 函数的一部分,返回比较逻辑的结果。外层返回最终结果,因此是距离最小的实体。)
最后,它看起来更干净(至少在我看来)。一旦你理解了这个概念,你就会看到它的美。:)
Andrew Durward 对这个答案的评论中提到了我将向您展示的最后一个改进:我们现在编写的 Lambda 函数fb
为每次调用复制一次值,GetNearestEntity
因为它可能自上次调用以来发生了变化。我们可以通过引用捕获来避免这种复制操作,这与引用调用的概念相同,但针对捕获的变量。只需&
在捕获表达式中的变量名前面写:
//...
[&fb](const Entity & a, const Entity & b) {
return distanceSquare(a, fb.PlayerLocation) <
distanceSquare(b, fb.PlayerLocation);
}
//...
捕获语法一开始可能看起来有点奇怪,但它提供了强大的控制,可以控制通过引用或值捕获封闭上下文中的哪些变量:
来源:http ://www.cprogramming.com/c++11/c++11-lambda-closures.html
[] Capture nothing (or, a scorched earth strategy?)
[&] Capture any referenced variable by reference
[=] Capture any referenced variable by making a copy
[=, &foo] Capture any referenced variable by making a copy, but capture variable foo by reference
[bar] Capture bar by making a copy; don't copy anything else
[this] Capture the this pointer of the enclosing class
如果您想了解更多关于在 lambda 函数中捕获变量的信息,请在此处阅读或在google 上阅读“C++11 lambda capture syntax”。
此代码的演示:http: //ideone.com/vKAFmx