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

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

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

14.2. Школьные дроби на базе классов

На первых порах развития языка C++ довольно часто прибегали к услугам структур для создания новых типов данных и процедур их обработки. Однако на современном этапе для создания классов обычно используют специальную конструкцию – class. На примере дробно-рациональных чисел попробуем разобраться в особенностях современного подхода. Ниже приводится текст файла rational.h, в котором содержится описание данных, конструкторов и некоторых методов для работы с дробями, а также прототипов всех используемых функций.

#ifndef __RATIONAL_H
#define __RATIONAL_H
//Класс Rational (У.Торп, У.Форд)
#include <iostream.h>
#include <stdlib.h>
//----------------------------------------------------------------
class Rational
{
private:
  long num,den;
//Закрытый конструктор, используется в арифметических операциях
  Rational(long num,long den);
//Функции-утилиты
  void Reduce(void);                //Метод – сокращение дроби
  long gcd(long m,long n);          //наибольший общий делитель
public:
  Rational(int num=0, int denum=1); //Конструктор int->Rational
  Rational(double x);               //Конструктор double->Rational
//ввод/вывод
  friend istream& operator>>(istream& t,Rational &r);
  friend ostream& operator<<(ostream& t,const Rational &r);
// бинарные арифметические операции
  Rational operator+(Rational r)const;
  Rational operator-(Rational r)const;
  Rational operator*(Rational r)const;
  Rational operator/(Rational r)const;
//унарный минус, изменение знака
  Rational operator-(void)const;
//операторы отношения
  int operator<(Rational r)const;
  int operator>(Rational r)const;
  int operator==(Rational r)const;
//Преобразование Rational->double
  operator double(void)const;
//Методы-утилиты
  long GetNum(void)const;
  long GetDen(void)const;
};	//конец объявления класса
#endif
14.1.

Тела остальных функций и методов полезно вынести в отдельный файл с именем rational.cpp, содержимое которого приводится ниже:

#include "rational.h"
//Определение наибольшего общего делителя
  long Rational::gcd(long x,long y)
{ if(y==0)return x; return gcd(y,x%y); }
//-------------------------------------------------------
//Сложение Rational+Rational
  Rational Rational::operator+(Rational r)const
{ Rational t;
  t.num=num*r.den+den*r.num; t.den=den*r.den;
  t.Reduce(); return t; }
//-------------------------------------------------------
//Вычитание Rational-Rational
  Rational Rational::operator-(Rational r)const
{ Rational t;
  t.num=num*r.den-den*r.num; t.den=den*r.den;
  t.Reduce(); return t; }
//--------------------------------------------------------
//Умножение Rational*Rational
  Rational Rational::operator*(Rational r)const
{ Rational t;
  t.num=num*r.num; t.den=den*r.den;  t.Reduce();
  return t; }
//-------------------------------------------------------
//Деление Rational/Rational
  Rational Rational::operator/(Rational r)const
{ Rational t=Rational(num*r.den,den*r.num);
  t.Reduce(); return t; }
//-------------------------------------------------------
//сравнение на ==
  int Rational::operator==(Rational r)const
{ return num*r.den==den*r.num; }
//-------------------------------------------------------
// сравнение на >
  int Rational::operator>(Rational r)const
{ return num*r.den>den*r.num; }
//-------------------------------------------------------
// сравнение на <
  int Rational::operator<(Rational r)const
{ return num*r.den<den*r.num; }
//--------------------------------------------------------
//унарный минус
  Rational Rational::operator-(void)const
{ return Rational(-num, den); }
//-------------------------------------------------------
//ввод в формате P/Q (дружественная функция)
  istream& operator>>(istream& t,Rational &r)
{ char c; //для чтения разделителя /
  t>>r.num>>c>>r.den;
  if(r.den==0) { cerr<<"Denominator=0!"; exit(1); }
  r.Reduce();  return t; }
//-------------------------------------------------------
// вывод в формате P/Q (дружественная функция)
  ostream& operator<<(ostream& t,const Rational &r)
{ t<<r.num<<'/'<<r.den; return t; }
//-------------------------------------------------------
//конструктор Rational(p,q)
  Rational:: Rational(long p,long q):num(p),den(q)
{ if(den==0) { cerr<<"Denominator=0!"; exit(1); } }
//-------------------------------------------------------
//конструктор Rational(p,q)
  Rational:: Rational(int p,int q):num(p),den(q)
{ if(den==0) { cerr<<"Denominator=0!"; exit(1); } }
//-------------------------------------------------------
//конструктор double->Rational
  Rational:: Rational(double x)
{ double val1,val2;
  val1=100000000L*x;   val2=10000000L*x;
  num=long(val1-val2); den=90000000L;
  Reduce();  }
