Концепцията за запазване на променливите произтича от това, че програмата се състои от функции и класове и това спомага за улесняване писането на програмен код, а от друга страна тази концепция прави по-ефективно използването на паметта в програмите и ускорява тяхното изпълнение.
1. Още нещо за декларацията и дефиницията. В някои от предишните материали беше пояснена разликата между декларация (declaration) и дефиниция (definition), но нека ги разгледаме по-подробно. Декларацията определя името и типа на данните, докато дефиницията прави не само това, но и казва на компилатора да запази памет за променливата - вижте дефинициите в линковете по-горе. Тази разлика досега беше правена само за функциите и последният пример бе от предишния материал в програмата overfun_def.cpp. В класа fancy_text функцията box_display беше първо декларирана
void box_display(char ch = '-', int n = MAX_LENGTH);
и после дефинирана извън класа
void
fancy_text::box_display(char ch, int n)
{
int j;
for (j = 0; j < n; j++)
cout << ch;
cout << endl << text << endl;
for (j = 0; j < n; j++)
cout << ch;
}
Според много автори, в C++ има два типа декларации на променливи - едните не запазват памет, а другите запазват памет. В настоящата лекция ще последва пример за първите, а всички разгледани декларации на променливи (от вида int ivar;) са от втория тип - т.е. запазват памет. Докато дефиницията винаги предизвиква запазване на памет за променливата.
Както споменахме, на практика концепцията за запазване на променливите определя две главни характеристики на променливите - видимостта (обхвата) на променливите (на английски visibility или scope) и времето на живот на променливите (lifetime). Обхватът на една променлива определя в кои части на програмата имаме достъп до променливата - тези части са клас, функция, файл или цялата програма. Времето на живот на променливата може да съвпада с това на обект, функция или цялата програма. Както ще видим в следващата точка всяко едно запазване на променливите определя дадена комбинация от обхват и време на живот.
2. Типове запазване на променливите (Storage Class). В предишните материали досега сме се запознали с един от Storage Class - автоматичните променливи (т. 3 от лекция 14). Тук ще разгледаме и другите комбинации на обхват и време на живот.
2.1. Автоматични променливи (Automatic Variables). Те са декларирани вътре във функциите и се създават автоматично когато функцията се извиква и се разрушават, когато тя връща управлението обратно след точката на извикване. От т. 3 от лекция 14 разбрахме, че тези променливи се създават в стека и после се премахват от него. Автоматичните променливи са видими само във функцията, в която са декларирани. Например една променлива с име var1, създадена в една функция func1() не може да бъде достъпна (т.е. да се извърши промяна или взимане на нейната стойност) в друга функция func2(). Това дава допълнително възможност за използването на автоматични променливи с едни и същи имена в различните функции, без да се безпокоим какво става с техните стойности - просто те са различни променливи.
Програмистът може да използва ключовата дума auto, но на практика това не е необходимо, защото всяка променлива, декларирана или дефинирана във функция се подразбира, че е автоматична.
Автоматичните променливи не се инициализират предварително и затова програмистът трябва да се погрижи те да получат първоначална стойност и да не разчита на това че те ще са 0 (съответно 0.0).
И последно за тези променливи - те могат да бъдат автоматични и при дефинирането им в блок, което е всяка една част от кода, заградена между двете големи скоби { и }. Също така променливи, дефинирани не само в тялото на функции, но и в тялото на цикли или if-else оператори са автоматични. При някои компилатори цикличните променливи дефинирани в условието на цикъла не могат да бъдат достъпни извън цикъла - например променливата i в цикъла по-долу.
for
(int i = 0; i < 10; ++i)
cout << i << endl;
2.2. Регистърни променливи (Register Variables). Това е специален вид автоматична променлива, дефинирана с ключовата дума register. Компилаторът ще се постарае да сложи променливата не в оперативната памет, а в регистъра на процесора. (Регистрите на процесора са също памети, в които се зареждат променливите и операциите, с които работи процесора и достъпът до регистрите е много по-бърз.) Пример за променлива, подходяща за регистъра, е променливата на най-вътрешия цикъл при няколко цикъла, вкарани един в друг. На практика това е само препоръка към компилатора, с която той може да не се съобрази, а от друга страна съвременните компилатори са толкова гъвкаво програмирани, че сами могат да определят коя променлива е подходяща за регистърна променлива.
2.3. Външни променливи (External Variables). Освен че променливите могат да бъдат дефинирани във функциите (отделни или тези на класовете), то те могат да бъдат дефинирани директно в програмния код, извън всякакви функции или класове, както това е направено с MAX_LENGTH в програмата overfun_def.cpp от предишния материал .
const int MAX_LENGTH = 40;
Подобни деклариции имаше и в програмите cardaray.cpp и cardenum.cpp от лекция 22
const
int jack = 11; //2 through
10 are unnamed integers
const
int queen = 12;
const
int king = 13;
const
int ace = 14;
и в програмата weekdays.cpp от лекция 20
const
int MAX = 10; // maximum length of day name, +1
const
int DPW = 7; // days per week
Външните променливи са видими в целия файл от точката на деклариране до края на файла. Тяхното време на живот е продължителността на програмата и те се инициализират по подразбиране с 0 (съответно за реалните 0.0).
2.4. Външни променливи и програма от няколко файла (External Variables and Multiple Files). Почти всички професионални програми (за разлика от нашите елементарни примери) се състоят от код, написан в няколко (десетки, даже стотици) файла. Една външна променлива е видима само във файла (от мястото на декларация до края му), в който е декларирана, но тя може да бъде направена видима (чрез декларация, която не заделя памет!) в друг файл чрез използване на ключовата дума extern. Ето един схематичен пример от книгата на Лафоор [1].
//
file #1
int
ivar; // definition
//
ivar is known in this file
//
end of file #1
//
file #2
extern
int ivar; // declaration
//
ivar is also known in this file
//
end of file #2
//
file #3
//
ivar is not known in this file
//
end of file #3
В първия файл е дефинирана
външна променлива ivar,
която е декларирана във втория файл с ключовата дума extern
и така тя е видима в този втори файл от това място до края му. Но в третия
файл променливата е недостъпна.
.
!За
всяка една променлива в дадена програма имаме само една дефиниция на тази
променлива, защото може да се посочи само едно място в паметта, където
се локализира променливата, но може да имаме няколко нейни декларации в
други файлове с помощта на ключовата дума
extern.
Освен това може да се ограничи видимостта на една външна променлива с помощта на ключовата дума static. Това не е статична променлива в смисъла на такава в класовете (ще се запознаем по-късно с тях), а просто е променлива с локална видимост (само във файла, където е дефинирана). Ето един схематичен пример от книгата на Лафоор [1].
//
file #1
static
int ivar; // definition
//
ivar can be known ONLY in this file
//
end of file #1
//
file #2
extern
int ivar; // useless
declaration
//
end of file #2
В първия файл е дефинирана статична външна променлива ivar, за която е направен опит да бъде декларирана във втория файл с ключовата дума extern и така тя да бъде видима в този втори файл от това място до края му. Но даже компилаторът да пропусне това логическо противоречие, то линкерът ще даде съобщение за грешка.
2.5. Статични локални променливи (Local Static Variables). Понякога програмистът иска функцията да запомня някаква стойност между нейните повиквания и това да не става с външни променливи. Тогава се използват т.н. статични локални променливи, които се дефинират вътре в тялото на функцията, но с ключовата дума static. Ето един пример от книгата на Лафоор [1] за функция, която изчислява средна стойност на няколко вкарани стойности.
float
average(float item)
{
static int n = 0;
// number of times we've been called
static float total = 0.0; // running total
total += item;
// add argument to running total
return total/++n;
// increment count, find average
}
Тези променливи съществуват по цялото време на изпълнение на програмата и се инициализират по подразбиране с 0 (съответно за реалните 0.0). Въпреки това, в кода по-горе изрично са инициализирани двете променливи - броя на вкараните стойности, n и тяхната сума, total, защото така става по-ясна логиката на програмата.
Статичните локални и външните променливи не се използват много в C++, защото затрудняват читаемостта на кода, пък и имат алтернатива в лицето на класовете и техните обекти, както ще видим по-късно.
3. Типове запазване на променливите за обектите. Обектите са точно като променливите и всичко, което написахме по-горе важи и за обектите, без регистърната автоматична променлива, която няма смисъл, тъй като един обект не може да бъде сложен в регистър на процесора.
Видимостта на променливите в класа зависи от ключовите думи private и public. Променливи на класа (instance data) дефинирани с ключовата дума private могат да бъдат достъпни само на функциите на класа (member functions) - даже не са достъпни чрез обектите на този клас. Променливи на класа (instance data) дефинирани с ключовата дума public са достъпни, както и на функциите на класа (member functions), така и на всички други функции, декларирани след мястото на класа: но тези променливи на класа са достъпни само чрез обектите на този клас (т.е чрез изполване на т.н. точков оператор (dot operator) - obj1.var1).
Времето на живот на съответните променливи е това на самия обект - след дефиниране на обекта (неговото създаване в паметта) за тези променливи се запазва памет и те освобождават тази памет при разрушаване на обекта. Но мислете за тях като за променливи на даден обект, а не на всички обекти от този клас - това последното не важи за статичните променливи на обектите, нещо което ще разгледаме в следващата лекция.
Ето една таблица, която систематизира казаното досега за запазване на променливите и видимостта на променливите и тяхното време на живот.
Таблица 1.
Типовете на (storage classes).
Винаги даваме и съответните термини на английски, затова ето таблица 1 в оригиналните термини.
Table 1.
Storage class, visibility, and lifetime.
Автор: Проф. Процесор Аритметиков
Keywords: С++,
OOP programming , C++ , Classes , Inheritance , Reusability , Creating
New Data Types , Polymorphism and Overloading
Ключови думи: клас
, обект, обектно ориентирано програмиране , полиморфизъм switch if else
?