Консультация № 183535
07.06.2011, 12:32
47.24 руб.
0 22 1
Здравствуйте! У меня возникли сложности с таким вопросом:

Опять я со своими баранами. Вот код проекта: исходники
Перегруженные операции для класса все работают, но работают по одиночке, если проводить несколько подряд операций - программа вылетает без сообщений об ошибке.
Код:
#include "baseset.h"

int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
BaseSet bset(arr, 9);
BaseSet bset2(bset);

bset.Print();
bset2.Print();

//Проверка операции деления
BaseSet bset3 = bset/bset2;
bset3.Print();

//Проверка операции вычитания
bset3 = bset - bset2;
bset3.Print();

//До сюда всё работает. Дальше - фиг
//Проверка операции умножения
bset3 = bset - bset2;
bset3.Print();

//Проверка операции сложения
// bset3 = bset + bset2;
// bset3.Print();

return 0;
}

Помогите разобраться...

Обсуждение

Неизвестный
07.06.2011, 12:47
общий
Адресаты:
У вас неправильно работает конструктор копирования если other является пустым множеством.
давно
Мастер-Эксперт
425
4118
07.06.2011, 12:53
общий
Я поправлю. Но там проблема возникает, когда множество гарантированно непусто.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
давно
Мастер-Эксперт
425
4118
07.06.2011, 12:57
общий
Кстати, а почему "неправильно"? Если множество пустое, то цикл копирования данных просто не запускается.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
07.06.2011, 13:01
общий
Адресаты:
Потому что в сконструированном объекте будут неинициализированные данные, то бишь случайный мусор.
Неизвестный
07.06.2011, 13:03
общий
это ответ
Здравствуйте, sir Henry!
Ошибка в конструкторе BaseSet(int *pIntArray, unsigned int len)
Вы не клонируете данные, а просто присваиваете указатель на данные. Которые, кстати, являются автоматической переменной. Т.е. данные не выделяются динамически. Соответственно, деструктор при попытке удалить их вылетает.
Код:
#include "baseset.h"

int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
BaseSet bset(arr, 9);
BaseSet bset2(bset);

bset.Print();
bset2.Print();

//Проверка операции деления
BaseSet bset3 = bset/bset2;
bset3.Print();


//Проверка операции вычитания
bset3 = bset - bset2;
bset3.Print();

//Проверка операции умножения
bset3 = bset - bset2;
bset3.Print();

//Проверка операции сложения
// bset3 = bset + bset2;
// bset3.Print();

return 0;
}

Код:
//baseset.h
//базовый класс - Множество
#ifndef BASESET_H
#define DASESET_H

#include <stdlib.h>
#include <iostream>

using namespace std;

//Класс - Множество

