Использование модификатора const

Модификатор const может использоваться для объявления функций-членов класса (селекторов), которые не будут изменять состояние класса и сообщают об этом компилятору. Это дает возможность использовать такие функции для экземпляров класса, объявленных как константы, например:

class X{

private:

int n;

public:

X(int a = 0){n = a;}

int getval(){return n;}

X& setval(int a){n = a; return *this;}

};

Если в программе есть:

X ob1(5);

const X ob2(5);

тогда:

ob1.setval(4); все в порядке;

ob2.setval(4); ошибка компиляции, т.к. значение константы не может быть изменено.

int k = ob1.getval(); все в порядке;

int l = ob2.getval(); ошибка компиляции (непредусмотренное использование константного объекта ob2), т.к. компилятор ничего не знает о том, что метод getval() не изменяет состояние адресата, а адресатом является константа.

Если изменить класс X следующим образом:

class X{

private:

int n;

public:

X(int a = 0){n = a;}

int getval() const {return n;}

X& setval(int a){n = a; return *this;}

};

Тогда все будет в порядке, и вызов int l = ob2.getval(); вернет корректное значение.

Если реализация метода приводится вне определения класса, тогда модификатор const должен появиться в двух местах: в прототипе функции-члена класса и в определении функции:

class X{

private:

int n;

public:

X(int a = 0){n = a;}

int getval() const;

X& setval(int a){n = a; return *this;}

};

int X::getval() const

{

return n;

}

Статические члены класса

 

Типы отношений между классами

 

Контейнерные классы: определение

 

Контейнерные классы: реализация методов

 

Контейнерные классы: реализация конструкторов

 

Производные классы: простое наследование

Основные понятия и определения: базовый класс и производные классы; производный класс есть базовый, обладающий дополнительными возможностями. Производный класс наследует все свойства базового класса и добавляет что-то свое. Отсюда, базовый класс - более абстрактное понятие (шире, чем производный); производный класс - более конкретное понятие (больше - по объему информации, чем базовый класс).

Отсюда:

 

базовый класс

собственные   собственные
Состояние - члены-данные   набор методов

 

 

       
   
 
 


производный класс

от базового класса   от базового класса
собственные   собственные
Состояние - члены-данные   набор методов

 
 

 

 


В производном классе имена методов (и членов - данных) могут совпадать с именами из базового класса.

Простое наследование: правила определения производного класса

Правила объявления производных классов. Две проблемы: могут ли (и как) методы производного класса обращаться к членам базового класса (доступность изнутри производного класса) и как члены базового класса, унаследованные производным классом, могут быть доступны извне производного класса.

Первая проблема решается уровнем видимости членов в базовом классе:

class B{

private:

// закрытая часть класса; не доступна никому, кроме методов класса (в том числе

// не доступна и из производного класса)

protected:

// защищенная часть класса; доступна из методов базового и производного класса

// (но не доступна извне класса)

public:

// открытая часть класса; доступна везде

};

Вторая проблема решается способом наследования:

class D: тип_наследования B{

. . .

};

тип_наследования: одно из private, protected, public.

Тип наследования не смягчает, но усиливает защиту данных: члены класса, имевшие менее защищенный уровень видимости в базовом классе, в производном классе приобретают уровень видимости, определяемый типом наследования.

  тип наследования
уровень видимости в базовом классе private protected public
protected private protected protected
public private protected public

Примеры.

Реализация конструкторов производного класса. Примеры.

Пример производного класса

Пример использования простого наследования: класс “точка” - базовый для класса “окружность”. Методы в классе “точка”: расстояние между двумя точками, вывод координат точки. Для класса окружность: свои дополнительные методы, отсутствовавшие в классе “точка” (пересечение окружностей); полностью наследуется метод - расстояние между точками (центрами двух окружностей) и требуется переопределить метод вывода окружности.

class Point{

protected:

int x, y;

public:

// конструкторы

Point():x(0), y(0){}

Point(int x0):x(x0),y(x0){}

Point(int x0, int y0):x(x0), y(y0){}

Point(const Point &p):x(p.x), y(p.y){}

void print();

float distance(Point p);

};

Реализация класса:

void Point::print()

{

cout << '(' << p.x << ", " << p.y << ')';

}

float Point::distance(Point p)

{

float dx = x - p.x;

float dy = y - p.y;

return sqrt(dx * dx + dy * dy);

}

Теперь - производный класс Окружность:

class Circle: public Point{

private:

int rad;

public:

// конструкторы

Circle():rad(0){} // Вызывается пустой конструктор базового класса

Circle(int r):rad(r){}

Circle(int x, int r):Point(x), rad(r){}

Circle(int x, int y, int r):Point(x, y), rad(r){}

Circle(Point p, int r):Point(p), rad(r){}

// методы

void print();

int intersect(Circle c);

};

Теперь - реализация методов

void Circle::print()

{

Point::print();

cout << " with rad " << rad;

}

int Circle::intersect(Circle c)

{

return distance(c) < rad + c.rad;

}

Метод distance из класса Point наследуется классом Circle.

Использование классов:

Circle c1(3), c2(2, 2);

cout << "distance between " << c1 << " and " << c2 << " is " << c1.distance(c2) << endl;

cout << c1 << " and " << c2;

if(c1.intersect(c2))

cout << " are intersected " << endl;

else

cout << " are not intersected " << endl;

Указатели на классы: базовый и производный

Представление экземпляров классов в памяти - производный класс = базовый плюс что-то свое:

class B{ . . . };

class D: public B { . . . };

B obj1; D obj2;

obj1 obj2

состояние часть базового
класса класса

собственная
часть

       
   


bPtr dPtr

Объявление указателей: B *b1Ptr, *b2Ptr; D *d1Ptr, *d2Ptr;

Указатели на тип определяют начальный адрес области памяти и ее размер, поэтому bPtr адресует только состояние, определяемое базовым классом, а dPtr - состояние, определяемое производным классом.

Определение указателей традиционное:

b1Ptr = &obj1; d1Ptr = &obj2; b2Ptr = b1Ptr; d2Ptr = d1Ptr;
b1Ptr = new B; d1Ptr = new D;

Можно указателю на базовый класс присвоить значение указателя на производный класс. В этом случае из производного класса будет использоваться только его часть, относящаяся к базовому классу:

b1Ptr = &obj2; b2Ptr = d1Ptr; b1Ptr = new D;

Обратное присваивание невозможно; записи вида d1Ptr = b1Ptr; вызовут сообщения об ошибках на этапе компиляции (присваивания указателей разного типа).

Вызов методов по указателю на класс

class B{ class D: public B{

. . . . . .

public: public:

. . . . . .

void f(); void f();

} }

D obj2, *dPtr = &obj2; B obj1, *b1Ptr = &obj1, *b2Ptr = &obj2;

Имеем следующее представление:

obj1 obj2

состояние часть базового
класса класса

собственная
часть

           
   
     
 


b1Ptr b2Ptr dPtr

 

Выполняем методы:

obj1.f() Вызывается метод f()базового класса для экземпляра базового класса obj1.

b1Ptr->f() Вызывается метод f()базового класса по указателю базового класса b1Ptr на экземпляр базового класса obj1.

obj2.f() Вызывается метод f()производного класса для экземпляра производного класса класса obj2.

dPtr->f() Вызывается метод f()производного класса по указателю производного класса dPtr на экземпляр производного класса obj2.

b2Ptr->f() Какой метод f()- базового или производного класса - вызывается здесь по указателю b2Ptr на экземпляр obj2 производного класса?