Помогни ни да направим Uroci.net по - богат! Добави урок

С++ част.2 (Типове данни)

C++ » C++
fix3d   трудност:    видян: 37905



2.8. Тип клас

Типът клас е един дефиниран от потребителя даннов тип, който представлява съвкупност от именувани елементи от данни, може и разнотипни, както и множество от операции, проектирани да обработват тези данни. Обикновено, един клас се използува за въвеждане на нов тип данни в програмата; добре проектираният клас може да бъде използван толкова лесно, колкото и който и да е друг предварително дефиниран тип данни. Класовете се разглеждат подробно в глави от 5 до 8. Понеже те представляват фундаментална концепция за С++ в този раздел се дават обясненияза тях на основата на един обширен пример -проектирането на класа на целите масиви.

Четири от най-неприятните аспекти на предварително дефинирания тип масив са:

1. Размерът на масива трябва да бъде константен израз. Програмистът, обаче, не винаги знае по време на компилация колко голям масив му е необходим. Един по гъвкав тип масив би разрешил на програмиста да определя или променя размерността на масива по време на изпълнение на програмата.

2. Не съществува проверка дали индексът на масива надхвърля размера му. Следващият програмен фрагмент, например, използува грешна константа за ограничител на горната граница на масив при инициализацията му. Въпреки, че програмата приключва успешно, резултатите вероятно ще бъдат неправилни поради изменението на паметта извън границите на масива.

const int SIZE = 25;
const int SZ = 10;
int ia[ SZ ];
// ...