class BaseSet
{
protected:
int* Array; //Массив хранения значений множества
unsigned int Count; //Количество хранящихся в массиве значений

//Удаление массива хранения

void Destroy()
{
if (Array)
{
delete [] Array;
Array = NULL;
Count = 0;
}
}

//Копирование значений из другого массива

void Copy(int *pIntArray, unsigned int len)
{
if (pIntArray)
{
Destroy();
Array = new int[len];
for (size_t i = 0; i < len; ++i)
{
Array[i] = pIntArray[i];
}
Count = len;
}
}
public:

//Конструкторы. Без параметров

BaseSet() : Array(0), Count(0)
{
}

//С созданием пустого множества
//Параметры:
//len - количество ячеек хранения

BaseSet(unsigned int len) : Array(0), Count(len)
{
Array = new int[Count];
for (unsigned int i = 0; i < Count; i++)
{
Array[i] = 0;
}
}

//С передачей указателя на целочисленный массив
//Параметры:
//*pIntArray - указатель на массив
//len - количество элементов в массиве

BaseSet(int *pIntArray, unsigned int len) : Array(0), Count(0)
{
if (pIntArray != NULL)
{
Copy(pIntArray, len);
}
else
{
cout << "Множество не создано. Видимо это пустой массив..." << endl;
}
}

//Конструктор копирования
//Параметры:
//Other - другой объект, из которого надо скопировать значения

BaseSet(const BaseSet& Other) : Array(0), Count(0)
{
Copy(Other.Array, Other.GetCount());
}

//Деструктор

~BaseSet()
{
Destroy();
}

//Получить количество ячеек хранения

unsigned int GetCount() const
{
return Count;
}

//Вывод значений множества на экран по горизонтали

void Print()
{
for (unsigned int i = 0; i < Count; i++)
{
cout << Array[i] << " ";
}
cout << endl;
}

//Перегрузка операций
//Индексация

int &operator[] (unsigned int Index) const
{
if (Index >= (Count))
{
cout << "Индекс " << Index << " больше, чем количество данных..." << endl;
exit(1);
}
return Array[Index];
}

//Присваивание

BaseSet & operator=(const BaseSet& Other)
{
if (this != &Other)
{
Copy(Other.Array, Other.Count);
}
return *this;
}

//Сложение

BaseSet operator+(const BaseSet& Other) const
{
unsigned int cnt = min(Count, Other.Count);
BaseSet result(cnt);
for (unsigned int i = 0; i < cnt; i++)
{
result.Array[i] = Array[i] + Other.Array[i];
}
return result;
}

//Вычитание

BaseSet operator-(const BaseSet& Other) const
{
unsigned int cnt = min(Count, Other.Count);
BaseSet result(cnt);
for (unsigned int i = 0; i < cnt; i++)
{
result.Array[i] = Array[i] - Other.Array[i];
}
return result;
}

//Умножение

BaseSet operator*(const BaseSet& Other) const
{
unsigned int cnt = min(Count, Other.Count);
BaseSet result(cnt);
for (unsigned int i = 0; i < cnt; i++)
{
result.Array[i] = Array[i] * Other.Array[i];
}
return result;
}

//Деление

BaseSet operator/(const BaseSet& Other) const
{
unsigned int cnt = min(Count, Other.Count);
BaseSet result(cnt);
div_t x;
for (unsigned int i = 0; i < cnt; i++)
{
x = div(Array[i], Other.Array[i]);
result.Array[i] = x.quot;
}
return result;
}
};
#endif
5
Как всегда - отлично!
Неизвестный
07.06.2011, 13:45
общий
Адресаты:
Цитата: 321399
Потому что в сконструированном объекте будут неинициализированные данные, то бишь случайный мусор.
Это к заданному вопросу отношения, как бы, не имеет. Т.к. в программе нигде пустое множество и не передается. Но можно изменить Copy() чтоб генерировала исключение при нулевом указателе.
давно
Мастер-Эксперт
425
4118
07.06.2011, 13:45
общий
Ок, я понял.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
давно
Мастер-Эксперт
425
4118
07.06.2011, 13:50
общий
07.06.2011, 13:51
Ошибка в конструкторе BaseSet(int *pIntArray, unsigned int len)

Вы что-нибудь ещё поправляли в моём коде, кроме конструктора копирования? Дело в том, что Ваш код, если его скопировать один-в-один, работает нормально. А вот если я перенесу Ваш конструктор копирования в свой код, то программа всё равно продолжает вылетать. А больше я отличий не вижу. Глаз замылился...
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
07.06.2011, 13:59
общий
Адресаты:
Поправлял чисто мелочи. Думаю Вы ошиблись конструктором. BaseSet(int *pIntArray, unsigned int len) - это не конструктор копирования.
давно
Мастер-Эксперт
425
4118
07.06.2011, 14:06
общий
07.06.2011, 14:08
Да, простите, ошибся. Просто "конструктор копирования" у меня долго в мозгах сидел. Ну будем искать...
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
07.06.2011, 15:52
общий
Это к заданному вопросу отношения, как бы, не имеет. Т.к. в программе нигде пустое множество и не передается. Но можно изменить Copy() чтоб генерировала исключение при нулевом указателе.

Я не запускал программу, это просто первая ошибка, которую я заметил.
давно
Мастер-Эксперт
425
4118
07.06.2011, 16:56
общий
Вопрос не совсем в тему, нро к программе он относится, клянусь своей треуголкой.
Есть в С++ какая-нибудь стандартная (или не совсем стандартная) библиотека для обработки общераспространённых исключительных ситуаций, таких как деление на ноль, как это есть в Дельфи и в Билдере?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
07.06.2011, 17:44
общий
07.06.2011, 17:45
Адресаты:
По умолчанию исключения для операций с плавающей точкой замаскированы на уровне сопроцессора. В каждой ОС есть свои средства для размаскирования и перехвата этих исключений.

Если же надо сделать все просто то можно после выполнения вычисления проверить результат функцией finite().

Код:
#include <iostream>
#include <cmath>
#include <iomanip>

using namespace std;

int main()
{
double a = 12.0 / 1;
cout<<"a="<<left<<setw(3)<<a<<right<<setw(15)<<"finite(a)="<<boolalpha<<(bool)finite(a)<<endl;

double b = 12.0 / 0;
cout<<"b="<<left<<setw(3)<<b<<right<<setw(15)<<"finite(b)="<<boolalpha<<(bool)finite(b)<<endl;

return 0;
}


