Опубликован: 25.11.2008 | Уровень: специалист | Доступ: свободно | ВУЗ: Нижегородский государственный университет им. Н.И.Лобачевского
Лекция 15:

Классы. Создание новых типов данных

< Лекция 14 || Лекция 15: 12345 || Лекция 16 >

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
< Лекция 14 || Лекция 15: 12345 || Лекция 16 >
Alexey Ku
Alexey Ku

Попробуйте часть кода до слова main заменить на 

#include "stdafx.h" //1

#include <iostream> //2
#include <conio.h>

using namespace std; //3

Александр Талеев
Александр Талеев

#include <iostream.h>
#include <conio.h>
int main(void)
{
int a,b,max;
cout << "a=5";
cin >> a;
cout <<"b=3";
cin >> b;
if(a>b) max=a;
else max=b;
cout <<" max="<<max;
getch();
return 0;
}

при запуске в visual express выдает ошибки 

Ошибка    1    error C1083: Не удается открыть файл включение: iostream.h: No such file or directory    c:\users\саня\documents\visual studio 2012\projects\проект3\проект3\исходный код.cpp    1    1    Проект3

    2    IntelliSense: не удается открыть источник файл "iostream.h"    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    1    1    Проект3

    3    IntelliSense: идентификатор "cout" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    6    1    Проект3

    4    IntelliSense: идентификатор "cin" не определен    c:\Users\Саня\Documents\Visual Studio 2012\Projects\Проект3\Проект3\Исходный код.cpp    7    1    Проект3

при создании файла я выбрал пустой проект. Может нужно было выбрать консольное приложение?

 

 

 

Даниил Варов
Даниил Варов
Австралия, Комбоддж