Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Алгоритмические языки и программирование. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
12.08.2026
Размер:
997 Кб
Скачать

Описание экземпляра класса состоит из имени класса и имени экземпляра. После описпния можно менять заначение полей класса и вызывать методы, объявленные с модификатором public. Доступ к полям и методам экземпляра осуществляется с помощью оператора «.».

Поскольку эземпляр имеет составной тип, для него недопустимо использование операторов ввода «>>» и вывода «<<». Для ввода и вывода требуется работать с отдельными полями либо описывать специальные методы. Также экземпляры нельзя сравнивать, т. е. недопустимо использование операторов «==» и «<».

Можно объявлять массив экземпляров класса, передавать и возвращать экземпляр из функции.

Метод

Отличие метода экземпляра от функции в том, что в метод автоматически передается неявный параметр this, который имеет тип указателя на класс. Этот указатель служит для того, чтобы знать, для какого экземпляра вызван данный метод. Благодаря этому указателю внутри метода есть доступ к полям экземпляра.

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

Конструктор

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

41

даже типа void. Конструкторов может быть сколько угодно, они должны отличаться типом или количеством параметров. Основными видами конструкторов являются конструктор по умолчанию и конструктор копирования.

Конструктор по умолчанию не имеет параметров, для его вызова не нужно писать круглые скобки. Он необходим, если в коде программы создается массив экземпляров класса.

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

Деструктор

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

Уровни доступа

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

Конструкторы и деструкторы должны быть открыты, для этого используется ключевое слово public – открыт для внешнего использования.

Для работы с закрытыми данными создаются специальные методы для чтения и установки значений. Эти методы называются методами доступа.

42

Перегрузка операторов

В языке C++ есть способ организовать методы в виде оператора. Например, работая с комплексными числами, удобнее использовать привычные символы "+", "*", чем пользоваться методами "Sum", "Mult".

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

– программа не должна действовать неожиданно для программиста. Для того чтобы соблюдать этот принцип, нужно при перегрузке метода ориентироваться на действие стандартных операторов и действовать аналогично. Так, описывая операторы "+", "*", не нужно закладывать в них смысл, несовместимый с операциями сложить и умножить.

Описание перегруженного оператора аналогично описанию метода, за исключением того, что используется ключевое слово operator и имя метода состоит из символа операции. В перегруженных операторах часто используется указатель на экземпляр this.

Не все операторы разрешается перегружать. Допустимы перегрузка арифметических, логических, сравнения, присвоения, индексирования "[]", вызова "()", а также операторов преобразования типа.

При перегрузке оператора присвоения нужно твердо понимать, что знак "=" не во всех случаях обозначает присвоение.

Пример:

Complex C1, C2 = C1;

Здесь строка C2 = C1 эквивалентна строке C2 (C1) и обозначает вызов конструктора копирования.

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

Complex C1, C2, C3; C1= C2 = C3;

43

Потоки

Одним из наиболее удобных операторов является оператор вывода "<<". Его также можно перегружать. Перегрузим оператор вывода для комплексных чисел.

Здесь возникает проблема, связанная с тем, что оператор, так же как метод, должен принадлежать экземпляру класса. Компилятор считает, что оператор принадлежит экземпляру, стоящему слева от знака оператора. Таким образом, оператор вывода должен быть членом класса ostream. Но мы не можем менять библиотечный класс, поэтому опишем свободный оператор – оператор, не принадлежащий классу. Проблема в том, что у свободного оператора не будет доступа к полям класса. Используем механизм, позволяющий функциям и классам получать доступ к закрытым членам некоторого класса. Для этого заголовок оператора скопируем в тело класса Complex и добавим в начало ключевое слово friend. Говорят, что оператор вывода стал другом класса Complex. После этого оператор имеет доступ к закрытым членам класса.

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

Аналогичным образом можно перегрузить оператор чтения из поток.

Шаблоны

Шаблон-функция

Рассмотрим задачу вычисления модуля. Если мы работаем с целыми числами, то функция вычисления модуля выглядит так:

int abs(int x){ if(x < 0){

44

return -x;

}

return x;

}

Для вещественных чисел нужно писать отдельную реализацию: double abs(double x){

if(x < 0){

return -x;

}

return x;

}

