Важнейшим свойством объектно-ориентированного программирования является В наследует класс A (класс B выведен из класса A ), в определении класса B после имени класса ставится двоеточие и затем перечисляются классы, из которых B наследует:
class A
{
public:
A();
~A();
MethodA();
};
class B : public A
{
public:
B();
. . .
};
B обладает всеми свойствами класса A, он их унаследовал
B b; b.MethodA(); // вызов метода базового класса
Часто
(рис 10.1) Пример иерархии классов.
Иерархия классов может быть сколь угодно глубокой. Если нужно различить, о каком именно классе идет речь, класс C называют непосредственным или прямым D, а класс A – косвенным D.
Предположим, что для библиотечной системы, которую мы разрабатываем, необходимо создать классы, описывающие различные книги, журналы и т.п., которые хранятся в библиотеке. Книга, журнал, газета и микрофильм обладают как общими, так и различными свойствами. У книги имеется автор или авторы, название и год издания. У журнала есть название, номер и содержание – список статей. В то же время книги, журналы и т.д. имеют и общие свойства: все это – "единицы хранения" в библиотеке, у них есть инвентарный номер, они могут быть в читальном зале, у читателей или в фонде хранения. Их можно выдать и, соответственно, сдать в библиотеку. Эти общие свойства удобно объединить в одном Item, который описывает единицу хранения в библиотеке:
class Item
{
public:
Item();
~Item();
// истина, если единица хранения на руках
bool IsTaken() const;
// истина, если этот предмет имеется в библиотеке
bool IsAvailable() const;
long GetInvNumber() const; // инвентарный номер
void Take(); // операция "взять"
void Return(); // операция "вернуть"
private:
// инвентарный номер — целое число
long invNumber;
// хранит состояние объекта - взят на руки
bool taken;
};
Когда мы разрабатываем часть системы, которая имеет дело с процессом выдачи и возврата книг, вполне достаточно того интерфейса, который представляет
// выдать на руки
void
TakeAnItem(Item i)
{
. . .
if (i.IsAvailable())
i.Take();
}
Конкретные свойства книги будут представлены классом Book.
class Book : public Item
{
public:
String Author(void) const;
String Title(void) const;
String Publisher(void) const;
long YearOfPublishing(void) const;
String Reference(void) const;
private:
String author;
String title;
String publisher;
short year;
}; // автор
// название
// издательство
// год выпуска
// полная ссылка
// на книгу
Для журнала класс Magazine предоставляет другие сведения:
class Magazine : public Item
{
public:
String Volume(void) const;
short Number(void) const;
String Title(void) const;
Date DateOfIssue() const;
private:
String volume;
short number;
String title;
Date date;
};
// том
// номер
// название
// дата выпуска
У объекта класса Book имеются методы, непосредственно определенные в классе Book, и методы, определенные в классе Item.
Book b; long in = b.GetInvNumber(); String t = b.Reference();
Reference класса Book будет выглядеть следующим образом:
String
Book::Reference(void) const
{
String result = author + "\n"
+ title + "\n"
+ String(GetInvNumber());
return result;
}
(Предполагается, что у класса String есть конструктор, который преобразует целое число в строку.) Запись:
String result = author + "\n"
+ title + "\n"
+ String(invNumber);
не разрешена, поскольку invNumber – внутренний атрибут класса Item. Однако если бы мы поместили invNumber в защищенную часть класса:
class Item
{
. . .
protected:
long invNumber;
};
то методы классов Book и Magazine могли бы непосредственно использовать этот атрибут.
Если одно и то же имя атрибута или метода встречается как в
class A
{
public:
. . .
int foo();
. . .
};
class B : public A
{
public:
int foo();
void bar();
};
void
B::bar()
{
x = foo();
// вызывается метод foo класса B
}
Однако метод foo сначала просматриваются атрибуты и методы самого класса. Если бы имя не было найдено, начался бы просмотр имен в foo существует в самом классе, поэтому оно и используется.
С помощью записи A::foo() можно явно указать, что нас интересует имя, определенное в классе A, и тогда запись:
x = A::foo();
вызовет метод
Вообще, запись класс::имя уже многократно нами использовалась. При поиске имени она означает, что имя относится к заданному классу.
В обоих классах, выведенных из класса Item, имеется метод Title, выдающий в качестве результата заглавие книги или название журнала. Кроме этого метода, полезно было бы иметь метод, выдающий полное название любой единицы хранения. Реализация этого метода различна, поскольку название книги и журнала состоит из разных частей. Однако вид метода – возвращаемое значение и аргументы – и его общий смысл один и тот же. Название – это общее свойство всех единиц хранения в библиотеке, и логично поместить метод, выдающий название, в
class Item
{
public:
virtual String Name(void) const;
. . .
};
class Book : public Item
{
public:
virtual String Name(void) const;
. . .
};
class Magazine : public Item
{
public:
virtual String Name(void) const;
. . .
};
Реализация метода Name для
String
Item::Name(void) const
{
return "";
}
Для книги название состоит из фамилии автора, названия книги, издательства и года издания:
String
Book::Name(void) const
{
return author + title + publisher +
String(year);
}
У журнала полное название состоит из названия журнала, года и номера:
String
Magazine::Name(void) const
{
return title + String(year) +
String(number);
}
Методы Name определены как virtual, стоящего перед определением метода. Item и с его помощью вызывается метод Name:
Item* ptr; . . . String name = ptr->Name();
то по виду вызова метода невозможно определить, какая из трех приведенных выше реализаций Name будет выполнена. Все зависит от того, на какой конкретный объект указывает указатель ptr.
Item* ptr;
. . .
if (type == "Book")
ptr = new Book;
else if (type == "Magazine")
ptr = new Magazine;
. . .
String name = ptr->Name();
В данном фрагменте программы, если переменная type, обозначающая тип библиотечной единицы, была равна " Book ", то будет вызван метод Name класса Book. Если же она была равна " Magazine ", то будет вызван метод класса Magazine.
Приведем еще один пример Shape. Конкретные фигуры, с которыми работает редактор, будут представлены классами Square (квадрат), Circle (круг), Ellipse (эллипс), Shape. Класс Shape определяет Draw для отображения формы на экране.
class Shape
{
public:
Shape();
virtual void Draw(void);
};
//
// квадрат
//
class Square : public Shape
{
public:
Square();
virtual void Draw(void);
private:
double length; // длина стороны
};
//
// круг
//
class Circle : public Shape
{
public:
Circle();
virtual void Draw(void);
private:
short radius;
};
. . .
Конкретные классы реализуют данный метод, и, разумеется, делают это по-разному. Однако в функции перерисовки текущей формы, если у нас имеется указатель на Square, Circle и т.д.) принадлежит объект, на который указывает указатель shape:
Repaint(Shape* shape)
{
shape->Draw();
}
Что бы изменилось, если бы метод Name не был описан как Name, поскольку мы работаем с указателем на Name класса Item. При определении метода как virtual решение о том, какой именно метод будет выполняться, принимается во время выполнения.
Свойство виртуальности проявляется только тогда, когда обращение к методу идет через указатель или ссылку на объект. Указатель или ссылка могут указывать как на объект
func(Item item)
{
item.Name(); // вызывается метод Item::Name()
}
func1(Item item)
{
item.Name(); // вызывается метод в соответствии
// с типом того объекта, на который
// ссылается item
}
Объект
Circle* pC; . . . Shape* pShape = pC;
Обратное не всегда верно. Преобразование из
Item* iPtr; . . . Book* bPtr = (Book*)iPtr;
небезопасно. Такое преобразование можно выполнять только тогда, когда точно известно, что iPtr указывает на объект класса Book.
До сих пор мы использовали только внешнее private, то
class B : private A
{
. . .
};
В случае внутреннего
Если перед именем
Фактически, при защищенном и внутреннем
Если в классе A был определен какой-то метод:
class A
{
public:
int foo();
};
то запись
B b; b.foo();
недопустима, так же, как и наследуемый от B
class C
{
int m() {
foo();
}
};
если класс B внутренне наследует A. Если же класс B использовал защищенное b.foo() также была бы неправильной, но зато вторая была бы верна.
Вернемся к примеру Item, который представляет общие свойства всех единиц хранения в библиотеке. Но существуют ли объекты класса Item? То есть существует ли в действительности "единица хранения" сама по себе? Конечно, каждая книга (класс Book ), журнал (класс Magazine ) и т.д. принадлежат и к классу Item, поскольку они выведены из него, однако объект самого
Тот факт, что в данном случае объекты Name. Его реализация в
С другой стороны, нам важен факт наличия метода Name во всех Name.
Такая ситуация складывается довольно часто в объектно-ориентированном программировании. (Вспомните пример с различными формами в графическом редакторе: рисование некой обобщенной формы невозможно.) В подобных случаях используется механизм Item немного по-другому:
class Item
{
public:
. . .
virtual String Name() const = 0;
};
Теперь мы определили метод Name как
Если метод объявлен Item. Наличие Item. В программе можно использовать указатели или ссылки на тип Item. Записи
Item it; Item* itptr = new Item;
не разрешены, и компилятор сообщит об ошибке. Однако можно записать:
Book b; Item* itptr = b; Item itref = b;
Отметим, что, определив Book, в следующем уровне Book ).
Если по каким-либо причинам в
Вообще говоря, класс можно сделать
class A
{
public:
virtual ~A() = 0;
};
A::~A()
{
. . .
}
Класс A – A создать невозможно. Однако деструктор его определен и будет вызван при уничтожении объектов
В языке Си++ имеется возможность в качестве
(рис 10.2) Иерархия классов при множественном наследовании.
В данном случае класс C наследует двум классам, A и B.
Предположим, имеющуюся библиотечную систему решено установить в университете и интегрировать с другой системой учета преподавателей и студентов. В библиотечной системе имеются классы, описывающие читателей и работников библиотеки. В системе учета кадров существуют классы, хранящие информацию о преподавателях и студентах. Используя
В графическом редакторе для некоторых фигур может быть предусмотрен пояснительный текст. При этом все алгоритмы форматирования и печати пояснений работают с классом Annotation. Тогда те фигуры, которые могут содержать пояснение, будут представлены классами,
class Annotation
{
public:
String GetText(void);
private:
String annotation;
};
class Shape
{
public:
virtual void Draw(void);
};
class AnnotatedSquare : public Shape,
public Annotation
{
public:
virtual void Draw();
};
У объекта класса AnnotatedSquare имеется метод GetText, Annotation, он определяет Draw, Shape.
При применении
class A
{
public:
void fun();
int a;
};
class B
{
public:
int fun();
int a;
};
class C : public A, public B
{
};
При записи
C* cp = new C; cp->fun();
невозможно определить, к какому из двух методов fun происходит обращение. Ситуация называется неоднозначной, и компилятор выдаст ошибку. Заметим, что ошибка выдается не при определении класса C, в котором заложена возможность возникновения неоднозначной ситуации, а лишь при попытке вызова метода fun.
Неоднозначность можно разрешить, явно указав, к которому из
cp->A::fun();
Вторая проблема заключается в возможности многократного включения Person:
class Person
{
public:
String name();
};
class Student : public Person
{
. . .
};
class Librarian : public Person
{
. . .
};
Если теперь создать класс для представления студентов, подрабатывающих в библиотеке
class StudentLibrarian : public Student,
public Librarian
{
};
то объект данного класса будет содержать объект
Кроме того, что подобная ситуация отражает нерациональное использование памяти, никаких неудобств в данном случае она не вызывает. Возможную неоднозначность можно разрешить, явно указав класс:
StudentLibrarian* sp; // ошибка – неоднозначное обращение, // непонятно, к какому именно экземпляру // типа Person обращаться sp->Person::name(); // правильное обращение sp->Student::Person::name();
Тем не менее, иногда необходимо, чтобы объект
class Student : virtual Person
{
};
class Librarian : virtual Person
{
};
Гарантировано, что объект виртуального
Важнейшим свойством объектно-ориентированного программирования является В наследует класс A (класс B выведен из класса A ), в определении класса B после имени класса ставится двоеточие и затем перечисляются классы, из которых B наследует:
class A
{
public:
A();
~A();
MethodA();
};
class B : public A
{
public:
B();
. . .
};
B обладает всеми свойствами класса A, он их унаследовал
B b; b.MethodA(); // вызов метода базового класса
Часто
(рис 10.1) Пример иерархии классов.
Иерархия классов может быть сколь угодно глубокой. Если нужно различить, о каком именно классе идет речь, класс C называют непосредственным или прямым D, а класс A – косвенным D.
Предположим, что для библиотечной системы, которую мы разрабатываем, необходимо создать классы, описывающие различные книги, журналы и т.п., которые хранятся в библиотеке. Книга, журнал, газета и микрофильм обладают как общими, так и различными свойствами. У книги имеется автор или авторы, название и год издания. У журнала есть название, номер и содержание – список статей. В то же время книги, журналы и т.д. имеют и общие свойства: все это – "единицы хранения" в библиотеке, у них есть инвентарный номер, они могут быть в читальном зале, у читателей или в фонде хранения. Их можно выдать и, соответственно, сдать в библиотеку. Эти общие свойства удобно объединить в одном Item, который описывает единицу хранения в библиотеке:
class Item
{
public:
Item();
~Item();
// истина, если единица хранения на руках
bool IsTaken() const;
// истина, если этот предмет имеется в библиотеке
bool IsAvailable() const;
long GetInvNumber() const; // инвентарный номер
void Take(); // операция "взять"
void Return(); // операция "вернуть"
private:
// инвентарный номер — целое число
long invNumber;
// хранит состояние объекта - взят на руки
bool taken;
};
Когда мы разрабатываем часть системы, которая имеет дело с процессом выдачи и возврата книг, вполне достаточно того интерфейса, который представляет
// выдать на руки
void
TakeAnItem(Item i)
{
. . .
if (i.IsAvailable())
i.Take();
}
Конкретные свойства книги будут представлены классом Book.
class Book : public Item
{
public:
String Author(void) const;
String Title(void) const;
String Publisher(void) const;
long YearOfPublishing(void) const;
String Reference(void) const;
private:
String author;
String title;
String publisher;
short year;
}; // автор
// название
// издательство
// год выпуска
// полная ссылка
// на книгу
Для журнала класс Magazine предоставляет другие сведения:
class Magazine : public Item
{
public:
String Volume(void) const;
short Number(void) const;
String Title(void) const;
Date DateOfIssue() const;
private:
String volume;
short number;
String title;
Date date;
};
// том
// номер
// название
// дата выпуска
У объекта класса Book имеются методы, непосредственно определенные в классе Book, и методы, определенные в классе Item.
Book b; long in = b.GetInvNumber(); String t = b.Reference();
Reference класса Book будет выглядеть следующим образом:
String
Book::Reference(void) const
{
String result = author + "\n"
+ title + "\n"
+ String(GetInvNumber());
return result;
}
(Предполагается, что у класса String есть конструктор, который преобразует целое число в строку.) Запись:
String result = author + "\n"
+ title + "\n"
+ String(invNumber);
не разрешена, поскольку invNumber – внутренний атрибут класса Item. Однако если бы мы поместили invNumber в защищенную часть класса:
class Item
{
. . .
protected:
long invNumber;
};
то методы классов Book и Magazine могли бы непосредственно использовать этот атрибут.
Если одно и то же имя атрибута или метода встречается как в
class A
{
public:
. . .
int foo();
. . .
};
class B : public A
{
public:
int foo();
void bar();
};
void
B::bar()
{
x = foo();
// вызывается метод foo класса B
}
Однако метод foo сначала просматриваются атрибуты и методы самого класса. Если бы имя не было найдено, начался бы просмотр имен в foo существует в самом классе, поэтому оно и используется.
С помощью записи A::foo() можно явно указать, что нас интересует имя, определенное в классе A, и тогда запись:
x = A::foo();
вызовет метод
Вообще, запись класс::имя уже многократно нами использовалась. При поиске имени она означает, что имя относится к заданному классу.
В обоих классах, выведенных из класса Item, имеется метод Title, выдающий в качестве результата заглавие книги или название журнала. Кроме этого метода, полезно было бы иметь метод, выдающий полное название любой единицы хранения. Реализация этого метода различна, поскольку название книги и журнала состоит из разных частей. Однако вид метода – возвращаемое значение и аргументы – и его общий смысл один и тот же. Название – это общее свойство всех единиц хранения в библиотеке, и логично поместить метод, выдающий название, в
class Item
{
public:
virtual String Name(void) const;
. . .
};
class Book : public Item
{
public:
virtual String Name(void) const;
. . .
};
class Magazine : public Item
{
public:
virtual String Name(void) const;
. . .
};
Реализация метода Name для
String
Item::Name(void) const
{
return "";
}
Для книги название состоит из фамилии автора, названия книги, издательства и года издания:
String
Book::Name(void) const
{
return author + title + publisher +
String(year);
}
У журнала полное название состоит из названия журнала, года и номера:
String
Magazine::Name(void) const
{
return title + String(year) +
String(number);
}
Методы Name определены как virtual, стоящего перед определением метода. Item и с его помощью вызывается метод Name:
Item* ptr; . . . String name = ptr->Name();
то по виду вызова метода невозможно определить, какая из трех приведенных выше реализаций Name будет выполнена. Все зависит от того, на какой конкретный объект указывает указатель ptr.
Item* ptr;
. . .
if (type == "Book")
ptr = new Book;
else if (type == "Magazine")
ptr = new Magazine;
. . .
String name = ptr->Name();
В данном фрагменте программы, если переменная type, обозначающая тип библиотечной единицы, была равна " Book ", то будет вызван метод Name класса Book. Если же она была равна " Magazine ", то будет вызван метод класса Magazine.
Приведем еще один пример Shape. Конкретные фигуры, с которыми работает редактор, будут представлены классами Square (квадрат), Circle (круг), Ellipse (эллипс), Shape. Класс Shape определяет Draw для отображения формы на экране.
class Shape
{
public:
Shape();
virtual void Draw(void);
};
//
// квадрат
//
class Square : public Shape
{
public:
Square();
virtual void Draw(void);
private:
double length; // длина стороны
};
//
// круг
//
class Circle : public Shape
{
public:
Circle();
virtual void Draw(void);
private:
short radius;
};
. . .
Конкретные классы реализуют данный метод, и, разумеется, делают это по-разному. Однако в функции перерисовки текущей формы, если у нас имеется указатель на Square, Circle и т.д.) принадлежит объект, на который указывает указатель shape:
Repaint(Shape* shape)
{
shape->Draw();
}
Что бы изменилось, если бы метод Name не был описан как Name, поскольку мы работаем с указателем на Name класса Item. При определении метода как virtual решение о том, какой именно метод будет выполняться, принимается во время выполнения.
Свойство виртуальности проявляется только тогда, когда обращение к методу идет через указатель или ссылку на объект. Указатель или ссылка могут указывать как на объект
func(Item item)
{
item.Name(); // вызывается метод Item::Name()
}
func1(Item item)
{
item.Name(); // вызывается метод в соответствии
// с типом того объекта, на который
// ссылается item
}
Объект
Circle* pC; . . . Shape* pShape = pC;
Обратное не всегда верно. Преобразование из
Item* iPtr; . . . Book* bPtr = (Book*)iPtr;
небезопасно. Такое преобразование можно выполнять только тогда, когда точно известно, что iPtr указывает на объект класса Book.
До сих пор мы использовали только внешнее private, то
class B : private A
{
. . .
};
В случае внутреннего
Если перед именем
Фактически, при защищенном и внутреннем
Если в классе A был определен какой-то метод:
class A
{
public:
int foo();
};
то запись
B b; b.foo();
недопустима, так же, как и наследуемый от B
class C
{
int m() {
foo();
}
};
если класс B внутренне наследует A. Если же класс B использовал защищенное b.foo() также была бы неправильной, но зато вторая была бы верна.
Вернемся к примеру Item, который представляет общие свойства всех единиц хранения в библиотеке. Но существуют ли объекты класса Item? То есть существует ли в действительности "единица хранения" сама по себе? Конечно, каждая книга (класс Book ), журнал (класс Magazine ) и т.д. принадлежат и к классу Item, поскольку они выведены из него, однако объект самого
Тот факт, что в данном случае объекты Name. Его реализация в
С другой стороны, нам важен факт наличия метода Name во всех Name.
Такая ситуация складывается довольно часто в объектно-ориентированном программировании. (Вспомните пример с различными формами в графическом редакторе: рисование некой обобщенной формы невозможно.) В подобных случаях используется механизм Item немного по-другому:
class Item
{
public:
. . .
virtual String Name() const = 0;
};
Теперь мы определили метод Name как
Если метод объявлен Item. Наличие Item. В программе можно использовать указатели или ссылки на тип Item. Записи
Item it; Item* itptr = new Item;
не разрешены, и компилятор сообщит об ошибке. Однако можно записать:
Book b; Item* itptr = b; Item itref = b;
Отметим, что, определив Book, в следующем уровне Book ).
Если по каким-либо причинам в
Вообще говоря, класс можно сделать
class A
{
public:
virtual ~A() = 0;
};
A::~A()
{
. . .
}
Класс A – A создать невозможно. Однако деструктор его определен и будет вызван при уничтожении объектов
В языке Си++ имеется возможность в качестве
(рис 10.2) Иерархия классов при множественном наследовании.
В данном случае класс C наследует двум классам, A и B.
Предположим, имеющуюся библиотечную систему решено установить в университете и интегрировать с другой системой учета преподавателей и студентов. В библиотечной системе имеются классы, описывающие читателей и работников библиотеки. В системе учета кадров существуют классы, хранящие информацию о преподавателях и студентах. Используя
В графическом редакторе для некоторых фигур может быть предусмотрен пояснительный текст. При этом все алгоритмы форматирования и печати пояснений работают с классом Annotation. Тогда те фигуры, которые могут содержать пояснение, будут представлены классами,
class Annotation
{
public:
String GetText(void);
private:
String annotation;
};
class Shape
{
public:
virtual void Draw(void);
};
class AnnotatedSquare : public Shape,
public Annotation
{
public:
virtual void Draw();
};
У объекта класса AnnotatedSquare имеется метод GetText, Annotation, он определяет Draw, Shape.
При применении
class A
{
public:
void fun();
int a;
};
class B
{
public:
int fun();
int a;
};
class C : public A, public B
{
};
При записи
C* cp = new C; cp->fun();
невозможно определить, к какому из двух методов fun происходит обращение. Ситуация называется неоднозначной, и компилятор выдаст ошибку. Заметим, что ошибка выдается не при определении класса C, в котором заложена возможность возникновения неоднозначной ситуации, а лишь при попытке вызова метода fun.
Неоднозначность можно разрешить, явно указав, к которому из
cp->A::fun();
Вторая проблема заключается в возможности многократного включения Person:
class Person
{
public:
String name();
};
class Student : public Person
{
. . .
};
class Librarian : public Person
{
. . .
};
Если теперь создать класс для представления студентов, подрабатывающих в библиотеке
class StudentLibrarian : public Student,
public Librarian
{
};
то объект данного класса будет содержать объект
Кроме того, что подобная ситуация отражает нерациональное использование памяти, никаких неудобств в данном случае она не вызывает. Возможную неоднозначность можно разрешить, явно указав класс:
StudentLibrarian* sp; // ошибка – неоднозначное обращение, // непонятно, к какому именно экземпляру // типа Person обращаться sp->Person::name(); // правильное обращение sp->Student::Person::name();
Тем не менее, иногда необходимо, чтобы объект
class Student : virtual Person
{
};
class Librarian : virtual Person
{
};
Гарантировано, что объект виртуального
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.