Объектно-ориентированное программирование - Учебное пособие (А.А. Хусаинов)

2.2. результаты работы программы

 

Введите через пробел координаты вектора (x и y): 12.4 3.56

Введите через пробел координаты вектора (x и y): 2.34 5.6

Введите через пробел координаты вектора (x и y): 7 8.02

 

Координаты вектора v: x=12.4 y=3.56

Координаты вектора w[0]: x=2.34 y=5.6

Координаты вектора w[1]: x=7 y=8.02

 

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

тип_возвращаемого_значения  имя_класса::функция(аргументы) {…}

 

Следует помнить, что указатель на объект, к которому принадлежит составная функция, определяется с помощью ключевого слова this. В частности, в предшествующем примере в функциях put() и get() переменные x и y будут равны (*this).x и (*this).y.

 

Пример 2. Рассмотрим подпрограмму перегрузки операции присваивания для структуры, состоящей из строки и ее длины. В теле класса эта функция объявлена как

 

str& operator = (const str&);

 

она будет возвращать адрес объекта, полученного после присваивания. Это позволит применять цепочки присваиваний, например, str1 = str2 = str3. Аргумент функции сделаем ссылочным, чтобы избежать копирования всего объекта в стек при вызове операции присваивания. В стек теперь будет сохраняться адрес объекта.

 

#include <string.h>

#include <iostream.h>

#include <conio.h>

 

// Класс строка

struct Str

   {

   char *s;            // Указатель на строку

   int len; // Длина строки

   void init(const char*);                         // Функция инициализации строки

   Str operator = (const Str);       // Перегрузка операции =

   };

 

// Перегрузка операции =

Str Str::operator = (const Str st)

   {

   len = st.len;      // Выяснение длины новой строки

   delete s;                       // Удаление старого содержимого

   s = new char[len + 1]; // Выделение памяти под новую строку     strcpy(s, st.s);              // Копирование строки

   return *this;                 // Возвращение полученной строки по значению

   }

 

// Функция инициализации строки

void Str::init(const char* s)

   {

   len = strlen(s); // Выяснение длины строки

   Str::s = new char[len + 1];       // Выделение памяти под строку     strcpy(Str::s, s);           // Копирование строки

   }

 

void main()

   {

   clrscr();            // Очистка экрана

 

   Str str1, str2, str3;       // Создание строк

   str1.init("Пирамида");            // Инициализация первой строки

   str3 = str2 = str1;         // Присваивание значения первой строки

                                                   // остальным двух строкам

   cout<<"Объект str3 = " << str3.s << ' ';       // Вывод третьей строки

 

   getch();            // Ожидание нажатия клавиши

   }

Результаты работы программы

 

Объект str3 = Пирамида

 

В этом примере мы столкнулись со следующей проблемой: подпрограмма init имеет формальный параметр с именем s, совпадающим с именем строки s в классе Str. Для того чтобы отличать имя строки в классе, применяется модификатор расширения области видимости «::». В данном случае к строке класса применяется обращение Str::s.

 

Определение первичного класса

 

Мы определили класс как тип данных, состоящий из полей (типов данных) и составных функций. Слияние данных и функций, работающих с этими данными, называется инкапсуляцией. Таким образом

класс = данные + составные функции.

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

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

 

class имя

{

               private:

               …

               protected:

               …

               public:

               …

};

 

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

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

Атрибут public имеют члены класса, обращение к которым осуществляется как к полям структуры. Эти члены называются открытыми.

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

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

 

class Vector

   {

               double x,y;

   public:

               double getx() {return x;}

               double gety() {return y;}

   };

 

то элементы класса x и y будут закрыты по умолчанию, и обращение к ним, как к открытым членам, приведет к ошибке. Эти элементы можно будет читать с помощью функции getx() и gety():

 

void main()

   {

   Vector a;

   int z;

   z=a.x;  //ошибка!

   z=a.getx();       //верно

   }

 

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

Составные функции, определяемые внутри тела класса, будут подставляемыми (inline). Составные функции, определенные как внешние, с помощью оператора разрешения области видимости “::”, тоже можно сделать подставляемыми, указав для них модификатор inline. Но такое определение необходимо поместить перед первым использованием этой функции.

 

Пример. Определим класс, объектом которого является стек целых чисел. Для инициализации стека определим дружественную функцию. Для записи элемента в стек и для чтения элемента из стека определим функции Push() и Pop().

 

#include <iostream.h>

#include <conio.h>

 

// Описание класса - целочисленный стек

class IntStack

   {

   // Закрытые элементы

               int *v;  // У нас стек будет реализован в виде массива

               int size, top;     // Размер стека и положение вершины

   public: // Общедоступные элементы

friend IntStack init(int size);     //Дружественная функция //инициализации стека

               int pop();         // Извлечение числа из вершины стека

               void push(int x);          // Занесение числа в стек

   };

 

// Инициализации стека

IntStack init(int size)

   {

   IntStack res;    // Создаём новый стек

   res.v=new int [size];    // Выделяем память под массив

   res.size=size;                                       // Указываем размер стека

   res.top=size;                                        // Устанавливаем вершину стека

   return res;                                                        // Возвращаем созданный стек

   }

 

// Занесение числа в стек

inline void IntStack::push(int x)

   {

   if(top>0) v[--top]=x;

   }

 

// Извлечение числа из стека

inline int IntStack::pop()

   {

   if(top<size) return v[top++];

   else return 0;

   }

 

void main()

   {

   clrscr();            // Очистка экрана

 

   IntStack s1, s2;           // Создание стеков

 

   s1=init(10); s2=init(20);          // Инициализация стеков

 

   cout<<"Заносим в стек s1 число -3 ";

   s1.push(-3);

 

   cout<<"Заносим в стек s2 число 1 ";

   s2.push(1);

 

   cout<<"Заносим в стек s1 число -2 ";

   s1.push(-2);

 

   cout<<"Извлекаем из стека s1 первое число "<<s1.pop();

   cout<<", затем второе "<<s1.pop()<<' ';

 

   cout<<"Извлекаем из стека s2 число "<<s2.pop()<<' ';

 

   getch();            // Ожидание нажатия клавиши

   }

 

Результаты работы программы

 

Заносим в стек s1 число -3

Заносим в стек s2 число 1

Заносим в стек s1 число -2

 

Извлекаем из стека s1 первое число -2, затем второе -3

Извлекаем из стека s2 число 1

 

Если функцию Pop() или функцию Push() определить за текстом главной программы, то модификатор inline приведет к ошибке, ибо компилятор при генерации кода главной программы использовал команды call, и при встрече модификатора inline  не может эти команды заменить на подставляемые функции.