mgr inż. Wacław Libront * Bobowa 2019
ZSO Bobowa, ul. Długoszowskich 1, 38-350 Bobowa, tel: 0183514009, fax: 0183530221, email: sekretariat@zsobobowa.eu, www: zsobobowa.eu
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. Plik na dysku można porównać do taśmy magnetofonowej, na której wykonujemy trzy operacje: zapis taśmy, odczyt taśmy, przesunięcie taśmy o jeden element dalej (albo na początek i na koniec).
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 ISTREAM, do zapisu OFSTREAM lub do zapisu lub 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!
Sprawy z plikami zaczynają komplikować się w zależności od tego, jak chcemy potraktować nasz plik: czy jako zbiór liczb, tekstów, znaków i innych danych. Kolejną komplikacją jest możliwość obsługi plików kilkoma sposobami. Na lekcjach będziemy zajmować się wyłącznie zapisem i odczytem za pomocą strumieni, podobnie zresztą jak robiliśmy to w konsoli.
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() { setlocale(LC_ALL, ""); 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 ECK - 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 i posługujemy się funkcję
EOF() i pętlą 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 mogą być rozdzielone spacjami lub każda w nowym wierszu - potraktowane zostaną identycznie.
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
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(); |
PALINDROMY
Palindromem nazywamy słowo, które czytane od lewej i od prawej strony
jest takie samo. Na przykład palindromami są słowa: JABFDFBAJ, HAJAHAJAH, ABBA,
Słowo JANA nie jest palindromem.
01) 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
1) Plik tekstowy palindrom.txt zawiera słowa wygenerowane przez komputer (maksymalnie 1000). Odczytaj je i sprawdź, które są palindromami, Wynik na ekranie i w pliku tekstowym.
HASŁA
02) Wygeneruj plik tekstowy hasla.txt, zawierający 200 słów, składających się z małych liter alfabetu angielskiego, każde w osobnym wierszy, których długość wynosi od 3 do 10 znaków.
2) Plik hasla.txt zawiera hasła używane w pewnej firmie (200). Odpowiedz na poniższe pytania: ekran i plik
a) ile jest haseł z parzystą - nieparzystą liczbą znaków
b) wypisz hasła będące palindromami - czytane wspak dadzą taki sam rezultat (np. kajak)
c) wypisz hasła, w których występuje obok siebie dwa identyczne znaki (np. kajjak)
CIĄGI
03) 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ć.
3) W pliku ciagi.txt znajduje się 1000 wierszy, a w każdym wierszu ciąg o długości trzech liter ze zbioru {A, B, C} -litery mogą się powtarzać. Napisz program, który:
a) obliczy prawdopodobieństwo, że w wierszu będzie ciąg składającym się z takich samych znaków
b) obliczy prawdopodobieństwo, że w wybranym wierszu będzie palindrom
CYFRY
04) Wygeneruj plik tekstowy cyfry.txt, zawierający 1000 liczb naturalnych, mniejszych niż 10000, każda w osobnym wierszu.
4) Plik cyfry.txt zawiera liczby naturalne (maksymalnie 1000).
Odpowiedz na poniższe pytania: ekran i plik
a) wylicz ich średnią
b) ile jest parzystych i nieparzystych liczb
c) podaj liczbę, której suma cyfr jest największa
d) która liczba powtarza się najczęściej
DWÓJKOWE
05) Wygeneruj plik tekstowy napisy.txt, zawierający 1000 napisów od 2 do 16 znaków, każdy w osobnym wierszu, składających się ze znaków ‘0’ i ‘1’.
5) 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
a) ile jest napisów o parzystej długości
b) ile jest napisów, które zawierają taką samą liczbę zer i jedynek
c) ile jest napisów składających się z samych zer, z samych jedynek
d) dla każdej liczby k = 2, 3, ...16 podaj liczbę napisów o długości k znajdujących się w pliku, tzn. ile jest napisów 2-znakowych, 3-znakowych itd.
e) potraktuj każdy napis, jak liczbę dwójkową i wykonaj polecenia z zadania 4
Przykładowe rozwiązania zadań
Jedynie ciężka umysłowa praca i zrozumienie działania
programu może nauczyć programowania!
//zadanie 1 - PALINDROM // Tworzenie pliku cout << "PALINDROMY" << endl; ofstream PALzapisz("palindrom.txt"); for (int i=1; i <= 1000; i++){ //ile znaków w wyrazie int ile=rand() % 24 + 2; string pal=""; //sklejamy wyrazy z ile znaków for (int j=1; j <= ile; j++){ //losowe znaki o kodach 65-74 char zna=char(rand() % 10 +65); pal=pal+zna; } //co 30 wyraz i jeśli krótszy niż 1 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(); // 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(); |
//zadanie 2 //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=""; //ile znaków od 3 do 10 int ile=rand() % 8 +3; 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(); //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; //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 < dl2; 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(); //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; } odczytC.close(); |
//zadanie 3 - CIĄGI ABC //tworzenie pliku do zadania - jednorazowo 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(); //takie same litery w ciągu ifstream CIAGodczytA("ciagi.txt"); int licznikA=0;//liczymy identyczne int licznikB=0;//liczymy palindromy int licznikR=0;//liczymy łącznie //string napis; while (!CIAGodczytA.eof()){ CIAGodczytA >> napis; licznikR++; //A - trzy znaki takie same //sprawdzamy zerowy z pierwszym i zerowy z drugim //nie potrzeba sprawdzać pierwszego z drugim if (napis[0]==napis[1] && napis[0]==napis[2] //&& napis[2]==napis[1] ) licznikA++; //B - czy palindrom // wystarczy sprawdzić pierwszy z zostatnim if (napis[0]==napis[2]) licznikB++; } CIAGodczytA.close(); //takie "cuda" trzeba robić w c++ //aby całkowite zamieniło na rzeczywiste //bo zwyczajne dzielenie nie potrafi! double P_A=(licznikA*1.0)/(licznikR*1.0); double P_B=(licznikB*1.0)/(licznikR*1.0); cout << "P[A]=" << P_A << endl; cout << "P[B]=" << P_B << endl; |
//zadanie 4 //generujemy liczby z zakresu 1..10000 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(); //na końcu generuje pusty wiersz, który potem jest czytany !!! //i dlatego trzeba dodatkowy warunek //średnia z cyfr ifstream CYFRYodczytA("cyfry.txt"); int liczbaA; int suma=0; while (!CYFRYodczytA.eof()){ CYFRYodczytA >> liczbaA; suma=suma+liczbaA; } CYFRYodczytA.close(); //dzielenie przez rzeczywiste 1000.0 //aby wynik był rzeczywisty cout << "Średnia=" << suma/1000.0 << endl; //parzyste z cyfr 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; //zadanie CYFRY c //korzystamy z funkcji SUMACYFR ifstream CYFRYodczytE("cyfry.txt"); int liczbaE,sumaE,liczbaEmax; int sumaEmax=0; while (!CYFRYodczytE.eof()){ CYFRYodczytE >> liczbaE; sumaE=SUMACYFR(liczbaE); if (sumaE > sumaEmax){ liczbaEmax=liczbaE; sumaEmax=sumaE; } } CYFRYodczytE.close(); cout << "SUMA CYFR max " << liczbaEmax << " " << sumaEmax << endl; //zadanie CYFRY d najczęściej //zwiększamy liczniki w komórkach tablicy ifstream CYFRYodczytD("cyfry.txt"); int liczbaD; int ileD[10001]; //zerowanie tablicy liczników cyfr for (int i=0; i <= 10001; i++) ileD[i]=0; while (!CYFRYodczytD.eof()){ CYFRYodczytD >> liczbaD; ileD[liczbaD]++; } CYFRYodczytD.close(); // a teraz trzeba wyszukać max w tablicy int maxD=0; int licD; for (int i=1; i <= 10001; i++) if (ileD[i] > maxD){ maxD=ileD[i]; licD=i; } cout << "Najczęściej: " << licD << " " << maxD << " razy" << endl; |
//zadanie 5 //napisy dwójkowe 2..16 znaków 01 ofstream NDzapis("napisy.txt"); int ile; int znak; for (int j=1;j<=1000;j++){ ile=rand() %15 +2; //2-16 string 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(); //zadanie a ifstream NDodczytA("napisy.txt"); string NDnapisA; int parz=0; while (!NDodczytA.eof()){ NDodczytA >> NDnapisA; if (NDnapisA.length() % 2 == 0) parz++; } NDodczytA.close(); cout << "parzyste: " << parz << endl; //zadanie b taka sama ilość zer i jedynek ifstream NDodczytB("napisy.txt"); string NDnapisB; //licznik wyrazów z taką samą iloścoą zer i jedynek int suma01=0; while (!NDodczytB.eof()){ NDodczytB >> NDnapisB; int dl=NDnapisB.length(); //liczniki zer i jedynek w napisie int ile0=0; int ile1=0; //przelatujemy po napisie i sprawdzamy znaki for (int i=0; i < dl; i++) if (NDnapisB[i] == '0') ile0++; else ile1++; //gdy taka sama ilość if (ile0 == ile1) suma01++; } NDodczytB.close(); cout << "tyle samo 0 i 1: " << suma01 << endl; //zadanie c same zera lub same jedynki ifstream NDodczytC("napisy.txt"); string NDnapisC; int ile0=0; int ile1=0; while (!NDodczytC.eof()){ NDodczytC >> NDnapisC; int dl=NDnapisC.length(); int suma=0; for (int i=0; i < dl; i++) suma=suma+int(NDnapisC[i])-48; // gdy suma zer jest równa zero, to same jedynki if (suma == 0) ile0++; //gdy suma jest równa długości, to same jedynki if (suma == dl) ile1++; } NDodczytC.close(); cout << "same 0: " << ile0 << endl; cout << "same 1: " << ile1 << endl; //zadanie d ile napisów każdej długości //tablica na liczniki napisów int tablica[17];//od 0 do 16 for (int i=0; i < 17; i++) tablica[i]=0; ifstream NDodczytD("napisy.txt"); string NDnapisD; while (!NDodczytD.eof()){ NDodczytD >> NDnapisD; int dl=NDnapisD.length(); tablica[dl]++; } NDodczytD.close(); //wyniki for (int i=2;i<17;i++) cout << i << " " << tablica[i] << endl; |
//SZABLON #include <stdlib.h> //system #include <iostream> //cout #include <iomanip> //fixed #include <cmath> //pow #include <fstream> //ofstream #include <ctime>//time using namespace std; int main(){ setlocale(LC_ALL, ""); system("pause"); } |