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

книги / Технологии разработки объектно-ориентированных программ на язык C++. Основы объектно-ориентированного программирования на алгоритмическом языке C++

.pdf
Скачиваний:
7
Добавлен:
12.11.2023
Размер:
876.09 Кб
Скачать

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

Например, член класса Car:

static int counter; // Объявление статического члена

Инициализация (в файле исходного кода .cpp):

int Car::counter = 5; // Инициализация статического члена

17.5. Конструктор копирования и операция присваивания

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

Объявление конструктора копирования выполняется следующим образом:

имя_класса(const имя_класса &);

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

Операция присваивания объявляется следующим образом:

имя_класса & operator=(const имя_класса &);

Операция присваивания и конструктор копирования необходимо переопределить, когда необходимо глубокое копирование. Например, копирование данных, находящихся в «куче». Копирование по умолчанию просто скопирует указатели на данные, и в итоге получится два объекта, указывающих на одни и те же данные, и при освобождении памяти одним данные пропадут и у второго.

21

Пример. Разработка класса Car с реализацией конструктора копирования и операции присваивания

//Объявление класса, заголовочный файл car4.h #ifndef CAR_H_

#define CAR_H_ class Car

{

private:

static int counter;

int ls = 0; // Мощность автомобиля int weight = 0; // Вес автомобиля

public:

Car(); // Конструктор без параметров

Car(int ls, int weight = 1500); // Конструктор c

параметрами

Car(const Car &); // Конструктор копирования

Car& operator=(const Car &); // Операция присваивания

~Car(); // Деструктор

};

#endif

//Определение методов класса, файл исходного кода car4.cpp

#include"stdafx.h"

#include<iostream>

//Определение конструктора без параметров

Car::Car() { ls = 150;

weight = 1500;

}

// Определение конструктора с параметрами

Car::Car(int ls, int weight) { counter++;

this >ls = ls; this >weight = weight;

}

22

//Определение деструктора

Car::~Car() { counter ;

std::cout <<"Автомобиль больше не используется!\n";

}

//Определение операции присваивания

Car & Car::operator=(const Car &t) { ls = t.ls;

weight = t.weight;

}

// Определение конструктора копирования

Car::Car(const Car &t) { counter++;

ls = t.ls;

weight = t.weight;

}

17.6. Список инициализаторов – членов класса

Обычно в программировании инициализация констант выполняется в конструкторе.

Например:

class Person { const int age; int money, exp; public:

person(int a, int b, int c) { age = a;

}

}

В коде видно, что age – константа, ее можно инициализировать, но ей нельзя присвоить значение. С концептуальной точки зрения вызов конструктора создает объект до того, как выполняется код внутренних фигурных скобок. Исходя из этого при вызове конструктора программа сначала выделяет память для данных (для age), а за-

23

тем выполняет код конструктора и заносит значения в выделенную память с помощью обычного присваивания. Следовательно, инициализацию age необходимо осуществить после того, как объект создан, но до того, как начнет выполняться конструктор. Таким образом, для инициализации константных данных класса в С++ используют список инициализаторов членов. Например, реализация списка инициализаторов – членов класса выглядит следующим образом:

имяКласса(аргументы): имяПеременной(аргумент), имяПеременной(аргумент)

person(int a) : age(a) {…} // Конкретный пример

Здесь age(a) – инициализатор. Такие инициализаторы могут применяться и для неконстантных данных, но это не имеет смысла.

Такой список инициализаторов – членов класса может применяться только в конструкторах.

24

Глава 18. НАСЛЕДОВАНИЕ КЛАССОВ

18.1. Основы наследования

Наследование – это механизм, позволяющий описать новый класс на основе уже существующего с частично или полностью заимствованной функциональностью [14]. Класс, от которого производится наследование, называется базовым или родительским. Новый класс – потомком, наследником или производным классом.

Синтаксис определения производного класса выглядит следующим образом:

class имя_класса : список_базовых_классов { список компонентов класса

}

При наследовании доступ к членам базового класса из производного ведется в соответствии с модификаторами доступа (public, private, protected). Когда программа создает объект производного класса, сначала конструируется объект базового класса. Это означает, что объект базового класса должен быть создан до того, как программа войдет в тело конструктора производного класса. Для этого в С++ применяют списки инициализаторов-членов. Если опустить вызов конструктора базового класса, то программа воспользуется конструктором базового класса по умолчанию.

Пример. Пример простого наследования Простым называется наследование, когда производный класс

имеет одного родителя.

#include <iostream>

// Базовый класс Animal (животное) (файл Animal.h) class Animal {

public: int age;

