Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Методичка ООП.doc
Скачиваний:
23
Добавлен:
08.11.2018
Размер:
1.4 Mб
Скачать

Перегрузка виртуальных методов

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

Если метод объявлен и как виртуальный и как перегружаемый, это означает, что в части классов-потомков он может быть перекрыт как виртуальный, а в другой части как перегружаемый:

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;

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

    1. Перегрузка имен функций в С++

Как уже было сказано, если несколько функций выполняет одно и то же действие над данными разных типов, то удобнее дать всем этим функциям одинаковые имена. Служебное слово 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);

  • сопоставление с использованием пользовательских преобразований.

  • сопоставление с использованием многоточия ... в описании функции.

Если найдены два сопоставления по самому приоритетному правилу, то вызов считается неоднозначным.

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

Язык С++ позволяет переопределить действие большинства операторов для классов. Например таких, как операторы «+», «-», присваивание и т.п.

Ограничения:

  • нельзя изменить количество параметров;

  • нельзя изменить приоритет;

  • нельзя определить новые операции;

  • нельзя использовать параметры по умолчанию;

  • нельзя переопределять операции « . », « ->», « ::»;

  • синтаксис не позволяет различить операции пре- и пост-инкремента и декремента.

Имя операции функции при ее определении представляет собой ключевое слово 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 ...;}; };

Подробнее это рассматривалось в теме «Приведение объектных типов в С++».

Для того чтобы обращаться к объекту класса (одному) как к массиву, перегружают оператор [].