Вектора в пространстве

class Vector3d
{
    template <typename T> void mul ( const T & t, ... )
    {
        *this = t ( *this );
    }

    template <typename T> void mul ( const T & t, double d )
    {
        x *= d;
        y *= d;
        z *= d;
    }
public :
    double x, y, z;

    Vector3d () {}
    Vector3d ( double a, double b, double c ) : x ( a ), y ( b ), z ( c ) {}
 
    Vector3d operator - () const
    {
        return Vector3d ( - x, - y, - z );
    }
 
    Vector3d & operator += ( const Vector3d & v )
    {
        x += v.x;
        y += v.y;
        z += v.z;
        return * this;
    }

    Vector3d & operator -= ( const Vector3d & v )
    {
        x -= v.x;
        y -= v.y;
        z -= v.z;
        return * this;
    }

    Vector3d & operator *= ( const Vector3d & v )
    {
        x *= v.x;
        y *= v.y;
        z *= v.z;
        return * this;
    }

    Vector3d & operator /= ( const Vector3d & v )
    {
        x /= v.x;
        y /= v.y;
        z /= v.z;
        return * this;
    }

    template <typename T> Vector3d & operator *= ( const T & t )
    {
        mul ( t, t );
        return * this;
    }

    Vector3d & operator /= ( const double d )
    {
        x /= d;
        y /= d;
        z /= d;
        return * this;
    }

    Vector3d & fill ( double d = 0 )
    {
        x = y = z = d;
        return * this;
    }

    bool operator ! () const
    {
        return !x && !y && !z;
    }
// Получение перпендикуляра к данному вектору
    Vector3d perpendicular () const;

// Задание векторных норм
    Vector3d & setNorm1 ( double p = 1 ); // единичная норма
    Vector3d & setNorm2 ( double p = 1 ); // квадратичная норма
    Vector3d & setNormU ( double p = 1 ); // бесконечная норма
};

Конструктор без параметров не инициализирует данные. Я видел, что некоторые люди инициализируют данные нулями, но на мой взгляд это лишняя работа. Я понимаю, что современные процессоры очень быстрые, но если что-то можно не делать, то лучше не делать. Конструктор копии, также как и оператор присваивания, здесь будут лишними, так как компилятор по умолчанию сгенерирует то же самое.

Унарный минус, как и несколько последующих функций возвращает неконстантное значение, хотя я читал о других рекомендациях. А сделано это для того, чтобы к результату можно было применить функцию модифицирующую значение. Например, я могу написать следующее:

const Vector3d v3 = ( v1 - v2 ).getNorm2();

+=, -=. Здесь всё понятно.

Для *= есть функция с параметром типа Vector3d и шаблонная функция. Если параметр функции может быть преобразован к типу double, то тогда вектор умножается на это число. Иначе считается, что параметр - это функтор и вектор преобразуется им.
Для /= есть две функции - с параметром типа Vector3d и double.

Функция fill заполняет вектор заданным значением ( нулевым по умолчанию ).

Оператор ! для вектора здесь делает то же самое, что и для чисел, т.е. if ( ! v ) ... - выполняется для нулевого вектора, а if ( !! v ) ... - выполняется для ненулевого вектора.

Функция-член perpendicular возвращает вектор единичной длины перпендикулярный данному. На самом деле таких векторов бесконечно много. Здесь какой-то один из них.

Функции-члены setNorm... в случае, когда вектор ненулевой, делают соответсвующую ему векторную норму равной значению | p | ( по умолчанию 1 ). Если параметр p - отрицательный, то вектор меняет направление на противоположное. В случае, когда вектор нулевой - он остаётся нулевым.

const Vector3d null3d ( 0, 0, 0 );
Я давно хотел придумать более короткую запись, чем Vector3d ( 0, 0, 0 ). Наконец 15 апреля 2016 года появился null3d.
inline Vector3d operator + ( const Vector3d& a, const Vector3d& b )
{
    return Vector3d ( a.x + b.x, a.y + b.y, a.z + b.z );
}

