Class CNested1; // объявление первого вложенного класса 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Class CNested1; // объявление первого вложенного класса



Class CNested2; // объявление второго вложенного класса

...

class CNested1 { // определение первого вложенного класса

public:

...

private:

static int __static_member;

};

};

// внешнее определение статического компонента

int CEnclosing::CNested1::__static_member = 1;

// внешнее определение второго вложенного класса

class CEnclosing::CNested2 {

public:

void SomeFunction1();

void SomeFunction2() { // внутреннее определение функции

...

}

...

};

// внешнее определение функции второго вложенного класса

void CEnclosing::CNested2::SomeFunction1() { /*... */ }

Особый интерес при работе с вложенными классами представляет взаимная доступность компонент объемлющего и вложенного классов. Компоненты вложенного класса могут иметь доступ только к открытым компонентам объемлющего класса. Справедливо и обратное: компоненты объемлющего класса могут иметь доступ только к открытым компонентам вложенного класса. Из указанных открытых компонент (как объемлющего, так и вложенного классов) непосредственно доступны только статические компоненты, имена типов и константы перечислений. Доступ к другим открытым компонентам требует указания объекта объемлющего / вложенного класса. Доступ к закрытым и защищенным компонентам возможен только при условии дружественности объемлющего и вложенного классов (дружественность может быть как односторонней, так и взаимной).

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

Пример

int x, y;

class CEnclosing {

public:

int x;

static int s;

enum { const1 = 1, const2 = 2 };

class CInner {

public:

void f(int i) {

x = i; // ошибка, неясно к какому объекту относится x

s = i; // нормально, инициализация статического компонента

::x = i; // нормально, доступ к глобальным переменным

y =::x;

// нормально, работаем с элементами перечисления

y = const1 + const2;

}

void g(CEnclosing * p, int i) {

p->x = i; // нормально, работаем с объектом *p

p->h(1); // ошибка, функция h недоступна

}

private:

static int q;

friend class CEnclosing;

};

protected:

void h(int i) {

CInner::q = i;

// нормально, компонент q доступен в силу дружественности

}

};

Вложенный класс, в принципе, является обычным классом; он просто локализован в пространстве имен объемлющего класса. Соответственно, он может быть базовым и/или производным классом. И еще одна интересная особенность. Интерфейс вложенного класса всегда включается в интерфейс объемлющего класса, т.е. все public-компоненты вложенного класса остаются открытыми независимо от того, в какой части объемлющего класса определен вложенный класс.


Локальные классы

Кроме вложенных классов С++ позволяет вводить так называемые локальные классы. Локальными, в соответствии со стандартом языка, принято называть классы, определенные в теле функции (разница между терминами «вложенный класс» и «локальный класс», как видим, принципиальная). Назначение локальных классов – моделирование вспомогательных абстракций.

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

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

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

Пример

int x;

void EnclosingFunction() { // объемлющая функция

static int s = -1;

extern double d;

int x;

struct LocalClass { // определение локального класса

LocalClass() { /* операторы конструктора */ }

int fun1() { return s; }

double fun2() { return d; }

int fun3() {

Return x; // ошибка, x автоматического класса памяти

}

int fun4() { return::x; }

Static int i; // ошибка, статические данные не допускаются

static void fun5() {} // статические функции допускаются

};

LocalClass oLC; // объект локального класса

...

}

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

Пример

#include <values.h> // для макроса MAXDOUBLE

// функция, анализирующая массивы

Double ArrayAnalyserFunction(

const double * array, size_t size, size_t whatparam)

{

// whatparam определяет, какой параметр нужно рассчитать

if (array == 0 || size == 0) return MAXDOUBLE;

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

static const double * _internal_array = 0;

_internal_array = array;

static size_t _internal_size = 0;

_internal_size = size;

// статические переменные позволяют «обойти» ограничение на

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

class ArrayAnalyser { // класс для анализа массивов

public:

static double Calculate(size_t whatparam) {

Return

whatparam == 1? static_cast<double>(NumNegatives()):

whatparam == 2? static_cast<double>(NumPositives()):

whatparam == 3? Mean(): MeanDeviation();

}

private:

// количество отрицательных элементов

static size_t NumNegatives() {

size_t nneg = 0;

for(size_t i = 0; i < _internal_size; ++i)

nneg += _internal_array[i] < 0? 1: 0;

return nneg;

}

// количество положительных элементов

static size_t NumPositives() {

size_t npos = 0;

for(size_t i = 0; i < _internal_size; ++i)

npos += _internal_array[i] > 0? 1: 0;

return npos;

}

// среднее значение

static double Mean() {

double mean = 0.0;

for(size_t i = 0; i < _internal_size; ++i)

mean += _internal_array[i];

return mean / _internal_size;

}

// среднее отклонение

static double MeanDeviation() {

double mean = Mean();

double meandev = 0.0;

for(size_t i = 0; i < _internal_size; ++i)

meandev += fabs(_internal_array[i] - mean);

return meandev / (_internal_size - 1);

}

}; /* ArrayAnalyser */

// использование локального класса

return ArrayAnalyser::Calculate(whatparam);

}

 


Вопросы для самопроверки

 

1. Каково назначение вложенных классов? Какие отношения предметной области они позволяют моделировать?

2. Каким образом дается внешнее определение вложенного класса? Приведите пример.

3. Как можно обеспечить доступ вложенного класса к открытым нестатическим данным объемлющего класса?

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

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

6. Можно ли вложить иерархию классов в класс? Если да, то как это сделать правильно? Ответ сопроводите примером.

7. Как выполнить определение и инициализацию статического компонента вложенного класса? Ответ сопроводите примером.

8. Как правильно определить локальный класс? Перечислите основные правила и приведите пример.

9. Имеется функция с прототипом в виде void func(int a, double b); В нее вложен класс LocalClass. Каким образом компонентная функция этого класса может получить доступ к аргументам функции func? Приведите фрагмент кода.

10. Возможно ли наличие статических компонент в локальных классах? Если да, то каких статических компонент и как их правильно определить?

11. Как определить вложенный класс в локальном классе? Ответ поясните примером.

 

Задачи

 

Решить задачи из раздела 3, используя механизм вложенных классов.

 


Объектная модель и шаблоны

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

Чтобы понять сущность шаблонов обратимся к примеру. Пусть нам нужно создать класс, инкапсулирующий работу с матрицами. Предположим вначале, что мы создаем класс для своих «внутренних» потребностей. При этом нам известны возможные типы элементов матрицы (например, это может быть int, unsigned, short, unsigned short, float, double, long double). Написав класс для каждого из перечисленных типов элементов, мы можем решить данную задачу. Однако ясно, что это плохое решение, поскольку мы многократно вынуждены повторять по сути один и тот же код[16]. А теперь допустим, что мы создаем библиотечный класс, который будет многократно использоваться другими программистами. Понятно, что теперь нам неизвестны возможные типы элементов матрицы, поэтому мы не сможем определить классы «на все случаи».

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



Поделиться:


Последнее изменение этой страницы: 2016-08-14; просмотров: 124; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.17.75.227 (0.025 с.)