Перегрузка постфиксных операций — КиберПедия 

Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...

Перегрузка постфиксных операций

2021-12-07 22
Перегрузка постфиксных операций 0.00 из 5.00 0 оценок
Заказать работу

Примеры постфиксной операции:

int a = 3;a++;a--;

То есть, здесь знак операции ставится после имени идентификатора. Для использования постфиксных операций с пользовательскими типами данных нужно совсем немного:

public: void operator++ () { counter += 1; } void operator++ (int) { counter += 1; }

Единственным отличием префиксной операции от постфиксной - ключевое слово int в списке аргументов. Но int - это не аргумент! Это слово говорит, что перегружается постфиксная операция. Теперь операцию ++ можно использовать как перед идентификатором объекта, так и после:

Counter a;++a;++a;a++;a++;

Перегрузка бинарных операций

Перегрузка операций с двумя аргументами очень похожа на перегрузку бинарных операций:

Counter operator+ (Counter t){ Counter summ; summ.counter = counter + t.counter; return summ;} Counter c1,c2,c3;c1.counter = 3;c2.counter = 2; c3 = c1 + c2;

Какая переменная вызовет функцию operator+? В перегруженных бинарных операциях всегда вызывается метод левого операнда. В данном случае метод operator+ вызывает объект c1.

В метод мы передаём аргумент. Аргументом всегда является правый операнд.

Кроме того, в данном случае операция + должна вернуть какой-то результат, чтобы присвоить его объекту c3. Мы возвращаем объект Counter. Возвращаемое значение присваивается переменной c3.

Заметьте, мы перегрузили операцию +, но не перегружали операцию =! Конечно же нужно добавить этот метод к классу Counter:

Counter operator= (Counter t){ Counter assign; counter = t.counter; assign.counter = t.counter; return assign;}

Внутри метода мы создали дополнительную переменную assign. Данная переменная используется, чтобы можно было работать вот с таким кодом:

Counter c1(5),c2,c3;c3 = c2 = c1;

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

38.   Преобразования типов

С-style cast

В языке С явное приведение типов осуществялось при заключении типа в круглые скобки, так называемое "приведение в круглых скобках"

Например, преобразование Double --> int:

      int i = (int) 2.5;  

Однако, такая форма приведения не наглядна, что существенно затрудняет поиск ошибок

Кроме того могут возникать ошибки при приведении пользовательских типов:

      A * a =...;      B * b = (B *) a;  

Для некоторых типов могут возникать неопределенности. A и B могут быть никак не связаны, т.е. эта операция может не быть правомощной.

Надо различать преобразования связанных типов и несвязанных типов.

В С++ ввели свои именованные операторы преобразования типов:

· const_cast < > ()

· static_cast < > ()

· reinterpret_cast < > ()

· dynamic_cast < > ()

Теперь боле подробно о каждом операторе приведения типов

const_cast < > ()

Оператор const_cast, как и следует из его имени позволяет убрать или добавить константность

      A const * ac =...;      A * a = const_cast<A *>(ac);  

Пример

В массиве Array реализуем функцию get, в которой может быть много проверок.

И хотим написать вторую функцию через первую

      T const & get (int i) const;      T & get (int i)            {          return const_cast<T &>(const_cast<Array const *>(this)->get(i));      }    

Нельзя написать get(i), т.к. уйдем в рекурсию

Надо привести this к const Array, а потом убрать константность

const_cast не применим:

Для тех объектов, которые объявлены как const, нельзы отменять константность. Будет undefined behaviour.

Если в функцию, например, передана константная ссылка, то внутри мы можем снять константность, хотя это не хорошо.

Для приведения констант применяется лишь оператор const_cast. Применение любого другого оператора приведения типов в данном случае привело бы к ошибке при компиляции. Аналогично, ошибка при компиляции произойдет в случае использования оператора const_cast в записи, которая осуществляет любые другие преобразования типов, отличные от создания или удаления константности

reinterpret_cast < > ()

Приводит друг к другу указатели, которые друг от друга не зависят, не меняя константности

