Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Основные понятия объектно-ориентированного программирования справочник с примерами на С++. Учебно-методическое пособие

.pdf
Скачиваний:
0
Добавлен:
09.08.2026
Размер:
384 Кб
Скачать

7. Что выведет на экран эта программа? Запишите все символы в одной строке без пробелов.

class TCount { int count;

public:

TCount() {count = 0; cout << count; } void print() { count ++; cout << count; }

};

int main()

{

TCount p; p.print(); p.print();

}

8. Какая команда создает в памяти новый объект класса TCount, вызывая конструктор без параметров, и записывает его адрес в переменную p. Выберите верный вариант ответа:

1.p = TCount;

2.p = TCount();

3.p = TCount.TCount()

4.TCount p;

5.TCount(p);

11

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

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

После спецификатора следует двоеточие, и все компоненты класса, указанные от одного спецификатора до другого, имеют указанный статус.

В примере с комплексными числами мы использовали спецификатор public, чтобы сделать функции общедоступными, и private – чтобы скрыть от внешнего использования поля a и b.

Вопросы для самоконтроля

1.Для чего используются ключевые слова public и private?

2.Чем отличается модификатор доступа protected от модификаторов private и public?

3.Обязательно ли делать поля класса приватными?

4.Может ли метод быть приватный?

12

5 Наследование. Иерархия классов

При наследовании используется следующая терминология: базовые (по-

рождающие, родительские, классы-предки, наследуемые) классы – уже имеющиеся классы, производные (порожденные, дочерние, классы-потомки,

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

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

При наследовании некоторые имена методов или полей данных базового класса могут быть переопределены в производном, при этом для доступа к переопределенным компонентам из базового класса используют операцию :: – операцию указания области видимости.

Наследование может быть нескольких видов (или типов). Типы определяются правами доступа к полям класса. Шаблон создаваемого класса выглядит следующим образом:

class имя_нового_класса-потомка : тип_наследования имя_класса-родителя { тело класса;

}

По умолчанию вид наследования private для class. В С++ возможно наследование сразу из нескольких классов (так называемое множественное наследование), тогда базовые классы перечисляются через запятую.

Режимы доступа при наследовании классов представлены в Таблице 5.1:

Таблица 5.1 – Режимы доступа при наследовании классов

Спецификатор доступа перед ба-

Объявление компонентов в

Видимость компонен-

зовым классом

классе-родителе

тов в классе-потомке

(вид наследования)

 

 

private

Private

Не доступны

 

Protected

private

 

 

 

 

Public

private

 

 

 

protected

Private

Не доступны

 

 

 

 

Protected

protected

 

 

 

 

Public

protected

 

 

 

public

Private

Не доступны

 

 

 

 

Protected

protected

 

 

 

 

Public

public

 

 

 

 

13

 

Вопросы для самоконтроля

1.Как называется передача базовым классом своих свойств и методов производному классу?

2.Какие типы наследования есть в С++?

3.Какие функции не наследуются?

4.Можно ли в производном классе объявлять новые поля? А методы?

5.Отметьте все пары классов, которые связаны отношениями наследования:

1)фрукт - яблоко

2)автомобиль - двигатель

3)строение - сарай

4)принтер - документ

5)компьютер - процессор

14

6 Методы – элементы класса

6.1 Определение и доступ

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

Пример описания методов:

class Print { void Hello()

{

cout << “Hello! ” << endl;

}

void Bye()

{

cout << “Bye! ” << endl;

}

int Summ(int a, int b)

{// метод, принимающий два параметра и // возвращающий их сумму

return a + b;

}

};

Доступ к методам (их вызов) происходит аналогично доступу к полям класса:

Print temp = new Print(); // создаем объект класса temp.Hello(); // на экране появится надпись «Hello!» temp.Bye(); // на экране появится надпись «Bye!»

cout << Summ(4,5) << endl; //на экран будет выведена надпись

«9»

6.2 Сокрытие методов базового класса

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

Пример сокрытия метода базового класса:

class A { public:

void fun(int){};

};

class B: public A { public:

void fun(long){};

};

15

int main()

{

B obj;

obj.fun(1L); // аргумент функции – единица типа long

}

В данном примере будет запущена функция из класса B, в которой принимается аргумент типа long.

Можно вызвать метод родительского класса в наследуемом классе с помощью конструкции:

родительский_класс::метод (аргументы);

Например,

#include <iostream> using namespace std; class A {

public:

void print(){

cout << "I’m A" << endl;

}

};

class B : public A { public:

void print(string str) { cout << str << endl;

A::print();//вызываем метод родительского класса

}

};

int main()

{

B obj;

obj.print("Hello"); //аргумент функции типа string

}

