Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
шпора к КПИЯП.docx
Скачиваний:
41
Добавлен:
25.02.2016
Размер:
135.65 Кб
Скачать

Объектно-ориентированное программирование

 

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

Рассмотрим более подробно ключевые понятия ООП.

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

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

 

Принципы объектно-ориентированного программирования:

1) абстрагирование;

2) инкапсуляция;

3) наследование;

4) полиморфизм.

 

Абстрагирование.

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

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

 

Инкапсуляция: пакетирование данных

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

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

Это механизм языка программирования, который позволяет объединять данные и код, взаимодействующий с этими данными. Описанный класс представляет собой новый тип данных. Экземпляр (элемент) нового типа представляет собой объект. Часть данных или кода может быть защищена от воздействия и использования вне описания объекта (класса).

 

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

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

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

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

Рисунок 14.1 Пример наследования объектов

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

Часто объекты одного класса на самом деле являются также и объектами другого класса. Прямоугольник являетсятакже и четырехугольник (как и квадрат, и параллелограмм, и трапеция). Таким образом, можно сказать, что класс Rectangle (прямоугольник) является наследником класса Quadrilaterial (четырехугольник). В этом контексте класс Quadrilaterial яв­ляется базовым классом, а класс Rectangle — производным классом. Пря­моугольник является специальным типом четырехугольника, но неверно ут­верждать, что четырехугольник является прямоугольником

Листинг 14.1 Пример открытого наследования классов

    1:  classbase {

2:        int x;

3:  public:

4:         void setx(int n) {x=n;}

5:         void showx() {cout<<x<<'\n';}

6:  };

7:  class derived: public base {

8:         int y;

9:  public:

10:         void sety(int n) {y=n;}

11:         void showy() { cout<<y<<'\n';}

12:  };

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

2.2 Классы. Объекты. Члены классов. Спецификаторы доступа.

В языке С++ новый тип данных создается в результате объявления класса. Класс — это набор переменных, зачастую различных типов, объединенный с набором функций, предназначенных для работы с ними.

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

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

Класс может состоять из комбинации переменных любых типов, а также других классов. Переменные в классе называют переменными-членами, или данными-членами. Класс Саг (автомобиль) может иметь переменные-члены, представляющие сидения, радиоприемник, шины и т.д.

Переменные-члены, известные также как данные-члены, являются переменными этого класса. Переменные-члены — такие же составные части класса, как колеса и мотор — составные части автомобиля.

Функции в классе обычно выполняют действия над переменными-членами. Они называются функциями-членами, или методами класса. К методам класса Саг можно отнести Start () (разгоняться) и Break () (тормозить). Класс Cat (кот) может иметь такие данные-члены, как Age и Weight (возраст и вес), а его методами могут быть Sleep (), Meow () и ChaseMice() (спать, мяукать и ловить мышей). Подобно переменным-членам, функции-члены являются составной частью класса. Именно они определяют, что данный класс может делать.

Объявление класса

  Для объявления класса используется ключевое слово class, за которым следует открывающая фигурная скобка, список данных-членов и методов класса. Объявление завершается закрывающей фигурной скобкой и точкой с запятой. Вот как выглядит объявление класса Cat:

class Cat {

unsigned int itsAge;

unsigned int itsWeight;

void Meow();

} ;

 

При объявлении класса Cat память не резервируется. Это объявление просто сообщает компилятору о существовании класса Cat, о том, какие данные он содержит (переменные itsAge и itsweight), а также о том, что он умеет делать (метод Meow()). Кроме того, объявление сообщает компилятору о размере класса Cat, т.е. сколько места должен зарезервировать компилятор для каждого объекта класса Cat. Поскольку в приведенном примере для целого значения требуются четыре байта, размер объекта Cat составит восемь байтов (четыре байта для переменной itsAge и четыре — для itsWeight). Метод Meow() не требует выделения памяти, поскольку для функций-членов (методов) объекта пространство в памяти не резервируется.

Определение объекта

 Объект нового типа определяется точно так же, как и любая целочисленная переменная:

unsigned int GrossWeight; // определение беззнакового целого

