Odczyt i zapis plików binarnych w Cpp
Ghostek
Niniejszy artykuł ma za zadanie zapoznać czytelnika z arcytrudną (i, wg. niektórych, wręcz 'paranormalną', cokolwiek by to miało w tym kontekście znaczyć) sztuką odczytywania i zapisywania plików binarnych przy użyciu języka C++, dostępną dotąd jedynie dla wąskiej grupki największych ekspertów w owym języku.
1 Co to jest plik binarny
2 Do czego się ich używa
3 Jak się je odczytuje
3.1 Przykład z życia wzięty - bitmapa
3.2 Niestandardowe wielkości pól
3.3 Zapis
Co to jest plik binarny
Najprostsza definicja pliku binarnego to: plik, który po otworzeniu w edytorze tekstu daje tzw. 'krzaki'. Ich zawartość to po prostu 'odbitka' surowych danych zapisanych w pamięci programu, który je utworzył, bez jakiegokolwiek przetwarzania na formę odczytywalną przez człowieka. Na przykład liczba 42 przechowywana w zmiennej typu int zostanie zapisana jako ciąg bajtów 00 00 00 2A, podczas gdy w pliku tekstowym zostałaby przed zapisaniem przetworzona na znaki '4' i '2', co w formacie ASCII odpowiada bitom 34 i 32. Edytor tekstu otwierając pliki dokonuje operacji odwrotnej, zamieniając dane binarne na znaki, które im odpowiadają. Oczywistym jest, że w wypadku plików binarnych taka zamiana nie ma sensu, stąd biorą się 'krzaki' podczas próby odczytania ich w ten sposób - np. wspomniana liczba 42 została by zamieniona na ' *'. Po to powstały tzw. hexedytory - programy pokazujące zawartość pliku liczbowo, bez konwersji na znaki. Jak jednak odczytać plik binarny z poziomu własnego programu? O tym dowiesz się z tego artykułu.Do czego się ich używa
Może się wydawać, że używanie plików to niepotrzebne utrudnianie sobie życie. Nie lepiej byłoby zapisywać wszytko jako tekst? Otóż okazuje się, że nie. Odczyt i zapis plików binarnych jest znacznie szybszy, niż tekstowych - mają one 'sztywną' strukturę, toteż komputer nie musi zastanawiać się nad tym, co oznaczają napotkane w pliku bajty. Dlatego używa się ich do przechowywania dużych ilości danych, które i tak nie muszą być edytowalne przez przeciętnego użytkownika.Jak się je odczytuje
Jest to stosunkowo proste. W języku C++ klasa ifstream posiada metodę read pozwalająca na odczyt surowych danych zapisanych w pliku binarnym. Na przykład, gdy chcemy odczytać z początku pliku jedną zmienną typu int i jedną typu float:#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream ifs("plik.foo", ios::binary); // otwieramy plik do odczytu binarnego
char* temp = new char[sizeof(int)]; // tymczasowy bufor na dane
ifs.read(temp, sizeof(int)); // wczytujemy dane do bufora
int* number1 = (int*)(temp); // rzutujemy zawartość bufora na typ int
// powtarzamy procedurę dla floata:
temp = new char[sizeof(float)];
ifs.read(temp, sizeof(float));
float* number2 = (float*)(temp);
cout << number1 << " " << number2 << endl;
delete number1;
delete number2;
}
Można ten proces usprawnić, pisząc strukturę opisującą ów plik:
#include <iostream>
#include <fstream>
using namespace std;
struct File
{
int number1;
float number2;
};
int main()
{
ifstream ifs("plik.foo", ios::binary); // otwieramy plik do odczytu binarnego
char* temp = new char[sizeof(File)]; // tymczasowy bufor na dane
ifs.read(temp, sizeof(File)); // wczytujemy dane do bufora
File* file = (File*)(temp); // rzutujemy zawartość bufora na typ File
cout << file->number1 << " " << file->number2 << endl;
delete file;
}
Jeśli wielkość struktury nie jest podzielna przez 4, kompilator doda do niej puste bajty dla ułatwienia adresowania. Nie ma to znaczenia dla normalnego użytkowania struktur, lecz w tym wypadku może ci uniemożliwić odczytanie pliku. By się tego pozbyć, dodaj '#pragma pack(push, 1)' przed i '#pragma pack(pop)' po definicji struktury. Będzie wtedy zajmować dokładnie tyle pamięci, co suma jej składowych.
Przykład z życia wzięty - bitmapa
Jeśli programujesz pod Windowsem, w wypadku bitmap można skorzystać z gotowych struktur zawartych w pliku nagłówkowym `<windows.h>`. Jeśli nie, skopiuj do programu następujące struktury: ```cpp #pragma pack(push, 1) struct BITMAPFILEHEADER { short bfType; int bfSize; short bfReserved1; short bfReserved2; int bfOffBits; };struct BITMAPINFOHEADER
{
int biSize;
int biWidth;
int biHeight;
short biPlanes;
short biBitCount;
int biCompression;
int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
int biClrUsed;
int biClrImportant;
};
#pragma pack(pop)
Bitmapę odczytuje się w następujący sposób:
```cpp
#include <iostream>
#include <fstream>
#include <windows.h> // zastąp przez powyższe definicje struktur jeśli nie posiadasz tego pliku
using namespace std;
#pragma pack(push, 1)
struct Pixel
{
unsigned char b, g, r;
};
#pragma pack(pop)
int main()
{
ifstream ifs("foo.bmp", ios::binary);
char* temp = new char[sizeof(BITMAPFILEHEADER)];
ifs.read(temp, sizeof(BITMAPFILEHEADER));
BITMAPFILEHEADER* bfh = (BITMAPFILEHEADER*)(temp);
temp = new char[sizeof(BITMAPINFOHEADER)];
ifs.read(temp, sizeof(BITMAPINFOHEADER));
BITMAPINFOHEADER* bih = (BITMAPINFOHEADER*)(temp);
ifs.seekg(bfh->bfOffBits, ios::beg); // bfOffBits wskazuje początek danych obrazka
int width = bih->biWidth;
if(width % 4) width += 4 - (width % 4); // piksele w bitmapie są wyrównywane do 4 bajtów
Pixel** pixs = new Pixel*[bih->biHeight];
for(int i=0; i<bih->biHeight; i++)
{
temp = new char[3*width];
ifs.read(temp, 3*width);
pixs[i] = (Pixel*)(temp); // uwaga - nigdy nie czytaj z tej tablicy więcej niż bih->biWidth pixeli
}
/* robisz z bitmapą co tam chcesz */
delete bfh;
delete bih;
for(int i=0; i<bih->biHeight; i++) delete[] pixs[i];
delete[] pixs;
}
Niestandardowe wielkości pól
Jeśli plik zawiera pola o wielkości innej niż dostępne typy danych, można posłużyć się następującą konstrukcją: ```cpp struct File { int foo : 4; // pole 4-bitowe int bar : 20; int baz : 3; int gazonk : 5; }; ``` Struktura taka będzie miała rozmiar jednego inta, czyli 4 bajty.Zapis
Pliki binarne zapisuje się w sposób analogiczny do odczytywania, tyle że odwrotny. Dla przykładu: ```cpp #include <iostream> #include <fstream>using namespace std;
struct File
{
int number1;
float number2;
};
int main()
{
ofstream ofs("plik.foo", ios::binary); // otwieramy plik do zapisu binarnego
File* file = new File;
cin >> file->number1 >> file->number2 ;
ofs.write((char*)(file), sizeof(File)); // zapisujemy dane do pliku
delete file;
}
<h1>Końcowe uwagi</h1>
Mam nadzieję, że artykuł okaże się pomocny. Domyślam się, że i tak ludzie będą dalej o to pytać na forum, ale przynajmniej będzie ich gdzie odsyłać ;)
A już sobie poradziłem np. tak:
#include <iostream>
#include <fstream>
using namespace std;
main()
{bool a=1;
ofstream ofs("plik.bin", ios_base::binary | ios_base::app );
for(int x=0;x<36;x++)
ofs.write((char*) &a, sizeof(a));
a=0; ofs.write((char*) &a, sizeof(a));
}
prosto i o to mi chodziło kodzik zapisuje boola do plik.bin w pątli for idzie 36 jedynek na końcu bool a =0 i też idzie to do pliku razem 37bitów
odczyt podobny tylko z ofs.read oczywiście i zamiast pojedyńczego boola można zrobić tabelkę i działa:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{bool a[37];
ifstream ofs("plik.bin", ios_base::binary);
ofs.read((char*) &a, sizeof(a));
cout<<a[0]<<a[1]<<a[2]<<a[3]<<a[4]<<a[5]<<a[6]<<a[7]<<a[8]<<a[9]<<a[10]<<a[11]<<a[12]<<a[13]<<a[14]<<a[15];
cout<<a[16]<<a[17]<<a[18]<<a[19]<<a[20]<<a[21]<<a[22]<<a[23]<<a[24]<<a[25]<<a[26]<<a[27]<<a[28]<<a[29]<<a[30];
cout<<a[31]<<a[32]<<a[33]<<a[34]<<a[35]<<a[36];}
proste pozdrawiam
@Rebus
Spróbuj dodać
#pragma pack(push, 1)
i
#pragma pack(pop))
tak jak to jest w przykładzie. Może to jest właśnie problem z zaokrąglaniem do 4.
a jak byście zapisali do pliku binarnego tablicę booli próbowałem zrobić na wzór arytkułu i wyszło coś takiego:
#include<fstream>
using namespace std;
main()
{
struct bool1
{bool a[10];};
bool1 adam={{1,0,1,0,1,0,0,1,0,1}};
ofstream bin.open("a",ios_base::binary);
bin.write((bool*)(adam), sizeof(bool1));
return 0;}
ale nie działa ;/
swoją drogą, można zapisać to prościej, bez tworzenia tempa:
We wszystkich napisanych wyżej programach można jeszcze na końcu zwolnić zasoby i zamknąć plik funkcją close().
Kurczę no i mamy dubel.. Odczyt i zapis plików binarnych w Cpp
Dobra usunąłem powyższy (ten jest aktualniejszy). To przekleję jeszcze swój komentarz :P
Oj, bedzie trzeba poprawić błąd z tymi plusami w Coyocie... Teraz nie mogę nawet przenieść czy edytować artykułu...
(zastąpienie + na %2B zdaje się na nic, bo przy zapisywaniu się sypie... click)
//edit: ok, udało się z wtyczką Developer Console do Opery (tudzież Webdeveloper dla FF)
--
Ghostek: w tych pierwszych kodach brakuje using namespace std i nie ios:binary a ios::binary - ktoś tu chyba pisał kody z palca ;)
Ponadto
<windows.h>
zapisuj jako<code><windows.h>
Dzięki za poprawienie błędu. Artykuł i tak musiałem zedytować, ponieważ jak się okazało tutejsza składnia boczy się na zapis typu < [bez spacji] windows.h [bez spacji] >, więc dodałem ````
Świetne. Właśnie tego mi było trzeba. Dzięki :)
Miałeś drobny błąd przy rozmiarze tablicy temp, ale już poprawiłem.
//Dodane: Edytuj ten artykuł i zamień gdzieś tam jedną literkę czy coś, bo mi głupio, że po małej modyfikacji już widnieję jako autor...