Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Содержание
История развития языков программирования.
Первые компьютеры появились в конце Великой Отечественной Войны сначала в Соединенных Штатах Америки, а позже в СССР. Эти машины могли решать ограниченный класс задач.
Кодирование происходило на физическом уровне. Сначала программы хранились на перфокартах. Таким образом, процесс отладки программы занимал очень много времени. Позже появились магнитные ленты и магнитные диски, что облегчило задачу переносимости программ.
В конце 1950 начале 1960 годов появились языки программирования Fortran(FORmula TRANslation) и Cobol(COmmon Business Oriented Language)-язык, ориентированный на выполнение общих экономических расчетов.
В 1960-70 годах было написано множество языков программирования. Почти каждый программист придумывал свой язык, мечтая увековечить свое имя.
В конце 1970-х появились Паскаль, Модула, Си, которые широко применялись.
В начале 1980-х широкое распространение получили персональные компьютеры. Примерно в это же время появился язык C++.
Естественно, что C++ более всего близок к языку C. Язык С полностью включен в C++, оставлены все возможности С как языка низкого уровня для выполнения наиболее сложных и универсальных программ. Другим источником вдохновения был язык Simula67; оттуда заимствованы концепции классов и производных классов с виртуальными функциями.
Название языка C++ возникло летом 1983 года. Более ранние версии, известные под именем “ C с Классами”, используются с 1980 года. Первоначально язык возник в процессе создания программы событийно-управляемой симуляции, для которой идеально подошел бы язык Simula67, если бы не соображения эффективности. “ C с Классами” использовался для основных проектов по симуляции только в программах, критичных по времени выполнения и объему используемой памяти. C++ впервые возник вне группы автора в июле 1983года, однако он уже тогда практически не отличался от современной версии языка.
Название “C++ “ было предложено Риком Масцитти и символизирует эволюционные изменения, произошедшие с языком C(“++” обозначение оператора инкрементации в языке C).
Таким образом, эволюцию языков можно показать на схеме:
Коды Ассемблеры Языки высокого Объектно-ориентированное и
уровня модульное программирование
Основы объектно-ориентированного программирования:
Объектно-ориентированное программирование это самый высокоуровневый вид программирования в настоящее время. Здесь поддерживаются достижения предыдущих поколений и добавлены новые свойства. Эти новые свойства реализуют парадигму объектно-ориентированного программирования.
Объектно-ориентированное программирование на C++ основывается на следующих основных этапах разработки программ.
Первый этап заключается в выделении абстракций. Выделение абстракций означает анализ предметной области, для которой составляется программа, с целью определения основных объектов предметной области, их свойств, отношений между объектами, а также возможных операций над объектами и их составляющими.
Второй этап состоит в типизации объектов и синтезе абстрактных типов данных.
Этап предполагает определение новых производных типов данных и наборов специфических функций или операций, применяемых к этим типам данных таким образом, чтобы исключить возможность смешивания или взаимозамены различных типов.
Третий этап заключается в объектной декомпозиции как выделении подтипов или подобъектов и их составляющих для каждого из типов объектов.
Четвертый этап представляет собой композиционную иерархизацию объектов как выделение родовидовых и композиционных отношений над объектами.
В результате объектно-ориентированного подхода к проектированию программ процесс разработки программы превращается в процесс эволюционного программирования, который для внесения каких-либо изменений и дополнений в программу не требует кардинального пересмотра составляющих ее алгоритмов. Эволюционный способ программирования опирается на сохранение целостности объектов программы, то есть внесение изменений в программу не должно затрагивать внутреннюю организацию существующих в ней объектов.
Важным свойством объектно-ориентированных языков является возможность разработки на них программ, работающих в системах со сложными параллельными вычислительными процессами, изначально присущими техническим средствам вычислительной техники. Это свойство опирается на концепцию активных и неактивных объектов в период функционирования программы. Одновременная активность различных объектов становится возможной за счет их строгой типизации и закрытости для изменений другими объектами.
Язык программирования C++ обладает всеми основными свойствами языков объектно-ориентированного программирования и существенно отличается по своей концепции от базового языка C.
Существует несколько принципов, лежащих в основе языка C++:
1. Инкапсуляция это объединение производного типа данных с набором функций, используемых при работе с этим типом, в единый класс. При этом функции, включенные в класс, называются методами класса, данные элементами данных, а конкретные представители класса объектами класса. Закрытие данных и методов оперирования этими данными происходит таким образом, чтобы обращаться можно было бы только определенным объектам (в данном случае только объектам этих типов).
2. Наследование это способность одних классов заимствовать основные свойства других классов, в частности методы классов и элементы данных. Класс, наследующий свойства, называют производным, а класс, предоставляющий свои свойства для наследования, - базовым. Механизм наследования позволяет создавать иерархию классов, то есть многоуровневую систему классов, связанных между собой отношением наследования.
3. Полиморфизм это возможность определения функции, работающей с различными по типу данных списками параметров в пределах какого-либо одного вида алгоритмов. Такие функции называются обычно виртуальными и проектируются как некоторое семейство одноименных функций, работающих с различными типами данных. Механизм, реализующий выбор какой-либо конкретной функции из определенного семейства, носит название механизма позднего связывания, поскольку может быть использован в процессе выполнения готовой программы.
Классы
Центральным понятием объектно-ориентированного программирования является понятие класса. Именно он реализует основные свойства: инкапсуляцию, наследование, полиморфизм.
Класс представляет собой тип, определяемый пользователем. Этот тип включает в себя совокупность полей данных и функций для работы с этими полями.
Класс объявляется:
class <имя класса>[: public <имя базового класса>]
{поля данных
спецификатор доступа
функции };
Отнесение переменной к какому-то типу данных определяет память, выделяемую для этой переменной и набор операций и функций, применяемых к таким переменным. Реализация этих принципов для типов, определяемых пользователем, осуществляется при помощи классов.
Спецификаторы доступа.
Существует ряд соображений, по которым было бы целесообразно ограничить доступ к элементам данных класса. К наиболее важным из них относятся следующие:
- ограничение доступа к данным класса рамками тех функций, которые включены программистом в этот класс, позволяет локализовать программные ошибки практически до начала работы программы;
- описание класса в этом случае позволяет пользователям классов более просто знакомиться с новыми библиотеками классов;
- при ограничении доступа упрощается корректировка программ, поскольку для их изменения достаточно скорректировать описание класса и функции, являющиеся его членами, не внося изменений в те места программы, где используются объекты класса;
- функциональное разграничение классов делает возможной разработку программ, использующих концепцию параллельных процессов.
public
Этим спецификатором помечается группа данных и функций, которые доступны другим функциям программы.
protected
Помечаются защищенные данные и, возможно, функции, если есть необходимость. Эти элементы доступны только функциям - членам данного класса и производных от него классов, то есть тех классов, которые объявят себя приемниками данного.
private
Служит для задания данных и функций, доступных только функциям данного класса. Это частные данные.
По умолчанию элементы считаются частными (private) для класса и открытыми (public) для структуры (объединения также относят к классам).
Классы лучше определять в файле с расширением .h, а реализацию в файле с тем же именем, но с расширением .cpp или .c. Чаще всего класс по одиночке не определяется, а создаются библиотеки.
Для иллюстрации рассмотрим пример класса, который задает координату на экране:
class Location
{ int x;
int y;
public:
Location (int _x, int _y); //конструктор
void setx(int nx);
void sety(int ny);
int Getx()
{return x;}
int Gety()
{return y;}
};
В данном примере, использовав спецификатор public, мы сделали открытыми для других функций методы, описанные в классе.
Определить функции члены класса можно внутри описания класса или за его пределами. В первом случае функция считается встраиваемой. Встраиваемая функция характерна тем, что компилятор C++, обрабатывая вызовы этой функции в программе, заменяет их не на вызов функции как подпрограммы, а непосредственно на объектный код, соответствующий определению этой функции. Вследствие сказанного, программист должен принимать во внимание, что встраиваемые функции, как правило, имеют короткие определения. В качестве примера можно привести определение функций Getx() и Gety().
Для определения функции члена класса за пределами описания класса необходимо определить ее где-либо в программе после определения класса, членом которого она является.
void Location :: setx(int nx)
{x=nx;}
void Location :: sety(int ny)
{y=ny;}
Location :: Location (int _x, int _y)
{x=_x; y=_y;}
Операция разрешения контекста (::) позволяет указать компилятору, к какому из классов принадлежит определяемая функция.
Имя класса в определении пишется для того, чтобы компилятор однозначно определил к какому классу принадлежит данная функция, так как функции члены различных классов могут иметь одинаковые имена.
При определении классов не происходит реального выделения памяти под объекты этого класса, а создаются лишь новые производные типы данных, для которых будут использоваться функции члены класса.
Для того, чтобы начать работу с реальными объектами какого-либо класса, эти объекты необходимо сначала определить. При этом в программе необходимо указать имя класса, объект которого должен быть создан, а также имя самого объекта. У каждого из классов может быть произвольное число объектов.
Конструктором называется функция-член класса, которая выделяет память под поля данных класса и производит их инициализацию, т.е. задает начальные значения в месте объявления переменных.
Имя конструктора совпадает с именем класса. Например, в классе Location конструктор имеет следующий вид: Location (int _x, int _y).
Конструктор не возвращает никакого значения, даже void.
Одним из важных свойств конструктора является его автоматический вызов при описании любого объекта какого-либо класса, использующего конструктор, что снимает с программиста задачу своевременного отслеживания инициализации вновь вводимых объектов.
В общем случае конструкторы классов могут иметь списки параметров, которые могут потребоваться при инициализации. При этом программист будет обязан задать список инициализации при описании каждого нового объекта.
Конструкторов в классе может быть много. В этом случае реализуется механизм перегрузки функции.
Если конструкторы не объявлены, компилятор сам создает конструктор без параметров по умолчанию.
Объявление объектов можно проиллюстрировать следующим образом:
void main (void)
{Location NK(0,0), KK(10,10), *PL;
cout<<KK.Getx(); //возвращаемое значение : 10
PL=&NK;
cout<<PL->Gety(); //возвращаемое значение : 0
}
Здесь при объявлении NK(0,0) и KK(10,10) неявно вызываются конструкторы.
cout<<KK.Getx() обращение идет через переменную.
cout<<PL->Gety() обращение идет через указатель.
Конструктор копий
{Location A(1,1),B,D=A;
… }
Сначала создается объект D и он инициализируется значением объекта A. Для инициализации нужно явно определить конструктор.
В конструкторе копий в качестве параметра используется простая или константная ссылка на объект.
Location::Location([const]Location &S)
{x=S.x; y=S.y}
Для каждого из объектов класса при очистке памяти компилятором создается деструктор по умолчанию. Определяется деструктор следующим образом: ~ имя. Имя деструктора совпадает с именем класса, но с символом ~ (тильда) в начале.
Деструктор решает обратную конструктором задачу, т.е. очищает память.
Если в конструкторе объекта запрашивается динамическая память или открывается файл, то при уничтожении объекта необходимо предусмотреть действия по очистке памяти и закрытию файла. В этом случае пользователю необходимо определять деструктор. Этот деструктор будет вызываться при выходе объекта из области видимости.
Локальные объекты удаляются тогда, когда они выходят из области видимости. Глобальные объекты удаляются при завершении программы.
Наследование
Применительно к C++ наследование это механизм, посредством которого один класс может наследовать свойства другого. Наследование позволяет строить иерархию классов, переходя от более общих к более специальным.
Класс, свойства и поведение которого наследуются, называется базовым классом.
Класс, который наследует называет, называется производным классом.
Обычно процесс наследования начинается с задания базового класса. Базовый класс определяет все те качества, которые будут общими для всех производных от него классов. В сущности, базовый класс представляет собой наиболее общее описание ряда характерных черт. Производный класс наследует эти общие черты и добавляет свойства, характерные только для него.
Наследование, при котором указывается один базовый класс, называется простым.
Если указываются несколько классов, то наследование называется множественным.
Объявление выглядит следующим образом:
class имя класса : public имя базового класса
Например, class D: public A
{ … }
После имени класса D имеется двоеточие, за которым следует ключевое слово public и имя класса A. Для компилятора это указание на то, что класс D будет наследовать все компоненты класса A. Само ключевое слово public информирует компилятор о том, что, поскольку класс A будет наследоваться, значит, все открытые элементы базового класса будут также открытыми элементами производного класса. Однако все закрытые элементы базового класса останутся закрытыми и к ним не будет прямого доступа из производного класса. Причина, по которой закрытые члены класса становятся недоступными для производных классов поддержка инкапсуляции. Если бы закрытые члены класса становились открытыми просто посредством наследования этого класса, инкапсуляция была бы совершенно несостоятельна.
При множественном наследовании объявление выглядит так:
class D: public A [, public C]
{ тело класса D}
Рассмотрим пример:
enum Bool
{false, true}; //константы сводятся к int. Они изменяются с шагом равным единице.
class Point: public Location
{protected:
Bool vis;
public:
Point (int _x, int _y);
void Show();
void Hide();
};
Point::Point (int_x, int_y) : Location(_x, _y)
{vis=false;}
Здесь класс Point наследует свойства базового класса Location.
Наследование и контроль доступа
Спецификатор доступа определяет то, как элементы базового класса наследуются производным классом. Если спецификатором доступа наследуемого базового класса является ключевое слово public, то все открытые члены базового класса остаются открытыми и в производном. Если спецификатором доступа наследуемого базового класса является ключевое слово private, то все открытые члены базового в производном классе становятся закрытыми. В обоих случаях все закрытые члены базового класса в производном классе остаются закрытыми и недоступными.
Важно понимать, что если спецификатором доступа является ключевое слово private, то хотя открытые члены базового класса становятся закрытыми в производном, они остаются доступными для функций членов производного класса.
Доступ к полям базового класса в производном классе может быть сохранен или ужесточен, но никогда не может быть облегчен. Чтобы нагляднее представить себе этот принцип, обратимся к таблице:
Доступ наследования |
Доступ компонентов в базовом классе |
Доступность компонентов базового класса в производном классе |
public |
private protected public |
Нет доступа protected public |
protected |
private protected public |
Нет доступа protected protected |
private |
private protected public |
Нет доступа private private |
Указатель this
Когда функция, принадлежащая классу, вызывается для обработки данных конкретного объекта, этой функции автоматически и неявно передается указатель на тот объект, для которого функция вызвана. Этот указатель имеет фиксированное имя this и незаметно для программиста (“тайно”) определен в каждой функции класса следующим образом:
имя_класса * const this=адрес обрабатываемого объекта;
Имя this является служебным (ключевым) словом. Явно описать или определить указатель this нельзя и не нужно. В соответствии с неявным определением this является константным указателем, т.е. изменить его нельзя, однако в каждой принадлежащей классу функции он указывает именно на тот объект, для которого функция вызывается. Говорят, что указатель this является дополнительным (скрытым) параметром каждой нестатической компонентной функции. Другими словами, при входе в тело принадлежащей классу функции указатель this инициализируется значением адреса того объекта, для которого вызвана функция. Объект, который адресуется указателем this, становится доступным внутри принадлежащей классу функции именно с помощью указателя this. При работе с компонентами класса внутри принадлежащей классу функции можно было бы везде использовать этот указатель.
Таким образом, каждая нестатическая функция элемент класса имеет доступ к объекту, для которого она вызвана через указатель this.
Рассмотрим пример:
comp &operator+(comp)
{real=real+x.real;
im=im+x.im;
return *this;}
В примере реализована для класса comp перегрузка операции сложения. Здесь последовательно складываются действительные и мнимые части. Возвращение результата происходит через указатель this. Если нужно вернуть адрес объекта, то пишется return this.
Друзья
Дружественной функцией класса называется функция, которая, не являясь его компонентом, имеет доступ к его защищенным и собственным компонентам.
Функция не может стать другом класса “без его согласия”. Для получения прав друга функция должна быть описана в теле класса со спецификатором friend. Именно при наличии такого описания класс предоставляет функции права доступа к защищенным и собственным компонентам.
class C
{ …
friend class A; }
Все функции класса A имеют доступ к закрытым полям класса C.
Дружба не носит ”сквозного” характера (не обладает свойством транзитивности): если класс A друг класса B, а класс B друг класса C, то это не означает, что A друг C.
Отметим особенности дружественных функций. Дружественная функция при вызове не получает указателя this. Объекты классов должны передаваться дружественной функции только явно через аппарат параметров. При вызове дружественной функции нельзя использовать операции выбора:
имя_объекта.имя_функции и указатель_на_объект->имя_функции
На все операции языка C++, кроме операций объявления, new, delete, и других операций, связанных с определением производных типов данных, распространяется свойство полиморфизма, т.е. возможности использования в различных случаях для одной и той же операции операндов различных типов. Так, например, операция сложения позволяет “смешивать” типы int, double, float и другие в одном выражении. Такой полиморфизм обеспечен внутренними механизмами языка C++.
Таким образом, нельзя перегружать такие операции: . :: * ?:
Чтобы появилась возможность использовать стандартную для языка C++ операцию с необычными для нее данными, необходимо специальным образом определить ее новое поведение. Это возможно, если хотя бы один из операндов является объектом некоторого класса, т.е. введенного пользователем типа. В этом случае применяется механизм, во многом схожий с механизмом определения функций. Для распространения действия операции на новые пользовательские типы данных программист определяет специальную функцию, называемую “операция-функция” (operator function). Формат определения операции-функции:
тип_возвращаемого_значения operator знак_операции (спецификация параметров операции-функции)
{операторы тела операции-функции }
При необходимости может добавляться и прототип операции-функции с таким форматом:
тип_возвращаемого_значения operator знак_операции (спецификация параметров операции-функции);
И в прототипе, и в заголовке определения операции-функции используется ключевое слово operator, вслед за которым помещен знак операции. Если принять, что
конструкция operator знак_операции есть имя некоторой функции, то определение и прототип операции-функции подобны определению и прототипу обычной функции языка C++. Например, для распространения действия бинарной операции * на объекты класса T может быть введена функция с заголовком T operator *(T x, T y).
Определенная таким образом операция (в нашем примере операция “ звездочка”) называется перегруженной (по-английски - overload), а сам механизм перегрузкой или расширением действия стандартных операций языка C++.
Количество параметров у операции-функции зависит от арности операции и от способа определения функции. Операция-функция определяет алгоритм выполнения перегруженной операции, когда эта операция применяется к объектам класса, для которого операция-функция введена. Чтобы явная связь с классом была обеспечена, операция-функция должна быть либо компонентом класса, либо она должна быть определена в классе как дружественная, либо у нее должен быть хотя бы один параметр типа класс (или ссылка на класс).
Если для класса T введена операция-функция с приведенным выше заголовком и определены два объекта A и B класса T,то выражение A*B интерпретируется как вызов функции operator * (A,B).
Рассмотрим пример. Реализуем перегрузку операции сложения для класса комплексных чисел.
class comp
{float im; float real;
public:
comp(float i, float r)
{real=r;
im=i;}
comp operator +(comp X)
{return comp(im+X.im, real+X.real);}
}
void main()
{ …
comp C1(1,1), C2(5,5),C3;
C3=C1.operator+(C2) // Прямой вызов операции-функции. Не используется.
C3=C1+C2 // Косвенный вызов операции-функции.
…
}
Компилятор по типам объектов С1 и С2 определяет, что необходимо реализовать не просто сложение двух скаляров, как это бывает в обычном использовании операции +, а вызвать перегруженную функцию operator +.Так как при определении класса поля im и real доступны функциям класса, есть необходимость определять только второй объект (X в нашем примере).
В языке C++ требуется, чтобы операции присваивания, индексации и косвенного обращения к полям класса (->) обязательно определялись как методы, т.е. как функции-члены класса.
Когда левый операнд операции является представителем класса, перегруженную операцию нужно определять как метод этого класса.
Для многих операций C++ существуют свои особенности при перегрузке (доопределении). Так, унарные операции переопределяются с описанием операции-функции без аргумента, например:
class A
{ …
A operator --() {текст функции}
… }
Соответственно доопределение бинарной операции использует описание операции-функции с одним аргументом, т.к. вторым является объект, для которого вызвана операция. Следует также помнить, что операция присваивания “=” может перегружаться только объявлением метода без описателя static. То же относится к операциям “()” и ”[]”.
Посмотрим, как будет выглядеть перегрузка операции присваивания для примера с комплексными числами.
comp & operator =([const] comp & X)
{real=X.real;
im=X.im;
return *this;}
Если указываем const, то это показывает, что параметр не должен изменяться внутри функции, а кроме того, позволяет обрабатывать константные объекты.
Операция присваивания не наследуется.
Константные объекты и константные методы
const Loc NK(0,0); //константный объект
После инициализации попытки изменения константного объекта отслеживаются и пресекаются компилятором.
Объявление константной функции в теле класса выглядит следующим образом:
Прототип const;
ПРАВИЛО:
Константные методы
Константные методы могут применяться как для константных, так и для неконстантных объектов.
Статические элементы класса
В классе можно определять элементы данных и функции со спецификатором static. В этом случае их можно рассматривать как глобальные переменные или функции в пределах класса, и доступны они не одному объекту, а разделяются между всем классом, не ассоциируются с отдельными представителями класса.
class Name
{ …
public:
…
static int count; }
Для элементов, которые объявлены в открытой секции класса, вызов, например, будет выглядеть следующим образом:
Name::count+=2;
Если элементы определены в закрытой секции, то такое обращение некорректно.
Если мы объявили такой элемент в закрытой секции класса, то мы должны определить функции для работы с этими элементами. Обращение к таким функциям выглядит так: Name::Add1();
ПРАВИЛО:
1) Статические функции и элементы класса не ассоциируются с отдельными
представителями класса. Обращение к ним производится выражением вида:
имя класса :: имя элемента
2) Им не передается указатель this, т.к. он ассоциируется с отдельным представителем класса.
3) Статические функции могут вызываться независимо от того, существует ли хотя бы один представитель класса.
4) Статические функции не могут быть виртуальными.
Виртуальные функции
Виртуальные функции это функции-члены класса, вызов которых осуществляется во время выполнения программы ( то есть динамически) по типу объекта, с которым она используется.
Например, базовый класс может описывать фигуру на экране без конкретизации ее вида, а производные классы (окружность, эллипс и т.п.) однозначно определяют ее формы и размеры. Если в базовом классе ввести функцию для изображения фигуры на экране, то выполнение этой функции будет возможно только для объектов каждого из производных классов, определяющих конкретные изображения.
Объявление виртуальной функции в классе:
virtual прототип_функции;
Отметим, что классы, включающие такие функции, играют особую роль в объектно-ориентированном программировании. Именно поэтому они носят специальное название полиморфные.
Вернемся к упомянутому выше примеру с фигурами. Рассмотрим класс Point
( производный от Location). Пусть в этом классе определена компонентная функция void Show(). Так как внешний вид фигуры, для которой будет использоваться данная функция в базовом классе еще не определен, то в каждый из производных классов нужно включить свою функцию void Show() для формирования изображения на экране. Это не очень удобно, поэтому в таких случаях используют механизм виртуальных функций. Любая нестатическая функция базового класса может быть сделана виртуальной, если в ее объявлении использовать спецификатор virtual.
class Point: public Location{
protected:
Boolean vis;
public:
Point (int nx,int ny);
virtual void Show();
virtual void Hide();
virtual void Drag(int by);
Boolean Isvis() { return vis;}
void MoveTo (int nx ,int ny); };
Виртуальными могут быть не любые функции, а только нестатические компонентные функции какого-либо класса. После того как функция определена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtual может не использоваться.
В производном классе нельзя определять функцию с тем же именем и с той же сигнатурой параметров, но с другим типом возвращаемого значения, чем у виртуальной функции базового класса. Это приводит к ошибке на этапе компиляции.
Если в производном классе ввести функцию с тем же именем и типом возвращаемого значения, что и виртуальная функция базового класса, но с другой сигнатурой параметров, то эта функция производного класса не будет виртуальной. В этом случае с помощью указателя на базовый класс при любом значении этого указателя выполняется обращение к функции базового класса (несмотря на спецификатор virtual и присутствие в производном классе похожей функции).
Рассмотрим как будет выглядеть вызов виртуальной функции void Show() в производном классе:
class Circle : public Point {
protected:
int R;
public:
Circle (int nx,int ny, int nr);
void Show();
void Hide();
void Expand(int by);
void Contract(int by); };
Как мы видим, спецификатор virtual можно уже не указывать.
Механизм виртуального вызова может быть подавлен с помощью явного использования полного квалифицированного имени. Таким образом, при необходимости вызова из производного класса виртуального метода (компонентной функции) базового класса употребляется полное имя.
Все выше сказанное можно объединить в ПРАВИЛА:
2) Объявления виртуальных функций в производных классах должны иметь ту же сигнатуру, что и в базовом. Указывать спецификатор virtual в этом случае не обязательно.
3) Виртуальная функция должна быть обязательно определена или быть чистой виртуальной функцией.
Чистой виртуальной функцией называется компонентная функция, которая имеет следующее определение:
virtual тип имя_функции (список формальных параметров)=0;
В этой записи конструкция “=0” называется “чистый спецификатор”. Пример описания чистой виртуальной функции:
virtual void F()=0;
Чистая виртуальная функция в определении класса показывает, что ее определение откладывается до производных классов. Чистая виртуальная функция “ничего не делает” и недоступна для вызовов. Ее назначение служить основой для подменяющих ее функций в производных классах и показывать, что данный класс абстрактный.
Абстрактные классы
Абстрактные классы могут использоваться только в качестве базовых для других классов. Предполагается, что абстрактный класс содержит одну или несколько чистых виртуальных функций.
Определять объекты абстрактного класса нельзя, но можно определять ссылку или указатель на абстрактный класс. Нельзя использовать абстрактный класс в качестве параметра функции. Производный от абстрактного класса также считается абстрактным, если в нем не определена хотя бы одна чистая виртуальная функция.
Как всякий класс, абстрактный класс может иметь явно определенный конструктор. Из конструктора возможен вызов методов класса, но любые прямые или опосредованные обращения из конструктора к чистым виртуальным функциям приведут к ошибкам во время выполнения программы.
Механизм абстрактных классов разработан для представления общих понятий, которые в дальнейшем предполагается конкретизировать. Эти общие понятия обычно невозможно использовать непосредственно, но на их основе можно, как на базе, построить частные производные классы, пригодные для описания конкретных объектов.
Таким образом, механизм абстрактных классов используется при создании сложных иерархий наследования.
В качестве иллюстрации выше сказанного рассмотрим пример для расчета площади треугольника и прямоугольника:
// Создание абстрактного класса
# include <iostream.h>
class area
{double dim1, dim2; //размеры фигуры
public:
void setarea(double dim1,double dim2)
{dim1=d1;
dim2=d2;
}
void getdim(double &dim1,double &dim2)
{d1=dim1;
d2=dim2;
}
virtual double getarea()=0; //чистая виртуальная функция
};
class rectangle: public area
{ public:
double getarea()
{double d1,d2;
getdim(d1,d2);
return d1*d2;
}
};
class triangle: public area
{ public:
double getarea()
{double d1,d2;
getdim(d1,d2);
return 0.5*d1*d2;
}
};
int main()
{area *p;
rectangle r;
triangle t;
r.setarea(3.3,4.5);
t.setarea(4.0,5.0);
p=&r;
cout<< “Площадь прямоугольника:”<<p->getarea()<<\n;
p=&t;
cout<< “Площадь треугольника:”<<p->getarea()<<\n;
return 0;
}
Теперь то, что функция getarea() является чистой виртуальной, гарантирует ее обязательную подмену в каждом производном классе.
Виртуальные классы
А |
C |
B |
D |
При таком наследовании может возникнуть проблема в наследовании двух экземпляров полей класса A в классе D через B и C (будет занята лишняя память и возникнет путаница). Чтобы это избежать, нужно при создании B и C объявить класс виртуальным.
class B : virtual public A
{…}
class C : virtual public A
{…}
class D : public C, public B
В этом случае последовательность в создании объектов класса D будет следующей: сначала вызывается конструктор класса А (вызывается один раз), затем конструкторы В и С, последним вызывается конструктор класса D. Уничтожение объектов класса D производится в обратном порядке.
Шаблоны
Шаблоны представляют собой обобщенные объявления, из которых компилятор может создавать функции или классы с заданными параметрами. Шаблоны позволяют пользователям оперировать параметризованными типами. Имея реализацию шаблона, пользователю нет необходимости переписывать функции для конкретных типов данных.
Шаблоны функций
Объявление шаблона функций выглядит следующим образом:
template <список аргументов>
заголовок функции
{тело функции}
Здесь угловые скобки являются неотъемлемым элементом определения. Список параметров шаблона должен быть заключен именно в угловые скобки.
Список аргументов состоит из выражений типа
<class идентификатор 1,…
class идентификатор n>
идентификатор 1, идентификатор n представляют собой обозначения параметризованных типов. Эти обозначения типов можно использовать вместо типов формальных параметров или локальных переменных.
Под классом в данном случае понимается самый обширный тип данных.
Например, шаблон функции обмена для массива будет выглядеть так:
template <class T>
void Obmen(T A[], int i, int j )
{T temp;
temp=A[i];
A[i]=A[j];
A[j]=temp;}
main()
{int Z[10];
…
Obmen (Z,5,7);
float x[100];
Obmen (x,6,7);
…
}
Шаблоны функций можно специализировать, т.е. для типов данных, к которым не подходит шаблон, определить функцию с таким же именем. В этом случае алгоритм обращения компилятора к функции будет следующим:
1) Компилятор пытается найти функцию, параметры которой точно соответствуют параметрам вызова.
Если это не удалось, то
2) компилятор пытается найти шаблон, из которого можно сгенерировать функцию с указанными параметрами.
Если и это не удалось, то
3) компилятор рассматривает имеющиеся функции на предмет преобразования типов параметров.
Шаблоны классов
Шаблоны классов задают обобщенные определения семейств классов, использующих произвольные типы и константы. Из шаблона класса компилятор может генерировать объекты с определенными типами и константами.
template <список аргументов>
class NameClass
{ … }
Список аргументов состоит в общем случае из конструкций
< class Ид 1,…
class Ид n ,
тип Ид k… >
Идентификаторы Ид 1 - Ид n используются при определении параметризованных типов.
Идентификатор Ид k используется для задания констант.
Объекты шаблонного класса создаются следующим образом:
NameClass <int> A (список инициализации)
Будет создан объект, где формальные типы будут заменены типом int. Список аргументов модифицируется следующим образом: аргументы вида
class Идентификатор
заменяется конкретным типом (int, double и т.д.), в том числе здесь можно использовать и классы.
Аргумент вида
тип Идентификатор
заменяется соответствующей константой.
Шаблоны классов специфицировать нельзя.
Пример использования шаблонов класса:
template <class T>
class TSteck
{int n; T *item;
public:
TSteck(int S=10)
{n=0; item=new T[S];}
void push(T t);
T pop();
}
template <class T>
void TSteck :: push(T t)
{item[n++]=t;}
T TSteck :: pop()
{return item[n--];}
TSteck <float> A(100); // создание объекта
Будет вызван конструктор, и будет моделироваться массив, имитирующий стек.
Обработка исключительных ситуаций в C++
C++ обеспечивает встроенный механизм обработки ошибок, называемый обработкой исключительных ситуаций. Он позволяет обрабатывать только синхронные исключительные ситуации, т.е. те ситуации, возникновение которых в явном виде предусмотрено в программе. Асинхронные исключения (ошибки оборудования, аппаратные прерывания) им не обрабатываются.
Для обработки исключительных ситуаций введены три ключевых слова:
try, catch, throw. Эти ключевые слова используются для предупреждения аварийного выхода из программы и корректной очистки памяти.
try служит для обозначения секции кода, которая может генерировать исключения, т.е. нескольких “подозрительных” операторов, в которых возможно возникновение исключительной ситуации (например, деление на ноль, переполнение и т.д.).
Операторы, включенные в фигурные скобки за ключевым словом try, принадлежат к try-блоку. Все функции прямо или косвенно вызываемые из try-блока также принадлежат к нему.
main()
{ …
try{
f1();
f2(); }
… }
f1 и f2 могут в свою очередь вызывать другие функции. Эти функции также принадлежат к try-блоку (они также могут генерировать исключения).
Инструкции catch должны следовать непосредственно за try-блоком. Они представляют собой секцию кода, на которую передается управление в случае возникновения исключения. Их может быть много.
catch (int i) //перехват ошибки
{ cout<<”перехвачена ошибка номер: ”;
cout<<i<<“\n”;
}
catch-обработчик будет срабатывать при выбросе исключения целого типа.
Последним пишется обработчик с тремя точками:
catch (…)
{ …
}
Из описанных в программе обработчиков всегда срабатывает только один.
Идентификация того обработчика, который нужно вызвать, осуществляется по типу выброшенного значения. Остальные обработчики не вызываются. Можно перехватывать любые типы данных, включая и типы создаваемых пользователем классов. Фактически в качестве исключительных ситуаций часто используются именно типы классов.
throw выбрасывает исключения и осуществляет переход к обработчику. За этим ключевым словом в общем случае может следовать выражение, сводящееся к определенному типу. Это значение может рассматриваться как фактический параметр при вызове функции обработчика. Типом этого операнда определяем какой из обработчиков должен перехватить исключение.
Место, где написано слово throw, называется точкой выброса.
Ключевое слово throw без операнда применяется, когда заново выбрасывается то исключение, которое в данный момент обрабатывается. Этот вариант используется либо в catch-обработчике, либо в функции им вызываемом.
void f1()
{ …
if (<условие>)
throw “ошибка”;
}
void f2()
{ …
if (<условие>)
{ x=3;
throw x;}
}
Когда выполняется throw
Рассмотрим пример программы, которая иллюстрирует работу с графическими объектами. Здесь реализованы основные принципы объектно-ориентированного программирования на языке C++.
#include <graphics.h> // директива препроцессора для работы с графикой
#include <iostream.h> //директива препроцессора для ввода-вывода
#include <conio.h> //директива препроцессора для ввода одиночного символа
enum Boolean{false, true}; //логический тип
class Location
{protected:
int X;int Y; //защищенные поля класса
public: // открытый блок
// Location (); //Такие конструкторы без параметров создаются компилятором. Его
//объявлять не надо.
Location (int nx, int ny) { X=nx;Y=ny;} // Если функцию определили сразу в классе, то
// это встроенная функция. Она вызывается не
//через стек, а подстановкой в программу.
int GetX() {return X;}
int GetY() { return Y;}
};
// класс Точка
class Point: public Location
{protected:
Boolean vis;
public:
Point (int nx,int ny); //конструктор
//виртуальные функции
virtual void Show(); //функция, реализующая отображение объекта на экране
virtual void Hide(); //функция, позволяющая скрыть объект
virtual void Drag(int by);// движение объекта по экрану в зависимости от шага by
Boolean Isvis() { return vis;}
void MoveTo (int nx ,int ny); // перенос точки из одного места на экране в другое
};
//класс Окружность
class Circle : public Point
{protected:
int R;
public:
Circle (int nx,int ny, int nr); //конструктор
void Show(); // Так как класс Circle является наследником класса Point, функции
void Hide(); // Show() и Hide() также являются виртуальными
void Expand(int by); // увеличение окружности
void Contract(int by);// уменьшение окружности
};
Boolean GetDelta (int &dx,int &dy); //функция определяет какую кнопку нажал
//пользователь
Point::Point(int nx,int ny):Location(nx,ny)
{ vis=false; }
void Point:: Show()
{ vis= true;putpixel(X,Y,getcolor());}// getcolor() возвращает цвет, который сейчас
// установлен
void Point :: Hide()
{vis=false;putpixel(X,Y,getbkcolor());}//getbkcolor()возвращает цвет фона
void Point :: MoveTo(int nx,int ny)
{Hide();X=nx;Y=ny;Show(); }// точка гасится на старом месте и воспроизводится
// на новом
Boolean GetDelta(int& dx,int& dy)
{ char Keych;
Boolean Quit;
dx=0;dy=0;
do
{Keych=getch();
if (Keych == 13)
return (false);
if (Keych == 0)
{Quit=true;
Keych=getch();
switch (Keych){
case 72:dy=-1;break;
case 80: dy=1; break;
case 75:dx=-1; break ;
case 77: dx=1; break ;
default : Quit=false;}; };
} while (!Quit);
return (true); }
// движение объекта по экрану в зависимости от шага by
// Можно нажимать две кнопки одновременно, точка
// будет перемещаться сразу по x и по y под углом 45 градусов
void Point::Drag(int by)
{ int dlx,dly;
int fx,fy;
Show();
fx= GetX();fy= GetY();
while (GetDelta(dlx,dly))
{ fx+=dlx*by;
fy+=dly*by;
MoveTo(fx,fy);}; }//перемещение точки, пока стрелка нажата
Circle::Circle(int nx,int ny,int nr):Point(nx,ny)
{ R=nr;}
void Circle:: Show()
{ vis=true;circle(X,Y,R); }
void Circle :: Hide()
{ unsigned tcol; // tcol - текущее значение цвета
tcol=getcolor();
setcolor(getbkcolor());// установка цвета фона
vis=false;
circle(X,Y,R); //стандартная функция рисования окружности
setcolor(tcol); }//гашение окружности, восстановление исходных значений цвета
void Circle :: Expand(int by) // увеличение окружности
{Hide();
R+=by;
if (R<0)
R=0;
Show(); }
void Circle :: Contract(int by) // уменьшение окружности
{Expand(-by);}
// класс Кольцо
class Ring: public Circle
{protected:
int R; int R1;
public:
Ring(int nx,int ny, int nr, int nr1); //конструктор
void Show();
void Hide();
void Expand(int by);
void Contract(int by);
};
Ring::Ring(int nx,int ny, int nr, int nr1):Circle(nx,ny,nr)
{R=nr;R1=nr1;}
void Ring::Show()
{vis=true;circle(X,Y,R);circle(X,Y,R1);}
void Ring::Hide()
{ unsigned tcol;
tcol=getcolor();
setcolor(getbkcolor());
vis=false;
circle(X,Y,R);
circle(X,Y,R1);
setcolor(tcol);
}
void Ring:: Expand(int by)
{Hide();
R+=by;
R1+=by;
if (R<0)
R=0;
if (R1<0)
R1=0;
Show();
}
void Ring:: Contract(int by)
{Expand(-by);}
//класс Ауди
class Audi : public Ring
{protected:
int R;int R1;
public:
Audi(int nx,int ny, int nr, int nr1);
void Show();
void Hide();
void Expand(int by);
void Contract(int by);
};
Audi::Audi(int nx,int ny, int nr, int nr1):Ring(nx,ny,nr,nr1)
{R=nr;R1=nr1;}
void Audi::Show()
{vis=true;
circle(X,Y,R);circle(X,Y,R1); //рисование взаимосвязанных колец
circle(X+120,Y,R);circle(X+120,Y,R1);
circle(X+240,Y,R);circle(X+240,Y,R1);
circle(X+360,Y,R);circle(X+360,Y,R1);
}
void Audi::Hide()
{ unsigned tcol;
tcol=getcolor();
setcolor(getbkcolor());
vis=false;
circle(X,Y,R);circle(X,Y,R1);
circle(X+120,Y,R);circle(X+120,Y,R1);
circle(X+240,Y,R);circle(X+240,Y,R1);
circle(X+360,Y,R);circle(X+360,Y,R1);
setcolor(tcol);
}
void Audi:: Expand(int by)
{Hide();
R+=by;
R1+=by;
if (R<0)
R=0;
if (R1<0)
R1=0;
Show();
}
void Audi:: Contract(int by)
{Expand(-by);}
//основная часть программы
void main()
//инициализация графического режима
{ int graphdriver=DETECT,graphmode;
initgraph(&graphdriver,&graphmode,"c:\\borlandc\\bgi");
// создание объекта класса Точка и действия с ним
Point P(100,50);
P.Show(); getch();
P.MoveTo(300,100); getch();
P.Hide();
// создание объекта класса Окружность и действия с ним
Circle C(200,200,100);
C.Show(); getch();
C.MoveTo(300,200); getch();
C.Contract(50);getch();
C.Expand(50);getch();
C.Drag(5);
C.Hide();
// создание объекта класса Кольцо и действия с ним
Ring R(300,300,100,80);
R.Show();getch();
R.MoveTo(200,200); getch();
R.Contract(50);getch();
R.Expand(50);getch();
R.Drag(10);
R.Hide();
// создание объекта класса Ауди и действия с ним
Audi A(100,300,80,70);
A.Show();getch();
A.MoveTo(150,200); getch();
A.Contract(50);getch();
A.Expand(50);getch();
A.Drag(10);
A.Hide();
//закрытие графического режима
closegraph();
}