Pull to refresh

Объектно-ориентированное программирование на C++ в примерах

Level of difficultyEasy

Мир состоит из объектов и сам — объект. Исследуя Мир, принято объекты классифицировать по каким-либо признакам. Выделяют классы животных и растений, планет и звезд, атомов и молекул, машин и механизмов, и многие, многие другие. Эта идея положена в основу объектно-ориентированного программирования.

Объектно-ориентированное программирование(ООП) подразумевает описание информационной модели взаимодействия объектов, которые могут содержать данные и алгоритмы. Данные представляются как поля (свойства, атрибуты), а алгоритмы — как процедуры или функции (методы).

Многие из используемых языков программирования (C++, Java, Python, Ruby и т. д.) в большей или меньшей степени поддерживают объектно-ориентированное программирование. Наиболее полно концепция ООП представлена в языке C++. Поэтому на нем написаны все примеры, приведенные ниже.

Классы

Любой объект принадлежит определенному классу. С точки зрения языка C++ класс — это тип, объект — это переменная.

Описать класс на языке C++ можно так:

class ClassName

{
	public:

	…
	protected:
	…
	private:
	…
};

Например, класс обыкновенных дробей может быть описан так:

class CFract
{
  public:
    CFract(){};
    CFract(int, int);
    CFract(const CFract&);
    ~CFract(){};
    CFract& operator=(const CFract&);
    CFract& operator=(std::string);
    CFract operator*(const CFract&);
    CFract operator/(const CFract&);
    CFract operator+(const CFract&);    
    CFract operator-(const CFract&);    
    std::string GetStr();
  private:
    int num{0};
    int den{1};
  protected:
    int SetDen(int);
    void Swap(int*, int*);
    int Nod(int, int);
    void Reduce();
    
};

В секции public: объявляются свойства и методы доступные в самом классе, его потомках. Кроме того, можно обратиться к ним через объект класса, используя точечную запись: имяОбъекта.свойтво/метод. Объявление конструкторов, деструктора, переопределение операций должно располагаться в этой секции.В приведенном примере определены:

три конструктора:

CFract(){}; - конструктор без параметров;

CFract(int, int); - конструктор с двумя параметрами: целые числа;

CFract(const CFract&); - копирующий конструктор.

две операции присваивания(перегрузка операций - тема отдельной статьи):

CFract& operator=(const CFract&);

CFract& operator=(std::string);

деструктор:

~CFract(){};

методы-арифметические операции

CFract operator*(const CFract&);

CFract operator/(const CFract&);

CFract operator+(const CFract&);

CFract operator-(const CFract&);

Обратите внимание, что при объявлении этих методов указаны только типы параметров. Имена понадобятся в определении методов.

Секция private: содержит свойства и методы доступные только в определении методов этого класса. В этой секции объявлены два поля: num(числитель) и den(знаменатель)

Секция protected: содержит свойства и методы доступные в классе и его потомках. В примере здесь объявлены вспомогательные функции.

Определение методов возможна внутри описания класса или после. Первый вариант неудобен для дальнейшей разработки и сопровождения класса. Поэтому будем определять методы после определения всего класса.

Конструктор без параметров определен внутри класса. там не предусмотрено ни каких действий.

Конструктор с параметрами:

CFract::CFract(int sourceNum, int sourceDen)

{

	num = sourceNum;

	den = sourceDen;

}

Может случится, что будет передан знаменатель меньше или равный нулю. Эту неприятность нужно обработать. В случае если передано отрицательное число, то нужно поменять знак и у числителя и у знаменателя, если передан 0, то вывести сообщение об ошибке. Для этого в protected объявим метод int SetDen(int).

int
CFract::SetDen(int sourceDen)
{
  if(sourceDen > 0)
    return den = sourceDen;
  else{
    if(sourceDen == 0){
      std::cout<<"Недопустимый знаменатель."<<std::endl;
      exit(1);
    }
    else{
      num = -num;
      return den = -sourceDen;
    }    
  }
}

Тогда конструктор с параметрами примет вид:

CFract::CFract(int sourceNum, int sourceDen)
{
	num = sourceNum;
	SetDen(sourceDen);
}