main() {
// not caugth during program execution
for ( int i = 0; i < SIZE; ++i )
ia[ i ] = i;
// ...}

3. Когато масив се изпраща като параметър на функция е необходимо да се указва и размера му. Масивите - аргументи биха могли да бъдат използувани по-лесно ако размера им е известен.

4. Би било добре да можем да копираме един масив в друг като използуваме само един оператор. Например:

int ia[ SZ ];
int ia2[ SZ ] = ia; // not supported

В този раздел се дефинира клас, който поддържа тези четири допълнителни свойства на масив. Освен това синтаксисът за използване на обектите от клас масив ще бъде толкова прост, колкото и на традиционния масив. Проектирането на типа клас се обсъжда подробно в глава 1. Тук ние просто ще представим дефиницията на класа масив и ще обсъдим реализацията му. Eто как изглежда тя:

const ArraySize = 24; // default size
class IntArray {
public;
// operations performed on arrays
IntArray( int sz = ArraySize );
IntArray( const IntArray& );
~IntArray() { delete ia; }
IntArray& operator = ( const IntArray& );
int& operator[]( int );
int getSize() { return size; )
protected:
// internal data representation
int size;
int *ia;
};

Една дефиниция на клас се състои от две части: глава на клас, състояща се от ключовата дума class и име, и тяло, затворено във фигурни скоби и завършващо с точка и запетая. Името на класа представя един нов тип данни. Той може да служи за спецификатор на тип по същия начин, по който и вградените типови спецификатори. Следват няколко примера за дефиниране на променливи от класа IntArray:

const int SZ = 10;
int mySize;
int ia[SZ]; // predefined array
IntArray myArray ( mySize ), iA( SZ );
IntArray *pA = &myArray;
InrArray iA2; // 24 elements by default

Тялото на класа съдържа няколко дефиниции. Елементите на класа представят операциите, които могат да бъдат изпълнявани от класа, и данните, необходими за определяне на свойствата му. В нашия случай целият масив се представя чрез два елемента данни:

1. size, който съдържа броя на елементите на масива.

2. ia, който адресира паметта, където се разполагат елементите.

Ключовите думи protected и public контролират достъпа до елементите на класа. Елементите, описани в раздела public на класа са достъпни от произволно място на програмите. Елементите, намиращи се в раздела protcted са достъпни само чрез член-функциите на класа IntArray. Това ограничение върху достъпа е известно като скриване на информация.

Изобщо, единствено на член-функциите на класа е разрешен достъпа до данновите му елементи. Това има две основни предимства:

1. Когато се налага промяна на представянето на данните в класа се променят само функциите на класа, а не и потребителските програми, които го използуват.

2. Когато се появи грешка при обработката на елементите от данни на класа може да бъде прегледано само малко множество от функции, описани в него, вместо да се преглежда цялата програма.

Три от член-функциите на IntArray - за инициализация и освобождаване - използуват името на класа като свое име. Въпреки, че са дефинирани от проектанта на класа, те се извикват автоматично от компилатора. Функцията, предшествана от тилда (“~”), т.е. функцията за освобождаване, се нарича деструктор. Друтите две функции служат за инициализация; те се наричат конструктори. Конструкторът

IntArray iA2;

обработва обичайните декларации. sz представя размера на масива, желан от потребителя. Ако потребителят не зададе такъв размер, по подразбиране масивът ще съдържа ArraySize на брой елементи. Такъв е размера на iA2 в дефиницията:

IntArray iA2;

Ето една реализация на представител на IntArray(). Той се въвежда чрез оператора new. Този оператор обработва динамичното разпределение на паметта. Раздел 4.1 разглежда оператора new.

IntArray::IntArray( int sz ) {
size = sz;

// allocate an integer array of size
// and set ia to point to it

ia = new int[ size ];

// initialize array

for ( int i = 0; i < sz; ++i )
ia[ i ] = 0;
}

Операторът две двуеточия :: се нарича scope оператор (за обхват). Той указва на компилатора, че функцията IntArray е дефинирана като член на класа IntArray. Функциите, които са елемент на даден клас имат достъп да собствените си класови елементи директно. Когато напишем

size = sz;

size се отнася до данни измежду класовите член-променливи, за които е била извикана член-функцията. В нашия пример, size е член-данни на iA2.

Вторият представител на IntArray() обработва инициализацията на един обект от тип IntArray чрез друг. Тя се извиква автоматично, когато се срещне дефиниция от вида:

IntArray iA3 = myArray;

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

IntArray::IntArray( const IntArray &iA ) {
size = iA.size;
ia = new int[ size ];
for ( int i = 0; i < size; ++i )
ia[ i ] = iA.ia[ i ];
}

За да се осигури достъп до подходящата променлива в класа трябва да се използува оператор за избор на член:

1. Операторът точка (“.”) се използува, когато програмистът иска достъп до член-данни или член-функция на подходящия клас на обекта.
2. Операторът стрелка (“->”) се използува, когато програмистът иска достъп до подходящия клас на обекта като се използува указател към клас. Например, значението на израза

iA.size;

може да бъде формулирано така: Избери член-данните size от класа на обекта iA.

Присвояването на стойността на един обект от тип масив чрез друг се обработва така:

IntArray& operator= ( IntArray& );

Гореспоменатият оператор е част от механизма, който позволява на класа да презарежда значението на С++ операторите, когато те се прилагат към обекти от тип клас. (Раздел 6.3) обсъжда подробно презареждането на класовите оператори). Ето и реализацията на тази функция. Забележете, че тя дава на масива-назначение размера на масива, от който се копира.

IntArray& IntArray::Operator=( const IntArray &iA ) {
delete ia; // free up existing memory
size = iA.size; // resize target
ia = new int[ size ]; // get new memory
for ( int i = 0; i < size; ++i )
ia[ i ] = iA.ia[ i ] ; // copy
return *this; }

Вж. Раздел 6.4 за повече информация относно оператора:

return *this;

Презареденият оператор за присвояване ще бъде извикван автоматично винаги, когато се присвоява един обект от тип IntArray на друг. Например,

ia2 = myArray;

Класът масив не би предизвиквал такъв практически интерес ако потребителят не може лесно да индексира един класов обект. Необходимо е да се поддържа следната конструкция за цикъл:

for ( int i = 0; i < upperBound; ++i )
myArray[ i ] = myArray[ i ] + 1;

където на upperBound е дадена стойността на size от обекта myArray на класа IntArray. Всичко това може да бъде реализирано чрез член-функциите getSize() и operator[]. getSize(), наречена функция за достъп, предлага достъп за четене на една друга собствена стойност. Понеже тя е много малка, дефиницията й е включена в дефиницията на класа. UpperBound може да получи стойност като използува getSize():

upperBound = myArray.getSize();

или самата getSize() може да замести upperBound в цикъла for:

for ( int i = 0; i < myArray.getSize(); ++i )