На экран будет выведено:

Hello

I’m A

6.3 Ключевое слово this

this – служебное слово, указатель на конкретный объект, с которым происходит та или иная работа в данный момент времени. Обращение к нему происходит следующим образом:

this -> pole; this -> func();

Указатель this не требуется объявлять, он существует всегда как неявный параметр метода класса. При входе в тело функции, принадлежащей классу, this

16

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

Данное ключевое слово удобно использовать, когда в методе присутствуют переменные, имена которых совпадают с именами полей класса. В этом случае обращение к переменной происходит как обычно – по ее имени, а обращение к полям класса – через ключевое слово this (this->param).

Служебное слово this используют, когда в теле функции необходимо явно задать адрес того объекта, для которого она вызвана.

6.4 Статические методы класса

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

Например,

static void func(){};

Статический метод означает, что он один на все экземпляры класса. Статические методы могут использоваться без создания экземпляра класса и могут работать только со статическими компонентами класса. Вызывать статический метод можно, как и не статический, через объект класса, а также через явное указание класса:

родительский_класс::метод (аргументы);

6.5 Раннее и позднее связывание. Виртуальные методы

Вспомним про полиморфизм: имя метода одно, а реализаций может быть несколько. Так вот, при вызове метода класса программа должна определить, с какой именно реализацией связать данный вызов. Есть два вида связывания: на этапе компиляции и во время выполнения программы. Рассмотрим оба варианта.

Раннее (статическое) связывание – это механизм, при котором объект и вызов метода связываются между собой на этапе компиляции, т.е. на этапе компиляции уже известно, какой именно метод будет вызван во время работы программы. Например,

#include <iostream> using namespace std;

class A { public:

void print(){ cout << "I’m method of class A" << endl;}

};

17

class B : public A { public:

void print(){cout << "I’m method of class B" << endl;}

};

int main()

{

B obj; obj.print();

}

В консоль будет выведено:

I’m method of class B

Ещё пример:

#include <iostream> using namespace std;

class shape { public:

void square(){ print();

}

void print(){

cout << "I’m the square of the figure" << endl;

}

};

class rect : public shape { public:

void print(){

cout << "I’m the square of the rect" << endl;

}

};

int main()

{

shape obj1; obj1.square();

rect obj2; obj2.square();

}

В консоль будет выведено:

I’m the square of the figure

I’m the square of the figure

Т.е. ещё до выполнения программы понятно, что при вызове метода square(), независимо от того, объект какого класса его вызовет, сработает метод print() класса shape. Хотя логичнее было бы видеть площадь прямоугольника при создании объекта класса Прямоугольника (rect) и вызове метода Площадь (square();).

18

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

Позднее (динамическое) связывание – это механизм, при котором объект и вызов метода связываются между собой на этапе выполнения программы, т.е. на этапе компиляции ещё неизвестно, какой именно метод будет вызван во время работы программы.

Для реализации механизма позднего связывания применяют виртуальные методы.

Виртуальный метод – это метод, объявленный в базовом классе с помощью спецификатора virtual, в классе наследнике он имеет такую же сигнатуру,

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

механизму динамического (позднего) связывания – объект и вызов функции связываются уже во время исполнения программы.

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

Пример создания виртуального метода:

#include <iostream> using namespace std;

class Language { public:

virtual void PrintError(int n){//объявляем виртуальный метод cout << "Null" << endl;

}

};

class CPP : public Language { public:

void PrintError(int n){ if(n == 1){

cout << "Fatal error!" << endl;

}

}

};

int main(){

Language *obj = new CPP(); obj->PrintError(1);

}

В данном примере мы объявили виртуальную функцию PrintError в базовом классе и переопределили ее в производном (спецификатор virtual в производном

классе можно опустить, как в примере).

19

В консоль будет выведено после выполнения примера:

Fatal error!

Если уберем спецификатор virtual в базовом классе, то результат работы программы будет другим:

Null

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

Одно из основных применений виртуальных методов – для создания мас-

сива, содержащего объекты разных классов (одной иерархии). Тип такого массива

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

Пример:

#include <iostream> using namespace std;

class Language { public:

virtual void PrintError(int n) { cout << "Null" << endl;

}

};

class CPP : public Language {

void PrintError(int n) { if (n == 1) {

cout << "Fatal error from CPP!" << endl;

}

}

};

class CSHARP : public Language { void PrintError(int n) {

if (n == 1) {

cout << "Fatal error from C#!" << endl;

}

}

};

int main()

{

Language *obj[2]; obj[0] = new CPP(); obj[1] = new CSHARP(); obj[0]->PrintError(1); obj[1]->PrintError(1);

return 0;

}

20

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]