МЕТОДИЧНІ ВКАЗІВКИ
ДО ЛАБОРАТОРНИХ ТА ПРАКТИЧНИХ
РОБІТ З ДИСЦИПЛІНИ
"ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ"
Харків 2007
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ
ХАРКІВСЬКИЙ НАЦІОНАЛЬНИЙ
УНІВЕРСИТЕТ РАДІОЕЛЕКТРОНІКИ
МЕТОДИЧНІ ВКАЗІВКИ
ДО ЛАБОРАТОРНИХ ТА ПРАКТИЧНИХ
РОБІТ З ДИСЦИПЛІНИ
"ОБ’ЄКТНО-ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ"
для студентів денної форми навчання спеціальності
_______________________
Відповідальний за випуск Путятін Є.П.
ЗАТВЕРДЖЕНО
кафедрою інформатики.
Протокол № від
Харків 2007
Методичні вказівки до лабораторних та практичних робіт з дисципліни “Об’єктно-орієнтоване програмування” для cтудентів денної форми навчання спеціальності ___________ /Упоряд.: В.А. Любченко, О.В. Яковлева, Д.О. Руденко - Харкiв: ХНУРЕ, 2007. - ___ с.
Упорядники: В.А. Любченко
О.В. Яковлева
Д.О. Руденко
Рецензент С.С. Танянський, доц. каф. ЕОМ
СОДЕРЖАНИЕ
ВСТУП.. 5
1 Основы разработки классов в языке С++. 7
2 Наследование, полиморфизм, инкапсуляция, исключения. 28
2 Работа с векторами стандартной библиотеки с++. 39
4 Работа со строками стандартной библиотеки с++. 46
5 Алгоритмы стандартной библиотеки С++. 54
5 Работа с элементами стандартной библиотеки С++. 80
7 Использование proxy-классов. 97
ВСТУП
Объектно-ориентированный подход в последнее десятилетие стал одним из наиболее интенсивно развивающихся направлений в программировании и наиболее популярным средством разработки программного обеспечения.
Начало развитию объектно-ориентированного подхода положил язык Simula 67, который был разработан в конце 60-х гг. в Норвегии. Несмотря на то, что язык намного опередил свое время, современники (программисты 60-х гг.) оказались не готовы воспринять ценности языка Simula 67, и он не выдержал конкуренции с другими языками программирования (прежде всего, с языком Fortran).
Но достоинства языка Simula 67 были замечены некоторыми программистами, и в 70-е гг. было разработано большое число экспериментальных объектно-ориентированных языков программирования. В результате исследования этих языков были разработаны современные объектно-ориентированные языки программирования: C++, Ada, Smalltalk и др.
Наиболее распространенным объектно-ориентированным языком программирования является язык C++ [1, 6, 8]. Он возник на базе соединения языков С и Simula. С++ был разработан в начале 80-х Бьерном Страуструпом, сотрудником компании AT&T. Все эти годы язык интенсивно развивался, и, наконец, в августе 1998 г. был принят международный стандарт языка С++.
Разработка новых объектно-ориентированных языков программирования продолжается и в настоящее время. Например, с 1995 г. стал широко распространяться объектно-ориентированный язык программирования Java, ориентированный на сети компьютеров и, прежде всего, на Internet. В настоящее время компанией Microsoft разрабатывается новый объектно-ориентированный язык C# (C Sharp), который во многом базируется на языке С++ и также ориентирован на разработку Internet-приложений.
Вместе с развитием объектно-ориентированного программирования стали развиваться и объектно-ориентированные методы разработки программного обеспечения, охватывающие стадии анализа и проектирования. Среди общепризнанных объектно-ориентированных подходов к анализу и проектированию следует выделить методы Г. Буча [3, 4], Д. Рамбо, А. Джекобсона, Шлеера-Меллора и Коуда-Йордона. В результате объединения усилий первых трех авторов появился на свет унифицированный язык моделирования UML [2, 5, 7, 9], который в 1997 г. был принят в качестве стандарта консорциумом Object Management Group и получил широкое распространение в сфере производства программного обеспечения.
1 Основы разработки классов в языке С++
Классы и объекты
Класс это такой программный тип, который определяет не только данные, но и функции, применимые к этим данным. Говорят, что данные задают состояние объекта, а функции – его поведение.
В С++ объявление класса похоже на структуру в языке С, но, помимо компонентов-данных, включает компоненты-функции, которые также называют методами.
Пример. Объявление класса “дата”. Хранит день, месяц, год. Способен установить дату, сообщить ее, и изменить на следующую.
ß
class date {
public:
int day, month, year;
void set (int, int, int);
void get (int&, int&, int&);
void next ();
};
Определение функций-компонент можно расположить внутри или за пределами объявления класса. В первом случае транслятор попытается создать функции inline. Во втором — имени определяемой функции должно предшествовать имя класса вместе с операцией разрешения видимости.
Все данные класса доступны из методов того же класса.
Пример. Определение функции-члена date::set, расположенное за пределами объявления класса.
void date::set (int d, int m, int y) {
day = d;
month = m;
year = y;
}
Класс представляет собой тип данных. Сами же данные такого типа называют экземплярами класса или объектами. Каждый объект имеет собственную копию всех данных класса. Чтобы создать объект в памяти, его надо определить, например,
date d;
После создания объекта получить доступ к его данным и методам можно при помощи операции прямого выбора, обозначаемой точкой.
ß
cout << d.day;
d.set(19,10,2001);
Инкапсуляция
Инкапсуляция в объектно-ориентированном программировании — это сокрытие деталей реализации класса. Класс вводит еще одну область видимости (другие области это файл, функция, блок, прототип функции, пространство имен). В пределах класса видимы все его компоненты, за пределами — только некоторые.
Спецификации доступа public и private регулируют видимость компонент извне.
Пример. Данные класса date скрыты от пользователя, а методы открыты.
ß
class date {
private:
int day, month, year;
public:
void set (int,int,int);
...
};
Члены класса всегда можно именовать, применяя операцию разрешения области видимости, например, вместо
int day, month, year;
в объявлении класса можно написать
int date::day, date::month, date::year;
но обычно операция разрешения видимости применяется за пределами объявления класса.
Конструктор
После создания объекта в памяти все его данные должны получить значения. Конструктор — это функция, специально предназначенная для инициализации объекта. Конструктор носит то же имя, что и класс и не возвращает значения. Класс может иметь несколько конструкторов.
Пример. Класс с тремя конструкторами.
ß
class date {
int day, month, year;
public:
date(int,int,int);
date(char*);
date();
};
Конструктор вызывается тогда, когда создается объект. Благодаря конструкторам, оператор объявления в С++ вызывает выполнение действий.
Примеры инициализации объектов:
void main(int argc, char* argv[])
{
date d1 = date(19, 10, 2001);
date d2(19, 10, 2001); // то же, но сокращенно
date d3 = date("19-Oct-2001");
date d4 = "19-Oct-2001"; // когда в конструкторе ровно 1 параметр
date d5; // работает конструктор без параметров
}
Если программист не определил конструктор, транслятор сам строит конструктор по умолчанию, который не имеет параметров и ничего не делает. При наличии в классе других конструкторов конструктор без параметров требует от программиста явного определения.
Деструктор
Деструктор решает задачу, обратную задаче конструктора. Он вызывается всякий раз, когда объект уничтожается. Имя деструктора состоит из знака тильды “~” и имени класса. Например,
~date().
Деструктор не имеет аргументов и не возвращает значения. Компилятор сам генерирует деструктор, если программист его не определил. Когда объект выходит из области видимости, деструктор вызывается автоматически.
Указатели на объекты
Объявление date* p создает указатель на объект класса date. Если p указывает на объект, обратиться к его членам можно при помощи операций (*p).set(), но для этого существует специальная операция косвенного выбора
p->set()
Указателю можно присвоить адрес уже существующего объекта.
ß
date d1;
date* p = &d1;
Каждый объект имеет указатель на себя, который обозначается идентификатором this. Он является скрытым параметром каждой функции-члена. Для класса X указатель this имеет тип:
X* const this;
Функцию-член можно объявить так, чтобы поля объекта были доступны ей только для чтения
int readme() const {/*тело функции*/};
Указатель this, передаваемый такой функции, будет иметь тип:
const X *const this.
Операции new и delete
Объект размещается в динамической памяти при помощи операции new.
ß
date* p = new date(19, 10, 2001);
Если выделить память не удалось, то по новому стандарту возбуждается исключение, по старому – возвращается 0.
Замечание. Программист может перегрузить операцию new для своего объекта и, тем самым, изменить ее поведение.
Удаляется объект из динамической памяти операцией delete.
ß
delete p;
Дважды удалять из динамической памяти один и тот же объект нельзя!
Статические элементы класса
С помощью модификатора static можно описать статические поля и методы класса. Их можно рассматривать как глобальные переменные или функции, доступные только в пределах области класса.
Статические поля
Статические поля применяются для хранения данных, общих для всех объектов класса, например, количества объектов или ссылки на разделяемый всеми объектами ресурс.
Эти поля существуют для всех объектов класса в единственном экземпляре, то есть не дублируются.
Особенности статических полей.
- Память под статическое поле выделяется один раз при его инициализации независимо от числа созданных объектов.
- Статические поля доступны как через имя класса, так и через имя объекта:
□ На статические поля распространяется действие спецификаторов доступа
□ Память, занимаемая статическим полем, не учитывается при определении размера объекта с помощью операции sizeof.
class A{
public:
static int cout;
};
int A::cout;
A b,*c;
void main()
{
cout<<A::cout<<endl;//10
cout<<b.cout<<endl;//10
cout<<c->cout<<endl;//10
A::cout=100;
cout<<A::cout<<endl;//10
cout<<b.cout<<endl;//10
cout<<c->cout<<endl;//10
/////////////////////////////
cin.get();
}
//---------------------------------------------------------------------------
Статические методы
Статические методы предназначены для обращения к статическим полям класса. Они могут обращаться непосредственно только к статическим полям и вызывать только другие статические методы класса, поскольку в им не передается скрытый указатель this. Обращение к статическим методам производится так же, как к статическим полям — либо через имя класса, либо, если хотя бы один объект класса уже создан, через имя объекта.
#include <iostream.h>
class B{
static int cout;
int data;
public:
static void inc_cout(){cout++;}
static int get_cout(){return cout;}
};
int B::cout(13);
void main()
{
B d,*e;
cout<<d.get_cout()<<endl;
d.inc_cout();
cout<<e->get_cout()<<endl;
cin.get();
}
//---------------------------------------------------------------------------
Статические методы не могут быть константными (const) и виртуальными (virtual).
Дружественные функции и классы
Иногда желательно иметь непосредственный доступ извне к скрытым полям класса, то есть расширить интерфейс класса. Для этого служат дружественные функции и дружественные классы.
Дружественная функция
Дружественные функции применяются для доступа к скрытым полям класса и представляют собой альтернативу методам.
Правила описания и особенности дружественных функций.
- Дружественная функция объявляется внутри класса, к элементам которого ей нужен доступ, с ключевым словом friend.
- В качестве параметра ей должен передаваться объект или ссылка на объект класса, поскольку указатель this ей не передается.
- Дружественная функция может быть обычной функцией или методом другого ранее определенного класса.
- Одна функция может быть дружественной сразу нескольким классами.
- Использования дружественных функций нужно по возможности избегать.
Дружественный класс
Если все методы какого-либо класса должны иметь доступ к скрытым полям другого, весь класс объявляется дружественным с помощью ключевого слова friend.
#include <iostream.h>
class TA{
int a;
int b;
public:
int d;
TA (int x=3,int y=4){a=x;b=y;}
friend void print(TA&);
friend class TB;
};
class TB{
public:
void inc_TA(TA& inca){inca.a++;};
void inc_TB(TA& incb){incb.b++;};
};
void print(TA &obj)
{cout<<obj.a<<"\t"<<obj.b<<endl;}
void main()
{
TA b,*c,d(4,5),*z;
c=new TA();
z=new TA(7,8);
print(b);
print(d); //4 5
print(*c);
print(*z);
delete c;
delete z;
TB q;
q.inc_TA(d);
q.inc_TB(d);
print(d);//5 6
cin.get();
}
Объявление friend не является спецификатором доступа и не наследуется.
Деструкторы
Деструктор — это особый вид метода, применяющийся для освобождения памяти, занимаемой объектом. Деструктор вызывается автоматически, когда объект выходит из области видимости:
□ для локальных объектов — при выходе из блока, в котором они объявлены;
□ для глобальных — как часть процедуры выхода из main:
□ для объектов, заданных через указатели, деструктор вызывается неявно при использовании операции delete.
Имя деструктора начинается с тильды (-), непосредственно за которой следует имя класса. Деструктор:
□ не имеет аргументов и возвращаемого значения;
□ не может быть объявлен как const или static;
□ не наследуется;
может быть виртуальным.
Если деструктор явным образом не определен, компилятор автоматически создает пустой деструктор.
Описывать в классе деструктор явным образом требуется в случае, когда объект содержит указатели на память, выделяемую динамически — иначе при уничтожении объекта память, на которую ссылались его поля-указатели, не будет помечена как свободная.
Указатель на деструктор определить нельзя.
Деструктор можно вызвать явным образом путем указания полностью уточненного имени:
Это может понадобиться для объектов, которым с помощью перегруженной операции new выделялся конкретный адрес памяти. Без необходимости явно вызывать деструктор объекта не рекомендуется.
Перегрузка операций
C++ позволяет переопределить действие большинства операций так, чтобы при использовании с объектами конкретного класса они выполняли заданные функции.
Можно перегружать любые операции, существующие в C++, за исключением:
..*?::: # ## sizeof
Перегрузка операций осуществляется с помощью методов специального вида (функций-операций) и подчиняется следующим правилам:
□ при перегрузке операций сохраняются количество аргументов, приоритеты операций и правила ассоциации (справа налево или слева направо), используемые в стандартных типах данных;
□ для стандартных типов данных переопределять операции нельзя;
□ функции-операции не могут иметь аргументов по умолчанию;
□ функции-операции наследуются (за исключением -);
□ функции-операции не могут определяться как static.
Функцию-операцию можно определить тремя способами: она должна быть либо методом класса, либо дружественной функцией класса, либо обычной функцией.
В двух последних случаях функция должна принимать хотя бы один аргумент, имеющий тип класса, указателя или ссылки на класс1.
Функция-операция содержит ключевое слово operator, за которым следует знак переопределяемой операции:
тип operator операция (список параметров) { тело функции }
Перегрузка унарных операций
#include <iostream.h>
class TA{
int a;
int b;
public:
TA (int x=3,int y=4){a=x;b=y;}
friend void print(TA&);
//перегрузка унарной операции
/*Унарная функция-операция, определяемая внутри класса, должна быть представлена с помощью нестатического метода без параметров, при этом операндом является вызвавший ее объект, например:
*/
TA& operator++(){a++;b++;return *this;}
//перегрузка бинарной операции: вызывающий объект считается первым операндом
const TA& operator=(const TA& operand2)
{if(&operand2==this) return *this;
//если класс содержит динамический массив необходимо удалить
//занимаемую память и выделить новую память нужного размера
//затем скопировать все элементы класса аналогично конструктору копирования
a=operand2.a;b=operand2.b;
}
TA operator+(TA& operand2){
TA sum;
sum.a=a+operand2.a;
sum.b=b+operand2.b;
return sum;
}
bool operator>(TA& operand2){
return (a>operand2.a&&b>operand2.b)? true: false;
}
//перегрузка потоков ввода вывода
//перегрузка потоков осуществляется только
//с помощью дружественных функций
friend ostream& operator<<(ostream& out,TA &obj)
{out<<"a= "<<obj.a<<"\tb="<<obj.b;
return out;}
friend istream& operator>>(istream& in,TA &obj)
{
cout<<"Vvedite a=";
in>>obj.a;
cout<<"Vvedite b=";
in>>obj.b;
return in;}
};
void main()
{
TA b(4,5),c(1,1);
print(b);
b++;
print(b);
TA s=b+c;
print(s);
if (s>b)cout<<">"<<endl;
cin>>s;
cout<<s<<endl;//a=6 b=7
cin.get();
cin.get();
}
//---------------------------------------------------------------------------
void print(TA &obj)
{cout<<obj.a<<"\t"<<obj.b<<endl;}
ЗАДАНИЯ НА ЛАБОРАТОРНУЮ РАБОТУ
Каждый разрабатываемый основной класс должен реализовывать:
1. конструктор по умолчанию
2. деструктор
3. конструкторы с параметрами
4. конструктор копирования
5. перегруженную операцию присваивания
6. перегруженную операцию вывода в поток
7. необходимые функции-элементы
Реализация элементов-данных не регламентируется, требуется только, чтобы класс полностью реализовывал заданный интерфейс.
Рекомендуется, где это необходимо, разрабатывать вспомогательные классы. Некоторые необходимые операции могут быть операциями этих вспомогательных классов.
ВАРИАНТ 1
Разработать класс, представляющий студента. Студент характеризуется именем, фамилией, группой и набором экзаменов, которые он сдавал. Экзамен характеризуется названием предмета, оценкой студента по нему и датой сдачи (год, семестр). Группа характеризуется курсом и факультетом.
Необходимые операции таковы:
1. узнать полное имя студента (имя+фамилия) и его курс
2. узнать, учится ли он на заданном факультете
3. узнать наивысшую оценку среди всех экзаменов по данному предмету
4. добавить ему оценку по экзамену
5. удалить для него оценку по экзамену
6. узнать число экзаменов, которые он сдал с указанной оценкой
7. узнать его средний балл за указанный семестр
ВАРИАНТ 2
Разработать класс, представляющий общежитие. Общежитие характеризуется улицей, номером дома, факультетом и набором комнат. Комната характеризуется номером, вместимостью и числом занятых мест. Факультет характеризуется институтом, названием и числом студентов.
Необходимые операции таковы:
1. узнать полный адрес общежития и к какому институту оно относится
2. узнать среднюю заселенность общежития
3. добавить для него комнату (открыть ее для заселения)
4. занять комнату (с проверкой)
5. освободить комнату
6. вернуть число свободных комнат (полностью/частично)
7. узнать, какой процент студентов института живет в общежитии
ВАРИАНТ 3
Разработать класс, представляющий факультет. Факультет характеризуется полным и кратким названием, институтом, к которому он относится и списком учебных групп. Институт характеризуется названием и адресом. Группа характеризуется названием, числом студентов и средним баллом по итогам последней сессии.
Необходимые операции таковы:
1. узнать полное и краткое название факультета и его институт
2. узнать, относится ли он к данному институту
3. узнать средний балл по всем группам с числом студентов выше
заданного
4. добавить группу (начало первого курса)
5. удалить группу (после диплома)
6. узнать число студентов данной группы
7. вернуть группу с наивысшим средним баллом
ВАРИАНТ 4
Разработать класс, представляющий преподавателя. Преподаватель характеризуется именем, фамилией, кафедрой, списком предметов, которые он читает. Кафедра относится к факультету и имеет имя. Предмет характеризуется названием, количеством часов и средним баллом, выставленным преподавателем на последней сессии по всем группам.
Необходимые операции таковы:
1. узнать имя и фамилию преподавателя и его кафедру
2. узнать, работает ли он на данном факультете
3. узнать среднее количество часов для всех предметов со средним баллом
не ниже данного
4. добавить предмет (внесли в учебный план)
5. удалить предмет (исключили из учебного плана)
6. найти самый низший средний балл по всем предметам
7. узнать, есть ли предмет с заданным количеством часов
ВАРИАНТ 5
Разработать класс, представляющий методическое пособие. Пособие характеризуется автором, названием, предметом и списком выдач экземпляров на руки. Выдача характеризуется датой и именем студента. Автор характеризуется именем, фамилией и названием кафедры.
Необходимые операции таковы:
1. узнать имя и фамилию автора пособия
2. узнать название, предмет пособия и кафедру
3. узнать среднее число выдач для всех студентов
4. выдать пособие на руки
5. получить пособие обратно
6. найти студента с наибольшим числом выдач
7. узнать число выдач для заданной даты
ВАРИАНТ 6
Разработать класс, представляющий книгу в библиотеке. Книга характеризуется списком авторов, названием и темой. Тема характеризуется кодом темы и названием темы. Автор характеризуется именем, фамилией, предпочтительной темой и числом написанных книг.
Необходимые операции таковы:
1. узнать название и тему книги
2. узнать, написал ли данный автор эту книгу
3. узнать среднее число написанных книг для всех авторов
4. добавить автора в книгу
5. вычеркнуть автора из книги
если автора нет - сгенерировать исключение
6. найти автора с максимальным числом написанных книг
7. найти число авторов, чья предпочтительная тема совпадает с темой
книги
ВАРИАНТ 7
Разработать класс, представляющий студенческую группу. Группа характеризуется факультетом, именем и списком студентов. Студент характеризуется именем, фамилией и средним баллом за последнюю сессию. Факультет характеризуется именем и профилем деятельности.
Необходимые операции таковы:
1. узнать имя и профиль группы
2. узнать, относится ли данная группа к данному факультету
3. узнать средний балл, рассчитанный по всем студентам с данным именем
4. принять студента в группу
5. исключить студента из группы
6. найти число студентов с данным средним баллом
7. найти средний балл указанного студента
ВАРИАНТ 8
Разработать класс, представляющий университет. Университет характеризуется названием, улицей, номером дома, списком факультетов и ректором. Ректор характеризуется именем, фамилией и ученой степенью. Факультет характеризуется названием, профилем и числом студентов.
Необходимые операции таковы:
1. узнать полный адрес университета
2. узнать имя, фамилию и ученую степень ректора
3. узнать число студентов данного факультета
4. открыть новый факультет
5. закрыть факультет
6. найти среднее число студентов по всем факультетам
7. найти общее число студентов всех факультетов данного профиля
ВАРИАНТ 9
Разработать класс, представляющий лекционный курс. Лекционный курс характеризуется названием, преподавателем, который его читает, группой и списком проведенных пар. Пара характеризуется датой и числом студентов, посетивших ее. Группа характеризуется названием и числом студентов.
Необходимые операции таковы:
1. узнать название курса и имя преподавателя
2. узнать число студентов группы, которой он читается
3. найти средний процент посещаемости по всем парам
4. провести пару
5. вычеркнуть проведенную пару
6. найти число пар, на которые пришло заданное число студентов
7. узнать, была ли стопроцентная посещаемость, если была, то когда в первый раз.
ВАРИАНТ 10
Разработать класс, представляющий учебный корпус института. Учебный корпус относится к институту и характеризуется названием и списком аудиторий. Институт характеризуется адресом и названием. Аудитория характеризуется номером, вместимостью и занятостью (занята/не занята).
Необходимые операции таковы:
1. узнать название института, название корпуса и адрес
2. узнать, относится ли данный корпус к данному институту
3. найти среднюю вместимость свободных аудиторий
4. добавить аудиторию (открыть ее для занятий)
5. удалить аудиторию (закрыть на ремонт)
6. узнать, занята ли данная аудитория
7. найти суммарную вместимость корпуса
Наследование, полиморфизм, инкапсуляция, исключения
2. Производные классы
Наследование
Производный класс (наследник) может получать атрибуты и поведение уже существующего базового класса. Наследование позволяет “вынести за скобки” то общее, что присуще нескольким классам — общие свойства придаются базовому классу, а отличия — его наследникам.
Пример базового и производного классов: date и birthday.
ß
class date {
int day, month, year;
public:
date(int,int,int);
date(char*);
};
class birthday: public date {
public:
string name;
};