Функцията, реализираща индексния оператор, не е много по-голяма от getSize(), но трябва да е така реализирана, че да осигурява възможност както за четене, така и за запис. Частта, осигуряваща четенето се реализира просто: взема се стйността на индекса и се връща съответния елемент. Това е необходимо за реализиране на оператори от вида:

int i = myArray[ someValue ];

както и от вида:

myArray[ i ] = someValue;

За да се явява myArray[i] от лявата страна на оператор за присвояване, той трябва да има стойност за запис (lvalue). Това може да бъде направено чрез дефиниране на връщаната стойност от псевдонимен /съотнасящ/ тип ( да припомним, че този тип предлага псевдоним на дадена променлива - Раздел 4.7 разглежда връщането на стойност от тип-псевдоним). Реализацията на тази член-функция би могла да изглежда така:

int& IntArray::Operator[](int index) { return ia[index]; }

Обикновено, дефиницията на класа, както и на всички свързани с него константни стойности се записват в един заглавен файл. Този файл се именува чрез името на класа. В гореописания случай заглавният файл се нарича IntArray.h. Всички програми, които класът IntArray() използува, трябва да са включени в този заглавен файл. Съответно, член-функциите на класа обикновено се записват в текстов файл, именуван също чрез името на класа. В този случай, член-функциите ще бъдат записани във файла IntArray.C. За да използува тези функци програмистът трябва да ги добави към своя изпълним файл. Вместо да прекомпилираме тези функции с всяка програма, която желае да използува класа Intarray, ние можем да ги компилираме в библиотека. Това се прави така:

$ CC -c IntArray.C
$ ar cr IntArray.a IntArray.o

ar е команда за създаване на архивна библиотека, поддържана от системата UNIX. Символите cr, които следват, представляват опции за командния ред. IntArray.o е един обектен файл, съдържащ машинните инструкции, съответствуващи на С++ програмата. Той се генерира, когато на компилатора се зададе опцията -с. IntArray.a е името, което ще бъде дадено на библиотеката, съдържаща класа IntArray. За да използува тази библиотека, програмистът може да зададе името й явно на командния ред когато компилира програма:

$ CC main.c IntArray.a

Това ще предизвика включването на член-функциите на класа IntArray в изпълнимия код на програмата.

Упражнение 1-14. Илюстрираният тук клас IntArray предлага минимален брой операции. Напишете списък на някои допълнителни възможности, които считате, че трябва да бъдат добавени към този клас.

Упражнение 1-15. Полезно би било да можем да инициализираме обект от тип IntArray чрез някакъв цял масив. Опишете един общ алгоритъм за реализиране на следния конструктор:

IntArray::IntArray( int *ia, int size );

Трябва да могат да бъдат поддържани следните дефиниции:

int ia[ 4 ] = { 0, 1, 2, 3 };
IntArray myIA( ia, 4 );

IntArray ни представя един важен аспект от използуването на класовия механизъм в С++. IntArray може да бъде наречен абстрактен даннов тип. Програмистите могат да използуват класа IntArray по съвсем същия начин, по който и всички останали предварително дефинирани типове данни в С++. Този аспект на класовете се разглежда в глави 5 и 6.

Втори важен аспект на класовия механизъм е възможнастта за дефиниране на подтипови връзки. Например, IntArrayRC е един тип на класа цели масиви, който също осигурява проверка дали индексните стойности съответствуват на дефинирания му обхват. Той е реализиран на основата на т.н. наследствен механизъм. Ето дефиницията на IntArrayRC:

#include "IntArray.h"
class IntArrayRC : public IntArray { public:// constructors are not inherited
IntArrayRC( int = ArraySize );
int& operator[]( int );
protected:
void rangeCheck( int );
}

IntArrayRC трябва да дефинира само тези аспекти на реализацията, които са различни или допълнителни спрямо реализацията на IntArray. Той трябва да предлага:

1. такъв индексен оператор на екземплярите си, който да проверява дали даден индекс не надхвърля границите на масива.
2. функция, която извършва тази проверка.
3. собствен набор от функции за автоматична инициализация - т.е. собствен набор от конструктори.


Страници: «4 5 6 7 8 »

Сподели урока:



Коментари (1)

hotsaucebg на 27.06 2009 в 17:43ч.
Няма ли уроци само за C

Регистрирайте се, за да добавите коментар


Калдейта Ком ЕООД - © 2003-. Всички права запазени.
Препоръчваме: IT Новини