Литмир - Электронная Библиотека
A
A

struct Shape {

  // ...

  virtual void draw_lines() const;

  virtual void move();

  // ...

};

  virtual void Shape::draw_lines() const { /* ... */ } // ошибка

  void Shape::move() { /* ... */ } // OK

14.3.3. Замещение

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Если вы хотите заместить виртуальную функцию, то должны использовать точно такое же имя и типы аргументов, как и в базовом классе. Рассмотрим пример.

struct Circle:Shape {

  void draw_lines(int) const; // возможно, ошибка (аргумент int?)

  void drawlines() const;     // возможно, ошибка (опечатка 
в имени?)

  void draw_lines();          // возможно, ошибка (нет const?)

  // ...

};

В данном случае компилятор увидит три функции, независимые от функции

Shape::draw_lines()
(поскольку они имеют другие имена или другие типы аргументов), и не будет их замещать. Хороший компилятор предупредит программиста о возможных ошибках. В данном случае нет никаких признаков того, что вы действительно собирались замещать виртуальную функцию.

Пример функции

draw_lines()
реален, и, следовательно, его трудно описать очень подробно, поэтому ограничимся чисто технической иллюстрацией замещения.

struct B {

  virtual void f() const { cout << "B::f "; }

  void g() const { cout << "B::g "; } // невиртуальная

};

struct D : B {

  void f() const { cout << "D::f "; } // замещает функцию B::f

  void g() { cout << "D::g "; }

};

struct DD : D {

  void f() { cout << "DD::f "; } // не замещает функцию D::f
 (нет const)

  void g() const { cout << "DD::g "; }

};

Здесь мы описали небольшую иерархию классов с одной виртуальной функцией

f()
. Мы можем попробовать использовать ее. В частности, можем попробовать вызвать функцию
f()
и невиртуальную функцию
g()
, не знающую конкретного типа объекта, который она должна вывести на печать, за исключением того, что он относится либо к классу
B
, либо к классу, производному от класса
B
.

void call(const B& b)

  // класс D — разновидность класса B,

  // поэтому функция call() может

  // получить объект класса D

  // класс DD — разновидность класса D,

  // а класс D — разновидность класса B,

  // поэтому функция call() может получать объект класса DD

{

  b.f();

  b.g();

}

int main()

{

  B b;

  D d;

  DD dd;

  call(b);

  call(d);

  call(dd);

  b.f();

  b.g();

  d.f();

  d.g();

  dd.f();

  dd.g();

}

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

B::f B::g D::f B::g D::f B::g B::f B::g D::f D::g DD::f DD::g

Если вы понимаете, почему, то знаете механизмы наследования и виртуальных функций. 

14.3.4. Доступ

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Язык С++ реализует простую модель доступа к членам класса. Члены класса могут относиться к следующим категориям.

• Закрытые (private). Если член класса объявлен с помощью ключевого слова

private
, то его имя могут использовать только члены данного класса.

• Защищенные (protected). Если член класса объявлен с помощью ключевого слова

protected
, то его имя могут использовать только члены данного класса или члены классов, производных от него.

• Открытые (public). Если член класса объявлен с помощью ключевого слова

public
, то его имя могут использовать все функции.

Изобразим это на рисунке.

Программирование. Принципы и практика использования C++ Исправленное издание - _154.png

Базовый класс также может иметь атрибут

private
,
protected
или
public
.

• Если базовый класс для класса

D
является закрытым, то имена его открытых и защищенных членов могут использоваться только членами класса
D

• Если базовый класс для класса

D
является защищенным, то имена его открытых и защищенных членов могут использоваться только членами класса
D
и членами классов, производных от класса
D
.

• Если базовый класс для класса

D
является открытым, то имена его открытых членов могут использоваться любыми функциями.

196
{"b":"847443","o":1}