//------------------------------------------------------
//преобразование Rational->double
  Rational::operator double(void)const
{ return double(num)/den; }
//------------------------------------------------------
//Метод – сокращение дроби
  void Rational::Reduce(void)
{ long bigdiv,temp;
  temp=(num<0)?-num:num;
  if(num==0)den=1;
  else
    { bigdiv=gcd(temp,den);
      if(bigdiv>1)
        { num /= bigdiv; den /= bigdiv; }
    }
}
//---------------------------------------------------------
//Метод – извлечение числителя
  long Rational::GetNum(void)const
{ return num; }
//---------------------------------------------------------
//Метод – извлечение знаменателя
  long Rational::GetDen(void)const
{ return den; }
//---------------------------------------------------------
14.2.

Объявление класса начинается со служебного слова class, вслед за которым указывается имя класса. Затем в фигурных скобках следует описание класса. Присутствующие в нем служебные слова private и public предшествуют данным и функциям, объявляемым как личные (приватные) и общедоступные компоненты класса. К личным компонентам класса имеют доступ только функции, описанные в классе (так называемые члены- функции), а также функции и классы, причисленные к друзьям класса (их описания сопровождаются добавкой friend). В описании класса может присутствовать несколько групп, выделенных как личные и общедоступные, порядок их роли не играет. Но если в самом начале описания класса объявлены члены-данные и члены-функции без указания права собственности, то они считаются приватными.

В нашем примере члены-данные объявлены приватными, поэтому в программе, использующей данный класс, нельзя воспользоваться следующим оператором:

Rational A;
A.num=1;  A.den=3;

Это означает, что прямой доступ к членам-данным для внешних пользователей запрещен. Объявленный объект можно проинициализировать либо с помощью соответствующего конструктора, либо прибегнуть к специальным функциям или методам типа SetNum и SetDen (правда, в приведенном тексте эти функции отсутствуют, мы ограничились только методами GetNum и GetDen ). Такая мера предосторожности позволяет проконтролировать, не нарушил ли программист диапазон данных, предусмотренный для тех или иных полей.

За пределы описания класса вынесены описания его членов-функций. Но для того, чтобы подчеркнуть их принадлежность к классу, перед именем функции расположено специальное указание – Rational::. Обратите внимание на то, что функции-друзья таким свойством не обладают. Однако им разрешен доступ к приватным компонентам класса.

В описании членов-функций класса Rational присутствуют обычные функции и методы. Основное отличие метода от функции заключается в способе обращения:

k = fun(obj1,obj2);	//вызов обычной функции с аргументами obj1 и obj2
k = obj1.met(obj2);	//вызов метода с аналогичным набором параметров

Говорят, что метод применяется к объекту и "знает" все его характеристики, поэтому количество параметров в методе на один меньше. Вообще говоря, эта информация об объекте поступает в метод в качестве неявного параметра – указателя this (от англ. – этот). В приведенном примере сокращение дроби осуществляется методом Reduce и соответствующее обращение с объектом тип Rational выглядит так: t.Reduce(). А в реализации класса с использованием структуры обращение к этой же процедуре имело вид: Reduce(t).

Среди членов-функций класса Rational довольно много функций, повторяющих ранее приводившиеся тексты программ. Обратим внимание лишь на некоторые особенности новой реализации. Во-первых, сами дроби представляются и на вводе, и на выводе в формате num/den. Во-вторых, в новой версии допускается работа с отрицательными дробями. Наконец, представляет интерес оригинальный алгоритм преобразования данных типа double в формат Rational.

Ниже приводится пример программы, использующей новый тип данных. Обращаем ваше внимание на то, что описание класса не создает никаких объектов. Объекты типа Rational появляются только в результате их объявления с использованием тех или иных конструкторов. Каждому объекту выделяется соответствующий участок оперативной памяти для хранения данных, а вот функции и методы, обслуживающие дробно- рациональные данные, не дублируются. Единственная их копия обслуживает все созданные объекты.

#include <iostream.h>
#include <conio.h>
#include "rational.cpp"
void main()
{ Rational r1(5),r2,r3;
  double d;
  d=r1.GetNum();
  cout<<"d="<<d<<endl; 
  cout<<"1.Rational - value 5 is "<<r1<<endl;
  cout<<"2.Input Rational number: ";
  cin>>r1;
  d=double(r1);
  cout<<"Equivalent of double: "<<d<<endl;
  cout<<"3.Input two Rational number: ";
  cin>>r1>>r2;
  cout<<"Results: "<<endl;
  cout<<r1<<" + "<<r2<<"="<<(r1+r2)<<endl;
  cout<<r1<<" - "<<r2<<"="<<(r1-r2)<<endl;
  cout<<r1<<" * "<<r2<<"="<<(r1*r2)<<endl;
  cout<<r1<<" / "<<r2<<"="<<(r1/r2)<<endl;
  if(r1<r2)
    cout<<"Relation < : " <<r1<<"<"<<r2<<endl;
  if(r1==r2)
    cout<<"Relation = : " <<r1<<"="<<r2<<endl;
  if(r1>r2)
    cout<<"Relation > : " <<r1<<" > "<<r2<<endl;
  cout<<"4.Input double number: ";
  cin>>d;
  r1=d;
  cout<<"Convert to Rational: "<<r1<<endl;
  d=r1;
  cout<<"Convert to double: "<<d<<endl;
  getch();
}
//=== Результат работы ===

< Лекция 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

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

 

 

 

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