Plik Binarny

0

Witam
Jestem nowy na tym forum. Piszę programik który ma być bazą danych. Od kilku dni męczę się nad zagadnieniem dotyczącym zapisem i odczytem danych (plik binarny)
Oto moja struktura danych:

 typedef unsigned int ui;
typedef short unsigned int sui;
struct osoba
{
char nick[30];
ui pkt; //ile pkt
ui wie; //ile wieżowców
ui dom; //ile domków
ui fab; //ile fabryk
ui raf; //ile rafinerii
ui kop; //ile kopalnii
ui amm; //ile fabryk amunicji
sui fsa; //ile fabryk samolotów
sui fcz; //ile fabryk czołgów
sui fst; //ile fabryk statków
sui geo; //jaki poziom geologii
sui scz; //jaki poziom symulatora czołgów
sui sst; //jaki poziom symulatora statkow
sui ssa; //jaki poziom symulatora samolotów
};

Zapis do pliku (jeszcze nie zrobiłem zabezpieczeń poprawnego otwierania itp. bo najpierw chcę by samo w sobie dobrze działało)

 ofstream ofs("plik.foo", ios::binary); // otwieramy plik do zapisu binarnego 
ofs.write((char*)(os), sizeof(osoba)); // zapisujemy dane do pliku

Oraz odczyt danych:

ifstream ifs("plik.foo", ios::binary); // otwieramy plik do odczytu binarnego
char* temp = new char[sizeof(osoba)]; // tymczasowy bufor na dane
ifs.read(temp, sizeof(osoba)); // wczytujemy dane do bufora
osoba* os = (osoba*)(temp); // rzutujemy zawartość bufora na typ File
sui dl=0;
while(os->nick[dl]!=char(13))
       {
       cout<<os->nick[dl];   
       dl++; 
       }
cout << os->pkt <<endl;
cout << os->wie <<endl;
cout << os->dom <<endl;
cout << os->fab <<endl;
cout << os->raf <<endl;
cout << os->amm <<endl;
cout << os->fsa <<endl;
cout << os->fcz <<endl;
cout << os->fst <<endl;
cout << os->geo <<endl;
cout << os->ssa <<endl;
cout << os->scz <<endl;
cout << os->sst <<endl; 

Mój problem:
Gdy wpisuje nick (tablica char 30-elementów) to po odczytaniu wywala mi jakieś krzaczki
np jak wpisałem egregious
to wyświetla mi egregiouss\system32\cmd.exe (piszę z pamięci)
Ogólnie wydaje mi się że problem jest już przy wprowadzaniu danych bo jak kazałem wyświetlić tablicę bez niczego (tzn bez ówczesnego zapisania jej do pliku tzn zaraz po wprowadzeniu danych) to i tak wali krzaki... problem rozwiązałem w taki sposób: gdy user zatwierdzi koniec wprowadzania nicku (enter - char(13) ) to po niku program wstawia enter
np egregious -- egregious[char(13)]
I w czasie odczytu jak widać on wypisuje do chwili napotkania char(13)

I to jest mój problem... walczę z tym już jakiś czas i to jedyne co wymyśliłem.
Natomiast drugi to nie mam pojęcia jak zmienić wczytywanie danych by wczytywał cały plik a nie tylko pierwszy rekord (btw wiem że zapisywany jest tylko jeden ale to nie problem dopiszę tylko ios::app przy otwieraniu pliku do zapisu i po kłopocie, usunąłem to tymczasowo by widzieć jakie zmiany wprowadzam) jedyne co wiem to że użyć trzeba pętli while i zapewne znacznika EOF ale jak zastosować to nie mam pojęcia...

Pozdrawiam Egregious

0

A co to niby jest

os->nick[dl]!=char(13)

cóż to za char(13)? Od kiedy w binarnym pliku masz zapisane \r albo \n? Jeśli wczytywałeś dane do tej tablicy normalnie, to na końcu powinieneś mieć \0 (jak to pominąłeś przy wczytywaniu to jesteś lekko niepoważny...) i wypisywać normalnie, a nie jakąś pętlą.

0

WOW xD Dziękuję wielkie... w sumie nie wiem czemu tak dałem... ten fragment powstał dzisiejszej nocy na wpół przytomny i może dlatego takie COŚ zrobiłem.
A Druga sprawa? Jak wczytać cały plik? chodzi mi o same ustawienie pętli wczytywania z pliku...

