• 15 апреля стартует «Курс «SQL-injection Master» ©» от команды The Codeby

    За 3 месяца вы пройдете путь от начальных навыков работы с SQL-запросами к базам данных до продвинутых техник. Научитесь находить уязвимости связанные с базами данных, и внедрять произвольный SQL-код в уязвимые приложения.

    На последнюю неделю приходится экзамен, где нужно будет показать свои навыки, взломав ряд уязвимых учебных сайтов, и добыть флаги. Успешно сдавшие экзамен получат сертификат.

    Запись на курс до 25 апреля. Получить промодоступ ...

Абстрактные Класс

  • Автор темы Vadik(R)
  • Дата начала
V

Vadik(R)

Здравствуйте, дорогие форумчане.
Скажу сразу, задание помогаю делать знакомой, и обычно проблем не возникало никаких с её заданиями, но в этот раз.. Я от задания сам стал находится в небольшом шоке.. В общим, хороший такой пробел у меня по абстрактным классам, меня в универе по ним не гоняли ((
Собственно, проблема такая (выложу пока её часть, по мере решения буду код ещё давать).
Итак, задание. В него ещё вникал некоторое время:
Реализации всех классов, а также основная функция программы, должны быть разнесены по различным файлам исходного текста и использовать соответствующие заголовочные файлы.

В следующих заданиях требуется реализовать абстрактный базовый класс, определив в нем чистые виртуальные функции. Эти функции определяются в производных классах. В базовых классах должны быть объявлены чистые виртуальные функции ввода/вывода, которые реализуются в производных классах.
Вызывающая программа должна продемонстрировать все варианты вызова виртуальных функций с помощью указателей на базовый класс. Написать функцию вывода, получающую параметры базового класса по ссылке и демонстрирующую виртуальный вызов.

Создать абстрактный базовый класс Pair с виртуальными арифметическими операциями. Создать производные классы Money (задание 1.24) и Fraction (задание 1.35).
Задание 1.35
Создать класс Fraction для работы с дробными числами. Число должно быть представлено двумя полями: целая часть - длинное целое со знаком, дробная часть - беззнаковое короткое целое. Реализовать арифметические операции сложения, вычитания, умножения и операции сравнения.
Во-первых, для меня было уже откровением, что есть чисто абстрактные методы.
Немного погуглив, приступил к реализации.
Вот, что пока вышло.
Код pair.h:
C++:
class Pair
{
public:
Pair();
~Pair();
virtual void read() = 0;
virtual void write() = 0;
virtual Pair& operator=(Pair &other) = 0;
virtual Pair& operator+(Pair &other) = 0;
};
Код pair.cpp:
C++:
#include "pair.h"

Pair::Pair()
{

}

Pair::~Pair()
{

}
Код fraction.h:
C++:
#include "pair.h"

class Fraction : public Pair
{
public:
Fraction();
Fraction(Fraction &other);
Fraction(long numerator, long denominator);
~Fraction();
void read();
void write();
virtual Pair& operator=(Pair &other);
virtual Pair& operator+(Pair &other);

protected:
virtual Pair& operator=(Fraction &other);
virtual Pair& operator+(Fraction &other);

private:
unsigned short denominator;
long numerator;
};
Код fraction.cpp:
C++:
#include <iostream>
#include "fraction.h"

using namespace std;

long gcd(long a, long b)
{
return b ? gcd(b, a % b) : a;
}

void normalize(long &numerator, long &denominator)
{
long n;
if (denominator < 0)
{
numerator = -numerator;
denominator = -denominator;
}
if (numerator == 0 || denominator == 0)
{
denominator = 1;
}
n = gcd(abs(numerator), abs(denominator));
numerator /= n;
denominator /= n;
}

Fraction::Fraction()
{
numerator = 0;
denominator = 1;
}

Fraction::Fraction(Fraction &other)
{
this->numerator = other.numerator;
this->denominator = other.denominator;
}

Fraction::Fraction(long numerator, long denominator)
{
normalize(numerator, denominator);
this->numerator = numerator;
this->denominator = denominator;
}

Fraction::~Fraction()
{

}

void Fraction::read()
{
cout << "Введите числитель: ";
cin >> numerator;
cout << "Введите знаменатель: ";
cin >> denominator;
}

void Fraction::write()
{
cout << numerator << " / " << denominator;
}

Pair& Fraction::operator=(Pair &other)
{
cout << "operator=Pair" << endl;
*this = *(Fraction*)&other;
return *this;
}

Pair& Fraction::operator+(Pair &other)
{
cout << "operator+Pair" << endl;
//return other + *this; - it doesn't work Oo
return *(Fraction*)&other + *this;
}

Pair& Fraction::operator=(Fraction &other)
{
cout << "operator=Fraction" << endl;
this->numerator = other.numerator;
this->denominator = other.denominator;
return *this;
}

Pair& Fraction::operator+(Fraction &other)
{
cout << "operator+Fraction" << endl;
//return Fraction(10, 8);
return *(new Fraction(this->numerator * other.denominator + other.numerator * this->denominator, this->denominator * other.denominator));
}
Код main.cpp:
C++:
#include <cmath>
#include <iostream>
#include <locale>
#include "fraction.h"

using namespace std;

int main()
{
Pair* a;
Pair* b;
Pair* c;
cout.setf(ios::fixed);
cout.precision(2);
setlocale(LC_ALL, "Rus");
a = new Fraction();
a->read();
b = new Fraction();
b->read();
c = new Fraction();
*c = *a + *b;
cout << "c = ";
c->write();
cout << endl;

delete a;
delete b;
delete c;
return 0;
}

Теперь мои комментарии к коду и вопросы. То, что write - это метод, а не оператор, << пишущий в ostream - опустим, примут, не сложно переделать.
Насчёт использования
C++:
virtual Pair& operator=(Pair &other);
virtual Pair& operator=(Fraction &other);
вообще не уверен, просто спустя большое количество просиженных часов, и перепробовав методом перебора кучу способов так, чтобы заработало, как-то сами собой эти строчки у меня получились. Возможно они и не нужны, жду подсказок от гуру, как нужно делать такие задания :)
Далее, собственно вопросы.
Наткнулся на такой полезный учебник, который и читал. А именно, вот:
Сделал всё по аналогии, но почему-то у меня не работает участок кода, как у них:
C++:
Pair& Fraction::operator+(Pair &other)
{
cout << "operator+Pair" << endl;
//return other + *this; - it doesn't work Oo
return *(Fraction*) &other + *this;
}
Он не подставляет дальше всё (закоменченную строчку) в
C++:
Pair& Fraction::operator+(Fraction &other)
Почему - не в курсе, но он рекурсивно вызывает сам себя и вылетает (для того cout и поставил :))
Это первый такой, основной вопрос. Я его решил приведением типа (но это же не профессионально, да?)
Тогда встал второй вопрос.
C++:
Pair& Fraction::operator+(Fraction &other)
{
cout << "operator+Fraction" << endl;
//return Fraction(10, 8);
return *(new Fraction(this->numerator * other.denominator + other.numerator * this->denominator, this->denominator * other.denominator));
}
Если я создаю просто объект, то он не всегда корректно потом выводит значения. Было return Fraction(5, 6);, c->write() в коде нормально работал (т. е. всегда писал 5 / 6). А вот на дробь 10 / 8 он почему-то стал выводить совсем другие значения.
return *new Fraction....
В общем, возврат значение указателя - в этом случае сложение происходит корректно. Но есть утечка памяти, что есть очень плохо. Проверяется:
C++:
for (unsigned int i = 0; i < 10000000; i++)
{
*c = *a + *b;
}
//для паузы
a->read();
Вот в таком случае явно видно, что занятая оперативная память около 400 Мб.

