Попробуйте часть кода до слова 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; }; //конец объявления класса #endif14.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(); } //=== Результат работы ===