对于任何可以帮助我解释何时以及何时不将运算符重载为类或结构的成员(例如,或不重载为成员,如全局成员)的任何人。
问题是,我正在学习所有关于向量和 3D 向量等的数学学习目的,并尝试模拟它们的常见操作,以便更好地理解它们的行为。
所以,我买了一本与我略有涉及相同方面的书,但我读到了这本书的作者用 C++ 编写了一个简单的向量结构并得到了一堆重载运算符,其中一些运算符被声明和在结构内部定义,有些则没有。
让我给你举一个让我完全困惑的例子。下面的示例将显示两个不同的重载运算符,其中两个运算符执行相同的“总体”目的(我认为?),即(向量/标量乘法),但是,一个是结构体的成员,另一个是结构体的成员
struct Vector3D
{
float x, y, z;
Vector3D() = default;
Vector3D(float a, float b, float c)
{
x = a;
y = b;
z = c;
}
Vector3D& operator*=(float s)
{
x *= s;
y *= y;
z *= z;
return(*this);
}
Vector3D& operator/=(float s)
{
s = 1.0f / s;
x *= s;
y *= s;
z *= s;
return(*this);
}
float& operator[](int i)
{
return ((&x)[i]);
}
const float& operator[](int i) const
{
return ((&x)[i]);
}
};
inline Vector3D operator*(const Vector3D& v, float s)
{
return (Vector3D(v.x * s, v.y * s, v.z * s));
}
我特别想问的是
Vector3D& operator*=(float s)
{
x *= s;
y *= s;
z *= s;
return(*this);
}
还有
inline Vector3D operator*(const Vector3D& v, float s)
{
return (Vector3D(v.x * s, v.y * s, v.z * s));
}
如果有人有明确的解释请分享
我当然尝试了上面的代码,看看它们有什么不同,我发现了一些不同的地方:
当我尝试使用这两种方法将向量与标量(任何浮点数)相乘的最终结果打印到屏幕上时,只有成员重载运算符会立即打印出来。除非将返回值分配给另一个向量实例,否则无法打印结构体之外的向量实例。
例如:
int main()
{
float s = 2; //Defining the scalar
// Invoking the first overloaded operator
Vector3D vecA(2, 2, 2);
vecA *= 3;
for(int i = 0; i < 3; ++i)
{
std::cout << vecA[i] << std::endl; // 6, 6, 6 (Worked)
}
// Now Invoking the global overloaded operator
Vector3D vecB(2, 2, 2);
vecB * s;
for(int i = 0; i < 3; ++i)
{
std::cout << vecB[i] << std::endl; // 2, 2, 2 (No change)
}
}
选择非成员运算符的原因之一是对称性。
标量乘法就是一个很好的例子。希望
(s * vec)
和 (vec * s)
都是允许的表达式是合理的。但是,如果您想使用 s
作为左操作数,则必须使用非成员函数。您不能使用成员函数,因为 s
不是 Vector3D
对象。
下面程序中的注释,进一步说明。该程序包含一些您可能会觉得有用的习语,包括“隐藏的朋友”习语。
// main.cpp
#include <array>
#include <cstddef>
#include <format>
#include <iostream>
#include <iomanip>
class Vector3D
{
enum : std::size_t { zero, one, two, three };
using value_type = double;
std::array<value_type, three> v{};
public:
value_type& x() noexcept { return v[zero]; }
value_type& y() noexcept { return v[one]; }
value_type& z() noexcept { return v[two]; }
value_type const& x() const noexcept { return v[zero]; }
value_type const& y() const noexcept { return v[one]; }
value_type const& z() const noexcept { return v[two]; }
Vector3D() noexcept
= default;
Vector3D(
value_type const x,
value_type const y,
value_type const z) noexcept
{
this->x() = x;
this->y() = y;
this->z() = z;
}
Vector3D& operator*=(value_type const s) noexcept
{
// operator*= must be a member function.
x() *= s;
y() *= s;
z() *= s;
return *this;
}
friend Vector3D operator*(Vector3D v, value_type const s) noexcept
{
// Note that v is a value parameter that receives
// a copy of its argument. Changing v here, will not
// change the original argument used when operator*
// is invoked.
//
// This function could also be implemented as a
// member function (with only one parameter, s).
//
// It is common for operator* to be implemented by
// calling member function operator*= to do the work.
return v *= s;
}
friend Vector3D operator*(value_type const s, Vector3D v) noexcept
{
// In order to have a symmetric operator*, where s
// can appear either first or second, we have to code
// a second version of operator*.
//
// This function cannot be a member function, because
// the first operand, s, is not an object!
//
// Note that the two operator* functions use the
// "hidden friend" idiom.
return v *= s;
}
Vector3D& operator+=(Vector3D const& that) noexcept
{
// operator+= must be a member function.
x() += that.x();
y() += that.y();
z() += that.z();
return *this;
}
friend Vector3D operator+(Vector3D a, Vector3D const& b)
{
// operator+ can be implemented either as a
// member function (with one parameter), or as
// a non-member function (with two parameters).
//
// I have chosen to use a non-member function,
// so that I can treat the left operand as
// a "local" variable.
//
// Note that parameter `a` is a value parameter.
// It receives a copy of its argument.
//
// Unlike scalar multiplication, there is no "symmetry"
// problem here, so I only need one version of
// operator+.
return a += b;
}
std::string to_string() const
{
return std::format("[{}, {}, {}]", x(), y(), z());
}
friend std::ostream& operator<< (
std::ostream& ost,
Vector3D const& v)
{
// Another hidden friend.
ost << v.to_string();
return ost;
}
value_type& operator[](std::size_t const i) noexcept
{
// Trap subscripting errors.
return v.at(i);
}
value_type const& operator[](std::size_t const i) const noexcept
{
// Trap subscripting errors.
return v.at(i);
}
};
int main()
{
Vector3D a{ 1, 2, 3 };
a *= 2;
auto b = a * 3;
std::cout
<< "a : " << a << "\n"
<< "b : " << b << "\n"
<< "a + b : " << a + b << "\n\n";
return 0;
}
// end file: main.cpp
输出:
a : [2, 4, 6]
b : [6, 12, 18]
a + b : [8, 16, 24]