C++ Builder
| Главная | Уроки | Статьи | FAQ | Форум | Downloads | Литература | Ссылки | RXLib | Диски |

стр.: (2) < [1] 2 >
Разбивка строки на элементы, вероятно, есть ошибка при работе с памятью
Dimon.Ru
Отправлено: 23.08.2005, 15:58


Станционный диспетчер

Группа: Участник
Сообщений: 92



Доброго времени суток!
опишу свою проблему:
есть у меня функция, которая преобразует строку в массив чисел (строка содержит числа через разделитель — ';').
есть подозрения, что в этой функции что-то не так.
хотя работает она всегда правильно, но иногда после ее работы я вижу Access Violation...
убираю использование функции из программы — ошибка исчезает.

возможно, я где-то напортачил с указателями, но где — не могу понять...
AnsiString string — строка вида "1,2;34;563,896;0;-52;" и т.д.
double* massif — массив чисел, который получается на выходе. например для вышепреведенной строки это будет double mas[5];


CODE
void Razbivka(AnsiString string, double* massif)
{
 AnsiString temp;
 char **result;
 int kolvo, leng, str_len, s,d,a,x,i;

str_len = string.Length();

for(int i=0; i<str_len; i++) if(string.c_str()[i] == '.') string.c_str()[i] = ',';

if (str_len == 0) return;
kolvo = 0; leng=0; x=0;

//Определяем количество искомых подстрок в строке и максимальную длину одной подстроки
for (s=1; s<str_len+1; s++)
 {
  if (string[s] == ';') //Если разделитель заранее известен и не может меняться
   {
    kolvo++;
    if (x > leng) leng = x;
    x = 0;
   }
  else x++;
 }
if (string[str_len] != ';')  //Если строка не заканчивается символом-разделителем
 {
  kolvo++;
  if (x > leng) leng = x;
 }

//Выделяем память
result = new char *[kolvo];
for (d=0; d<kolvo; d++)
  result[d] = new char [leng];

//Разбиваем строку
a=1; //Здесь — нач. позиция нужного идентификатора в строке
x=0; //Здесь — количество символов в идентификаторе
d=0; //Здесь — счетчик переменных
str_len = string.Length();
for (s=1; s<str_len+1; s++)
 {
  if (string[s] != ';') x++;
  else
   {
    temp = string.SubString(a, x);
    strcpy(result[d],temp.c_str());
    a=a+x+1; x=0; d++;
   }
 }
if (string[str_len] != ';')  //Если строка не заканчивается символом-разделителем, вычленим еще одну, завершающую подстроку
 {
  temp = string.SubString(a, x);
  strcpy(result[d],temp.c_str());
 }
for (d=0; d<kolvo; d++)
{
  massif[d]=StrToFloat(result[d]);;
}
for (d=0; d<kolvo; d++)
  delete result[d];
delete result;
str_len = 0;
return;
}
gvg
Отправлено: 23.08.2005, 16:29


Машинист паровоза

Группа: Участник
Сообщений: 222



Мда, реализация не простая для чтения... А в massif количество элементов где определяется? Вообще, все это переделать надо бы, используя имеющиеся методы AnsiString (напр. Pos). Куда проще выйдет.
Gedeon
Отправлено: 23.08.2005, 17:21


Ветеран

Группа: Модератор
Сообщений: 1742



CodeGuard хорошая весчь.
Doga
Отправлено: 23.08.2005, 19:08


Мастер участка

Группа: Участник
Сообщений: 575



Не парьтесь smile.gif


*HPP

CODE

#include <system.hpp>
#include <Classes.hpp>
//---------------------------------------------------------------------------
typedef struct {
                double *Values;
                bool   *Errors;
                int    Count;
              } RESULTSTRUCT;
//---------------------------------------------------------------------------
RESULTSTRUCT * __fastcall StringToValues(AnsiString ValuesString, AnsiString Separator = ";");
//---------------------------------------------------------------------------


*.CPP

CODE

