Синтаксис описания функции-операции
тип operator операция (список параметров) {тело}
Например, опишем операцию удаления из строки последнего символа
class CStr
{ … CStr & operator —() {s[len-1]=’\0’; —len; return *this;} };
Бинарная функция-операция, определяемая внутри класса, должна быть представлена с помощью нестатического метода с параметрами, при этом вызвавший ее объект считается первым операндом. Например, операция сравнения строк.
class CStr
{ bool operator = =(const CStr & st)
{if (strcmp (s, st.get_str())==0) return true; return false; }
}
Приведем пример функции — операции, являющейся дружественной двум классам.
friend ostream& operаtor << (ostream& out, CStr& st )
{return out<<st.s;}
Таким образом, нами описан следущий класс CStr
class CStr
{
protected:
char* s; int len;
public:
CStr(); CStr(char*);
CStr(char ); CStr(const CStr&);
CStr& operator=(const CStr&);
bool operator ==(CStr &);
void empty();
operator int(){return len;}
~CStr(){delete[]s; cout<<» \nDestructor! «;}
char* get_str() const {return s;}int get_len()const {return len;}
friend ostream& operator<<(ostream&,CStr&);
}
// Конструктор создания пустой строки
Str::CStr():len(0)
{s=new char;*s=’\0′; cout<<«\nContructor1»;}
// Конструктор создания строки, равной заданной С- строке
CStr::CStr(char* a)
{s=new char[len=strlen(a)];
strcpy(s,a);
cout<<«\nContructor2»;
}
// Конструктор создания строки из одного символа
CStr::CStr(char a)
{s=new char[len=2];s[0]=a; s[1]=’\0′;cout<<«\nContructor3»;}
// Конструктор копирования
CStr::CStr(const CStr& a)
{s=new char[len=a];strcpy(s,a.s);cout<<«\nContructor4 «;}
// Операцияприсваивания
CStr& CStr::operator = (const CStr & a)
{
if (&a==this) return *this;
if (len) delete []s;
s=new char [len=a];
strcpy(s,a.s);
cout<<» \nDONE == «;
return *this;
}
// Операция сравнения строк
bool CStr::operator ==(CStr & st)
{
if (strcmp (s, st.s)==0) return true;
return false;
}
// Метод, делающий строку пустой
void CStr::empty()
{ if (len)
{ len = 0; delete []s; s = new char; *s= ‘\0’;}
}
// Операция записи в поток вывода на экран
ostream& operator<<(ostream& a, CStr& x)
{return a<<x.s;}
Вторым основным принципом ООП является наследование. Наследование – это возможность создания иерархии классов, в которой потомки (производные классы, наследники) получают элементов своих предков (родителей, базовых классов), могут их изменять и добавлять новые.
Синтаксис описания класса-наследника
сlass имя : [ключ доступа] имя базового класса { тело класса};
Ключ доступа может иметь одно из трех значений private, protected, public
Ключ доступа private (защищенное наследование, действует по умолчанию) – понижает статусы доступа publicи protectedэлементов базового класса до private. Ключ доступа public (открытое наследование) — не изменяет статуса доступа элементов базового класса. Ключ доступа рrotected(защищенное наследование) понижает статус доступа publicэлементов базового класса до protected.
Например, создадим производный класс CBStrот базового класса CStr, предназначенный для хранения бинарных строк.
class CBStr: public CStr
{public:
CBStr();
CBStr(char* a);
CBStr& operator = (const CBStr & );
CBStr operator +( const CBStr& );
void empty();
operator int();
};
Рассмотрим поля и методы производного класса.
Все поля базового класса наследуются.
Если поля родителя имеют тип private, то для работы с ними в классе – наследнике необходимо использовать методы базового класса или объявить их явным образом в наследнике в секции public следующим образом имя базового_класса::имя_поля. Например, если бы поля s, lenбыли бы описаны в классе CStrкак private, то в классе CBStrих следует объявить следующим образом:
class CBStr: public CStr
{
….
public:
СStr::s;
СStr::len;
….
};
Кроме того, если функциям производного класса требуется работать с полями базового, то в базовом классе такие поля можно описать как protected, как это и сделано в классе CStr.
Для различных методов класса существует различные методы наследования. Наследуются все методы, кроме конструкторов, деструкторов и операции присваивания.
То есть класс CBStrнаследует методы empty(), operator ==, operator int(), get_str(), get_len(), и и дружественную функцию-оператор operator<<;
Однако, как мы видим в классе CBStrметоды empty(), operator int() переопределены.
//Метод, делающий строку пустой
void CBStr:: empty()
{ if (len)
{ delete []s;
len = 1;
s = new char[2];
s[0]=’0′;
s[1]= ‘\0’;
}
}
//Функция-операция преобразования типа, возвращающая десятичное значение двоичной строки
CBStr:: operator int()
{
int k=s[len-1]-48;
int st=2;
for (int i=len-2; i>=0; i—)
{ k+=((s[i]-48)*st); st*=2;}
returnk;
}
Кроме того, в классе CBStr определен новый метод
// Операция сложения двух двоичных чисел
CBStrCBStr::operator+( constCBStr& a)
{
int l;
if (len>a.len) l=len; else l=a.len;
char * str =new char[l+2];
itoa (int(*this)+int (a), str, 2);
CBStr S(str);
delete str;
return S;
}
Опредлелим конструкторы класса
Предположим, что создание пустой бинарной строки равносильно созданию обычной строки, состоящей из одного символа ‘0’. Тогда конструктор производного класса должен вызывать конструктор базового класса с параметром ‘0’:
CBStr():CStr(‘0’){}
Конструктор бинарной строки, равной заданной С-строке, должен вызывать конструктор и если созданная строка, содержит символы отличнее от 0 и 1, делать строку пустой
CBStr::CBStr(char* a):CStr (a){if (!bin(a)) empty();}
{ if (!bin(a)) empty(); }
где bin() – функция проверки С-строки на бинарность, empty () – метод, делающий строку пустой.
bool bin(char *a)
{
int i=0;
while (a[i])
{ if (a[i]!=’0′ && a[i]!=’1′) return false;
i++;
}
return true;
}
Конструктор копирования создастся автоматически и вызовет конструктор копирования базового класса.
Для rласса CBStrне требуется явным образом создавать деструктор, так как удалить бинарную строку это тоже самое, что и удалить строку (если в производном классе деструктор не определен программистом, то он создастся автоматематически компилятором, причем из созданного деструктора будет вызван деструктор базового класса.)
Так Как операция присваивания не наследуеются, ее необходимо явным образом переопределить
CBStr& CBStr::operator = (const CBStr & a)
{ CStr :: operator = (a);}
Эффективнее всего работать с объектами одной иерархии через указатели на базовый класс. При открытом наследовании можно присваивать указателю на объект базового класса как адрес объекта базового класса, так и адрес объекта любого производного класса.
Рассмотрим пример работы с объектами одной иерархии через указатели.
CStr a(«aaa»);
CBStr b(«101»);
CStr * p1=&a;
CBStr * p2=&b;
cout<<«\na= «<<a <<» «<<int(a);
cout<<«\nb= «<<b <<» «<<int(b);
p1->empty();
p2->empty();
cout<<«\na= «<<a <<» «<<int(a);
cout<<«\nb= «<<b <<» «<<int(b);
CBStr c(«1011»);
CStr * p3=&c;
cout<<«\nc= «<<c <<» «<<int(c);
p3->empty();
cout<<«\nc= «<<c <<» «<<int(c);
Эта программа выведет на экран
a=aaa 3
b=101 5
a= 0
b= 0
c=1011 11
c= -48
Как мы видим, для объекта, на который ссылается указатель p3, был вызван метод empty() базового класса CStr, что семантически неверно.
Таким образом, переопределенный метод empty() производного класса оказался недоступным. Это происходит из-за того, что компилятор не может предсказать на объект какого класса будет фактически ссылается указатель во время выполнения программы и выбирает всегда метод базового класса. Чтобы избежать этой ситуации необходимо объявить в баовом классе метод empty() как виртуальный, то есть со спецификатором virtual.