Код:
a=12      finite(a)=true
b=inf finite(b)=false
Неизвестный
07.06.2011, 17:47
общий
Адресаты:
Есть стандартный класс std::exception. От него наследуются все исключения стандартной библиотеки C++. Пользователь может выбирать, наследовать свои исключения от std::exception, либо создать свой класс для обработки ошибок.
Если вас интересует конкретно деление на ноль, то в C++ нет стандартных средств для его обработки. При необходимости нужно использовать платформо-зависимые средства.
Неизвестный
08.06.2011, 09:38
общий
08.06.2011, 10:19
Если уж проверять, то знаменатель перед делением, а не результат после.
Работоспособность кода с проверкой finite зависит от текущего состояния сопроцессора, да и деление на ноль не только в сопроцессоре случается.
Неизвестный
08.06.2011, 10:28
общий
08.06.2011, 10:29
Нет никакой разницы, что проверять.

Впрочем, есть. Т.к. корректность результата зависит не только от знаменателя. Есть еще и underflow/overflow, например, кроме деления на ноль. Так, что проверка результата предпочтительнее.

Что касается обработки исключений, то можно вообще ничего не проверять, а использовать сигнал SIGFPE в *nix, или seh в win с размаскированием прерываний от сопроцессора.

Впрочем, для этого класса, на мой взгляд, это и не нужно. Пусть корректностью данных занимается код использующий его.
давно
Мастер-Эксперт
425
4118
08.06.2011, 11:20
общий

Мне вот только одно непонятно. Неужели можно вот так вот просто разделить на ноль, а потом ещё и результат проверить? В дельфи проблема возникает сразу же, в момент проведения операции, ну т.е., когда программа получает сигнал от процессора о делении на 0. А тут - дели себе на здоровье и если результат не проверить, то никто и ничего не заметит, пока ракета, вместо полёта на Меркурий, не улетит в Вашингтон.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
08.06.2011, 12:22
общий
Адресаты:
Я тут все говорил про проверку результата действительных операций и только теперь вспомнил, что речь то идет о целых числах(в программе).

Целочисленное деление типа int y=5/0 вызовет прерывание сразу.

Что же касается Borland, то это вопрос реализации. В C# тоже поднимется исключение.
Неизвестный
08.06.2011, 13:30
общий
Адресаты:
При целочисленных расчётах, которые выполняются центральным процессором, деление на ноль однозначно приводит к исключению. Если вы его не обработаете, то ОС принудительно завершит ваш процесс.

При расчётах с плавающей точкой возможны варианты. Сопроцессор - достаточно гибкое устройство с кучей флагов, влияющих на режим проведения расчётов. При делении на ноль в зависимости от флагов либо генерируется исключение аналогично ЦП, либо в результат записывается специальное значение - бесконечность. Последнее может быть удобно в некоторых приложениях. Корректность обращения с бесконечностями конечно целиком лежит на совести программиста.
Неизвестный
08.06.2011, 23:34
общий
Адресаты:
Кстати, вспомнил вот.

На самом деле никто не мешает нам сделать обработку исключений как в Delphi/C#. Надо только вспомнить, что в этих языках элементарные типы вовсе не элементарные(как в C/C++), а представляют собой классы, имеющие общего предка. Соответственно, для них определены операторы с обработкой ошибок. Цена этому - снижение производительности. Мы могли бы сделать так же. Но, код написанный на C/C++ всегда славился своим быстродействием. Цена этому - надо ручками прописывать контроль ошибок.

Каждому своё.
Неизвестный
09.06.2011, 11:51
общий
Насколько я помню, в Delphi типы с плавающей точкой вполне элементарные.
Я думаю Runtime конфигурирует сопроцессор на генерацию исключения при делении на ноль, далее при выполнении программы исключение ловится с помощью SEH, и уже SEH-обработчик генерирует соответствующее дельфийское исключение.
Проблема в том, что не на всех аппаратных платформах сопроцессор умеет генерировать исключения. В таком случае остаются только проверки с соответствующей просадкой производительности. Поэтому в C++ не стали включать средств для ловли деления на ноль.
Неизвестный
09.06.2011, 12:40
общий
09.06.2011, 13:22
Да, действительно, в Delphi это элементарные типы. В C# - struct. Впрочем можно исключение ловить и самим. Как то так в win:
Код:
#include <iostream>
#include <eh.h>
#include <float.h>

using namespace std;

class my_seh_exception:public exception
{
public:
my_seh_exception()
:exception("SEH Exception!!!")
{

}
};


void handler(unsigned int code, struct _EXCEPTION_POINTERS* ep)
{
_clearfp();
throw my_seh_exception();
}

class test
{
public:
test()
{
cout<<"constructor test"<<endl;
}
~test()
{
cout<<"destructor test"<<endl;
}
};

int main()
{
_set_se_translator(handler);
#pragma warning(disable:4996)
_controlfp(FPE_ZERODIVIDE,_MCW_EM);

try
{
test t;
double x=0;
double iy=10.0/x;
cout<<iy<<endl;
}
catch(exception& e)
{
cerr<<e.what()<<endl;
}

system("pause");

return 0;
}
Форма ответа