//---------------------------------------------------------------------------
RESULTSTRUCT * __fastcall StringToValues(AnsiString ValuesString, AnsiString Separator)
{
 RESULTSTRUCT *Result = NULL;

 if ((ValuesString != "") && (Separator != ""))
 {
   ValuesString = StringReplace(ValuesString, Separator, "\n", TReplaceFlags()<<rfReplaceAll);

   TStringList *ValuesList = new TStringList();
   ValuesList->Clear();
   ValuesList->Text = ValuesString;

   for (int k = 0; k < ValuesList->Count; k++)
   {
     if (ValuesList->Strings[k] == "")
     {
       ValuesList->Delete(k);
       k--;
     }
   }

   if (ValuesList->Count > 0)
   {
     Result = new RESULTSTRUCT;
     Result->Count  = ValuesList->Count;
     Result->Values = new double[Result->Count];
     Result->Errors = new bool[Result->Count];
   
     for (int k = 0; k < ValuesList->Count; k++)
     {
       try
       {
         Result->Values[k] = ValuesList->Strings[k].ToDouble();
         Result->Errors[k] = false;
       }
       catch (EConvertError &E)
       {
         Result->Errors[k] = true;
       }
     }
   }  

   delete ValuesList;
 }
 
 return Result;
}
//---------------------------------------------------------------------------


Using

CODE

RESULTSTRUCT *ValuesStruct = StringToValues(String)
if (ValuesStruct)
{
 for (int k = 0; k < ValuesStruct->Count; k++)
 {
   if (!ValuesStruct->Errors[k])
   {
     //Можно спрашивать ValuesStruct->Values[k]
   }
   else
   {
     //ОШИБКА
   }
 }
}
else
{
 //ОШИБКА
}

//Или
RESULTSTRUCT *ValuesStruct = StringToValues(String, " ")
//...
Admin
Отправлено: 23.08.2005, 21:00


Владимир

Группа: Администратор
Сообщений: 1190



Как-то вы все усложнили, буду завтра днем на работе,
постараюсь найти время ответить, все это делается
намного проще, там у AnsiString и соответствующие
методы по работе с разделителями насколько я помню есть.
Георгий
Отправлено: 23.08.2005, 21:22


Почетный железнодорожник

Группа: Модератор
Сообщений: 874



голову за такой код отрывать надо.

когда то считалось что достаточно написать программу что бы она работала... сейчас это уже не так — программу надо писать так, что бы она была легко модернизируемой.

PS. всё что выше написано о первом коде в этой ветке.

Отредактировано Георгий — 24/08/2005, 08:31
Doga
Отправлено: 23.08.2005, 23:02


Мастер участка

Группа: Участник
Сообщений: 575



2Георгий

Всегда приятно слышать добрые слова в свой адрес biggrin.gif

По крайней мере Вы признаёте, что этот код работает. smile.gif

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

Никто не запрещает ему переписать всё это в ввиде, ну например:

double *StringToValues(int &Count, AnsiString ValuesString, AnsiString Separator);

А ипользование в коде TStringList и StringReplace не напрягает ни сам процессор ни его озу. Тем более меня. А таких компов, которых это напрягает, в природе уже не почти не существует smile.gif

Если Вы уж решили наводить критику, то, будьте так любезны, поподробнее о том что Вам не нравится. Не совсем понятно, причём здесь легкость модернизации?

Отредактировано Doga — 23/08/2005, 23:03
Rius
Отправлено: 24.08.2005, 07:01


Мастер участка

Группа: Участник
Сообщений: 321



Вот похожая функция, для получения элементов строки вида "1.2.5.870":
CODE
bool __fastcall TPlugin::ExtractFileVersions(AnsiString Vers, WORD * Major, WORD * Minor, WORD * Release, WORD * Build)
{
try
{
       int i1, i2;
       AnsiString str = Vers;
       i1 = 1;

       i2 = str.AnsiPos(".");
       *Major = str.SubString(i1, i2-i1).ToInt();
       str.Delete(i1, i2-i1+1);

       i2 = str.AnsiPos(".");
       *Minor = str.SubString(i1, i2-i1).ToInt();
       str.Delete(i1, i2-i1+1);

       i2 = str.AnsiPos(".");
       *Release = str.SubString(i1, i2-i1).ToInt();
       str.Delete(i1, i2-i1+1);

//        i2 = str.AnsiPos(".");
//        i3 = str.Length();
       *Build = str.ToInt();
//        i2 = str.SubString(i2+1, i3-i2-1);
}
catch(...)
{
//        Application->MessageBox(AnsiString("Ошибка при считывании версии файла " + Vers).c_str(), "Внимание",MB_ICONEXCLAMATION|MB_OK);
       return false;
}
return true;
}
Георгий
Отправлено: 24.08.2005, 07:41


Почетный железнодорожник

