Объектно-ориентированное программирование
Литература:
1) Объектно-ориентированный анализ и проектирование с примерами приложений на С++
Автор: Градебуч
2) Объектно-ориентированное программирование на С++
Автор: Пол А.
3) Самоучитель по С++
Автор: Шилдт Г.
4) а) Программирование на С++
б) Практикум С++
Автор: Павловская Т.А.
5) С++
Автор: Подбельский В.
6 февраля 2012
Тема: «ООП как направление современной теории и практики создания программных систем».
Развитие технологий программирования происходило параллельно развитию вычислительной техники. Рассматривая это развитие в историческом плане можно выделить 4 основные этапа:
1.
Программа |
данные |
Сложность программы ограничивалась возможности программиста одновременно отслеживать последовательность выполняемых операций и местоположением данных в программе.
Этот этап развития программирования называют стихийным программированием, подчеркивая тем самым то, что методы программирования находились в зачаточном состоянии.
a. Важное значение в теории программирования имело создание языков-Ассемблеров, которые позволили использовать символические имена для данных, и мнемоники кодов операции.
b. Революционным изменением было создание языков высокого уровня, которые позволили уменьшить детализацию операций, повысить читаемость программ, и следовательно увеличить их сложность и объем.
c. следующая ступень — появление возможности использования подпрограмм.
Основная программа |
Глобальные данные |
ПП 2 |
ПП 3 |
ПП 1 |
Главное преимущество такой структуры — возможность создания библиотек подпрограмм, реализующих типовые задачи.
В середине 60-х годов XX века разразился кризис программирования, который заключался в том, что многие проекты по созданию программного обеспечения не заканчивались вовремя, или не заканчивались вообще.
Объективной причиной кризиса являлась низкое развитие методов программирования.
Основной метод программирования в то время — снизу–вверх. То есть сначала прорабатывались подпрограммы, а затем они объединялись в общий проект.
В 60-70 года было разработано большое количество методов программирования, среди которых особо выделялся структурный подход программирования по принципу сверху–вниз. Суть этого подхода заключалась в следующем: сначала прорабатывалась общая идея проекта, а затем уже разрабатывались отдельные её части.
S |
S 23 |
S 22 |
S 21 |
S 12 |
S 11 |
S2 |
S1 |
Структурный подход определил второй этап развития программирования (начался в 60-70 годах).
2. Принципы структурного подхода реализованы в таких языках как Pascal, C.
Структурный подход позволил создавать более сложные и объемные программы, которые не могли быть представлены одной программой.
Основная программа |
Глобальные данные |
Модуль 2 |
Данные |
ПП |
ПП |
Модуль 1 |
Данные |
ПП |
ПП |
Структурный подход вместе с модульным программированием привел к возможности создание программных систем, которые получили название Промышленные программные продукты.
Если программная система имела объем, превышающий 100 тысяч операторов, структурный подход не давал возможности создавать надежные программы.
13 февраля 2012г.
Продолжение…
В результате такой ситуации в 80-е годы активно проводилась работа по созданию новых методов проектирования сложных программных систем. Разработанный объектно-ориентированный подход к проектированию определил начало третьего этапа развития теории программирования.
Объектно-ориентированное программирование основано на представлении программной системы в виде совокупности взаимодействующих объектов, каждый из которых принадлежит некоторому классу, а классы составляют иерархические структуры, используя наследование.
Данный способ программирования основывается на объектной модели. Объект локализует в себе некоторую порцию данных, и интегрирует с этими данными подпрограммы их обработки.
Основное преимущество объектного подхода в том, что с помощью этого подхода можно создавать программные системы неограниченной сложности.
Независимость объектов позволяет использовать такие объекты в следующих проектах, повышая таким образом повторяемость кода. Это позволяют значительно ускорить процесс разработки программ за счет создания библиотек класса.
В объектном подходе используются такие механизмы как наследование, полиморфизм, которые позволяют создавать сложные объекты используя более простые. Однако, все эти механизмы реализуются с дополнительными затратами времени, а иногда и памяти. В связи с этим можно отметить следующие недостатки объектного подхода — высокие начальные затраты и снижение быстродействия. Начальные затраты обусловлены тем, что задача выявления объектов для рассматриваемой предметной области довольно сложные.
Понятие «объект» одновременно появилось в разных областях связанных с компьютерами. Так были созданы объектно-ориентированные компьютеры Intel 432, IBM System/38, объектно-ориентированные операционные системы Star OS, iMax. Понятие «объект» впервые было использовано в языке имитационного моделирования Simula. Затем объектный подход был использован в другом языке моделирования — Smalltalk.
Затем принципы объектного подхода были внесены в традиционные языки высыкого уровня:
· C+ООП —> C++
· Pascal + ООП —> Object Pascal.
В 1990-е годы начался новый этап развития теории программирования. Этот этап связывают с использованием компонентной технологии и case-технологии.
Компонентная технология предполагает сборку программного приложения из компонент, которые объединяются с помощью стандартизированных двоичных интерфейсов. В отличии от обычных объектов, объектные компоненты представляются в виде exe-файлов, или динамически подключаемых библиотек. Такие компоненты могут быть вставлены в приложение, написанное на любом языке программирования.
14 февраля 2012г.
Тема: «Концептуальные основы объектного подхода».
Каждый стиль программирования имеет свою концептуальную основу. Объектно-ориентированный стиль программирования имеет в качестве концептуальной базы объектную модель. То есть объекты являются основными строительными блоками объектно-ориентированных приложений.
Понятие объекта.
В восприятии человека, объект — нечто осязаемое, или ощущаемое, имеющее какие-то границы.
Объект (в программировании) — абстрактная или реальная сущность, которая имеет определенное функциональное назначение в рассматриваемой предметной области. Объект характеризуется идентичностью, состоянием и поведением.
Свойство идентичности позволяет различать объекты и обращаться к ним. Обычно для этого используются либо имена, либо адреса объектов. Состояние объекта определяется перечнем свойств объекта. Этот перечень статичен для определенной предметной области, а значение каждого из свойств может меняться.
Пример:
Объект|аудитория
1. Количество мест.
2. Статус аудитории: занята/свободна.
3. Тип аудитории (лекционный зал, компьютерный класс и т.д.).
4. Номер аудитории.
В объектно-ориентированных системах используется контрактная модель программирования. Суть её в том, что одни объекты предоставляют услуги (серверы), а другие объекты используют эти услуги (клиенты).
Поведение объекта определяется перечнем операций, которые он предоставляет своему клиенту. Могут быть следующие разновидности операций:
· Модификатор — операция, которая может изменять состояние объекта.
· Селектор — операция, позволяющая получить значение свойств объекта.
· Итератор — операция, которая позволяет обратиться к элементам объекта в определенной последовательности.
· Конструктор — операция, создающая объекты. Так как объект обладает состоянием, то объект должен занимать определенное адресное пространство. Это пространство для объекта резервируется конструктором. При необходимости конструктор может инициализировать объект начальными значениями.
· Деструктор — операция, которая удаляет объект и освобождает память, занимаемую объектом.
Конструктор и деструктор являются универсальными операциями, которые обязательно должны быть определены для любого объекта.
Объекты в программной системе не существуют изолированно, они взаимодействуют друг с другом.
В процессе взаимодействия объекты вступают в связи, в которых они могут играть разные роли:
· Актер — объект, который воздействует на другие объекты, но никогда не подвергается воздействию сам.
· Сервер — подвергается воздействию других объектов, но не воздействует сам.
· Агент — объект, который может воздействовать на других, и сам подвергается воздействию.
Исходя из тех ролей, которые могут играть объекты во взаимодействии можно разделить объекты на активные и пассивные.
Активный объект имеет свой поток управления, а пассивный объект может проявлять свое поведение только под воздействием активных. Если в программной системе активные объекты действуют последовательно друг за другом, то такая система рассматривается как последовательная система. Если же в системе одновременно могут действовать несколько активных объектов, то такая система называется параллельной, и возникает необходимость решать вопросы согласования деятельности активных объектов.
Понятие класса.
Понятия объекта и класса взаимосвязаны.
Объект — конкретная сущность, которая занимает определенное адресное пространство.
Класс — описание свойств и поведения группы однотипных объектов. Таким образом, объект — экземпляр класса (переменной класса). Класс же представляет собой тип данных.
В составе класса выделяется интерфейсная часть и реализация. В интерфейсной части описываются прототипы операций, объявляются переменные. Реализация класса содержит определения операций в интерфейсной части. Сама интерфейсная часть может быть разделена на три части, в зависимости от уровня видимости.
20 февраля 2012г.
//продолжение…
Элементами класса являются поля и методы.
Поля — это данные, которые образуют значение нового типа данных (типом данных является класс).
Методы — операции над значениями нового типа данных.
Одним из важнейших принципов объектного подхода является инкапсуляция, которая подразумевает такое объединение внутри класса его полей и методов, при котором доступ к полю осуществляется только вызовом соответствующего метода. Реализация принципа инкапсуляции позволяет существенно повысить надежность программирования, т.к. вместо неконтролируемого использования в программе имён переменных программист должен использовать ограниченное число методов. Эти методы могут быть тщательно протестированы, и вероятность ошибок может быть сведена к минимуму.
Для использования в программе определенного класса необходимо создать экземпляры этого класса (объекта). Экземпляры могут создаваться автоматически при входе в блок, в котором эти экземпляры объявлены, или динамически, с помощью средств языка программирования.
Уничтожение автоматически созданных экземпляров класса тоже происходит автоматически при завершении выполнения блока, в котором они были созданы. Динамические экземпляры уничтожаются с помощью специальных средств (delete).
Для создания и уничтожения экземпляра в составе класса предусматриваются специальные методы — конструкторы и деструкторы.
Универсальных рекомендаций по составу классу не имеется, но от правильного выбора метода класса зависит эффективность использования класса. Элементы класса разделяются на личные (доступ разрешен только внутри методов этого класса) и общие (доступ разрешен из любого места программы). Принцип инкапсуляции не рекомендует создавать общие поля.
Среди элементов класса могут быть статические элементы. Статическими могут быть объявлены как поля, так и методы. Статические поля общие для всех экземпляров класса. Статическое поле может использоваться, если ни одного экземпляра класса не существует. Статические функции позволяют работать со статическими полями также независимо от объектов класса.
Ещё одним важнейшим принципом ООП является наследование, которое устанавливает между классами отношение «родитель-потомок».
Класс родитель — класс, предоставляющий свои возможности другим классам. Эти другие классы называются классами потомками.
Класс родитель кроме личных и общих элементов может иметь защищенные поля и методы, доступ к которым разрешен для самого класса-родителя и для всех его потомков. Для того чтобы класс стал потомком, в его объявлении необходимо указать имя родителя и бит-наследования.
Реализация принципа наследования позволяет существенно сократить нового программирования, т.к. классы можно создавать на основе уже существующих классов доопределяя их, или переопределяя некоторые элементы родительского класса.
Семантически отношение выражается как «is a». Это отношение определяет связь «обобщение—специализация», где класс потомок представляется как специализированный частный случай своего родителя. Некоторые языки программирования, например Object Pascal, реализуют простую модель наследования, которая называется простое наследование. При таком наследовании у потомка может быть только один родитель. Более мощная модель наследования называется множественным наследованием. При этом, у класса-потомка может быть несколько родителей. Множественное наследование поддерживается в C++.
В языках, где множественное наследование отсутствует, используется агрегирование объектов, которое семантически обозначается «part of».
Еще одним важнейшим принципом объектного подхода является полиморфизм. Термин обозначает «много форм».
Полиморфизм предполагает возможность определения единого, по имени метода, для всей иерархии производных классов. Причем в каждом из них этот метод может реализовываться со своими особенностями. Принцип полиморфизма гарантирует, что для любого экземпляра класса будут вызываться методы именно этого класса, а не одного из его предков. Полиморфизм реализуется с помощью виртуальной функции, который отличается от обычных тем, что адрес вызываемого метода определяется не во время компиляции, а при её выполнении. В любой экземпляр класса с виртуальными методами добавляется скрытое поле-указатель, на таблицу виртуальных методов, в которой для каждого из методов указывается адрес его реализации в данном классе.
Есть возможность в классе родителей не определять виртуальные функции. Предполагают, что это обязательно будет сделано в классах потомках. Подобные методы называются абстрактными, а класс, содержащий неопределенные функции, называется абстрактным классом. Создавать экземпляры такого класса нельзя. Полиморфизм реализуется также с помощью механизма перегрузки функций и операторов. Такой полиморфизм называется статическим. Перегрузка функций позволяет создавать одноименные функции, отличающиеся сигнатурой. Перегрузка оператора дает возможность использовать стандартные операторы нестандартным операндом.
Операнды — то, над чем совершается операция.
______________
Тема для самостоятельного изучения: «Объектные и объектно-ориентированные языки программирования».
______________
Существует более 2000 языков программирования высокого уровня. Большинство из них создавались для решения конкретных задач определенной предметной области. Некоторые языки разрабатывались как универсальные, т.е. приспособленные на решение разнообразных задач. Большое влияние на развитие языков программирования оказывает развитие теории вычислений. Самые последние достижения связаны с объектной моделью. Существует более 200 объектных, и объектно-ориентированных языков программирования.
Объектными называют те языки программирования, которые поддерживают понятие объекта и класса, а объектно-ориентированными называют языки, которые поддерживают наследование и полиморфизм.
Предком практически всех языков программирования, поддерживающих объектный подход, является язык Simula, разработанный в 1960 году в Норвежском компьютерном центре. Синтаксис этого языка был заимствован из языка Algol.
Следующим «чисто» объектно-ориентированным языком был SmallTalk, который долгое время держал лидерство в области объектно-ориентированного программирования. Затем первенство перешло к языку C++.
___________
Рассмотреть самостоятельно: язык SmallTalk, Object Pascal.
___________
27 февраля 2012
Тема: «ООП на С++».
Классы. Объявление классов.
Класс в С++ — пользовательский тип данных, который описывает структуру и поведение группы объектов. В связи с этим в состав класса должны входить члены двух видов: члены-данные и члены функции. Члены-данные – переменные, с помощью которых описываются свойства объектов. Членами-данными могут быть переменные любых типов, как встроенных, так и пользовательских. Членами-данными могут быть объекты других классов, а также ссылки и указатели на данный класс.
Функции-члены класса – функции, объявленные в составе класса, которые реализуют все возможные действия с данными класса.
Имена членов класса имеют область видимости сам класс. Поэтому функции могут свободно обращаться к остальным членам класса просто по имени. Формат объявления класса подобен формату объявления структуры:
структура | класс |
struct [<имя структуры>] { элементы структуры } [структурные переменные]; | class [<имя класса>] { члены класса (данные, функции) } [экземпляры класса]; |
Имя класса должно быть правильным идентификатором. Оно может быть опущено. Если все экземпляры класса объявляются сразу после описания класса. Класс без имени есть анонимный класс. Если имя класса использовано, то в дальнейшем оно рассматривается как имя нового пользовательского типа данных и может использоваться наряду с int, char. long и т.д.
Члены класса могут иметь один их трёх уровней видимости:
1. общедоступный – public;
2. частный (закрытый) – private;
3. защищенный – protected.
Полный формат объявления класса имеет вид:
Class [<имя класса>]
{
[private:]
<члены класса>
Public:
<члены класса>
Protected:
<члены класса>
} [экзмепляры класса];
По умолчанию члены класса имеют уровень private (*примечание: в структурах по умолчанию члены являются общедоступными, т.е. уровня доступа public).
Пример класса:
class myclass
{
int a;
public:
void set (int _a)
{ a = _a; }
void print()
{ cout << a; }
};
_______
Встраиваемые функции
В языке С++ можно объявлять функции, которые не вызываются, а вставляются в то место в программе, где осуществляется ее вызов. Они называются встраиваемыми. Для того, чтобы функция рассматривалась как встраиваемая используется ключевое слово inline в ее заголовке (спереди). Встраиваемые функции работают быстрее, чем обычные, так как в них не используется передача аргументов и возврат результата в стек. Встраиваемая функция обязательно должна быть объявлена до первого вызова. Ключевое слово inline – не команда, а запрос, который компилятор может не удовлетворить, если имеются следующие ограничения:
1. функция является рекурсивной;
2. функция содержит какой-либо оператор цикла (for, while, do-while);
3. функция содержит оператор множественного выбора (switch);
4. функция содержит оператор безусловного перехода (goto);
5. функция содержит статические переменные:
6. функция содержит код на ассемблере;
и т. д. — в зависимости от компилятора.
Встраиваемыми могут быть не только обычные функции, но и функции члены-класса. Если определение функции члена-класса помещено в объявление класса, то такая функция по умолчанию является встраиваемой.
Функции-члены класса можно определить и вне класса. Такие функции называются внешними. В заголовке такой функции-члена класса перед именем функции указывается имя класса и операция расширения области видимости «::».
Формат внешне определяемой функции:
<тип> <имя класса>::<имя функции> (<параметры>)
{
определение
}
Внешняя функция также может быть встраиваемой, если в ее определение использовано inline.
Пример. Создать класс «Точка», описывающий точку на плоскости.
class point
{
float x, y;
public:
void setxy(float _x, float _y);
void printxy();
};
inline point point::setxy(float _x, float _y)
{
x = _x;
y = _y;
}
inline void point::printxy()
{
cout << “x: “ << x << “; y: “ << y;
}
_______
Объявление объектов класса и массивов объектов
Это осуществляется двумя способами:
1. объявление объекта после описания класса;
2. объявление классов в любом месте программы, где возможно объявление обычных переменных.
28 февраля 2012
//продолжение…
После того, как объект класса создан, можно обратиться к открытым членам класса, используя следующий формат:
<имя объекта>.<имя открытого члена класса>
#include <iostream.h>
class point
{ float x, y;
public:
void Set (float a, float b);
void Print ();
};
inline void point::Set (float a, float b)
{ x=a;
y=b;
};
inline void point::Print()
{cout<<x<<"\n"<<y;
};
void main ()
{
point p;
p.set (1, 8);
p.print ();
return;
}
_______
Если необходимо использовать множество объектов класса, то их можно объявить массивом. Формат объявления такой же как и для обычных массивов:
<имя класса> <имя массива> [<размер>];
#include <iostream.h>
class myclass
{ int x;
public:
void set (int a)
{
x=a;
}
void print ()
{
cout<<x; }};
main ()
{myclass mass [5];
for (int i=0; i<5; i++)
mass [i].set (i);
forv(i=0; i<5; i++)
mass [i].print();
return (0);
}
__________
Указатели на объекты.
Можно создавать указатели на объекты. Формат объявления таких указателей такой же, как формат объявления обычных указателей:
<имя класса> * <имя указателя>;
Через указатель на объект можно обратиться к открытым членам класса с помощью оператора «стрелка» ->
Формат:
<имя указателя> -> <имя открытого члена класса>;
main ()
{myclass ob;
myclass *p;
p=&ob;
p -> set (7); //любое число
p -> print ();
return (0); }
___________
main ()
{myclass mass [5];
myclass *p;
p=&mass [0];
for (int i=0; i<5; i++)
{ p->set(i);
p++;}
return (0);}
_________
Указатели чаще всего используются для работы с динамической памятью. Резервирование памяти под динамический объект производится теми же (calloc/malloc/new) средствами, что и резервирование памяти под динамические переменные.
main ()
{ myclass *p;
p=new myclass;
p -> set(7); //любое число
p -> print ();
delete p;
p=new myclass[10]; //размер выделяемой памяти, размерностью myclass каждый
return (0);
}
________
Статические члены класса.
Каждый объект класса имеет свои данные, которые располагаются в определенном адресном пространстве. Но есть возможность объявить в классе такие данные, которые могут быть использованы всеми объектами данного класса. То есть такие переменные могут рассматриваться как глобальные переменные внутри класса. Чтобы объявить такие члены класса нужно использовать ключевое слово static. Статические данные члены класса объявляются внутри класса, и определяются вне его.
class myclass
{static int x;
public:
void set (int a)
{x=a; }
void print ()
{cou<<x; }};
int myclass::x;
main ()
{ myclass ob1, ob2;
ob1.set (1);
ob1.print ();
ob2.print ();
return (0); } }
__________
Статические данные члены класса могу быть использованы, даже если объект класса не создан. Обращение к статической переменной осуществляется через имя класса и операцию расширения области видимости. Статические переменные могут иметь открытый или закрытый уровень видимости. Если статическая переменная описана как закрытая, то доступ к ней можно получить только с помощью открытых функций. Доступ осуществляется так же как и к обычным членам класса. Однако, статические данные могут использоваться даже если объект класса не создан. В этом случае для обращения к статическим данным используются статические функции.
Статическая функция объявляется с помощью слова static, и может вызываться даже если ни один объект класса не создан. Вызов осуществляется через имя класса и операцию расширения области видимости.
Константные функции и константные объекты.
Функции и члены класса могут объявляться константными с помощью слова const. Формат объявления таких функций следующий:
<тип> <имя функции> (<параметры>) const;
Константная функция не может изменять состояние объектов для которых она вызывается. Если такие изменения будут выполняться, то это приведет к ошибке компиляции.
Однако, есть возможность разрешить константной функции изменять некоторые члены-данные класса. Такие модифицируемые данные члены-класса помечаются с помощью слова mutable.
Объекты класса могут быть константами. В этом случае используется следующее объявление:
const <имя класса> <имя объекта>;
5 марта 2012
Конструкторы и деструкторы.
Конструктор — функция член класса, которая создаёт объекты класса. В языке C++ конструктор имеет следующие характеристики:
1. Имя конструктора такое же как и имя класса.
2. Конструктор не возвращает значение, и void для него не указывается.
3. Конструктор может иметь, или не иметь параметров.
4. В составе класса может быть несколько конструкторов.
5. Если в составе класса не объявлен ни один конструктор, то конструктор будет генерироваться автоматически.
6. Конструктор не наследуется.
7. Конструктор нельзя использовать с ключевыми словами static, const, virtual.
Деструктор — это функция член класса, которая разрушает объекты класса, удаляя их из памяти. В C++ деструктор обладает следующими характеристиками:
1. Имя деструктора такое же как имя класса, но перед ним записывается знак "~".
2. Деструктор не возвращает значение, и тип void для него не указывается.
3. Деструктор не может иметь параметров.
4. В составе класса может быть только один деструктор.
5. Если деструктор явно в составе класса не описан, то он генерируется автоматически.
6. Деструктор не наследуется.
7. Деструктор нельзя использовать с ключевыми словами const и static.
Конструкторы и деструкторы вызываются автоматически. Для глобальных объектов конструктор вызывается в начале программы, а деструктор в конце программы.
Для локальных объектов конструктор вызывается при объявлении объектов в некотором блоке, а деструктор при выходе из области видимости.
class myclass
{ int a;
public:
myclass ()
{ cout<<"объект создан»;
}
~myclass ()
{ cout<<"объект удален"; }
};
main ()
{ myclass ob;
return 0; }
В окне вывода будут созданы записи «объект создан/объект удалён»
______
Конструктор с параметрами. Список инициализации.
Конструктор создаёт объекты класса. Кроме того, конструктор может инициализировать некоторыми начальными значениями поля объекта класса. Такой конструктор называется конструктор с параметрами. Формат:
<имя конструктора> (<параметры>)
Параметры конструктора описываются так же, как описываются параметры любой другой функции, то есть по следующему формату:
<тип> <имя параметра> [=<значение>]
class myclass
{ int a;
public:
myclass (int _a) //конструктор с параметрами
{a=_a;}
…
};
________
Если в составе класса есть конструктор с параметрами, то при создании объектов такого класса необходимо указать аргументы, которые будут передаваться конструктору. Формат объявления следующий:
<имя класса> <имя объекта> (<аргумент>)
Эта форма записи является сокращенной формой записи следующего оператора:
<имя класса> <имя объекта> = <имя конструктора> (<аршумент>);
main ()
{myclass ob (5);
Конструктор может иметь значения по умолчанию. Такой конструктор называется «конструктор по умолчанию». При выхове аргументы могут не передаваться.
myclass (int _a=0)
{a=_a;}
Конструкторы с параметрами необходимо вызывать явно, когда создается массив объектов класса его необходимо инициализировать. Если в классе всего лишь один данный член класса, то массив можно инициализировать следующим способом:
main ()
{myclass mas_ob [5]={1, 2, 3, 4, 5};
Если класс имеет более одного данного члена класса, то для инициализации элементов массива нужно явно вызывать конструктор с параметрами.
class point
{ float x, y;
public: point (float x_1, float y_1)
{ x=x_1; y=y_1;}
};
Main ()
{ point mas [3]= { point (5,7), point (3,2), point (4,3); }
return (0); }
__________
Конструктор может выполнять инициализацию двумя способами:
1. Путем присваивания в теле конструктора
2. С помощью списка инициализации.
Список инициализации записывается между заголовком и телом конструктора и отделяется от заголовка двоеточием.
Список содержит указание переменных, после которых в круглых скобках следует инициализирующее значение.
point (float _x, float _y):x(_x), y(_y)
{ }
Способ инициализации выбирается программистом. Однако, инициализация с помощью списка даёт небольшое преимущество по быстродействию, и кроме того список инициализации является единственным способом инициализации в трех случаях:
1. Если объекты являются константами.
2. Если объекты являются ссылками.
3. Для объектов класса, в состав которого входят другие объекты.
Пример.
Создать класс «окружность», в состав которого входит объект класса «точка».
class point
{ float x, y;
public : point (float x_1, float y_1)
{ x=x_1; y=y_1; }
};
class circle {
point ob;
float r;
public: circle (point_ob, float_r) : ob(_ob), r(_r) { };
};
main ()
{ point p (5, 10);
circle c (p, 7);
return (0);
}
________
Конструктор копирования.
Если при создании объекта класса нужно проинициализировать его значениями полей его уже существующего объекта, то для этого используется специальный вид конструктора — «конструктор копирования».
12 марта 2012г.
//продолжение…
Конструктор копирования в качестве параметра должен получить объект с которого и снимается копия. Используются следующие два формата для конструктора копирования:
1. <имя конструктора> (<имя класса>& <имя объекта>);
2. <имя конструктора> (const <имя класса>& <имя объекта>);
Вторая форма предпочтительнее, т.к. позволяет копировать и константные объекты.
Если в составе класса конструктор копирования не присутствует, он может быть сгенерирован автоматически. Такой автоматический конструктор копирования переписывает все поля копируемого объекта в новый объект. Если в составе класса есть указатели и используется динамическая память, то автоматически конструктор копирования создаст точную копию существующего объекта, и два объекта будут ссылаться на одни и те же ячейки памяти. Удаление одного из объектов может привести к непредсказуемым результатам. Поэтому, в таких случаях необходимо создавать конструктор копирования, который будет выполнять правильное копирование объекта. Конструктор копирования вызывается в трех случаях:
1. Создание объекта-копии уже существующего объекта.
2. Передача объекта в функцию по значению.
3. Когда объект передается функцией в качестве возвращаемого значения.
Пример:
Класс «точка», копировать его.
class point
{ float x, y;
public: point (float x1, float y1)
{x=x1; y=y1;} //конструктор с параметром
point (const point &a)
{x=a.x; y=a.y;} //конструктор копирования
void print ()
{cout<<x<<y;}
};
void main ()
{point obj (7,7);
point obj1 (obj);
obj.print (); // output 77
obj1.print ();
}
_______
Пример:
Создать класс для хранения динамического массива, размер которого передается при создании объекта класса в качестве параметра конструктора.
class massiv
{ int *ptr;
int mas;
public:
massiv (int m)
{mas=m;
ptr=new int [mas]; }
massiv (const massiv &ob)
{mas=ob.mas;
ptr=new int [mas];
for (int i=0, i<mas; i++)
ptr [i]=ob.ptr [i]; }
void set ()
{ for (int i=0; i<mas; i++)
ptr[i]=random (16); }
~massiv ()
{delete [] ptr; } } ;
main ()
{massiv ob1 (5);
ob1.set ();
massiv ob2 (ob1);
return ();
}
_______
Пример:
Определить попадает ли точка с координатами вводимыми с клавиатуры в окружность, являющуюся объектом класса circle. Попадение точки должна определять функция.
class circle
{private:
int x, y, r;
public:
circle (int _x, int _y, int _r)
{x=_x, y=_y, r=_r;}
circle (const circle &a)
{ x=a.x; y=a.y; r=a.r; }
void getcoord (int &a, int &b, int &c)
{ a=x; b=y, c=r; }};
int comp (circle 0, int xx, int yy)
{int xa; ya; ra; result;
0.getcoord (xa, ya, ra);
if (((xx-xa)*(xx-xa))+((yy-ya)*(yy-ya))<ra*ra) result=1;
else result=0;
return result;
}
void main ()
{circle ob (5,6,3);
int x, y;
cin>>x>>y; //ввод координат
cout<<comp (ob, x, y);
return;
}
13 марта 2012г.
Указатель "this".
Каждый объект класса обладает своей копией данных. Функции члены класса должны работать с теми данными, которые принадлежат объекту, вызывающему данную функцию.
Для решения этой проблемы был использован специальный механизм: в состав класса неявно добавляется еще один параметр — указатель "this". Указатель инициализируется адресом объекта, для которого вызывается функция член класса. Указатель "this" неявно передается вызываемой функции в качестве первого параметра.
В ранних версиях C++ обращение членов класса осуществлялось через указатель "this". В дальнейшем появилась возможность обращаться просто по имени.
В настоящее время указатель "this" используется явно только при перегрузке операторов.
Тема: «Друзья класса».
В C++ имеется возможность обойти основополагающий принцип объектного подхода — инкапсуляцию. Это осуществляется с помощью друзей класса. В C++ имеется два вида друзей:
1. Дружественные функции.
2. Дружественные классы.
Дружественные функции.
Не являются членами класса, однако имеют доступ к закрытым членам этого класса и, следовательно, являются альтернативой функциям членам класса.
Функции члены класса как правило описывают свойство объектов этого класса, а в виде дружественных функций оформляются действия не являющимися свойствами класса, но концептуально входящие в его интерфейс и нуждающиеся в доступе к скрытым полям этого класса. Дружественные функции также используются в том случае, когда функция должна получить доступ к скрытым полям нескольких классов. Дружественной может быть обычная функция, или функция член некоторого ранее определенного класса. Чтобы функция стала дружественной классу необходимо поместить ее прототип в состав класса, предварив его словом friend.
На дружественную функцию не распространяется действие спецификаторов, определяющих уровни видимости. Поэтому дружественная функция может быть описана в любом месте класса. Хотя дружественная функция и получает доступ к закрытым членам класса, но этот доступ отличается от доступа, который имеют функции члены класса. Функции члены класса могут обратиться к закрытым полям просто по имени. Дружественная функция может получить доступ к закрытым полям только через объект класса. В связи с этим объект класса должен быть либо объявлен внутри этой функции, либо передан ей в качестве параметра. Дружественная функция не является членом класса, поэтому дружественная функция не вызывается через объект класса и оператор "точка", или указатель на объект и оператор "стрелка". Дружественной функции указатель "this" не передается.
Пример:
class myclass
{ int x, int y;
public:
myclass (int x_1, int y_1)
{ x=x_1; y=y_1; }
friend int f (myclass ob); };
19 марта 2012г.
//продолжение…
class myclass
{int x, int y;
public: myclass (int x_1, int y_1)
{ x=x_1; y=y_1;}
friend int f (myclass ob);};
Int f (myclass ob)
{if (ob.x % ob.y==0)
return (1);
else return (0);};
main ()
{class myclass ob (5,9);
if (f(ob)
cout<<"delirsya bez ostarka";
else cout<<"ne delitsya bez ostatka";
return ();
}
______
//--//--//
Но нельзя одновременно определить несколько классов, поэтому используется предварительное объявление класса (forward decloration). Формат:
class <имя класса>;
Пример 2: создать два класса, в которых хранится по одной целочисленной переменной и дружественную функцию для двух классов.
class myclass1; //предварительное имя класса
class myclass2
{int x;
public:
myclass2 (int_x)
{x=_x;}
friend int f (myclass1 ob1, myclass2 ob2);
};
myclass2
{int y;
public:
myclass1 (int_y)
{y=_y;}
friend int f (myclass1 ob1, myclass2 ob2);
};
Int f (myclass1 ob1, myclass2 ob2)
{if (ob2.x % ob.y==0) return (1);
else return (0);
};
main ()
{class myclass2 ob2 (5);
class myclass ob1 (9);
if (f(ob1, ob2)
______
Дружественная функция может быть не только независимой функцией, но и функцией членом некоторого класса. То есть функция может являться членом одного класса и быть дружественной для другого класса. В этом случае дружественная описывается так:
friend <тип возвращаемого значения> <имя класса>::<имя функции (<параметры>);
Пример:
class myclass1;
class myclass2
{int x;
public:
myclass2 (int_x)
{x=_x;}
int f (myclass1 ob);
class myclass1
{int a;
public:
myclass1 (int_a)
{a=_a;}
friend int myclass::f (myclass1 ob);
};
int myclass2::f (myclass1 ob);
{if (x%ob.a==0)
return (1);
else return (0);};
main ()
{class myclass1 ob1 (5);
class myclass2 ob2 (9);
if (ob2.f (ob1))
cout<<"делится без остатка";
else
cout<<"не делится без остатка";
return (0);
}
_________
Содержание пояснительной записки:
1. Титульный лист
*ФБГО
*Пояснительная записка к курсовой работе по дисциплине объектно-ориентированное программирование на тему: «тема»
2. Аннотация
Количество:
Листов
Рисунков
3. Лист задания.
Он не нумеруется
4. Содержание
Всё предыдущее не нумеруется!!!
«содержание» в содержании не отражается
5. Введение
Анализ задачи и проекта
6. Анализ технического задания
Первый нумерованный документ.
7. Выбор языка программирования (выбор средств из языка)
8. Проектирование класса/-ов.
Каждый класс должен быть описан в следующем формате: имя класса, ответственность класса, атрибуты класса, функции класса
9. Программирование задачи.
*полный текст см.в приложении 1
10. Заключение
11. Список литературы.
12. Приложение
Текст программы
Окно вывода
Не меньше 25 страниц
14 кегль
1.5 интервал
27 марта 2012г.
Тема: «Дружественные классы».
Если все функции некоторого класса должны получить доступ к закрытым членам другого класса, тогда весь класс объявляется дружественным.
Дружественные классы объявляются с помощью ключевого слова friend.
Чтобы класс стал другом некоторого класса его объявление нужно поместить в состав этого другого класса. Чтобы дружественный класс мог работать с закрытыми членами класса в составе этого дружественного класса должен объявляться объект класса, к которому этот класс дружественен.
Пример.
class myclass1;
class myclass2
{int a;
public:
myclass2 () {}
friend class myclass1;
};
class myclass1
{ public:
myclass2 ob;
void set (int _a)
{ ob.a=_a; }
void print ()
{ cout<<ob.a;}
};
________
Дружественность не является взаимным свойством. То есть, если некоторый класс А является другом класса В, то это не означает, что класс В является другом класса А.
Взаимная дружба классов должна объявляться явно:
class A;
class B
{
friend class A;
};
class A
{
friend class B;
};
_______
Дружественность не наследуется. Это означает, что если класс В является другом класса А, то это не означает, что классы, производные от В, так же являются друзьями класса А.
Дружественность не передается от базового класса. Это означает, что если класс В является другом класса А, то классы, производные от А, не имеют в друзьях класс В.
Тема: «Наследование».
Простое наследование.
В языке C++ имеется возможность организовать такие отношения между классами, когда один класс наследует структуру и поведение одного или нескольких других классов. Такие отношения называются наследованием.
Класс, от которого производится наследование, называется предком (базовым классом). Класс, который наследует, называется потомком (производным классом).
Если имеется только один базовый класс, то наследование называется простым. Если же класс имеет несколько базовых классов, то такое наследование называется множественным.
Наследование может быть организовано двумя способами:
1. Обобщение.
2. Специализация.
Обобщение реализуется следующим образом: для группы классов выделяются общие поля и методы, которые помещаются в базовый класс. Каждый производный класс имеет собственные поля и методы, в дополнение к наследуемым.
Специализация организуется следующим образом: в базовом классе имеются типовые методы обработки, а производный класс видоизменяет эти алгоритмы под свои нужды.
Синтаксис объявления класса при простом наследовании следующий:
class <имя базового класса>
{…};
class <имя производного класса>:<спецификатор наследуемого доступа> <имя базового класса>
{…};
В базовом классе можно использовать метку protected. Это позволяет обеспечить доступ членам базового класса к производному классу, при этом оставляя закрытый доступ для всей остальной программы.
Спецификаторы наследуемого доступа определяют какой доступ будут иметь члены базового класса для объектов производного класса. Этот спецификатор может иметь тризначения: public, private, protected.
Пример 1:
class base
{ int a;
public:
void seta (int _a)
{a=_a;}
void printa ()
{ cout<<x;}
};
class potomok : public base
{ int b;
public:
void setb (int _b)
{ b=_b;}
void printb ()
{cout<<b;}
};
main ()
{ potomok ob;
ob.seta (5);
ob.setb (10);
ob.printa ();
ob.printb ();
base ob1;
return (0);
}
_________
Пример 2:
class base
{ int a;
public:
void seta (int _a)
{a=_a;}
void printa ()
{ cout<<x;}
};
class potomok : private base
{ int b;
public:
void set (int _a, int _b)
{ seta (_a); b=_b;}
void printa ()
{cout<<b;}
};
main ()
{ potomok ob;
ob.seta (5); //ошибка
ob.set (5, 10);
ob.printa (); // ошибка
ob.print ();
base ob1;
return (0);
}
________
Пример 3:
Создать систему классов Point (базовый) и Circle (производный).
class Point
{float x, y;
public:
void setp (float _x, float _y)
{x=_x; y=_y};
void print ()
{cout<<x<<y;} };
class Corcle : public point
{ float r;
public:
void setr (float _r)
{r=_r};
void printr ()
{ cout<<r;} };
main ()
{ circle ob;
ob.setp (5.0, 6.7);
ob.setr (10.3);
ob.printp ();
ob.printr ();
return (0) }
_________
Конструкторы и деструктор производных классов.
Конструкторы и деструктор не наследуются. При создании объекта производного класса для него должны резервироваться участки памяти, необходимые как для собственных данных класса, так и для данных, унаследованных от базового класса. Поэтому, должны вызываться конструкторы базового и производного класса.
2 апреля 2012г.
…//продолжение
В связи с этим, при создании объекта произвольного класса, должны вызываться как конструктор базового класса, так и конструктор производного класса.
Порядок их вызова: сначала вызывается конструктор базового класса, и он резервирует области памяти, необходимые для унаследованных членов. Затем работу по созданию объектов продолжает конструктор производного класса.
Для деструктора определен обратный порядок: т.е. сначала работает деструктор производного класса, а дальнейшую работу по освобождению памяти осуществляет деструктор базового класса.
Если конструктор базового класса имеет параметры, то для конструктора применяется специальная форма записи, в которой осуществляется явный вызов конструкторов базового класса с передачей ему тех аргументов, которые необходимы для инициализации его полей
<имя конструктора производного класса> (<параметры>):
<имя конструктора базового класса> (<аргументы>);
{…}
Пример:
class point
{float x, y;
public:
point (float _x, float _y)
x=_x; y=_y;}};
class circle: public point
{float r;
public:
circle (float _x, float _y, float _r):point (_x, _y)
{r=_r;}};
main ()
{circle ob (3.3, 4.1, 5.7);
return (0); }
Замещение функций членом базового класса.
Наследование может быть организовано двумя способами: способом специализации производный класс может видоизменить функции члены базового класса. Для этого в C++ предусмотрен специальный механизм, который называется замещением или переопределением функций базового класса. Для того, чтобы функция базового класса была переопределена в производном классе должен быть объявлен такой же прототип функции, а тело функции может быть другим. Если для объекта производного класса вызывается такая функция, то будет вызвана именно функция из производного класса.
Пример:
class myclass
{public:
void sh_name ()
{cout<<"myclass \n";}
};
class potomok:public myclass
{public:
void sh_name ()
{cout<<"potomok";}};
void main ()
{potomok o;
o.sh_name ();
o.myclass::sh_name ();
Если необходимо, то можно вызвать замещенный вариант базового класса. В этом случае используется имя класса и операция расширения области видимости.
Замещенную функцию базового класса можно вызывать и в переопределенном варианте функции производного класса.
void sh_name ()
{cout<<"klass potomok yavlyaetsya potomkom klassa";}
myclass::sh_name ();
}
Тема: «Множественное наследование».
При множественном наследовании класс потомок может быть создан на основе двух или более базовых классов. Множественное наследование может быть реализовано двумя способами:
1. Имеет место тогда, когда создаются многоуровневые иерархии. В этом случае некоторый класс может являться базовым для другого класса, а этот другой класс базовым для следующего класса.
Пример:
class A
{ int a;
public:
A (int _a)
{a=_a; }};
class B:public A
{int b;
public:
B (int _a, int _b,): A (_a)
{b=_b;}};
class C: public B
{int c;
public:
c (int _a, int _b, int _c): B(_a, _b)
{c=_c;}};
void main ()
{ c ob(3,5,8)
return;
}
_______
2. Имеет место, если класс объявляется наследником двух или более классов. Формат:
class <имя базового класса1>
{…};
class <имя базового класса2>
{…};
…
class <имя базового класса N>
{…};
class <имя производного класса>:
При создании объекта производного класса сначала вызываются конструкторы всех базовых классов. Порядок вызова определяется списком наследования. Затем вызывается конструктор производного класса.
При удалении объекта производного класса вызываются как деструкторы базовых классов, так и деструктор производного класса. Порядок их вызовов точно противоположен конструкторам.
Пример:
class A
{public:
A ()
{ cout<<1;}
~A ()
{cout <<2;}};
class B
{public:
B ()
{cout<<3;}
~B () {cout<<4;}};
class C: public A
public B
{public :
C ()
{cout<<5;}
~C ()
{cout<<6;}};
main ()
{ C ob;
return (0);}
______
Если базовые классы имеют конструкторы с параметрами, то конструктор производного класса имеет специальную форму, в которой предусмотрены явные вызовы конструкторов базового класса.
<имя конструктора производного класса> (<параметры>):
<имя конструктора базового класса1> (<аргументы>);
<имя конструктора базового класса2> (<аргументы>);
<имя конструктора базового классаN> (<аргументы>);
{…}
10 апреля 2012г.
Виртуальные базовые классы.
В многоуровневых иерархиях классов может сложиться ситуация, когда производный класс косвенно наследует более одной копии базового класса
В результате такого наследования при обращении из производного класса D к членам косвенно-наследуемого класса A создается ситуация неоднозначности.
A |
C |
B |
D |
class A
{ public:
int a;
};
class b: public A
{…};
class C: public A
{…};
class D: public B, public C
{…};
main ()
{ D ob;
ob.a=5; //ошибка неоднозначности. Правильный вариант: ob.B::a=5
ob.C::a=5
Чтобы избавиться от неоднозначности можно уточнить, через какой класс должно производиться обращение, то есть явно указать имя класса для обращения к переменной.
Обращение через конкретный класс может избавить от неоднозначности, но в производный класс D будут включены две копии косвенно-наследуемого класса А. Чтобы сэкономить память используются виртуальные базовые классы. Чтобы базовый класс стал виртуальным используется ключевое слово virtual, которое записывается либо перед, либо после спецификатора наследуемого доступа.
class B:virtual public A
{…};
class C: public virtual A
{…};
При множественном наследовании производный класс может наследовать базовый класс как виртуально, так и не виртуально.
В результате изменяется порядок вызова конструкторов при создании объекта производного класса: сначала вызываются конструкторы виртуальных базовых классов, причем в порядке их следования в списке наследования. Затем вызываются конструкторы невиртуальных базовых классов, так же в порядке их следования в списке наследования. Затем вызывается коструктор производного класса. Порядок вызова деструкторов происходит наоборот.
Тема: «Полиморфизм».
Перегрузка функций.
Перегрузка функций — один из видов полиморфизма, который реализуется в C++.
Перегрузка функций — использование функций с одинаковыми именами. Функции, идентифицируемые этим именем называются перегруженными функциями. Если в одной области видимости есть несколько функций с одинаковыми именами, то имеют место перегрузки, но только в том случае, если функции различаются либо числом, либо типом параметров.
Перегрузка позволяет упростить программу за счет того, что используется одно и то же имя для обозначения схожих действий.
math.h
abs (int)
labs (long)
fabs (float)
_____
int ABS (int a)
{ if (a<0) return -a;
else return a;}
long ABS (long b)
{return (a<0)?-a:a;}
float ABS (float f)
{ return (f<0)?-f:f;}
main ()
{int n=-5;
long m=-17;
float z=-1.7;
cout<<"\n Абс.зн.:"<<ABS(n);
cout<<"\n Абс.зн.:"<<ABS(m);
cout<<"\n Абс.зн.:"<<ABS(z);
return (0);
}
Чтобы реализовать перегрузку функций был использован механизм декорирования имен функций. Разные компиляторы выполняют декорирование разным образом. В компиляторе Borland C++ декорирование имен функций производится следующим образом: сначала пишется имя класса, предваряемое символом @, затем имя функции, предваряемое символом@, и затем @q, после которых идут коды параметров функции. Параметры кодируются первой буквой типа параметра. Указатели обозначаются [p], ссылки как [r].
class myclass
{public:
void fun (); //@myclass@fun@qv
void fun (int);//@myclass@fun@qi
void fun (int, int);//@myclass@fun@qii
};
Если одна и та же программа используется с разными компиляторами, то декорирование имен нужно отключить. Это делается следующим образом:
extern "c"{}
На перегруженные функции накладывается ряд ограничений:
1. Перегружать можно только те функции, которые отличаются списком параметров.
2. Функции члены класса могут перегружаться независимо от того, какой уровень доступа они имеют.
3. Функции не могут быть перегружены на основании того, что одна является статической, а другая нет.
4. TypeDEF определение не влияет на механизм перегрузки, т.к. они не вводят определение новых типов, а определяют синонимы для уже существующих типов.
typedef <имя существующего типа> <новое имя>
typedef char* PTR
void fun (char*p);