Тема 3.2 Перегрузка методов. Операции класса. Иерархия классов.


Перегрузка операций
Перегрузка операций позволяет определить действия для объектов в выражениях.
<имя_класса> operator op(<имя_класса > a, < имя_класса > b) - общий вид перегруженной операции op.
В C++ существует 40 операций, которые можно перегрузить.
Нельзя перегрузить следующие операции: «.», «.*»,«?:», «::», «sizeof», «#», «##».
При создании описания класса существует две возможности определения операций:
1)    определение операции как функции класса;
2)     определение операции как дружественной функции.
Если бинарная перегруженная операция реализована как функция класса, то ее первым операндом является объект, которому принадлежит сообщение, следовательно, такой функции передается только второй член операции, следовательно, у нее 1 параметр.
Если унарная перегруженная операция реализована как функция класса, то у нее нет параметров.
Если перегруженная операция реализована как дружественная функция, то ей передается 1 или 2 параметра.
С++ «не понимает» семантики перегруженного оператора. С++ не может выводить сложные операторы из простых. Нельзя изменять синтаксис перегруженных операций. Нельзя изобретать новые операторы, а можно использовать только 40.
Пример (работа с комплексными числами):
Вариант 1.
//f1.h
class Complex { float real, imag; public:Complex(float aReal, float almag): real(aReal), imag(almag) {}; Complex() {real=imag=0.0;}
Complex(float aReal): real(aReal), imag(0.0) {}; float GetReal() {return real;} float GetImag() {return imag;}
friend Complex operator+(Complex &c1, Complex &c2); friend Complex operator-(Complex &c1, Complex &c2);
};
Complex operator+(Complex &c1, Complex &c2)
{        return Complex(c1.real+c2.real, c1.imag+c2.imag);}
Complex operator-(Complex &c1, Complex &c2)
{        return Complex(c1.real-c2.real, c1.imag-c2.imag);}
//fl.cpp
#include <iostream.h>
#include "f1.h" void main()
{
Complex a(1.0,2.0), b(3.0,4.0); a=a+b;
cout<<a.GetReal()<<endl<<a.GetImag()<<endl;
}
Вариант 2.
//f1.h
class Complex { float real, imag; public:
Complex(float aReal, float aImag): real(aReal), imag(aImag) {}; Complex() {real=imag=0.0;}
Complex(float aReal): real(aReal), imag(0.0) {}; float GetReal() {return real;} float GetImag() {return imag;} void operator+(Complex &c1); void operator-(Complex &c1);
};
void Complex::operator +(Complex &c1)
{        real+=c1.real;
imag+=c1.imag;
}
void Complex::operator -(Complex &c1)
{        real-=c1.real;
imag-=c1.imag;
}
//fl.cpp
#include <iostream.h>
#include "fl.h" void main()
{
Complex a(1.0,2.0), b(3.0,4.0); a+b;
cout<<a.GetReal()<<endl<<a.GetImag()<<endl;
}
Нельзя смешивать два типа объявлений.
Контейнерные классы
Контейнерные классы - это классы, которые содержат в своем описании один или несколько объектов или указатели на объекты. В этом случае имеет место отношение «содержит».
//f1.h
#include <iostream.h> class Tail {int length; public:
Tail(int n) {length=n;} int GetTail() {return length;}
};
class Dog {Tail tail; public:
Dog(int n):tail(n) {};
void DisplayPar() {cout<<tail.GetTail()<<endl; return;}
};
//f1.cpp
#include "fl.h" void main()
{ Dog d(20); d.DisplayPar();
}
Сначала инициализируются все поля-объекты, которые содержатся в описании класса, причем в том порядке, в котором они объявлены. Деструкторы вызываются в порядке, обратном инициализации.
Иерархия классов Простое наследование
Простое наследование - это такое наследование, при котором порождаемые классы наследуют методы и свойства одного базового класса.

Производный класс А является базовым для класса Б.
Производные классы могут наследовать любые данные и функции базового класса, кроме конструктора и деструктора.
Не существует ограничений на количество производных классов. #include <iostream.h> class TBase {private:
int count; public:
TBase() {count=0;}
int GetCount() {return count;}
void SetCount(int n) {count =n;}
};
class TDerived:public TBase {public:
TDerived():TBase() {};
void incr(int n) {SetCount(GetCount()+n);} //в этой функции нельзя написать строку count+=n; так как такая строка вызовет ошибку компиляции, потому что у производного класса нет прав доступа к переменным и функциям базового класса с уровнем доступа private
};
void main()
{TDerived d; d.SetCount(15); d.incr(10);
cout<<d.GetCount(); //на экране будет значение 25;
}
Спецификаторы доступа базовых классов
Когда класс Second порождается от First со спецификатором прав доступа private, например: class First {...};
class Second:First {...}; или
class First {...};
class Second: private First {...};
то все наследуемые (т. е. защищенные и общедоступные) имена базового класса становятся приватными в производном классе.
Когда класс Second порождается от First со спецификатором прав доступа protected, например: class First {...};
class Second: protected First {...};
то все наследуемые (т. е. защищенные и общедоступные) имена базового класса становятся защищенными в производном классе.
Когда класс Second порождается от First со спецификатором прав доступа public, например: class First {...}; class Second: public First {...};
то все общедоступные имена базового класса будут общедоступными, а все защищенные будут защищенными в производном классе.
Порядок вызова конструкторов
При создании экземпляра класса вызывается его конструктор. Если класс является производным, то должен быть вызван конструктор базового класса. Порядок вызова в C++ фиксирован. Если базовый класс, в свою очередь, является производным, то процесс рекурсивно повторяется до тех пор, пока не будет достигнут корневой класс.
Например, class First {...}; class Second: public First {...}; class Third: public Second {...};
При создании экземпляра класса Third конструкторы вызываются в следующем порядке:
First::First()
Second::Second()
Third::Third()
Порядок вызова конструкторов
Деструкторы для производных классов вызываются в порядке обратном вызову конструкторов. Таким образом, порядок вызовов деструкторов, сгенерированных для разрушения экземпляра класса Third, будет следующим:
Third::~Third()
Second: :~Second()
First::~First()

Разрешение области видимости
Порождая один класс от другого, можно прийти к такой ситуации, когда в нескольких классах используются переменные и функции с одинаковыми именами.
Например: class A {public:
int fun() {return 1;}
};
class B : public A {public:
int fun() {return 2;}
};
void main()
{ A a;
B b;
int i = a.fun(); //i=1 int j=b.fun(); //j=2
}
В этом случае компилятор действует по следующему алгоритму: если имя в базовом классе переобъявляется в производном, то имя в производном классе подавляет соответствующее имя в базовом.
В С++ можно заставить компилятор «видеть» за пределами текущей области видимости. Для этого используются оператор разрешения видимости. Общая форма этого оператора такова:
<имя класса>::<идентификатор из класса>,
где <имя класса> - это имя базового или производного класса, а Идентификатор из класса> - это имя любой переменной или функции, объявленной в классе.
Модифицируем наш класс B следующим образом:
class B : public A
{public:
int fun() {return 2;}
int fun1() {return A::fun();}
};
Теперь вызов функции B.fin1() приведет к вызову функции fun() класса A.


This site was made on Tilda — a website builder that helps to create a website without any code
Create a website