Страницы

Поиск по вопросам

Показаны сообщения с ярлыком перегрузка-операторов. Показать все сообщения
Показаны сообщения с ярлыком перегрузка-операторов. Показать все сообщения

четверг, 9 апреля 2020 г.

Работа перегруженного оператора во время инициализации экземпляра класса [закрыт]

#cpp #классы #указатели #перегрузка_операторов #операторы

                            
             
                
                    
                        
                            Закрыт. Этот вопрос не по теме. Ответы на него в данный
момент не принимаются.
                            
                        
                    
                
            
                    
                
                        
                            
                        
                    
                        
                            Хотите улучшить этот вопрос? Переформулируйте вопрос,
чтобы он соответствовал тематике «Stack Overflow на русском».
                        
                        Закрыт 4 года назад.
                    
                
        

Как перегрузить, например, оператор присваивания таким образом, чтобы он работал
во время инициализации экземпляра класса? 

#include `
using namespace std;`

class Matrix
{
private:
    int **Arr;
    int Size;
public:
    Matrix(int SizeOfMatrix);
Matrix operator = (Matrix &);
};

int main()
{
    Matrix A(4);
    Matrix B(4) = A;
    system("pause");
    return 0;
}
Matrix::Matrix(int SizeOfMatrix)
{
    Size = SizeOfMatrix;
    Arr = new int *[Size];
    for (int i = 0; i < Size; i++)
    {
        Arr[i] = new int[Size];
        for (int j = 0; j < Size; j++)
            Arr[i][j] = rand() % 50;
    }
}
Matrix Matrix :: operator =(Matrix &A)
{
    for (int i = 0; i < Size; i++)
    {
        for (int j = 0; j < Size; j++)
            Arr[i][j] = A.Arr[i][j];
    }
    return *this;
}

    


Ответы

Ответ 1



Прежде всего хотел бы отметить, что данное предложение Matrix B(4) = A; некорректное и не должно компилироваться. Фактически в данном предложении имеется два инициализатора: 4 и A. Но даже если вы правильно напишите Matrix A(4); Matrix B = A; то здесь вызывается не оператор присваивания, как вы думаете, а конструктор копирования, который определен компилятором неявно, и который просто почленно копирует члены данных объектов. Когда вы динамически в классе распределяете память, то есть используете указатели, то вы должны определить явно по крайней мере конструктор копирования, копирующий оператор присваивания и деструктор. В противном случае поведение вашей программы может оказаться неопределенным. Например, в результате использования конструктора копирования, созданного компилятором неявно, может оказаться, что два объекта имеют указатели на одну и ту же область памяти, а потому при удалении этих объектов будет осуществлена попытка удалить одну и ту же память дважды. Ваш класс может выглядеть следующим образом class Matrix { private: int **Arr; size_t Size; public: explicit Matrix( size_t ); Matrix( const Matrix & ); ~Matrix(); Matrix & operator = ( const Matrix & ); }; Matrix::Matrix( size_t SizeOfMatrix ) : Arr( nullptr ), Size ( SizeOfMatrix ) { if ( Size ) { Arr = new int *[Size]; for ( size_t i = 0; i < Size; i++ ) { Arr[i] = new int[Size]; for ( size_t j = 0; j < Size; j++ ) Arr[i][j] = rand() % 50; } } } Matrix::Matrix( const Matrix &rhs ) : Arr( nullptr ), Size ( rhs.Size ) { if ( Size ) { Arr = new int *[Size]; for ( size_t i = 0; i < Size; i++ ) { Arr[i] = new int[Size]; for ( size_t j = 0; j < Size; j++ ) Arr[i][j] = rhs.Arr[i][j]; } } } Matrix::~Matrix() { for ( size_t i = 0; i < Size; i++ ) delete [] Arr[i]; delete [] Arr; } Matrix & Matrix::operator =( const Matrix &rhs ) { if ( this != &rhs ) { int **tmp = nullptr; if ( rhs.Size != 0 ) { if ( Size != rhs.Size ) { tmp = new int *[rhs.Size]; for ( size_t i = 0; i < rhs.Size; i++ ) { tmp[i] = new int[rhs.Size]; } } else { tmp = Arr; } for ( size_t i = 0; i < rhs.Size; i++ ) { for ( size_t j = 0; j < rhs.Size; j++ ) tmp[i][j] = rhs.Arr[i][j]; } } if ( Size != rhs.Size ) { for ( size_t i = 0; i < Size; i++ ) delete [] Arr[i]; delete [] Arr; } Arr = tmp; Size = rhs.Size; } return *this; }

суббота, 21 марта 2020 г.

Перегрузка оператора @ в Python

#python #python_3x #python_35 #перегрузка_операторов


Столкнулся с необходимостью перегрузки оператора @ в Python. Требуется чтобы объекты
класса M могли взаимодействовать друг с другом через оператор @.
Например: 

a = M()
b = M()
a @ b



требуемая сигнатура поведения.
Подскажите, какой магический метод отвечает за этот оператор. Заранее спасибо.

    


Ответы

Ответ 1



Для поддержки оператора @ нужно переопределить метод __matmul__ (Или __rmatmul__, если нужно реализовать ситуацию, когда слева от @ объект не поддерживающий оператор @, а справа объект нашего класса), для оператора @= - метод __imatmul__. Пример: class A: def __init__(self, x): self.x = x def __repr__(self): return 'A({})'.format(self.x) def __matmul__(self, other): print("Вызван метод __matmul__") if(isinstance(other, A)): return A(self.x * other.x) else: return A(self.x * other) def __rmatmul__(self, other): print("Вызван метод __rmatmul__") if(isinstance(other, A)): return A(self.x * other.x) else: return A(self.x * other) def __imatmul__(self, other): print("Вызван метод __imatmul__") return self @ other a2 = A(2) print(a2 @ A(3)) # Вывод: # Вызван метод __matmul__ # A(6) print(a2 @ 4) # Вывод: # Вызван метод __matmul__ # A(8) print(5 @ a2) # Вывод: # Вызван метод __rmatmul__ # A(10) a2 @= A(6) print(a2) # A(12) # Вывод: # Вызван метод __imatmul__ # Вызван метод __matmul__ # A(12) Документация: Emulating numeric types

Ответ 2



Он называется matmul: operator.matmul(a, b) operator.__matmul__(a, b) Return a @ b. Пример перекрытия: class M: def __matmul__(self, other): print(self, other) a = M() b = M() a @ b # <__main__.M object at 0x00000281C3A07860> <__main__.M object at 0x00000281C5935E10>

воскресенье, 15 марта 2020 г.

Перегрузка оператора вывода <<

#cpp #перегрузка_операторов


Для опытов со структурами данных написал такую структуру:

typedef struct complex{
    double real;
    double imaginary;
public:
    std::ostream& operator <<(std::ostream &os, complex &c){
        os << "(" << c.real << ", " << c.imaginary << ")";
        return os;
    }
}complex;


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


Ответы

Ответ 1



Определенный вами оператор как функция член класса имеет неявный параметр - указатель на экземпляр класса, то есть this. В результате он имеет, фактически три параметра вместо двух, как полагается для этого бинарного оператора. Либо объявите функцию со спецификатором friend. Либо вынесите определение (и, соответственно, объявление) функции за пределы класса. То есть сделайте ее не функцией - членом класса. Имейте в виду, что так как вы используете ключевое слово struct в объявлении класса, то его члены real и imaginary имеют по умолчанию класс доступа public Итак, либо напишите struct complex{ //private: double real; double imaginary; friend std::ostream& operator <<(std::ostream &os, const complex &c){ os << "(" << c.real << ", " << c.imaginary << ")"; return os; } }; Либо typedef struct complex{ double real; double imaginary; }complex; std::ostream& operator <<(std::ostream &os, const complex &c){ os << "(" << c.real << ", " << c.imaginary << ")"; return os; }

суббота, 7 марта 2020 г.

Операторы перегрузки

#cpp #перегрузка_операторов


Доброго времени суток! Можете понятно объяснить про перегрузку операторов?
Нужно создать класс для представления простой дроби, с чем я справилась, но никак
не могу понять, как перегрузить операции +, - и так далее.

class SimpleFraction
{
private:
int a,
    b;
public:
SimpleFraction(int a1, int a2)
{
    setSimpleFraction(a1, a2 );
    if (a2 == 0)
        throw std::runtime_error("zero division error");
}

void setSimpleFraction( int a1, int a2)
{
    a = a1;
    b = a2;
}

void getSimpleFraction()
{
    cout << a << "/"<< b ;
}
 };


Классы только начала изучать, так что могут быть ошибки
    


Ответы

Ответ 1



У вас тут, как говорится, корова не валялась :) Вы должны, вообще-то, после каждого действия дробь упрощать - искать НОД числителя и знаменателя и сокращать... Далее - зачем вам setSimpleFraction? Это можно сделать просто в конструкторе... Словом, должно быть примерно так: class SimpleFraction { static int gcd(int m, int n) // Вычисление НОД { while(m && n) if (m < n) n %= m; else m %= n; return (m == 0) ? n : m; } private: int a, b; public: SimpleFraction(int a1 = 0, int a2 = 1):a(a1),b(a2) { if (a2 == 0) throw std::runtime_error("zero division error"); } // Нас устраивают копирование и присваивание по умолчанию SimpleFraction(const SimpleFraction&) = default; SimpleFraction& operator=(const SimpleFraction&) = default; // Перегрузка оператора вывода в поток friend ostream& operator<<(ostream&out,const SimpleFraction& f) { return out << f.a << "/" << f.b; } friend SimpleFraction operator +(const SimpleFraction&x, const SimpleFraction&y) { int a = x.a*y.b + x.b*y.a; // Числитель int b = x.b*y.b; // Знаменатель int n = gcd(a,b); // НОД return SimpleFraction(a/n,b/n); } SimpleFraction operator -(const SimpleFraction&y) const { int aa = a*y.b - b*y.a; // Числитель int bb = b*y.b; // Знаменатель int n = gcd(aa,bb); // НОД return SimpleFraction(aa/n,bb/n); } friend SimpleFraction operator *(const SimpleFraction&x, const SimpleFraction&y) { int a = x.a*y.a; // Числитель int b = x.b*y.b; // Знаменатель int n = gcd(a,b); // НОД return SimpleFraction(a/n,b/n); } }; int main() { SimpleFraction a(2,3), b(3,5), c(4,9); SimpleFraction x = a+b; cout << x << endl; x = c*b; cout << x << endl; x = a-c; cout << x << endl; } перегрузка операторов - это просто функция особого вида, с именем operator @, где @ - тот самый оператор. Может быть членом класса (тогда первый ее неявный параметр - *this, может быть свободной - как в приведенном выше коде. В вашем случае лучше свободная, и возвращающая новое значение типа SimpleFraction - как я сделал с операторами + и *, но можно и член класса - см. оператор -. Они просто вычисляют новую дробь, выполняют ее сокращение, и возвращают новое значение. Так достаточно или что-то надо разъяснить подробнее?

среда, 4 марта 2020 г.

Возможно ли реализовать перегрузку операции () в Java?

#java #перегрузка_операторов #операторы


Создать несколько объектов  (например, a и b) разработанного класса.
Класс – вектор (одномерный массив). Реализовать для объектов данного
класса перегрузку операции  ():   (a(i)=b(j)).
    


Ответы

Ответ 1



В vanilla/plain Java нет перегрузки операторов. Да, но если очень сильно постараться, то можно изобразить - через плагины к Java компилятору. Есть например решение по перегрузке арифметических операторов - со скобками конечно будет сложнее. Но если хорошо подумать, наверное можно выкрутиться.

Ответ 2



Нет, переопределить поведение операторов в Java нельзя. В таких случаях обычно берут пример с List и создают методы get и set: a.set(i, b.get(j)); В принципе для данного класса будет логично реализовать интерфейс List или воспользоваться одной из готовых реализаций (если задание допускает использование стандартных классов). Также можете посмотреть в сторону языка Kotlin, он работает под JVM и предоставляет много синтаксического сахара, в том числе переопределение оператора ().

PIMPL на unique или shared указателях?

#cpp #перегрузка_операторов #smart_pointer


Привожу код заготовки реализации идиомы pImpl с этого сайта.

// in header file
class widget 
{
public:
    widget();
    ~widget();
private:
    class impl;
    unique_ptr pimpl;
};

// in implementation file
class widget::impl 
{
    // :::
};

widget::widget() : pimpl{ new impl{ /*...*/ } } { }
widget::~widget() { }


Далее там написано следующее:


  Prefer to hold the Pimpl using a unique_ptr. It’s more efficient than
  using a shared_ptr, and correctly expresses the intent that the Pimpl
  object should not be shared.


Вопрос: в чем конкретно эффективность (предпочтительное более быстрое перемещение?)
использования std::unique_ptr и почему предлагается не разделять реализацию между несколькими
экземплярами? Я ранее задавал вопрос относительно идиомы copy-on-write, почему бы не
использовать реализацию идиомы pImpl на указателе типа shared_ptr и дополнить ее вот
такими реализациями операторов, характерными для COW идиомы:

    // Non-const * and -> , copying
    T& operator*()
    {
        copy();
        return *m_sp;
    }

    T* operator->()
    {
        copy();
        return m_sp.operator->();
    }

    // Const * and -> methods no need to copy
    const T& operator*() const
    {
        return *m_sp;
    }

    const T* operator->() const
    {
        return m_sp.operator->();
    }

    


Ответы

Ответ 1



Для начала нужно понимать разницу в идеологии между shared_ptr и unique_ptr. Первый подразумевает, что объект будет в общем владении, а unique_ptr только единоличное владение. Так как pimpl подразумевает единоличное владение (не, можно конечно сделать и расшаренное, но это как то не нормально), то unique_ptr в самый раз. Почему же оно быстрее? все очень просто. shared_ptr внутри себя содержит как минимум счетчик ссылок либо на атомиках, либо с мютексами. В любом случае это не быстро. unique_ptr это все не нужно. В многих случаях компилятор может оптимизировать до такой степени, что будет не хуже голого указателя. почему предлагается не разделять реализацию между несколькими экземплярами потому что это будет уже не pimpl. дополнить ее вот такими реализациями операторов, характерными для COW идиомы: Ваша реализация требует наличие некой функции copy. И второе - она не очень thread-safe. Почему такое не сделали в shared_ptr? Потому что это был бы cow_ptr. Это лишняя функциональность, которая обычно не нужна (если бы она была нужна, то наверно она появилась бы и в бусте, откуда пришел этот умный указатель), а в с++ не принято "платить за то, что не используется".

Ответ 2



Если вы дополните умный указатель этими операторами - это будет уже не shared_ptr, а уже какой-нибудь cow_ptr. И да, так тоже будет работать. Тут главное - найти способ вынести copy() в модуль реализации, потому что нельзя скопировать необъявленный тип данных. Как-то так в итоге должно получиться: class widget { public: widget(); ~widget(); private: class impl; impl* copy(pimpl* impl); cow_ptr pimpl; }; Однако, на самом деле операция копирования объекта - очень странная по смыслу. И в большинстве прикладных случаев ее явно запрещают. А если копирование запрещено архитектурно, то зачем все эти сложности с реализацией?

вторник, 25 февраля 2020 г.

Перегрузка двойной индексации C++

#перегрузка_операторов #cpp


Пусть имеется класс Matrix:
    class matrix
    { 
       public:

          int row;
          int col;
          double **M;

          matrix (int i, int j) {
              row = i;  
              col = j;
              M = new double*[row];

              for (int i = 0; i < row; i++) {
                  M[i] = new double[col];
                  for (int j = 0; j < col; j++) {
                      M[i][j] = 0;
                  }
              }
         };
     };

Нужно реализовать перегрузку двойной индексации на чтение и на запись элемента матрицы.
Ход моих мыслей:


Сделаю сначала на чтение, это легко =>
double* operator [] (int i, int j){ return M[i][j]; }



Угу... много параметров... =>
friend double* operator [] (int i, int j){ return M[i][j]; }



operator[] must be a member function ...хм...блин...


Вопрос: В чем проблема? Как исправить? =)    


Ответы

Ответ 1



Так не получится. Индексация в С++ всегда одинарная. Запись M[x][y] означает (M[x])[y] Просто M[x] должен возвращать строку, а далее к строке применяется её индексация. double* operator [] (int i){return M[i];}

Ответ 2



#include "stdafx.h" #include using namespace std; class MMassiveInt { private: class submasint;// подкласс строк матрицы (объявление) int row, col; // количества строк и колонок матрицы submasint *arrint; // указатель на массив подкласса строк матрицы class submasint// подкласс строк матрицы (реализация) { public: int *sarrint; // конструктор по умолчанию необходим для создания массива указателей на класс подстрок матрицы submasint() {} submasint(int size) { sarrint = new int[size]; // создание массива целочисленных данных for (int i = 0; i < size; i++)//инициализация полей нулями { sarrint[i] = 0; } } int & operator [] (int i) // оператор [] возвращает ссылку на int { return sarrint[i]; } // деструктор не нужен, если если его поставить, то строки матрицы будут // удаляться до того, как заполнится вся матрица // ~submasint() //{ // if (sarrint != nullptr) delete[] sarrint; //} }; public: MMassiveInt(const int rows, const int cols) //класс матрица целых чисел { rows < 1 ? row = 1: row = rows; // защита от ввода количества строк или столбцов менее 1 cols < 1 ? col = 1: col = cols; // создание массива указателей с использованием конструктора по умолчанию (без параметров) arrint = new submasint [row]; for (int i = 0; i < row; i++) { // создания строк матрицы. если у подкласса строк матрицы будет деструктор, // то строки будут удаляться в каждей итерации цикла arrint[i] = submasint(col); } } submasint & operator [] (int i)// оператор [] возвращает ссылку на подкласс строк матрицы { return arrint[i]; } int GetCol() // метод получения количества колонок матрицы { return col; } int GetRow()// метод получения количества строк матрицы { return row; } ~MMassiveInt() { for (int i = 0; i < row; i++) {// удаление строк матрицы if (arrint[i].sarrint!=nullptr) delete[] (arrint[i].sarrint); } if (arrint!=nullptr) delete[] arrint;// удаление массива указателей на строки матрицы } }; int main() { int mrow = 2, mcol = 3; // инициализация значения количество строк и столбцов матрицы MMassiveInt mas(mrow, mcol); // создание двумерного массива, и инициализация его полей нулями // вывода матрицы в консоль for (int i = 0; i < mas.GetRow(); i++) { for (int k = 0; k < mas.GetCol(); k++) { cout << mas[i][k] << "\t"; } cout << endl; } system("Pause"); return 0; } Аналогично делается и для Double.

понедельник, 24 февраля 2020 г.

invalid initialization of non-const

#cpp #классы #ссылки #перегрузка_операторов


#include 
#include 

using namespace std;

class complex {
    double re, im;
public:
    complex(double = 0, double = 0);
    ~complex();

    complex operator+(complex&);
    complex operator-(complex&);
    complex operator*(complex&);
    complex operator/(complex&);
    complex operator^(unsigned);
    friend istream& operator >> (istream &, complex&);
    friend ostream& operator << (ostream &, complex &);
};

complex::complex(double r, double i)
{
    re = r;
    im = i;
}

complex::~complex() { }

complex complex::operator+(complex&y)
{
    return complex(re + y.re, im + y.im);
}

complex complex::operator-(complex&y)
{
    return complex(re - y.re, im - y.im);
}

complex complex::operator*(complex&y)
{
    return complex(re*y.re - im*y.im, re*y.im + im*y.re);
}

complex complex::operator/(complex&y)
{
    double r1 = re;
    double i1 = im;
    double r2 = y.re;
    double i2 = y.im;
    return complex((r1*r2 - i1*i1) / (r2*r2 + i2*i2), (-r1*i2 + i1*r2) / (r2*r2 +
i2*i2));
}

complex complex::operator^(unsigned n)
{
    complex y(1, 0);
        for (int i = 1; i <= n; i++)
            y = y*(*this);
    return y;
}

istream& operator >> (istream& is, complex& x)
{
    char c;
    cin >> c;
    assert(c == '(');
    cin >> x.re;
    cin >> c;
    assert(c == ',');
    cin >> x.im;
    cin >> c;
    assert(c == ')');
    return is;

}

ostream& operator<<(ostream&os, complex&x)
{
    os << '(' << x.re << ',' << x.im << ')' << endl;
    return os;
}

int main(){

    complex a(1, 1);
    complex b(1, 1);
    complex c(1, 1);
    complex x;

    cout << "Введите комплексное число в формате: (re,im) ->";
    cin >> x;
    cout << "Результат =" << a*(x ^ 2)) + b*x + c << endl;
    return 0;
}


На линуксе выводит ошибку:


  invalid initialization of non-const


для строки:

cout << "Результат =" << a*(x ^ 2) + b*x + c << endl;


Саму программу скопировал с Windows, ошибки исправил, только эту выдает. В чем здесь
проблема? И как можно ее решить? На Visual Studio работала нормально.
    


Ответы

Ответ 1



Объявите данные операторы следующим образом complex operator+( const complex&) const; complex operator-(const complex&) const; complex operator*(const complex&) const; complex operator/( const complex&) const; complex operator ^( unsigned int ) const; friend ostream& operator << (ostream &, const complex &); Нельзя связывать временные объекты с неконстантной lvalue ссылкой. Обратите внимание , что конструктор класса является преобразующим конструктором. Поэтому у вас будет работать выражение complex + double, как, например, complex a( 10, 10 ); complex b = a + 10; Но не будет работать выражение double + complex complex a( 10, 10 ); complex b = 10 + a; // ошибка компиляции так как ваши операторы являются членами класса. Вы могли бы их объявить как отдельные функции, не являющиеся членами класса, которые принимают два аргумента. Например, friend complex operator +( const complex &lhs, const complex &rhs ) { return complex( lhs.re + rhs.re, lhs.im + rhs.im ); } В этом случае второе выражение было бы также верным. Эта дружественная функция может быть определена как в самом классе, и тогда при использовании оператора будет применен зависящий от аргумента поиск (ADL), либо вне класса. Что касается Visual Studio, то его компилятор имеет собственные расширения языка (которые активизированы по умолчанию; вы можете отключить эту опцию в настройках компилятора, и тогда будет выдано сообщение об ошибке), которые порой противоречат стандарту C++. Не стоит полагаться на эти расширения языка компилятора C++ Visual Studio, так как код будет не переносимым. EDIT: Так как у вас возникают трудности правильно набрать код, то приведу для вас рабочий вариант программы. Я не проверял, определены ли правильно операторы с точки зрения математики. #include #include class complex { private: double re, im; public: complex( double = 0, double = 0 ); ~complex(); complex operator +( const complex & ) const; complex operator -( const complex & ) const; complex operator *( const complex & ) const; complex operator /( const complex & ) const; complex operator ^( unsigned int ) const; friend std::istream & operator >>( std::istream &, complex & ); friend std::ostream & operator <<( std::ostream &, const complex & ); }; complex::complex( double r, double i ) : re( r ), im( i ) { } complex::~complex() { } complex complex::operator +( const complex &c ) const { return complex( re + c.re, im + c.im ); } complex complex::operator -( const complex &c ) const { return complex( re - c.re, im - c.im ); } complex complex::operator *( const complex &c ) const { return complex( re * c.re - im * c.im, re * c.im + im * c.re ); } complex complex::operator /( const complex & c ) const { double r1 = re; double i1 = im; double r2 = c.re; double i2 = c.im; return complex( ( r1 * r2 - i1 * i1 ) / ( r2 * r2 + i2 * i2 ), (-r1 * i2 + i1 * r2 ) / ( r2 * r2 + i2 * i2 ) ); } complex complex::operator ^( unsigned int n ) const { complex c( 1, 0 ); for ( unsigned int i = 1; i <= n; i++ ) c = c * *this; return c; } std::istream & operator >>( std::istream &is, complex &c ) { char ch; is >> ch; assert( ch == '(' ); is >> c.re; is >> ch; assert( ch == ',' ); is >> c.im; is >> ch; assert( ch == ')' ); return is; } std::ostream & operator <<( std::ostream &os, const complex &c ) { return os << '(' << c.re << ',' << c.im << ')'; } int main() { complex a(1, 1); complex b(1, 1); complex c(1, 1); complex x; std::cout << "Введите комплексное число в формате: (re,im) ->"; std::cin >> x; std::cout << "x = " << x << std::endl; std::cout << "x ^ 2 = " << ( x ^ 2 ) << std::endl; std::cout << "a * ( x ^ 2 ) = " << a * ( x ^ 2 ) << std::endl; std::cout << "b * x = " << b * x << std::endl; std::cout << "a * ( x ^ 2 ) + b * x = " << a * ( x ^ 2 ) + b * x << std::endl; std::cout << "Результат =" << a * ( x ^ 2 ) + b * x + c << std::endl; return 0; } Если ввести значение (10,10), то вывод будет выглядеть следующим образом: Введите комплексное число в формате: (re,im) ->(10,10) x = (10,10) x ^ 2 = (0,200) a * ( x ^ 2 ) = (-200,200) b * x = (0,20) a * ( x ^ 2 ) + b * x = (-200,220) Результат =(-199,221)

Ответ 2



Для таких простых классов с семантикой значения и малым размером лучше делать операторы свободными функциями (в силу семантики), и даже, возможно, с передачей по значению (в силу малости - 2 double скопировать против разыменований адресов (ссылка работает через адрес) - неизвестно, что выиграет... Как минимум, стоит выполнить несколько хронометражей): friend complex operator+(complex a, complex b) { return complex(a.re+b.re,a.im+b.im); } Update Провел хронометраж. VC++ 2015. На 32 разрядах константная ссылка быстрее примерно в 1.5 раза. На 64 разрядах одинаково. В принципе, более-менее понятно, почему. Ну, значит, нужно friend complex operator+(const complex& a, const complex& b) { return complex(a.re+b.re,a.im+b.im); }

четверг, 13 февраля 2020 г.

C++, глобальные перегруженные операторы и операторы-члены

#cpp #перегрузка_операторов #операторы


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

Буду очень благодарен, если кто-нибудь поможет мне разобраться в этом вопросе.
    


Ответы

Ответ 1



Во-первых, есть несколько групп ситуаций, когда у вас просто нет возможности выбирать между этими двумя вариантами: Некоторые операторы разрешается перегружать только методами класса: =, (), [], ->. Ситуации, когда первый (или единственный) операнд оператора не является class-типом. Тогда перегрузка возможна только самостоятельной функцией. Например, оператор сложения int + myclass или инкремента myenum++. Ситуации, когда изменять определение класса первого операнда у вас нет возможности. Тогда перегрузка возможна только самостоятельной функцией. Всем нам знакомый пример: операторы ввода-вывода << и >> для стандартных потоков. Во-вторых, для остальных ситуаций жестких правил нет. Но можно порекомендовать использовать самостоятельные функции для таких семантически-симметричных бинарных операторов как арифметические операторы, логические операторы, побитовые операторы, операторы относительного сравнения и операторы сравнения на равенство. Для унарных операторов зачастую уместнее реализация методом класса. Для операторов составного присваивания (+= и т.п.) тоже интуитивно уместнее реализация методом класса (они сродни копирующему оператору присваивания =). Операторы сдвига << и >> - в зависимости от степени симметричности той семантики, которой вы их наделяете. Обратите также внимание, что в стандартной библиотеке низкоуровневые фундаментальные операторы форматированного ввода-вывода реализованы методами классов, в то время как более высокоуровневые "составные" операторы - самостоятельными функциями. То есть можно предложить и такой вариант разделения подходов к перегрузке операторов.

Ответ 2



Все очень просто. Если оператор модифицирует обьект, то его стоит делать членом класса, а когда оператор выполняет действие с другим обьектом и возвращает другой обьект(или ничего не возвращает), то лучше написать его как независимая функция, дабы избежать разбухания кода. Например: class Some_class { //... public: Some_class& operator+=(const Some_class&); Some_class& operator/=(const Some_class&); //... }; inline Some_class operator +(const Some_class&, const Some_class&); inline Some_class operator *(const Some_class&, const Some_class&); //... Одним словом вы сами создаете абстракцию. Вы можете написать так, как представляете(одни смотрят на собак как на друга, который умеет то то, то то, доугие могут смотреть на них как на зверей, которых нужно размножать для корма зверей в зоопарке). Если абстракция выражает только то, что умеет делать обьект или что можно выражать этим классом, то это хороший код(стиль), понятный читательям и пользовательям. Не нужно туда пихать все то, что могут делать пользователи с экземпляром, не то можно туда пихнуть сотни и тысяча функции и сущности....

Перегрузка математических операторов

#операторы #перегрузка_операторов #cpp


Пишу класс для работы с матричной алгеброй. Матрица может быть произвольного размера,
поэтому память выделяю в конструкторе (а в деструкторе уничтожаю).
Переопределил оператор +. Возникает проблема при возврате объекта из функции-оператора:
деструктор срабатывает раньше, чем вызывающая программа успеет прочитать данные из
объекта. Если деструктор убрать - все работает, но память уплывает.
Что я сделал не так? Спасибо.
Вот код:
class Matrice{

float ** items;

float & v(uint8_t i, uint8_t j){
    return *(float *)(items + i * w + j);
}

public:

int h, w;

Matrice(uint8_t height, uint8_t width){
    h = width;
    w = width;
    items = (float **)malloc(h * w * sizeof(float));
}

~Matrice(){
    free((void *)items);
}

float * operator[](uint8_t i){
    return (float *)(items + i * w);
}

const Matrice operator +(Matrice M){

    Matrice A(M.h, M.w);

    for (int i = 0; i < h; i++){
        for (int j = 0; j < w; j++){
            A[i][j] = v(i, j) + M[i][j];
        }
    }

    return A;

}

};    


Ответы

Ответ 1



Вам нужен правильный конструктор копирования. Без него работает автоматически сгенерированный конструктор, который просто копирует указатель на элементы (items) из одной матрицы в другую. В результате, когда исходная матрица уничтожается, данные в скопированной матрице становятся недоступны. Да, и если уж пишете на C++, переходите с malloc/free на new[]/delete[].

понедельник, 10 февраля 2020 г.

В чем ошибка при перегрузке оператора+?

#cpp #классы #перегрузка_операторов


Перегружаю бинарный + в своем классе:

MyClass operator+(const MyClass &a, const MyClass &b) {
    ...
    return a.value + b.value;
}


Получаю ошибку бинарный оператор + имеет слишком много параметров. В чем проблема?
Везде указывается именно такая сигнатура перегрузки этого оператора.
    


Ответы

Ответ 1



Каждая не статическая функция-член класса имеет неявный параметр, который получает значение this, то есть указатель на сам объект класса. Вам надо либо этот оператор определить в классе как дружественную функцию (если требуется обращение к закрытым или защищенным членам класса), например friend MyClass operator+(const MyClass &a, const MyClass &b) { ... return a.value + b.value; } Или объявить ее как обычную функцию вне класса, если не требуется обращение к закрытым или защищенным членам класса. Либо сделать оператор функцией-членом класса, но с одним явным параметром MyClass operator+( const MyClass &b) const { ... return this->value + b.value; }

Ответ 2



В классе он должен перегружаться с одним аргументом. Второй аргумент - объект самого класса. Если вы создаете его как свободную функцию (вне класса) - все верно, именно такая сигнатура и должна быть.

воскресенье, 9 февраля 2020 г.

Перегрузка операторов перечисляемого типа в Delphi

#delphi #перегрузка_операторов #delphi_xe


Можно ли перегрузить оператор для перечисляемого типа?
т.е. имеем например:

Type
  TAngle = (a0, a90, a180, a270);

Var
  AngleA, AngleB, AngleC: TAngle;

AngleA := a90;
AngleB := a180;
AngleC := AngleA + AngleB; //a270
AngleC := a270 + AngleB;   //a90
AngleC := AngleB + a180;   //a0


По идее перегрузить бы оператор сложения, но это возможно только для записей, как
я понимаю? Может можно запись через Implicit неявно приводить к этому перечисляемому
типу? Но тогда это всё равно будет уже новый тип.

Вообще логика такая получается, пока сделал через функцию:

Result := TAngle((Ord(AngleA) + Ord(AngleB)) Mod 4)

    


Ответы

Ответ 1



Как уже сказано в ответе @zed перегрузка доступна только для записей. Поэтому, если хочется, вы можете добавить соответствующую структуру, и работать с ней. TAngleValue = (av0, av90, av180, av270); TAngle = record FValue : TAngleValue; function getAngle():integer; public class operator Add(a, b: TAngle ):TAngle; class operator Implicit(angle: TAngleValue):TAngle; property Angle: integer read getAngle; property Value: TAngleValue read FValue; end; перегрузив сложение и, для начала, неявное приведение class operator TAngle.Add(a, b: TAngle): TAngle; begin result.FValue := TAngleValue( (byte(a.FValue) + byte(b.FValue)) mod 4); end; class operator TAngle.Implicit(angle: TAngleValue): TAngle; begin result.FValue := angle; end; function TAngle.getAngle(): integer; begin result := byte(FValue)*90; end; в итоге само манипулироввание значениями получится таким, как вы хотели var a, b, c : TAngle; begin a := av90; b := av180; c := a + b + av90; writeln( a.Angle, ' ', b.Angle, ' ', c.Angle); readln; end.

Ответ 2



Нельзя такого сделать. В десктопных версиях Delphi перегрузка операторов возможна только у записей: Operator Overloading (Delphi). Для мобильных версий (с ARC) перегрузка доступна так же и для классов: Class Operators in Delphi

суббота, 8 февраля 2020 г.

Конструктор <vector>

#cpp #классы #перегрузка_операторов #конструктор #вектор


Хочу написать конструктор класса, который в качестве параметра принимает статический
массив (указатель на 1 элемент) и инициализирует вектор внутри класса этим массивом:

class A
{ 
  public:
    explicit A(int x[]):v(x,x+sizeof(x)/sizeof(*x)) {} // или (int* x)
  private:
    vector v;
};


Не работает... В чём проблема?
    


Ответы

Ответ 1



Параметр вашего конструктора имеет тип int *. Поэтому выражение sizeof(x)/sizeof(*x) эквивалентно выражению sizeof( int * )/sizeof( int ) И может равняться либо 2, если размер указателя равен, например, 8 байтам, а размер объекта типа int равен 4 байтам, либо 1, если данные размеры совпадают. Лучше объявить два перегруженных конструктора. Первый конструктор может быть определен как A( const int *first, const int *last ) : v( first, last ) {} а второй конструктор может быть определен либо как A( const int *first, size_t n ) : v( first, first + n ) {} либо использовать делегирующий конструктор A( const int *first, size_t n ) : A( first, first + n ) {} Также вы можете объявить шаблонный конструктор, как, например, template A( const int ( &a )[N] ) : v( a, a + N ) {} Вот демонстрационная программа, в которой показаны все три способа вызова конструкторов класса #include #include struct A { A( const int *first, const int *last ) : v( first, last ) {} A( const int *first, size_t n ) : v( first, first + n ) {} template explicit A( const int ( &a )[N] ) : v( a, a + N ) {} std::vector v; }; int main() { int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; A a1( a, a + sizeof( a ) / sizeof( *a ) ); for ( int x : a1.v ) std::cout << x << ' '; std::cout << std::endl; A a2( a, sizeof( a ) / sizeof( *a ) ); for ( int x : a1.v ) std::cout << x << ' '; std::cout << std::endl; A a3( a ); for ( int x : a1.v ) std::cout << x << ' '; std::cout << std::endl; } Ее вывод на консоль 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

Ответ 2



В параметрах функции, в отличие от объявлений массива, int x[] вырождается в int* x, т.е. ничем не отличается от обычного указателя. (Параметры int x[5] или int x[6] также вырождаются int* x). Поэтому надо использовать шаблон и ссылку на массив. class A { public: template explicit A(int (&x)[N]) : v(x, x + N) {} private: vector v; }; Ссылка int (&x)[N] не теряет количество элементов в массиве.

среда, 5 февраля 2020 г.

Перегрузка оператора +=

#cpp #строки #классы #перегрузка_операторов #конструктор


// Реализовать класс String  для работы со строками. Перегрузить операторы = + +=
- > < [] (подстрока) ++ --
#pragma once
#include 
using namespace std;

class String
{


    char* s;
    int length;

public:


    String(const char* str="")
    {
        this->length = strlen(str);
        this->s = new char[this->length+1];
        for (int i = 0; i < length; i++)
        this->s[i] = str[i];
    }


    String(const String& s1)
    {
        this->length = s1.length;
        this->s = new char[this->length];
        for (int i = 0; i < this->length; i++)
            this->s[i] = s1.s[i];
    }


    ~String()
    {
         delete[] s;
    }



String& String::operator=(String& right);
    String String::operator+(String right);
    //String& String::operator+=(const String& right);
    friend String& operator+=(String& left, const String& right);
    void String::show();

};


А это цпп файл: 

#include "String.h"

void String::show()
{
    for (int i = 0; i < this->length; i++)
        cout << this->s[i];
}


String& String::operator=(String& right)
{
    delete[] this->s;
    this->length = right.length;
    this->s = new char[this->length];
    for (int i = 0; i < this->length; i++)
        this->s[i] = right.s[i];
    return *this;
}

String String::operator+(String right)
{
    String new_string;
    new_string.length = this->length + right.length;
    new_string.s =new char[this->length + right.length+1];
    for (int i = 0; i < this->length; i++)
        new_string.s[i] = this->s[i];
    for (int i = 0; i < right.length; i++)
        new_string.s[this->length + i] = right.s[i];
    return new_string;
}

String& operator+=(String& left, const String& right)
{
    return left+right;
}


Почему у меня s3 всё равно при выводе пустая? Помогите начинающему!

#include "String.h"

void main()
{
    String s2("Hello");
    String s3;
    s3 += s2;
    s3.show();
    system("pause");
}

    


Ответы

Ответ 1



Мы, начинающие программисты, должны помогать друг другу.:) Начнем с того, что вам следует включить заголовок #include так как вы используете функции, объявленные в этом заголовке. Длину строки лучше определять, как имеющую тип size_t вместо int. class String { char* s; size_t length; //... Ваши конструкторы неверно копируют строки. Они не копируют в результирующую строку завершающий символ нуля '\0'. Например, String(const char* str="") { this->length = strlen(str); this->s = new char[this->length+1]; for (int i = 0; i < length; i++) // символ нуля не входи в диапазон `[0, length)` this->s[i] = str[i]; } Иначе непонятно, зачем вы выделяете память длиною this->length+1 вместо this->length. Непонятно, зачем вы вообще используете циклы для копирования строк, когда вы уже используете стандартные C функции из заголовка Лучше было бы объявить конструктор следующим образом String( const char *str = "" ) { this->length = std::strlen( str ); this->s = new char[ this->length + 1 ]; std::strcpy( this->s, str ); } То же самое замечание справедливо и для конструктора копирования. В определении класса нельзя указывать квалифицированное имя функций-членов класса при их объявлении. Хотя MS VC++ это допускает, но это является его собственным расширением языка, которое не соответствует стандарту. String& String::operator=(String& right); ^^^^^^^^ Эти объявления String String::operator+(String right); friend String& operator+=(String& left, const String& right); лучше переопределить следующим образом friend String operator + ( const String &left, const String &right ); String & operator += ( const String &right ); Функцию show лучше объявить как std::ostream & String::show( std::ostream &os = std::cout ) const; Переопределите соответствующие функции с учетом выше приведенных замечаний. Что касается копирующего оператора присваивания, то он может быть определен следующим образом String & String::operator =( const String &right ) { if ( this != &right ) { size_t n = std::strlen( right.s ); char tmp = new char[ n + 1 ]; std::strcpy( tmp, right.s ); delete [] this->s; this->s = tmp; this->length = n; } return *this; } Аналогичным образом вам нужно определить оператор String & operator += ( const String &right ); Например String & String::operator +=( const String &right ) { size_t n = this->length + std::strlen( right.s ); char tmp = new char[ n + 1 ]; std::strcpy( tmp, this->s ); std::strcat( tmp, right.s ); delete [] this->s; this->s = tmp; this->length = n; return *this; } Функция show определяется просто std::ostream & String::show( std::ostream &os ) const { return os << this->s; } Что касается вашего вопроса Почему у меня s3 всё равно при выводе пустая? то этот оператор String& operator+=(String& left, const String& right) { return left+right; } не изменяет левый аргумент. И более того имеет неопределенное поведение, так как возвращает ссылку на временный объект, создаваемый выражением left+right. EDIT: Если оператор operator [], который, как я понимаю, вы также должны определить, позволяет вставлять в объект класса символ '\0', то вам следует изменить определения конструктора копирования, копирующего опреатора присваивания, оператора operator +=, и дружественной функции operator +, заменив стандартные C функции strXXX на memXXX. Ниже приведена демонстрационная программа, которая показывает, как это можно сделать на примере некоторых членов класса. Другие члены класса постарайтесь определить сами. #include #include class String { private: char *s; size_t length; public: String( const char *str = "" ) { this->length = std::strlen( str ); this->s = new char[ this->length + 1 ]; std::strcpy( this->s, str ); } String( const String &src ) { this->length = src.length; this->s = new char[ this->length + 1 ]; std::memcpy( this->s, src.s, this->length + 1 ); } String & operator =( const String &src ) { if ( this !=+ &src ) { char *tmp = new char[ src.length + 1 ]; std::memcpy( tmp, src.s, src.length + 1 ); delete [] this->s; this->s = tmp; this->length = src.length; } return *this; } String & operator +=( const String &src ) { size_t n = this->length + src.length; char *tmp = new char[ n + 1 ]; std::memcpy( tmp, this->s, this->length ); std::memcpy( tmp + this->length, src.s, src.length + 1 ); delete [] this->s; this->s = tmp; this->length = n; return *this; } ~String() { delete [] this->s; } std::ostream & show( std::ostream &os = std::cout ) { return os << this->s; } }; int main() { String s1( "Hello world!" ); String s2; s2 = s1; s2.show() << std::endl; s2 += " Glad to see you!"; s2.show() << std::endl; return 0; } Вывод программы будет следующим Hello world! Hello world! Glad to see you!

Перегрузка оператора сложения для дробей

#cpp #перегрузка_операторов #операторы


Пишу класс рациональных чисел. Хочу перегрузить оператор сложения, чтобы можно было
складывать с дробями целые числа. Всё получилось, но проблема в том что возможна запись 
<мой класс> = <мой класс> + <целое число> 
Если же я пишу так:
<мой класс> = <целое число> + <мой класс>
всё крашится: no match for 'operator+' (operand types are 'int' and 'Number')

Вoт код в .h:

class Number {
public:
    Number(int numerator = 0, int denominator = 1);

    int getNumerator() {return this->numerator;}
    int getDenominator() {return this->denominator;}
    string toString();

    // определяем арифметические действия дробей с дробями
    Number operator+ (Number &other);
    Number operator- (Number &other);
    Number operator* (Number &other);
    Number operator/ (Number &other);

    // определяем арифметические действия дробей с целыми числами
    Number operator+ (int i);
protected:
    int numerator; // числитель
    int denominator; // знаменатель
};


Код в .cpp:

Number Number::operator+(int i) {
    Number res(i * this->denominator() + this->numerator(), this->denominator());
    return res;
}

    


Ответы

Ответ 1



Вот в том числе и по этой причине такие операторы рекомендуется реализовывать не методами класса, как у вас, а отдельными (дружественными) функциями. Вы реализовали именно оператор сложения, в котором целое является правым операндом. Реализовать оператор сложения для целого числа в качестве левого операнда методом класса невозможно в принципе. Его в любом случае придется реализовывать обычной функцией. Но тут на самом деле стоит сделать шаг назад и посмотреть на общий дизайн. Вам на самом деле вообще не нужно реализовывать операторы для сложения с целыми числами. Ваш класс Number уже обладает конвертирующим конструктором, который умеет преобразовывать целые числа в ваш Number. После этого ваши готовые операторы для операндов типа Number уже сами сделают все правильно. Для этого, однако необходимо 1) реализовать их обычными функциями, 2) не забыть о const в параметрах-ссылках, который у вас почему-то отсутствует. class Number { public: Number(int numerator = 0, int denominator = 1); // определяем арифметические действия дробей с дробями friend Number operator +(const Number &lhs, const Number &rhs); friend Number operator -(const Number &lhs, const Number &rhs); friend Number operator *(const Number &lhs, const Number &rhs); friend Number operator /(const Number &lhs, const Number &rhs); protected: // <- ??? int numerator; // числитель int denominator; // знаменатель }; Такой класс уже сам по себе будет поддерживать Number a(2, 3); Number b = a + 1; Number c = 25 + a; и ничего для этого дополнительно писать не надо. Если вам при этом захочется написать отдельные реализации для операций с обычными числами (например, в целях оптимизации) - вы можете это сделать. Но лучше это делать обычными функциями с двумя параметрами, как показано выше, а не методами класса.

Ответ 2



#include using namespace std; struct Number { int numerator; // числитель int denominator; // знаменатель Number(int num = 0, int denom = 1) : numerator(num), denominator(denom) {} //... // определяем арифметические действия дробей с целыми числами Number operator+ (int i) { return Number(i * denominator + numerator, denominator); } friend Number operator +(const int i, const Number& N) { return Number(i * N.denominator + N.numerator, N.denominator); } friend ostream& operator <<(ostream& os, const Number& N) { os << N.numerator << " / " << N.denominator; return os; } }; int main() { Number test(4, 2); std::cout << 5 + test; return 0; // теперь все нормально! } просто оператор определите и еще можете при возвращении Number производить еще и сокращение дроби... и не нужно делать данные закрытыми, если вы всеравно обеспечиваете доступ ко всем данным! Если вам удобно, то вы можете не определить int + Number, но в кодах написать не 5 + Number, а Number(5) + Number...

Можно ли перегрузить опреатор [] как не член класса?

#cpp #классы #перегрузка_операторов


В целом вопрос в заголовке, не знаю что ещё добавить, кроме примера

Перегрузка как член класса:

class A
{
public:
    int* some_arr;

    int operator [](uint_t i)
    {
        return this->some_arr[i];
    }
}


Перегрузка, как я себе представляю, как не член класса:

class A
{
   // ...
public:
   int* some_arr;
   // ...
}

int operator [](const A& a, uint_t i) // либо поменять аргументы местами, поэтому-то
и не уверен
{
    return a.some_arr[i];
}


И если перегружать как не член класса, то, получается, нужно передавать экземпляр,
но сначала нужно передавать экземпляр, а потом индекс, или наоборот? Или вообще нельзя?
    


Ответы

Ответ 1



Нет: 16.5.5 Subscripting [over.sub] 1 operator[] shall be a non-static member function with exactly one parameter.

воскресенье, 2 февраля 2020 г.

Перегрузка оператор С++

#cpp #перегрузка_операторов #операторы


Такой вопрос. Я перегружаю операторы для работы с вектором, который состоит из трек
точек. Есть несколько задач:


Найти скалярное произведения векторов; (возвращает double)
Найти векторное произведения векторов; (возвращает вектор)


И получается такая проблема, что компилятор не знает какой из двух методов вызвать
(хотя они отличаются возвращающимися параметрами). И как все это решить?

using namespace std;
class Vec3D
{
private:
    double x, y, z;
public:
    Vec3D(double xx = 0, double yy = 0, double zz =0);
    Vec3D operator+ (const Vec3D & V);
//  double operator* (const Vec3D & V); // проблема тут
    friend ostream& operator<< (ostream& cout, Vec3D & V);
    friend Vec3D operator* (const double c, const Vec3D & V);   
    friend Vec3D operator* (const Vec3D & V1,const Vec3D & V2); // и тут
};


P.S. То что нужно реализовать семеричные\бинарные операции через friend функции я знаю
    


Ответы

Ответ 1



Компилятор в общем случае не может определить, какую вызывать перегруженную функцию только по возвращаемому значению. Например, рассмотрите следующий фрагмент кода void f() {} int f() { return 0; } f(); Как определить, какая функция из двух вызывается? Поэтому функции не перегружаются по возвращаемому значению. Перегрузите этот оператор следующим образом, как показано в демонстрационной программе ниже. #include class Vec3D { private: double x, y, z; public: Vec3D( double x = 0, double y = 0, double z =0) : x( x ), y( y ), z( z ) { } friend std::ostream & operator <<( std::ostream &os, const Vec3D &v ); friend Vec3D operator *( const Vec3D &V, double d ); friend Vec3D operator *( double d, const Vec3D &v ); friend Vec3D operator *( const Vec3D &V1, const Vec3D &V2 ); // и тут }; std::ostream & operator <<( std::ostream &os, const Vec3D &v ) { return os << "( " << v.x << ", " << v.y << ", " << v.z << " )"; } Vec3D operator *( const Vec3D &v, double d ) { return Vec3D( d * v.x, d * v.y, d * v.z ); } Vec3D operator *( double d, const Vec3D &v ) { return v * d; } Vec3D operator *( const Vec3D &v1, const Vec3D &v2 ) { return Vec3D( v1.x * v2.x, v1.y * v2.y, v1.z * v2.z ); } int main() { Vec3D v1( 1, 2, 3 ); Vec3D v2( 1, 2, 3 ); std::cout << ( 2 * v1 ) * ( v2 * 3 ) << std::endl; return 0; } Ее вывод на консоль ( 6, 24, 54 ) Нельзя перегрузить оператор по возвращаемому типу. Но вы могли бы взять еще один оператор, как, например, operator ^, чтобы использовать его для одного из видов произведения векторов. В итоге у вас может получиться два оператора operator * и operator ^ Объявление перегруженного оператора operator ^ может выглядеть аналогично объявлению оператора operator * за исключением типа возвращаемого значения.

Ответ 2



Понятие сигнатуры функции не включает возвращаемый тип, поэтому использовать перегрузку для одинаковых типов параметров, но разных возвращаемых типов в C++ нельзя. Увы, решайте проблему иначе... P.S. Недаром они даже в математике обозначаются по-разному :)

Ответ 3



Тут можно постараться и сделать, например, такое (но это не означает, что так делать надо): http://ideone.com/8wAlaK #include struct v3d { double x, y, z; }; struct v3d_mr // multiplication result { double scalar; v3d vector; operator double () { return this->scalar; } operator v3d () { return this->vector; } }; v3d_mr operator * (const v3d &a, const v3d &b) { return { a.x*b.x + a.y*b.y + a.z*b.z, { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - b.x*a.y } }; } int main() { v3d a = {1, 2, 3}, b = {4, 5, 6}; double s = a * b; v3d v = a * b; printf("%lf\n", s); printf("%lf %lf %lf\n", v.x, v.y, v.z); return 0; }

Возврат по ссылке/значению примитивных типов

#cpp #ссылки #перегрузка_операторов


В этом вопросе сказано, что примитивные типы лучше передавать в функцию по значению.
Насколько я понимаю, то же верно и для возврата из функции. Тогда почему операторы
индексации (например, вектора) возвращают исключительно ссылки, а не специализированны
под возврат по значению для фундаментальных типов? Тем более так наверняка будет использована
move семантика.

upd то, что я хочу сделать:

#include 

template
struct A
{
    T x;

    T& operator[](int) {return x;}
    const T& operator[](int) const {return x;}
};
template<>
struct A
{
    int x;

    int& operator[](int) {return x;}
    int operator[](int) const {return x;}
};


int main()
{
    const A a{};
    A b{};
    //a[0] = 3;
    b[0] = 3;
    std::cout << a[0] << ' ' << b[0];
}

    


Ответы

Ответ 1



Если специализировать для всех фундаментальных типов каклй-нибудь стандартный контейнер, который обладает множеством различных методов, то это будет очень не эффективно. Поэтому оператор индексирования в любом случае возвращает ссылку на объект. Передача по ссылке не является ресурсо-затратной операцией. Объекты при этом не создаются и не копируются. Более того для фундаментальных типов конструктор перемещения, как таковой, не существует. Поэтому и нет никакой необходимости специально перегружать стандартные контейнеры для фундаментальных типов, которых немало. К тому же получится неоднозначность с определением перегруженного оператора индексации, так как встроенный оператор возвращает всегда lvalue. Есть еще один нюанс. Даже если вы не собираетесь присваивать возвращаемому объекту новое значение, тем не менее у вас может быть необходимость хранить указатели на эти объекты. Например, у вас может возникнуть необходимость создать объект типа std::reference_wrapper допустим с помощью функции std::cref, который будет указывать на исходный объект в контейнере. Если не передавать ссылку на объект из контейнера, то вы не сможете воспользоваться такой возможностью. Рассмотрите следующий пример #include #include #include int main() { const std::vector v = { 3, 2, 4, 5, 1 }; std::vector> rv = { std::cref(v[0]), std::cref(v[1]), std::cref(v[2]), std::cref(v[3]), std::cref(v[4]) }; std::sort(rv.begin(), rv.end(), [](auto &a, auto &b) { return a.get() < b.get(); }); for (int x : v) std::cout << x << ' '; std::cout << std::endl; for (const auto &r : rv) std::cout << r.get() << ' '; std::cout << std::endl; } Вывод программы на консоль 3 2 4 5 1 1 2 3 4 5 В этой программе объявлен константный вектор. Однако, так как оператор operator [] возвращает ссылку на исходный объект, то можно связать его с объектом std::reference_wrapper, и тем самым представлять элементы исходного вектора упорядочными по различным критериям. То есть неважно, является ли объект константным или нет, тем не менее нередко необходимо получить на него ссылку, чтобы отслеживать его состояние. Константные объекты могут помимо прочего быть еще и volatile объектами или иметь члены данных с модификатором mutable. Если же возвращать объект по значению из оператора, то связь с исходным объектом будет утеряна, и вы будете иметь дело с копией.

Ответ 2



Думаю, это то, что требуется. template struct A { using ConstIndexRetType = typename std::conditional::value, T, const T&>::type; T x; T& operator[](int) { return x; } ConstIndexRetType operator[](int) const { return x; } }; int main(int argc, char **argv) { struct C {}; const A a; const A b; static_assert(std::is_same::value, "?"); static_assert(std::is_same::value, "?"); }

Ответ 3



А давайте глянем на практике... Так сказать, найдите три отличия: https://godbolt.org/g/s95gJt Вот это void f(const A& a, A& b) { std::cout << a[0]; std::cout << b[0]; } компилируется в push rbx mov rbx, rsi mov esi, DWORD PTR [rdi] mov edi, OFFSET FLAT:std::cout call std::basic_ostream >::operator<<(int) mov esi, DWORD PTR [rbx] mov edi, OFFSET FLAT:std::cout pop rbx jmp std::basic_ostream >::operator<<(int) Как видите, разницы никакой.

среда, 29 января 2020 г.

С++: Перегрузка оператора [] в классе массив для изменения элемента

#cpp #массивы #классы #cpp11 #перегрузка_операторов


Реализовать перегрузку оператора [] для класса массив не составляет труда,если нужно
элемент массива просто получить, а как реализовать перегрузку оператора [] для реализации
вида:

intArray a(5);
a[0]=5;
a[1]=10; 
// и т.д.


Буду благодарна за ответ или ссылку на ответ.
    


Ответы

Ответ 1



Если есть класс и нужно перегрузить оператор [] для элементов класса имеющих тип T, то оператор перегружается обычно следующим образом T & operator []( size_t ); и const T & operator []( size_t ) const; Первый оператор позволяет изменять переданный по ссылке элемент класса, а второй позволяет работать с константными объектами. Обратите внимание, что следует одновременно определить оба этих оператора в классе. Для скалярных типов, когда копирование объектов является простой операцией, то можно второй оператор также определить как T operator []( size_t ) const; То есть он будет возвращать временный объект. Как я понимаю, для вашего случая типом T будет служить тип int. Также вы можете перегрузить оператор, когда параметром является список инициализации. Ниже показана демонстрационная программа. #include #include #include class Array { public: Array() : n(0), a(nullptr) {} explicit Array(size_t n) : n(n), a(nullptr) { if (n) a = new int[n](); } Array(size_t n, int value ) : n(n), a(nullptr) { if (n) { a = new int[n]; std::fill(a, a + n, value); } } Array(const Array &a) : n(0), a(nullptr) { if (a.n) { this->a = new int[a.n]; this->n = a.n; std::copy(a.begin(), a.end(), this->begin()); } } ~Array() { delete a; } size_t size() const { return n; } const int & operator [](size_t i) const { return a[i]; } int & operator [](size_t i) { return a[i]; } Array operator [](std::initializer_list lst) const { Array a(lst.size()); size_t i = 0; for ( size_t j : lst ) { a.a[i++] = this->a[j]; } return a; } int * begin() { return a; } const int * begin() const { return a; } int * end() { return a + n; } const int * end() const { return a + n; } private: size_t n; int *a; }; int main() { const int N = 10; Array a(N); int i = N; for (auto &x : a) x = --i; for (auto x : a) std::cout << x << ' '; std::cout << std::endl; Array b = a[{ 0, 2, 4, 6, 8}]; for (auto x : b) std::cout << x << ' '; std::cout << std::endl; } Вывод программы на консоль 9 8 7 6 5 4 3 2 1 0 9 7 5 3 1 В этом примере определены три оператора operator [] const int & operator [](size_t i) const; int & operator [](size_t i); Array operator [](std::initializer_list lst) const; Я класс определил по минимуму. Например, в нем отсутствует копирующий оператор присваивания. Вы можете самостоятельно его написать. Чтобы код компилировался в MS Visual Studio, вам необходимо включить макрообъявление в заголовочный файл следующим образом #pragma once #define _CRT_SECURE_NO_WARNINGS //...

Ответ 2



Если для получения вы определяете оператор как int operator[](size_t index); то для того, чтобы и получать значение, и присваивать, его нужно определить как ссылку: int& operator[](size_t index);

четверг, 23 января 2020 г.

Перегрузка операторов ввода и вывода из потока в шаблонном классе

#cpp #перегрузка_операторов #шаблоны_с++


Доброго времени суток. Пишу свой вектор. В нём надо перегрузить вышеназванные операторы.
Vector.h

class Vector
{
    // ...
    template friend std::ostream&  operator<< (std::ostream&, Vector&);
    // ...
};

template
std::ostream& Vector::operator << (std::ostream& os, Vector& v)
{
    for(int i=0;i::operator<<(std::ostream, const     Vector&)' must take
exactly one argument
std::ostream Vector::operator << (std::ostream output, Vector const& v)
^

    


Ответы

Ответ 1



Найдите 4 отличия: friend std::ostream& operator << (std::ostream&, Vector &); template std::ostream Vector::operator << (std::ostream os, Vector &v)