Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Доступность компонентов класса




Ноткин, А.М.

Объектно-ориентированное программирование:ООП на языке С++:

Учебное пособие / А.М.Ноткин.– Пермь: Изд-во ПНИПУ – 182 с.

 

Пособие является первой частью многотомного издания, посвященного технологии объектно-ориентированного программирования и посвящено ООП на языке С++. Следующие тома будут посвящены ООП на языках Java, C# NET, Pyton и Ruby.

Задача пособия – дать студентам основные понятия ООП и технологию объектно-ориентированного программирования на языке С++. Подробно рассматриваются синтаксис, семантика и техника программирования. Приведено большое количество примеров, иллюстрирующих возможности и особенности применения языка С++ для создания объектно-ориентированных программ.

Предназначено для студентов направления «Информатика и вычислительная техника» как для самостоятельной работы, так и в рамках аудиторных занятий.

 

УДК 681.3

 

©ФГБОУ ВПО «Пермский национальный исследовательский политехнический университет», 2012

Содержание

1. Классы С++……………………………………………….5

1.1. Новый тип данных– класс. …………………………….5

1.2. Доступность компонентов класса……………………..7

1.3. Конструктор и деструктор……………………………..8

1.4. Компоненты– данные и компоненты– функции…….14

1.4.1. Данные– члены класса………………………………14

1.4.2. Функции– члены класса…………………………….14

1.4.3. Константные компоненты-функции………………..15

1.4.4. Статические члены класса………………………….15

1.5. Указатель на компоненты класса……………………..18

1.5.1. Указатель на компоненты– данные………………....18

1.5.2. Указатель на компоненты– функции……………….19

1.6. Указатель this…………………………………………...19

1.7. Друзья классов………………………………………….21

1.7.1. Дружественная функция……………………………..21

1.7.2. Дружественный класс………………………………..23

1.8. Определение классов и методов классов……………..24

2. Наследование……………………………………………..29

2.1. Определение производного класса……………………29

2.2. Конструкторы и деструкторы производных классов...32

2.3. Виртуальные функции…………………………………36

2.4. Абстрактные классы…………………………………...38

2.5. Включение объектов…………………………………..40

2.6. Включение и наследование……………………………42

2.7. Множественное наследование………………………...47

2.8. Локальные и вложенные классы………………………50

2.9. Пример программы…………………………………….54

2.10. Упражнения……………………………………………62

3. Перегрузка операций……………………………………..68

3.1. Перегрузка унарных операций………………………...69

3.2. Перегрузка бинарных операций……………………….70

3.3. Перегрузка операций ++ и --…………………………..71

3.4. Перегрузка операции вызова функции……………….72

3.5. Перегрузка операции присваивания………………….72

3.6. Перегрузка операции new……………………………..75

3.7. Перегрузка операции delete…………………………...80

3.8. Основные правила перегрузки операций…………….80

3.9. Примеры программ…………………………………….82

4. Шаблоны функций и классов…………………………....87

4.1. Шаблоны функций……………………………………..87

4.2. Шаблоны классов……………………………………....91

4.3.Компонентные функции параметризованных классов..92

4.4. Примеры программ……………………………………..95

5. Обработка исключительных ситуаций………………….100

5.1. Механизм обработки исключений в С++……………..100

5.2. Получение дополнительной информации

об исключении………………………………………………107

5.3. Определение типа исключения………………………..109

5.4. Иерархия исключений………………………………….111

5.5. Спецификация функций, обрабатывающих

исключения…………………………………………………..112

6. Потоковые классы………………………………………...114

6.1. Библиотека потоковых классов………………………..114

6.2. Ввод-вывод в языке С++……………………………….114

6.3. Стандартные потоки ввода-вывода……………………117

6.4. Форматирование………………………………………..118

6.5. Манипуляторы………………………………………….119

6.6. Ввод-вывод объектов пользовательских классов…….120

6.7. Определение пользовательских манипуляторов……..122

6.8. Пользовательские манипуляторы с параметрами…....124

6.9. Использование макросов для создания

манипуляторов ……………………………………………..125

6.10. Состояние потока…………………………………….126

6.11. Неформатированный ввод-вывод………………..….128

6.12. Файловый ввод-вывод……………………………….132

7. Новые возможности языка С++………………………...138

7.1. Пространство имен………………………………….....138

7.2. Динамическая идентификация типов………………...141