Группа: Модератор
Сообщений: 874



QUOTE (Doga @ 24/08/2005, 00:02)
2Георгий
Если Вы уж решили наводить критику, то, будьте так любезны, поподробнее о том что Вам не нравится. Не совсем понятно, причём здесь легкость модернизации?

я о dimon`e говорил

твой код несколько лучше, чем Dimon`а, но всё равно содержит жуткие вещи:

возврат массива в виде double* или массива структур как у тебя.
почему бы не использовать банальный std::vector или std::vector и описать RESULTSTRUCT не как 2 синхронных массива, а как
CODE
typedef struct {
double Values;
bool Errors;
} RESULTSTRUCT;

так проще? проще. проблем с освобождением/выделением памяти нет? нет. с выходом за индексы тоже проблем нет? нет.

осталось твою функцию подправить, что бы утечек памяти не было в случае возникновения исключений.
CODE
auto_ptr<TStringList>ValuesList(new TStringList());


в результате получаем код, в который кто угодно, какие угодно изменения внесёт и программы не вылетит по AE и не будет память течь.

Отредактировано Георгий — 24/08/2005, 08:46
olegenty
Отправлено: 24.08.2005, 08:08


Ветеран

Группа: Модератор
Сообщений: 2412



у auto_ptr есть только один недостаток — его содержимое хрен куда отдашь как параметр (нативного типа).
gvg
Отправлено: 24.08.2005, 08:36


Машинист паровоза

Группа: Участник
Сообщений: 222



А вот еще одна функция для разбивки строк:

//---------------------------------------------------------------------------
// если mode — убираем повторяющиеся разделители
void __fastcall ParseStr ( TStrArr &sa, AnsiString Value, char sep, bool mode )
{
char *s;
AnsiString str;

Value = Value.Trim ();
s = Value.c_str ();
sa.Clear ();

for ( int i = 0; s[i]; i++ )
{
if ( mode && s[ i + 1 ] && s[i] == sep && s[ i + 1 ] == sep )
continue;
if ( s[i] == sep )
{
sa.Add ( str );
str = "";
}
else
{
str += s[i];
}
}

if ( !str.IsEmpty ())
sa.Add ( str );
}
//---------------------------------------------------------------------------

Только заменить TStrArr на TStringList. Здесь sep — символ разделителя, при mode = true игнорируются пустые строки между разделителями.
Использую лет 5 уже как...
Admin
Отправлено: 24.08.2005, 09:31


Владимир

Группа: Администратор
Сообщений: 1190



QUOTE

  AnsiString string — строка вида "1.2;34;563.896;0;-52;" и т.д.
  double* massif — массив чисел, который получается на выходе.
  например для вышепреведенной строки это будет double mas[5];


Ну вот, добрался до работы...
Во первых, где-то есть функция такой разбивки
у AnsiString, название не помню ищите в help-e

---

Вот так еще проще(может и есть какие ошибки проверяйте сами):

CODE


using namespace std;
#include <vector>

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  vector <double> massif;
  AnsiString s = Edit1->Text; // "1.2;34;563.896;0;-52;"

  while(int ld = s.Pos(";")){
     massif.push_back(s.SubString(1, ld-1).ToDouble());
     s.Delete(1, ld);
     }

  for(int i=0; i<massif.size(); i++) ShowMessage(massif[i]);

  massif.clear(); // очищаем массив, освобождаем память когда стал не нужен.
}
//---------------------------------------------------------------------------


Doga
Отправлено: 24.08.2005, 13:33


Мастер участка

Группа: Участник
Сообщений: 575



2Георгий
QUOTE

но всё равно содержит жуткие вещи


Вы просто не знаете для чего использовалась эта функция. smile.gif

В структуре было ещё 2 поля — список подстрок и список типов подстрок, т.е. double, hex и др. Были ещё и аналогичные функции для конвертации в остальные типы. А массивы должны были использоваться в дальнейшем именно в том виде как они объявлены в структуре. Все данные для использования результирующих массивов имеются, так что только младенец может допустить утечку памяти.

А вместо std::vector предпочитаю использовать TList с соответствующими типизированными оболочками.

Георгий
Отправлено: 24.08.2005, 20:18


Почетный железнодорожник

Группа: Модератор
Сообщений: 874



всё равно — синхронные массивы это зло вселенского масштаба.