Animal(int age = 0) { this >age = age; }

void walk() { std::cout <<"Животное бегает"; }

25

};

// Производный класс Rabbit (кролик), наследуется от

Animal

class Rabbit : public Animal { private:

int color = 0; public:

// Переопределение метода walk базового класса Animal void walk() { std::cout <<"Кролик бегает"; }

};

18.2. Конструкторы и деструкторы при наследовании

Следует знать, что конструкторы и деструкторы не наследуются. Для вызова родительского конструктора используют списки инициализаторов-членов. Ниже перечислены основные моменты, которые следует знать о конструкторах для производных классов. Для конструкторов производных классов необходимо выполнение следующих правил:

Сначала создается объект базового класса.

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

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

Удаление объектов с помощью деструкторов происходит

впорядке, обратном порядку их создания.

Пример. Конструкторыидеструкторыпринаследовании классов

#include <iostream>

// Базовый класс Animal (животное) (файл Animal1.h) class Animal {

public: int age;

Animal(int age = 0) { this >age = age; }

void walk() { std::cout <<"Животное бегает"; }

26

// Деструктор базового класса

~Animal() { std::cout <<"Животное убежало:("; } };

// Производный класс Rabbit (кролик), наследуется от

Animal

class Rabbit : public Animal { private:

int color = 0; // Добавленный производным классом элемент public:

//Конструктор Rabbit, вызывает конструктор Animal через

//список инициализаторов

Rabbit(int c, int age) : Animal(age) { color = c;

}

// Переопределение метода walk базового класса Animal void walk() { std::cout <<"Кролик бегает"; }

/*

Так как деструктор не определен, то компилятор создаст деструктор по умолчанию, который после своего

выполнения

вызовет деструктор Animal

*/

};

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

Rabbit one; // Создан объект one класса Rabbit

Animal* ptr = &one; // Создан указатель на базовый класс

//Animal,который указывает на объект

//one производного класса Rabbit

Animal& ref = one; // Создана ссылка на базовый класс

//Animal, которая указывает на объект

//one производного класса Rabbit

27

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

18.3. Полиморфное открытое наследование

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

переопределение методов базового класса в производном

классе;

использование виртуальных методов.

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

СОВЕТ: Если необходимо переопределять какой-либо метод базового класса, то обычно такой метод объявляется в базовом классе как виртуальный. Тогда программа выбирает версию метода, основываясь на типе объекта, а не на типе ссылки или указателя.

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

18.4. Статическое и динамическое связывание

Какой блок исполняемого кода выполняется, когда программа вызывает функцию? На этот вопрос отвечает компилятор. Интерпретация вызова функции в исходном коде в виде выполнения определенной части кода называется связыванием имени функции. При этом в С++ компилятор должен учесть не только имя, но и ар-

28

гументы функции, чтобы определить, какую функцию использовать. Такой тип связывания выполняется во время компиляции. Связывание, выполняемое во время компиляции, называется статическим (или ранним) связыванием. Однако виртуальные функции усложняют ситуацию.

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

18.5. Абстрактные базовые классы

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

Чтобы можно было объявить класс как базовый абстрактный, он должен содержать, по крайней мере, одну чистую виртуальную функцию. Обычная виртуальная функция превращается в чистую с помощью конструкции «= 0» в прототипе. Тогда становится логичным следующее: если объявление класса содержит чистую виртуальную функцию, то объект такого класса создать невозможно, а все наследники обязаны перегрузить данную функцию.

Чистая виртуальная функция реализуется следующим образом: virtual void engine() const = 0;

Пример. Объявление и использование абстрактного базового класса (АБК) Animal

#include <iostream>

// Абстрактный базовый класс Animal (животное) (файл

Animal2.h)

29

// Так как Animal теперь АБК – от него нельзя создать объект

class Animal { public:

int age;

Animal(int age = 0) { this >age = age; }

virtual void walk() = 0; // Чистая виртуальная функция, все

// наследники обязаны

перегрузить ее

~Animal() { std::cout <<"Животное убежало:("; } };

// Производный класс Rabbit (кролик) наследуется от АБК

Animal

class Rabbit : public Animal { private:

int color = 0; public:

//Конструктор Rabbit вызывает конструктор Animal через список

//инициализаторов членов

Rabbit(int c, int age) : Animal(age) { color = c;

}

// Обязательно перегружаем чистую виртуальную функцию void walk() { std::cout <<"Кролик бегает"; }

};

18.6. Множественное наследование

Множественное наследование описывает класс, у которого есть несколько базовых классов (рис. 18.1):

class car : private engine, private transmission { … }

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

30

Соседние файлы в папке книги