В общем, любые комментарии, помощь - приветствуются. Мне это важно не только, чтобы знакомой сделать лабу, а чисто для себя.
В олимпиадном программировании и собственном быдлокодинге абстрактные классы никак мне не пригодились, а вот при устройстве на работу, чувствую, вполне возможны вопросы по абстрактным классам на собеседовании. Заранее всем спасибо :)

P.S. Для удобства добавил сам проект :)Посмотреть вложение test.rar
 
R

rrrFer

Ну я поясню.
- говносайт, ворующий контент с других сайтов. Ты можешь загуглить первую строку статьи по своей ссылки и убедиться. Но это видно и невооруженным взглядом (на номральных программерских сайтах подстветка кода работает, не работает только на говносайтах типа кодебая {АДМИН, ОБРАТИ ВНИМАНИЕ}). С моего блога тоже воруют статьи, поэтому я негодую, когда ссылаются на воров.

То что описано в статье - нрмальными людьми называется двойной диспетчеризацией и к твоей задачи отношения не имеет. Там должен быть где-то ранг класса (чтобы программа на падала в твоей рекурсии).

Но есть утечка памяти, что есть очень плохо.
Потому что оператор не должен использовать new. Никогда. А у тебя использует.

Добавлено: Почитай на хабре большую статью про операторы- там есть примеры. Вот по ним и решай свою задачу.
 
V

Vadik(R)

Потому что оператор не должен использовать new. Никогда. А у тебя использует.
Добавил вывод сообщений в конструкторах и деструкторах, и у меня получается так, что если я делаю без new, например, просто даже return Fraction(10, 8); то происходит следующее:
1. Конструктор Pair
2. Конструктор Fraction
3. Деструктор Fraction
4. Деструктор Pair
И только потом уже происходит попытка присвоения *c некой "пустоты" (деструктор же всё уничтожил).

Спасибо, стараюсь дальше решить проблему. Мне бы хотя бы для оператора+ разобраться, дальше по аналогии уже пойму как делать :)
 
R

rrrFer

Напиши как ты возвращаешь результат - если по значению - то да - перед возвратом создается временный объект, который уничтожается при выходе из функции (деструктор вызывается, все законно)



посмотри примеры.
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!