Оператор reinterpret_cast является жестко машинно-зависимым. Чтобы безопасно ипользовать операторreinterpret_cast, следует хорошо понимать, как именно реализованы используемые типы, а также то, как компилятор осуществляет приведение

Рассмотрим следующий пример приведения

      int * p =...;      double * d = reinterpret_cast<int *>(p);  

Эта инструкция ни во что не компилируется, как и const_cast. Здесь не происходит вызова никакой функции. Это просто указание систее типов, что теперь у нас другой тип

В данном случае следует помнить, что фактическм типов объекта, адрес которого содержит указатель d, является int, а не символьный массив. Любая попытка применения указателя d там, где необходим обычный символьный указатель, вероятнее всего, потерпит неудачу именно во время выполнения. Например, его использование для инициализации объекта типа string приведет к ошибке во время выполнения. Это хороший пример, который служит для демонстрации того, почему явные приведения отнюдь небезопасны. Проблема заключается в том, что при изменении типа компилятор не выдаст никаких предупреждений или сообщений об ошибке. Компилятор не способен выяснить, адрес какого именно значения фактически хранит указатель. Отследить такие ошибки иногда чрезвычайно трудно, особенно если приведение указтеля к другому типу происходит в одном файле, а используется указатель - в другом.

static_cast < > ()

Преобразование между связанными значениями.

Связанность проверяется на этапе компиляции, поэтому и называется static.

Типы, к котрым применим static_cast

· числовые

· типы, связанные наследованием

· пользовательские преобразования

· к void *

Теперь подробнее о каждом преобразовании

· числовые типы

      static_cast<int>(5.5);  

· типы, связанные наследованием, а также ссылки и указатели на них

(Подразумеваются ссылки и указатели)

       B * b =...;       A * a = static_cast<A *>(b);       b = static_cast<B *>(a);  

В том месте, где пишем это преобразование, должно быть известно, что B - наследник A

Если просто объявить эти классы:

       struct A;       struct B; 

то этот код не сработает

(А reinterpret_cast, как и const_cast, сработал бы, ему не надо знать, как устроены типы)

· пользовательские преобразования

      static_cast<string>("Hello");  

Это эквивалентно:

      string("Hello");  // Вызов конструктора  

Что эквивалентно C-style cast:

      (string)"Hello";    

Основное место, где возникают проблемы, это шаблоны. Пусть заданы типы V и T. Считаем, что V наследуется от T. Если это не так, то код со static_cast не скомпилируется.

· void *

Можно преобразовать любой указатель к void * и обратно

Пример

Только объявим классы:

      struct A;            struct B;            struct D;             A * a =...;       B * b = static_cast<B *>(a);  // Не скомпилируется       B * b = (B *)a;  // Это сработает как reinterpret_cast              // Мы не сдвинем указатель, но компилятор не выдаст ошибки  

Если же помимо объявления будут определения классов, то static_cast сработает правильно

Замечание: если приводим встроенные типы, то можно использовать C-style cast, в остальных случаях надо использовать C++ операторы преобразования типов

RTTI

RTTI = Run-Time Type Information

RTTI позволяет программам, которые используют указатели или ссылки на базовые классы, выяснять фактически типы объектов произвольных классов, к которым относятся эти указели и ссылки.

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

RTTI обеспечивает два следующих оператора:

· Оператор typeid, которые возвращает фактический тип объекта, к которому относится указатель или ссылка.

· Оператор dynamic_cast, который осуществляет безопасное преобразование указатели или ссылки на базовый класс в указатель или сссылку на произвольный класс

Эти операторы возвращают инфомацию о динамическом типе только для тех классов, которые обладают одной или несколькими виртуальными функциями. Для всех остальных классов возвращается информация о статическом типе (т.е. типе времени компиляции)

Typeid

Оператор typeid имеет следующий формат:

      typeid(e)  

Здесь е - это любое выражение или имя класса

