Operacje na plikach

Dane do programów wprowadzaliśmy do tej pory z klawiatury (lub generowaliśmy liczby losowe).
Wyniki obliczeń wypisywane były na ekranie komputera.
Duże ilości danych trudno jednak wpisywać ręcznie i w większości przypadków pobieramy je z plików zapisanych na dysku.
Podobnie jest z wynikami obliczeń – można je zapisać w postaci pliku i przeglądać w dowolnym momencie na ekranie komputera.

Dane do plików zapisujemy sekwencyjnie, to znaczy, że muszą być zapisywane po kolei.
Aby zapisać element setny należy najpierw zapisać 99 elementów.
Podobnie jest z odczytywaniem.
Plik należy otworzyć (do zapisu lub odczytu) OPEN i po zakończeniu działań koniecznie go zamknąć CLOSE.
Plik można otworzyć:

  • do odczytu IFSTREAM
  • do zapisu OFSTREAM
  • zapisu i odczytu FSTREAM
Dane do pliku wprowadza się funkcją WRITE i strumieniem <<.
Dane odczytuje się z pliku funkcją READ i strumieniem >>.
Funkcja logiczna EOF pozwala sprawdzić czy ciągnięto już koniec pliku.
Kolejny zapis do tego samego pliku powoduje usunięcie poprzednich danych!

Zapis do pliku (string): napisy, liczby, konsola

Do obsługi konsoli służyła biblioteka iostream, do obsługi strumieni plikowych należy zadeklarować bibliotekę fstream.

    #include <iostream>
    #include <fstream>
    using namespace std;
    
    int main() {
        ofstream ZAPIS("dane.txt");
        int l=128;
        string t="Ala ma kota";
        ZAPIS << "piszemy do pliku" << endl;
        ZAPIS << l << endl;
        ZAPIS << t << endl;
        string tekst;
        cout << "wpisz tekst:";
        cin >> tekst;
        ZAPIS << tekst<< endl;
        ZAPIS.close();
        return 0;
   }

W pliku dane.txt znajdują się 4 wiersze tekstów, które można odczytać zwykłym notatnikiem

    piszemy do pliku
    128
    Ala ma kota
    cześć

ofstream - strumień o nazwie ZAPIS kojarzymy z plikiem ”dane.txt” na dysku w katalogu bieżącym.
Możemy zapisywać liczby i teksty, identycznie jak na ekranie konsoli za pomocą polecenia COUT.
Bezwzględnie należy pamiętać o zamknięciu strumienia plikowego za pomocą polecenia close.

W pliku tekstowym "dane.txt" pojawią się 4 wiersze tekstu.
Problemem mogą być polskie znaki diakrytyczne, zapisywane przez Windows w kodzie OEM 852.

Podczas wczytywania tekstów z konsoli posługujemy się typem string, który reaguje standardowo na spację i enter.
Typu char należy używać raczej do wczytywania pojedynczych znaków.


Zapis do pliku (char): klawiatura-plik tekstowy
Do wczytywania znaków używamy funkcji getch() z biblioteki conio.h. Program będzie wczytywał znaki z klawiatury, zapisywał je do pliku dyskowego. Pętla zapisu kończy się w momencie naciśnięci klawisza ESC (kod 27).

    ofstream klaw("klaw.txt");
    char c;
    do {
        c=getch();
        if (c==13) {
            cout << endl;
            klaw << endl;
        }
        if (c!=27){
            cout << c;
            klaw << c;
        }
    } while (c!=27);
    klaw.close();

Zwróć uwagę, w jaki sposób program obsługuje klawisz ENTER - sami musimy zadbać o przejście do nowego wiersza i klawisz ESC - nie chcemy aby program wstawiał „dziwny” znaczek na końcu.


Zapis do pliku: liczby i kolumny
Pisanie liczb do pliku wygląda tak samo, jak pisaliśmy je na ekranie w konsoli.

    #include <stdlib.h>
    ...
    ofstream Pliczby("liczby.txt");
    int liczba;
    for (int i=0; i<10; i++) {
        liczba=rand() % 6 + 1;
        Pliczby << liczba << endl;
        cout << liczba << endl;
    }
    Pliczby.close();
    