Cat Frisky;      // определение объекта Cat

В этом коде определяется переменная GrossWeight, которая имеет тип unsigned int, а также определяется объект Frisky класса (или типа) cat.

Классы и объекты

 Кот — это разновидность домашнего животного, но никто не заводит дома разновидность, обычно покупают конкретного живого котенка. То есть, существует различие между абстрактным котом как понятием и конкретным котенком Фриски, который сейчас разгуливает по гостиной автора. Точно так же в языке С++ существует различие между классом Cat, который является концепцией кота, и каждым конкретным объектом класса Cat, который гуляет сам по себе. Таким образом, Frisky — это объект типа Cat, точно так же, как и GrossWeight является переменной типа unsigned int. Объект — это конкретный экземпляр абстрактного класса.

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

 После определения реального объекта класса Cat, например, Cat Frisky;, возникает необходимость получить доступ к членам этого объекта. Для этого используется точечный оператор (.), который позволяет обратиться к элементам объекта непосредственно. Следовательно, чтобы присвоить число 50 переменной-члену weight объекта Frisky, можно написать: Frisky.itsWeight = 50;

Аналогично для вызова метода Meow () достаточно использовать следующую запись: Frisky.Meow();

Когда необходимо использовать определенный метод класса, выполняется вызов этого метода. В данном примере осуществляется вызов метода Meow() объекта Frisky.

 

Значения присваивают объектам, а не классам

 В языке С++ нельзя присвоить значение типу данных, они присваиваются только переменным. Например, нельзя написать: int = 5;          // неверно

Компилятор пометит эту строку как ошибочную, поскольку нельзя присваивать число 5 типу int. Вместо этого нужно определить целочисленную переменную и присвоить ей число 5. Например:

int х;         // определить х как переменную типа int

х = 5;        // присвоить переменной х значение 5

Таким образом, число 5 присваивается переменной х, которая имеет тип int. Точно так же недопустима следующая строка:

Cat.itsAge = 5;    // неверно

Компилятор снова пометит эту строку как ошибочную, поскольку нельзя присвоить число 5 части класса Cat, ведь это всего лишь декларация. Сначала необходимо создать объект класса Cat, а потом его переменной itsAge присвоить значение 5. Например:

Cat Frisky;           // то же, что и int х;

Frisky.itsAge =5; // то же, что и х = 5;

Закрытые и открытые члены класса

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

Ключевые слова public и private применимы для всех членов класса: и переменных-членов, и методов. К закрытым членам могут обращаться только те методы, которые принадлежат этому классуОткрытые члены доступны для всех других функций программы. Это отличие крайне важно, но не совсем понятно. Чтобы прояснить ситуацию, рассмотрим пример, который уже приводился в этой главе.

class Cat

{

unsigned int itsAge;

unsigned int itsWeight;

void Meow();

} ;

 Здесь объявлены закрытые переменные itsAge и itsWeight, а также закрытый метод Meow(), поскольку по умолчанию все члены класса являются закрытыми. Это значит, что если член класса не указан как открытый явно, то он считается закрытым.