Результатом операнда typeid является ссылка на объект библиотечного типа type_info. Чтобы использовать классtype_info, необходимо подключить библиотечный заголовок typeinfo:

      #include <typeinfo>  

type_info - структура, которая позволяет получить некоторую информацию о типе

Действия, которые можно производить с объектами типа type_info:

· Сравнение на равенство ==

· Сравнение на неравенство!=

· name() - возвращает символьную строку в стиле С, содержащую отображаемую версию имени типа. Может вернуть пустую строку, что не очень хорошо.

· before() - возвращает логическое значение, указывающее на то, следует ли один тип прежде другого. Порядок следования зависит от компилятора. Т.е. с помощью этого метода типы можно упорядочить.

       A * a = new C();       type_info ti = typeid(* a);  // Передается ссылка на объект       ti.name();  // "C"  

typeid(int) - определяется в момент компиляции

typeid(A) - определяется в момент компиляции // "A"

typeid(a) - определяется в момент компиляции // "A * "

typeid(* a) - действие времени выполнения. Для того чтобы это действие действительно было времением выполения класс А должен обладать полиформизмом (должно быть наличие виртуальной функции), иначе вернет "A"

Пример

Хотим написать функцию пересечения двух фигур

       bool intersert (shape * a, shape * b)       {           if (typeid(*a)== typeid(Circle))           {               if (...)           }       }  

Т.е. перебор всех возможных вариантов - это n^2

Чтобы избавится от if можно написать map

        map<pair<type_info, type_info>, bool(*)(shape *, shape *)>  

Пусть есть M типов

Умеем переводить только некоторые из них:

· A --> B

· B --> C

· A --> D

· C --> E

· E --> D

Например, это преобразования единиц длины

Хотим перевести A --> E, но прямого такого преобразования нет, можно построить кратчайший путь, используяtype_info

dynamic_cast < > ()

Можно воспользоваться оператором приведения типов dynamic_cast

       if (Circle * c = dynamic_cast<Circle>(a))       {          ...  // Первая фигура - круг       }  

Преобразование указателей с помощью dynamic_cast

       A * a = new C();       B * b = dynamic_cast<B *>(a);; // Вернет 0  

Преобразование ссылок с помощью dynamic_cast

       B & b = dynamic_cast<B &>(* a);; // В случае неудачи бросит исключение  

Т.е это корректный механизм, который проверяет правомерность преобразований

Но обычно всё можно реализовать без dynamic_cast

 

39. НАСЛЕДОВАНИЕ

Цель объектно-ориентированного программирования состоит в повторном использовании созданных вами классов, что экономит ваше время и силы. Если вы уже создали некоторый класс, то возможны ситуации, что новому классу нужны многие или даже все особенности уже существующего класса, и необходимо добавить один или несколько элементов данных или функций. В таких случаях C++ позволяет вам строить новый объект, используя характеристики уже существующего объекта. Другими словами, новый объект будет наследовать элементы существующего класса (называемого базовым классом). Когда вы строите новый класс из существующего, этот новый класс часто называется производным классом. В этом уроке впервые вводится наследование классов в C++. К концу данного урока вы изучите следующие основные концепции:

  • Ели ваши программы используют наследование, то для порождения нового класса необходим базовый класс, т.е. новый класс наследует элементы базового класса.
  • Для инициализации элементов производного класса ваша программа должна вызвать конструкторы базового и производного классов.
  • Используя оператор точку, программы могут легко обращаться к элементам базового и производного классов.
  • В дополнение к общим (public) (доступным всем) и частным (private) (доступным методам класса) элементам C++ предоставляет защищенные (protected) элементы, которые доступны базовому и производному классам.
  • Для разрешения конфликта имен между элементами базового и производного классов ваша программа может использовать оператор глобального разрешения, указывая перед ним имя базового или производного класса.

Наследование является фундаментальной концепцией объектно-ориентированного программирования. Выберите время для экспериментов с программами, представленными в этом уроке. И вы обнаружите, что реально наследование реализуется очень просто и может сохранить огромные усилия, затрачиваемые на программирование.

ПРОСТОЕ НАСЛЕДОВАНИЕ

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

class employee

{
public:
employee(char *, char *, float);
void show_employee(void);
private:
char name[64];
char position[64];
float salary;
};

Далее предположим, что вашей программе требуется класс manager, который добавляет следующие элементы данных в класс employee:

float annual_bonus;
char company_car[64];
int stock_options;

В данном случае ваша программа может выбрать два варианта: во-первых, программа может создать новый класс manager, который дублирует многие элементы класса employee, или программа может породить класс типа manager из базового класса employee. Порождая класс manager из существующего класса employee, вы снижаете объем требуемого программирования и исключаете дублирование кода внутри вашей программы.

Для определения этого класса вы должны указать ключевое слово class, имя manager, следующее за ним двоеточие и имя employee, как показано ниже:

Производный класс //-----> class manager: public employee { <-------// Базовый класс

// Здесь определяются элементы
};