W pliku liczby.txt znajdują się losowe liczby

    6
    6
    5
    5
    6
    5
    1
    1
    5
    3

Zapis w kolumnach - tabliczka mnożenia

    ofstream plik("mnoz.txt");
    int mnoz;
    for (int i=1; i<=10; i++) {
        for (int j=1; j<=10; j++) {
            mnoz=i*j;
            plik.width(4);
            cout.width(4);
            plik << mnoz;
            cout << mnoz;
        }
        plik << endl;
        cout << endl;
    }
    plik.close();

W pliku mnoz.txt znajdują się wyniki mnożenia, w takim samym układzie, jak na ekranie konsoli

Odczyt z pliku

Jeżeli znamy dokładnie ilość danych do odczytania, możemy np. korzystać z pętli FOR.
W wielu przypadkach jednak nie znamy długości pliku wówczas posługujemy się funkcją eof() stosowaną w pętli while, co można przeczytać: „dopóki nie osiągnęliśmy końca pliku …

    ifstream PLIK("plik.txt");
    while (!PLIK.eof()){
        ...
    }
    PLIK.close();

Odczyt z pliku znak po znaku
Podczas czytania danych z plików pojawiają się problemy podobne do tych podczas zapisywania znaków – pomijane są spacja i enter (zarówno dla typu string i char).

    ifstream ODCZYT("dane.txt");
    char z;
    while (!ODCZYT.eof()){
        ODCZYT >> z;
        cout << z;
    }
    ODCZYT.close();
Przedstawiony fragment programu czyta pojedyncze znaki z pliku ”dane.txt” i wyświetla na ekranie, bez spacji i przejść do nowego wiersza.

Jeżeli użyjemy funkcji GET ominiemy problemy z tzw. "białymi znakami".
ODCZYT.get(z);

    ifstream ODCZYT("dane.txt");
    char z;
    while (!ODCZYT.eof()){
        ODCZYT.get(z);
        cout << z;
    }
    ODCZYT.close();

Odczyt z pliku: czytanie wierszami
Instrukcja getline odczytuje całe wiersze, łączenie ze spacjami.
Jeśli odczytujemy w ten sposób liczby i teksty, konieczne będzie wyodrębnianie z tekstu fragmentów i konwersja.

    ifstream Wodczyt("dane.txt");
    string wiersz;
    while (!Wodczyt.eof()){
        getline(Wodczyt,wiersz);
        cout << wiersz << endl; 
    }
    Wodczyt.close();

Odczyt z pliku: liczby na ekran i liczby do tablicy
Liczby czytamy dokładnie tak samo jak teksty.
W pliku dane mogą być rozdzielone spacjami lub każda może wystąpić w nowym wierszu.
Spacja i nowy wiersz zostaną potraktowane identycznie - odczytane jako nowa watrość.

    ifstream ODCZYT("liczby.txt");
    int li;
    while (!ODCZYT.eof()){
        ODCZYT >> li;
        cout << li;
    }
    ODCZYT.close();
    int tabl[20];
    int ile=0;
    ifstream oplik("liczby.txt");
    if(oplik.is_open()) {
       while(!oplik.eof()) {
            oplik >> tabl[ile];
            ile++;
        }
    }
    else cout << "Brak pliku";
    oplik.close();
    
    for (int i=0; i < ile; i++)
        cout << tabl[i];

Warto zwrócić uwagę na sprawdzenie poprawności otwarcia pliku.
Jeśli takiego pliku nie ma - funkcja is_open(), to możemy pominąć wykonywanie programu.
Problemy mogą pojawić się, jeśli plik na końcu zawiera pusty wiersz - do tablicy zostanie wczytany jeden więcej element - zero!

Dopisywanie do pliku