код надо писать вот такой, как Владимир привёл — коротко, ясно, ошибиться просто негде. единственно у него обработка завершается после считывания последней ";", но это исправляетя добавлением ещё 2 функциональных строчек
CODE
if(s[s.Length()]!=";")s+=";";
и всё — получена функция в 10 строчек! сравните это с исходной функцией, в которой одних только циклов 5 штук!!!

Отредактировано Георгий — 24/08/2005, 21:27
olegenty
Отправлено: 25.08.2005, 14:51


Ветеран

Группа: Модератор
Сообщений: 2412



QUOTE (Георгий @ 24/08/2005, 21:18)
код надо писать вот такой, как Владимир привёл — коротко, ясно, ошибиться просто негде. единственно у него обработка завершается после считывания последней ";", но это исправляетя добавлением ещё 2 функциональных строчек
CODE
if(s[s.Length()]!=";")s+=";";

2 Георгий
угу, только в реальной жизни ещё убедиться, что не IsEmpty(), иначе приведённый тобой код по идее приводит к AV.
Admin
Отправлено: 25.08.2005, 15:29


Владимир

Группа: Администратор
Сообщений: 1190



Ну в реальной жизни еще надо выполнять проверку
на то что в строке, иначе и в преобразовании .ToDouble();
получим AV EConvertError.

P.S. Ну ведь самое обидное — есть такая "родная" функция в Builder
видел ее, даже раз пользовался, но не могу найти !


Отредактировано Admin — 25/08/2005, 15:32
avc*
Отправлено: 25.08.2005, 15:35


Не зарегистрирован







IsEmpty мало. Нужна функция типа AnsiString::ToDoubleDef(double df_value);
Admin
Отправлено: 25.08.2005, 15:38


Владимир

Группа: Администратор
Сообщений: 1190



to avc:
не успели на 6 минут smile.gif
avc*
Отправлено: 25.08.2005, 17:05


Не зарегистрирован







smile.gif Ага. опоздал. INet второй день глючит, то "нэма" то "нэту". Провайдер что-то перенастраивает.
Admin
Отправлено: 25.08.2005, 18:59


Владимир

Группа: Администратор
Сообщений: 1190



QUOTE
Нужна функция типа AnsiString::ToDoubleDef(double df_value);


Такой функции нету. Есть аналогичная StrToFloatDef()
а что искал так и не нашел, нашел похожую — ExtractStrings(),
но у той было еще более длинное название.

Тогда можно написать и так:

CODE

//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
  double errValue = -1000000; // ошибка при преобразовании
  AnsiString s = "195.2;11;154.254;-52;10;-544;1451;28";

  TStringList *SL = new TStringList;
  TSysCharSet Delimiters;  Delimiters<<';';

  int n = ExtractStrings(Delimiters, Delimiters, s.c_str(), SL);
 if(n){
   double *massif = new double[n];
   for(int i=0; i<n; i++) {
      massif[i]= StrToFloatDef(SL->Strings[i], errValue);
      ShowMessage(massif[i]);
      }
   delete []massif;
   }
   delete SL;
}
//---------------------------------------------------------------------------
Doga
Отправлено: 26.08.2005, 12:51


Мастер участка

Группа: Участник
Сообщений: 575



А если значение какой либо подстроки случайно совпвдет с errValue?

Тогда уж лучше его не добавлять в массив... Или всё таки использовать флаги ошибок smile.gif

Отредактировано Doga — 26/08/2005, 12:51
AVC
Отправлено: 26.08.2005, 13:03


Ветеран

Группа: Модератор
Сообщений: 1583



QUOTE

А если значение какой либо подстроки случайно совпвдет с errValue?

Это не возможно, так как errValue это значение "по умолчанию" и используется если строка не переводима в число. Другой вопрос, нужны ли DF значения заказчику? Но это зависит от логики программы.

2Admin
А у меня и StrToFloatDef нет (по крайней мере в Help не упоминается). А AnsisToDoubleDef давным давно написал и включил в одну из библиотек нижнего уровня.
Dimon.Ru
Отправлено: 30.08.2005, 16:05


Станционный диспетчер

Группа: Участник
Сообщений: 92



Спасибо всем за советы. на днях раскопал в одной очень старенькой своей проге решение своего вопроса....
всего несколько строк, но вопрос решился быстро!!!

приду домой — выложу код.
Dimon.Ru
Отправлено: 31.08.2005, 16:44


Станционный диспетчер

