Flatik.ru

Перейти на главную страницу

Поиск по ключевым словам:

страница 1 ... страница 10страница 11страница 12страница 13

Производные классы, наследование


Важнейшим свойством объектно-ориентированного программирования является наследование. Для того, чтобы показать, что класс В наследует класс A (класс B выведен из класса A), в определении класса B после имени класса ставится двоеточие и затем перечисляются классы, из которых B наследует:

class A


{

public:


A();

~A();


MethodA();

};

class B : public A



{

public:


B();

. . .


};

Термин "наследование" означает, что класс B обладает всеми свойствами класса A, он их унаследовал. У объекта производного класса есть все атрибуты и методы базового класса. Разумеется, новый класс может добавить собственные атрибуты и методы.

B b;

b.MethodA(); // вызов метода базового класса



Часто выведенный класс называют подклассом, а базовый класс – суперклассом. Из одного базового класса можно вывести сколько угодно подклассов. В свою очередь, производный класс может служить базовым для других классов. Изображая отношения наследования, их часто рисуют в виде иерархии или дерева.

пример иерархии классов.

Пример иерархии классов

Иерархия классов может быть сколь угодно глубокой. Если нужно различить, о каком именно классе идет речь, класс 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;

};


// том

// номер


// название

// дата выпуска

Ключевое слово public перед именем базового класса определяет, что внешний интерфейс базового класса становится внешним интерфейсом порожденного класса. Это наиболее употребляемый тип наследования.

У объекта класса 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 могли бы непосредственно использовать этот атрибут.



Назначение защищенной (protected) части класса в том и состоит, чтобы, закрыв доступ "извне" к определенным атрибутам и методам, разрешить пользоваться ими производным классам.

Если одно и то же имя атрибута или метода встречается как в базовом классе, так и в производном, то производный класс перекрывает базовый.

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();

вызовет метод базового класса.

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


19. Контроль доступа к объекту


Интерфейс и состояние объекта

Основной характеристикой класса с точки зрения его использования является интерфейс, т.е. перечень методов, с помощью которых можно обратиться к объекту данного класса. Кроме интерфейса, объект обладает текущим значением или состоянием, которое он хранит в атрибутах класса. В С++ имеются богатые возможности, позволяющие следить за тем, к каким частям класса можно обращаться извне, т.е. при использовании объектов, и какие части являются "внутренними", необходимыми лишь для реализации интерфейса.

Определение класса можно поделить на три части – внешнюю, внутреннюю и защищенную. Внешняя часть предваряется ключевым словом public , после которого ставится двоеточие. Внешняя часть – это определение интерфейса. Методы и атрибуты, определенные во внешней части класса, доступны как объектам данного класса, так и любым функциям и объектам других классов. Определением внешней части мы контролируем способ обращения к объекту. Предположим, мы хотим определить класс для работы со строками текста. Прежде всего, нам надо соединять строки, заменять заглавные буквы на строчные и знать длину строк. Соответственно, эти операции мы поместим во внешнюю часть класса:

class String

{

public:


// добавить строку в конец текущей строки

void Concat(const String& str);

// заменить заглавные буквы на строчные

void ToLower(void);

int GetLength(void) const;

// сообщить длину строки

. . .

};


Внутренняя и защищенная части класса доступны только при реализации методов этого класса. Внутренняя часть предваряется ключевым словом private, защищенная – ключевым словом protected.

class String

{

public:


// добавить строку в конец текущей строки

void Concat(const String& str);

// заменить заглавные буквы на строчные

void ToLower(void);

int GetLength(void) const;

// сообщить длину строки

private:

char* str;

int length;

};


В большинстве случаев атрибуты во внешнюю часть класса не помещаются, поскольку они представляют состояние объекта, и возможности их использования и изменения должны быть ограничены. Представьте себе, что произойдет, если в классе String будет изменен указатель на строку без изменения длины строки, которая хранится в атрибуте length.

Объявляя атрибуты str и length как private, мы говорим, что непосредственно к ним обращаться можно только при реализации методов класса, как бы изнутри класса (private по-английски – частный, личный). Например:

int

String::GetLength(void) const



{

return length;

}

Внутри определения методов класса можно обращаться не только к внутренним атрибутам текущего объекта, но и к внутренним атрибутам любых других известных данному методу объектов того же класса. Реализация метода Concat будет выглядеть следующим образом:



void

String::Concat(const String& x)

{

length += x.length;



char* tmp = new char[length + 1];

::strcpy(tmp, str);

::strcat(tmp, x.str);

delete [] str;

str = tmp;

}

Однако если в программе будет предпринята попытка обратиться к внутреннему атрибуту или методу класса вне определения метода, компилятор выдаст ошибку, например:



main()

{

String s;



if (s.length > 0) // ошибка

. . .


}

При записи классов мы помещаем первой внешнюю часть, затем защищенную часть и последней – внутреннюю часть. Дело в том, что внешняя часть определяет интерфейс, использование объектов данного класса. Соответственно, при чтении программы эта часть нужна прежде всего. Защищенная часть необходима при разработке зависимых от данного класса новых классов.

Объявление friend

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

class String

{

. . .



friend String concat(const String& s1,

const String& s2);

};

Тогда функция concat может быть реализована следующим образом:



String

concat(const String& s1, const String& s2)

{

String result;



result.length = s1.length + s2.length;

result.str = new char[result.length + 1];

if (result.str == 0) {

// обработка ошибки

}

strcpy(result.str, s1.str);



strcat(result.str, s2.str);

return result;

}

С помощью механизма friend можно разрешить обращение к внутренним элементам класса как отдельной функции, отдельному методу другого класса или всем методам другого класса:



class String

{

// все методы класса StringParser обладают



// правом доступа ко всем атрибутам класса

// String

friend class StringParser;

// из класса Lexer только метод CharCounter

// может обращаться к внутренним атрибутам

// String

friend int Lexer::CharCounter(const

String& s, char c);

};

Конечно, злоупотреблять механизмом friend не следует. Каждое решение по использованию friend должно быть продумано. Если только одному методу какого-либо класса действительно необходим доступ, не следует объявлять весь класс как friend.



Использование описателя const

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

Если в начале описания переменной стоит описатель const, то описываемый объект во время выполнения программы не изменяется:

const double pi = 3.1415;

const Complex one(1,1);

Если const стоит перед определением указателя или ссылки, то это означает, что не изменяется объект, на который данный указатель или ссылка указывает:

// указатель на неизменяемую строку

const char* ptr = &string;

char x = *ptr;

ptr++;


*ptr = '0';

// обращение по указателю — допустимо

// изменение указателя — допустимо

// попытка изменения объекта, на

// который указатель указывает –

// ошибка

Если нужно объявить указатель, значение которого не изменяется, то такое объявление выглядит следующим образом:

char* const ptr = &string;

// неизменяемый указатель

char x = *ptr;

ptr++;

*ptr = '0';



// обращение по указателю – допустимо

// изменение указателя – ошибка

// изменение объекта, на который

// указатель указывает – допустимо


Доступ к объекту по чтению и записи

Кроме контроля доступа к атрибутам класса с помощью разделения класса на внутреннюю, защищенную и внешнюю части, нужно следить за тем, с помощью каких методов можно изменить текущее значение объекта, а с помощью каких – нельзя.

При описании метода класса как const выполнение метода не может изменять значение объекта, который этот метод выполняет.

class A

{

public:


int GetValue (void) const;

int AddValue (int x) const;

private:

int value;

}

int


A::GetValue(void) const

{

return value; }



// объект не изменяется

int


A::AddValue(int x) const

{


value += x;

// попытка изменить атрибут объекта

// приводит к ошибке компиляции
return value;

}

Таким образом, использование описателя const позволяет программисту контролировать возможность изменения информации в программе, тем самым предупреждая ошибки.



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

int


Lexer::CharCounter(const String& s, char c)

{ int n = s.GetLength(); // допустимо

s.Concat("ab");

// ошибка – Concat изменяет значение s



}

Общим правилом является объявление всех методов как неизменяемых, за исключением тех, которые действительно изменяют значение объекта. Иными словами, объявляйте как можно больше методов как const. Такое правило соответствует правилу объявления аргументов как const. Объявление константных аргументов запрещает изменение объектов во время выполнения функции и тем самым предотвращает случайные ошибки.


Литература




  1. Фридман А.Л. Язык программирования Си++ . Интернет-университет информационных технологий - ИНТУИТ.ру, 2004

  2. Анисимов А.Е., Пупышев В.В.
    Сборник заданий по основаниям программирования
    БИНОМ. Лаборатория знаний, Интернет-университет информационных технологий - ИНТУИТ.ру, 2006

  3. Непейвода Н.Н. Стили и методы программирования
    Интернет-университет информационных технологий - ИНТУИТ.ру, 2005.

  4. Борисенко В.В. Основы программирования. Интернет-университет информационных технологий – https://www.intuit.ru, 2005

  5. Терехов А.Н. Технология программирования. БИНОМ. Лаборатория знаний, Интернет-университет информационных технологий - https://www.intuit.ru, 2007.

  6. Шилдт Г. Самоучитель С++ - СПб:BHV-Санкт-Петербург, 1998. - 512 с.



<предыдущая страница


О. В. Прохорова основы программирования. Часть «Основы языка С++»

Разработчиком языка Си++ является Бьерн Страуструп. В своей работе он опирался на опыт создателей языков Симула, Модула 2, абстрактных типов данных. Основные работы велись в исслед

1072.81kb.

10 10 2014
13 стр.


Урок дата сабақ №1 Күн ТЕМА УРОКА сабақ тың тақырыбы: Язык программирования. Основы языка Бейсик

В итоге получается текст программы полное, законченное и детальное описание алгоритма на языке программирования. Затем этот текст программы специальными служебными приложениями, ко

611.95kb.

25 12 2014
5 стр.


Программа по курсу основы информатики (Введение в программирование) по направлению

Язык программирования С++ (история, стандарт, обратная совместимость с С, место среди других языков программирования)

151.35kb.

09 10 2014
1 стр.


Учебное пособие 10 часть Основы общей теории управления. Функциональный и процессный подходы к управлению организацией 15

Основы процессного управления. Компоненты процессов. Идентификация и классификация процессов 31

1845.11kb.

25 12 2014
17 стр.


1. Среда языка программирования Pascal abc

Блеза Паскаля. На основе языка Паскаль в 1985 г фирма Borland выпустила версию Turbo Pascal версии с этого времени язык Паскаль используется во всем мире в учебных заведениях в кач

537.72kb.

02 10 2014
4 стр.


«Химическая физика, горение и взрыв, физика экстремальных состояний вещества»

В основу настоящей программы положены следующие дисциплины: строение веществ, основы молекулярной фотоники, динамика атомов и молекул, основы химической кинетики, основы синергетик

81.95kb.

10 09 2014
1 стр.


Рабочая программа дисциплины промышленные типы месторождений полезных ископаемых направление

Пререквизиты: минералогия, петрография, литология, основы учения о полезных ископаемых (основы теории рудообразующих процессов), формационный анализ, опробование твердых полезных и

353.45kb.

01 10 2014
3 стр.


Учебно-методический комплекс дисциплины Основы творческой деятельности журналиста Часть V художественная публицистика Для студентов факультета журналистики

Учебно-методический комплекс дисциплины «Основы творческой деятельности журналиста». Часть V: Художественная публицистика. – М.: Импэ им. А. С. Грибоедова, 2008. – 10 с

157.74kb.

01 10 2014
1 стр.