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

Шаблоны

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

template <class A, class B>

class Entity {

A var1;

B var2;

};

Шаблоны классов используются так же, как и классы, с тем отличием, что для них обязательно указываются типы переменных, которые необходимо использовать в качестве параметров шаблона. Например, для того, чтобы объявить переменную e класса Entity с параметрами типа int и float необходим следующий программный код:

Entity<int, float> e;

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

Далее рассмотрим второй вид шаблонов: шаблон функции. Шаблон функции, как и шаблон класса, имеет один или несколько параметров, которые могут использоваться в теле функции подобно типам. В ходе компиляции из шаблона функции конструируются функции – по одной для каждого сочетания параметров шаблона, встреченного в исходном тексте программы. При этом параметры шаблона заменяются на конкретные типы по всему телу функции. Например, шаблоном функции на языке C++ является:

template <class T>

T Mid (T x, T y) { return (x+y)/2; }

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

float x=Mid<float> (a, b);

В ходе компиляции для этого шаблона могут быть созданы, например, следующие функции:

Сравнение объектно-ориентированных языков

C++

Множественное наследование

Типы переменных

Отсутствие интерфейсов

Статическая таблица виртуальных методов

Именование конструкторов, деструкторов

Конструктор копирования

Объявление динамических методов, абстрактных, постоянных

Личное и общее наследование

Структуры как классы

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

Шаблоны

Java

Динамическая таблица виртуальных методов

Синхронизированные методы

C#

call back функции

Лекция №2

  1. Классы

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

Надо заметить при этом, что при правильной разработке программы класс (в отличие от модуля) является формализацией некоторой концепции (например, "матрица"), а не просто изолированным фрагментом программы.

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

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

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

Рассмотрим в качестве примера концепцию "строка символов".

class Str

{

char str[80];

unsigned char att;

int row, col;

public:

void setStr (char*);

void setAtt (unsigned char);

void setCoord (int, int);

void printStr (int=0, int=0);

};

Метка public, которая может присутствовать в декларации класса, в нашем примере делит его тело на две части – "личную" (private) и "общую" (public). Доступ к данным-членам класса и функциям-членам класса, находящимся в личной части, возможет лишь через функции-члены класса, находящиеся в общей части.

В декларации класса могут присутствовать также метки private и protected. Они делят тело класса в общем случае на три части, различающиеся по уровню доступа. К членам класса, находящимся в личной части, доступ возможен только с помощью функций-членов класса и так называемых привилегированных (friend) функций, к общим же членам класса можно обратиться из любой функции программы. Метка protected ведет себя подобно private с отличиями для производных классов, что будет рассмотрено позже.

Функции-члены класса могут вызываться только после того, как в программе создан объект соответствующего типа; они вызываются только для конкретного объекта, и выполняемые ими действия никак не могут влиять на состояние других объектов этого типа.

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

class Str

{

// …

void setStr (char *s) { strcpy (str, s); }

// …

};

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

class Str

{

// …

void setStr (char *s) ;

// …

};

void Str::setStr (char*)

{

strcpy (str, s);

}

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

Данные-члены класса почти аналогичны элементам структур языка C. Различия состоят только в следующем: во-первых, с именем члена класса сопоставлен один из трех уровней доступа, а во-вторых, в C++ могут существовать статические данные-члены класса.

Сама по себе декларация класса не приводит к резервированию места в памяти. Место в памяти выделяется при создании объектов указанного класса. При вызове функции-члена класса ей в качестве неявного аргумента передается указатель на тот объект, для которого она вызвана; он всегда имеет имя this и его не нужно (и невозможно) определять явно.

В C++ предусмотрены специальные функции-члены класса, которые в большинстве случаев вызываются не программистом, а компилятором языка. Функции, выполняющие инициализацию объектов абстрактных типов, называются конструкторами, а функции, "уничтожающие" такие объекты – деструкторами. Невозможно ни создать объект абстрактного типа без вызова конструктора, ни уничтожить его без вызова деструктора. Таким образом, такой объект всегда будет инициализированным. Конструктор имеет имя, совпадающие с именем класса и не имеет возвращаемого значения. Имя деструктора начинается с символа ~, за которым следует имя класса. Деструктор также не имеет возвращаемого значения.

Хотя конструкторы и деструкторы являются функциями-членами класса, они имеют определенные отличия от обычных функций-членов класса:

а) Конструкторы и деструкторы имеют строго определенные имена.

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

в) Конструкторы могут иметь аргументы, в том числе аргументы по умолчанию; деструкторы аргументов не имеют. Конструктор класса X не может иметь аргументы типа X, хотя аргументы типа X* и X& возможны.

г) Нельзя получить адрес ни конструктора, ни деструктора.

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

Полное имя члена класса – это имя члена, которому предшествует имя класса и символ ::. Оно используется, когда необходимо отличить, например, член класса от глобального объекта с тем же именем. Перед символом :: может отсутствовать имя класса, тогда полное имя рассматривается как имя глобального объекта (или функции).

Если член класса объявлен как static, то он становится полноценным самостоятельным объектом; при этом его имя имеет область существования "класс". Все объекты, сколько бы их не было, используют заранее созданную одну-единственную копию своего статического члена. К статическим членам класса можно обратиться и как к обычным членам – по имени или указателю на созданный объект с использованием стандартной мнемоники обращения к члену класса, так и по его полному имени – даже в том случае, если в программе не создан ни один объект класса X. Статические члены класса могут иметь любой из трех уровней доступа.

Использование слова static применительно к именам функций имеет несколько другой смысл, чем его использование применительно к именам объектов. Статическая функция-член класса может быть вызвана для получения доступа к статическому данному-члену класса X, причем может не существовать объектов типа X. Для нее не определен указатель this.

  1. Переопределение операций

Для того, чтобы переопределить одну из стандартных операций языка C++ для работы с операндами абстрактных типов, программист должен написать функцию с именем operator@, где @ - обозначение этой операции. При этом в языке существуют несколько ограничений:

  1. программист не может придумывать свои символы операций;

  2. не могут быть переопределены операции: ::, *, ?:

  3. символ унарной операции не может быть использован для переопределения бинарной операции и наоборот;

  4. переопределение операций не меняет ни их приоритетов, ни порядка выполнения;

  5. при переопределении операции компилятор не делает никаких предположений о ее свойствах (коммутативность и т.п.);

  6. для переопределенных операций ++ и -- префиксная форма не отличается от постфиксной;

  7. никакая операция не может быть переопределена для операнда стандартных типов.

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

Для выполнения переопределенной унарной операции @x (или x@), где x – объект некоторого абстрактного типа Class, компилятор попробует найти либо функцию Class::operator@(void), либо ::operator@(Class); если найдены одновременно оба варианта, то выдается сообщение об ошибке. Интерпретация выражения @x осуществляется либо как x.operator@(void), либо как operator@(x).

Для выполнения переопределенной бинарной операции x@y, где x обязательно является объектом абстрактного типа Class, компилятор ищет либо функцию Class::operator@(type y), либо функцию ::operator@(Class x, type y), причем type может быть как абстрактным, так и стандартным типом. Интерпретируется выражение x@y либо как x.operator@(y), либо как operator@(x,y).

Как для унарной, так и для бинарной операций число аргументов (явных и неявных) функции operator@ () точно должно соответствовать числу операндов этой операции – быть не больше и не меньше.

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

Функции, реализующие операции =, [], (), -> должны быть членом класса.

Лекция №3