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

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

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



2.4. Съотнасящи типове (reference types)

Този тип се дефинира като след спецификатора на тип се добави адресен оператор. Дефиницията на съотнесен обект трябва да включва и инициализация. Например,

int val = 10;
int &refal = val; // ok
int &refVal12; // error: uninitialized

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

refVal += 2;

добавя 2 към val, като тя става 12.

int ii = refVal;

присвоява на ii стойността на val, докато

int *pi = &refVal;

инициализира pi чрез адреса на val.

За съотнасянето може да се мисли като за специален вид указател, към който може да бъде прилаган синтаксиса на обекти. Например, стойността на израза

( *pi == refVal && pi == &refVal )

винаги е истина ако pi и refVal адресират един и същ обект. За разлика от указателя, обаче, съотносителната променлива може да бъде инициализирана и, веднъж инициализирана, не може да стане псевдоним на друг обект.

В списъка от декларации на две или повече променливи -псевдоними е необходимо да се добавя адресен оператор пред всеки идентификатор. Например,

int i;

int &f1 = i, r2 = i; // one reference, r1; one object, r2
int r1, &r2 = i; // one object, one reference, r2
int &r1 = i, &r2 = i; // two references, r1 and r2

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

int &ir = 1024;

се преобразува така:

int T1 = 1024;
int &ir = T1;

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

unsigned int ui = 20;
int &ir = ui;

се преобразува като

int T2 = int(ui);
int &ir = T2;

Основното използуване на този тип е като аргумент или като тип за връщане, особено когато се прилага за дефинирани от потребителя класови типове.

2.5. Константни типове

Съществуват два проблема с оператора за цикъл for, отнасящи се до използуването на 512 като горна граница.

for (int i = 0; i < 512; ++i );

Първият проблем е свързан с четимостта на текста. Какво означава да се сравни i с 512? Какво прави цикъла, т.е., какво значение има 512? (В този пример, 512 може да се нарече “вълшебно число”, чието значение не е очевидно в контекста на използуването му. В този случай може да се каже, че числото е откъснато от средата си).

Вторият проблем е свързан с поддържането. Представете си, че дадена програма се състои от 10,000 реда. Цикъл for от подобен вид се явява в 4% от текста. Стойността 512 трябва да бъде променена на 1024. Всичките 400 появявания на 512 трябва да бъдат открити и променени. Пропускането дори и на един екземпляр прекъсва програмата.

Двата проблема могат да бъдат решени като се използува идентификатор, инициализиран с 512. Чрез избирането на подходящо мнемонично име, напр. bufSize, ние можем да направим програмата по-лесна за четене. Проверката сега е по-скоро спрямо идентификатор, отколкото спрямо константа:

i < bufSize

Вече не е необходимо да бъдат променяни 400 - те появи на константата за случая, когато се променя bufSize. По-скоро може да бъде коригиран само реда, на който се инициализира bufSize. Това не само значително намалява работата, но и снижава вероятността за допускане на грешки. Цената на решението е една допълнителна променлива. За стойността 512 сега може да се каже, че е локализирана.

int bufSize = 512; // input buffer size
// ...
for ( int i = 0; i < bufSize; ++i )
// ...

Проблемът при това решение е, че bufSize е стойност за запис - lvalue. Възможно е променливата bufSize случайно да бъде променена в програмата. Например, ето една обща грешка, правена често от програмисти преминаващи от паскалоподобен език към С++.

// accidentally changes the value of bufSize
if ( bufSize = 1 )
// ...

В С++ “=” е оператор за присвояване, а “==” е оператор за проверка на равенство. Паскал и произлезлите от него езици използуват “=” като оператор за проверка на равенство. Така програмистът може случайно да промени стойността на bufSize на 1, което ще се превърне в трудна за откриване програмна грешка. (Често такава грешка е трудна за откриване, защото програмистът не може да я “види” в програмата - затова много компилатори правят предупреждение за този тип присвояване). Модификаторът на тип const дава едно решение на проблема. Той преобразува променливата в константа. Например,

const int bufSize = 512; // input buffer size

дефинира bufSize като константа, инициализирана със стойност 512. Всеки опит за променяне на тази стойност някъде вътре в програмата ще причини появата на грешка по време на компилация. Именуваната константа може да се нарече променлива само за четене.

Понеже именуваната константа не може да бъде променяна след като е била дефинирана, тя трябва да бъде инициализирана. Дефиницията на неинициализирана именувана константа ще предизвика грешка по време на компилация. Например,

const double PI; // error: uninitialized const

Грешка от компилация се появява и когато адрес на именувана константа се присвоява на указател. Иначе, стойността на константата би могла да бъде променена чрез указателя. Например,

const double minWage = 3.60;
double *p = &minWage; // error
*p += 1.40;

Програмистът, обаче, може да декларира указател, който да адресира константа. Например,

const double *pc;

pc е указател към константен обект от тип double. pc, сам по себе си, обаче, не е константа. Това означава следното:

1. pc може да бъде променян да адресира друга променлива от тип double по всяко време вътре в програмата.
2. Стойността на обекта, адресиран чрез pc, не може да бъде променяна като се използува pc. Например,

