
1.5. Перегрузка операций
Объекты класса являются переменными, определенными пользователем. Для работы с объектами и выполнения операций над ними необходимо определить эти операции. Для этого существует такой механизм ООП как перегрузка операций.
Перегрузка операций дает возможность переопределить С++. Это механизм, который дает возможность определить новые операции над объектами.
Для определения нового оператора используется ключевое слово operator. Операторы определяются внутри класса, по такому же принципу как обычные методы класса, как внутри класса, так и вне него.
Оператор, определенный в классе, может быть использован для объектов данного класса.
Так, если мы напишем ob++, то в классе, к которому относится объект, должен быть определен оператор «++», иначе компилятор не сможет выполнить эту операцию, т.к. все операции языка С++ определены для базовых типов данных.
Формат записи перегрузки унарных операций:
тип_возвр_значения operator «операция» (список аргументов)
{тело оператора}
например: void operator ++()
{…..}
Формат перегрузки бинарных операций:
Тип_возвр_значения operator «операция» (объект)
{тело оператора
Пример:
class myclass
{
int x, y;
public:
coord () {x=0; y=0;} //конструктор
coord operator + (coord ob2); //Бинарная операция «+»
coord operator +();//унарная операция «+»
};
coord coord:: operator +(coord ob2) //реализация бинарной операции «+»
{ coord temp;
temp.x=x+ob2.x;
temp.y=y+ob2.y;
return temp;
}
coord coord::operator +() //реализация унарной операции «+»
{ coord temp;
x=+x;
y=+y;
return *this;
}
Для определения перегрузки бинарных операций существует правило: объект стоящий с левой стороны операции, вызывает функцию оператора, объект стоящий справа от знака операции должен быть передан в функцию в качестве аргумента.
Конструкторы с параметрами.
Конструкторы и деструкторы
Так как в ООП реализована концепция инкапсуляции, которая позволяет защищать данные извне от несанкционированного доступа, то чаще всего данные в классе объявляются с идентификатором доступа private. Поэтому доступ к данным возможен только через функции. При создании объекта, необходимо решить вопрос инициализации данных. Это возможно через специальные функции, называемые конструкторами.
Конструктор – это специальная функция, вызываемая автоматически при создании объекта с целью инициализации данных.
Конструктор имеет имя, совпадающее с именем класса, и не имеет возвращаемого значения. Формат описания конструктора следующий:
Имя_класса (список_формальных параметров)
{ тело конструктора}
Т.о. конструктор используется для инициализации объекта.
Пример:
class myclass
{
private:
int x,y;
public:
myclass () {x=0; y=5;} //конструктор
~myclass(){} //деструктор
……..
}
Функция, являющаяся противоположностью конструктора, которая существует для разрушения объекта (удаления всей, связанной с объектом информации из памяти) называется деструктором.
Формат задания деструктора совпадает с форматом задания конструктора, но перед именем класса задается знак «~» (тильда).
Деструктор класса – это функция, автоматически вызываемая при удалении объекта.
Конструктор и деструктор могут выполнять любые типы операций, но использование конструктора и деструктора для операций, не связанных с инициализацией, считается очень плохим стилем программирования.
Если при определении класса не создается конструктор, то компилятор автоматически встраивает конструктор и инициализирует данные сам.
Адреса конструктора и деструктора получить невозможно.
Конструктор можно создавать с параметрами, т.е. при создании объекта конструктору можно передавать параметры.
В этом случае формат конструктора следующий:
Имя_класса (список формальных параметров)
{тело конструктора}
Тогда формат записи создания объекта следующий:
Имя_класса имя_объекта(фактические параметры_конструктора);
Или
Имя_класса (фактические_параметры_конструктора);
Первая форма допускается только при непустом списке параметров. Она предусматривает вызов конструктора при определении нового объекта данного класса.
Вторая форма явного вызова конструктора приводит к созданию объекта, не имеющего имени.
В одном и том же классе могут быть определены несколько конструкторов. При создании объекта, компилятор по типу фактических параметров определяет какой конструктор вызвать.
Существует два способа инициализации данных с помощью конструкторов:
Передача параметров в тело конструктора;
Применение списка инициализаторов данных объекта. Этот список помещается между списком параметров и телом конструктора.
Имя_класса (список параметров): список_инициализаторов_компонентных данных
{Тело конструктора}
Каждый инициализатор списка относится к конкретному компоненту имеет вид - имя_компонента_данных (выражение)
Пример:
class A
{ int ii;
float ee;
char cc;
public:
A(int in, float en, char cn): ii(5),ee(ii*en+in), cc(cn)) {}
A () {ii=1; ee=5.0; cc=’f’;}
};
main ()
{
A A1(2,3.0, ‘d’);
A A2;
………
}
Перегрузка операции индексации массива.
Перегрузка операции индексации обычно применяется к объекту со свойствами массива (имеющему поле-массив). Операция индексации должна быть перегружена в двух вариантах: обычном и константном. Обычная перегрузка обеспечивает использование операции индексации в левой и правой части операции присваивания. Константная - только в правой. Константный вариант перегрузки необходим для использования операции в константных методах или для константных объектов.
Метод (операция индексации может быть перегружена только как метод класса), перегружающий операцию индексации в обычном варианте должен:
Иметь возвращаемый тип – ссылка на тип элемента индексируемого массива.
Перед выполнением операции проверять, не выходит ли запрашиваемое значение индекса за границы массива и возбуждать при необходимости исключение OUTOFRANGE.
Возвращать запрашиваемый элемент массива.
Метод, перегружающий операцию индексации в константном варианте, отличается от обычного только тем, что является константным методом класса и должен иметь возвращаемый тип – константная ссылка на тип элемента индексируемого массива.
Перегрузка операции индексации массива.
class string
{
char *str;
int len;
public:
class range {};
char &operator[](int);
};
char &string::operator[] (int x)
{
if((x<0)||(x>=len) throw range();
return str[x];
}
main()
{
string S;
…
try
{
char c=str[-3];
printf(“%c”,c); //не будет выполняться
}
catch (string::range)
{
printf(“выход за пределы строки”);
}
printf(“конец работы программы”);
}
Примечание. Операция индексации может быть перегружена не только для параметра целочисленный индекс, но и для любого типа параметра. В частности, операцию индексации перегружают для объекта типа контейнер, имеющего набор (список) объектов некоторого класса. Операция индексации в этом случае выполняет поиск объекта набора по одному из его признаков. Подробнее – см. тему Контейнер.
Определение методов класса вне класса.
Методы класса
Методы класса в обязательном порядке определяются в классе , а описываться могут как внутри класса, так и вне его. Если метод описывается вне класса, то формат записи методов следующий:
тип_возвр_значения имя_класса::имя_функции(аргументы) { тело функции}
Такая форма записи устанавливает взаимосвязь функции и класса, к которой относится эта функция.
Пример:
#include <conio.h>
#include <iostream.h>
class MyClass
{
int a; //Переменная а доступна только внутри класса MyClass
public:
MyClass(); //Прототип конструктора
void show_a(); //Прототип функции show_a()
} obj1; //obj1 - Переменная типа MyClass. Здесь описание класса закончено, обязательны точка с запятой
//Для описания методов вне класса используют оператор глобального разрешения
MyClass::MyClass() //Класс::Конструктор Конструктор не возвращает никаких значений
{
cout<<"Wwedi a: "; //В конструкторе выполняется ввод значения в а с клавиатуры
cin>>a;
}
void MyClass::show_a() //Тип функции Класс::Функция. Описываем функцию show_a()
{
cout<<a<<endl; //Функция выводит на экран значение a из класса MyClass
}
//Описание методов вне класса закончено
//++++++++++++++++++++++++++++++++++++++++++
void main()
{
clrscr(); //Очистка экрана для очистки при первом запуске
obj1.show_a(); //Показали а
getch(); //Ожидание нажатия Enter
clrscr(); //Так как объекты конструируются до входа в функцию main, делаем генеральную уборку с экрана непосредственно перед самым выходом
return;
}
11 Преобразование типов.
Преобразование типов.
Есть 4 вида преобразования:
1) const_cast – служит для удаления модификатора const. Снимает cv qualifiers — const и volatile, то есть константность и отказ от оптимизации компилятором переменной. Это преобразование проверяется на уровне компиляции и в случае ошибки приведения типов будет выдано сообщение.
const_cast <тип> (выражение)
Пример:
const char *str=”abc”
void display(char *s)
{printf(s);}
display(str); //неправильно, т.к. нужен char, а не const char
display(const_cast <char*>(str)); //правильно
2)dynamic_cast – нужен для преобразования родственных классов иерархии. Используется для динамического приведения типов во время выполнения. В случае неправильного приведения типов для ссылок вызывается исключительная ситуация std::bad_cast, а для указателей будет возвращен 0. Использует систему RTTI (Runtime Type Information). Безопасное приведение типов по иерархии наследования, в том числе для виртуального наследования.
dynamic_cast <тип*> (выражение)
Пример:
class B {public: virtual void f1() {} };
class C: public B
{public void f2() {printf(“f2”);}};
void display(B *p)
{
C *c = dynamic_cast <C*> (p);
if (c==0) printf(“not class C”);
else cf2;
}
B *b=new B();
display(b); //not class C
B *c=new C();
display(c); //f2
3)static_cast – служит для преобразования типов на этапе компиляции. static_cast преобразует выражения одного статического типа в объекты и значения другого статического типа. Поддерживается преобразование численных типов, указателей и ссылок по иерархии наследования как вверх, так и вниз. Проверка производится на уровне компиляции, так что в случае ошибки сообщение будет получено в момент сборки приложения или библиотеки.
static_cast <тип> (выражение)
Пример:
float f=100;
int a = static_cast <int> (f); //a=100
4)reinterpret_cast – выполняется на этапе компиляции. В отличие от static_cast внутреннее представление данных остается неизменным, изменяется только точка зрения компилятора на данный момент. Приведение типов без проверки. reinterpret_cast — непосредственное указание компилятору. Применяется только в случае полной уверенности программиста в собственных действиях. Не снимает константность и volatile. применяется для приведения указателя к указателю, указателя к целому и наоборот.
reinterpret_cast <тип> (выражение)
Пример:
float f=100.5;
int a = static_cast <int> (f); //a=100;
int b = reinterpret_cast <int> (f); //b не равно 100
12 Преобразование объектов одного класса в объекты другого класса.
Преобразование основных типов
Вычисление значений выражений в операторах C++ обеспечивается выполнением операций и вызовом функций. Операции используют операнды, функции требуют параметров. Операнды и параметры характеризуются типом. В C++ не существует операций, которые, например, обеспечивали бы сложение или умножение операндов различных типов. Выражения вызова функций также требуют соответствия типа параметров типу параметров определения и прототипа.
Однако не всегда в программе удаётся легко согласовать типы операндов и параметров. И здесь проблем, связанных с согласованием типов операндов и параметров транслятор берёт на себя. Фактически это означает введение ещё одной системы правил, которая называется правилами стандартного преобразования типов.
В общем случае, при определении значения выражения могут возникать следующие ситуации:
1.Присвоение "большему типу" значения "меньшего типа". Безопасное присвоение, гарантирует сохранение значения.
unsigned int UnsignedIntVal; unsigned char UnsignedCharVal; UnsignedIntVal = UnsignedCharVal;
2.Присвоение "меньшему типу" значения "большего типа". Потенциально опасное присвоение, грозит потерей информации.
int IntVal; char CharVal; CharVal = IntVal;
3.Преобразование значения из "меньшего типа" в "больший тип". Называется расширением типа.
(unsigned int)UnsignedCharVal;
4.Преобразование значения из "большего типа" в "меньший тип". Называется сужением типа. Является опасным преобразованием.
(char)IntVal;
Корректное выполнение действий со значениями различных типов в безопасных случаях и в ряде опасных случаев обеспечивается благодаря реализованной в C++ системе преобразования типов.
При трансляции выражений с различными типами операндов транслятор использует механизмы неявных преобразований, которые основываются на следующих правилах стандартных преобразований:
Присваивание значения объекту преобразует это значение к типу объекта.
unsigned int MyIntU;
MyIntU = 3.14159;
Эквивалентно
MyIntU = (unsigned int)3.14159;
Передача значения при вызове функции преобразует это значение в тип параметра функции. Он становится известен благодаря прототипу вызываемой функции.
void ff(int); // Прототип функции.
:::::
ff(3.14159);
Эквивалентно
ff((int)3.14159);
При этом на стадии трансляции возможно появление предупреждения о сужении типа.
В арифметическом выражении тип результата выражения определяется самым "широким" типом среди всех образующих выражение операндов. Этот тип называют результирующим типом выражения. К этому типу преобразуются все остальные операнды.
unsigned int MyIntU = 5;
…(MyIntU + 3.14159)…
Результирующим типом выражения здесь оказывается тип double, представленный в выражении литералом 3.14159. В процессе вычисления выражения значение переменной MyIntU преобразуется в 5.0, к которому прибавляется 3.14159.
Преобразование типа при вычислениях арифметических выражений применяется к копиям значений образующих выражение подвыражений. В процессе преобразования типов результаты преобразований подвыражениям не присваиваются.
unsigned int MyIntU = 5;
MyIntU = MyIntU + 3.14159;
Здесь имеют место два последовательных преобразования:
По ходу вычисления выражения значение переменной MyIntU расширяется до double и к расширенной копии значения 5.0 прибавляется 3.14159. После этого результирующее значение 8.14159, в соответствии с первым правилом, сужается до типа unsigned int. В результате чего получается значение 8, которое и присваивается переменной MyIntU.
Указатель на любой не являющийся константой тип можно присваивать указателю типа void*. Этот указатель способен адресовать объекты любого типа данных. Он используется всякий раз, когда неизвестен тип объекта.
int iVal;
int *p_iVal = 0;
char *p_chVal = 0;
void *p_Val;
const int *pc_iVal = &iVal;
p_Val = p_iVal;
p_Val = p_chVal;
// ПРАВИЛО 5 выполняется...
p_Val = pc_iVal;
//Ошибка: pc_iVal - указатель на константу.
const void *pcVal = pc_iVal;
/*
А здесь всё хорошо! Указателю на константу присвоен указатель на константу.
*/
Перед операцией разыменования указатель типа void* нужно явно преобразовать в указатель на конкретный тип, поскольку в этом случае отсутствует информация о типе, подсказывающая транслятору способ интерпретации битовой последовательности, представляемой указателем:
char *p_chValName = "Marina";
p_Val = p_chValName;
p_chVal = (char*)p_Val; /*Явное приведение.*/
Механизм неявных преобразований может быть отключён посредством явного указания в тексте программы требуемого преобразования типов.
Так, модификация ранее рассмотренного примера
MyIntU = MyIntU + (int)3.14159;
отключает механизм неявных преобразований и при вычислении значения переменной производится лишь одно преобразование типа, которое заключается в сужении типа значения литерала 3.14159.
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include<iostream> using namespace std;
class money { private:
double m;
public:
money(double tm) { m = tm; }
void show() { cout << "GB to USA : " << m << endl; }
double mGet() { return m; }
};
class sterling { private:
int p; int sh; int pen;
public:
sterling(int tp, int tsh, int tpen) { p = tp; sh = tsh; pen = tpen; }
sterling (money temp) { pen = temp.mGet() * 100 / 20.83 + 0.5; p = pen / 240 + 0.5; sh = (pen - p * 240) / 12 + 0.5; pen = pen - p * 240 - sh * 12 + 0.5; }
void show() { cout << "USA to GB : " << p << "." << sh << "." << pen << endl; }
operator money() const { double tm; tm = static_cast<int>((p * 20 * 12 + sh * 12 + pen) * 20.83 + 0.5); tm /= 100; return money(tm); }
};
int main() { sterling s1(1, 0, 0); money m1 = s1; m1.show();
money m2(50.00); sterling s2 = m2; s2.show();
system("pause"); return 0; } |
13. Операторы new и delete.