|
Попробуйте часть кода до слова main заменить на #include "stdafx.h" //1 #include <iostream> //2 using namespace std; //3 |
Классы. Создание новых типов данных
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();
}
//=== Результат работы ===
