Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиПростой

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

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

Многие из используемых языков программирования (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 нужна только для тестирования. Вообще ее следует определять в отдельном файле.

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.