Для чисел типа char, long, short также нужны отдельные реализации, отличающиеся только типом аргумента и возвращаемого значения. Основная проблема такого подхода – много одинакового кода. Эта проблема называется дублированием кода и считается одной из самых неприятных для его сопровождения. Для решения проблемы дублирования кода в языке C++ существует специальный механизм, называемый шаблоном функции. Для функции модуля шаблон выглядит так:

template<typename T> T abs(T x){

if(x < 0){

return -x;

}

return x;

}

Ключевое слово template говорит компилятору, что далее идет шаблон, ключевое слово typename обозначает аргумент шаблона по имени T (вместо него также допустимо ключевое слово class). По описанию шаблона функция не создается, это происходит только при вызове функции:

45

int main(){

cout << abs(-10) << endl; cout << abs(-10.5); return 0;

}

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

Рассмотрим пример с несколькими шаблонными аргументами: template<typename T>

int find_element(T Arr[], int Size, T element){ for(int i = 0; i < Size; ++i){

if(element == Arr[i]){ return i;

}

}

return -1;

}

Функция-шаблон ищет в массиве заданный элемент, возвращает номер в массиве или -1, если его нет. Шаблонный параметр T отвечает за тип элементов.

Пример использования функции: int main(){

int Array[] = {1, 2, 3, 4};

int Size = sizeof(Array) / sizeof(Array[0]); cout << find_element(Array, Size, 5) << endl; cout << find_element(Array, Size, 2) << endl;

double Array1[] = {1.5, 2.1, 3.2, 4.4}; Size = sizeof(Array1) / sizeof(Array1[0]);

cout << find_element(Array1, Size, 1.5) << endl;

46

cout << find_element(Array1, Size, 2.0) << endl; return 0;

}

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

template<typename TElement, typename TSize>

int find_element(TElement Arr[], TSize Size, TElement element){ for(int i = 0; i < Size; ++i){

if(element == Arr[i]){ return i;

}

}

return -1;

}

Пример использования функции: int main(){

int Array[] = {1, 2, 3, 4};

unsigned long long Size = sizeof(Array) / sizeof(Array[0]); cout << find_element(Array, Size, 5) << endl;

cout << find_element(Array, Size, 2) << endl; return 0;

}

Макрос

Результат, аналогичный использованию шаблонов, может быть получен с помощью макроопределений (макросов):

#define abs(x) x<0 ? -(x) : (x) int main(){

cout << abs(-1.123); return 0;

}

47

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

Замечания

Как лучше начать работу с шаблоном? Сначала рекомендуется написать обычную функцию, отладить ее на примерах и только затем сделать ее шаблоном.

Когда будет работать шаблон? Для того чтобы проверить, будет ли шаблон работать с некоторым типом, нужно отследить используемые в шаблоне операции и убедиться, что все они поддерживаются требуемым типом.

Шаблон-класс

Язык C++ позволяет описывать также классы с шаблонными аргументами – типами. Наиболее ярким примером применения этого подхода является библиотека STL, одним из основных компонентов которой являются классыконтейнеры, предназначенные для хранения элементов произвольных (в том числе и пользовательских) типов. Рассмотрим контейнер, реализующий интерфейс контейнера стек:

template<typename T> class Stack{

private:

enum { MaxSize = 1000}; T Array[MaxSize]; unsigned int Size;

public:

Stack(){

Size = 0;

}

48

void Push(T E){

if(Size == MaxSize){ return;

}

Array[Size++] = E;

}

T Pop(){

if(Size == 0){ return -1;

}

return Array[--Size];

}

bool Empty(){

return Size == 0;

}

};

Для описания шаблона класса также используется ключевое слово template с шаблонным аргументом. Для задания максимального возможного размера стека используется безымянный перечисляемый тип enum. Методы Push и Pop отвечают за добавление и удаление элемента.

Пример использования контейнера: int main(){

Stack<int> S; int E;

while(cin >> E){ S.Push(E);

}

cin.clear(); cout << endl;

while(!S.Empty()){

cout << S.Pop() << endl;

}

49

Stack<double> S1; double E1; while(cin >> E1){

S1.Push(E1);

}

cout << endl; while(!S1.Empty()){

cout << S1.Pop() << endl;

}

return 0;

}

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

template<typename T = int> class Stack{

};

Исключения

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

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

50

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]