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

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

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



Дефиниции на променливи

Една проста дефиниция се състои от спецификатор на тип следван от име. Дефиницията се ограничава от точка и запетая. Ето някои примери на прости дефиниции:

double salary;
double wage;
int month;
int day;
int year;
unsigned long distance;

Когато се дефинира повече от един идентификатор за даден тип, списъкът от идентификатори, записан след спецификатора на тип, се разделя чрез запетаи. Този списък може да бъде разположен на някол-ко реда. Ограничава се от точка и запетая. Например, предходните дефиниции могат да бъдат записани по следния начин:

double salary, wage;
int month,
day, year;
unsigned long distance;

Всяка проста дефиниция определя типа и идентификатора на променливата. Тя не дава начална стойност. За променлива, която няма начална стойност, се казва че е неинициализирана. Всяка неинициализирана променлива фактически има стойност; но по-скоро може да се каже, че стойността й е недефинирана. Това е така, понеже паметта, отделена за съхраняване на променливата не е изтрита. Просто е останало това, което е било записано в паметта при предходното използуване на тази памет. Когато се чете една неинициализирана променлива случайната битова после-дователност се интерпретира като нейна стойност. Тази стойност ще се променя за различните изпълнения на програмата. Следната примерна програма илюстрира случайния характер на неинициализираните данни.

#include <stream.h>

const iterations = 2;
void func() {
// illustrate danger of uninitialized variables
int value1, value2; // uninitialized
static int depth = 0;
if ( depth < iterations ) {
++depth;
func();
}
else depth = 0;
cout << "nvalue1:t" << value1;
cout << "nvalue2:t" << value2;
cout << "tsum:t" << value1 + value2;
}
main() {
for ( int i = 0; i < iterations; ++i )
func();
}

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

value1: 0 value2: 74924 sum: 74924
value1: 0 value2: 68748 sum: 68748
value1: 0 value2: 68756 sum: 68756
value1: 148620 value2: 2350 sum: 150970
value1: 2147479844 value2: 671088640 sum: -1476398812
value1: 0 value2: 68756 sum: 68756

В тази програма iterations се използува като константа. Това се отбелязва с ключовата дума const. Константите се разглеждат в раздел 1.5 на тази глава. depth представлява локална статична променлива.

Значението на думата static се разяснява в раздел 3.10 при обсъждането на обхвата. func() е описана като рекурсивна функция. Раздел 4.1 разглежда рекурсивните функции.

В дефиницията на една променлива може да й се даде първоначална стойност. За променлива, на която е дадена първоначална стойност в декларацията се казва, че е инициализирана. Ето няколко примера за инициализиране на променливи:

#include <math.h>
double price = 109.99, discount = 0.16,
salePrice = price * discount;
int val = getValue();
unsigned absVal = abs ( val );

Предварително дефинираната функция abs(), намираща се в библиотеката math, връща абсолютната стойност на аргумента си. getValue() е функция, дефинирана от потребителя, която връща случайно цяло число. Променливите могат да бъдат инициализирани със произволни сложни изрази.

2.3. Указателни типове

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

Всеки указател се свързва с определен тип. Типът на данните определя типа на обекта, който ще бъде адресиран чрез указателя. Например, указател от тип int ще соче обект от тип int. Съответно, за да сочи обект от тип double указателят трябва да се дефинира от тип double.

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

int *ip1, *ip2;
unsigned char *ucp;
double *dp;

Дефиницията на указател се състои от идентификатор, предхождан от оператора (“*”). В разделения със запетаи списък на дефинициите операторът * трябва да предхожда всеки идентификатор, който искаме да ни служи като указател. В следващия пример lp се интерпретира като указател към променлива от тип long, а lp2 - като даннов обект от тип long, а не като указател.

long *lp, lp2;

В примера, който следва, fp се интерпретира като даннов обект от тип float, а fp2 се интерпретира като указател към променлива от тип float:

float fpf, *fp2;

За по-голяма яснота се препоръчва да се записва

char *cp;

а не

char* cp;

Много често, програмистът, желаещ да дефинира по-късно втори указател към тип char, ще промени неправилно тази дефиниция така:

char* cp, cp2;

Даден указател може да бъде инициализиран със стойността за запис (lvalue) на даннов обект от същия тип. Припомняме, че обекта, намиращ се от дясно на оператора за присвояване дава стойността за четене (rvalue). За да се извлече стойността за запис на обект а трябва да се приложи специален оператор. Той се нарича адресен оператор и се записва със съмвола &. Например,

int i = 1024;
int *ip = &i; // assign ip the addres of i

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

int *ip2 = ip;

Винаги се счита за грешка ако указател се инициализира като се използува даннов обект от тип rvalue. Следните декларации няма да бъдат приети за правилни по време на компилация:

int i = 1024;
int *ip = i; //error

Грешно е също указател да се инициализира чрез стойността за запис lvalue на обект от различен тип. Дефинициите на uip и uip2 ще бъдат отбелязани като неправилни по време на компилация:

int i = 1024, *ip = &i; // ok
unsigned int *uip = &i; // illegal
*uip2 = ip; // illegal

