6.1. Дефиниция на клас
Всяка дефиниция на клас има две части: глава, съставена от ключовата дума class, следвана от името на класа и тяло, затворено във двойка фигурни скоби, които могат да бъдат последвани от точка и запетая или списък от декларации. Например,
class Screen { /* ... */ }
class Screen { /* ... */ }
myScreen, yourScreen;
Вътре в класа се определят член данните, член функцийните нивата на информационно скриване. Следните подраздели разглеждат различни типове спецификации.
Член данни
Декларацията на член данните на класа е същата, както и декларацията на променливи с едно изключение - не са позволени явни инициализатори. Например, класът Screen може да дефинира представянето си така:
class Screen {
short height; // number of Screen rows
short width; // number of Screen columns
char *cursor; // current Screen position
char *screen; // screen array (height*width) };
Както и при декларацията на променливи не е необходимо два члена short или два члена char* да се декларират отделно. Следните дефиниции са еквивалентни:
class Screen {
/* * height and width refer to row and column * cursor points to current Screen position * screen addresses array height*width */
short height, width;
char *cursor, *screen; };
Изобщо, ако няма причина да бъде направено друго, декларирайте членовете на класа подредени в нарастващ според размера ред. Това обикновено дава оптимално разполагане върху всички машини. Член данните могат да бъдат от всеки тип. Например,
class StackScree {
int toptack; void (*handler)(); // handles exceptions Screen stack[ STACK_SIZE ];
}
Един класов обект може да бъде деклариран като член данни само ако неговата класова дефиниция вече е била открита. В случаите, когато класовата дефиниция не е била открита може да бъде приложена предварителна декларация на клас. Предварителната декларация разрешава указатели и псевдоними на обекти от даден клас да бъдат декларирани като член данни. Указателите и псевдонимите са разрешени понеже и двата са с фиксиран размер независимо от типа, който те адресират. Например, ето дефиницията на StackScreen с използуване на предварителна декларация:
class Screen; // forward declaration class StackScreen {
// pointer to STACK_SIZE Screen objects
int topStack;
Screen *stack;
void (*handler)(); };
Един клас не се счита за дефиниран докато не бъде открита затварящата скоба на тялото му - това предпазва класа от деклариране на член обект от неговия собствен тип. Класът се счита за деклариран, обаче, и може да дефинира член данни указатели и псевдоними от собствения си класов тип. Например,
class LinkScreen {
Screen window;
LinkScreen *next;
LinkList *prev; }
Член функции
Потребителите на класа Screen трябва да изпълняват широк кръг от операции върху обектите от класа Screen. Ще бъде необходим един набор от операции за движение на курсора. Също трябва да бъде приложена способността да се проверяват и установяват точки върху екрана. Потребителят трябва да бъде в състояние да копира един обект от тип Screen в друг и да открива по време на изпълнение фактическата размерност на екрана. Този набор от операции за обработване на обект от тип Screen е деклариран в тялото на класа. За тези операции се говори като за член функции на класа.
Член функциите на класа се декларират в тялото на класа. Всяка декларация се състои от прототип на функция. Например,
class Screen {
public
void home();
void move( int, int );
char get();
char get( int, int ); void chackRange( int, int );
// ... };
В тялото на класа може също да бъде поставена и дефиниция на член функция. Например,
class Screen {
public:
void home() { cursor = screen; }
char get() { return *cursor; }
// ... }
home() позиционира курсора в горния ляв ъгъл на екрана. get() връща стойността на текущата позиция на курсора. Понеже те са дефинирани вътре в тялото на класа автоматично се обработват като функции inline.
Член функциите, които имат повече от един - два реда, е най- добре да бъдат дефинирани вън от тялото на класа. Това изисква специален синтаксис за определяне на членството на функция към даден клас. Например, ето дефиницията на checkRange():
#include "Screen.h"
#include <stream.h>
#include <stdlib.h>
void Screen::checkRange( int row, int col ) {
// validate coordinates
if ( row < 1 || row > height || col < 1 || col > width ) {
cerr << "Screen coordinates ( " << row << ", " << col
<< " ) out of bounds.n"; exit( -1 );
} }
Член функция, която е декларирана вън от тялото на класа трябва явно да декларира, че ще бъде от тип inline. Например, следният фрагмент дефинира move() като inline член функция на Screen:
inline void
Screen::move( int r, int c ) { // move cursor to absolute position
checkRange( r, c ); // valid address?
int row = ( r-1 )*width; // row location
cursor = screen + row + c - 1;
}
Член функциите могат да бъдат разграничени от обичайните функции по следните атрибути: - член функциите имат пълни права за достъп както до публичните, така и до личните членове на класа докато, докато обичайните функции имат достъп само до публичните членове на класа. Разбира се, член функциите на един клас нямат права за достъп до членовете на други класове. - Член функциите са дефинирани в обхвата на класа си; обичайните функции са дефинирани с файлов обхват. (Раздел 6.8 разглежда подробно класовия обхват).
Дадена член функция може да презареди само друга член функция от класа си. Това се дължи на факта, че презаредими функции могат да се описват само в един и същ обхват. Описаните по-долу втори представители на get(), например, нямат никаква връзка с глобалната функция get(), която не е член функция, нито пък с някаква член функция get() от друг клас.
inline char
Screen::get( int r, int c ) {
move( r, c ); // position cursor
return get(); // other
Screen get()
}
Скриване на информация
Често се случва така, че вътрешното представяне на един класов тип се изменя след началната му употреба. Например, представете си, че e проведено едно изследване сред потребителите на нашия клас Screen и се е установило, че всички класови обекти Screen се дефинирани с размерност 24 х 80. В този случай следното представяне на класа Screen ще бъде не така гъвкаво, но по- ефективно:
const Height = 24;
const Width = 80;
class Screen {
char screen[ Height ][ Width ]; short cursor[2];
};
- Всяка член функция трябва да бъде реализирана отново, но интерфейсът й (т.е. сигнатурата и типа за връщане) ще останат непроменени. Какъв ще бъде ефекта от тази промяна на вътрешното представяне за потребителите на този клас?
- Всяка програма, която е имала директен достъп до член данните на старото представяне на Screen вече не работи. Необходимо е да се открият и напишат отново всички онези части от текста, използуващи това представяне, преди програмите да могат да бъдат използувани отново.
- Всяка програма, която е ограничила своя достъп до обекти от тип Screen само върху член функциите им не се нуждае от промяна на текста. Необходимо е, обаче, едно повторно компилиране.
Скриването на информацията е един формален механизъм за ограничаване на потребителския достъп до вътрешното представяне на класовия тип. То се определя чрез етикираните чрез public, private и protection раздели в тялото на класа. Членовете, декларирани в частта public стават публични членове; тези, декларирани в private и protected стават лични или защитени.
- Един публичен член е достъпен от произволно място вътре в програмата. Всеки клас, който принудително скрива информация, ограничава публичните си членове до член функции, имащи значение за дефиниране на функционални оператори за класа.
- Един защитен член има поведение като на един публичен член от произлязъл клас; той се държи като личен член за остатъка то програмата. (Ние видяхме един пример за това как защитените членове са използувани в произлезлия клас IntArrayRC, описан в глава 1. Пълното описание на защитените членове е отложено за глава 8, в която се въвеждат произлезлите класове и концепцията за наследствеността).
- Един личен член може да бъде достъпен само посредством член функциите и приятелите на класа. (Вж. раздел 6.5 по-нататък в тази глава за подробности относно приятелите). Всеки клас, който желае да скрие информация, декларира член данните си като лични.
Следните дефиниции на Screen определят публичните и личните му части:
class Screen {
public:
void home() { move( 0, 0 ); }
char get() { return *cursor; }
char get( int, int );
inline void move( int, int ); // ...
private:
short heigth, width;
char *cursor, *screen; };
Съществува споразумение, според което публичните членове се записват първи. Личните членове се задават в края на тялото на класа.
Един клас може да съдържа много много секции, означени като публични, защитени или лични. Статусът на всеки раздел остава в сила докато не бъде срещнат друг етикет на раздел или затваряща скоба на тялото на класа. Ако не е зададен никакъв етикет се приема по подразбиране, че секцията, започваща незабавно след лявата отваряща скоба е лична.
6.2. Класови обекти
Предходната дефиниция на Screen не предизвикваше отделянето на каквото и да е количество памет. С дефиницията на всеки класов обект се отделя памет за класа. Дефиницията: Screen myScreen; например, отделя памет, достатъчна да побере четерите член данни на класа Screen.
Обектите от един и същ клас могат да бъдат инициализирани и присвоявани един на друг. По подразбиране, копирането на елементи на класове е еквивалентно на копирането на всички техни елементи. Например,
// bufScreen.height = myScreen.height
// bufScreen.width = myScreen.width
// bufScreen.cursor = myScreen.cursor
// bufScreen.screen = myScreen.screen
Screen bufScreen = nyScreen
По подразбиране класовете обекти се изпращат по стойност като аргументи на функции и типове за връщане на функции. Указател към обект клас може да бъде инициализиран или да му бъде присвоявана стойност чрез оператора new или чрез адресния оператор (“&”). Например,
Screen *ptr = new Screen;
myScreen = *ptr;
ptr = &bufScreen;
Вън от обхвата на класа на операторите за избор на член е необходимо да имат достъп или до член данните или до член функциите на класа. Селекторът на обект клас (“.”) се използува с обект клас или негов псевдоним; селекторът на указател към обект клас (“->”) се използува с указател към обект клас. Например,
#include "Screen.h"
isEqual( Screen& s1, Screen *s2 ) {
// return 0 if not equal, 1 if equal
if ( s1.getHeigth() != s2->getHeigth() || s1.getWidth() != s2->getWidth() )
return 0; // not equal
for ( int i = 0; i < s1.getHeigth(); ++i)
for ( int j = 0; j < s2->getWidth(); ++j)
if (s1.get( i, j ) != s2->get( i, j )) return 0; // not equal // still hehre? then screens are equal
return 1; }
isEqual() е една нечлен функция, която сравнява два Screen обекта за еквивалентност. isEqual() няма права на достъп до личните член данни на Screen; тя трябва да разчита на публичните член функции на класа Screen.
getHeigth() и getWidth(), наречени функции за достъп, предлагат достъп само за четене до личните член данни на класа. Тяхната реализация следва. За да бъдат по-ефективни те са дефинирани като inline:
class Screen { public: int getHeigth() { return heigth; }
int getWidth() { return width; } // ...
private: short heigth, width; // ... };
Членовете на класа могат да бъдат достъпни директно в обхвата на класа. Всяка член функция се включва в обхвата на класа, независимо дали е дефинирана вътре или вън от тялото на класа. Една член функция има достъп до членовете на класа си без да използува операторите за селекция. Например,
#include "String.h"
#include <string.h>
void Screen::copy( Screen& s ) {
// copy one Screen object with another delete screen;
// free up existing storage
heigth = s.heigth;
width = s.width;
screen = cursor = new char[heigth*width + 1];
strcpy( screen, s.screen ); }
Упражнение 6-1. copy() дава такъв размер на обекта наречен Screen, какъвто е размера на обекта Screen, който се копира. Реализрайте copy() така, че да позволява размерите на обектите източник и назначение Screen да бъдат различни.
Упражнение 6-2. Относно символите, съдържащи се в screen е било направено едно интересно, а вероятно и малко опасно предположение. Това предположение позволява членовете на screen да бъдат копирани като се използува библиотечната функция strcpy(). Какво е това предположение? Реализирайте copy() отново така, че да не зависи от това предположение.