Перегрузка операций в С++

Тип работы:
Реферат
Предмет:
Программирование


Узнать стоимость

Детальная информация о работе

Выдержка из работы

Содержание

Введение

1. Понятие перегрузки операций

2. Перегрузка различных операций

3. Пpимеp программы с перегрузкой операций

4. Полиморфизм

5. Правило isA ()

Заключение

Список литературы

Введение

Тип переменной определяет набор значений, которые она может хранить, а также набор операций, которые можно выполнять над этой переменной. Например, над значением переменной типа int ваша программа может выполнять сложение, вычитание, умножение и деление. С другой стороны, использование оператора плюс для сложения двух строк лишено всякого смысла. Когда вы определяете в своей программе класс, то по существу вы определяете новый тип. А если так, C++ позволяет вам определить операции, соответствующие этому новому типу.

Перегрузка оператора состоит в изменении смысла оператора (например, оператора плюс (+), который обычно в C++ используется для сложения) при использовании его с определенным классом. В данном уроке вы определите класс string и перегрузите операторы плюс и минус. Для объектов типа string оператор плюс будет добавлять указанные символы к текущему содержимому строки. Подобным образом оператор минус будет удалять каждое вхождение указанного символа из строки.

Перегрузка операторов может упростить наиболее общие операции класса и улучшить читаемость программы.

перегрузка доопределение оператор полиморфизм

1. Понятие перегрузки операций

В С++ используется множество операций, среди которых арифметические операции (+, *, -, / и т. п.), логические (> >, &, | и т. п.), операции отношений (==, >, <, <= и т. п.).

На все операции языка С++, кроме операций объявления, new, delete, и других операций, связанных с определением производных типов данных, распространяется свойство перегружаемости, т. е. возможности использования в различных случаях для одной и той же операции операндов различных типов. Так, например, операция сложения позволяет «смешивать» типы int, double, float и другие в одном выражении. Такой полиморфизм обеспечен внутренними механизмами языка С++.

В том случае, если программист определил какие-либо свои классы, он может сам решить, что будут означать операции языка С++, когда они используются над объектами этих классов, тем самым расширив число типов данных, для которых можно использовать ту или иную операцию. Переопределять или доопределять операции допускается не только для знаков операций, введенных самим программистом, но и для знаков, определенных в языке изначально (таких, как «+» или «*»).

Перегрузка смысла операций осуществляется посредством определения соответствующих функций, определяющих алгоритм выполнения операции над объектами классов. Определение этих функций полностью аналогично определению методов класса, за исключением того, что в качестве имени функции указывается строка operator@, где @ - знак переопределяемой (чаще, доопределяемой) операции.

Например, можно доопределить операцию «+» для текстовых строк как операцию конкатенации (слияния строк) следующим образом.

class String {

protected:

char *PointerToString; // Указатель на строку

int StringSize; // Длина строки

public:

String (char *); // Конструктор

~String (); // Деструктор

void Print ();

String operator+ (String); // Перегрузка операции

}; // «+»

//----------------------------------------------------//

// Определение смысла операции «+» как //

// конкатенации текстовых строк //

//----------------------------------------------------//

String String: :operator+ (String One)

{

// Инициализация объекта Result

// (он будет результатом конкатенации)

String Result (««);

int Length;

// Копирование первой строки в результурующую

strcpy (Result. PointerToString, One. PointerToString);

// Установка длины строки объекта, для которого

// будет вызвана операция

Length = strlen (One. PointerToString);

// Добавление второй строки вслед за первой

memcpy (Result. PointerToString, PointerToString+Length,

strlen (PointerToString));

// Установка общей длины строки в результате

Result. StringSize = strlen (Result. PointerToString) + 1;

Result. PointerToString[Result. StringSize] = '';

return (Result);

}

2. Перегрузка различных операций

Для многих операций С++ существуют свои особенности при перегрузке (доопределении). Так унарные операции переопределяются с описанием функции операции без аргумента, например:

