class
Stack
// a stack holds up to 20 ints
{
private:
int st[20];
// integers are stored in array
int top;
// index of last item pushed
public:
. . .
};
Какви са проблемите?
1. Числото 20 нищо не говори на един друг програмист. Ако програмата е значително голяма, след време даже и на автора и ще му струва усилие да разбере какво е това число.
2. Число 19 (20 - 1) е необходимо във функцията на класа push(), за да се защити програмата от работа извън паметта на масива:
void
push(int var)
{
if(top >= 19)
// if at or beyond the end of array,
cout << "Illegal push"; // error message
else
st(++top) = var;
}
Ясно е, че ако авторът на програмата или друг програмист иска да смени размера на масива, то той трябва да променя тези числа поне на две места (или при голяма програма на няколко места), като това е не само неудобство, но съществува и риск да бъде пропуснати една или повече замени.
Проблемът може да бъде решен като масивът се оразмери с външна константа (array sized with external const), чието име ни подсеща за нейното предназначение - например size или array_size или даже още по-добре stack_array_size. Освен това е хубаво да се постави и коментар.
// size of st variable of Stack class
const int stack_array_size = 20;
Редът с дефиницията на константата трябва да се постави преди дефиницията на класа и както беше споменато в лекция 27 тази константа става глобална за програмата и е видима от мястотото си на дефиниция до края на файла. Точно това прави този начин на дефиниция чужд на концепцията за ООП, според която цялата информация за класа трябва да е в него и недостъпна извън него (освен евентуално с функции на класа).
2. Инициализиране на променливи на класа (Initializing Member Variables). За съжаление следният запис по-долу е невалиден
Stack
{
private:
const int size = 20; // illegal initialization!!!
int st[size];
int top;
public:
. . .
};
и компилаторът издава съобщение
Cannot initialize a class member here.
тъй като променливите на класа трябва се инициализират от списък за инициализация (initialization list), както това е обясненено в лекция 32. Причина за това е, че грешното твърдение по-горе е опит една променлива на класа да бъде инициализирана с една и съща стойност, която тя ще има във всички обекти на класа. Това последното е в разрез с концепцията за ООП, защото един обект се инициализира точно когато се създава.
Разбира се, може да инициализираме променливата size в конструктора по следния начин
Stack() : top(-1), size(20) // constructor
{ }
Но да не забравяме, че тази инициализация става по време на създаване на обектите, т.е. по време на изпълнение на програмата, а не по време на нейното компилиране! Т.е. не ще можем след това да запишем следната дефиниция в класа
int st[size];
Даже и да запишем в конструктора, че size е константа по следния начин
class
Stack
// a stack holds up to 20 ints
{
private:
const int size; // array size; mention that it is defined as
constant!!!
int st[size]; // the constructor complains "Constant
expression required."!!!
int top; // index
of last item pushed
public:
Stack() : top(-1), size(20) // constructor with an initialization of size!
{ }
. . .
};
то компилаторът издава съобщение
Constant expression required.
Тук трябва да се отбележи, че инициализацията не е присвояване (Initialization Is Not Assignment) и затова компилаторът не се оплаква, че инициализираме константата size, както това би станало ако в конструктора сложим твърдението size = 20;, както е по-долу:
class
Stack
// a stack holds up to 20 ints
{
private:
const int size; // array size
int st[size];
// integers are stored in array
int top;
// index of last item pushed
public:
Stack() : top(-1) // constructor
{
size = 20;
}
. . .
}
Всъщност при горната неверна конструкция компилаторът издава две съобщения:
Constant member 'Stack::size' is not initialized.
Cannot modify a const object.
Ще повторим отново, че не можем да инициализираме размера на масива (дейност която се извършва по време на изпълнение на програмата), за да дефинираме масиви, нещо което става по време на компилиране на програмата (т.е. по-рано).
3. Инициализация с изброен тип (The enum Hack). Не случайно Лафоор е употребил думата Hack [1], която означава "справям се с", (което дава името на хакерството), защото това не е най-елегантния начин за справяне с проблема, а просто едно негово заобикаляне. Ето и частта от класа, която използва изброения тип (enum), за да се справи с проблема. (За изброените типове данни вижте лекция 22).
class
Stack
{
private:
enum { size = 20 }; // established at compile time
int st[size]; // OK
int top;
. . .
};
Този начин работи, защото компилаторът просто заменя името size с числото 20. Както споменахме, този начин не е елегантен и създава затруднения за бързото му разбиране от друг програмист, ако програмата е голяма. Ето защо създателите на C++ са допълнили ANSI/ISO версията с друга възможност.
4. Инициализация със статична константа (Static Constant Variables). Това е едно разширение на езика, което за съжаление според Лафоор [1] още не се поддържа от всички компилатори. Ето и кодът, който пояснява тази инициализация:
class
Stack
{
private:
static const int size = 20; // static constant
int st[size];
int top;
. . .
}
Константата size не може да се променя (както и искаме!), но понеже е статична, тя се инициализира по време на компилацията и затова може да се използва за оразмеряване на масива st. Статични променливи на класа бяха въведени в лекция 28.
Ето и променената програма staticon.cpp, която върши същото като предишната програма stackcon.cpp от лекция 31.
//
staticon.cpp
//
class models a stack
//
uses static constant for member array size
#include
<iostream.h>
#include
<conio.h>
// for getch()
class
Stack
// a stack holds up to 20 ints
{
private:
static const int size = 20; // array size
int st[size];
// integers are stored in array
int top;
// index of last item pushed
public:
Stack() : top(-1) // constructor
{ }
void push(int var) // place an item on the
stack
{
if(top >= size - 1)
// if at or beyond the end of array,
cout << "Illegal push"; // error message
else
st[++top] = var;
}
// remove an item from the stack
int pop()
{
if (top < 0)
// if beyond the beginning of array,
{
cout << "Cannot pop: stack is emty! "; //
error message
return 0;
}
else
return st[top--];
}
};
// class Stack
void
main()
{
Stack s1;
// create a stack object
s1.push(11);
// push 3 items onto stack
s1.push(12);
s1.push(13);
cout << s1.pop() << endl; // pop 3 items and display
them
cout << s1.pop() << endl;
cout << s1.pop() << endl;
getch();
}
// main()
Обърнете внимание на защитата if(top >= size - 1), която проверява дали сме на края на масива top = size - 1 (той започва от нула!) или сме след последната стойност top > size - 1. Както и на защитата във функцията pop(): редът return 0; в нея е необходим, защото cout се опитва да върне някаква стойност и ако го няма този ред, връща произволна стойност, което е объркващо.
5. Инициализация на масив, който е променлива на класа (Initializing a Member Array). Масивите не могат да се инициализират нито чрез инициализиращ списък (initialization list) в конструктора, нито директно като променливи на класа.
В програмата weekdays.cpp от лекция 20 масивът от символи day_name беше дефиниран като глобална константа и инициализиран преди класа weekday. Но той е част от класа и е правилно да бъде дефиниран вътре в него. За съжаление, следните две конструкции са неверни и не могат да бъдат компилирани:
//
FIRST INCORRECT construction
class
weekday
{
private:
// no good
char day_name[DPW][MAX] = { "Sunday", "Monday",
"Tuesday",
"Wednesday", "Thursday", "Friday",
"Saturday"};
. . .
}; // end of class weekday
дава съобщението
Cannot initialize a class member here.
//
SECOND INCORRECT construction
class
weekday
{
private:
char day_name[DPW][MAX]
public:
// no good!!!
weekday() : day_name[0]("Sunday"), day_name[1]("Monday"),
day_name[2]("Tuesday"), day_name[3]("Wednesday"),
day_name[4]("Thursday"), day_name[5]("Friday"),
day_name[6]("Saturday")
{ }
. . .
}; // end of class weekday
дава съобщенията (вижда се, че са повторени няколко пъти)
(
expected.
Member
'weekday::day_name' is initialized more than once.
(
expected.
Member
'weekday::day_name' is initialized more than once.
(
expected.
Member
'weekday::day_name' is initialized more than once.
(
expected.
Member
'weekday::day_name' is initialized more than once.
(
expected.
Member
'weekday::day_name' is initialized more than once.
(
expected.
Member
'weekday::day_name' is initialized more than once.
(
expected.
При втория фрагмент от код повторенията се дължат на това, че компилаторът не очаква средните скоби след day_name, а иска веднага кръгла скоба (с инициализиращата стойност след нея). Също така, срещането на day_name[0], day_name[1] и т.н. се разбира от компилаторът като срещане на няколко пъти на името day_name и съответно неговото повторно инициализиране в инициализиращия списък на конструктора.
Проблемът може да бъде решен, ако променливата day_name бъде дакларирана като статична и инициализирана след декларацията на класа по следния начин:
class
weekday
// class of day names
{
private:
int day_number;
// Sunday = 0, etc.
static const char day_name[DPW][MAX];
. . .
}; // end of class weekday
const
char weekday::day_name[DPW][MAX] = {"Sunday", "Monday",
"Tuesday", "Wednesday",
"Thursday", "Friday",
"Saturday" };
Ето и цялата програма, която прави същото както старата:
//
weekdays1.cpp
//
creates a class of days of the week objects
#include
<iostream.h>
#include
<string.h> // for stricmp()
#include
<conio.h> // for getche()
class
weekday
// class of day names
{
private:
int day_number;
// Sunday = 0, etc.
static const int MAX = 10; // maximum length of
day name, +1
static const int DPW = 7; // days per week
static const char day_name[DPW][MAX];
public:
void inday()
// user inputs day name
{
char tempday[MAX];
// holds user input
int gotit = 0;
// match found? (0=false)
int j;
// loop variable
while( !gotit )
// cycle until user enters
{
// a correct name
cout << "Enter day of week (e.g., Friday): ";
cin >> tempday;
for (j = 0; j < DPW; j++) // compare user
input
{
// with list of names
if (stricmp(tempday, day_name[j]) == 0)
{
// if there's a match,
gotit = 1; // set
flag,
break;
// break out of for loop
}
} // end for
day_number = j;
// record what day it was
} // end while
} // void inday()
void outday()
// display the day name
{
cout << day_name[day_number];
}
void outnumber()
// display the day number
{
cout << (day_number + 1);
}
void add(int days)
// add days to
{
// this weekday
day_number += days; //
add days
day_number %= DPW;
// ensure not > 7
}
};
// end weekdays class
const
char weekday::day_name[DPW][MAX] = {"Sunday", "Monday",
"Tuesday", "Wednesday",
"Thursday", "Friday",
"Saturday" };
////////////////////////////////////////////////////////////////
void
main()
{
weekday wd;
// make a weekday object
cout << "What day is it?" << endl;
wd.inday();
// user gives it a value
cout << "You entered ";
wd.outday();
// display its day name
cout << "\nThat's day number ";
wd.outnumber();
// display its day number
wd.add(10);
// add 10 days to it
cout << "\nTen days later is ";
wd.outday();
// display its day name
// stop the flow on the monitor
getch();
} //end main
Обърнете внимание, че константите MAX и DPW също са обявени като статични:
static const int MAX = 10; // maximum length of
day name, +1
static const int DPW = 7; // days per week
За да осмислите напълно използването на
статичните константи в горната програма припомнете си лекция
28 за статични променливи на класа.
.
Keywords: С++,
OOP programming , C++ , Classes , Inheritance , Reusability , Creating
New Data Types , Polymorphism and Overloading
Ключови думи: клас
, обект, обектно ориентирано програмиране , полиморфизъм switch if else
?
Arrays as Instance Data
Array Sized with Constant
Static Constant Variables
The enum Hack
Arrays Sized at Compile Time
Initialization Is Not Assignment
Instance Data Initialization