Кроме метода проверки знаменателя нужен метод сокращения дроби Reduce. Добавим его объявление в секцию protected: , а определение выглядит так:

void 
CFract::Reduce()
{
  int tmp = Nod(num, den);
  num = num / tmp;
  SetDen(den / tmp);      
}

Для сокращения дроби нужно найти наибольший общий делитель числителя и знаменателя. Добавим метод Nod в секцию protected, а ее определение выглядит так:

int 
CFract::Nod(int sa, int sb)
{
  int n, d;
  d = sb;

  n = (sa<0)? -sa : sa;
  
  if(n < d) Swap(&n, &d);

  while(d){
    n = n - d;
    if(n < d) Swap(&n, &d);
  }

  return n;
}

Еще один метод, который необходим в дальнейшем. Он также используется в методе Nod - обмен значениями переменных Swap. Добавим его объявление в секцию protected и определим:

void 
CFract::Swap(int *a, int *b)
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}

Следующий важный метод - операция присваивания:

CFract& 
CFract::operator=(const CFract& source)
{
  if(this == &source) return *this;
      
      num = source.num;
      SetDen(source.den);
      return *this;
}

Ключевое слово operator указывает на то, что это не просто метод, а операция, которую в дальнейшем можно использовать в выражениях. Например:

...

CFract a(1,2);

CFract b;

b = a;

...

Хорошо бы перегрузить операцию присваивания в классе CFract, чтобы иметь возможность читать дробь из строки, то есть чтобы была допустима запись:

...

CFract a = "2/3"

...

Примерная реализация этого метода (не совсем очевидна; пожалуй это тема отдельной статьи):

CFract& 
CFract::operator=(std::string source)
{
  int a, b;          //числитель, знаменатель, счетчик
  std::string tmp;  //временная переменная для числа
  bool flA;         //флаг введен или нет числитель
  
  tmp = "";
  b = 1;
  flA = false;
  
  for(auto s: source){
    if((s >= '0') && (s <= '9')) tmp += s;
    if(s == '/') {
      //прехват исключения "неверный формат числа"
      try{
      a = std::stoi(tmp);
      }
      catch(...){
        std::cout << "Неверный формат числа.";
      }
      tmp = "";
      flA = true;      
    }    
  } 
  try{
  b = ((tmp != "") && (flA)) ? std::stoi(tmp) : 1;
  a = (!flA) ? std::stoi(tmp): 0;
  }
  catch(...){
    std::cout << "Неверный формат числа.";
  }
  num = a;
  SetDen(b);
  return *this;
  
}

Определение арифметических операций

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

CFract
CFract::operator+(const CFract& source)
{
  CFract tmp;
  int tmpNod, tmpDen;
  
  tmpNod = Nod(den, source.den);
  tmpDen = (den * source.den) / tmpNod;      
  tmp.num = num * tmpDen / this -> den +                   
              source.num * tmpDen / source.den;
  tmp.SetDen(tmpDen);      
  tmp.Reduce();
  return tmp;
}


Остальные операции определить гораздо проще. В приложении приведены определения этих операций.

И последнее в этой заметке: объявление и определение принято писать в различных файлах: fract1.h - объявление класса, fract1.cpp - определение методов. Вот эти файлы:

файл fract1.h

#ifndef _FRACT1_H
#define _FRACT1_H
#include <string>
class CFract
{
  public:
    CFract(){};
    CFract(int, int);
    CFract(const CFract& source);
    ~CFract(){};
    CFract& operator=(const CFract&);
    CFract& operator=(std::string);
    CFract operator*(const CFract&);
    CFract operator/(const CFract&);
    CFract operator+(const CFract&);    
    CFract operator-(const CFract&);    
    std::string GetStr();
  private:
    int num{0};
    int den{1};
  protected:
    int SetDen(int sourceDen);
    void Swap(int *a, int *b);
    int Nod(int sa, int sb);
    void Reduce();
    
};
#endif

файл fract1.cpp

#include <iostream>
#include <sstream>
#include "fract1.h"

CFract::CFract(int sourceNum, int sourceDen)
{
  num = sourceNum;
  SetDen(sourceDen);  
}

