- •Предисловие
- •Введение Эволюция разработки программного обеспечения
- •Технологии программирования
- •Основные понятия объектно-ориентированного программирования
- •Инкапсуляция
- •Свойства
- •Векторные свойства
- •Создание и уничтожение объектов
- •Конструкторы
- •Деструкторы
- •Наследование
- •Свойства
- •Конструкторы и деструкторы класса-предка
- •Полиморфизм, виртуальные и динамические методы
- •Статическое перекрытие виртуальных методов
- •Виртуальное перекрытие конструкторов и деструкторов
- •Абстрактные методы
- •Области видимости
- •Перекрытие и переопределение свойств
- •Перекрытие методов доступа к свойствам
- •Приведение объектных типов, операторы as и is
- •Агрегация
- •События
- •Процедурный тип
- •Создание события
- •Инициаторы события
- •Делегирование
- •Внутреннее устройство объекта
- •Указатели на класс
- •Виртуальные конструкторы
- •Методы класса
- •Обработка исключительных ситуаций
- •Операторы try...Except и try...Finally
- •Исключительные ситуации как объекты
- •Перегрузка методов
- •Перегрузка виртуальных методов
- •Параметры по умолчанию
- •Основы объектно-ориентированного анализа и проектирования
- •Объектно-ориентированная модель
- •Классы и объекты
- •Заключение Применение объектно-ориентированного программирования
- •Библиографический список
Перегрузка виртуальных методов
Перегружаемые методы не антагонистичны виртуальным и динамическим. И виртуальные и динамические методы могут быть перегружаемыми.
Если метод объявлен и как виртуальный и как перегружаемый, это означает, что в части классов-потомков он может быть перекрыт как виртуальный, а в другой части как перегружаемый:
procedure Test(i: integer); overload; virtual;
Виртуальное перекрытие метода, объявленного и как виртуальный и как перегружаемый, выполняется как обычно:
procedure Test(i: integer); override;
Поскольку операция перегрузки метода по своей природе является статической, а не виртуальной, для перегружаемых виртуальных методов в классе-потомке в дополнении к директиве overload используется директива reintroduce:
procedure Test(s: string); reintroduce; overload;
Действие операции перегрузки при этом ничем не отличается от перегрузки обычного статического метода.
Например:
Interface Type T1 = class(TObject) procedure Some(i: integer); overload; virtual; end; T2 = class(T1) procedure Some(s: string); reintroduce; overload; end; ...
Implementation Procedure Test; var SomeObject: T2; BadObject: T1; begin SomeObject := T2.Create; //создаем объект T2 SomeObject.Some('Hello!'); //будет вызван T2.Some(s: string) SomeObject. Some(7); //будет вызван T1.Some(i: integer)
BadObject := T2.Create; //создаем объект T2 BadObject. Some(7); //будет вызван T1.Some(i: integer)
//BadObject. Some('Hello!'); //вызовет ошибку, так как //перегрузка является статической операцией и //для класса-предка T1 не определена ... SomeObject.Free; BadObject.Free; end;
Сравним действие директивы reintroduce отдельно и вместе с директивой overload.
Первый вариант:
Interface Type TOb1 = class ... procedure Met; overload; virtual; end;
TOb2 = class(TOb1) ... procedure Met(s: string); reintroduce; end;
Implementation Procedure Test; var Ob: TOb2; begin Ob := TOb2.Create; //Ob.Met; //вызовет ошибку компиляции, так как метод //статически перекрыт Ob.Met(‘xxx’); Ob.Free; end;
В этом случае происходит статическое перекрытие виртуального метода. Перегрузки нет, поэтому класс-потомок имеет единственный метод Met c параметром типа string.
Второй вариант:
Type TOb1 = class ... procedure Met; overload; virtual; end;
TOb2 = class(TOb1) ... procedure Met(s: string); reintroduce; overload; end;
Implementation Procedure Test; var Ob: TOb2; begin Ob := TOb2.Create; Ob.Met; //не вызовет ошибки компиляции Ob.Met(‘xxx’); Ob.Free; end;
В этом случае происходит перегрузка метода Met. Поэтому класс-потомок имеет два перегружаемых метода с именем Met. У первого нет параметров, второй имеет единственный параметр типа string.
На перегрузку методов наложены некоторые ограничения: нельзя перегружать методы, которые объявлены как read и write для свойства. И нельзя перегружать методы, находящиеся в области видимости published.
При вызове перегружаемых методов, так же как и обычных, допустимо использование типов, не идентичных объявленным, но совместимых с ними. Например, при объявлении формального параметра подпрограммы типа real можно передавать фактический параметр типа integer. Но иногда это делает допустимым вызов нескольких методов. В таком случае, когда это возможно сделать без двусмысленности, будет вызван тот метод, чьи параметры имеют тип с наименьшим диапазоном, вмещающим фактический параметр. Например:
Interface type TOb = class private i: real; j: integer; ... public procedure SetData(AValue: real); overload; procedure SetData(AValue: integer); overload; ... end;
implementation ...
procedure Test; var T: TOb; b: byte; begin T := TOb.Create; ... b := 1; T.SetData(b); //будет вызван метод с параметром //типа integer ... T.Free; end;
Тип byte совместим как с типом real, так и integer. Но тип integer имеет меньший диапазон, поэтому будет вызван метод имеющий параметр типа integer.
В Object Pascal перегружать можно не только методы классов, но и обычные процедуры и функции. Назначение и синтаксис перегрузки процедур и функций те же, что и при перегрузке методов.
Пример:
Implementation Procedure ShowResult(i: integer); overload; Begin ShowMessage(IntToStr(i)); End;
Procedure ShowResult(s: string); overload; Begin ShowMessage(s); End;
Основой для принятия решения, какую процедуру или функцию вызвать служит, как и для методов, тип параметров.
-
Перегрузка имен функций в С++
Как уже было сказано, если несколько функций выполняет одно и то же действие над данными разных типов, то удобнее дать всем этим функциям одинаковые имена. Служебное слово overload при этом может не использоваться:
int sort(char **str); int sort(int*, int); int sort(double*, double);
Перегружаться могут как свободные функции, так и функции-элементы класса:
class CMyClass { int min(int, int); int min(int, int, int); double min(double, double); char min(char, char); };
При вызове нужная функция будет выбрана по числу и типу аргументов.
При выборе нужной функции используются правила сопоставления параметров. Правила применяются в следующем порядке по убыванию их приоритета:
-
точное сопоставление: сопоставление произошло без всяких преобразований типа или только с неизбежными преобразованиями (например, имени массива в указатель, имени функции в указатель на функцию и типа T в const T);
-
сопоставление с использованием стандартных целочисленных преобразований (т.е. char в int, short в int и их беззнаковых двойников в int), а также преобразований float в double;
-
сопоставление с использованием стандартных преобразований (например, int в double, unsigned в int);
-
сопоставление с использованием пользовательских преобразований.
-
сопоставление с использованием многоточия ... в описании функции.
Если найдены два сопоставления по самому приоритетному правилу, то вызов считается неоднозначным.
-
Перегрузка операций в С++
Язык С++ позволяет переопределить действие большинства операторов для классов. Например таких, как операторы «+», «-», присваивание и т.п.
Ограничения:
-
нельзя изменить количество параметров;
-
нельзя изменить приоритет;
-
нельзя определить новые операции;
-
нельзя использовать параметры по умолчанию;
-
нельзя переопределять операции « . », « ->», « ::»;
-
синтаксис не позволяет различить операции пре- и пост-инкремента и декремента.
Имя операции функции при ее определении представляет собой ключевое слово operator, за которым следует символ операции. Это должен быть либо метод, либо дружественная функция соответствующего класса. Бинарный оператор – функция-элемент с одним аргументом или дружественная функция с двумя аргументами. Унарный оператор – функция-элемент без аргументов или дружественная функция с одним аргументом. Возвращаемый тип соответствует классу.
Вызывается эта функция при использовании соответствующего символа операции с объектами данного класса, хотя может вызываться и с помощью явного указания имени.
Пример перегрузки операции с помощью дружественной функции:
class ССomplex //класс комплексных чисел { double real, imag; public: ССomplex(){real = 0;); ССomplex(double r, double i); //перегружаем оператор сложения для комплексных чисел friend ССomplex operator+(ССomplex c1, ССomplex c2); };
ССomplex operator+(ССomplex c1, ССomplex c2) //реализация оператора сложения { return ССomplex(c1.real+c2.real, c1.imag+c2.image); };
Пример перегрузки операции с помощью функции-элемента:
class ССomplex //класс комплексных чисел { double real, imag;
public: ССomplex(){real = 0;); ССomplex(double r, double i); //перегружаем оператор сложения для комплексных чисел ССomplex operator+(ССomplex c); };
ССomplex ССomplex::operator+(ССomplex c) //реализация оператора сложения { return ССomplex(real+c.real,imag+c.image); };
Пример использования перегруженной операции:
ССomplex c1(0,1), c2(1,0), c3; c3 = c1 + c2; //выполнятся два оператора – сложение //и присваивание
Часто нужно, чтобы операции могли иметь в качестве операндов объекты другого типа, например – добавление к комплексному числу обычного. Один из путей добиться этого – перегрузка функций-элементов или дружественных функций:
class ССomplex { double real, imag; public: ССomplex(){real = 0;); ССomplex(double r, double i); friend ССomplex operator+(ССomplex c1, ССomplex c2); friend ССomplex operator+(double c1, ССomplex c2); friend ССomplex operator+(ССomplex c1, double c2); };
Правила сопоставления параметров при перегрузке операторов совпадают с правилами для перегрузки функций.
Функция, перегружающая оператор для класса, должна либо быть элементом, либо иметь хотя бы один аргумент типа класс. Функция оператора, которая должна использовать в качестве первого операнда базовый тип не может быть функцией-элементом, поскольку:
ССomplex c1(0,1), c2; c2 = c1 + 2; // может интерпретироваться как с1.operator+(2) //c2 = 2 + c1;//должна интерпретироваться как 2.operator+(c1) //но у типа int нет оператора сложения для класса ССomplex
В этом случае можно использовать только дружественные функции.
Альтернативой использованию отдельных функций для всех вариантов аргументов операторов является объявление конструкторов, которые создают данные типа класс на основе базовых типов:
class ССomplex { double real, imag; public: ... friend ССomplex operator+(ССomplex c1, ССomplex c2); ССomplex(double r){real = r;); };
Семантика инициализации и передачи аргументов функции такова, что эти конструкторы будут вызываться автоматически, вызывать их явно можно, но не обязательно:
ССomplex a, b; ... a = b*2; // будет интерпретироваться как //a = operator+(b,ССomplex(double(2));
Особое значение имеет оператор присваивания. В отличие от операторов арифметических преобразований он определен для объектов типа классов и означает побитовое копирование полей. Иногда это не то, что хотелось бы. Особенно в отношении указателей – мы получим два объекта, ссылающихся на одну и ту же область памяти. (Подобная проблема возникает и при использовании конструкторов копирования.) Поэтому, для классов, содержащих указатели, оператор присваивания рекомендуется перегружать:
class С1 { int a; ... //указатели
public: ... С1(const С1& с); //конструктор копирования void operator=(С1& с); //оператор присваивания };
С1::С1(const С1& с) { a = с.a; ... //работа с указателями };
void С1::operator=(С1& с) { a = с.a; ... //работа с указателями };
Можно определить оператор преобразования для приведения объектов типа класс к базовым типам:
class CTiny { ... public: operator int() {return ...;}; };
Подробнее это рассматривалось в теме «Приведение объектных типов в С++».
Для того чтобы обращаться к объекту класса (одному) как к массиву, перегружают оператор [].