Operacje na plikach

Download Report

Transcript Operacje na plikach

Operacje na plikach w C++
Wprowadzenie
• Było – dane są przechowywane w pamięci
operacyjnej, programy także.
• Wniosek – dane „żyją” tylko i wyłącznie w czasie
pracy programu. Potem są nieodwołalnie
usuwane.
• Problem – co z danymi, których jest tak dużo, że
nie warto ich wprowadzać za każdym
uruchomieniem programu – np. dane PESEL,
• Rozwiązaniem jest wykorzystanie plików.
Wprowadzenie
• Co to jest plik?
– Plik jest to zorganizowana struktura przechowywania informacji w
sposób trwały.
• Jaka jest organizacja pliku?
– Fizyczna,
– Logiczna.
• Fizyczna nas (w tej chwili) nie interesuje - ścieżki, sektory, cylindry,
rekordy (zazwyczaj 512B) itd.
• Logiczna – system plików i katalogów.
• By w programowaniu korzystać z plików musimy orientować się w
strukturze logicznej plików, czyli znać hierarchiczną strukturę plików
na komputerze, na którym programujemy.
• Hierarchiczna struktura plików to katalogi (katalog główny, bieżący,
podkatalogi) i pliki w katalogach.
• Katalog jest to taki plik, który może zawierać inne pliki.
Model pliku w programowaniu
• W pewnym uproszczeniu każdy plik możemy wyobrazić
sobie jako ciąg kolejnych elementów. Za ostatnim
znajduje się znacznik końca pliku.
• Element który zostanie przeniesiony z lub do RAM przy
kolejnym odczycie lub zapisie wskazywany jest przez
wskaźnik pliku stojący na początku tego elementu.
• Wskaźnik końca może być realizowane na różne
sposoby. Czasami jest to wyróżniona wartość (inna niż
dowolna przechowywana w pliku informacja) zapisana
na końcu pliku, a w innych rozwiązaniach informacja o
końcu pliku zapisana jest w strukturze systemu zbiorów
zwanej FAT (ang. file allocation table).
Typy plików
• Ze względu na organizacje danych
wewnątrz pliku (na poziomie logicznym),
będziemy rozróżniać dwa typy plików: pliki
binarne i pliki tekstowe.
Pliki tekstowe
• Pliki tekstowe zasługują na szczególną uwagę, gdyż sposób
kodowania informacji w tych plikach jest najbardziej
rozpowszechnionym standardem, a także dlatego, że w wielu
językach programowania stanowią one osobny wyróżniony typ
danych z dodatkowym zestawem poleceń.
• Plik tekstowy to ciąg znaków z podziałem na wiersze. Można
powiedzieć, że podstawową jednostką danych w pliku tekstowym
jest jeden znak, co w plikach zakodowanych w ASCII przekłada się
na 1 bajt (nie zawsze).
• Koniec wiersza kodowany jest na różne sposoby. Np. w systemie
UNIX znak przejścia do nowego wiersza, a w systemie MS Windows
koniec wiersza oznaczony jest dwoma znakami przejściem do
nowego wiersza i powrotem karetki.
• Są ważne.
Pliki binarne
• Plik binarny to ciąg bajtów.
• Inaczej można powiedzieć, że jest to plik, w którym
rekord logiczny ma rozmiar jednego bajta.
• Dane zawarte w pliku binarnym zawsze traktowane są
jak ciąg bajtów, bez względu na rodzaj zapisanej w pliku
informacji.
• Każdy plik możemy potraktować jak plik binarny.
• Musi być program, który te bajty interpretuje (w plikach
tekstowych wszystkie bajty są znakami drukowalnymi
(poza znakiem przejścia do nowego wiersza).
Rodzaje dostępu do pliku
• Budowa pamięci masowej może też
powodować ograniczenia w sposobie
dostępu do poszczególnych części pliku.
• Ze względu na te ograniczenia pliki
możemy podzielić na:
– pliki sekwencyjne,
– pliki o dostępie bezpośrednim.
Pliki o dostępie sekwencyjnym
• Pliki o dostępie sekwencyjnym
charakteryzują się ograniczoną swobodą
poruszania się po rekordach.
• Aby przeczytać k-ty rekord trzeba
przeczytać poprzedzające go k-1
rekordów.
• Dodatkowo możemy przeskoczyć na
początek albo na koniec pliku, ale nigdy w
dowolne miejsce wewnątrz pliku.
Pliki o dostępie bezpośrednim
• Pliki o dostępie bezpośrednim charakteryzują
się tym, że rekordy logiczne są ponumerowane i
można odczytywać je w dowolnym porządku
dzięki operacji pozwalającej na przeskoczenie
do rekordu o podanym numerze.
• W tego rodzaju plikach wszystkie rekordy
logiczne muszą mieć taki sam rozmiar.
• Pliki o dostępie bezpośrednim realizuje się na
takich urządzeniach, w których łatwo można się
fizycznie dostać do dowolnego miejsca,
niezależnie od jego położenia. Przykładami
takich urządzeń są twarde dyski, pamięci flash.
Schemat przetwarzanie plików
•
•
•
•
Otwarcie pliku,
Operacje na danych w pliku,
Zamknięcie pliku.
Uwaga1: w języku C++ za pracę z plikami
odpowiada biblioteka fstream. Zawiera ona
predefiniowane stałe i zdefiniowane funkcje
ułatwiające operacje na plikach.
• Uwaga2: ważne typy danych związanych z
plikami:
– fstream – dowolny pliki,
– ofstream – plik do zapisu,
– ifstream – plik do odczytu.
Przykład
#include <fstream>
#include <iostream>
using namespace std;
int main(){
ofstream strumien;
//definicja strumienia
strumien.open("plik.txt");
//wskazanie pliku
strumien << "Zapis do pliku"; //przeprowadzenie operacji (zapis)
strumien.close();
//zamknięcie strumienia (następuje zapis do pliku, strumień jest buforowany)
}
Uwaga: Nie zamkniecie pliku może prowadzić do utraty danych.
Dlaczego? – bo zapis jest buforowany.
Co trzeba zrobić, by pracować z
plikami w C++
• Aby korzystać z plików należy dołączyć plik nagłówkowy fstream.h
dyrektywą include:
– #include <fstream>
• Aby otworzyć plik do pisania, należy zdefiniować obiekt klasy ofstream
(output file stream), np.
– ofstream plikwy(”nazwapliku”,ios::out);
• Użyte argumenty oznaczają:
– nazwapliku – nazwę pliku,
– ios::out – tryb otwarcia tego pliku.
• Do odczytu to ios::in
• Innym trybem może być ios::app – dla dodawania nowych elementów
do pliku.
• Gdy chcemy dopisywać nowe elementy, to należy użyć trybu ios::app.
• Gdy chcemy by plik był pusty po utworzeniu, to używamy ios::trunc,
• Typ pliku binarny to ios::binary, a tekstowy to ios::text.
Sprawdzenie, czy operacja
otwarcia pliku się udała
• Dobrym zwyczajem jest sprawdzenie (przed próbą pisania lub
czytania), czy plik został pomyślnie otwarty.
• Można to zrobić następująco:
if (!plikwy)
{ // nie udało się otworzyć pliku
cerr<<”nie można otworzyć pliku wyjściowego”;
exit (-1);
}
• cerr jest standardowym urządzeniem na które są wysyłane
komunikaty o błędach. Domyślnie jest to ekran (podobnie jak i cout).
• Sprawdzać można także za pomocą funkcji:
– good();
– is_open();
– Sa to funkcje, które zwracają prawdę lub fałsz
Przykład
fstream plik;
plik.open( "nazwa_pliku.txt", std::ios::in | std::ios::out );
if( plik.good() )
cout << "Uzyskano dostep do pliku! ";
else
cout << " Nie uzyskano dostepu ";
Przykład
• Przykład programu pobierającego znaki ze standardowego strumienia
wejściowego i wysyłającego do pliku kopia (domyślnym typem pliku
jest plik tekstowy)
#include <iostream>
#include <fstream>
main()
{
ofstream plikwy(”nazwapliku”,ios::out);
if (!plikwy)
{ // nie udało się otworzyć pliku
cerr<<”nie można otworzyć pliku wyjściowego”;
exit (-1);
}
char zn;
while (cin.get(zn))
plikwy.put(zn);
return 0;
}
Czytanie z pliku
• nazwa >> zmienna; //wczytanie zmiennej
• getline( plik, dane ); //wczytanie wiersza
• istream plik.getline( char * odczytane_dane, streamsize ilosc_danych,
char znak_konca_linii );
Parametry oznaczają kolejno:
– (odczytane_dane) wskaźnik zmiennej, do której mają zostać wczytane dane z
pliku;
– (ilosc_danych) maksymalna ilość znaków jakie mogą zostać zapisane do
zmiennej;
– (znak_konca_linii) parametr jest opcjonalny. Umożliwia zmianę znaku końca
linii.
• Przykład:
– fstream plik( „dane.txt", ios::in );
– char dane[ 255 ];
plik.getline( dane, 255 );
Czytanie blokowe z pliku
• Czytanie blokowe z pliku jest bezpieczne dla
typu binary.
– istream plik.read( char * bufor, streamsize
rozmiar_bufora );
• Bufor nie musi być wypełniony cały danymi. Do
sprawdzenia, ile danych było w buforze służy
funkcja gcount(),
• Przykład:
– fstream plik( "nazwa_pliku.txt", std::ios::in );
– char bufor[ 1024 ];
plik.read( bufor, 1024 );
– cout << "Wczytano " << plik.gcount() << " bajtów”;
Zapisywanie danych do pliku
• ostream plik.write( const char *bufor,
streamsize ilosc);
fstream plik( "plik.txt", ios::out );
string napis;
getline(cin, napis );
plik.write( &napis[ 0 ], napis.length() ); //zapisuje
//dane poczynając od 0 indeksu
Poruszanie się po pliku
• Do tego celu służą funkcje seekg() i seekp()
– seekg() ustawia wewnętrzny wskaźnik pliku dla funkcji
odczytujących dane;
– seekp() ustawia wewnętrzny wskaźnik pliku dla funkcji
zapisujących dane.
• Uzycie:
– istream p.seekg( streamoff offset, ios_base::seekdir kierunek );
ostream p.seekp( streamoff offset, ios_base::seekdir kierunek );
• Dla kierunek sa możliwości:
– ios_base::beg - Przesunięcie względem początku pliku
(domyślne)
– ios_base::cur - Przesunięcie względem aktualnej pozycji
– ios_base::end - Przesunięcie względem końca pliku
Poruszanie się po pliku
• Aby sprawdzić, czy skok na nową pozycję
zakończył się sukcesem możemy dokonać
tego na dwa sposoby:
– Sprawdzić aktualną pozycję pliku i porównać
z tą, którą chcieliśmy otrzymać;
– Wywołać funkcję fail(), należącą do klasy
fstream. Jest ona postaci: fail(); i zwraca
wartość logiczną.
• Do testowania końca pliku służy funkcja
plik.eof()
//--------------------------------------------------------------------------#include <conio.h>
#include <iostream.h>
#include <fstream.h>
int main(int argc, char* argv[])
{
ofstream zapis;
ifstream odczyt;
zapis.open("c:\\plik1.doc",ios::out);
if ( !zapis )
{
cerr << "Nieudane otwarcie pliku do zapisu\n";
getch();
exit( 1 );
}
zapis << "To jest pierwszy wiersz tekstu, \n";
zapis << "a to drugi.\n";
char napis[20];
cout<<"napis=";
cin>>napis;
zapis << napis;
zapis.close();
odczyt.open("c:\\plik1.doc",ios::in);
char znak;
while ((znak=odczyt.get())!=EOF)
cout<<znak;
cout<<"Koniec";
odczyt.close();
getch();
return 0;
Przykład – czytanie znaków i wyrazów
#include <vcl.h>
#include <iostream.h>
#include <fstream.h>
#include <conio.h>
#pragma hdrstop
#pragma argsused
int main(int argc, char* argv[])
{
float liczba=1.0;
fstream plik("c:\\liczby.txt",ios::in | ios::out | ios::trunc);
while(true){
cout << "Podaj liczbe: ";
cin >> liczba;
if(liczba!=0) plik << liczba << " ";
else break;
}
plik.close();
plik.open("c:\\liczby.txt");
while(!plik.eof()){
plik>>liczba;
if(!plik.fail()) cout<<liczba<<" "; //ostatnim znakiem jest spacja, wiec eof jest dopiero po
spacji
}
plik.close();
cout<<endl;
getch();
return 0;
}
Przykład – czytanie liczb
•
Uwaga: Funkcja int fail() - funkcja zwraca wartość niezerową np. gdy
oczekujemy wartości liczbowej a otrzymamy tekst
Wszystkie tryby otwarcia pliku
• app - otwarcie pliku do dopisywania, dane dołączane są do końca
pliku.
• ate - otwarcie pliku z ustawieniem wskaźnika plikowego na końcu
pliku.
• in - otwarcie pliku do odczytu (tryb domyślny klasy ifstream)
• out - otwarcie pliku do zapisu (tryb domyślny klasy ofstream)
• binary - otwarcie pliku w trybie binarnym. Pliki obsługiwane są za
pomocą klas ifstream i ofstream otwierane są domyślnie w trybie
tekstowym,
• trunc - Otwarcie pliku ze zniszczeniem jego poprzedniej zawartości.
Tryb ten jest domyślny, o ile nie został użyty specyfikator app lub
ate.
Zapis do plików binarnych
#include <iostream.h>
#include <fstream.h>
int main(int argc, char* argv[])
{
struct firmy{
char* nazwa;
float przychod;
};
//zapis danych
firmy firma;
ofstream plik1("dane.dat",ios::binary | ios::app| ios::out);
firma.nazwa="test";
firma.przychod=23;
// zapis do pliku struktury firma przekonwertowanej (przeinterpretowanej) na ciąg znaków
// ponieważ jest ios::app to dopisujemy na końcu pliku
plik1.write(reinterpret_cast<char*>(&firma),sizeof(firma));
plik1.close();
plik.close();
getchar();
return 0;
}
//---------------------------------------------------------------------------
#include <iostream.h>
#include <fstream.h>
int main(int argc, char* argv[])
{
struct firmy{
char* nazwa;
float przychod;
};
//odczyt danych
int i=1;
ifstream plik("dane.dat",ios::binary| ios::in);
while (1)
{
// odczyt z pliku ciągu znaków i przekonwertowanie (przeinterpretowanie) na ciąg strukturę firma
plik.read(reinterpret_cast<char*>(&firma),sizeof(firma));
//z plikiem związany jest znacznik pliku. Znacznik pliku może by na końcu,
//ale funkcja eof() zwróci wartosc false.
//Funkcja zwróci wartosc true gdy znacznik jest na końcu pliku i jeszcze cos czytamy.
if(plik.eof()) break;
//tu jestesmy, gdy czytanie powiodło się, więc to co przeczytalismy wypisujemy.
cout<<i<<" "<<firma.nazwa<<" "<<firma.przychod<<endl;
i++;
}
Odczyt danych z
pliku binarnego
plik1.close();
plik.close();
getchar();
return 0;
}
Operator reinterpret_cast
• Zapis <T> oznacza szablon – jest to pojecie z programowania
obiektowego.
• reinterpret_cast< T > (arg) to operator, którego celem jest konwersja
przez zamianę typów, które są niepewne (nie mamy pewności co do
konwersji niejawnej dokonanej przez kompilator) lub zależne od
implementacji.
• W deklaracji, reinterpret_cast< T > (arg) , T musi być wskaźnikiem,
referencją, typem arytmetycznym, wskaźnikiem na funkcję lub
wskaźnikiem na element.
• Wskaźnik może być całkowicie przekonwertowany na typ wbudowany.
• Wbudowany arg może być przekonwertowany na wskaźnik. Konwersja
wskaźnika na typ wbudowany i na odwrót na ten sam typ wskaźnikowy
dostarcza oryginalną wartość.
• Możliwe jest użycie do konwersji jeszcze nie zdefiniowanej klasy
wskaźnika lub referencji.
• Wskaźnik na funkcje może być poprawnie przekonwertowany na
wskaźnik na obiekt pod warunkiem, że dostarczany wskaźnik na
obiekt, posiada wystarczającą ilość bitów do przechowania wskaźnika
na funkcję. Wskaźnik na obiekt może być poprawnie
przekonwertowany na wskaźnik na funkcję tylko jeśli wskaźnik na
funkcję jest wystarczająco duży aby przechować wskaźnik na obiekt.
Modyfikacja danych w pliku
binarnym
• Algorytm:
– Ustawiamy się przed rekordem, który chcemy
zmodyfikować za pomocą polecenia:
• plik1.seekp(sizeof(struct firmy)*(k-1));
– Odczytujemy rekord (polecenie read),
– Ustawiamy poprawne dane,
– Zapisujemy rekord (polecenie write).
Usunięcie wybranego rekordu
• Rozwiązanie z wykorzystaniem pomocniczego pliku:
– określamy, który rekord chcemy usunąć, niech będzie to rekord
o numerze k,
– Przepisujemy wszystkie rekordy od numeru 0 do numeru k-1,
– Wczytujemy rekord o numerze k (nie przepisujemy go),
– Przepisujemy pozostałe rekordy o numerach k+1 do końca,
– Zamykamy pliki (mamy poprawne dane w tymczasowym i
oryginalny zawiera wszystkie rekordy),
– Otwieramy oryginalny i czyścimy go (trunc),
– Przepisujemy wszystkie rekordy z pliku tymczasowego do
oryginalnego,
– Usuwamy dane z tymczasowego.
Pliki jako parametry funkcji main
•
Nagłówek funkcji głównej:
–
–
–
•
int main(int argc, char* argv[]);
argc – liczba argumentów,
argv[] – tablica argumentów.
Przykładowy program:
#include <fstream.h>
int main(int argc, char* argv[]) {
char znak;
if(argc != 2)
{
cerr <<”Napisz: czytaj <nazwa-pliku>\n”;
return 1;
}
ifstream plik=open(argv[1]);
if(!plik)
{
cerr <<”Nieudane otwarcie pliku do odczytu\n ”;
return 1;
}
while(!plik.eof())
{
plik.get(znak);
cout << znak;
}
return 0;
}