Попробуйте часть кода до слова main заменить на #include "stdafx.h" //1 #include <iostream> //2 using namespace std; //3 |
Классы. Создание новых типов данных
14.3. Класс на базе объединения
Не так уж и часто для создания класса используют объединения. Один из таких редких примеров описан в книге Г. Шилдта "Теория и практика C++". СПб.: БХВ-Петербург, 2000, – 416 с. Он относится к узко специализированному классу, который выводит в двоичном виде все байты вещественного числа типа double.
#include <iostream.h> #include <conio.h> union bits { double d; //первое данное unsigned char c[sizeof(double)]; //совмещаемый массив bits(double n); //конструктор инициализации void show_bits(); //отображение байтов d }; bits::bits(double n) {d=n;} void bits::show_bits() { int i,j; for(j=sizeof(double)-1; j>=0; j--) { cout<<"Byte "<<j<<": "; for(i=128; i; i>>=1) if(i & c[j]) cout << "1"; else cout<< "0"; cout<<"\n"; } } void main() { bits qq(2006.0203); qq.show_bits(); getch(); } //=== Результат работы === Byte 7: 01000000 Byte 6: 10011111 Byte 5: 01011000 Byte 4: 00010100 Byte 3: 11001001 Byte 2: 10000101 Byte 1: 11110000 Byte 0: 01101111
На что в этом примере можно обратить внимание. Во-первых, на организацию цикла по i. Начальное значение управляющей переменной в этом цикле представлено единицей в восьмом разряде. При очередном повторении цикла эта единица сдвигается вправо на один бит и используется для проверки соответствующего разряда в очередном байте массива c. Во-вторых, в приведенном классе отсутствуют указания о правах доступа. Для объединения это означает, что все члены класса являются общедоступными (в частности, это одно из немногих отличий между классами, созданными с использованием union, от настоящих классов, создаваемых с помощью class ). Классы, создаваемые на базе объединений, имеют ряд ограничений. В них, например, не могут использоваться статические и виртуальные функции.
14.4. Новые типы данных на базе перечисления
Еще реже для создания новых типов данных в языке C++ используют перечисления. Один из таких примеров приведен в книге В.Лаптева. Он связан с "наведением порядка" в перечислении Decade, где для обозначения цифр от 0 до 9 использованы их английские эквиваленты. Новый порядок разрешает пользователю складывать элементы этого "множества" по модулю 10, производить сложения элементов перечисления с целыми числами (по тому же модулю 10), выводить значения переменных типа Decade словами. Ниже приводится полный текст файла decade.h, содержащего описание класса и тексты всех его внешних функций:
#ifndef _DECADE_H #define _DECADE_H //Объявление нового типа данных enum Decade {zero,one,two,three,four,five,six,seven,eight,nine}; //Переопределение операции + Decade operator+(const Decade &a, const Decade &b) { return Decade((int(a)+int(b))%10); } //Переопределение операции += Decade operator+=(Decade &a, const Decade &b) { return a=Decade(int(a+b) % 10); } //Переопределение операции ++a Decade operator++(Decade &a) { a=Decade((int(a)+1) % 10); return a; } //Переопределение операции a++ Decade operator++( Decade &a,int) { Decade t=a; a=Decade((int(a)+1) % 10); return Decade(t); } //Переопределение операции << ostream& operator<<(ostream &out, const Decade &a) { char *s[]={"zero","one","two","three","four","five","six","seven", "eight","nine"}; out<<s[a]; return out; } //Переопределение операции Decade+int Decade operator+(const Decade &a, const int b) { int t= ((int)a + b) % 10; return Decade(t); } //Переопределение операции int+Decade Decade operator+(const int a, const Decade &b) { return Decade((a+int(b)) % 10); } #endif
Программа тестирования новой декады и результаты ее работы приведены ниже:
#include <iostream.h> #include <conio.h> #include "decade.h" void main() { Decade A=seven,B=six; cout<<"A="<<A<<endl; //Результат A=seven cout<<"B="<<B<<endl; //Результат B=six cout<<"A+B="<<A+B<<endl; //Результат A+B=three A += B; cout<<"A="<<A<<endl; //Результат A=three ++A; cout<<"A="<<A<<endl; //Результат A=four A++; cout<<"A="<<A<<endl; //Результат A=five B=A+6; cout<<"B="<<B<<endl; //Результат B=one A=3+B; cout<<"A="<<A<<endl; //Результат A=four getch(); }
Приведенный пример, конечно, носит только учебный характер. Как такового полноценного класса с объединением данных и обрабатывающих функций в единый блок здесь нет. Собственно и синтаксис перечисления не позволяет включить в фигурные скобки ничего кроме списка символьных констант. Но этот пример демонстрирует возможность "обучения" компилятора процедурам обработки новых данных, которые выглядят в программе достаточно привычно для человека.
14.5. Встраиваемые функции
Встраиваемые ( inline ) функции – это очень короткие функции, реализуемые небольшим числом машинных команд. К ним невыгодно обращаться с использованием стандартного механизма, требующего обязательной засылки передаваемых аргументов в стек, извлечения данных из стека, засылки возвращаемого результата в стандартный регистр и т.п. Гораздо проще на место вызова inline -функции вставить и настроить само тело функции. Это намного эффективнее, особенно в тех случаях, когда работа функции сводится к нескольким машинным командам. Такая техника компиляции напоминает процедуру макроподстановки в ассемблере или процесс обработки препроцессором директив #define.
Типичными примерами встраиваемых функций являются процедуры определения абсолютной величины ( abs(x) ), выбора максимального или минимального значения из двух аргументов и т.п. Иногда, с целью оптимизации узких мест в программе, полезно попросить компилятор применить технику встраивания к наиболее часто вызываемым функциям. Прямым указанием о том, что функция должна быть встраиваемой, является использование служебного слова inline в заголовке функции:
inline int even(int x) { return !(x%2); } inline double min(double a,double b) { return a < b ? a : b; }
К числу встраиваемых функций относятся и функции-члены класса, тела которых описаны в разделе объявления класса, хотя они могут и не содержать спецификатора inline. Обычно в описание класса включают конструкторы и деструкторы. Для встраиваемых функций-членов, описание которых вынесено за пределы объявление класса, добавление служебного слова inline обязательно:
class sample { int i,j; //приватные данные public: sample(int x,int y):i(x),j(y){} //встраиваемый конструктор int is_divisor(); //описание функции вынесено }; inline int sample::is_divisor() //вынесенная встроенная функция { return !(i%j); }
Использование встраиваемых функций в некоторых случаях позволяет избежать трудно воспринимаемые фокусы, которые происходят при макроподстановке. Рассмотрим, например, процедуру возведения числа в куб. Ее можно оформить как макроопределение:
#define Cube(x) (x)*(x)*(x)
Казалось бы все нормально с точки зрения математики. Должно работать для числовых данных любого типа. Предусмотрели скобки, которые снимают проблемы при подстановке формул вместо аргумента x. Однако попробуйте вставить в программу следующие строки:
q=4; cout << Cube(q++);
Макроподстановка заменит вторую строку на:
cout << (q++)*(q++)*(q++);
В соответствии с правилами выполнения инкрементных операций такое произведение в результате даст 5*6*7. Какой же это куб?
Если же оформить эту процедуру как встроенную функцию, то никакие выкрутасы в смысле языка C на правильность результата не повлияют:
inline int Cube(int x) { return x*x*x; }
Если бы понадобилось написать более универсальную функцию, обрабатывающую числовые аргументы любого типа, то можно было бы использовать следующий шаблон:
inline template <class T> T Cube(T x) { return x*x*x; }
Результат использования такого шаблона приведен ниже:
#include <iostream.h> #include <conio.h> inline template <class T> T Cube(T x) { return x*x*x; } void main() { int x=2; float y=3; double z=4; cout<<Cube(x)<<endl; cout<<Cube(y)<<endl; cout<<Cube(z)<<endl; getch(); } //=== Результат работы === 8 27 64