Do istniejącego pliku możemy dopisywać na końcu pliku nowe dane:

    fstream dopis;
    dopis.open("dane.txt", ios::app);
    dopis << "Tekst dopisany na końcu";
    dopis.close();

Zadania

PALINDROMY
Palindromem nazywamy słowo, które czytane od lewej i od prawej strony jest takie samo.
Palindromami są słowa: JABFDFBAJ, HAJAHAJAH, ABBA.
Słowo JANA nie jest palindromem.

zadanie:

Wygeneruj plik tekstowy palindrom.txt, który zawierał będzie 1000 słów o długościach od 2 do 25 znaków, każde w nowym wierszu, składających się z wielkich liter A, B, C, D, E, F, G, H, I, J.

Aby plik zawierał jakieś „ciekawe” palindromy zmodyfikuj program w następujący sposób:
- co 30 generowany wyraz sprawdź, czy jest krótszy niż 13 znaków
- jeśli tak, to doklej do niego ten sam odwrócony wyraz, np. DCEAB i doklejamy BAECD

rozwiązanie
    ofstream PALzapisz("palindrom.txt");
    for (int i=1; i <= 1000; i++){
        int ile=rand() % 24 + 2;    //ile znaków w wyrazie
        string pal="";
        
        //sklejamy wyrazy z ile znaków
        for (int j=1; j <= ile; j++){
            char zna=char(rand() % 10 +65); //losowe znaki o kodach 65-74
            pal=pal+zna;		
        }

        //co 30 wyraz i jeśli krótszy niż 13 znaków
        //robimy z wyrazu palindrom
        int dl=pal.length();
        if (i%30 == 0 && dl <= 12){
            for (int k=0; k < dl; k++)
                pal=pal+pal[dl-k-1];
        }
        //zapisujemy wyrazy w pliku
        PALzapisz << pal << endl;
        cout << pal << endl;
    }
    PALzapisz.close();
    

Plik tekstowy palindrom.txt zawiera wygenerowane słowa (maksymalnie 1000).
Odczytaj je i sprawdź, które są palindromami.
Wynik wyświetl na ekranie oraz zapisz w pliku tekstowym palindrom-wynik.txt.

rozwiązanie
    // odczytywanie pliku i sprawdzanie palindromów
    ifstream PALodczyt("palindrom.txt");
    string wyraz;
    while (!PALodczyt.eof()){
        PALodczyt >> wyraz;
        
        //całkowita połowa długości
        int dl=wyraz.length();
        int dl2=dl/2;
        bool palindrom=true;
        //sprawdzamy od początku do połowy
        //i porównujemy ze znakami od końca
        for (int i=0; i < dl2; i++)
            //jeśli którakilwiek litera się nie zgadza
            //to nie jest palindrom
            if (wyraz[i] != wyraz[dl-1-i]) 
                palindrom=false;
        if (palindrom) 
            cout << wyraz << endl;
    }
    PALodczyt.close();

Przykładowe słowa będące palindromami z wygenerowanego pliku:

            FBJGGJBF
            EE
            CBBC
            ICI
            ECGECFFCEGCE
            EIFBDHHDBFIE
            FF
            HGHAEFEIGIBBIGIEFEAHGH

HASŁA
Wygeneruj plik tekstowy hasla.txt, zawierający 200 słów, składających się z małych liter alfabetu angielskiego, każde w osobnym wierszu, których długość wynosi od 3 do 10 znaków.

rozwiązanie
    //generator pliku z hasłami
    //3-10 małych liter (ASCII 97-122)

    ofstream HASzapis("hasla.txt");
    string napis;
    for (int i=1;i<=200;i++){
        napis="";
        int ile=rand() % 8 +3;  //ile znaków od 3 do 10
        for  (int j=1;j<=ile;j++)
            //losujemy małe litery kodów ASCII
            napis+=rand() % (122-97+1)+97;
        HASzapis << napis << endl;
    }
    HASzapis.close();