Группа: Участник
Сообщений: 92



вотъ:
CODE

bool Razbivka(AnsiString stroka, double* massif)
{// Разбивка строки на элементы массива
AnsiString* itogMass; //Массив
int quanValues=0; //количество переменных
char *buff;
char *vhod;
int k,str_len=0;

buff=stroka.c_str();

//Определяем длину строки
str_len = stroka.Length();
if(str_len==0) return false;

// Определяем количество переменных
for(int i=0; i<str_len; i++)
if(stroka.c_str()[i] == ';') quanValues++;
if(quanValues<2) return false;

// Выделяем память под строки
itogMass = new AnsiString[quanValues];

for(int i=0;i<quanValues;i++)
{
if((vhod=strchr(buff,';'))==NULL) break;
k=(int)(vhod-buff);
char TEMP[100];
strncpy(TEMP,buff,k);
TEMP[k]='\0';
itogMass[i]=TEMP;
massif[i]=StrToFloat(itogMass[i]);
buff+=(k+1);
}

// Удалим массив
delete[] itogMass;

return true;
}


а double* massif объявляется до вызова функции Razbivka()

зы: может и коряво, но работает.

Отредактировано Dimon.Ru — 31/08/2005, 16:45
Admin
Отправлено: 31.08.2005, 16:51


Владимир

Группа: Администратор
Сообщений: 1190



А каким образом double * massif объявляется до вызова функции Razbivka() ???

Если вы заранее не знаете сколько в этом массиве будет
элементов — где выделение памяти под этот массив ?
Размер массива — 1 , 100, 1000, 10000 элементов ???

(остальной код не смотрел)



Asher
Отправлено: 31.08.2005, 17:59


Мастер участка

Группа: Модератор
Сообщений: 550



Привет.
Если тема еще интерсна, то есть еще вариант.
CODE

AnsiString s = "";
TStringList* psl = new TStringList();
psl->Delimiter = ';';
psl->DelimitedText = "195.2;11;154.254;-52;10;-544;1451;28";
int itEnd = psl->Count;
for(int itPos = 0; itPos < itEnd; ++itPos)//
s = psl->Strings[itPos];

Все само работает. biggrin.gif
Из минусов — завязка на VCL

P.S.
Отредактировано Asher — 31/08/2005, 19:00
gvg
Отправлено: 01.09.2005, 08:01


Машинист паровоза

Группа: Участник
Сообщений: 222



QUOTE (Asher @ 31/08/2005, 17:59)
Привет.
Если тема еще интерсна, то есть еще вариант.
CODE

AnsiString s = "";
TStringList* psl = new TStringList();
psl->Delimiter = ';';
psl->DelimitedText = "195.2;11;154.254;-52;10;-544;1451;28";
int itEnd = psl->Count;
for(int itPos = 0; itPos < itEnd; ++itPos)//
s = psl->Strings[itPos];

Все само работает. biggrin.gif
Из минусов — завязка на VCL

P.S. <tpjgfcyjt преобразование в численные значения — отдельная тема

Смотрится красиво, но откуда
psl->Delimiter ?
psl->DelimitedText ?
Asher
Отправлено: 01.09.2005, 08:16


Мастер участка

Группа: Модератор
Сообщений: 550



В смысле откуда?
Унаследованы TStringList от своего предка TStrings

Во всяком случае в Builder6.
Пятый давно не стоит, проверить не могу.
gvg
Отправлено: 01.09.2005, 08:24


Машинист паровоза

Группа: Участник
Сообщений: 222



Понял, у меня пятый. biggrin.gif
Dimon.Ru
Отправлено: 01.09.2005, 13:10


Станционный диспетчер

Группа: Участник
Сообщений: 92



QUOTE (Admin @ 31/08/2005, 16:51)
А каким образом double * massif объявляется до вызова функции Razbivka() ???

Если вы заранее не знаете сколько в этом массиве будет
элементов — где выделение памяти под этот массив ?
Размер массива — 1 , 100, 1000, 10000 элементов ???

(остальной код не смотрел)

массив объявляется до вызова либо как massif[]
Либо *massif, а потом выделяется память (до вызова функции).

в моём конкретном случае так удобнее.

вот заодно может быть посоветуете как сделать, что бы память выделялась в этой функции?

как ее в этом случае освобождать?
стр.: (2) < [1] 2 >
Вернуться в Вопросы программирования в C++Builder