Глава 1: Типове данни в С++
Езикът С++ предлага набор от предварително дефинирани
типове данни, оператори за обработката им, както и няколко
оператори за програмен контрол. Тези елементи определят
азбуката, чрез която могат да бъдат написани множество големи
системи, приложими в реалния свят. На това базисно ниво С++ е
един прост език. Неговата изразителна сила се увеличава чрез
поддържането на механизми, които позволяват на програмиста да
дефинира нови даннови съвкупности.
Първата стъпка при усвояването на С++ - разбирането
на базисния език - е тема на тази и следващата глава. Тази
глава обсъжда предварително дифинираните типове данни пояснява
механизма за конструиране на нови типове данни, докато глава 2
разглежда предварително дефинираните операции и оператори.
Текстът на програмата, която пишем, както и данните,
които обработваме, са записани в паметта на компютъра като
последователност от битове. Всеки бит представлява единична
клетка, където могат да се съдържат стойностите 0 или 1. На
физичен език тези стойности са елктрически заряди,
съответствуващи на "off" или "on". Обикновено част от паметта
на компютъра изглежда така:
...00011011011100010110010000111011...
Съвкупността от битове на това ниво няма структура. Трудно е
да се говори за този поток от битове по който и да е смислен
начин.
Върху последователността от битове се налага структура
като се счита, че те са групирани в байтове и думи. Най - общо
казано, байтът е съвкупност от 8 бита. Обикновено една дума се
образува от 16 или 32 бита. Размерът на байта и думата варират
между различните компютри. За тези стойности често се казва,
че са машинно зависими. Фигура 1.1. показва горната
последователност от битове, организирана в четири адресуеми
редици от байтове.
Организацията на паметта ни позволява да се обръщаме
към подходяща съвкупност от битове. По такъв начин вече е
възможно да говорим за думата на адрес 1024 или за байта на
адрес 1040, което ни позволява да казваме например, че байта
на адрес 1032 не е равен на байта от адрес 1048.
Но все още не е възможно да се говори смислено за
съдържанието на байта на адрес 1032. Защо? Защото не знаем как
да интерпретираме неговата битова последователност. За да
говорим за значението на байта от адрес 1032, ние трябва да
знаем типа на стойността, която е представена.
Абстракцията на типовете ни позволява да правим
смислена интерпретация на битова последователност с фиксирана
дължина. Символите, целите и реалните числа са примери за
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
16.
1024 0 0 0 1 1 0 1 1
1032 0 1 1 1 0 0 0 1
1040 0 1 1 0 0 1 0 0
1048 0 0 1 1 1 0 1 1
Фиг. 1.1 Адресуема машинна памет
типове данни. Други типове са адресите в паметта и машинните
инструкции, които управляват работата на компютъра.
С++ предлага един предварително дефиниран набор от
типове на данни, който позволява представянето на цели и
реални числа и на смостоятелни символи.
- Типът char може да бъде използуван за представяне на
единични символи или малки цели числа. Записва се в една
машинна дума.
- Типът int се използува за представяне на цели
стойности. Обикновено се записва в една машинна дума.
С++ предлага също short и long integer типове.
Фактическият размер на тези типове е машинно зависим. Типовете
char, short, int и long се наричат цели типове. Целите типове
могат да бъдат със или без знак (signed/unsigned). Разликата
се проявява в предназначението на най-левия бит на типа. Ако
типът има знак,най-левият бит се интерпретира като знаков бит,
а останалите битове представят стойността. Ако типът представя
беззнакова стойност, всички битове определят стойността. Ако
знаковият бит има съдържание 1, стойността се интерпретира
като отрицателна; ако е 0, като положителна. Един 8-битов
signed char може да представи стойностите от -128 до 127; а
unsigned char - от 0 до 255.
Типовете float и double представят реални числа с
единична и двойна точност.(ў) Обикновено типът float се
представя в една дума, а double - в две. Истинският размер е
машинно зависим. Изборът на типа данни се определя от размера
на стойностите, които трябва да бъдат записвани. Например, ако
стойностите никога не надхвърлят 255 и не са по-малки от 0,
тогава типът unsigned char е подходящ. Обаче, ако се очаква
стойностите да надхвърлят 255, е необходимо да се избере някой
от по-големите даннови типове.
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
(ў) Третият тип данни, представящ реални числа long double,
вероятно ще бъде добавен в близко бъдеще. Long double е
предложен за включване към стандарта на езика C ANSI.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
17.
1.1. Константни стойности
Когато в дадена програма се появява стойност като 1,
например, тя се приема за литерална константа: литерална,
защото можем да говорим за нея само като за стойност,
константа, защото стойността й не може да бъде променяна.
Всеки литерал има съответен тип. 1, например е от тип
int. 3.14159 е литерална константа от тип double. Считаме
литералните константи за неадресуеми; въпреки, че тяхната
стойност е разположена някъде в паметта, достъпът до този
адрес не е съществен.
Целите литерални константи могат да бъдат написани в
десетичен, осмичен или шестнадесетичен вид. ( Това не променя
битовото представяне на стойността.) Стойността 20, например,
може да бъде записана по един от следните три начина:
20 // десетичен
024 // осмичен
0х14 // шестнадесетичен
Водещата нула за литерална константа от цял тип указва, че
константата е от осмичен тип. Представяне, използуващо 0х или
0Х в началото на константата, указва, че тя е в
шестнадесетичен запис. (Приложение А обсъжда отпечатването на
стойности в осмичен и шестнадесетичен запис).
Всяка цяла литерална константа може да бъде дефинирана
от тип long чрез записване на L или l след стойността й.
(Буквата L може да бъде главна или малка). Използуването на
малка буква l не се препоръчва, понеже лесно може да бъде
сбъркана с цифрата 1. По подобен начин цяла литерална
константа може да бъде дефинирана като unsigned чрез добавяне
на U или u след стойността й. Литерална константа от тип
unsigned long може също да се дефинира. Например,
128u 1024UL 1L 8Lu
Реалните литерални константи могат да бъдат записвани
чрез експонента или по обичайния начин. При първото
представяне експонентата може да бъде записана като се
използуват буквите Е или е. Реална литерална константа може да
бъде дефинирана и от тип float чрез записване на F или f след
стойността й. Ето няколко примера за реални литерални
константи:
3.14159F 0.1f 0.0
3e1 1.0E-3 2.
Печатуемите литерални символни константи могат да
бъдат записани чрез заграждането на символа в единични
кавички. Например,
'a' '2' ',' ' ' (blank)
Непечатуемите символи, единичните или двойните кавички, както
и обърнатата наклонена черта могат да бъдат представени чрез
следните escape - последователности:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
18.
newline \n
horizontal tab \t
vertical tab \v
backspace \b
carrige return \r
formfeed \f
alert (bell) \a
backslash \\
question mark \?
single quote \'
double quote \"
Може да бъде използувана и обобщена escape - последователност.
Тя изглежда така:
\ооо
където ооо представлява последователност от една, две или три
осмични цифри. Стойността, представена чрез осмичните цифри,
представлява числената стойност на символа в символния набор
на машината. Примерите, които следват, представляват литерални
константи, като се използува символния набор ASCII:
\7 (bell) \14 (newLine)
\0 (null) \062 ('2')
Всяка низова литерална константа представлява съвкупност от
нула или повече символа, обградени с двойни кавички.
Непечатуемите символи могат да бъдат представяни чрез техните
escape - последователности. Низов литерал може да заеме
няколко реда от текста на програмата. Обратната наклонена
черта като последен символ на реда указва, че низовият литерал
продължава на следващия ред. Следва пример за низови литерални
константи:
"" (null string)
"a"
"\nCC\toptions\tfile:[cC]\n"
"a multi-line \
string literal signal its \
continuation with a backslash"
Низовият литерал е от тип масив от символи. Той се състои от
низов литерал и ограничаващия символ null, добавен от
компилатора. Например, докато 'a' представя единичния символ
а, то "a" се записва като символа а, следван от символа null.
Символът null се използува за отбелязване на края на низа.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
19.
1.2. Променливи
Представете си, че Ви е дадена задача да изчислите 2
на степен 10. Нашият първи опит би могъл да изглежда така:
#include
main()
{
// a first solution
cout << 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2;
cout << "\n";
return 0;
}
Написаното работи, въпреки че ще ни се наложи да преброим два
или три пъти дали сме записали константата 2 точно 10 пъти.
Само тогава ще бъдем доволни. Нашата програма правилно дава
отговор 1024.
Сега обаче, ни се налага да изчислим 2, повдигнато на
17 степен, а след това на 23. Неприятно е да променяме
програмата си всеки път. Още по-лошо, изглежда поразително
лесно да се направи грешка като се постави една двойка в
повече или по-малко. Обаче, понеже сме внимателни, ние
избягваме грешките.
Накрая ни се налага да направим таблица, която да
съдържа степените на двойката от 0 до 31. Ако използуваме
литерални константи в директни кодови последователности ще ни
бъдат нобходими 64 реда от следния вид:
cout << "2 raised to the power of X\t";
cout << 2 * ... * 2;
където Х ще се увеличава с единица за всяка кодова двойка.
В този момент, а може би и по-рано, ние осъзнаваме, че
трябва да има по-добър начин. Както и наистина има. Решението
изисква въвеждането на две понятия, които все още не са
формално дефинирани:
1. Променливи, които позволяват да се съхраняват и
възстановяват стойности.
2. Съвкупност от управляващи оператори, които
позволяват многократното изпълнение на част от програмния код.
Например, ето един втори начин за изчисляване на 2 на 10
степен:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
20.
#include
main()
{
// a second more general solution
int value = 2;
int pow = 10;
cout << value
<< " raised to the power of "
<< pow << ": \t";
for ( int i = 1, res = 1; i <= pow; ++i )
{
res = res * value;
}
cout << res << "\n";
return 0;
}
Операторът, започващ с for, се нарича оператор за цикъл:
докато i е по-малко или равно на pow, се изпълнява тялото на
for, затворено във фигурни скоби. Цикълът for се нарича
поточно управляващ оператор. (Програмните орератори са описани
подробно в гл. 2).
value, pow, res и i са променливи, които позволяват да
се съхраняват, променят и възстановяват стойности. Те са тема
на следващите подглави. Първо, обаче, нека приложим друго ниво
на обобщанане на програмата като отделим част от програмата,
която изчислява степента на величината и да я дефинираме като
отделна функция.
unsigned int
pow ( int val, int exp )
{
//compute val raised to exp power
for ( unsigned int res = 1; exp > 0; --exp );
res = res * val;
return res;
}
Всяка задача, която изисква изчисляването на някаква степен на
дадена стойност, сега може просто да извика pow() с подходящо
множество от аргументи. Исканата таблица от степени на
двойката сега може да бъде получена по следния начин:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
21.
#include
extern unsigned int pow ( int, int );
main()
{
int val = 2;
int exp = 16;
cout << "\mThe Power of 2\n";
for ( int i = 0; i <= exp; ++i )
cout << i << ": " << pow ( val, i ) << "\n";
return 0;
}
Таблица 1.1 представя резултата от изпълнението на тази
програма.
The Power of 2
0: 1
1: 2
2: 4
3: 8
4: 16
5: 32
6: 64
7: 128
8: 256
9: 512
10: 1024
11: 2048
12: 4096
13: 8192
14: 16384
15: 32768
16: 65536
Таблица 1.1 Степени на 2
Тази реализация на pow() не проверява онези особени
случаи, когато имаме повдигане на отрицателна степен или
стойността - резултат е много голяма.
Упражнение 1-1. Какво ще стане ако pow() бъде извикана
с отрицателен втори аргумент? Как може да бъде променена pow()
за да обработва това?
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
22.
Упражнение 1-2. Всеки тип данни има долна и горна
граница за стойностите, които може да поддържа, определени от
броя на битовете, отделени за представянето му. Как това може
да се отрази на pow()? Как да бъде променена функцията pow()
за да обработва повиквания като pow( 2, 48 )?
Какво е променлива?
Всяка променлива се идентифицира от име, дефинирано
от потребителя. Тя има и съответен тип. Например, следващият
оператор дефинира променлива ch от тип char:
char ch;
char спецификатор на тип. short, int, long, float и double
също представят типови спецификации. Изобщо, всяка декларация
трябва да започва с типов спецификатор. Типовете на данните
определят количеството памет, отделено за променливата, както
и набора от операции, които могат да бъдат прилагани над този
тип данни. (За нашите предположения char ще има размер в
битове 8).
Както променливите, така и константите се съхраняват в
паметта и са свързани с определен тип. Разликата се състои в
това, че променливите са адресуеми. Т.е., има две стойности,
свързани с дадена променлива:
1. Нейната стойност, съхранена на някакво място в
паметта. Това поянкога се нарича нейна rvalue (произнася се
"are-value").
2. Стойността, определяща местоположението й; т.е.,
адреса в паметта, където е записана величината. Това понякога
се нарича нейна lvalue (произнася се "ell-value").
В израза
ch = ch - '0';
променливата ch се намира както от ляво така и отдясно на
оператора за присвояване. Написана от ляво, тя трябва да бъде
прочетена. Стойността й се извлича от местоположението й в
паметта. След това символният литерал се изважда от тази
стойност. Терминът rvalue произлиза от местоположението на
променливата в дясно на оператора за присвояване. Тя може да
бъде четена, но не и променяна. За нея може да се мисли като
за стойност за четене.
Написана от дясно, променливата ch ще бъде записвана.
Резултатът от операцията изваждане се записва на мястото за
стойност на ch върху предходната стойност. Терминът lvalue
произлиза от разположението на променливата от лявата страна
на оператора за присвояване. За нея може да се мисли като за
стойност на местоположение.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
23.
ch се означава като обект. Всеки обект представя
някаква област от паметта. ch представя област от паметта с
размер 1 байт.
Дефиницията на една променлива указва как тя да бъде
съхранена. Дефиницията определя името на променливата и нейния
тип. Съответно, би могла да бъде добавена и начална стойност
за променливата. Трябва да има една и само една дефиниция на
дадена променлива в програма.
Декларацията на променливата обявява, че променливата
съществува и е дефинирана някъде. Тя се състои от името на
променливата, типа й и ключовата дума extern. (За повече
информация, вж. раздел 3.9 (стр. 124) Програмен обхват).
Декларацията не е дефиниция. Тя не предизвиква заделяне на
място в памет. По-скоро тя едно твърдение, че дефиницията на
променливата съществува някъде в текста на програмата. Една
променлива може да бъде декларирана неколкократно в
програмата.
В С++ всяка променлива трябва да бъде дефинирана или
декларирана в програмата преди да може да бъде използувана.
Име на променлива
Името на променливата, т.е. нейният идентификатор,
може да бъде образувано от букви, цифри и подчертаващо
тиренце. Главните и малките букви са различими. Няма езиково
наложено ограничение върху разрешената дължина на името, тя е
различна за различните реализации.
В С++ има набор от думи, предназначени за използуване
от езика като ключови думи. Предварително дефинираните типови
спецификатори, например, са запазени думи. Идентификаторите,
които са ключови думи, не могат да бъдат използувани като
програмни идентификатори. Таблица 1.2 дава списък на
запазените ключови думи в С++.(ў)
Съществуват множество общоприети споразумения за
именуване на идентификатори, подпомагащи читаемостта на
програмата:
- Обикновено идентификаторът се записва с малки букви.
- Идентификаторът има мнемонично име; т.е., име, което
пояснява неговото използуване в програмата.
- Идентификаторите, които се състоят от няколко думи,
се записват или с разделящо подчертаващо тире или като се
използуват главни букви за всяка включена дума. Ннапример,
може да се напише is_empty или isEmpty, но не isempty.
ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ
(ў) Забележете, че template е предполагаема ключова дума за
възможно бъдещо разширение на С++ за поддържане на
параметризирани типове.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
24.
asm delete if register template
auto do inline return try
break double int short typedef
case else long signed union
catch enum new sizeof unsigned
char extern operator static virtual
class float overload struct void
const for private switch volatile
continue friend protected this while
default goto public
Таблица 1.2 Ключови думи в С++
Дефиниции на променливи
Една проста дефиниция се състои от спецификатор на тип
следван от име. Дифиницията се ограничава от точка и запетая.
Ето някои примери на прости дифиниции:
double salary;
double wage;
int month;
int day;
int year;
unsigned long distance;
Когато се дефинира повече от един идентификатор за
даден тип, списъкът от идентификатори, записан след
спецификатора на тип, се разделя чрез запетаи. Този списък
може да бъде разположен на няколко реда. Ограничава се от
точка и запетая. Например, предходните дефиниции могат да
бъдат записани по следния начин:
double salary, wage;
int month,
day, year;
unsigned long distance;
Всяка проста дефиниция определя типа и идентификатора
на променливата. Тя не дава начална стойност. За променлива,
която няма начална стойност, се казва че е неинициализирана.
Всяка неинициализирана променлива фактически има стойност; но
по-скоро може да се каже, че стойността й е недефинирана. Това
е така, понеже паметта, отделена за съхраняване на
променливата не е изтрита. Просто е останало това, което е
било записано в паметта при предходното използуване на тази
памет. Когато се чете една неинициализирана променлива
случайната битова последователност се интерпретира като нейна
стойност. Тази стойност ще се променя за различните изпълнения
на програмата. Следната премерна програма илюстрира случайния
характер на неинициализираните данни.
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
25.
#include
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 (стр. 36) на тази
глава. depth представлява локална статична променлива.
Значението на думата static се разяснява в раздел 3.10 (стр.
132) при обсъждането на обхвата. func() е описана като
рекурсивна функция. Раздел 3.1 (стр. 105) разглежда
рекурсивните функции.
В дефиницията на една прменлива може да й се даде
първоначална стойност. За променлива, на която е дадена
първоначална стойност в декларацията се казва, че е
инициализирана. Ето няколко примера за инициализиране на
променливи:
ЇЇЇЇЇЇЇЇЇЇЇЇ
ЇЇЇЇЇЇЇЇЇЇЇЇ
26.
#include
double price = 109.99, discount = 0.16,
salePrice = price * discount;
int val = getValue();
unsigned absVal = abs ( val );
Пледварително дефинираната функция abs(), намираща се в
библиотеката math, връща абсолютната стойност на аргумента си.
getValue() е функция, дефинирана от потребителя, която връща
случайно цяло число. Променливите могат да бъдат
инициализирани със произволни сложни изрази.
1.3. Указателни типове
Променливата указател съдържа стойност, която
представлява адрес на обект в паметта. Чрез указатела можем да
се обърнем към обекта непряко. Едно типично използуване на
указатели е за създаване на свързани списъци и управление на
обекти, адресирани по време на изпълнение на програмата.
Всеки указател се свързва с определен тип. Типът на
данните определя типа на обекта, който ще бъде адресиран чрез
указателя. Например, указател от тип int ще соче обект от тип
int. Съответно, за да сочи обект от тип double указателят
трябва да се дефинира от тип double.
Сподели с приятели: |