Например, если в функции main() написать нижеследующее, то компилятор пометит эту строку как ошибку:

 int main() {

Cat Boots;

Boots.itsAge = 5; // Ошибка! Нельзя обращаться к закрытым данным!

Фактически компилятору было сказано: "Все обращения к itsAge, itsWeight и Meow() осуществляются только функциями-членами класса Cat". Но здесь происходит обращение к переменной itsAge (члену объекта Boots) извне объекта, принадлежащего классу Cat. Только то, что Boots является объектом класса Cat, еще не означает, что ко всем его элементам можно свободно обратиться (даже при том, что они видимы в объявлении).

Именно эти моменты являются источником бесконечных недоразумений у начинающих программистов. Автор прямо-таки слышит возмущенный вопль: "Эй! Вы же только что сказали, что Буч — это кот. Почему же он не может обратиться к своему собственному возрасту?" Ответ прост: Boots может, а разработчик — нет. Boots в своих собственных методах может обращаться ко всем своим элементам — и к открытым, и к закрытым. Существование объекта класса Cat вовсе не гарантирует доступ ко всем его членам, являющимся закрытыми.

Чтобы разрешить доступ извне к переменным-членам объектов созданных на базе класса Cat, необходимо сделать их открытыми:

 

class Cat {

public:

unsigned int itsAge;

unsigned int itsWeight;

void Meow();

} ;

 

Теперь все члены itsAge, itsWeight и Meow() стали открытыми, а строка Boots.itsAge = 5 больше не вызывает у компилятора проблем.

 

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

 

В листинге 6.1 показано объявление класса Cat с открытыми переменными-членами.

 

Листинг 6.1. Доступ к открытым члена простого класса

1: // Листинг 6.1. Пример объявления класса

2: //и определения его объекта

3 :

4: #include

5 :

6: class Cat

7: {

8: public:

9:     int itsAge;

10:    int itsWeight;

11: } ;

12 :

13     :

14     : int main()

15: {

16: Cat Frisky;

17: Frisky.itsAge = 5; // обращение к переменной-члену

18: std::cout << "Frisky is a cat who is ";

19: std::cout << Frisky.itsAge << " years old.\n";

20: return 0;

21: }

 

Результат:

Frisky is a cat who is 5 years old.

 

Анализ:

В строке 6 находится ключевое слово class. Оно сообщает компилятору о том, что следующий после него блок является объявлением класса. Имя нового класса стоит сразу после ключевого слова class. В данном случае это класс cat.

Тело объявления класса начинается открывающейся фигурной скобкой в строке 7, а заканчивается закрывающей фигурной скобкой и точкой с запятой в строке 11. Строка 8 содержит ключевое слово public, которое означает, что все члены ниже его (пока не встретится ключевое слово private или конец объявления класса) являются открытыми.

В строках 9 и 10 объявлены переменные-члены itsAge и itsWeight.

В строке 14 начинается функция main(). Объект Frisky создается в строке 16 как экземпляр класса Cat, т.е. как объект класса cat. В строке 17 объекту Frisky присваивается значение возраста (значение переменной itsAge), равное 5. А в строках 18 и 19 перемен-ная-член itsAge используется для вывода на экран сообщения о значении возраста. Обратите внимание на способ доступа к членам объекта Frisky в строках 17 и 18. Для обращения к переменной-члену ItsAge используется имя объекта (в данном случае — Frisky), сопровождаемое точкой и именем члена класса (в данном случае — itsAge).

 

Закрытые данные-члены

 

Согласно общей стратегии использования классов, его переменные-члены следует оставлять закрытыми. Благодаря этому обеспечивается инкапсуляция данных внутри класса. Следовательно, чтобы передавать и возвращать значения закрытых переменных, необходимо создать открытые функции, известные как методы доступа (accessor method).

Открытый метод доступа — это функция-член класса, предназначенная для установки и получения значений закрытых переменных-членов класса.

Зачем же нужен дополнительный уровень косвенного доступа? В конце концов, значительно проще и легче использовать сами данные, вместо того чтобы работать с ними через дополнительные функции доступа.

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

Если для какой-либо внешней функции, возвращающей значение возраста объекта класса Cat, открыть непосредственный доступ к переменной itsAge, то функцию пришлось бы переписывать, если бы автор класса Cat решил изменить способ хранения этого компонента данных. Но если внешняя функция получает данные класса через функцию-член GetAge (), то класс Cat можно модернизировать сколько угодно — это никак не повлияет на возможности функции GetAge () в основном коде программы. Вызывающая функция вовсе не обязана "знать", хранится ли нужное значение в переменной типа unsigned int или long либо оно вычисляется при запросе.

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

Кроме того, функции доступа могут содержать код, реализующий дополнительную логику. Например, маловероятно, что возраст кота будет 100 лет, а его вес — 1000 кг. Вполне очевидно, что такие значения недопустимы. Код функции доступа способен реализовать подобные ограничения, а также решить мнопие другие задачи.

 

Реализация методов класса

 

Как уже говорилось, функция доступа обеспечивает открытый интерфейс к закрытым переменным-членам класса. Для каждой функции доступа, наряду с другими методами, объявленными в классе, должна существовать реализация. Реализация — это описание действий функции.

Определение функции-члена начинается аналогично определению обычной функции. Сначала указывают тип данных, которые возвращает функция (или тип void, если она ничто не возвращает). Затем следует имя класса, два двоеточия, имя функции и, наконец, параметры функции. В листинге 6.3 показано объявление класса Cat, в котором находится реализация объявленных ранее методов доступа к данным и одна обычная функция-член.

 

Листинг 6.3. Реализация методов класса

1:     // Листинг 6.3. Пример объявления класса и

2:     // реализации его методов

3:     #include     // для cout

4 :

5:     class Cat II начало объявления класса

6:     {

7: public:  II начало раздела public

8:     int GetAge(); // функция доступа

9:     void SetAge (int age); // функция доступа

10:    void Meow();  // обычная функция

11: private:    // начало раздела private

12:    int itsAge;   // переменные-члены

13     :    } ;

14     :

15:    // Реализация открытой функции доступа GetAgeO,

16:    // возвращающей значение элемента itsAge

17:    int Cat::GetAge()  // Реализация GetAge

18:    {

19: return itsAge;

20:    } 21:

22:    // Реализация открытой функции

23:    // доступа SetAge(), устанавливающей значение

24:    // элемента itsAge

25:    void Cat::SetAge(int age)

26:    {

27: // присвоить переменной-члену itsAge значение,

28: //переданное через параметр age

29: itsAge = age;

30:    }

31:

32:    // Реализация метода Meow()

33:    // Возвращает: ничего (void)

34:    // Параметры : нет

35: //Действия : выводит на экран "Мяу" (Meow)

36:    void Cat::Meow()

37 :   {

38: std::cout << "Meow.\n" ;

39:    }

40 :

41:    // Создать кота, установить его возраст, мяукнуть,

42: //сообщить его возраст, затем мяукнуть снова.

43:    int main()

44:    {

45: Cat Frisky;

46: Frisky.SetAge(5);

47: Frisky.Meow();

48: std::cout << "Frisky is a cat who is ";

49: std::cout << Frisky.GetAge() << " years old.\n";

50: Frisky.Meow();

51:    return 0;

52:    }

 

Результат

 

Meow.

Frisky is a cat who is 5 years old.

Meow.

 

Анализ

 

В строках 5—13 содержится определение^класса Cat. Строка 7 содержит ключевое слово public, которое сообщает компилятору, что за ним следует набор открытых членов класса.

В строке 8 содержится объявление открытого метода GetAge (), который возвращает значение закрытой переменной-члена itsAge, объявленной в строке 12. В строке 9 объявлена открытая функция доступа SetAge (), которая принимает в качестве аргумента целочисленное значение и присваивает его переменной itsAge.

В строке 10 объявляется метод Meow(). Этот метод не является функцией доступа к данным-членам класса, а используется для вывода на экран слова Meow.

В строке 11 начинается раздел private, который содержит в строке 12 только одно объявление закрытой переменной-члена itsAge. Объявление класса завершается закрывающей фигурной скобкой и точкой с запятой в строке 13.

Строки 17—20 содержат реализацию функции-члена GetAge(). Этот метод не принимает никаких аргументов и возвращает целочисленное значение. Обратите внимание, что при определении методов класса используется имя класса, за которым следуют два двоеточия и имя функции (строка 17). Благодаря этому синтаксису компилятор узнает, что реализуемая здесь функция GetAge О объявлена в классе Cat. За исключением строки заголовка, метод GetAge () создается точно так же, как и другие функции.

 

Реализация функции GetAge () занимает только одну строку, в которой она возвращает значение переменной-члена itsAge. Обратите внимание, что функция main () не может обратиться к этой переменной, поскольку она объявлена закрытой (только для класса Cat). Но из функции main (_) можно обратиться к открытому методу GetAge ()_j_a поскольку он является функцией-членом класса Cat, то имеет все права доступа к переменной-члену itsAge. В результате функция GetAge() возвращает значение  переменной itsAsre в функцию main().

В строке 25 начинается реализация функции-члена SetAgre с;. Она получает- целочисленный параметр и присваивает его переменной itsAge в строке 29. Являясь членом класса Cat, функция SetAge () имеет прямой доступ к переменной-члену itsAge.

В строке 36 начинается реализация метода Meow() класса Cat. Этот метод занимает всего одну строку, в которой на экран выводится слово Meow с последующим переходом на новую строку. Помните, что для перехода на новую строку используется символ \п. Как можно заметить, метод Meow() объявлен точно так же, как и функции доступа. Сначала указан возвращаемый тип, затем следует имя класса, имя функции и параметры (в данном случае их нет).

В строке 43 начинается тело функции main (). В строке 45 в функции main () создается объект класса Cat по имени Frisky. Иными словами, функция main О объявляет о создании объекта Frisky класса Cat.

В строке 46 переменной-члену itsAge с помощью метода доступа SetAge () присваивается значение 5. Обратите внимание, что при вызове метода указывается имя объекта (Frisky), за которым следуют оператор прямого доступа (.) и имя самого метода (SetAge ()). Таким же образом можно вызывать и другие методы класса.

Термины функция-член (member function) и метод (method) являются синонимами, они применяются попеременно.

В строке 47 осуществляется вызов функции-члена Meow (), а в строке 48 на экран выводится сообщение с использованием функции доступа GetAge (). В строке 50 также расположен вызов функции Meow(). Хотя эти методы являются частью класса (Cat) и используются при помощи объекта (Frisky), работают они точно так же, как и функции, описанные ранее.

 

Постоянные функции-члены

 

Если объявить метод класса как const (константа), то он не сможет изменить значение ни одного члена класса. Чтобы объявлять метод класса как постоянный, поместите ключевое слово const после круглых скобок, но перед точкой с запятой. Например, объявление постоянной функции-члена SomeFunction (), не получающей никаких аргументов и ничего не возвращающей, выглядит так:

void SomeFunction() const;

Функции доступа, которые лишь возвращают значение, зачастую объявляют как постоянные (const). Класс Cat имеет две функции доступа:

void SetAge(int anAge);

 int GetAge();

Функция SetAge () не может быть объявлена как постоянная, поскольку она изменяет значение переменной-члена itsAge. А вот функция GetAge О вполне может и даже должна быть объявлена как постоянная, поскольку она ничего не изменяет в классе. Функция GetAge () просто возвращает текущее значение переменной-члена itsAge. Следовательно, объявление этих функций необходимо записать так:

void SetAge(int anAge);

int GetAge() const;

Если объявить функцию как постоянную, а затем в ее реализации каким-либо образом изменить объект, изменив значение любого из его членов, то компилятор пометит эту строку как ошибочную. Например, если реализовать функцию GetAge () таким образом, чтобы она подсчитывала количество обращений к ней, то это приведет к ошибке компиляции. Это связано с тем, что объект Cat изменился бы при вызове данного метода.

Использовать ключевое слово const в объявлениях методов, не изменяющих объект, считается хорошим тоном в программировании. Это позволяет компилятору обнаруживать ошибки до того, как они станут причиной проблем.

 

Интерфейс и реализация

 

Клиент — это часть программы, которая создает и использует объекты класса. Открытый интерфейс класса (объявление класса) можно рассматривать как соглашение с этим клиентом, объявляющее ему о том, как этот класс будет себя вести.

В объявлении класса Cat, например, заключено соглашение, что возраст каждого кота может быть инициализирован в его конструкторе и изменен с помощью функции доступа SetAge () или возвращен с помощью метода доступа GetAge (). Обещано также, что каждый кот сможет мяукать (Meow()). Заметьте, что в открытом интерфейсе ничего не говорится о переменной-члене itsAge; это та подробность реализации, которая не является частью соглашения. Значение возраста можно возвратить из объекта с помощью метода GetAge () и установить его с помощью метода SetAge (), но сама переменная itsAge, в которой хранится это значение, от клиентов скрыта.

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

Язык С++ является строго типизированным, что означает невозможность изменения типа данных после их объявления, а компилятор, скрупулезно придерживаясь соглашения, сообщит об ошибке при попытке их нарушения. Листинг 6.5 демонстрирует программу, которая не компилируется из-за нарушения этих соглашений (интерфейса).

 

(ПРЕДУПРЕЖДЕНИЕ Программа в листинге 6.5 не компилируется!

Листинг 6.5. Пример нарушения интерфейса

1:  // Листинг 6.5. Демонстрация ошибки компиляции

2:  // Эта программа не компилируется!

3:  #include   // для cout

4 :

5:  class Cat

6:  {

7:  public:

8: Cat(int initialAge);

9: ~Cat();

10: int GetAge() const;    // постоянная функция доступа

11: void SetAge (int age);

12: void Meow();

13:            private:

14: int itsAge;

15:            };

16 :

17:            // Конструктор класса Cat

18:            Cat::Cat(int initialAge)

19:            {

20: itsAge = initialAge; 21: std::cout << "Cat Constructor\n";

22 :           }

23 :

24:            Cat::~Cat()     // деструктор, не делает ничего

25:            {

26: std::cout << "Cat Destructor\n";

27 :           }

28:            // функция GetAge объявлена как const,

29:            // но соглашение нарушено!

30:            int Cat::GetAge() const

31:            {

32: return (itsAge++);      // нарушение const!

33 :           }

34:

35:            // Реализация открытой функции доступа SetAge(),

36:            // устанавливающей значение элемента itsAge

37: void Cat::SetAge(int age) {

38: // присвоить переменной-члену itsAge значение, II переданное через параметр age itsAge = age;

39:}

40://          Реализация

41://          Возвращает

42://          Параметры

43://          Действия метода Meow() ничего (void) нетвыводит на экран "Мяу" (Meow)

44: void Cat::Meow() {

45: std::cout << "Meow.\n";

46: }

47: // примеры различных нарушений интерфейса,

48: // которые приводят к ошибкам компиляции

49: int main() {

50: Cat Frisky;

51: Frisky.Meow();

52: Frisky.Bark();

53: Frisky.itsAge = 7;

54: return 0;

55: }

 

Анализ

 

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

В строке 10 функция GetAge () объявлена как постоянный метод доступа, на что указывает ключевое слово const. Но в теле функции GetAge (), а именно — в строке 32, выполняется приращение переменной-члена itsAge. Однако поскольку этот метод объявлен как const, он не имеет права изменять значение переменной itsAge. Следовательно, во время компиляции программы эта строка будет помечена как содержащая ошибку.

В строке 12 объявлен метод Meow(), на этот раз без ключевого слова const. И хотя такое упущение не является ошибкой, это далеко не лучший стиль программирования. Если учесть, что данный метод не должен изменять значения переменных-членов класса Cat, то его следовало бы объявить как постоянный.

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

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

В строке 61 предпринимается попытка присвоить переменной itsAge значение 7. Поскольку переменная itsAge относится к числу закрытых данных-членов, при компиляции программы эта строка приведет к ошибке.

 

Где объявлять класс и где располагать реализацию методов

 

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

Определение должно находиться в файле, доступном компилятору. Для большинства компиляторов С++ такой файл должен иметь расширение .с или .срр. В этой книге используется расширение . срр.

Объявления классов можно поместить в один файл с программой, но это не считается хорошим тоном. В соглашении, которого придерживаются многие программисты, принято помещать объявления в файл заголовка (header file), имя которого обычно совпадает с именем файла программы и имеет расширение .h, .hp или .hpp. В этой книге для имен файлов заголовков используется расширение . hpp.

Например, можно поместить объявление класса Cat в файл Cat.hpp, а определения методов класса — в файл Cat.срр. Затем файл заголовка необходимо подключить в файл кода с расширением .срр. Для этого в файле Cat.срр перед началом программного кода используется уже известная директива: #include "Cat.hpp"

Это заставит компилятор внести содержимое файла Cat.hpp в соответствующее место программы. Имейте в виду, что некоторые компиляторы чувствительны к регистру букв и требуют точного соответствия написания имен файла в директиве #include и на диске.

Зачем же отделять файл заголовка (с расширением .hpp) от файла основной программы (с расширением .срр), если впоследствии их все равно придется собрать в одну программу? Как показывает практика, клиентов класса обычно не очень волнуют подробности его реализации. Небольшой файл заголовка содержит всю необходимую для них информацию, а файл с подробностями реализации класса можно до определенного момента игнорировать. Кроме того, не исключено, что содержимое файла заголовка (с расширением .hpp) удастся применить не в одном, а в нескольких файлах с расширением .срр.

 

Встраиваемая реализация

 

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

inline int Cat::GetWeight() {

return itsWeight; // возвратить значение переменной-члена Weight

}

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

 

class Cat {

public:

int GetWeight()

{

return itsWeight;

} // встраиваемая

void SetWeight(int aWeight);

} ;

 

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

 

class Cat {

public:

int GetWeight() const {

return itsWeight; }  // встраиваемая

void SetWeight(int aWeight);

} ;

 

В листингах 6.6 и 6.7 вновь создается класс cat, но теперь объявление класса содержится в файле Cat.hpp, а реализация — в файле Cat.cpp. Кроме того, в листинге 6.7 метод доступа к данным класса и метод Meow() являются встраиваемыми.

 Листинг 6.6. Объявление класса cat в файле cat.hpp

1:  #include

2:  class Cat

3:  {

4: public:

5:  Cat (int initialAge);

6:  -Cat();

7:  int GetAge() const { return itsAge; }            // Встроен!

8:  void SetAge (int age) { itsAge = age; }        // Встроен!

9:  void Meow() const { std::cout << "Meow.Nn"; } // Встроен!

10: private:

11:            int itsAge;

12 :           } ;

 

Листинг 6.7. Реализация класса cat в файле cat. срр

1:  // Листинг 6.7. Пример использования встраиваемых

2:  // функций и подключения файлов заголовка

3:  // Убедитесь в подключении файла заголовка!

4:  #include "Cat.hpp"

5   :

6   :

7:  Cat::Cat(int initialAge) // конструктор

8:  {

9: itsAge = initialAge;

10:            }

11:

12:            Cat::~Cat()     // деструктор, не делает ничего

13:            {

14:            }

15 :

16:            // Создать кота, установить его возраст, мяукнуть,

17:            // сообщить его возраст, затем мяукнуть снова.

18:            int main()

19:            {Cat Frisky(5);

20:Frisky.Meow();

21:std::cout << "Frisky is a cat who is ";

22:std::cout << Frisky.GetAge() << " years old.\n";

23:Frisky.Meow();

24:Frisky.SetAge(7);

25:std::cout << "Now Frisky is ";

26:std::cout << Frisky.GetAge() << " years old.\n";

27:return 0;

 

Результат

Meow.

Frisky is a cat who is 5 years old.

Meow.

Now Frisky is 7 years old.

 

Анализ

 

Код, приведенный в листингах 6.6 и 6.7, аналогичен программе из листинга 6.4, за исключением того, что три метода класса объявлены встраиваемыми, а само объявление класса вынесено в файл заголовка cat.hpp (листинг 6.6).

В строке 7 файла Cat. hpp объявлена функция GetAge (), и тут же расположена ее встраиваемая реализация. Строки 8 и 9 содержат еще две встраиваемые функции, реализация которых не изменилась "с прошлого раза".

В строке 4 листинга 6.7 находится директива #include "Cat.hpp", подключающая к коду программы содержимое файла Cat.hpp. Подключение файла Cat.hpp выполняет препроцессор, который, прочитав файл Cat.hpp, вставит его текст вместо строки 5.

Такой подход позволяет размещать объявления и реализацию в разных файлах, оставляя их доступными для компилятора. Это обычный прием в программировании на языке С++. Как правило, объявления класса находятся в файле с расширением .hpp, который с помощью директивы #include подключается затем в основной файл .срр.

В строках 18—29 в точности повторяется тело функции main() из листинга 6.4. Это доказывает, что, став встраиваемыми, функции не утратили своей работоспособности.