class A { //…

A operator- - () { // текст функции для операции «- -» };

//…

};

Соответственно доопределение бинарной операции использует описание функции операции с одним аргументом, т.к. вторым является объект, для которого вызвана операция. Следует также помнить, что операция присваивания «=» может перегружаться только объявлением метода без описателя static. То же относится к операциям «()» и «[ ]».

3. Пpимеp программы с перегрузкой операций

Пусть необходимо запрограммировать переопределение операций для объекта «строка», где операция «+» будет означать конкатенацию строк.

/********************/

/* Operations for */

/* Class */

/* String */

/********************/

/* v. 25. 12. 2002 */

#include «iostream. h»

#include «string. h»

class String {

protected:

char *PointerToString; // Указатель на строку

int StringSize; // Длина строки

public:

String (char *);

~String ();

void Print ();

String operator+ (String);

};

// Конструктор

String: :String (char * Str)

{

StringSize = strlen (Str);

PointerToString = new char [StringSize + 1];

strcpy (PointerToString, Str);

}

// Деструктор

String: :~String ()

{

StringSize = 0;

delete PointerToString;

PointerToString = NULL;

}

// Переопределение операции

String String: :operator+ (String One)

{

String Result (««);

int Length;

strcpy (Result. PointerToString, One. PointerToString);

Length = strlen (One. PointerToString);

memcpy (Result. PointerToString, PointerToString+Length,

strlen (PointerToString));

Result. StringSize = strlen (Result. PointerToString) + 1;

Result. PointerToString[Result. StringSize] = '';

return (Result);

}

// Определение функции вывода объекта

void String: :Print ()

{

cout < < PointerToString;

}

// Программа, проверяющая работоспособность операции «+»

void main ()

{

String A («111»);

A. Print ();

String B («222»);

B. Print ();

String C (««);

C = A + B;

C. Print ();

}

4. Полиморфизм

В одном и том же классе можно определить или описать любое число методов с одинаковым именем. При этом важно лишь то, чтобы формальные параметры одноименных функций-методов как-нибудь различались. Этот случай обычно называют перегрузкой меетодов-функций.

Однако и подкласс (производный класс) может содержать функцию-метод с именем, совпадающим с уже присутствующим в базовом классе. Такой случай назвают полиморфизмом. Например, конструкции типа SubClass из нижеследующего фрагмента программы могут иметь собственные методы для печати print ().

struct SubClass: public Base {

SubClass (char* n): Base (n) { }

void print () {

cout< < «Это компонент подкласса=»

< < name

< < endl;

}

};

void main () {

Base aBaseObject;

SubClass aSubClassObject;

}

Ссылка на print () по умолчанию относится к методу самого низкого уровня, который применим в этом случае. В приведенном примере aBaseObject. print () ссылается на Base: :print (), тогда как aSubClassObject. print () ссылается на SubClass: :print (). Программа может вызывать конкретную функцию, если задано полностью квалифицированное имя, например aSubClassObject. Base: print (). Методы подкласса могут таким же способом ссылаться на методы базового класса. Вместе с тем программа не может ссылаться на aBaseObject. SubClass: print (), поскольку SubClass: :print () не является компонентом класса Base.

Если метод подкласса перекрывает своим новым определением метод базового класса, то могут возникнть проблемы. Рассмотрим, что будет означать для функции fn () определение нового метода print ().

void fn (Base& aBaseRef) {

cout< <"Из fn (): «;

aBaseRef. print ();

}

Объект aBaseRef теперь объявлен как относящийся к классу Base. Ссылка на aBaseRef. print () всегда будет фактически относиться к Base: :print (), но вместо объекта базового класса будет использован объект подкласса. Таким образом, fn () можно вызывать либо как fn (aSub ClassObject), либо как fn (aBaseClassObject). Вызов print () соответственно, приведет к вызову функции SubClass: print ().

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

C++ оставляет использование позднего связывания на усмотрение программиста. По умолчанию, даже при наличии неоднозначности имен, транслятором принимается решение о раннем связывании имен программы. Следовательно, по способу записи определений классов fn () всегда будет давать обращение к Base: :print (). Позднее связывание здесь не выполняется. Добавление в определение функции ключевого слова virtual делает эту функцию полиморфной.

virtual void print () {

cout < < «Это компонент базового класса=»

< < name

< < endl;

}

Вызовы virtual print () используют позднее связывание.

Виртуальная функция не может быть объявлена как static (статическая). При отсутствии объекта Turbo C++ не может выполнить позднее связывание.

Объявление виртуальной функции-метода автоматически делает виртуальными все функции с этим именем в подклассах.

Если метод в подклассе с тем же именем принимает другие аргументы, то никакого полиморфизма нет. Рассмотрим пример:

#include < iostream. h>

struct Base {

virtual void print () {

cout < < «Это объект базового класса»

< < endl;

}

};

struct SubClass: public Base {

virtual void print (char* c) {

cout < < «Это объект подкласса «

< < c

< < endl;

}

};

void fn (Base& obj) {

obj. print ();

obj. print («Relative object»); //ошибка компилятора #1

}

void main () {

SubClass aSubClass;

aSubClass. print (); //ошибка компилятора #2

aSubClass. print («aSubClass»);

fn (aSubClass);

}

Оба класса, Base и SubClass, содержат функции print (); однако, эти две функции имеют разные аргументы. Компилятор C++ не позволит сделать вызов Base: :print () с неверными типами аргументов, что приведет к ошибке, с соответствующим сообщением от компилятора. Аналогичная ситуация возникнет и во втором случае, когда компилятор встретит вызов SubClass: print ().

5. Правило isA ()

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

Для решения этой проблемы программист должен определить метод идентификации, обычно называемый isA (). Это виртуальное правило возвращает константу, которая является уникальной для каждого типа подкласса. Можно перевести слово isA как «являющийся (под)классом определенного типа». Рассмотрим следующую некомпонентную версию функции print ().

#include < iostream. h>

struct Base {

enum ClassType {BASE, SUBCLASS};

virtual ClassType isA () {return BASE; }

}

void print (Base& obj) {

if (obj. isA ()==Base: :BASE)

cout< < «Это объект базового классаn»;

else

if (obj. isA ()==Base: :SUBCLASS)

cout< < «Это объект подклассаn»;

else

cout< < «Это неизвестный тип объектаn»;

}

void fn (Base& obj) {

print (obj);

}

void main () {

Base aBaseClass;

SubClass aSubClass;

fn (aBaseClass);

fn (aSubClass);

}

На выходе этой программы будет следующее:

Это объект базового класса

Это объект подкласса

Заключение

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

Чтобы перегрузить оператор, вы должны определить класс, которому оператор будет назначен.

Когда вы перегружаете оператор, перегрузка действует только для класса, в котором он определяется. Если программа использует оператор с неклассовыми переменными (например, переменными типа int или float), используется стандартное определение оператора.

Чтобы перегрузить оператор класса, используйте ключевое слово C++ operator для определения метода класса, который C++ вызывает каждый раз, когда переменная класса использует оператор.

C++ не позволяет вашим программам перегружать оператор выбора элемента (.), оператор указателя на элемент (. *), оператор разрешения области видимости (: :) и условный оператор сравнения (?:).

Список литературы

1. М. Эллис, Б. Строуструп. Справочное руководство по языку C++ с комментариями: Пер. с англ. — Москва: Мир, 1992. 445с.

2. Стенли Б. Липпман. C++ для начинающих: Пер. с англ. 2тт. — Москва: Унитех; Рязань: Гэлион, 1992, 304−345сс.

3. Бруно Бабэ. Просто и ясно о Borland C++: Пер. с англ. — Москва: БИНОМ, 1994. 400с.

4. В. В. Подбельский. Язык C++: Учебное пособие. — Москва: Финансы и статистика, 1995. 560с.

5. Ирэ Пол. Объектно-ориентированное программирование с использованием C++: Пер. с англ. — Киев: НИИПФ ДиаСофт Лтд, 1995. 480с.

6. Т. Фейсон. Объектно-ориентированное программирование на Borland C++ 4. 5: Пер. с англ. — Киев: Диалектика, 1996. 544с.

ПоказатьСвернуть
Заполнить форму текущей работой