7.3.Безопасное приведение типа…………………………..143

8. Стандартная библиотека шаблонов…………………….146

8.1. Введение в STL…………………………………………146

8.2. Итераторы……………………………………………....148

8.3. Классы-контейнеры…………………………………....150

8.4. Контейнер vector……………………………………….153

8.5. Многомерные массивы…………………………….…..157

8.6. Ассоциативные контейнеры……………………….….160

8.7.Объекты-функции…………………………….…….…..167

8.8. Алгоритмы………………………………………..…….168

 

Приложение…………………………………………………174

 

 

1. Классы С++

 

Новый тип данных – класс

Целью введения концепции классов в С++ является предоставление программисту средств создания новых типов, которые настолько же удобны в использовании, как и встроенные типы. Тип является конкретным представлением некоторой концепции. Например, встроенный тип С++ float вместе с операциями +,-,* и т.д.является воплощением математической концепции вещественного числа. Класс– это определенный пользователем тип. Мы создаем новый тип для определения концепции, не выражаемой непосредственно встроенными типами. Например, мы могли бы ввести тип TrunkLine (междугородная линия) в программе, имеющей отношение к телефонии, тип Depositir (вкладчик) в программе управления банком или тип Pretator (хищник) в программе экологического моделирования.

Класс – фундаментальное понятие С++ и лежит в основе многих свойств С++. Класс предоставляет механизм для создания объектов. В классе отражены важнейшие концепции объектно-ориентированного программирования: инкапсуляция, наследование, полиморфизм.

С точки зрения синтаксиса класс в С++ – это структурированный тип, образованный на основе уже существующих типов.

В этом смысле класс является расширением понятия структуры. В простейшем случае класс можно определить с помощью конструкции:

тип_класса имя_класса{список_членов_класса};

где

тип_класса – одно из служебных слов class, struct, union;

имя_класса – идентификатор;

список_членов_класса – определения и описания типизированных данных и принадлежащих классу функций.

Функции – это методы класса, определяющие операции над объектом.

Данные – это поля объекта, образующие его структуру. Значения полей определяет состояние объекта.

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

Пример 1.1.1

struct date //дата

{int month,day,year; // поля: месяц, день, год

void set(int,int,int); // метод – установить дату

void get(int*,int*,int*); // метод – получить дату

void next(); // метод – установить следующую дату

void print(); // метод – вывести дату

};

Пример 1.1.2.

struct complex // комплексное число

{double re,im;

double real(){return(re);}

double imag(){return(im);}

void set(double x,double y){re = x; im = y;}

void print(){cout<<”re = ”<<re; cout<<“im = ”<<im;}

};

Для описания объекта класса (экземпляра класса) используется конструкция

имя_класса имя_объекта

date today,my_birthday;

date *point = &today; //указатель на объект типа date

date clim[30]; // массив объектов

date &name = my_birthday; //ссылка на объект

В определяемые объекты входят данные, соответствующие членам­ – данным класса. Функции – члены класса позволяют обрабатывать данные конкретных объектов класса. Обращаться к данным объекта и вызывать функции для объекта можно двумя способами. Во-первых, с помощью «квалифицированных» имен:

имя_объекта.имя_класса:: имя_данного

имя_объекта.имя_класса:: имя_функции

Имя класса может быть опущено

имя_объекта.имя_данного

имя_объекта.имя_функции

Например:

класс “комплексное число”

complex x1,x2;

x1.re = 1.24;

x1.im = 2.3;

x2.set(5.1,1.7);

x1.print();

 

Второй способ доступа использует указатель на объект

указатель_на_объект–>имя_компонента

complex *point = &x1; // или point = new complex;

point –>re = 1.24;

point –>im = 2.3;

point –>print();

 

Пример 1.1.3.

Класс “товары”

int percent=12; // наценка

struct goods

{char name[40];

float price;

void Input()

{cout<<“наименование: ”;

cin>>name;

cout<<“цена: ”;

cin>>price;}

void print()

{cout<<“\n”<<name;

cout<<“, цена: ”;

cout<<long(price*(1.0+percent*0.01));}

};

void main(void)

