Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
21-34_ПР-ИЕ.doc
Скачиваний:
13
Добавлен:
28.08.2019
Размер:
328.19 Кб
Скачать

31. Свойства классов. Доступ к членам класса. Статические члены классов. Массивы объектов классов. Указатель объекта класса на себя. Дружественные функции. Перегрузка операторов.

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

Доступ к членам класса. Наиболее простым способом контролировать доступ является использование модификатора доступа по отношению ко всему классу. В большинстве случаев подходящими для класса модификаторами являются только public, который означает, что класс может быть виден из любой части кода, и internal. Модификатор internal позволяет предоставить доступ к данному классу большому количеству классов, не гарантируя при этом доступ абсолютно всем классам. Класс, помеченный как internal, становится доступным всем классам, входящим в данную сборку (assembly). Таким образом такой класс становится доступным всей программе, но нигде больше.

Модификатор internal часто используется при написании вспомогательных классов (helper classes), которые должны быть скрыты от конечного пользователя.

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

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

Такое применение модификатора internal особенно полезно, если необходимо организовать взаимодействие между несколькими открытыми (public) классами, но некоторые члены этих классов не должны быть доступны любому коду.

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

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

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

Массивы объектов классов. Пусть имеется класс АА. а1,а2,а3 - объект класса АА. Массив хранящий копии этих объектов можно создать так: АА ar AA[3]= {а1,а2,а3}; . При этом создается массив из 5 элементов по умолчанию. Если определен конструктор по умолчанию, то будет создан массив объектов, хранящих данные по умолчанию. Если класс АА не имеет конструктора, то компилятор сам попытается сгенерировать конструктор по умолчанию. Если класс АА содержит конструкторы, но при этом не содержит конструкторов по умолчанию, то компилятор выдаст сообщение об ошибке. Если класс АА содержит конструкторы, то массив объекта этого класса можно создавать помещая в список инициализаторов вызов этих конструкторов:

AA ar AA[]={ AA ( /*список аргументов*/)

AA ( /*список аргументов*/)

AA ( /*список аргументов*/) };

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

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

Перегрузка операторов. Перечень операторов, которые можно перегружать: +, -, *, / , %, ^ , &, |, ~, !, =, >, <, +=, -=, *=, /=, %=, ^=, &=, |=, <<, >>, <<=, >>=, ==, !=, <=, >=, &&, ||, ++, --, ->*, ,, ->, [], (), new, new[], delete[]. Не разрешается: ::, ?:, sizeof, typeid. У программиста отсутствует возможность изменение приоретета перегруженного оператора в выражениях. Отсутствует возможность изменения предопределенного числа операндов у некоторых операторов. Бинарные операторы. А) Если бинарный оператор перегружается внешней оператор функцией, тогда эта функция должна иметь два формальных аргумента, причем левый операнд будет передавать через нее первый аргумент, а правый – через второй аргумент. Б) Если операторная функция является членом класса, то левый операнд ее бинарного оператора будет объектом ее класса-владельца, а правый ее формальным аргументом. Унарные операторы. Префиксная форма. А) Если унарный оператор перегружен внешней оператор функцией, тогда у этой функции один формальный аргумент, играющий роль единственного операнда. Б) Если операторная функция является членом класса, тогда единственный операнд ее унарного оператора будет объектом ее класса владельца. Формальных аргументов тогда у нее не будет. В) Унарный оператор. Постфиксная форма. В этом случае в его операторную функцию следует добавить дополнительный формальный аргумент типа int, который называется фиктивный аргумент, имя которого можно не указывать. 1. Операторные функции члены всегда должны быть нестатические. 2. Перегрузка операторов присваивания =, составных операторов присваивания +=, -=, *=, операторы вызова функции (), оператор индексации [] и -> можно использовать только функцией члена класса, т.е. внешние функции использовать здесь нельзя. Это гарантирует, что левый операнд будет представлять собой именующие выражение value. 3. Для перегрузки операторов преобразования объектов класса к другим типам также необходимо использовать функции члены – класса. 4. Следует включать в определение класса те операторы, которые модифицируют значение своего операнда, например префиксный инкремент. 5. Операторы которые выдают новый объект класса на основе своих операндов, либо не изменяют содержимое операндов, следует определить вне класса.

Перегрузка основных унарных операторов. Обе формы удобно использовать как члены класса. 1. Префиксная форма.

complex & complex :: operator ++()

{ m_Re+=1

return * this; }

complex c1(2,3); // 2+3*i

++c1; // 3+3*i

++++c1; //5+3*i

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

complex & complex :: operator ++(int)

{ complex temp(*this);

m_Re+=1

return temp; }

complex c1, c2(2,3); ++c1; // 3+3*i

c1=c2++; //c2=3+3*i, c1=2+3*i.

Постфиксная форма выполняется медленнее чем префиксная.

Унарный минус и унарный плюс. Унарный + ничего не делает, нужен только лишь для обеспечения возможности обеспечивать унарный плюс в выражениях. a=+b-c+d. Поэтому удобно определять этот оператор как член класса, причем так, чтобы он просто возвращал ссылку на свой операнд.

complex & complex :: operator ++()

{return * this; }.

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

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