Odpowiedz na poniższe pytania:

  1. ile jest haseł z parzystą - nieparzystą liczbą znaków
    rozwiązanie
        //parzysta-nieparzysta
        int PA=0;   //parzyste
        int NP=0;   //nieparzyste
        ifstream odczytA("hasla.txt");
        string napisA;
        while (!odczytA.eof()){
            odczytA >> napisA;
            //sprawdzamy długości napisów za pomocą modulo
        	if (napisA.length() % 2 ==0) 
                PA++; 
            else 
                NP++;
        }
        odczytA.close();
        cout << "   parzyste: " << PA << endl;
        cout << "nieparzyste: " << NP << endl;
  2. wypisz hasła będące palindromami - czytane wspak dadzą taki sam rezultat (np. kajak)
    rozwiązanie
        //palindromy
        cout << "PALINDROMY" << endl;
        ifstream odczytB("hasla.txt");
        string napisB;
        while (!odczytB.eof()){
            odczytB >> napisB;
            int dl=napisB.length();
            int dl2=dl/2;
        
            //na początku każdy wyraz jest palindromem
            bool palindrom=true;
            for (int i=0; i < dl-2; i++)
                //porównujemy znak z początku ze znakiem od końca
                if (napisB[i]!=napisB[dl-1-i]) 
                    //jeśli jakikolwiek znak różny to
                    //nie jest palindromem
                    palindrom=false; 
    
                // jest palindromem, bo porównało
                //i wszystkie znaki były zgodne
    
                if (palindrom) 
                    cout << napisB << endl;
        }
        odczytB.close();
  3. wypisz hasła, w których występują obok siebie dwa identyczne znaki (np. kajjak)
    rozwiązanie
        //dwa identyczne znaki
        cout << "ASCII 220"<< endl;
        ifstream odczytC("hasla.txt");
        string napisC;
        while (!odczytC.eof()){
            odczytC >> napisC;
            int dl=napisC.length();
            //sprawdzamy do przedostatniego znaku
            //aby nie zawiesił się na ostatnim [i+1]
            for (int i=0;i < dl-2;i++)
                //dwa znaki obok siebie takie same
                if (napisC[i] == napisC[i+1]) {
                    cout << napisC << endl;
                    break;
                }
        }
        odczytC.close();

CIĄGI
Wygeneruj plik tekstowy ciagi.txt, zawierający 1000 słów składających się z trzech liter A, B, C, każde słowo w osobnym wierszu, litery mogą się powtarzać.

rozwiązanie
    ofstream CIAGzapis("ciagi.txt");
    string napis;
    for (int i=1; i<=1000; i++){
        napis="";
        //losowe znali ABC
        napis=rand() % 3+65; 
        napis+=rand() % 3+65;
        napis+=rand() % 3+65;
        CIAGzapis << napis << endl;
    }
    CIAGzapis.close();

Odpowiedz na poniższe pytania:

  1. Ile jest słów składających się z takich samych liter (np. BBB)?
    rozwiązanie
            ifstream CIAGodczyt("ciagi.txt");
            int licznik = 0; 
            string napis;
            while (!CIAGodczyt.eof()) {
                CIAGodczyt >> napis;
                
                //sprawdzamy zerowy z pierwszym i zerowy z drugim
                //nie potrzeba sprawdzać pierwszego z drugim
                if (napis[0]==napis[1] && napis[0]==napis[2])
                    licznik++;
            }
            CIAGodczyt.close();
  2. Ile jest palindromów (np. ACA lub BBB)?
    rozwiązanie
            ifstream CIAGodczyt("ciagi.txt");
            int licznik = 0; 
            string napis;
            while (!CIAGodczyt.eof()) {
                CIAGodczyt >> napis;
                
                //wystarczy sprawdzić zerowy z drugim
                if (napis[0]==napis[2])
                    licznik++;
            }
            CIAGodczyt.close();

CYFRY
Wygeneruj plik tekstowy cyfry.txt, zawierający 1000 liczb naturalnych, mniejszych niż 10000, każda w osobnym wierszu.

