IMHO.WS

IMHO.WS (http://www.imho.ws/index.php)
-   Программирование (http://www.imho.ws/forumdisplay.php?f=40)
-   -   Заполнение массива в С++ Builder из файла "txt" (http://www.imho.ws/showthread.php?t=131669)

Emelman 20.04.2008 19:16

Заполнение массива в С++ Builder из файла "txt"
 
Существует текстовый файл "file.txt" следующего вида:
Цитата:

Номер;Описание_характеристики;Данные1;Данные2;Данные3;Данные4;Всего
1;Характеристика1;1;0;0;1;2
2;Характеристика2;0;8;2;1;11
3;Характеристика3;1;1;0;0;2
;ВСЕГО;2;9;2;2;15
;В %%;13;60;13;13;100
Необходимо заполнить двумерный массив числовыми данными из файла. Т.е. проигнорировать полностью первую строку в файле, значения "номер по порядку" и "Имя характеристики". В конечном результате нужно получить заполненный массив следующего вида:
Цитата:

1 0 0 1 2
0 8 2 1 11
1 1 0 0 2
2 9 2 2 15
13 60 13 13 100
Помогите, пожалуйста, это выполнить на языке C++ Builder. :help: Заранее благодарен за помощь.

spike 21.04.2008 13:12

не сильно заботясь об быстроте:
Код:

// создаем список
TStringList *slFile;
slFile = new TStringList;

slFile->LoadFromFile(GetCurrentDir()+"\\file.txt"); // заполняем список из файла

// обработка
for(int iC = 0; iC < slFile->Count; iC++)
{
AnsiString strTMP = slFile->Strings[iC];
strTMP.Delete(1,strTMP.Pos(";")); // стирается 1ый столбец
strTMP.Delete(1,strTMP.Pos(";")); // стирается 2ой столбец

strTMP.SubString(1,strTMP.Pos(";") - 1); // значение 3го столбца
strTMP.Delete(1,strTMP.Pos(";") + 1); // стирается 3й столбец

/* остальные столбцы по аналогии */

strTMP; // последний столбец
}

delete slFile; // удаляем список


Emelman 23.04.2008 21:06

spike, прости меня дурака, но я не всё понял. Разъясни, пожалуйста...
Я понял, что при открытии файла мы заполняем список. Затем пробегаем по каждой строке списка и удаляем содержимое до знака ";". Тем самым удаляем содержимое первых 2 столбцов. А вот что значит строчка
Цитата:

strTMP.SubString(1,strTMP.Pos(";") - 1); // значение 3го столбца
Что за команда "SubString"? И что значит "-1" в конце команды? Потом тоже не совсем понятно что обозначает первая единица в скобках? Один символ/одно слово или вообще что-то другое?
Потом мы зачем-то стираем и третий столбец.
Цитата:

strTMP.Delete(1,strTMP.Pos(";") + 1); // стирается 3й столбец
И здесь снова присутствует не понятное мне "+1"... :idontnow:
Строка
Цитата:

strTMP; // последний столбец
мне тоже не понятна. Мы считали оставшийся столбец, но ничего с ним не делаем... И нигде не вижу упоминания о массиве. Или я просто туплю?

Когда-то я неплохо программил на Pascal'е и Borland C++, но с тех пор прошло ОЧЕНЬ много времени и я без практики всё позабыл. И сейчас глядя на этот код чувствую себя первокласником... :( Может я конечно совсем обнаглел так наседая, но мне очень нужно в этом разобраться, поэтому очень прошу твоей помощи!!! :молись:

Borland 23.04.2008 23:59

Ну, если уж делать "как положено", то есть с привлечением средств ООП, то нужно создать класс, описывающий формат файла и умеющий считывать его в память (опционально - записывать содержимое памяти обратно в файл того же формата) с функцией-членом, обеспечивающей чтение произвольного элемента.
И при заполнении целевого массива использовать эту функцию со сдвигом на 2 столбца (т.е. 1-й элемент строки целевого массива считывается считывается из 3-го элемента соответствующей "строки" класса).
А всю "прозу жизни" (разбор строк файла) упрятывать в этот класс.
Если количество строк сильно переменное (скажем, от 10 до 10000) имеет смысл целевой двумерный массив заменить на связанный список указателей на элементы-"строки" (чтобы не выделять априорно лишнюю память и в то же время не оказаться в ситуации, когда выделенная память недостаточна для считывания файла).
В общем, очень многое зависит от того, что в дальнейшем планируется делать с целевым массивом...
Цитата:

Сообщение от Emelman (Сообщение 1547471)
что значит "-1" в конце команды

Означает, что ";" в подстроку не попадает (крайним правым символом подстроки-результата этой команды становится символ слева от ";").
На самом деле, данная функция должна стоять в правой части оператора присваивания... ;)
Цитата:

Сообщение от Emelman (Сообщение 1547471)
Что за команда "SubString"?

Функция выделения подстроки, что вполне очевидно...

Цитата:

Сообщение от Emelman (Сообщение 1547471)
Потом мы зачем-то стираем и третий столбец.

Чтобы следующая strTMP.SubString(1,strTMP.Pos(";") - 1); вернула значение четвёртого столбца...
Цитата:

Сообщение от Emelman (Сообщение 1547471)
не понятное мне "+1"

Вот здесь мне тоже не совсем понятно, а лезть в мануал за описанием команды лень... Если данная команда - правильная, то почему "+1" нету в команде удаления первых двух столбцов?

spike 24.04.2008 09:20

Borland, "+1" это я ошибся, не надо

Emelman 27.04.2008 02:04

Borland, прошу прощения за свою назойливость, но не мог бы ты привести хотя бы примерный код? Я в классах вообще не разбираюсь. Только вчера удалось найти книгу, в которой рассказывается про классы и наследования.
Цитата:

Сообщение от Borland (Сообщение 1547530)
Если количество строк сильно переменное (скажем, от 10 до 10000) имеет смысл целевой двумерный массив заменить на связанный список указателей на элементы-"строки" (чтобы не выделять априорно лишнюю память и в то же время не оказаться в ситуации, когда выделенная память недостаточна для считывания файла).

Массив будет постоянного размера - 5 столбцов и 37 строк. Соответственно текстовый файл сотоит из 38 строк (первая строка вообще не нужна, там пояснения).
Цитата:

Сообщение от Borland (Сообщение 1547530)
В общем, очень многое зависит от того, что в дальнейшем планируется делать с целевым массивом...

С массивом ничего делаться не будет. В данном случае массив необходим исключительно как хранилище числовых данных, на основе которых потом будет строиться своеобразная диаграмма.

Emelman 03.06.2008 23:06

В конечном итоге с классами не разобрался. Массив заполнил использовав совет spike'а. Только немного изменил код, подогнав под свои нужды.
Сейчас возникла другая трудность. В программе участвуют 10 массивов одинакового размера, со всеми ними будут производиться одни и те же действия, поэтому пришёл к выводу, что нужно написать функцию, как в старом добром "Borland C++" (не Builder), и вызывать её для каждого массива. Для примера:
Код:

void vvodmass (int mass[5][5]) //объявляем функцию
{int i,j;
for (i=0; i<5; i++)
for (j-0; j<5; j++)
cin>>mass[i][j];}

void main()
{int a[5][5], b[5][5];
vvodmass (a); //вызываем функцию для массива "a"
vvodmass (b); //вызываем функцию для массива "b"
}

Так вот решил написать нечто подобное, но не пойму как правильно объявлять функцию. Написал примерно, как в примере:
Код:

__fastcall TForm1::TForm1(TComponent* Owner)
        : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void diagramma(int mass[37][5])
{int i,j,otstup;

otstup=10;
for (i=0; i<35; i++)
{
Image1->(!Здесь выскакивает ошибка!)Canvas->Brush->Color=(TColor)RGB(0,255,0);
Image1->Canvas->Rectangle . . .
//дальше идёт процесс рисования на элементе Image1 столбиков диаграммы.
//Чтобы не грузить вас формулами, стёр всё ненужное.
}
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{int mass1[37][5],mass2[37][5],mass3[37][5],mass4[37][5],mass5[37][5];
int mass6[37][5],mass7[37][5],mass8[37][5],mass9[37][5],mass10[37][5];

diagramma (mass1);
}

Не знаю правильно ли я описал функцию и будет ли она работать, но при таком написании функции выскакивает лишь одна ошибка:
Цитата:

[C++ Error] Unit1.cpp(29): E2451 Undefined symbol 'Image1'
Подскажите, пожалуйста, где я, что не так написал. Почему программа перестала понимать что такое "Image1"? :молись:

P.S. Если забыть про остальные массивы, и прописать процесс рисования не как функцию, то диаграмма рисуется просто замечательно.

PhoeniX 04.06.2008 00:08

у тебя функция diagramma не является методом класса TForm1 и соответственно не может юзать внутренние объекты , коим является Image1
нужно либо функцию определить методом класса:
в Unit1.h там где "public: //User decloration" прописать прототип функции, а при описании функции указать void TForm1::diagramma(...),
либо передавать в функцию указатель на объект на ряду с остальными параметрами

Emelman 04.06.2008 00:16

С классами я уже пробовал разбираться, поэтому отбрасываем этот вариант. А что значит передавать в функцию указатель на объект? Везде прописывать "Form1->Image1->..."? Можно хотябы маленький пример для наглядности?

PhoeniX 04.06.2008 00:40

да, так как ты написал тоже сработает в данном конкретном примере, но это имхо не хорошо, делать свои функции зависимыми от глобальных переменных

я говорил о том, что в функцию передавать указательна объект, то есть прототип функции будет выглядеть так:
void diagramma(int mass[37][5], TImage *Image1)
а вызывать ее соответственно вот так:
diagramma (mass1, Image1);
опять же в твоем конкретном случае, потому что функция diagramma вызывается из метода класса TForm1, где Image1 определен

да и с классом все просто:
в нижней панели окна кода находим закладки, среди них unit1.h, переходим туда и в public, там где определяется пользователем пишем
void diagramma(int mass[37][5]);
а в основном коде (cpp - файл) определяем прототип как
void TForm1::diagramma(int mass[37][5])

метод выбирай на свой вкус, судить о правильности и корректности я не буду (ну не считая первого случая, но там мое имхо)

Emelman 04.06.2008 21:17

Очередная проблема: Создаю "Form2" и заполняю на ней два поля "Edit". После закрытия этой формы нужно вернуть значения этих полей. Но командра "return" не желает работать. Выдаётся ошибка:
Цитата:

[C++ Error] Unit2.cpp(24): Е2467 '__fastcall TForm2::Button1Click(TObject *)' cannot return a value
Код:

void __fastcall TForm2::Button1Click(TObject *Sender)
{AnsiString author, nazvanie;

author=Edit1->Text;
nazvanie=Edit2->Text;
Form2->Close();
return author,nazvanie;
}

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

Borland 04.06.2008 23:23

Это ж самые основы программирования!!!
return ВСЕГДА возвращает ОДНО значение.
К тому же, насколько я понимаю описание твоей функции, она вообще не может возвращать значений ибо void (что вполне логично для обработчика клика).

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

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

Emelman 05.06.2008 02:07

Цитата:

Сообщение от Borland (Сообщение 1561404)
К тому же, насколько я понимаю описание твоей функции, она вообще не может возвращать значений ибо void (что вполне логично для обработчика клика).

Я уже побегал немного по просторам глобальной сети и выяснил, что void ничего не возвращает. Сейчас задумался над "глобальными переменными". Но их почему-то все хаят. А мне похоже без них не обойтись.
К тому же, я прописал функцию рисования диаграммы, как сказал PhoeniX (мне нужна была функция, потому что придётся постоянно рисовать диаграмму, но с разным масштабом/с разных массивов), но вызвать её могу только при нажатии на "Button1", потому что я объявил массивы именно при нажании на эту кнопку.
Код:

void __fastcall TForm1::Button1Click(TObject *Sender)
{int mass1[37][5],mass2[37][5],mass3[37][5],mass4[37][5],mass5[37][5];
int mass6[37][5],mass7[37][5],mass8[37][5],mass9[37][5],mass10[37][5];

При нажатии на другие кнопки или выборы в ComboBox (один из 10 массивов) естественно выскакивает ошибка:
Цитата:

[C++ Error] Unit1.cpp(201): E2451 Undefined symbol 'mass1'
Можно ли как-то объявить эти массивы глобально, чтобы их было видно при нажатии на любую кнопку?

P.S. Извините, что задаю так много вопросов и часто - очень глупых, но мне очень нужна ваша помощь. :help::молись: Заранее ПРЕМНОГО благодарен за все ответы и советы. Практически все предыдущие советы мне очень помогли.

doro 05.06.2008 08:49

А не проще воспользоваться isdigit()?

Emelman 06.06.2008 01:39

В общем выяснил как объявлять переменные глобально и объявил соответствующим способом все 10 массивов и нужные для работы переменные. Программа отлично заработала. Теперь мне интересен другой вопрос: У меня в программе есть поток событий:
Код:

{import (mass1);
Form2->Show();
ComboBox11->Items->Delete(0);
ComboBox11->Items->Insert(0,"Имя диаграммы");
masshtab=(max*100/mass1[35][4]);
diagramma (mass1, masshtab, Image1, 310, 300);}

Подскажите, пожалуйста, что нужно прописать, чтобы после открытия "Form2" ничего не выполнялось до тех пор, пока она не будет закрыта?

ЕЖ 06.06.2008 22:46

Emelman, Form2->ShowModal();

Emelman 08.06.2008 17:23

ЕЖ, то, что надо! :yees:

Возникла очередная интересная проблемка: Существует каталог, где расположен exe'шник программы и внутри него папка Base, в которой хранятся текстовые файлы с вышеописанными данными. В программе присутствует 2 кнопки: одна - импорт файла папки "Base", а другая вызывает OpenDialog. Если сначала импортировать файлы из папки "Base" (путь прописан "Base/<имя текстового файла>"), то всё работает "на Ура". Но стоит нажать другую кнопку и указать текстовый файл из левого каталога, то видимо рабочий каталог меняется на последний используемый, и из базы данных уже ничего не получить (<левый каталог>/Base/<имя текстового файла> - не найдено).
В идеале было бы не плохо сначала определить путь текущего каталога (где находится exe'шник) и запонимть его (пускай даже в переменной типа String). А перед нажатием на кнопку импортирования файла из папки "Base" изменить путь рабочего каталога на изначальный. Но я нигде не могу найти существуют-ли команды определения и смены рабочего каталога. Помогите, пожалуйста!

ЕЖ 10.06.2008 01:08

Смотри свойства/функции Application->ExeName / ExtractFilePath() / GetCurrentDir()

При импорте просто используй абсолютный путь, что то типа ExtractFilePath(Application->ExeName) + твои файлы из Base

WISE 11.06.2008 19:59

Path:=Application.ExeName; // полный путь и название запущенной программы
Path:=ExtractFileDir(Path); // отбрасываем название программы. Остается путь.
if Path[Length(Path)]<>'\' then Path:=Path+'\'; // если последний символ не \ то добавить его
{Последняя строка нужна для присвоения последнего символа '\',
потому что при обрезке файла 'C:\PROG.EXE' получим 'C:\',
а при обрезке 'C:\WINDOWS\PROG.EXE' получим 'C:\WINDOWS'}

ЕЖ 13.06.2008 01:25

WISE, зачем? Есть ExtractFileDir, а есть ExtractFilePath - разница как раз в том, что ExtractFileDir не оставляет последнего символа '\', а ExtractFilePath оставляет.

Emelman 14.06.2008 23:21

Я решил не заморачиваться с файлами и загнал все данные в базу данных. Так, возможно, и работать быстрее будет. А информация насчёт путей всё равно полезная. Тем более, что в интернете по этому вопросу практически ничего нет.

Emelman 17.06.2008 15:51

Вроде бы всё работает, разве что напрягает, что данные записываются в конец базы данных ("Append();"). Чтобы данные записывались сразу по сортировке хотел написать следующий код:
Код:

Table1->First();
while (!Table1->Eof)
        {if (Table1->Fields->FieldByName("Author")->AsString<author2)
              {Table1->Cancel(); Table1->Next();}
        else {Table1->Insert();
              Table1->FieldByName("Author")->AsString=author1;
              Table1->Post();
              return;}
        }

Но, видимо, операция сравнения (<,>) со строками не работает. Потому что в результате добавление новой записи происходит в начало базы. Подскажите, как правильно реализовать задуманное.

PhoeniX 17.06.2008 16:07

сравнение должно работать со строками (как я понимаю тут AnsiString)

к сожалению, из приведенного куска кода не ясно что такое author1 и author2, но предполагаю, что в сравнении и присвоении все же должна быть одна и та же переменная :idontnow:

проверь это, может все и дело в том что не с той переменной сравниваешь?

Emelman 17.06.2008 16:12

PhoeniX, :yees: :ржать:
Вторые сутки коверкаю код как могу, и за всё это время не увидел опечатки!!! Когда же я прекращу тупить?... Спасибо за замечание! :biggrin:

Emelman 17.06.2008 22:39

А можно ли отучить C++ различать регистр при сравнении? А то на данный момент получается, что "Z" (Зет большое)<"a"(А малое).

crawler 17.06.2008 22:58

сравнивай в верхнем регистре (должна быть функция ToUpper или чтото вроде того)

ЕЖ 18.06.2008 01:14

Цитата:

Сообщение от Emelman (Сообщение 1564771)
разве что напрягает, что данные записываются в конец базы данных ("Append();"). Чтобы данные записывались сразу по сортировке хотел написать следующий код

Вообще какое-то странное занятие пытаться писать данные в БД в порядке сортировки. Никогда нельзя быть уверенным как БД физически сохранит запись. Нужно пользоваться сортировкой при выборке данных, или задавать ее уже в визуальных компонентах.

Emelman 19.06.2008 19:29

Цитата:

Сообщение от crawler (Сообщение 1564845)
сравнивай в верхнем регистре (должна быть функция ToUpper или чтото вроде того)

К сожалению, конкретно данная команда не известна компилятору, да и похожих я не нашёл. :(
Цитата:

Сообщение от ЕЖ (Сообщение 1564868)
Вообще какое-то странное занятие пытаться писать данные в БД в порядке сортировки. Никогда нельзя быть уверенным как БД физически сохранит запись. Нужно пользоваться сортировкой при выборке данных, или задавать ее уже в визуальных компонентах.

Что значит "как БД физически сохранит запись"? Разве, если я записал автора "Гоголя" между "Достоевским" и "Толстым", то при следующем запуске программы "Гоголь" может оказаться в другом месте? :confused:
Во всяком случае на данный момент сортированность данных не нарушается, и это для меня самое главное. Единственное, что мне сейчас не нравится - что при данном написании кода "Толстой" (с большой буквы) записывается в базу перед "достоевским" (с маленькой буквы). Т.е. при сравнении строк идёт проверка в следующем порядке: сначала идут заглавные латинские буквы "A-Z", затем прописные латинские буквы "a-z", затем идут заглавные русские буквы "А-Я" и уже в конце идут прописные русские буквы "а-я". Мне же хотелось, чтобы заглавные и прописные буквы расценивались одинаково.
Т.е. на данный момент записи хранятся в следующем виде:
Цитата:

Author2
author1
author3
Автор5
автор4
автор6
А хотелось бы, чтобы записи хранились вот так:
Цитата:

author1
Author2
author3
автор4
Автор5
автор6

ЕЖ 19.06.2008 22:43

Emelman, нельзя обсуждать эти вопросы не зная конкретную используемую СУБД. Тут надо думать не на уровне программы, а на уровне СУБД.

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

В серьезных же СУБД, типа MS SQL Server / Oracle такого не происходит. Единственный язык, который они понимают - это SQL, который для записи новых данных использует только одну команду - INSERT, без всякого указания места, куда эту запись вставить. Только сервер решает куда она физически будет записана, и это вполне может оказаться вообще другой диск, если так сконфигурирована база. Соответственно при следующей выборке данных трудно говорить о порядке и полагаться на что-то. Для достижения нужного результата лучше самому указывать сортировку по некому признаку.

Что касается сортировки по умолчанию и сравнения строчных и прописных букв - это опять же настройки collation конкретного драйвера/СУБД, хотя указывая сортировку в SQL-запросе всегда можно задать и свои условия, что-то типа ORDER BY UPPER(Name).

Emelman 19.06.2008 23:04

Я писал данную программу с вашей помощью и помощью нескольких книг типа "C++ Builder для начинающих". Как в книгах было написано про простую базу данных, так я её и создал. А именно: Запустил "BDE Administrator" и создал в нём базу данных с характеристиками:
Цитата:

Type: STANDART
DEFAULT DRIVER: PARADOX
ENABLE BCD: FALSE
База данных имеет расширение *.db. Используется исключительно для хранения данных. Никаких фильтров, никаких выборок, запросов и т.п. вещей. Только запись/стирание записей. Хотя советовать что-либо изменить, наверное, уже поздно. Разве что на заметку, на будущее, т.к. это была дипломная работа, и защита её состоится через 5 дней. ;)

дима 05.05.2009 19:16

Помогите"SOS"
Мне нужно найти количество положительных чисел в каждом столбце 2х массива.
Заранее спасибо.


Часовой пояс GMT +4, время: 05:39.

Powered by vBulletin® Version 3.8.5
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.