CFract::CFract(const CFract& source)
{
  num = source.num;
  SetDen(source.den);  
}

CFract& 
CFract::operator=(const CFract& source)
{
  if(this == &source) return *this;
      
      num = source.num;
      SetDen(source.den);
      return *this;
}
CFract& 
CFract::operator=(std::string source)
{
  int a, b;          //числитель, знаменатель, счетчик
  std::string tmp;  //временная переменная для числа
  bool flA;         //флаг введен или нет числитель
  
  tmp = "";
  b = 1;
  flA = false;
  
  for(auto s: source){
    if((s >= '0') && (s <= '9')) tmp += s;
    if(s == '/') {
      //возможна исключительная ситуация "неворный формат числа"
      try{
      a = std::stoi(tmp);
      }
      catch(...){
        std::cout << "Неверный формат числа.";
      }
      tmp = "";
      flA = true;      
    }    
  } 
  try{
  b = ((tmp != "") && (flA)) ? std::stoi(tmp) : 1;
  a = (!flA) ? std::stoi(tmp): 0;
  }
  catch(...){
    std::cout << "Неверный формат числа.";
  }
  num = a;
  SetDen(b);
  return *this;
  
}

CFract 
CFract::operator*(const CFract& source)
{
  CFract tmp;
 
  tmp.num = num * source.num;
  tmp.SetDen(den * source.den);  
  tmp.Reduce();
  
  return tmp;
}

CFract
CFract::operator/(const CFract& source)
{
  CFract tmp;
  
  tmp.num = num * source.den;         
  tmp.SetDen(den * source.num);           
  tmp.Reduce();
  
  return tmp;
        
}
CFract
CFract::operator+(const CFract& source)
{
  CFract tmp;
  int tmpNod, tmpDen;
  tmpNod = Nod(den, source.den);
  tmpDen = (den * source.den) / tmpNod;      
  tmp.num = num * tmpDen / this -> den +                   
              source.num * tmpDen / source.den;
  tmp.SetDen(tmpDen);      
  tmp.Reduce();
  return tmp;
}


CFract
CFract::operator-(const CFract& source)
{
  CFract tmp;
  int tmpNod, tmpDen;
  tmpNod = Nod(den, source.den);
  tmpDen = (den * source.den) / tmpNod;
        
  tmp.num = num * tmpDen / den -                   
              source.num * tmpDen / source.den;
              
  tmp.SetDen(tmpDen);      
  tmp.Reduce();  
  return tmp;
}


int
CFract::SetDen(int sourceDen)
{
  if(sourceDen > 0)
    return den = sourceDen;
  else{
    if(sourceDen == 0){
      std::cout<<"Недопустимый знаменатель."<<std::endl;
      exit(1);
    }
    else{
      num = -num;
      return den = -sourceDen;
    }
    
  }
}

std::string
CFract::GetStr()
{
  std::ostringstream s;
  s <<"{" << num << "/" << den<<"}";
  return s.str();
}

void 
CFract::Swap(int *a, int *b)
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}
int 
CFract::Nod(int sa, int sb)
{
  int n, d;
  d = sb;
  n = (sa<0)? -sa : sa;
  if(n < d) Swap(&n, &d);
  while(d){
    n = n - d;
    if(n < d) Swap(&n, &d);
  }
  return n;
}
void 
CFract::Reduce()
{
  int tmp = Nod(num, den);
  num = num / tmp;
  SetDen(den / tmp);      
}

//перегрузка метода вывода в поток "<<"
std::ostream& operator<<(std::ostream& out, CFract source)
{
  out << source.GetStr();
  return out;
}
int main()
{
    CFract a(-1, 2), b(-3, 4);
    CFract c;
    c = a + b;
    std::cout<<c<<std::endl;
    c = a - b;
    std::cout<<c<<std::endl;    
    c = a * b;
    std::cout<<c<<std::endl;
    c = a / b;
    std::cout<<c<<std::endl;
    c = "2/3";
    std::cout<<c<<std::endl;
    return 0;
}

Функция main нужна только для тестирования. Вообще ее следует определять в отдельном файле.

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.