С++ е строго типизиран език. Всички инициализации и присвоявания на стойности се проверяват за да сме сигурни, че тези стойности са коректно съпоставими. Ако те не са и съществува правило за съпоставянето им, компилаторът ще го приложи. Това правило се нарича правило за преобразуване на типовете. (вж. раздел 2.10 за подробности). Ако правило няма, операторът се отбелязва като грешен. Желателно е това да бъде извършвано, понеже не е безопасно да се прави инициализация или присвояване без преобразуващо правило и вероятно ще бъде последвано от програмна грешка по време на изпълнение.

Би следвало да бъде очевидно защо е опасно присвояването на обект от тип rvalue на указател. По дефиниция стойността на указателя представя адрес в паметта. Всеки опит за четене или запис на този “адрес” е опасен.

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

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

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

Указател от произволен тип може да получи стойност 0, като това ще показва, че в момента указателят не адресира даннов обект. Стойността 0, когато се използува като стойност на указател, понякога се нарича NULL. Съществува също специален тип на указател, void*, с който може да бъде присвоен адрес на обект от произволен даннов тип. (Раздел 3.10 разглежда указателния тип void*).

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

int i = 1024;
int *ip = &i; // ip now points to i
int k = *ip; // k now contains 1024

Когато не е приложен оператора *, k ще бъде инициализирана като адрес на i, а не чрез нейната стойност, което ще предизвика грешка при компилация.

int *ip = &i; // ip now points to i
int k = ip; // error

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

int *ip = &i; // ip now points to i
*ip = k; // i = k;
*ip = abs( *ip ); // i = abs(i);
*ip = *ip + 1; // i = i + 1;

Следните два оператора за присвояване дават съвсем различни резултати, въпреки че и двата са коректни. Първият оператор увеличава адреса който указателя ip съдържа; вторият увеличава стойността на данновия обект, който ip адресира.

int i, j, k;
int *ip = &i;
ip = ip + 2; // add to the address ip contains
*ip = *ip + 2; // i = i + 2;

Към адресната стойност на указателя може да бъде добавяна или изваждана цяла стойност. Този тип обработка на указатели, наричан указателна или адресна аритметика, в началото изглежда малко неестествен, докато не осъзнаем, че се осъществява събиране с даннов обект, а не с отделна десетична стойност. Т.е., добавянето на 2 към един указател увеличава стойността на адреса, който той съдържа, с размера на два обекта от неговия типа. Например, като допуснем, че типът char заема 1 байт, int - 4 байта, а double - 8, добавянето на 2 към даден указател увеличава адресната му стойност съответно с 2, 8 или 16 в зависимост от типа му char, int или double.

Упражнение 1-3. Дадени са следните дефиниции:

int ival = 1024;
int *iptr;
double *dptr;

участвуващи в следните оператори за присвояване. Кои от тях са правилни? Обяснете защо.

(a) ival = *iptr; (b) ival = iptr;
(c) *iptr = ival; (d) iptr = ival;
(e) *iptr = &ival; (f) iptr = &ival;
(g) dptr = iptr; (h) dptr = *iptr;

Упражнение 1-4. На дадена променлива се присвоява една от следните три стойности:

0, 128 и 255.

Разгледайте предимствата и недостатъците на декларирането на променливата като принадлежаща на някои от следните даннови типове:

(a) double (c) unsigned char
(b) int (d) char


Указатели към низове

Най-често указатели се дефинират към предварително дефинирания даннов тип char*. Това е така, понеже цялата обработка на низове в С++ се осъществява чрез символни указатели. Този подраздел пояснява подробно използуването на char*. В глава 6 ще дефинираме класовия тип String.

Типът на литерална низова константа представлява указател към първия символ на низа. Това означава, че всяка низова константа е от тип char* и може да бъде инициализарана като низ по следния начин:

char *st = "The expense of spiritn";

Следната програма, проектирана да изчислява дължината на st, използува адресната аритметика за преглеждането на низа. Идеята е да се завърши изпълнението на цикъла, когато бъде срещнат нулевия символ, поставян от компилатора в края на всяка литерална низова константа. За нещастие програмата, която сме написали е неправилна. Бихте ли могли да установите каква е грешката?

#include <stream.h>

char *st = "The expense of spiritn";
main() {
int len = 0;
while ( st++ != ‘�’ ) ++len;
cout << len << ": " << st;
return 0;
}

Грешката в тази програма произтича от факта, че st не е указана. Т.е.,

st++ != ‘�’

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

Нашата втора версия на програмата поправя тази грешка. Тя се изпълнява до край. За нeщастие, обаче, има грешка в изхода й. Низът, адресиран от st не се отпечатва. Бихте ли могли да откриете грешката?

#include <stream.h>

char *st = "The expense of spiritn";
main() {
int len = 0;
while ( *st++ != ‘�’ ) ++len;
cout << len << ": " << st;
return 0;
}

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

st -= len;
cout << len << ": " << st;

Програмата може да бъде компилирана и изпълнена. Но изходът й все още е некоректен. Той има вида:

22: he expense of spirit

Това е свързано със самото естество на програмирането. Можете ли да откриете грешката, която е допусната този път?

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

st -= len + 1;

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


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

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



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

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

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


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