{ goods wares[5];

int k = 5;

for(int i = 0; i < k; i++) wares[i].Input();

cout<<“\nСписок товаров при наценке ”<<percent<<“ % ”;

for(i = 0; i < k; i++) wares[i].print();

percent = 10;

cout<<“\nСписок товаров при наценке ”<< percent<<” % ”;

goods *pGoods = wares;

for(i = 0; i < k; i++) pGoods++–>print();

}

 

Доступность компонентов класса

В рассмотренных ранее примерах классов компоненты классов являются общедоступными. В любом месте программы, где «видно» определение класса, можно получить доступ к компонентам объекта класса. Тем самым не выполняется основной принцип абстракции данных – инкапсуляция (сокрытие) данных внутри объекта. Для изменения видимости компонентов в определении класса можно использовать спецификаторы доступа: public, private, protected.

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

имя_объекта.имя_члена_класса;

ссылка_на_объект.имя_члена_класса;

указатель_на_объект->имя_члена_класса;

 

Собственные (private) компоненты локализованы в классе и не доступны извне. Они могут использоваться функциями – членами данного класса и функциями – «друзьями» того класса, в котором они описаны.

Защищенные (protected) компоненты доступны внутри класса и в производных классах. Защищенные компоненты нужны только в случае построения иерархии классов. Они используются так же, как и private-члены, но дополнительно могут использоваться функциями – членами и функциями – «друзьями» классов, производных от описанного класса.

Изменить статус доступа к компонентам класса можно и с помощью использования в определении класса ключевого слова class. В этом случае все компоненты класса по умолчанию являются собственными.

Пример 1.2.1.

class complex

{

double re, im; // private по умолчанию

public:

double real(){return re;}

double imag(){return im;}

void set(double x,double y){re = x; im = y;}

};

Современный стиль программирования рекомендует для определения класса использовать ключевое слово class.

 

Конструктор и деструктор

Недостатком рассмотренных ранее классов является отсутствие автоматической инициализации создаваемых объектов. Для каждого вновь создаваемого объекта необходимо было вызвать функцию типа set (как для класса complex) либо явным образом присваивать значения данным объекта. Однако для инициализации объектов класса в его определение можно явно включить специальную компонентную функцию, называемую конструктором. Формат определения конструктора следующий

имя_класса(список_форм_параметров)

{операторы_тела_конструктора};

Имя этой компонентной функции по правилам языка С++ должно совпадать с именем класса. Такая функция автоматически вызывается при определении или размещении в памяти с помощью оператора new каждого объекта класса.

Пример 1.3.1.

сomplex(double re1 = 0.0,double im1 = 0.0){re = re1; im = im1;}

Конструктор выделяет память для объекта и инициализирует данные – члены класса.

Конструктор имеет ряд особенностей:

* Для конструктора не определяется тип возвращаемого значения. Даже тип void не допустим.

* Указатель на конструктор не может быть определен и, соответственно, нельзя получить адрес конструктора.

* Конструкторы не наследуются.

* Конструкторы не могут быть описаны с ключевыми словами virtual, static, const, mutuable, valatile.

Конструктор всегда существует для любого класса, причем, если он не определен явно, он создается автоматически. По умолчанию создается конструктор без параметров и конструктор копирования. Если конструктор описан явно, то конструктор по умолчанию не создается. По умолчанию конструкторы создаются общедоступными (public).

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

имя_класса имя_объекта(фактические_параметры);

имя_класса(фактические_параметры);

Первая форма допускается только при непустом списке фактических параметров. Она предусматривает вызов конструктора при определении нового объекта данного класса:

complex ss(5.9,0.15);

Вторая форма вызова приводит к созданию объекта без имени:

complex ss = complex(5.9,0.15);

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

имя_данного(выражение)

Пример 1.3.2.

class A

{

int i; float e; char c;

public:

A(int ii,float ee,char cc): i(8),e(i * ee + ii),с(сс){}

...

};

Пример 1.3.3. Класс "символьная строка”.

#include <string.h>

#include <iostream.h>

class string

{

char *ch; // указатель на текстовую строку

int len; // длина текстовой строки

public:

// конструкторы

// создает объект – пустая строка

string(int N = 80): len(0){ch = new char[N+1]; ch[0] = ‘\0’;}

// создает объект по заданной строке

string(const char *arch){len = strlen(arch);

ch = new char[len+1];

strcpy(ch,arch);}

// компоненты-функции

// возвращает ссылку на длину строки

int& len_str(void){return len;}

 

// возвращает указатель на строку

char *str(void){return ch;}

...

};