rozwiązanie
        ofstream CYFRYzapis("cyfry.txt");
        for (int i=1; i <= 1000; i++){
            int c=rand() % 10000 + 1;
            CYFRYzapis << c;
            //nowy wiersz bez ostatniego
            if (i < 1000) CYFRYzapis << endl;
        }
        CYFRYzapis.close();

Odpowiedz na poniższe pytania:

  1. Jaka jest średnia arytmetyczna tych liczb.
    rozwiązanie
            ifstream CYFRYodczytA("cyfry.txt");
            int liczba, suma=0, ilosc=0;
            while (!CYFRYodczytA.eof()){
              CYFRYodczytA >> liczba;
              ilosc++;
              suma += liczba;
            }
            CYFRYodczytA.close();
    
            //aby wynik był rzecywisty, wykonujemy daiałanie: (ilosc*1.0)
            cout << "Średnia=" << suma/(ilosc*1.0) << endl;
    
  2. ile jest parzystych i nieparzystych liczb.
    rozwiązanie
            ifstream CYFRYodczytB("cyfry.txt");
            int liczbaB;
            int CP=0;//licznik parzystych
            int CN=0;//i nieparzystych
            while (!CYFRYodczytB.eof()){
              CYFRYodczytB >> liczbaB;
                  if (liczbaB % 2 == 0) CP++;
                  else CN++;
            }
            CYFRYodczytB.close();
            cout << "Parzyste=" << CP << endl;
            cout << "nieParzyste=" << CN << endl;
    
  3. Jaka liczba ma największą sumę cyfr?
    rozwiązanie
            
  4. Która liczba powtarza się najczęściej?
    rozwiązanie
            

DWÓJKOWE
Wygeneruj plik tekstowy napisy.txt, zawierający 1000 napisów od 20 do 100 znaków.
Napisy to liczby binarne składające się ze znaków ‘0’ lub ‘1’.

rozwiązanie
ofstream NDzapis("liczby.txt");
        int ile;
        int znak;
        string napis;
        
        for (int j=1;j<=1000;j++){
            ile=rand() %80 + 20; //20-100
            if(rand()%150 == 10) napis = "00000000000";
            else if(rand()%200 == 10) napis = "1111111111111111111111111111";
            else if(rand()%200 == 10) napis = "00000000000000000000000000000000000000000010000000000000100000000000000000000100";
            else {
                napis="";
                for (int i=1;i<=ile;i++){
                    znak=rand() % 2;//0 lub 1 losowo
                    //znak ASCII '0' lub '1'
                    napis=napis+char(znak+48); 
                }
            }
            NDzapis << napis << endl;
        }
        NDzapis.close();