inline Vector3d operator - ( const Vector3d& a, const Vector3d& b )
{
    return Vector3d ( a.x - b.x, a.y - b.y, a.z - b.z );
}
 
inline Vector3d operator * ( const Vector3d& a, double d )
{
    return Vector3d ( a.x * d, a.y * d, a.z * d );
}
 
inline Vector3d operator / ( const Vector3d& a, double d )
{
    return Vector3d ( a.x / d, a.y / d, a.z / d );
}

inline Vector3d operator * ( double d, const Vector3d& a )
{
    return Vector3d ( a.x * d, a.y * d, a.z * d );
}

inline double operator * ( const Vector3d& a, const Vector3d& b )
{
    return a.x * b.x + a.y * b.y + a.z * b.z;
}
}

inline Vector3d operator % ( const Vector3d& a, const Vector3d& b )
{
    return Vector3d ( a.y * b.z - a.z * b.y,
                      a.z * b.x - a.x * b.z,
                      a.x * b.y - a.y * b.x );
}

Сумма, разность двух векторов, умножение ( с двух сторон ) и деление ( с одной стороны ) на число - обычно эти реализации споров не вызывают. Другое дело - скалярное и векторное произведения. Тут большое количество вариантов. Я видел употребление знаков &, |, ^ и разные названия функций. Мой подход - это * для скалярного и % для векторного произведения. В математике ( а работа с векторами это прежде всего математика, а потом уже программирование ) скалярное произведение обозначается, как обычное умножение для чисел - точкой. Т.к. в С++ ( и других языках ) умножение обозначается *, отсюда и выбор для скалярного произведения. Векторное произведение в математике обозначается двуми перекрещенными палочками. Отсюда и выбор знака % для обозначения векторного произведения. Одна палочка в нём есть, а вторую символизируют два кружочка. В чём ещё преимущество этих знаков перед &, |, ^ так это приоритет выполнения. В выражениях типа v1*v2 + v3*v4 скобки не нужны, а если использовать знаки &, |, ^, то у них приоритет выполнения ниже, чем у знака + и придётся использовать скобки. Помимо неудобств это противоречит математической нотации.

inline double qmod ( const Vector3d& a )
{
    return a.x * a.x + a.y * a.y + a.z * a.z;

inline bool operator != ( const Vector3d & a, const Vector3d & b )
{
    return a.x != b.x || a.y != b.y || a.z != b.z;
}

inline bool operator == ( const Vector3d & a, const Vector3d & b )
{
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

inline void reper ( const Vector3d & x, Vector3d & y, Vector3d & z )
{
    y = x.perpendicular ();
    z = x % y;
}

double norm1 ( const Vector3d & v ); // единичная норма
double norm2 ( const Vector3d & v ); // квадратичная норма
double normU ( const Vector3d & v ); // бесконечная норма

Функция qmod возвращает квадрат модуля вектора или другими словами квадрат длины вектора. Эта функция в отличии от norm2 не использует квадратный корень (sqrt), поэтому когда надо сравнить длины двух векторов лучше использовать эту.

По поводу операторов сравнения ( ==, != ) можно сказать следующее. С ними надо обращаться разумно. Числа типа double могут незначительно отличаться по каким-то причинам и тогда сравнивать их таким образом не нужно. С другой стороны, если известно, что вектора имеют конкретные значения, то их можно сравнивать.

Функция reper делает из одного вектора ( первый параметр ) ещё два взаимно перпендикулярных, т.е. мы получаем правую прямоугольную тройку векторов. Если первый вектор был единичным, то и два других тоже будут единичными.

Функции norm1, norm2 и normU вычисляют соответствующие векторные нормы.

Примеры использования класса Vector3d можно посмотреть в приложении DEMO.

Исходники находятся в файлах vector3d.h, vector3d.cpp.

Наверх