pc = &minWage; // ok
double d;
pc = &d; // ok
d = 3.14159; // ok
*pc = 3.14159; // error

Адресът на именувана константа също може да бъде присвояван на указател към константа, такъв като pc. Указател към константа, обаче, може също да адресира и обикновена променлива, както например

pc = &d;

Въпреки че d не е константа, програмистът е убеден, че стойността й няма да бъде променяна чрез pc. Указатели към константни обекти най-често се дефинират като формални параметри на функции. Раздел 4.6 разгрежда използуването на указатели към константи. Програмистът може също да дефинира указател-константа. Например,

int errNumb; // possible error status of program
int *const curErr = &errNumb; // constant pointer
curErr константен указател към обект от тип int. Програмистът може да променя стойността на обекта на адрес curErr:
if ( *curErr ){
errorHandler();
*curErr = 0;
}

но не може да променя адреса, който curErr съдържа:

curErr = &myErrNumb; // error

Може да бъде дефиниран и указател - константа към константен обект:

const int pass = 1;
const int *const true = &pass;

В този случай не могат да бъдат променяни, както стойността на адресирания чрез true обект, така и самия адрес.

Упражнение 1-7. Обяснете значението на следните пет дефиниции. Определете кои от тях са правилни.

(a) int i; (d) int *const cpi;
(b) const int ic; (e) const int *const cpic;
(c) const int *pic;

Упражнение 1-8. Кои от следните инициализации са коректни? Обяснете защо.

(a) int i = ‘a’;
(b) const int ic = i;
(c) const int *pic = &ic;
(d) int *const cpi = &ic;
(e) const int *const cpic = &ic;

Упражнение 1-9. Като имате предвид дефинициите в предишните упражнения, кажете кои от следните оператори за присвояване са коректни? Обяснете защо.

(a) i = ic; (d) pic = cpic;
(b) pic = &ic; (e) cpic = &ic;
(c) cpi = pic; (f) ic = *cpic;


2.6. Изброими типове

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

Един изброим тип може да бъде деклариран като се използува ключовата дума enum и списък от разделени със запетая идентификатори, затворен във фигурни скоби. По подразбиране на първия идентификатор се присвоява стойност 0. На всеки следващ идентифкатор се присвоява стойност с едно по-голяма от тази на предхождащия го. Например, следните декларации свързват FALSE с 0, а TRUE - с 1.

enum { FALSE, TRUE }; // FALSE == 0, TRUE == 1

Дадена стойност може явно да бъде присвоена на елемент от изброим тип. Не е необходимо тази стойност да бъде уникална. Както и преди, когато не беше дадено явно присвояването, стойността, свързвана с даден елемент, е с едно по-голяма от тази на предхождащия го елемент. В следващия пример FALSE и FALL се свързват със стойността 0, а PASS и TRUE - с 1:

enum { FALSE, FALL = 0, PASS, TRUE = 1 };

На всеки изброим тип може да бъде дадено име. Всеки именуван из-броим тип дефинира уникален тип, който може да бъде използуван като спецификатор на тип за деклариране на идентификатори. Например,

enum TestStatus { NOT_RUN = -1, FALL, PASS };
enum Boolean { FALSE, TRUE };
main() {
const testSize = 100;
TestStatus testSuite [ testSize ];
Boolean found = FALSE;
for ( int i = 0; i < testSize; ++i )
testSuite [ i ] = NOT_RUN;
}

Именуваният изброим тип не беше дефиниран във версиите на езика С++, преди 2.0. Следователно за осигуряване на съвместимост, нарушаването на типовете при инициализация и присвояване на стойност на идентификатор от изброим тип не се отбелязва като грешка в настоящата реализация на езика от AT&T. Обаче се издават предупреждения, и те не трябва да бъдат игнорирани. Например,

main() {
TestStatus test = NOT_RUN;
Boolean found = FALSE;
test = -1; // error: TestStatus = int
test = 10; // error: TestStatus = int
test = found; // error: TestStatus = Boolean
test = FALSE; // error: TestStatus = const Boolean
int st = test; // ok: implicit conversion
}

Като декларира променливата test от изброимия тип TestStatus, програмистът указва на компилатора да проверява, дали на test се присвоява една от трите валидни стойности. И накрая, използуването на именуван изброим тип предлага една удобна форма за документиране на програмата.

2.7. Тип масив

Масивът е съвкупност от елементи от един и същ даннов тип. Самите обекти не са именувани, а по-скоро са достъпни чрез използуването на местоположението им в масива. Този метод на достъп се нарича обикновено индексен или абонатен.

Например,

int i;

декларира единичен обект от цял тип, докато

int ia[ 10 ];

декларира масив от 10 такива обекта. Всеки обект се нарича елемент на масива ia. Така

i = ia[ 7 ];

присвоява на i стойността на елемента с индекс 7. Съответно, ia[ 7 ] = i;

присвоява на елемента с индекс 7 стойността на i.