W pliku napisy.txt znajduje się 1000 napisów o długościach od 2 do 16 znaków, każdy napis w osobnym wierszu. W każdym napisie mogą wystąpić jedynie dwa znaki: „0” lub „1”. Odpowiedz na poniższe pytania: ekran i plik

  1. ile jest napisów o parzystej długości
    rozwiązanie
    int zadanie1() {
            int parzyste=0;
            string liczba;
        
            ifstream plik("liczby.txt");
        
            if(plik.is_open()) {
                while(!plik.eof()) {
                    plik >> liczba;
                    if(liczba.size() % 2 == 0) parzyste++;
                }
                plik.close();
            } else {
                cout << "Problem z otwarciem pliku ilczby.txt ";
            }
            return parzyste;
        }
  2. ile jest napisów, które zawierają taką samą liczbę zer i jedynek
    rozwiązanie
    int zadanie2() {
                int zera=0, jedynki=0, ilosc=0;
                string liczba;
            
                ifstream plik("liczby.txt");
            
                if(plik.is_open()) {
                    while(!plik.eof()) {
                        zera=0;
                        jedynki=0;
                        plik >> liczba;
                        for(int i=0; i<liczba.size(); i++) {
                            if(liczba[i] == '0') zera++;
                            if(liczba[i] == '1') jedynki++;
                        }
                        if(zera == jedynki) ilosc++;
                    }
                    plik.close();
                } else {
                    cout << "Problem z otwarciem pliku ilczby.txt ";
                }
                return ilosc;
        }
  3. ile jest napisów składających się z samych zer, z samych jedynek
    rozwiązanie
    void zadanie3() {
                int zera=0, jedynki=0, suma=0;
                string liczba;
                
                ifstream plik("liczby.txt");
            
                if(plik.is_open()) {
                    while(!plik.eof()) {
                        suma = 0;
                        plik >> liczba;
                
                        for(int i=0; i<liczba.size(); i++) {
                            suma += int(liczba[i]) - 48; 
                        }
                        if(suma == 0) zera++;
                        if(suma == liczba.size()) jedynki++;
                    }
                    plik.close();
                    cout << "W pliku liczby.txt:\n" << zera << " - same zera\n" << jedynki << " - same jedynki.";
                } else {
                    cout << "Problem z otwarciem pliku ilczby.txt ";
                }
            }
  4. podaj ile jest napisów 20-znakowych, 21-znakowych itd.
    rozwiązanie
    void zadanie4() {
                int tablica[80];
                for (int i=0; i < 80; i++) tablica[i]=0;
                
                string liczba;
                
                ifstream plik("liczby.txt");
            
                if(plik.is_open()) {
                    while(!plik.eof()) {
                        plik >> liczba;
                        tablica[liczba.size()-20]++;
                    }
                    plik.close();
                    cout << "W pliku liczby.txt:\n";
                    for(int i=0; i < 80; i++) {
                        cout << i+20 << " znakow: " << tablica[i] << ", ";
                    }
                } else {
                    cout << "Problem z otwarciem pliku ilczby.txt ";
                }
            }
  5. podaj ile jest napisów z maksymalną ilością jedynek
    rozwiązanie
    int jedynki(string liczba) {
                int ilosc=0;
                for(int i=0; i<liczba.length(); i++) {
                    if(liczba[i] == '1') ilosc++;
                }
                return ilosc;
            }
            
            void zadanie5() {
                int ile=0, ile1, max1=0;
                string liczba;
            
                ifstream plik("liczby.txt");
            
                if(plik.is_open()) {
                    plik >> liczba;
                    ile = 1;
                    max1 = ile1 = jedynki(liczba);
                    while(!plik.eof()) {
                        plik >> liczba;
                        ile0 = jedynki(liczba);
                        if(ile1 == max1) ile++;
                        else if(ile1 > max1) {
                            max1 = ile1;
                            ile = 1;
                        }
                    }
                    plik.close();
                    cout << "W pliku liczby.txt maksymalna ilosc jedynek wynosi: " << max1 << " - takich liczb jest: " << ile;
                } else {
                    cout << "Problem z otwarciem pliku ilczby.txt ";
                }
            }
  6. podaj ile jest napisów z maksymalną ilością zer
    rozwiązanie
    int zera(string liczba) {
                int ilosc=0;
                for(int i=0; i<liczba.length(); i++) {
                    if(liczba[i] == '0') ilosc++;
                }
                return ilosc;
        }
            
            void zadanie6() {
                int ile=0, ile0, max0=0;
                string liczba;
            
                ifstream plik("liczby.txt");
            
                if(plik.is_open()) {
                    plik >> liczba;
                    ile = 1;
                    max0 = ile0 = zera(liczba);
                    while(!plik.eof()) {
                        plik >> liczba;
                        ile0 = zera(liczba);
                        if(ile0 == max0) ile++;
                        else if(ile0 > max0) {
                            max0 = ile0;
                            ile = 1;
                        }
                    }
                    plik.close();
                    cout << "W pliku liczby.txt maksymalna ilosc zer wynosi: " << max0 << " - takich liczb jest: " << ile;
                } else {
                    cout << "Problem z otwarciem pliku ilczby.txt ";
                }
            }

Projekt i wykonanie: Ryszard Rogacz© 1999−2024