- •Раздел 4. Разработка по Тема 4.1. Проектирование интерфейса с пользователем
- •4.1.1. Типы пользовательских интерфейсов.
- •4.1.2. Пользовательская и программная модели интерфейса.
- •4.1.3. Разработка диалогов.
- •4.1.4. Основные компоненты графических пользовательских интерфейсов.
- •Тема 4.2. Реализация графических пользовательских интерфейсов.
- •4.2.1. Диалоги, управляемые пользователем.
- •4.2.2. Диалоги, управляемые системой.
- •4.2.3. Использование метафор.
- •4.2.4. Технология Drag and Drop.
- •4.2.5. Интеллектуальные элементы.
- •4.3.1. Базовые типы данных.
- •Константы
- •Область действия имен
- •4.3.2. Указатели и адресная арифметика.
- •4.3.3. Составные типы данных. Структуры
- •Битовые поля
- •Определение типов
- •Перечислимые типы
- •4.3.4. Выражения и операции.
- •4.3.5. Управляющие конструкции. Условные операторы
- •Операторы циклов
- •4.4.1. Статические одномерные массивы.
- •4.4.2. Статические многомерные массивы.
- •4.4.3. Динамические массивы.
- •4.4.4. Массивы указателей.
- •4.5.1. Стеки.
- •4.5.2. Очереди.
- •4.5.3. Списки.
- •4.5.4. Бинарные деревья.
- •4.6.1. Объявление классов и экземпляров классов.
- •4.6.2. Инкапсуляция данных и методов.
- •4.6.3. Конструкторы классов.
- •Конструктор по умолчанию
- •Конструктор копирования
- •4.6.4. Деструкторы классов.
- •4.7.1. Разделы в описании класса.
- •4.7.2. Friend-конструкции.
- •4.7.3. Статические члены классов.
- •4.7.4. Использование описателя const в классах.
- •4.8.1. Вложенность классов.
- •4.8.2. Наследование данных и методов.
- •4.8.3. Типы наследования.
- •4.9.1. Полиморфизм раннего связывания.
- •4.9.2. Полиморфизм позднего связывания и виртуальные функции.
- •4.9.3. Абстрактные методы и классы.
- •4.10.1. Функции консольного ввода-вывода.
- •4.10.2. Функции файлового ввода-вывода.
- •4.10.3. Использование библиотеки классов потокового ввода-вывода.
- •4.11.1. Перегрузка операций.
- •4.11.2. Шаблоны функций.
- •4.11.3. Шаблоны классов.
- •4.11.4. Обработка исключений.
- •Тема 4.12. Com-технология.
- •4.12.1. Основные понятия.
- •4.12.2. Типы интерфейсов.
- •Свойства интерфейсов
- •Типы интерфейсов
- •4.12.3. Типы com-объектов.
- •4.12.4. Фабрика классов.
- •Тема 4.13. Построение com-сервера.
- •4.13.1. Язык idl.
- •Содержимое файла idl
- •4.13.2. Определение пользовательского интерфейса.
- •4.13.3. Реализация пользовательского интерфейса.
- •4.13.4. Создание тестового клиента.
- •Тема 4.14. Обзор платформы ms .Net.
- •4.14.1. Общая идея архитектуры .Net.
- •4.14.2. Достоинства и недостатки .Net.
- •4.14.3. Схема трансляции программ в .Net.
- •4.14.4. Язык msil.
- •4.14.5. Объектно-ориентированная модель .Net.
4.7.4. Использование описателя const в классах.
Этот описатель используется в качестве средства повышения безопасности, а следовательно, и надежности в объектно-ориентированных программах. Он применим к методам класса, параметрам функций и объектам класса. Если какой-то метод класса снабжен описателем const, это означает, что он не должен изменять никаких данных класса. Применение const к параметру метода означает, что этот конкретный параметр не может быть изменен внутри данного метода. Попытка изменить его приводит к ошибке на этапе компиляции. Наконец, если какой-то объект класса определен с описателем const, то это означает, что он должен пользоваться const-методами. Случай пользования обычным методом порождает ошибку. Например:
class A {
int j;
public:
A (int k=0) { j=k; }// Конструктор с параметром
int GetJ (int k) const // Метод не может изменять j
{
if (k>j) return j; // Он этого и не делает
else return k;
}
int GetJ (int k) // Метод может изменять j
{
if (k<j) return j;
else return j=k; // и он это делает
}
void change (A& al, const A& a2) // Параметр а1 можно изменять
{
al.j = a2.j;
a2.j = 5; // Ошибка; а2 нельзя изменять
}
void out ()
{
printf(" j =%d”,j);
}
};
void main()
{
A a, b(5); // Обычные объекты
const A c; // Const-объект
a.GetJ(1); // Будет вызвана int GetJ (int)
a.out(); // Выведет j= 1
c.GetJ(2); // Будет вызвана int GetJ(int) const
c.out(); // Ошибка, так как out () не const -метод
a.out(); // Выведет j= 1
a.change(a, b);
a.out(); // Выведет j=5
c.change(a, b); // Ошибка, так как change не const-метод
}
Еще один пример использования константных функций-членов класса. Кроме того, в нем показано описание функций вне тела класса.
// Example of a constant member function
class Date
{
public:
Date( int mn, int dy, int yr );
int getMonth() const; // A read-only function
void setMonth( int mn ); // A write function;
// cannot be const
private:
int month;
};
int Date::getMonth() const
{
return month; // Doesn't modify anything
}
void Date::setMonth( int mn )
{
month = mn; // Modifies data member
}
Тема 4.8. Наследование классов в Visual C++.
4.8.1. Вложенность классов.
Как мы видели, компонентами класса могут быть данные и методы.
Существует специально зарезервированное ключевое слово this, определяемое как указатель на объект, которому было послано обрабатываемое сообщение. В каком-то конкретном методе указатель this содержит адрес объекта, который инициировал этот метод. Это справедливо только для методов класса, не имеющих описатель static. При любом вызове метода класса указатель this передается как скрытый (hidden) параметр, то есть передается неявно. Он является локальной переменной внутри метода и неявно используется при обращении к данным и методам класса. Иногда его используют явно. Рассмотрим эти возможности на примере инкапсуляции в классе Node такой распространенной структуры данных, как узел линейного односвязного списка. Класс Node имеет в качестве элемента данных указатель next на объект своего собственного класса.
class Node {
char *data; // Информационная часть узла
Node *next; // Указатель на следующий узел
publiс:
Node (char *s)
{ // Конструктор
data = strcpy(new char['strlen(s)+l], s);
next = NULL;
}
void GetData()
{
puts (this->data);
}
void SetNextTo (Node& n)
{
n.next = this;
}
Node* GetNext()
{
return next;
}
~Node ()
{
puts("\nDeleting data");
delete data;
}
};
void main ()
{
// Использование класса Node
Node nl(“First"), n2( "Second"); nl.GetData();
n2.GetData(); n2.SetNextTo(nl); nl.GetNext()->GetData();
}
Вопрос: написать результаты работы.
Конструктор класса Node дважды вызывается в функции main при объявлении объектов n1 и n2. Каждый раз он запрашивает память из области heap и размещает там строку символов (содержимое узла списка), которая передастся конструктору в качестве параметра. Метод класса GetData выводит в поток stdout (стандартный выходной поток) поле data того объекта, которому передается сообщение. Здесь мы специально вставили выбор с помощью указателя (this->), чтобы проиллюстрировать то, что обычно делает компилятор. Его можно оставить, а можно и убрать. Любое обращение к data в любом методе класса Node компилятор расширяет до this->data. Оператор программы n1.GetData(); выведет строку "First", а оператор n2.GetData(); — строку "Second". Внутри конструктора мы обращаемся к переменной data, не указывая явно, какому объекту принадлежит это поле данных. Компилятор C++ сам расширяет это обращение до this->data. Метод SetNextTo получает в качестве параметра ссылку на объект класса Node. Поскольку этот метод принадлежит тому же классу, что и параметр, то внутренний компонент next объекта n прямо доступен в данном методе. Ему присваивается адрес объекта, который инициировал метол (n.next=this;). Адрес содержится в переменной this. Так как в нашем случае объекту n2 послано сообщение SetNextTo(n1), то указатель this содержит адрес объекта n2, и он присваивается полю next объекта nl. Получается, что объект nl имеет в качестве следующего узла списка объект n2, или можно сказать, что n2 зацеплен за nl.
Метод GetNext позволяет получить адрес следующего элемента списка, то есть поле next объекта, которому послано сообщение. В последней строке функции main он используется для того, чтобы добыть объект, стоящий в списке за объектом nl. Выражение n1.GetNext() имеет результатом адрес объекта n2, которому посылается сообщение GetData. Следовательно, результатом всего выражения будет вывод поля data объекта n2, то есть строка "Second".
Подумайте, можно ли заменить тело метода GetNext на return this->next;? ДА!
Теперь рассмотрим другие возможности вложения. Прежде всего, объекты какого-либо класса могут быть вложены в число данных другого класса. Другой возможный способ вложения — это когда описание какого-то класса вложено в описание другого класса. Рассмотрим сначала первый случай вложения:
// Класс One, объекты которого вложены в другой класс Two
class One {
int i1; //Private-данное класса One public:
One(int i) //Конструктор с параметром
{
il=i;
}
void p()// Вывод private-компонента il
{
printf ("\n i1=%d",i1);
}
};
class Two {
// Класс Two содержит объекты класса One
int i2; // Private-данниое класса Two One cl,dl; // Объекты другого класса
public: // Особенность конструктора
Two (int j, int k, int l) : cl(k), dl(l) // Инициализация в заголовке {
i2=j; // Присвоение в теле конструктора
}
// Методы вывода
void р()
{
printf ("\n i2=%d",i2);
}
void p1()
{
cl.p();
}
void p2()
{
dl.One::p();
}
};
//Использование объектов классов
void main()
{
One man(1);
Two men(2, 3, 4);
man.p();
men.p();
men.p1();
men.p2();
}
Анализируя этот пример, обратите внимание на конструктор класса Two. Он содержит в себе кроме собственного тела еще и список инициализации двух вложенных объектов: cl(k), dl(l) класса One. Инициализаторы внутренних объектов могут следовать в произвольном порядке, но они должны быть расположены в заголовке конструктора (за двоеточием и разделяться запятыми). Выполняются они до исполнения тела конструктора объемлющего класса. При выходе из области действия объекта внешнего класса деструкторы классов вложенных объектов также выполняются до деструктора внешнего класса. Отметьте, что оба класса в нашем примере имеют в своем составе метод с именем р(). Спецификатор One в теле метода р2() необязателен, но он помогает читающему текст программы. Компилятор распознает, какой из двух методов следует вызвать по принадлежности объекта к конкретному классу.
Значения переменных, которые будут выведены на экран, таковы:
i1=1, i2=2, i1=3, i1=4.
Отметим следующие различия:
• il=l — это значение внутренней переменной il объекта man класса One;
• i2=2 — это значение внутренней переменной i2 объекта men класса Two;
• i1=3 — это значение внутренней переменной i1 объекта сl класса One (объект cl, в свою очередь, вложен в класс Two и, следовательно, является внутренним данным объекта men класса Two);
• i1=4 — это значение внутренней переменной i1 объекта dl класса One, который также вложен в класс Two.
Описание класса или структуры является по сути описанием нового типа данных. При описании типа возможно сложное вложение классов, так же, как и для структур языка С. Например:
class А{ //Какай-то класс
public:
int i; // Его переменная
class В {
// Вложенное описание класса В
public:
int j; // Данное класса В
В (int i=0) { j=i; } // Конструктор вложенного класса
void p() // Метод вложенного класса
{
printf("\nj=%d",j);
}
} b , *pb ; // Объект и указатель вложенного класса
// Конструктор объемлющего класса // Инициализация объекта и указателя
A (int i1, int jj) { i=i1; b.j=jj; pb=&b; }
// Метод объемлющего класса
void show ()
{ printf("\ni=%d b. j=%d",i ,b. j) ;
}
};
void main()
{ //Иллюстрация использования
A a ( 1 , 2 ) ; // Объект объемлющего класса
A *pa=new A(3,4); // Указатель объемлющего класса
A::B bb(5); // Объект вложенного класса
А::В *pbb=new A::B(6); // Указатель вложенного класса
// Разнообразие обращений к методам
a.show();
pa->show();
a.b.p();
a.pb->p();
pa->b.p();
pa->pb->p();
bb.p();
pbb->p();
// Разнообразие присвоений
a.b.j =7; a.show();
pa->b.j =8; pa->show();
pa->pb->j=9; pa->show();
a.pb->j =10; a.show();
bb.j=11; bb.p();
}
Обратите внимание на то, как объявлены объекты вложенного класса В. Внутри класса А — это простое объявление, следующее сразу за объявлением класса В. Внутри main — это объявление вида А::В bb;. Полезно провести сравнение со случаем структур. Наличие объектов вложенного класса и указателей на них порождают множество вариантов доступа к методу вложенного класса. Так, оператор a.b.p(); обращается к методу void p() класса В, который вложен в класс А. При этом будет выведена строка j=2. Это же действие имеет другое словесное описание. Объекту B, являющемуся внутренним данным объекта A, послано сообщение в виде имени метода р(). Сам метод может быть идентифицирован как void А::В::р();, что кратко передает суть факта вложенности. Так как в классе А имеется указатель pb, который содержит адрес объекта B, то тот же самый метод можно вызвать с помощью указателя: a.pb->p();. Указатель ра содержит адрес вновь созданного объекта класса А. Вызов ра->b.р(); приведет к появлению на экране строки j=4. Два указателя используются при вызове pa->pb->p():. Здесь важно понимать, что указатель ра объявлен и действует внутри main, а указатель pb является public-данным класса А и только благодаря этому факту доступен внутри main.
Объект bb объявлен внутри main как объект класса В, вложенного в класс А. Прямой вызов все того же метода р() для объекта bb выглядит так: bb.p();. На экране появится строка j=5. Косвенный вызов pbb->p(): приведет к появлению строки j=6. Благодаря наличию указателей как на вложенный, так и на объемлющий классы способы доступа к полю данных вложенного класса также могут быть весьма разнообразными. Однако следует иметь в виду, что прямой доступ возможен только потому, что поле j расположено в секции public вложенного класса. Если бы это было не так, то указанное поле можно было бы модифицировать внутри main только с помощью методов вложенного класса В. В примере таких методов у класса В не имеется.