Всяка дефиниция на масив се състои от спецификатор на тип, идентификатор и размерност. Размерността, която определя броя наелементите на масива, се затваря в скоби от вида “[ ]”. Масивът може да има размерност по-голяма или равна на единица. Стойността, задаваща размерността, трябва да бъде константен израз; т.е. необходимо е тази стойност да може да бъде изчислена по време на компилация. Това означава, че не може да се използува променлива за задаване на размерност на масив. Следват няколко примера за коректни дефиниции на масиви:

const bufSize = 512,
stackSize = 25, maxFiles = 20, staffSize = 27;
char inputBuffer [ bufSize ];
int tokenStack [ stackSize ];
char *fileTable [ maxFiles - 3 ];
double salaries [ staffSize ];

Забележете, че елементите на един масив се номерират, като се започва от 0. За масив от 10 елемента правилните индексни стойности са от 0 до 9, а не от 1 до 10. Това често се явява причина за програмни грешки. Например, следният цикъл for преглежда десетте елемента на един масив, като всеки от тях се инициализира със стойността на индекса си:

const SIZE = 10;
int ia[ SIZE ];
main () {
for ( int i = 0; i < SIZE; ++i )
ia[ i ] = i;
}

Даден масив може да бъде да бъде инициализиран явно като се използува затворен в скоби списък от стойности, разделени със запетаи.( Такава инициализация е възможна само за масиви, дефинирани вън от функции. Разликата между дефинициите вътре и вън от функции се обсъжда в глава 3). Например,

const SZ = 3;
int ia[] = { 0, 1, 2 };

За явно инициализирания масив не е необходимо да се задава размерност. Компилаторът ще опрдели размера на масива по броя на записаните елементи:

int ia[] = { 0, 1, 2 }; // an array of dimension 3

Ако размерността е определена, броят на зададените елементи не трябва да надхвърля този размер. В противен случай ще се появи грешка по време на компилация. Ако размерността е по-голяма от броя на зададените елементи, то елементите на масива, които не са инициализирани явно ще получат стойност 0.

const SZ = 5;
int ia[ SZ ] = { 0, 1, 2 }; // ia ==> { 0, 1, 2, 0, 0 }

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

char ca1[] = { ‘C’, ‘+’, ‘+’ };
char ca2[] = "C++";
ca1 има размерност 3, а ca2 има размерност 4. Следните декларации ще бъдат отбелязани като грешни:
char ch3[6] = "Pascal";// error: "Pascal" is 7 elements

Не е разрешено масив да бъде инициализиран като се използува друг масив, нито пък даден масив да бъде присвояван на друг.

const int SZ = 3;
int ia[ SZ ] = { 0, 1, 2 };
int ia2[] = ia; // error
int ia3[ SZ ];
ia3 = ia; // error

За да бъде копиран един масив в друг последователно трябва да бъдат копирани всичките му елементи. Тази операция е достатъчно обща за да бъде организирана в отделна функция; нека я наречем copyArray().

copyArray() изисква два масива, единият за да получи

стойностите, които ще бъдат копирани, а другият да съдържа тези стойности. Ще ги наречем съответно назначение и източник. Това ще бъдат аргументите на тази функция.

Трябва да се отбележи, че ще са необходими различни функции за всеки различен тип масив, който ние желаем да копираме, като масив от цели или реални числа, например, дори и ако истинският код на С++, реализиращ това е един и същ. За целите на нашия пример ние ще дефинираме функцията copyArraay() за цели числа. Първоначалното заглавие на нашата функция ще има вида:

void copyArray( int torget[], int source[] );

Когато даден масив се изпраща като параметър на функция, той се преобразува в указател към нулевия си елемент; информацията за размерността се загубва. Функцията, към която се изпраща масив трябва някак да установи размерността му. Един възможен начин да се направи това е да се добави втори аргумент, съдържащ размера на масива. copyArray() се реализира така:

С въвеждането на параметризиран тип това ограничение ще бъде отстранено. Вж. Приложение В за пояснения.

void copyArray( int target[], int source[],
int targetSize, int sourceSize ) {

/* copy source to target
set additional target elements to 0 */
ind upperBound = targetSize;
if ( targetSize > sourceSize )
upperBound = sourceSize;
for ( int i = 0; i < upperBound; ++i )
target[ i ] = source[ i ];
while ( i < targetSize )
target[ i++ ] = 0;
}

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

Упражнение 1-10. Кои от следните дефиниции на масиви са правилни? Обяснете защо.

int getSize();
int bufSize = 1024;

(a) int ia[ bufSize ]; (c) int ia[ 4 * 7 - 14 ];
(b) int ia[ getSize() ]; (d) int ia[ 2 * 7 - 14 ];

Упражнение 1-11. Защо следната инициализаця е грешна?

char st[ 11 ] = "fundamental";

Упражнение 1-12. В следващия кодов фрагмент има две грешки, свързани с индексирането на масива ia. Намерете ги.

main() {
const max = 10;
int ia[ max ];
for ( int index = 1; index <= max; ++index )
ia[ index ] = index;
// ...
}



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

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



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

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

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


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