Ключевое слово public, которое предваряет имя класса employee, указывает, что общие (public) элементы класса employee также являются общими и в классе manager. Например, следующие операторы порождают класс manager.

class manager: public employee

{
public:
manager(char *, char *, char *, float, float, int);
void show_manager(void);
private:
float annual_bonus;
char company_car[64];
int stock_options;
};

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

Следующая программа MGR_EMP.CPP иллюстрирует использованиенаследования в C++, создавая класс manager из базового класса employee:

#include <iostream.h>

#include <string.h>

class employee

{
public:
employee(char *, char *, float);
void show_employee(void);
private:
char name [ 64 ];
char position[64];
float salary;
};

employee::employee(char *name, char *position,float salary)

{
strcpy(employee::name, name);
strcpy(employee::position, position);
employee::salary = salary;
}

void employee::show_employee(void)

{
cout << "Имя: " << name << endl;
cout << "Должность: " << position << endl;
cout << "Оклад: $" << salary << endl;
}

class manager: public employee

{
public:
manager(char *, char *, char *, float, float, int);
void show_manager(void);
private:
float annual_bonus;
char company_car[64];
int stock_options;
};

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options): employee(name, position, salary)

{
strcpy(manager::company_car, company_car);
manager::annual_bonus = bonus;
manager::stock_options = stock_options;
}

void manager::show_manager(void)

{
show_employee();
cout << "Машина фирмы: " << company_car << endl;
cout << "Ежегодная премия: $" << annual_bonus << endl;
cout << "Фондовый опцион: " << stock_options << endl;
}

void main(void)

{
employee worker("Джон Дой", "Программист", 35000);
manager boss("Джейн Дой", "Вице-президент ", "Lexus", 50000.0, 5000, 1000);
worker.show_employee();
boss.show_manager();
}

Как видите, программа определяет базовый класс employee, а затем определяет производный класс manager. Обратите внимание на конструктор manager. Когда вы порождаете класс из базового класса, конструктор производного класса должен вызвать конструктор базового класса. Чтобы вызвать конструктор базового класса, поместите двоеточие сразу же после конструктора производного класса, а затем укажите имя конструктора базового класса с требуемыми параметрами:

manager::manager(char *name, char *position, char *company_car, float salary, float bonus, int stock_options):
employee(name, position, salary) //————————————— Конструктор базового класса

{
strcpy(manager::company_car, company_car);
manager::annual_bonus = bonus;
manager::stock_options = stock_options;
}

Также обратите внимание, что функция show_manager вызывает функцию show_employee, которая является элементом класса employee. Поскольку класс manager является производным класса employee, класс manager может обращаться к общим элементам класса employee, как если бы все эти элементы были определены внутри класса manager,


Поделиться с друзьями:

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Кормораздатчик мобильный электрифицированный: схема и процесс работы устройства...

Особенности сооружения опор в сложных условиях: Сооружение ВЛ в районах с суровыми климатическими и тяжелыми геологическими условиями...

Таксономические единицы (категории) растений: Каждая система классификации состоит из определённых соподчиненных друг другу...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.073 с.