Здесь у класса string два конструктора – перегружаемые функции.

По умолчанию создается также конструктор копирования вида T::T(const T&), где Т – имя класса. Конструктор копирования вызывается всякий раз, когда выполняется копирование объектов, принадлежащих классу. В частности, он вызывается:

а) когда объект передается функции по значению;

б) при построении временного объекта как возвращаемого значения функции;

в) при использовании объекта для инициализации другого объекта.

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

string s1(“это строка”);

string s2=s1;

Здесь объект s2 инициализируется объектом s1 путем вызова конструктора копирования, созданного компилятором по умолчанию. В результате эти объекты имеют одинаковое значение в полях ch, то есть эти поля указывают на одну и ту же область памяти. В результате при удалении объекта s1 будет освобождаться и область, занятая строкой, но она еще нужна объекту s2. Чтобы не возникало подобных ошибок, определим собственный конструктор копирования.

string(const string& st)

{len=strlen(st.len);

ch=new char[len+1];

strcpy(ch,st.ch); }

 

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

Например:

class complex

{double re,im;

complex(double r):re(r),im(0){ }

...

};

Этот конструктор реализует представление вещественной оси в комплексной плоскости.

Вызвать этот конструктор можно традиционным способом:

complex b(5);

Но можно вызвать его и так:

complex b=5;

Здесь необходимо преобразование скалярной величины (типа аргумента конструктора) в тип complex. Это осуществляется вызовом конструктора с одним параметром. Поэтому конструктор, имеющий один аргумент, не нужно вызывать явно, а можно просто записать complex b=5, что означает complex b = complex(3).

Преобразование, определяемое пользователем, неявно применяется в том случае, если оно уникально. Например,

class demo{

demo(char);

demo(long);

demo(char*);

demo(int*);

...}

Здесь в

demo a=3;

неоднозначность: вызов demo(char)? или demo(long)?

А в demo a=0; также неоднозначность: вызов demo(char*) или demo(int*), или demo(char), или demo(int)?

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

class string{

char * ch;

int len;

public:

string(int size){

len=size; ch=new[len+1]; ch[0]=’\0’;

};

В этом случае неявное преобразование может привести к ошибке. В случае string s=’a’; создается строка длиной int(‘a’). Вряд ли это то, что мы хотели.

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

class string{

char * ch;

int len;

public:

explicit string(int size);

string(const char* ch);

};

string s1=’a’; // Ошибка,

// нет явного преобразования char в string

string s2(10); // Правильно,

//строка для хранения 10-ти символов-

//явный вызов конструктора

string s3=10; //Ошибка, нет явного преобразования int в string

string s4=string(10); // Правильно,

// конструктор вызывается явно

string s5=”строка”;// Правильно,

// неявный вызов конструктора s5=string(“строка”)

Можно создавать массив объектов, однако при этом соответствующий класс должен иметь конструктор по умолчанию (без параметров).

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

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

class demo{

int x;

public:

demo(){x=0;}

demo(int i){x=i;}

};

void main(){

class demo a[20]; //вызов конструктора без параметров (по умолчанию)

class demo b[2]={demo(10),demo(100)};//явное присваивание

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

Динамическое выделение памяти для объекта создает необходимость освобождения этой памяти при уничтожении объекта. Например, если объект формируется как локальный внутри блока, то целесообразно, чтобы при выходе из блока, когда уже объект перестает существовать, выделенная для него память была возвращена. Желательно, чтобы освобождение памяти происходило автоматически. Такую возможность обеспечивает специальный компонент класса – деструктор класса. Его формат

~имя_класса(){операторы_тела_деструктора};

Имя деструктора совпадает с именем его класса, но предваряется символом “~” (тильда).

Деструктор не имеет параметров и возвращаемого значения. Вызов деструктора выполняется неявно (автоматически), как только объект класса уничтожается.

Например, при выходе за область определения или при вызове оператора delete для указателя на объект.

string *p=new string(“строка”);

delete p;

Если в классе деструктор не определен явно, то компилятор генерирует деструктор по умолчанию, который просто освобождает память занятую данными объекта. В тех случаях, когда требуется выполнить освобождение и других объектов памяти, например область, на которую указывает ch в объекте string, необходимо определить деструктор явно: ~string(){delete []ch;}

Так же как и для конструктора, не может быть определен указатель на деструктор.

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...