Алгоритмические языки и программирование. Учебное пособие
.pdfявляется потомком другого), «реализован посредством» (один класс реализован посредством другого, например класс-оболочка), «композиция» (классы описывают объекты, один из которых является неотделимой частью другого). Основные средства моделирования отношений: наследование и полиморфизм.
Совокупность классов и их отношений называется архитектурой программы. Человек, который создает архитектуру, называется архитектором, а процесс – проектированием.
Обобщенное программирование
Создателем данной парадигмы является Александр Степанов. Наглядный ее пример – библиотека STL. Основные сущности библиотеки: контейнеры, алгоритмы и итераторы. Все они реализованы при помощи шаблонов, поэтому библиотека называется библиотекой шаблонов.
Контейнер – способ организации хранения данных. Примеры контейнеров – стек, связанный список.
Под алгоритмами понимаются процедуры, применяемые к контейнерам для обработки их данных различными способами (например, существуют алгоритмы поиска, сортировки, объединения). Алгоритмы представлены в библиотеке в виде свободных шаблонов функций. Благодаря этому достигается универсальность алгоритмов. Алгоритмы могут применяться к произвольным контейнерам, в том числе к массивам и пользовательским контейнерам.
Итераторы – это обобщение понятия указателя, они ссылаются на элементы контейнера. Их задача – связать контейнер с алгоритмом, поскольку они позволяют осуществить обход контейнера.
31
Основные средства
Указатель
При объявлении переменной и константы требуется указать тип. Тип определяет место, выделяемое для хранения значений, и допустимые операторы. Если по синтаксису требуется указать тип, но тип не нужен, указывается ключевое слово void.
Для создания константы до имени типа пишется ключевое слово const. При определении константы требуется ее инициализировать, после этого запрещается менять ее значение.
Весь код программы хранится в памяти. Память разбита на участки, называемые байтами. У каждого байта есть адрес – номер в памяти. Поскольку переменные хранятся в памяти, у каждой переменной есть адрес – номер байта, с которого начинается выделенная под нее память. В языке С++ есть специальный тип, значениями которого являются адреса переменных, –
указатель.
Работая с указателем, можно напрямую создавать и удалять переменные. Для создания переменной используется оператор new, после которого указывается тип переменной. Имени у нее нет. Используя оператор разыменования «*», можно по адресу (значению переменной) получить доступ к выделенной под нее памяти, читать и записывать туда значения.
Для освобождения памяти (удаления переменной) используется оператор delete. После удаления переменной указатель не меняется, соответственно по ошибке можно либо попытаться обратиться к удаленной переменной, либо попытаться удалить ее во второй раз. В результате программа вылетает. Для избежания такой ошибки рекомендуется после освобождения памяти устанавливать для указателя специальное значение, говорящее о том, что он ни на что не указывает, – это константа NULL.
Спомощью указателя можно создавать не только отдельные переменные, но
имассивы, которые называются динамическими, поскольку, в отличие от обычных, у них может быть неконстантный размер.
32
Пример
int Size = 0; |
|
cin >> Size; |
|
double *Values = new double[Size]; |
|
for (int i = 0;i < Size; ++i){ |
|
cin >> Values[i]; |
|
} |
|
delete[]Values; |
|
Основнаяпользаот использованиядинамическихмассивов– эко |
номия |
памяти, поскольку массив создается, когда мы точно можем сказать, сколько элементов нам понадобится.
Для работы с матрицами следует использовать двумерный массив, который реализуется с помощью массива указателей.
Пример int N = 0, M = 0;
cin >> N >> M;
double **Matrix = new double*[N]; for (int i = 0;i < N; ++i){
Matrix[i] = new double[M]; for (int j = 0;j < M; ++j){
cin >> Matrix[i][j];
}
}
for (int i = 0; i < N; ++i){ delete[]Matrix[i];
}
delete[]Matrix;
Указатели могут быть как параметром функции, так и возвращаемым значением.
Обычный массив, так же как и строка, реализован посредством указателей. При передаче в функцию можно указывать в качестве типа параметра как массив, так и указатель.
33
void Display(int A[], int Size); int *Array = new int [10]; Display(Array, 10); //OK delete[]Array;
При работе со структурой синтаксис требует написания скобок, что затрудняет чтение кода. Предпочтительно использовать альтернативный вариант: использование стрелки « >».
Указатели активно применяются для создания сложных динамических структур данных.
Пример: связанный список, предназначенный для динамического добавления и удаления большого количества элементов. Для работы со списком заранее не требуется знать количество элементов.
Структура
Структура – составной тип, который включает одну или более переменных базового типа или другого составного. Такие переменные называются полями структуры. Назначение структуры в том, чтобы синтаксически связать логически связанные переменные. В качестве типа поля не может выступать сама структура, но может указатель или ссылка на нее.
Пример: структура «Время» struct Time{
int Hour; int Minute;
};
Для описания структуры используется ключевое слово struct. После этого в фигурных скобках идет описание типов и имен полей. Здесь описание не является определением – память под переменные выделяется только при создании переменной типа структура, которая называется экземпляром структуры.
34
Кполям можно получить доступ с помощью оператора «.», который используется после имени экземпляра.
Кэкземплярам структуры нельзя применять операторы ввода и вывода, операторы сравнения.
Пример: структура «Комплексное число» struct Complex{
double Re; double Im;
};
Complex C;
cin >> C.Re >> C.Im;
Можно описывать указатели и ссылки на структуру, создавать массивы.
Пример: студенты и баллы struct Student{
char Name[100]; int Points;
};
Student Group[10];
Структуру можно передавать как параметр и как возвращаемое значение функции.
Пример: корни уравнения struct Roots{
double Root[2]; int N;
};
Roots Find (double a, double b, double c); int main(){
double a, b, c;
cin >> a >> b >> c; cout << endl;
35
Roots Answer = Find (a, b, c); for(int i = 0; i < Answer.N; ++i){
cout << Answer.Root[i] << ' ';
}
return 0;
}
Roots Find(double a, double b, double c){ Roots Res = {{0}, 0};
double D = b*b - 4 * a *c; if(D < 0){
return Res;
}
if(0 == D){ Res.N = 1;
Res.Root[0] = -b / (2*a); return Res;
}
Res.N = 2;
Res.Root[0] = (-b - sqrt(D))/ (2*a); Res.Root[1] = (-b + sqrt(D))/ (2*a); return Res;
}
Макросы
До этапа компиляции программа проходит через макропроцессор, который обрабатывает макроподстановки. Макроподстановка описывается, например, так:
#define PI 3.14
и может использоваться для определения констант. Макросы считаются устаревшим способом и не рекомендуются к использованию.
В макросах допустимы параметры.
#define abs(x) (x)< 0 ? -x : x
36
В макросах могут быть специального вида операторы: конкатенации ”##” и оператор превращения в строку ”#”. Они могут использоваться следующим образом:
#define CONCAT(x, n) x##n
int main(){ int x1 = 1,
x2 = 2;
cout << CONCAT(x, 2);
}
Вместо вызова макроса будет подставлена переменная x2 и выведется 2.
#define TRACE(per) #per " = " << per int main(){
int x1 = 1,
x2 = 2;
cout << TRACE(x1) << endl; cout << TRACE(x2);
}
Программа выведет: x1 = 1, x2 = 2.
В макросах возможны условия – для этого используются операторы #if, #elif, #else, #endif. Можно проверить, определен ли макрос с помощью #ifdef или
#defined.
Пространство имен
При использовании большого количества библиотек возможна проблема, которая называется конфликтом имен – в подключенной библиотеке есть переменная или функция с именем, совпадающим с именем в программе. Для исключения такой проблемы существует механизм пространства имен.
Пример: namespace MySpace{
int function();
}
37
Ключевое слово namespace обозначает, что далее идет пространство имен.
У пространства |
имен может |
быть, а может |
не быть имени. Функции |
и переменные, |
объявленные |
в пространстве |
имен, не видны снаружи. |
Для обращения к ним нужно использовать оператор «::» – MySpace :: function. Для сокращения записи в начале файла можно написать using MySpace :: function; и обращаться к функции только по имени. Можно написать using namespace MySpace; и обращаться только по имени к каждому члену пространства имен, но тогда возможен конфликт.
Пространство имен можно расширять, т. е. добавлять в него новые переменные и функции.
Все стандартные функции и классы хранятся в пространстве имен std. Пространство имен может быть вложенным.
Заголовочные файлы
Большие программы рекомендуется разбивать на несколько файлов. Стандартный подход: вспомогательные функции выносятся в отдельный файл с расширением CPP, заголовки функций переносятся в файл с тем же именем, но расширением H (англ. header – заголовок).
В заголовочных файлах не должно быть определения функции и переменных, только описания. Заголовочный файл подключается к основной программе с помощью директивы #include.
При компиляции содержимое заголовочного файла добавляется в основной, и происходит компиляция всех CPP файлов. Затем полученные OBJ файлы компонуются с помощью программы-компоновщика. При этом во вспомогательном CPP файле отыскиваются определения используемых функций и добавляются к основному файлу. Полученный файл транслируется в
EXE файл.
Распространенной проблемой является дублирование функций. Это может произойти, если один и тот же H файл подключается несколько раз. Для того
38
чтобы этого не было, ранее было принято во вспомогательном H файле ставить защиту. В начале файла писали
#ifndef __FILENAME_H__ #define __FILENAME_H__
в конце
#endif
Если файл уже был подключен, то повторного подключения не будет. Сейчас рекомендуется использовать настройки компилятора:
#pragma once
Механизмы абстракции
Класс
Тип
Класс – это способ создания пользовательских типов в программе. Класс является составным типом, т. е. он может состоять из одного или нескольких базовых или также составных типов. Основным назначением класса является объединение в одном типе логически связанных данных и действий, другими словами – переменных и функций (например, в программе необходим способ работать с датой – хранить день, месяц и год, запрашивать дату у пользователя, выводить дату на экран).
Описание
Для описания класса используется ключевое слово class, далее в фигурных скобках идет описание переменных, которые в данном случае называются полями класса, и функций (методов класса). Ключевое слово public говорит, что все то, что идет после него, доступно извне класса.
В качестве типов полей допустимы любые базовые типы, массивы и пользовательские типы, а также указатели и ссылки на них. Сам класс нельзя
39
использовать в качестве типа поля, но можно использовать указатель или ссылку.
class TDate{ public:
int Day; //Fields of class int Month;
int Year;
void Set(){ //Method of class cin >> Day;
cin >> Month; cin >> Year;
}
void Display(){
cout << Day << "." << Month << "." << Year;
}
};
После описания класса ставится точка с запятой.
Экземпляр
Непосредственно с классом работать нельзя – описанных переменных и функций на самом деле не существует, это лишь их описание. Их создание произойдет только при описании переменной данного класса, которая называется экземпляром класса.
int main(){ TDate Date;
Date.Day = 31;
Date.Month = 12;
Date.Year = 2011; Date.Set(); Date.Display(); return 0;
}
40
