Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кетков.doc
Скачиваний:
17
Добавлен:
27.09.2019
Размер:
2.22 Mб
Скачать

15.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 denom=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

Тела остальных функций и методов полезно вынести в отдельный файл с именем 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; }

//---------------------------------------------------------

Объявление класса начинается со служебного слова 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();

}

//=== Результат работы ===