0
do 
{
//
} while(!plikeof())
0

A ja ci szczerze mówiąc nie polecam korzystania z zapisywania struktur w ten sposób do pliku w C++. Serializacja jest trochę uboga w C++ i lepiej mieć nad nią większą kontrolę. Ma to taki minus, że jest trochę więcej kodu do napisania i w przypadku zmiany struktury, musisz również zmienić metodę odczytującą oraz serializującą. W przypadku takich najprostszych struktur plusów może nie być, ale gdy będziesz miał już klasy, które będą zawierać jakiekolwiek wskaźniki, które będzie trzeba odpowiednio odbudować po wczytaniu, masz ułatwione zadanie.

Jakiś czas temu pisałem prostą bazę danych w C++. Mamy klasę Database, ona posiada Table(s), tabele posiadają Column(s), te zawierają jedynie nazwę oraz typ oraz Row(s), które mają Field(s) - to z kolei abstrakcyjna klasa z wirtualną metodą Serialize, poszczególne typy serializują się w wiadomy sobie jedynie sposób. Każda klasa posiada własną metodę Serialize i tak, Database serializuje wszystkie tabele, te z kolei kolumny i wiersze, wiersze pola. Dzięki takiej hierarchii mamy bardzo ładny sposób całej bazy danych, wystarczy wywołanie jednej metody.

I przykładowo, metoda Serialize klasy Row wygląda tak:

void Row::Serialize(ostream& stream)
{
	Utils::Write(stream, fields.size());

	vector<Field*>::iterator it;

	for(it = fields.begin(); it != fields.end(); it++)
		(*it)->Serialize(stream);
}

Zapisujemy najpierw ilość pól (int), później wszystkie pola. I tak gdy np. zapisać mamy pole typu Text, wywoływana jest następująca metoda:

void TextField::Serialize(ostream& stream)
{
	Utils::Write<string>(stream, "t");

	Utils::Write(stream, value);
}

Na początek zapisujemy identyfikator typu pola, później samą wartość. Wczytywanie jest bardzo analogiczne, przeładowane są konstruktory, które pobierają istream&.

Pomyśl nad zrobieniem tego w ten sposób. Zaprojektuj sobie najpierw (w głowie, na papierze, tworząc wykresy, etc) jak mogły by wyglądać odpowiednie klasy. Możesz mieć np. klasę GameState i w nim np. vector klas Player, Map, obiekty występujące w grze itd. I zapisanie całego stanu gry będzie się sprowadzać do GameState::Save.

W stosunkowo prosty sposób serializacja w C++ jest wytłumaczona tutaj: http://www.parashift.com/c++-faq-lite/serialization.html

I jeszcze moja klasa Utils z metodami Write i Read:

#pragma once

#include <iostream>

using namespace std;

class Utils
{
public:
	template <typename T>
	inline static T Read(istream& stream)
	{
		T value;
		stream.read(reinterpret_cast<char*>(&value), sizeof(value));

		return value;
	}

	template <typename T>
	inline static void Write(ostream& stream, T value)
	{
		stream.write(reinterpret_cast<char*>(&value), sizeof(value));
	}

	template <>
	inline static string Read<string>(istream& stream)
	{
		int length = Read<int>(stream);
		char* value = new char[length + 1];
		stream.read(value, length);
		value[length] = '\0';
		string result = value;
		delete[] value;
	
		return result;
	}

	template <>
	inline static void Write<string>(ostream& stream, string value)
	{
		int length = value.length();
		Write(stream, length);
		stream.write(value.c_str(), length);
	}
};
0

Dziękuję pięknie :)
Jednak zostanę tym razem przy strukturze. Klas jeszcze nie umiem, a program który piszę jest na zaliczenie właśnie struktur więc z góry mam narzucone w jaki sposób rekord ma być zbudowany.
Natomiast Twoją radę zapamiętam i gdy będę się uczył klas to zwrócę uwagę na taki podział bazy danych

Obu użytkownikom dziękuję za pomoc i cierpliwość (dopiero się uczę C++, i w większości jestem samoukiem).
Pozdrawiam

1 użytkowników online, w tym zalogowanych: 0, gości: 1