На этом шаге мы рассмотрим особенности описания и использования методов класса (компонентных функций).
Компонентная функция должна быть обязательно описана в теле класса. В отличие от обычных (глобальных) функций компонентная функция имеет доступ ко всем компонентам класса (с любым статусом доступа). Функция-компонент класса имеет ту же область видимости, что и класс, к которому она относится.
В языке C++ программист может влиять на компилятор, предлагая ему оформить ту или иную функцию как подставляемую (встраиваемую). Для этих целей в определении функции указывается служебное слово (спецификатор) inline.
При определении классов их методы также могут быть специфицированы как подставляемые. Кроме явного использования служебного слова inline для этого используется следующее соглашение.
Если определение (не только прототип) принадлежащей классу функции полностью размещено в классе (в теле класса), то эта функция по умолчанию считается подставляемой.
Именно таким образом определены компонентные функции классов complex1, goods, complex, stroka, point3, использованных ранее в качестве примеров. Все функции перечисленных классов воспринимаются компьютером как подставляемые, т.е. при каждом вызове этих функций их код "встраивается" непосредственно в точку вызова. Как уже говорилось в связи с обсуждением особенностей подставляемых функций, это не всегда удобно, так как подставляемыми могут быть не всякие функции.
При внешнем определении компонентной функции программист "должен сообщить" компилятору к какому именно классу она относится. Для этого используется бинарная форма операции "::" (указания области видимости). Формат ее использования в этом случае таков:
имя_класса::имя_компонентной_функции
Приведенная конструкция, называемая квалифицированным именем компонентной функции, означает, что функция есть компонент класса и лежит в области его действия. Именно такое определение привязывает функцию к классу и позволяет в ее теле непосредственно использовать любые данные класса (его объектов) и любые принадлежащие классу функции. (Это относится и к собственным и к защищенным компонентам.)
Итак, при внешнем определении компонентной функции в теле класса помещается ее прототип:
тип имя_функции (спецификация_и_инициалиэация_параметров);
Вне тела класса компонентная функция определяется таким образом:
тип имя_класса:: имя_функции(спецификация_формальных_параметров) { тело_принадлежащей_классу_функции }
В качестве примера класса с внешним определением компонентных функций введем класс point, определяющий понятие "символ '*' на экране дисплея". Разместим описание класса в отдельном файле с названием point.h:
//POINT.H - описание класса с внешними определениями //методов. #ifndef POINTH #define POINTH 1 class point { // Символ '*' на экране дисплея. protected: // Защищенные данные класса. int х, у; // Координаты символа. // Прототипы общедоступных компонентных функций. public: point(int xi = 0, int yi = 0); // Конструктор. int& givex(void); // Доступ к х. int& givey(void); // Доступ к у. void show(void); // Изобразить символ на экране. // Переместить точку в новое место экрана: // (хn = 0, уn = 0 - умалчиваемые значения параметров). void move(int xn = 0, int yn = 0); private: // Собственная функция класса. void hide(); // Убрать с экрана изображение символа. }; #endif
Так как описание класса point в дальнейшем планируется включать в другие классы, то для предотвращения недопустимого дублирования описаний в текст включена условная препроцессорная директива #ifndef POINTH. Препроцессорный идентификатор POINTH определяется тут же с помощью директивы:
#define POINTH 1
Тем самым текст описания класса point может появляться в компилируемом файле только однократно, несмотря на возможность неоднократных появлений директив #inciude "point.h". Обратите внимание на обычную необязательность имен формальных параметров при описании прототипов функций. В прототипе конструктоpa и в прототипе функции move() имена формальных параметров xi, yi, xn, yn можно было бы опустить.
Определим методы класса point следующим образом:
// POINT.СРР - внешнее определение функций класса. #ifndef POINTCPP #define POINTCPP 1 #include <iostream.h> #include <conio.h> //Для функции gotoxy(); #include "point.h" // Описание класса point. point::point (int xi, int yi) { x = xi; у = yi;} // Определение данных объекта. int& point::givex(void) { return x; } // Доступ к х. int& point::givey(void) { return y; } // Доступ к у. // Изобразить символ на экране. void point::show(void) { gotoxy(x,у); cout << "*"; } // Убрать с экрана изображение символа. void point::hide(void) { gotoxy(x,y); cout << " "; } // Переместить символ в новое место экрана. void point::move (int xn, int yn) { hide(); x = xn; у = yn; show(); } #endif
Конструктор point и прототип функции move() снабжены умалчиваемыми значениями параметров. Координаты создаваемого по умолчанию (без указания значений параметров) символа равны нулю. Туда же по умолчанию перемещается символ.
Внешнее определение методов класса в противоположность встроенному позволяет модифицировать принадлежащие классу функции, не изменяя текста описания класса.
Определив класс и его компонентные функции, приведем программу, иллюстрирующую работу с классом point:
//Р10_1.СРР - работа с классом "символ на экране". #include <conio.h> // Прототип функции getch(). #include "point.cpp" // Определение класса point. void main() { point A(2,5); // Создается невидимая точка A. point B; // Невидимая точка В с нулевыми // координатами по умолчанию. point D (5,2); // Создается невидимая точка D. A.show(); // Показать на экране точку A. getch(); // Ждать нажатия клавиши. B.show(); // Показать на экране точку B. getch(); // Ждать нажатия клавиши. D.show(); // Показать на экране точку D. getch(); // Ждать нажатия клавиши. A.move(); // Переместить точку A. getch(); // Ждать нажатия клавиши. B.move(3,4); // Переместить точку B. }
На следующем шаге мы рассмотрим указатель this.