65.9K
CodeProject 正在变化。 阅读更多。
Home

一个符合STL的数学向量类

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.78/5 (8投票s)

2005年5月12日

6分钟阅读

viewsIcon

53105

downloadIcon

1113

一个支持模板、符合STL的数学向量类的可移植实现。

引言

本文附加的头文件实现了EVector类。为了从一开始就避免混淆,这个类不是一个数据结构,也不是STL容器。然而,它类似于STL提供的std::vector类,尽管它们的用途截然不同。

std::vector基本上是类型为T的对象的一个容器。而这个类,则是一个数学意义上的向量的实现。因此,它包含了如+, -, *等运算符,以及用于操作向量的其他函数。

背景

这个类是我正在编写的一个库的一部分。我想创建一些数学基础设施供库的其他部分使用。到目前为止,我已经使用这个类编写了一个矩阵类,并为图形和计算几何算法中的2D和3D向量特化了情况。尽管我还没有提供那些类(以后可能会的话……),但我认为任何用户都可以非常容易地为这些目的使用/扩展/特化这个类。

实现

在编写这个类时,我遵循了以下指南:

  • 这个类非常底层,几乎随处可用。因此,它必须写得好,并符合广泛使用的约定。
  • 未来的更改将是功能的添加,而不是现有行为的更改。
  • 可移植性。
  • 为了能被其他第三方库使用或替换类似的类,这个类应该符合标准行为。
  • 其他人可能会使用这个类,因此它必须文档齐全。这也有助于未来的更改。

为了遵守上述规定,代码使用了Doxygen进行文档化。此外,它还符合STL。这意味着它有一个分配器对象和适当的迭代器,可用于遍历向量的元素。向量的元素使用分配器进行分配和释放。

这个类的意图之一是用于图形和运行时关键的代码。你们中任何使用过OpenGL或Direct3D的人都熟悉顶点数组。顶点数组正如其名——就是(通常是3D)向量的一个数组。这样的数组可以被缓存或优化,出于这些原因,没有必要强制使用每个EVector实例的分配器。在这个例子中,一个EVector数组可以很容易地用作传递给OpenGL的顶点数组,而且程序员甚至不需要计算连续EVector实例之间的偏移量(尽管这很容易……)。如果每个EVector实例都没有分配器,这样的数组肯定会占用更少的内存。这就是我使用E_VECTOR_USE_ALLOCATOR标志的原因。

如果你查看代码,你会看到E_VECTOR_USE_ALLOCATOR标志。如果定义了这个标志,EVector会有一个模板参数A,它决定了分配器的类型(默认为std::allocator)。此外,每个EVector对象实例都将持有一个分配器实例——就像std::vector(或其他任何STL容器)一样。然而,如果它没有被定义,相同类型的EVector的所有实例(即具有相同的Tn)将共享同一个分配器实例。简而言之,E_VECTOR_USE_ALLOCATOR决定了分配器是否是EVector的静态成员。这仍然允许灵活地提供不同的对象分配方式,而不会带来大多数情况下可能出现的复杂性和内存开销。需要强调的是,无论是否定义E_VECTOR_USE_ALLOCATOREVector都可以被相同地使用。这是因为如果E_VECTOR_USE_ALLOCATOR未被定义,A就成为该类的typedef,而get_allocator()简单地返回静态成员。

不幸的是,我无法深入到详细的数学细节,所以我假设这个类的大多数用户都了解一些线性代数。这真的不复杂。如上所述,该类包含了操作向量的标准函数。如果缺少了什么并且应该有,请告诉我。:-)

如上所述,该类是符合STL的——这允许它被STL算法使用。我在一个小的测试程序中演示了std::reverse的情况,当然,它也可以用于其他算法。它还包括简单的到流的序列化和从流的反序列化。

一个重要的注意点。向量的大小是静态的!它不能在运行时更改,并且完全由模板参数n决定。这与我见过的其他一些类形成对比。这有几个原因。主要两个是简单性和性能。正如我所说,这个类旨在用于性能关键的任务。如果大小可以在运行时更改,那么加法、减法、点积等简单操作必须通过在运行时检查向量大小是否相同并抛出异常来验证。保持大小是静态的并且是模板参数的一部分,消除了所有这些问题,因为这些都在编译期间得到了验证。如果有人尝试定义

EVector< float, 5 > v;
EVector< float, 3 > u;

cout << u+v << endl;    // compilation error!

由于上述运算符定义为相同大小的向量,所以会得到一个编译错误。

使用代码

这很简单。您所要做的就是将*EVector.h*包含到您的源代码中。然后您可以定义

EVector< double, 4 > v;    // creates an invalid vector of 4 doubles
EVector< int, 2 > p;    // creates an invalid vector of 2 integers
v.fill(0.1);    // v = (0.1, 0.1, 0.1, 0.1)
p[0] = 1; p[1] = 5;    // p = (1, 5)
v.normalize();    // |v| = v.length() = 1
std::reverse(p.begin(), p.end());    // p = (5, 1)

并开始享受它们的乐趣……

请查看代码中的(Doxygen)注释,以了解为该类定义的运算符和函数。

关注点

请注意,默认构造函数创建一个无效向量。无效向量是指其某个参数是NaN(非数字)的向量。要检查向量的有效性,应该使用isValid()成员函数。

您会看到,对于某些函数,有一个可选的epsilon参数。这是因为一些计算(实际上,大多数计算……)会产生数值误差。例如,两个向量如果它们的点积为0,则被认为是正交的。由于计算机只有有限的精度,两个实际上正交的向量在计算它们的点积时可能不会得到精确的0。epsilon参数通过考虑点积小于或等于epsilon的向量为正交,点积大于epsilon的向量为非正交来解决这个问题。相同的原则适用于其他类型的测试。

历史

还没有历史记录……

© . All rights reserved.