Обычные арифметические и логические операторы: +, -, *, /,==,!=,<=. Эти операторы реализуют сложение и реализуют дольше, чем аналогичные им составные операторы присваивания, потому что при их реализации используют три объекта. Два в качестве операнда, третий в качестве результата. Т.к. обычные операторы не меняют содержимое своего первого операнда, то соответствующие им операторы функции следует делать внешними. Логические операторы обычно требуют при своей реализации доступ к закрытым членам, поэтому они часто являются дружественными. Арифметические обычно не требуют такого доступа, т.к. реализованы через составные операторы присваивания. Их часто называют оператор – функции помощники. Функция помощник для некоторого класса – это внешняя функция не являющаяся дружественной к этому классу, но предназначен для работы с объектом этого класса.

32. Наследование и производные классы. Единичное и множественное наследование. Иерархия классов. Управление доступом к членам базового класса. Полиморфизм времени выполнения. Виртуальные функции. Абстрактные классы. Примеры.

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

В языке C# как наследование, так и реализация интерфейса определяются оператором : . Базовый класс должен всегда занимать крайнее левое положение в объявлении класса.

В следующем коде определяется класс с именем CoOrds с двумя закрытыми переменными членов x и y, представляя положение точки. Эти переменные вызываются через свойства с именем X и Y соответственно.

public class CoOrds

{ private int x, y;

public CoOrds() // constructor

{ x = 0;

y = 0;

}

public int X

{ get { return x; }

set { x = value; }

}

public int Y

{ get { return y; }

set { y = value; }

}

}

Производный от класса CoOrds новый класс с именем ColorCoOrds создается следующим образом:

public class ColorCoOrds : CoOrds

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

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

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 { /* ... */ };

Доступ к базовым и производным классам. Доступность члена базового класса в произвольном классе определяется двумя факторами: 1) режимом доступа к нему внутри базового класса, 2) режимом наследования базового класса произвольным классом. Применение наследования потребовало введения режима доступа к членам класса, обеспечиваемого модификатором protected. Члены класса расположенные после него называются защищенными. К ним могут обращаться функции члена и функции друзья этого класса, а также функции члена и функции друзья произвольных от него классов.

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

class Base

{ private:

int m_Priv;

protected:

int m_Prot;

public

int m_Publ;

void setPriv(const int ci)

{ m_Priv = ci;}

void set Prot (const int ci)

{ m_ Prot = ci;}

{int GetPriv() const {return m_Priv;}

int GetProt() const {return m_Prot;}};

class Derived: public Base

{ public:

Int Sum PrivProtPubl() const

{return GetPrid()+m_Prod+ m_Publ ;}

void setPrivProtPubl(const int cPriv

const int cProt

const int cPubl)

setPriv(cPriv);

m_Prot = cProt;

m_Publ = cPubl;}};

void main()

{Derived ob;

оb.setPriv(1);

оb.setProt(2);

оb.m_Publ=3;

оb.setPriv Publ (1,2,3); }

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

class Base

{// тоже самое}

void main()

{Derived ob;

оb.setPriv(1); //ошибка!

оb.setProt(2); //ошибка!

оb.m_Publ=3; //ошибка!

оb.setPriv Publ (1,2,3); }

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

class Base

{// тоже самое}

class Derived: private Base

{// тоже самое}

void main()

{// тоже самое}

Полиморфизм времени выполнения.

Времени выполнения объектно-ориентированного программирования Полиморфизм является одним из самых мощных механизм повторного использования кода, концепция динамических может быть описан как "один интерфейс, множество методов"

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

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

Абстрактный класс в объектно-ориентированном программировании — базовый класс, который не предполагает создания экземпляров. Абстрактный класс может содержать (и не содержать) абстрактные методы и свойства. Абстрактный метод не реализуется для класса, в котором описан, однако должен быть реализован для его неабстрактных потомков. Абстрактные классы представляют собой наиболее общие абстракции, то есть имеющие наибольший объем и наименьшее содержание.

Пример на языке программирования C++:

#include <iostream.h>

class CA { // Абстрактный класс

public:

CA ( void ) { std::cout << "This object of the class "; }

virtual void Abstr ( void ) = 0; // Чистая (пустая) виртуальная функция.

void fun ( void ) { std::cout << "Реализация не будет наследоваться!"; }

~CA () { std::cout << "." << std::endl; } //Вызывается в обр. порядке конструкторов

};

class CB : public CA {

public:

CB ( void ) { std::cout << "CB;"; }

void Abstr ( void ){ std::cout << " call function cb.Abstr();"; } //Подменяющая функция.

void fun ( void ){ std::cout << " call function cb.fun()"; }

~CB () {} // Неверно для абстр. кл. ~CC(){ ~CA(); }

};

class CC : public CA {

public:

CC ( void ) { std::cout << "CC;"; }

void Abstr ( void ) { std::cout << " call function cc.Abstr();"; } //Подменяющая функция.

void fun ( void ) { std::cout << " call function cc.fun()"; }

~CC () {} // Неверно для абстр. кл. ~CC(){ ~CA(); }

};

int main () {

std::cout << "Program:" << std::endl;

CB cb;

cb.Abstr(); cb.fun(); cb.~CB();

CC cc;

cc.Abstr(); cc.fun(); cc.~CC();

return 0;

}