- •Раздел 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.11.1. Перегрузка операций.
Суть этой уникальной возможности, предоставляемой языком C++, состоит в том, что программист может переопределить смысл любой из более 40 разрешенных операций для объектов данного класса. Сделав это однажды, мы как бы наделяем язык свойствами, которыми он раньше не обладал. Например, переопределив операции +,-,*,/ в классе комплексных чисел, мы как бы наделяем язык C++ способностью оперировать комплексными числами по соответствующим правилам. Конечно, этого же можно добиться и традиционным способом, например, создав четыре функции: cadd, csubtr, cmult, cdiv. Однако вызов этих функций будет выглядеть намного менее естественным, чем просто запись вида z=zl+z2; или z=zl/z2; в предположении, что z, zl и z2 были объявлены объектами класса комплексных чисел. Чтобы переопределить операцию, необходимо особым образом определить ее либо как метод класса, либо как friend-функцию. Существует возможность переопределить в классе любую операцию за исключением следующих шести: . . * :: ?: # ##. Функция, переопределяющая операцию, не может изменить количество аргументов операции. Например, унарная операция не может стать бинарной, и наоборот. Невозможно также изменить существующий приоритет и правила ассоциации, действующие при нормальном использовании операции. Рассмотрим выражение х + у, где х, у являются объектами какого-либо класса. Предположим, что в этом классе одним из двух возможных способов переопределена операция +. Тогда выражение х + у может быть интерпретировано компилятором также двумя способами:
х.operator + (у); //либо как
operator + (х.у);
Первая интерпретация означает, что объекту х посылается сообщение operator* (у). То есть вызывается функция (член класса) с именем operator+(), при этом в качестве единственного параметра передается объект у. Вторая означает вызов внешней функции с именем operator+(), которой передаются два параметра х и у. В соответствии с этим существуют два способа переопределения бинарных операций: public-методом с одним аргументом и friend-функцией с двумя аргументами. Сходные рассуждения приводят к выводу, что унарную операцию можно переопределить либо public-методом класса без параметров, либо friend-функцией с одним параметром.
Следует обратить внимание на следующие особенности:
• следует отдельно переопределять префиксную и постфиксную модификации унарных операций ++ и - -, чтобы иметь возможность их различать;
• если типы операндов бинарной операции различаются, и первым идет объект другого типа, то переопределить операцию можно с помощью friend-функции с двумя параметрами.
Например, в выражении вида c+z, где с — число типа double, a z — объект класса комплексных чисел, операция + может быть переопределена только friend-функцией с двумя параметрами. В этом можно убедиться, анализируя способ интерпретации компилятором выражения z+48. Если операция + реализована как метод класса, то он будет z.operator+(48);, что вполне осмысленно, но при этом выражение 48+z; должно интерпретироваться как 48.operator+(z);. Однако это не имеет смысла, так как 48 не является и не может быть (в C++) объектом класса, определенного пользователем.
Необходимо также иметь в виду и следующие ограничения:
• операции =,(),[],-> могут быть переопределены только с помощью метода класса;
• описатель static для метода класса, переопределяющего операцию, недопустим.
Рассмотрим, как используется возможность переопределения операции на примере реализации абстрактного типа данных вектор на плоскости. Каждый вектор (объект класса Vector) имеет две координаты, задающие положение вектора на плоскости. Будем хранить эти координаты в двух private-компонентах класса xl, х2. Совместим с этим типом данных следующие операции: присвоения, сложения, вычитания, скалярного произведения, проверки равенства, отношения «больше», а также вычисления нормы. С этой целью осуществим переопределение операций (соответственно: =, +, -, *, ==, >, !) в классе Vector. Ясно, что только одна операция взятия нормы является унарной. Реализуем ее в виде метода класса без параметров. Операции =, ==, >, + , - тоже можно реализовать в виде методов класса с одним параметром. С целью иллюстрации технологии выполним переопределение операции умножения на скаляр (слева) с помощью внешней friend-функции:
class Vector {
double xl, x2; // Координаты вектора
public:
// Три конструктора
Vector (double cl, double c2) // С параметрами
{ xl=cl; x2=c2;}
Vector () // Default
{ xl = x2 = 0.; }
Vector (const Vector& v) // Сору
(xl = v.xl; x2 = v.x2;}
//= = = = = = Переопределение операций =====//
// Присвоение
Vector operator=(Vector& v)
{
if (this==&v) return *this; xl=v.xl;
x2=v.x2; return *this;
}
// Операция >
bool operator>(Vectors v)
{return !*this > !v;}
// Проверка равенства
bool operator==(Vector& g)
{ return xl==g.xl && x2==g.x2; }
// Сложение
Vector operator+(Vector& g)
{ return Vector (xl+g.xl, x2+g.x2); }
Vector operator-(Vectors g) //Вычитание {return Vector(xl-g.xl, x2-g.x2);}
Vector operator*(double f) //Умножение на скаляр {return Vector(f*xl, f*x2);}
double operator*(Vector& g) // Скалярное произведение return (xl*g.xl + x2*g.x2):
double operator! () // Вычисление нормы {return sqrt (xl*xl+x2*x2);}
//====== Внешняя friend-функция ======//
friend Vector operator*(double, Vector&);
Vector operator*(double f, Vectors g) { // Умножение на скаляр return Vector(f*g.xl, f*g.x2);
}
void main()
{
Vector a(3,4), b(0,4), c=a, vv[5];
puts ("\n Class Vector Demonstration");
c=vv[0]=b; //Цепочка присвоений
printf ("\n Norm of c=vv[0]=b:\t %6.2f",!c);
c=5.*a;
printf ("\n Norm of 5.*a:\t\t %6.2f",!c):
c=b*!a*5.;
printf ("\n Norm of c=b*!a*5.:\t %6.2f",!c);
c=a+b;
printf ("\n Norm of a+b:\t\t %6.2f",!c);
printf ("\n Norm of a-b:\t\t %6.2f",!(a-b));
printf ("\n\n Scalar product (a.b) = %3.0f",a*b);
if (b>a)
puts("\n\n Norm of b is greater than that of a");
if (a==b) ;
else
puts("\n\n Vector a is not equal to vector b");
a=b;
if (a==b)
puts("\n After assignment a=b; we have a==b");
vv[3]=a;
printf ("\n Norm of vv[3]=a =%6.2f",!vv[3]);
}
При анализе этой программы следует обратить внимание на следующие моменты. Класс Vector имеет три конструктора. Конструктор с двумя параметрами позволяет создать вектор с желаемыми компонентами, конструктор без параметров позволяет определить массив объектов класса, конструктор копирования позволяет корректно производить инициализацию объектов класса Vector другими объектами своего же класса.
Реализация public-метода, переопределяющего бинарную операцию присвоения, использует указатель this. Так как this содержит адрес объекта, пославшего сообщение (operator=), то значением, возвращаемым оператором return *this;, будет объект, стоящий слева от знака присвоения. Поэтому становится возможной цепочка присвоений c=vv[0]=a;. Метод класса, переопределяющий операцию !, возвращает вещественное число, равное сумме квадратов компонентов вектора, то есть выбранную нами норму вектора. Здесь унарная операция переопределяется с помощью public-метода без аргументов. Результат проверки равенства двух векторов (операция ==) имеет тип boo!. Параметры всех friend-функций есть ссылки на объекты класса Vector. Это позволяет сэкономить память стека, так как при передаче ссылкой копии параметров в системном стеке не делаются. Методы, переопределяющие операции + и -, возвращают объекты класса Vector. Операция * переопределена трижды:
• методом, возвращающим double (скалярное произведение двух векторов);
• методом, возвращающим Vector (произведение вектора на скаляр);
• внешней функцией, возвращающей Vector (произведение скаляра на вектор).
Чтобы переопределять операции ++ и - - с учетом префиксной и постфиксной модификаций, допустим, что эти операции определены для вектора на плоскости и означают увеличение или уменьшение модуля вектора на единицу при неизменной его ориентации. Очевидно, формулы пересчета координат вектора должны быть:
xl=(|x|+l)*xl/|x|. х2=(|х|+1)*х2/|х|.
где | х | — модуль вектора. Любую унарную операцию можно переопределить либо с помощью метода класса без параметров, либо с помощью friend-функции с одним параметром. В Visual C++ принято отличать постфиксную модификацию от префиксной путем описания лишнего фиктивного, неиспользуемого формального параметра типа int в заголовке метода или функции. Например, объявления:
Vector Vector:: operator++(int);
friend Vector operator++(Vector& v, int);
определяют постфиксную модификацию при переопределении операции ++ методом класса или внешней функцией. Если реализация тела префиксной операции ++ очевидна, то постфиксная модификация требует пояснений. Возвращаемое функцией значение вектора должно быть старым, а содержимое объекта, пославшего сообщение, следует изменить на новое, вычисленное в соответствии с приведенными формулами. Таким образом, описание класса Vector можно дополнить двумя методами:
Vector operator++() //Префиксная + +
{
dn=(d+l.)/d;
x2 *= dn;
double d=!*this, xl *= dn;
return *this;
}
Vector operator++(int) //Постфиксная ++
{
Vector temp=*this; // Запоминаем старый вектор double d=!*this, dn=(d+l. )/d;
xl *= dn; x2 *= dn: // Новые значения компонентов return temp ; // Результат выражения — старый }
Обратите внимание: d=!*this; вычисляет норму вектора, пославшего сообщение. Здесь * означает разадресацию, ! означает уже переопределенную операцию взятия нормы. Для проверки функционирования вновь введенных операций в конец функции main можно добавить строки:
а=++b ;
printf ("\n Norm of a=++b:\t\t %6.2f",!a);
b=a++ ;:
printf ("\n Norm of b=a++:\t\t %6.2f",!b);
printf ("\n After a++ the norm of a:%6.2f", !a) ;
Если постфиксная модификация работает корректно, то последнее выведенное число должно быть на единицу больше, чем предпоследнее. Операция -- может быть переопределена аналогичным образом. Класс Vector выглядит далеко не полным. Разумно было бы дополнить его методами вычисления угла между двумя векторами, вычисления векторного произведения и другими. Не хватает также методов для ввода и вывода компонентов вектора.