Skip to content

Instantly share code, notes, and snippets.

@NickTikhomirov
Last active June 19, 2019 19:12
Show Gist options
  • Save NickTikhomirov/f958bf9a73527fd0aa3ecbb3b872a212 to your computer and use it in GitHub Desktop.
Save NickTikhomirov/f958bf9a73527fd0aa3ecbb3b872a212 to your computer and use it in GitHub Desktop.
Перегрузка

Вопросы 9-12, Перегрузки

Автор: Никита

Головной раздел: Меню

Билет 9-10(11). Перегрузка стандартных операций. пример.

Под операциями подразумеваются всякие штуки с участием операторов (+, -, =, *, /, <<...)

Перегрузка операций в языке Си++ это возможность распространения действия стандартных операций на операнды, для которых эти операции первоначально не предназначались. Это возможно, если хотя бы один из операндов является объектом класса, для этого создается специальная, так называемая, оператор-функция (Какая-то заумная хрень от Быкова. Если по-простому, то перегружать можно, если есть класс).

Список операторов, которые можно перегружать: (это не для вопроса, а для общего развития)

Унарные арифметические: + и - 
Бинарные арифметические: +, -, *, /, %
Сравнение: ==, !=, <, >, <=, >=
Логические унарные: !
Логические бинарные: &&, ||
Побитовые унарные: ~
Побитовые бинарные: &, |, ^, <<, >>
Инкремент и декремент: ++а, а++, --а, а--
Присваивание: = (не перегружайте, если не чувствуете себя большим специалистом)
Составное присваивание: х=, где х - любая бин.оп. кроме сравнения (можно перегружать свободно)

Любое приведение типа: (тип) a  (это как с++ за вас приводит int к double, если надо)
Квадратные скобки (бинарный): х[у]
Круглые скобки (n-арный): х(у) (скобки можно перегрузить на любое количество аргументов)
Переход по указателю (унарный): *x
Получение адреса (унарный): &x
"От указателя к полю": ->, ->*
Работа с памятью: new, new..[], delete, delete[]
Запятая: ,

Список операторов, которые нельзя перегружать:

Получение поля: . и .*
Тернарный оператор: ?:
Область видимости: :: (как в std::cout)
Оператор получения занятой памяти: sizeof
Оператор получения типа: typeid() (см.34 вопрос)
Оператор alignof
Указания препроцессору: # и ##

Способы перегрузки:

  1. Описать оператор в классе (Очевидно, статус доступа: public)
  2. Описать оператор вне класса (и, при желании, назначить его другом класса)

Различия перегрузки способами 0 и 1:

Если мы перегружаем операцию вне класса, то она принимает столько аргументов, сколько надо.
Если же перегружаем как член класса, то она принимает на один аргумент меньше, один всегда занят классом.
То есть унарные в классе не принимают аргументов, а вне класса - принимают один (и тот - сам класс)
Бинарные в классе принимают один аргумент, а вне класса - принимают два (среди которых один - это класс)

Если перегружаем оператор в классе, и он бинарный, то перегружаются обе постановки:
  Студент+число     //пример для класса студент, оператора + и второго операнда представленного числом
  Число+студент
Если же вне класса, то придётся перегружать каждую постановку вручную
  Студент+число - первая перегрузка
  Число+студент - вторая перергрузка

Правила перегрузки:

  1. Не придумывать новых операторов
  2. Бинарный оператор перегружаем как бинарный, унарный как унарный
  3. #include "Список операторов, которые нельзя перегружать"
  4. Операции "=", "[]", () и -> перегружаются обязательно как функции класса
  5. #include "Различия перегрузки способами 0 и 1"

Пример:

  class Vector2{
    int x;
    int y;
  public:
     Vector2 operator-();                    //В классе
     friend int operator*(Vector2, Vector2); //Вне класса
  };
  
  Vector2 Vector2::operator-(){
     Vector2 m;
     m.x = -x;
     m.y = -y;
     return m;
  }
  int operator*(Vector2 a, Vector2 b){
    return (a.x*b.x + a.y*b.y);
  }

Особенности перегрузки инкремента и декремента (БИЛЕТ 11)

//Префиксная - слева, постфиксная - справа
//Инкремент и декремент СЛЕВА перегружаются просто как унарные операторы
//Инкремент и декремент СПРАВА получают фиктивный второй параметр типа int (нигде не используется). 
//  Пример:
  Vector operator++(Vector a1, int) 
  {
    Vector a2=a1;
    a1.x++;
    a1.y++;
    return a2;
  }
Дополнительный материал (не к билету) - перегрузка преобразования типов
class Date{
    char days;
    char months;
    unsigned short years;
public:
    Date(char d=1, char m=1, unsigned short y=1970)
    { days=d;  months=m;  years=y; }
    operator string(){          //Оформление правильное - я компилировал, я проверял
        string p=" ";
        if(days%10==days) p+="0";
        p+=to_string(days);
        p+=".";
        if(months%10==months) p+="0";
        p+=to_string(months);
        p+=".";
        p+=to_string(years);
        return p.substr(1,10);
    }
    operator int(){  return years;  }
};


int main(){
    Date a;
    cout<<string(a)<<endl;
    //Без подсказки железка не понимает моего желания перевести в string, с cout.
    //Даже если других преобразований не прописано.
    //Зато в функцию с аргументом-string передавать можно без подсказки.
    cout<<a;   //В int умеет :)
}

__________________________________________________________________

Билет 12. Особенности перемещения объекта, продемонстрировать пример перемещения при перегрузке операции «=» (присваивание) на примере своего класса вектор, содержащем в качестве поля динамический массив элементов типа int.

-- Заимствование из 3 билета--

Конструктор перемещения - это конструктор вида (для случая внутри класса):

A(A&& a){               //A - имя класса
  Операторы_тела конструктора
}
Назначение конструктора перемещения: взять поля в исходном объекте и перекинуть в новый так, чтобы исходный можно было свободно уничтожить, не повредив новый. Пример реализации - скопировать поля в новый и занулить указатели в исходном, чтобы его деструктор не добрался до важных объектов.

Вызов конструктора перемещения в коде программы:

  A a1;
  A a2 = std::move(a1);

--Конец заимствования--

Но работать мы будем не с конструктором, а с присваиванием перемещением. Тут почти всё то же самое.

Давайте для начала введём наш класс:

  class MyVector{
  public:
    int *coords;
    int length;
    MyVector(){ length=0; coords=0;}
    void operator=(MyVector&&);     //Кстати здесь мы сломали присваивание. Без move() оно больше не работает
    void print();
  };


void MyVector::print(){
    if(length==0) cout<<"empty"<<endl;
    for(int i=0; i<length; i++)
        cout<<coords[i]<<endl;
}

void MyVector::operator=(MyVector&& a){
    if(coords!=0) delete[] coords;       //Совершенно необязательная чистка памяти
    coords = a.coords;
    length = a.length;
    a.coords = 0;
    a.length = 0;
}

А затем добавим капельку intmain-а: (можете сами что-нибудь поинтереснее придумать)

int main(){
    int len = 3;
    MyVector r,l;
    r.length = len;
    r.coords = new int[len];
    for(int i=0; i<r.length; i++)
        r.coords[i] = i;
    cout<<"r:";
    r.print();
    l=move(r);   //Быков как-то это без move делает, но это блин фокус какой-то
    cout<<"r:";
    r.print();
    cout<<"l:";
    l.print();
}

После этого примера (отклеятся обои) программа выведет r: 0 1 2, затем присвоит, выведет r: empty и l: 0 1 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment