Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Шпоры резать.doc
Скачиваний:
5
Добавлен:
21.12.2018
Размер:
1.24 Mб
Скачать

3. Наследовние

Наследование (inheritance) - это процесс, посредством которого один объект может приобретать свойства другого. Точнее, объект может наследовать основные свойства другого объекта и добавлять к ним черты, характерные только для него. Наследование является важным, поскольку оно позволяет поддерживать концепцию иерархии классов (hierarchical classification). Применение иерархии классов делает управляемыми большие потоки информации. Например, подумайте об описании жилого дома. Дом - это часть общего класса, называемого строением. С другой стороны, строение - это часть более общего класса - конструкции, который является частью ещё более общего класса объектов, который можно назвать созданием рук человека. В каждом случае порождённый класс наследует все, связанные с родителем, качества и добавляет к ним свои собственные определяющие характеристики. Без использования иерархии классов, для каждого объекта пришлось бы задать все характеристики, которые бы исчерпывающи его определяли. Однако при использовании наследования можно описать объект путём определения того общего класса (или классов), к которому он относится, с теми специальными чертами, которые делают объект уникальным. Наследование играет очень важную роль в OOP.

class A{ //базовый класс };

class B : public A{ //public наследование }

class C : protected A{ //protected наследование }

class Z : private A{ //private наследование }

В C++ существует три типа наследования: public, protected, private. Спецификаторы доступа членов базового класса меняются в потомках следующим образом:

при public-наследовании все спецификаторы остаются без изменения.

при protected-наследовании все спецификаторы остаются без изменения, кроме спецификатора public, который меняется на спецификатор protected (то есть public-члены базового класса в потомках становятся protected).

при private-наследовании все спецификаторы меняются на private.

Одним из основных преимуществ public-наследования является то, что указатель на классы—наследники может быть неявно преобразован в указатель на базовый класс, то есть для примера выше можно написать:

A* a = new B;

Эта интересная особенность открывает возможность динамической идентификации типа (RTTI).

4. Иерархия классов

Производный класс сам в свою очередь может быть базовым классом:

class employee { /* ... */ };

class manager : public employee { /* ... */ };

class director : public manager { /* ... */ };

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

class temporary { /* ... */ };

class secretary : public employee { /* ... */ };

class tsec

: public temporary, public secretary { /* ... */ };

class consultant

: public temporary, public manager { /* ... */ };

85.

Понятие объекта и класса.

Классы C++ являются основой объектно-ориентированного программирования.

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

Программы на C++ представляют объекты посредством классов.

Класс, подобно структуре, содержит элементы. Элементы класса могут хранить информацию (данные) или быть функциями (методами), которые оперируют этими данными.

Каждый класс имеет уникальное имя.

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

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

В известном смысле объект представляет собой сущность. Программа обычно использует переменные для хранения информации о различных реально существующих сущностях, например служащих, книгах и даже файлах. При объектно-ориентированном программировании вы фокусируетесь на предметах, образующих систему, и операциях, которые вы должны выполнять над этими предметами. Например, для объекта-файла вы могли бы иметь операции, которые печатают, отображают или изменяют файл. В C++ вы используете класс для определения своих объектов. Ваша цель состоит в том чтобы включить в класс столько информации об объекте, сколько требуется. Исходя из этого, можно подобрать класс, созданный вами для одной программы, и использовать его в нескольких разных программах.

Класс позволяет вашим программам группировать данные и функции которые выполняют операции над этими данными. Большинство книг и статей об объектно-ориентированном программировании называют функции класса методами. Подобно структуре, класс C++ должен иметь уникальное имя, за которым следует открывающая фигурная скобка, один или несколько элементов и закрывающая фигурная скобка:

class class_name

{

int data_member; // Элемент данных

void show_member(int); // Функция-элемент

};

После определения класса вы можете объявлять переменные типа этого класса (называемые объектами), как показано ниже:

class_name object_one, object_two, object_three;

Следующее определение создает класс employee, который содержит определения данных и метода:

class employee

{

public:

char name[64] ;

long employee_id;

float salary;

void show_employee(void)

{

cout << "Имя: " << name << endl;

cout << "Номер служащего: " << employee_id << endl;

cout << "Оклад: " << salary << endl;

};

};

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

————————————————————— Имя класса

employee worker, boss, secretary ; //--------->Переменные класса (объекты)

Представление об объектах

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

86.

Включение в данные объекта информации о методах их обработки

Методы класса

Классы C++ позволяют вашим программам группировать данные объекта и функции объекта (методы), которые оперируют с этими данными, в одной переменной. У вас есть две возможности определения методов объекта. Первая состоит в том, что вы можете включить весь код функции внутрь определения класса. Несмотря на то что включение кода метода в определение класса может представляться удобным, однако, когда классы становятся сложнее и включают несколько методов, операторы функций могут вносить беспорядок в определение классов. Таким образом, многие программы определяют операторы функции вне класса. В определение класса программа должна включать прототип функции, который указывает имя функции, тип возвращаемого значения и типы параметров.

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

return_type class_name::function_name(parameters)

{ // Операторы }

88.

Объявление класса: private, protected, public элементы класса

Класс в языке C++ создаётся следующим образом:

class MyClass: ParentClass // ParentClass — класс-предок, если таковой имеется

{

public:

// элементы в этой секции доступны из любой части программы

protected:

// элементы в этой секции доступны из класса и его потомков

private:

// элементы в этой секции доступны только из класса; это область доступа по умолчанию

MyClass() // конструктор

~MyClass() // деструктор

}

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

MyClass myinstance;

Обращение к членам класса:

myinstance.classmember

Уничтожается экземпляр класса, как и любая переменная, только в случае, если функция, в которой он был создан завершила работу или если была принудительно освобождена динамическая память, выделенная под класс.

private (закрытый, внутренний член класса) — обращения к члену допускаются только из методов того класса, в котором этот член определён. Любые наследники класса уже не смогут получить доступ к этому члену. Наследование по типу private запрещает доступ из дочернего класса ко всем членам родительского класса, включая даже public-члены (С++);

protected (защищённый, внутренний член иерархии классов) — обращения к члену допускаются из методов того класса, в котором этот член определён, а также из любых методов его классов-наследников. Наследование по типу protected делает все public-члены родительского класса protected-членами класса-наследника (С++);

public (открытый член класса) — обращения к члену допускаются из любого кода. Наследование по типу public не меняет модификаторов родительского класса (С++);

89.

Интерфейс класса. Свойство защищенности интерфейса класса

В программировании существует понятие программного интерфейса, означающего перечень возможных вычислений, которые может выполнить та или иная часть программы, включая описание того, какие аргументы и в каком порядке требуется передавать на вход алгоритмам из этого перечня, а также что и в каком виде они будут возвращать. Абстрактный тип данных интерфейс придуман для формализованного описания такого перечня. Сами алгоритмы, т.е. действительный программный код, который будет выполнять все эти вычисления, интерфейсом не задаётся и называется реализацией интерфейса.

C++ поддерживает множественное наследование и абстрактные классы, поэтому, как уже упоминалось выше, отдельная синтаксическая конструкция для интерфейсов в этом языке не нужна. Интерфейсы определяются при помощи абстрактных классов, а реализация интерфейса производится путём наследования этих классов.

Пример определения интерфейса:

/**

* interface.Openable.hpp

*

*/

#ifndef INTERFACE_OPENABLE_HPP

#define INTERFACE_OPENABLE_HPP

// Класс интерфейса iOpenable. Определяет возможность открытия/закрытия чего либо.

class iOpenable

{

public:

virtual ~iOpenable(){}

virtual void open()=0;

virtual void close()=0;

};

#endif

Интерфейс реализуется через наследование. Благодаря наличию множественного наследования, ничто не мешает реализовать в одном классе несколько интерфейсов, если в этом есть необходимость

90.

Доступ к глобальным и локальным данным, область видимости для идентификаторов, объявленных внутри класса.

Локальные переменные представляют собой переменные, объявленные внутри функции.

Локальные переменные известны только той функции, внутри которой они объявлены.

Несколько функций могут использовать одно и то же имя для локальной переменной без каких-либо конфликтов.

Глобальная переменная представляет собой переменную, чье имя и значение известны на протяжении всей программы.

Чтобы объявить глобальную переменную, объявите переменную в начале вашего исходного файла вне какой-либо функции.

Поскольку глобальные переменные могут быть легко изменены любой функцией, они привносят возможность появления трудно обнаруживаемьгх ошибок, поэтому избегайте использования глобальных переменных в своих программах.

ОБЪЯВЛЕНИЕ ЛОКАЛЬНЫХ ПЕРЕМЕННЫХ

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

void some_function(void)

{

int count;

float result;

}

О конфликте имен

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

О локальных переменных

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

ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ

Как вы уже знаете, локальная переменная объявляется и известна только в определенной функции. В дополнение к локальным переменным C++ позволяет вашим программам объявлять глобальные переменные, которые известны на протяжении всей программы (глобально для всех функций). Чтобы объявить глобальную переменную, следует просто поместить объявление переменной в начало вашей программы вне какой-либо функции:

int some_global_variable; ---> Объявление глобальной переменной

void main(void)

{

// Здесь должны быть операторы программы

}

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

Если имена глобальных и локальных переменных конфликтуют

По мере возможности следует избегать использования в программах глобальных переменных. Однако если ваша программа должна использовать глобальную переменную, то может случиться, что имя глобальной переменной конфликтует с именем локальной переменной. При возникновении такого конфликта C++ предоставляет приоритет локальной переменной. Другими словами, программа предполагает, что в случае конфликта каждая ссылка на имя соответствует локальной переменной.

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

О глобальных переменных

Глобальная переменная представляет собой переменную, чье имя и значение известны на протяжении всей программы. Для создания глобальной переменной вы объявляете переменную в начале вашего исходного файла вне какой-либо функции. Все функции, которые следуют за таким объявлением, могут использовать эту глобальную переменную. Однако, поскольку злоупотребление глобальными переменными может привести к ошибкам, следует избегать их использования в ваших программах везде, где это возможно.

Представление об области видимости переменных

При чтении книг и журнальных статей по C++ вы можете встретить термин область видимости, что определяет участок в программе, где имя переменной имеет смысл (и, следовательно, используется). Для локальной переменной область видимости ограничена функцией, внутри которой эта переменная объявлена. С другой стороны, глобальные переменные известны на протяжении всей программы. В результате глобальные переменные имеют большую область видимости.

91.

Для ограничения уровня доступа к данным и функциям-членам класса в С++ существуют три ключевых слова private: (частный), protected: (защищенный), public: (общедоступный), задающие разделы поступа в классе. Каждый раздел в классе начинается с одного из приведенных слов. Если ни одно из ключевых слов не использовалось, то все объявления в классе считаются частными. Разделы с разными привилегиями доступа могут появлятся в любом порядке и в любом колличестве. Рассмотрим пример.

class Example {

int x1; // частные по умолчанию

int f1(void);

protected:

int x2; // защищенные

int f2(void);

private:

int x3; // опять частные

int f3(void);

public:

int x4; // общедоступные

inf f4(void);

};

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

class B {

protected:

int i;

};

class D1 : public B {

};

class D2 : public B {

friend void fr(B*, D1*, D2*);

void mem(B*, D1*);

};

void fr(B* pb, D1* p1, D2* p2)

{

pb->i = 1; // недопустимо

p1->i = 2; // недопустимо

p2->i = 3; // нормально (обращение через D2)

}

void D2::mem(B* pb, D1* p1)

{

pb->i = 1; // недопустимо

p1->i = 2; // недопустимо

i = 3; // нормально (обращение через this)

}

void g(B* pb, D1* p1, D2* p2)

{

pb->i = 1; // недопустимо

p1->i = 2; // недопустимо

p2->i = 3; // недопустимо

}

92.

Объекты класса как члены

Рассмотрим пример:

class classdef {

table members;

int no_of_members;

// ...

classdef(int size);

~classdef();

};

Цель этого определения, очевидно, в том, чтобы classdef содержал член, являющийся таблицей размером size, но есть сложность: надо обеспечить вызов конструктора table::table() с параметром size. Это можно сделать, например, так:

classdef::classdef(int size)

:members(size)

{

no_of_members = size;

// ...

}

Параметр для конструктора члена (т.е. для table::table()) указывается в определении (но не в описании) конструктора класса, содержащего член (т.е. в определении classdef::classdef()). Конструктор для члена будет вызываться до выполнения тела того конструктора, который задает для него список параметров.

Аналогично можно задать параметры для конструкторов других членов (если есть еще другие члены):

class classdef {

table members;

table friends;

int no_of_members;

// ...

classdef(int size);

~classdef();

};

Списки параметров для членов отделяются друг от друга запятыми (а не двоеточиями), а список инициализаторов для членов можно задавать в произвольном порядке:

classdef::classdef(int size)

: friends(size), members(size), no_of_members(size)

{

// ...

}

Конструкторы вызываются в том порядке, в котором они заданы в описании класса.

Подобные описания конструкторов существенны для типов, инициализация и присваивание которых отличны друг от друга, иными словами, для объектов, являющихся членами класса с конструктором, для постоянных членов или для членов типа ссылки. Однако, как показывает член no_of_members из приведенного примера, такие описания конструкторов можно использовать для членов любого типа.

Если конструктору члена не требуется параметров, то и не нужно задавать никаких списков параметров. Так, поскольку конструктор table::table() был определен со стандартным значением параметра, равным 15, достаточно такого определения:

classdef::classdef(int size)

: members(size), no_of_members(size)

{

// ...

}

Тогда размер таблицы friends будет равен 15.

Если уничтожается объект класса, который сам содержит объекты класса (например, classdef), то вначале выполняется тело деструктора объемлющего класса, а затем деструкторы членов в порядке, обратном их описанию.

Рассмотрим вместо вхождения объектов класса в качестве членов традиционное альтернативное ему решение: иметь в классе указатели на члены и инициализировать члены в конструкторе:

class classdef {

table* members;

table* friends;

int no_of_members;

// ...

};

classdef::classdef(int size)

{

members = new table(size);

friends = new table; // используется стандартный

// размер table

no_of_members = size;

// ...

}

Поскольку таблицы создавались с помощью операции new, они должны уничтожаться операцией delete:

classdef::~classdef()

{

// ...

delete members;

delete friends;

}

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

93.

Обычно каждый объект класса имеет свою собственную копию всех данных-элементов класса. Но в определенных случаях во всех объектах класса должна фигурировать только одна копия некоторых данных-элементов для всех объектов класса. Для этих и других целей используются статические данные-элементы, которые содержат информацию «для всего класса». Объявление статических элементов начинается с ключевого слова static.Хотя может показаться, что статические элементы данные похожи на глобальные переменные, тем не менее они имеют областью действия класс. Статические элементы могут быть открытыми, закрытыми или защищенными (protected). Статическим данным-элементам можно задать начальные значения один (и только один) раз в области действия файл. Доступ к открытым статическим элементам класса возможен посредством любого объекта класса или посредством имени класса, с помощью бинарной операции разрешения области действия. Закрытые и защищенные статические элементы класса должны быть доступны открытым функциям-элементам этого класса или друзьям класса. Статические элементы класса существуют даже тогда, когда не существует никаких объектов этого класса. Чтобы в этом случае обеспечить доступ к открытому статическому элементу, просто поставьте перед элементом данных имя класса и бинарную операцию разрешения области действия. Для обеспечения доступа в указанном случае к закрытому или защищенному элементу класса должна быть предусмотрена открытая статическая функция-элемент, которая должна вызываться с добавлением перед ее именем имени класса и бинарной операции разрешения области действия.

94.

Ссылки

В языке программирования C++ ссылка — это простой ссылочный тип, менее мощный, но более безопасный, чем указатель, унаследованый от языка Си. Название C++ ссылка может приводить к путанице, так как в информатике под ссылкой понимается обобщенный концептуальный тип, а указатели и С++ ссылки являются специфическими реализациями ссылочного типа.

Объявление вида:

<Type> & <Name>

где <Type> — тип и <Name> — идентификатор, указывают идентификатор, чьим типом является ссылка на <Type>.

Примеры:

int A = 5;

int& rA = A;

extern int& rB;

int& foo ();

void bar (int& rP);

class MyClass { int& m_b; /* ... */ };

int funcX() { return 42 ; }; int (&xFunc)() = funcX;

Здесь, rA и rB являются типами «ссылок на int», foo() — функция, возвращающая ссылку на int, bar() — функция с ссылкой в качестве параметра, которая ссылается на int, MyClass — класс (class) с членом, ссылающимся на int, funcX() — функция, возвращающая int, xFunc() — псевдоним для funcX.

Типы, относящиеся к «ссылка на <Type>», иногда называются ссылочными типами. Идентификаторы ссылочного типа называются ссылочными переменными. Вызывать их переменную фактически будет неправильно (показано дальше).

Связь с указателями

C++ ссылки отличаются от указателей несколькими особенностями:

Невозможно ссылаться напрямую на объект ссылочного типа после его определения; каждое упоминание его имени ссылается напрямую на объект, на который он ссылается.

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

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

Ссылки не могут быть null (т.е.указывать в никуда), тогда как указатели - могут; каждая ссылка ссылается на некий объект, вне зависимости от его корректности.

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

Пример:

int& k; // компилятор выдаст сообщение: ошибка: 'k' declared as reference but not initialized ('k' объявлена как ссылка, но не инициализирована)

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

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

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

Если она ссылается на объект с автоматическим размещением в памяти, которое выходит за границы видимости,

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

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

Указатели

Указатели являются одной из сильных сторон С++. Грамотное их применение позволяет повысить скорость выполнения программы и более эффективно использовать память. Например, во многих случаях в качестве аргументов функции лучше использовать указатели на объект, чем каждый раз создавать их копии.

С указателями используются следующие операции:

& - взятие адреса переменной;

* - разъименование указателя, т.е. получение доступа к объекту;

-> - разъименование члена структуры;

[] - индексация, доступ к элементу массива, при этом считается, что указатель содержит адрес 0 элемента.

Операция индексации может быть реализована с помощью адресной арифметики, где применяются операции ++,--, и +, -.

Можно создавать указатели на любые типы за исключением ссылок и битовых полей. Указатель может указывать не только на данные, но и на область кода, т.е. на функцию.

95.

Функция элемент - это функция, объявленная внутри определения класса и тесно связанная с типом этого класса.

Общий вид синтаксиса вызова выглядит как:

имя_класса_объекта.имя_функции(список аргументов).

sber_bank

Объект сбербанк

< deposit << Вклад денег

$$$ < >

<

Данные > withdraw >> Снятие денег

Функции элементы

Наглядное представление функции элемента withdraw() приводится ниже:

Селектор

Определяемый класс Имя функции элемента

Возвращаемый тип Аргумент

v v v v v

double sber_bank::withdraw(double bucks)

{

big_bucks -= bucks;

if (big_bucks < 0.0) {

printf("К сожалению, товарищ, у вас нет таких денег!\n");

big_bucks -= 10.0; // Штрафная санкция

}

return big_bucks;

}

Имя класса sber_bank используется для указания компилятору, к какому классу принадлежит withdraw (так как могут иметься и другие варианты withdraw, принадлежащие другим классам). При внутреннем определении селектор '::' не потребуется, так как и так ясно, что withdraw принадлежит классу sber_bank.

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

Объект Имя функции элемента

v v

my_bank.deposit(paycheck);

^ ^ ^

Простой селектор

в обращении к задан- Вызов функции элемента

ному элементу струк-

туры

96.

Функции-члены могут определяться вне списка членов класса. При определении функции-члена класса за пределами данного класса, в списке членов класса размещается прототип функции-члена. А при определении функции-члена используется квалифицированное имя. Квалифицированное имя состоит из последовательности имён классов, разделённых операциями разрешения области видимости. Эта последовательность имён завершается именем определяемой функции. Последовательность имён классов в квалифицированных именах определяется степенью вложенности объявлений классов.

Наличие функций-членов делает объявление класса подобным определению (как и любые функции, функции-члены определяются). Как сказано в Справочном руководстве по C++, "Если бы не исторические причины, объявление класса следовало называть определением класса".

Данные-члены класса не могут объявляться со спецификаторами auto, extern, register.

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

97.

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

Friend им_функции ( список_формальных_параметров); Дружественная функция имеет доступ ко всем компонентам класса вне зависимости от их области видимости. Дружественная функция класса может быть обычной внешней функцией программы, а может – компонентной функцией другого класса.

Дружественная функция становится расширением интерфейса класса, и этот интерфейс реализует взаимодействие объекта с другими объектами программы.

Рассмотрим простой пример.

Дружественные функции классов

class ClassB;

class ClassA

{ int x;

friend void func(ClassA,ClassB); //объявляем функцию дружественной классу ClassA

};

class ClassB

{ int y;

friend void func(ClassA,ClassB); //объявляем функцию дружественной классу ClassВ

};

void func(ClassA a,ClassB b)

{

cout <<a.x+b.y;//дружественная функция имеет доступ к частным компонентам обоих

// классов

}

main()

{

ClassA a;

ClassB b;

func(a,b);

}

В определены два класса ClassA и ClassB, а также внеш

няя функция func, объявленная дружественной в обоих классах, благодаря чему func имеет доступ к частным компонентам x и y классов. При этом функция func является внешней по отношению к классу функцией и объекты класса должны либо передаваться ей в качестве параметров, либо объявляться в теле функции. Неявной передачи адреса объекта в дружественную функцию при вызове, как это осуществляется для компонентных функций класса, не происходит.

Дружественная классу функция может быть компонентной функцией другого класса.

98.

99.

C++ обеспечивает удобные способы создания и удаления объектов, через специальные процедуры. Процедуры создания объектов называются конструкторами, а процедуры уничтожения - деструкторами. Конструкторы автоматически вызываются при описании объекта, а деструкторы - при выходе из блока, в котором этот объект был описан. Если необходимые конструкторы или деструктор для класса не описаны, то транслятор создает их сам. Для того, чтобы понять важность конструкторов, сделаем маленькое отступление. Общей проблемой для всех традиционных языков программирования является инициализация: прежде чем использовать структуру данных, нужно выделить ей память и проинициализировать.

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

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

100.

ПРЕДСТАВЛЕНИЕ О ДЕСТРУКТОРЕ

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

Каждая из созданных вами до сих пор программ создавала объекты в самом начале своего выполнения, просто объявляя их. При завершении программ C++ уничтожал объекты. Если вы определяете деструктор внутри своей программы, C++ будет автоматически вызывать деструктор для каждого объекта, когда программа завершается (т.е. когда объекты уничтожаются). Подобно конструктору, деструктор имеет такое же имя, как и класс объекта. Однако в случае деструктора вы предваряете его имя символом тильды (~), как показано ниже:

~class_name (void) //----------->указывает деструктор

{

// Операторы деструктора

}

В отличие от конструктора вы не можете передавать параметры деструктору. Следующая программа DESTRUCT.CPP определяет деструктор для класса employee:

void employee::-employee(void)

{

cout << "Уничтожение объекта для " << name << endl;

}

В данном случае деструктор просто выводит на ваш экран сообщение о том, что C++ уничтожает объект. Когда программа завершается, C++ автоматически вызывает деструктор для каждого объекта. Ниже приведена реализация программы DESTRUCT.CPP:

#include <iostream.h>

#include <string.h>

class employee

{

public:

employee(char *, long, float);

~employee(void);

void show_employee(void);

int change_salary(float);

long get_id(void);

private:

char name [64] ;

long employee_id;

float salary;

};

employee::employee(char *name, long employee_id, float salary)

{

strcpy(employee::name, name) ;

employee::employee_id = employee_id;

if (salary < 50000.0) employee::salary = salary;

else // Недопустимый оклад

employee::salary в 0.0;

}

void employee::-employee(void)

{

cout << "Уничтожение объекта для " << name << endl;

}

void employee::show_employee(void)

{

cout << "Служащий: " << name << endl;

cout << "Номер служащего: " << employee_id << endl;

cout << "Оклад: " << salary << endl;

}

void main(void)

{

employee worker("Happy Jamsa", 101, 10101.0);

worker.show_employee();

}

Если вы откомпилируете и запустите эту программу, на вашем экране появится следующий вывод:

С:\> DESTRUCT <ENTER>

Служащий: Happy Jamsa

Номер служащего: 101

Оклад: 10101

Уничтожение объекта для Happy Jamsa

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

Деструкторы

Деструктор представляет собой функцию, которую C++ автоматически запускает, когда он или ваша программа уничтожает объект. Деструктор имеет такое же имя, как и класс объекта; однако вы предваряете имя деструктора символом тильды (~), например ~employee. В своей программе вы определяете деструктор точно так же, как и любой другой метод класса.

101.

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

Перегруженные функции оказываются весьма полезными, когда одну и ту же операцию необходимо выполнить над аргументами разных типов. Перегрузка функций C++ позволяет программам определять несколько функций с одним и тем же именем. Перегруженные функции должны возвращать значения одинакового типа*, но могут отличаться количеством и типом параметров.

102.

Перегрузка конструкторов

Как вы уже знаете из урока 13, C++ позволяет вашим программам перегружать определения функций, указывая альтернативные функции для других типов параметров. C++ позволяет вам также перегружать конструкторы. Следующая программа CONSOVER.CPP перегружает конструктор employee. Первый конструктор требует, чтобы программа указывала имя служащего, номер служащего и оклад. Второй конструктор запрашивает пользователя ввести требуемый оклад, если программа не указывает его:

employee::employee(char *name, long employee_id)

{ strcpy(employee::name, name);

employee::employee_id = employee_id;

do

{

cout << "Введите оклад для " << name << " меньше $50000: ";

cin >> employee::salary;

}

while (salary >= 50000.0);

}

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

class employee

{

public:

employee (char *, long, float);|___ Прототипы перегруженных

employee(char *, long); |функций

void show_employee(void);

int change_salary(float);

long get_id(void);

private:

char name [64];

long employee_id;

float salary;

}

Ниже приведена реализация программы CONSOVER.CPP:

#include <iostream.h>

#include <string.h>

class employee

{

public:

employee(char *, long, float);

employee(char *, long);

void show_employee(void);

int change_salary(float) ;

long get_id(void);

private:

char name [64];

long employee_id;

float salary;

};

employee::employee(char *name, long employee_id, float salary)

{

strcpy(employee::name, name);

employee::employee_id = employee_id;

if (salary < 50000.0) employee::salary = salary;

else // Недопустимый оклад

employee::salary = 0.0;

}

employee::employee(char *name, long employee_id)

{

strcpy(employee::name, name);

employee::employee_id = employee_id;

do

{

cout << "Введите оклад для " << name << " меньше $50000: ";

cin >> employee::salary;

}

while (salary >= 50000.0);

}

void employee::show_employee(void)

{

cout << "Служащий: " << name << endl;

cout << "Номер служащего: " << employee_id << endl;

cout << "Оклад: " << salary << endl;

}

void main(void)

{

employee worker("Happy Jamsa", 101, 10101.0);

employee manager("Jane Doe", 102);

worker.show_employee();

manager.sbow_employee();

}

Если вы откомпилируете и запустите эту программу, на вашем экране появится запрос ввести оклад для Jane Doe. Когда вы введете оклад, программа отобразит информацию об обоих служащих.

103.

Теперь о том, какие операторы можно, а какие - нельза перегружать. Запрещено перегружать всего 4 оператора:

:: разрешение области видимости

. выбор члена

.* выбор члена через указатель на член

?: троичный оператор

Чтобы перегрузить оператор в форме внешней функции, необходимо определить глобальную функцию. Перегруженная функция выглядит так же, как и любая глобальная функция (если не считать странного имени). Именно для таких случаев и были придуманы друзья. Если бы мы не объявили функцию operator+ другом, то она не имела бы доступа к переменной s, и мы оказались бы перед выбором: то ли разрешить всем на свете доступ к char*, то ли перейти к менее эффективной реализации, при которой строка копируется при каждом обращении к ней. С концептуальной точки зрения operator+ является частью библиотеки String, поэтому нет ничего страшного в том, чтобы объявить эту функцию другом и вручить ей ключи к внутреннему устройству String.

Внешними функциями могут перегружаться любые операторы, кроме операторов преобразования, =, [], () и -> - все эти операторы должны перегружаться только функциями класса.

Перегрузка операторов в форме функций класса

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

1. Первый аргумент относится к базовому типу (например, int или double).

2. Тип первого аргумента определен в коммерческой библиотеке, которую нежелательно модифицировать.

Компилятор ищет перегрузку в форме функций класса, просматривая левую часть бинарных операторов и единственный аргумент унарных. Если ваш тип указывается справа и вы хотите воспользоваться перегрузкой в форме функции класса, вам не повезло. Самый распространенный пример перегрузки в форме внешней функции - оператор << в библиотеке ostream.

104.

Оператор return. Точка вызова и точка возврата

Нам уже известна операция вызова функции и синтаксис постфиксного выражения, обеспечивающего ввызов. Можно довольно просто представить внешний вид оператора вызова функции. Это оператор-выражение произвольной сложности, в состав которого входит выражение вызова функции. Любое выражение имеет значение и тип. Значение выражения вычисляется в ходе выполнения соответствующего программного кода.

Для каждого выражения существует момент начала вычисления значения. Этот момент характеризуется соответствующими значениями регистров процессора и состоянием памяти компьютера. Это обстоятельство позволяет определить гипотетическую точку начала выполнения выражения. На листинге программы эта точка располагается обычно слева, но она может быть расположена и справа от соответствующего выражения. Расположение этой точки зависит от многих обстоятельств. В том числе, от приоритета выполняемых операций и от порядка вычисления выражения, который зависит от входящей в выражение операции.

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

Точка завершения выполнения выражения соответствует моменту завершения вычисления значения и на листинге программы располагается справа или соответственно слева от вычисляемого выражения. В точке завершения становится известно значение выражения.

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

Так вот оператор return немедленно прекращает выполнение операторов в теле функции и передаёт управление в точку возврата. Поскольку вызов функции является выражением, точка возврата имеет значение. Это значение определяется значением выражения, которое обычно располагается непосредственно за оператором возврата return. Тип возвращаемого значения должен соответствовать типу, который указывается спецификатором определения в объявлении и определении функции.

Если в качестве спецификатора объявления в определении и объявлении функции используется ключевое слово void, оператор return в теле этой функции используется без выражения. В этом случае выражение вызова функции оказывается выражением типа void, а значение выражения вызова в точке возврата оказывается неопределённым. Такое выражение не может входить в состав выражений более сложной структуры в качестве операнда выражения, поскольку значение всего выражения оказывается неопределённым.

Выражение с неопределённым значением (выражение вызова функции типа void) может выступать лишь в качестве выражения-оператора. Главное - это не забыть поставить в конце этого выражения разделитель ';', который и превращает это выражение в оператор

Операторная функция operator() с несколькими параметрами

И это ещё не всё! Ещё не рассматривались варианты операторной функции operator() с несколькими параметрами. И здесь следует вспомнить о функциях с переменным количеством параметров. Это не единственный, но наиболее оптимальный подход к объявлению операторной функции operator() с несколькими параметрами. Здесь мы не будем вдаваться в детали алгоритма извлечения информации из списка параметров (мы их уже обсуждали раньше), а ограничимся лишь общей схемой объявления и вариантами выражения вызова. В нашей версии (всего лишь одной из возможных!), первым параметром функции всегда будет целое число:

ComplexType& operator () (int, ...);// Прототип. ::::: ComplexType& ComplexType::operator () (int iKey, ...) { cout << "This is operator ( " << iKey << ", ...)" << endl; return *this; } ::::: CDw2(50); CDw2(50, 100); CDw2(50, "Это тоже вызов операторной функции", 3.14, 0,123456789);

В C++ может быть объявлено более трёх десятков различных вариантов операторных функций. К этому выводу приводит анализ списка символов операций, которые потенциально могут входить в качестве элемента имени операции.

Здесь не имеет смысла описывать все возможные операторные функции по отдельности. В этом разделе мы рассмотрим ещё несколько интересных "нетипичных" случаев объявления, в следующих разделах будут описаны типичные общие схемы объявлений операторных функций.

Как известно, операция косвенного обращения -> является бинарной операцией. Её первым операндом является указатель на объект, вторым - имя члена класса.

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

Оператор инициализации выполняется один раз перед началом цикла. В данном примере перед началом выполнения цикла переменной ix присваивается значение 2.

Условие используется для управления циклом. Оно вычисляется перед началом каждого повторения цикла. Пока значение условия остается истинным, выполняется оператор. Оператором может быть один оператор или блок операторов. Если в результате первого вычисления условия было получено значение false, то оператор не будет выполнен ни разу. В данном примере в условии проверяется, будет ли значение ix меньше значения seq_size.

выражение вычисляется после каждого повторения цикла. Обычно оно-используется для изменения объектов, которые инициализируются в операторе инициализации и проверяются в условии. Если при первом вычислении условия было получено значение false, то выражение не будет выполнено никогда. В данном примере после каждого повторения цикла значение i X увеличивается на единицу.

Чтобы напечатать все элементы, нужно пройти по всему набору чисел.

cout " "первые " " seq_size

" " элементов ряда пелла:\n\t";

for (intix=0; ix< seq_size; ++ix ) cout " pell_seq[ ix ] " 1 ';

cout " '\n';

Можно пропускать оператор инициализации, выражение или, реже, условие цикла. Например, можно переписать предыдущий цикл в таком виде.

int ix = 0; // ...

for С ; ix < seq_size; ++ix ) // ...

Точка с запятой нужна для обозначения пустого оператора инициализации.

В контейнере хранятся второй, третий и четвертый элементы каждого из шести рядов. Каким образом можно заполнить контейнер нужными значениями? Для встроенных массивов существует возможность указать список инициализации, список начальных значений всех или части элементов, разделенных запятыми.

int elem_seq[ seq_size ] = {

1, 2, 3, // Фибоначчи 3, 4, 7, // Лукас

2, 5, 12, ¦// пелл

3, 6, 10, // Треугольный

4, 9, 16, // квадратный

5, 12, 22, // пятиугольный

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

// компилятор вычислит размер в 18 элементов int elem_seq[] = {

1,'2, 3, 3, 4, 7, 2, 5, 12,

3, 6, 10, 4, 9, 16, 5, 12, 22

}

105.

Функции-элементы класса могут быть перегружены: то есть две или несколько функций-элементов могут иметь одно и то же имя при условии, что списки их аргументов различаются. Компилятор следит за тем, чтобы вызывалась нужная функция, проверяя соответствие аргументов. Довольно обычной является перегрузка конструкторов. Например:

#include <time.h>

const int TIME_STR_LEN = 30;

class Time

{

char timeStr[TIME_STR_LEN];

public:

Time();

Time(char *str);

};