Znasz już podstawowe typy zmiennych i umiesz używać rekordów. Wiesz zatem już prawie wszystko o pisaniu bazy danych. Brakuje Ci jeszcze tylko niewielkiej informacji o tym, jak zoptymalizować operacje na wielu rekordach jednocześnie. W przykładzie wykorzystamy typ rekordowy uczen poznany na poprzedniej lekcji. Spróbujmy wpisać do bazy informacje o wszystkich uczniach:
#include <stdio.h> #include <conio.h> main (void) { struct { char *imie, *nazwisko; int numer; float srednia; }uczen[29]; clrscr(); printf("Podaj imię pierwszej osoby\n"); scanf("%s",&uczen[0].imie); printf("Podaj nazwisko pierwszej osoby\n"); scanf("%s",&uczen[0].nazwisko); printf("Podaj numer pierwszej osoby\n"); scanf("%d",&uczen[0].numer); printf("Podaj średnią pierwszej osoby\n"); scanf("%f",&uczen[0].srednia); printf("Podaj imię drugiej osoby\n"); scanf("%s",&uczen[1].imie); printf("Podaj nazwisko drugiej osoby\n"); scanf("%s",&uczen[1].nazwisko); printf("Podaj numer drugiej osoby\n"); scanf("%d",&uczen[1].numer); printf("Podaj średnią drugiej osoby\n"); scanf("%f",&uczen[1].srednia); printf("Podaj imię trzeciej osoby\n"); scanf("%s",&uczen[2].imie); printf("Podaj nazwisko trzeciej osoby\n"); scanf("%s",&uczen[2].nazwisko); printf("Podaj numer trzeciej osoby\n"); scanf("%d",&uczen[2].numer); printf("Podaj średnią trzeciej osoby\n"); scanf("%f",&uczen[2].srednia); getch(); return 0 ; }
Jak widać, wypisanie zapytania o dane trzech pierwszych uczniów jest pracochłonne i sporo wydłuża kod programu. Gdybyśmy zapytali się o każdego z trzydziestu uczniów, kod wydłużyłby się niemiłosiernie. Łatwo byłoby się w nim zagubić.
Istnieje prosty sposób, aby czynności wykonywane wiele razy zgrupować. Służy do tego pętla for. Pozwala ona powtórzyć pewne czynności określoną ilość razy. Dla naszej bazy czynność zapytania o imię czy nazwisko musi powtórzyć się 30 razy. Gdyby nie pętla for byłoby to bardzo pracochłonne przedsięwzięcie.
Ponieważ pętla ma powtórzyć pewien ciąg instrukcji wiele razy, musimy utworzyć pewien licznik, który zliczy ile razy zadanie zostało już wykonane. Przy użyciu instrukcji for i licznika będzie więc trzeba zaznaczyć pewne warunki:
Zmodyfikujmy teraz nasz poprzedni program:
#include <stdio.h> #include <conio.h> main (void) { int licznik; struct { char *imie, *nazwisko; int numer; float srednia; }uczen[29]; clrscr(); for(licznik=0;licznik<=29;licznik++) { printf("Podaj imię osoby nr: %d\n",licznik); scanf("%s",&uczen[licznik].imie); printf("Podaj nazwisko osoby nr %d\n",licznik); scanf("%s",&uczen[licznik].nazwisko); printf("Podaj numer osoby nr %d\n",licznik); scanf("%d",&uczen[licznik].numer); printf("Podaj średnią osoby nr %d\n",licznik); scanf("%f",&uczen[licznik].srednia); } getch(); return 0 ; }
Użyliśmy zapisu: for(licznik=0;licznik<=29;licznik++) {instrukcje...}. Tak dosłownie tłumaczy się ten zapis:
Tak napisany program 30 razy zapyta nas o te same dane różnych osób i przypisze je do wirtualnej bazy. Jako ćwiczenie proponuję dokończyć program: użyć pętli for do wypisania wszystkich danych z bazy na ekran.
Podsumowując:
Instrukcja for jest bardzo wygodna, ale ma jedną wadę: trzeba z góry wiedzieć, ile razy zostanie wykonana. Może o tym decydować inna zmienna, ale zawsze trzeba ją określić przed pierwszym wykonaniem pętli. Czasami zależy nam jednak w programie na tym, aby przerwać działanie pętli w momencie, kiedy pewien warunek przestanie być spełniany, ale nie wiadomo, kiedy to nastąpi. Np. program dotąd pyta użytkownika o odpowiedź, aż ten odpowie poprawnie.
Pętle, o których mowa to while i do-while. Różnią się one bardzo niewiele od siebie, ale wykazują sporo różnic w porównaniu z pętlą for. Po pierwsze: nie zawierają zmiennej sterującej, czyli licznika, który określał, ile razy pętla została wykonana (nic nie stoi jednak na przeszkodzie, aby taki licznik sobie tam umieścić, jeśli zajdzie taka potrzeba). Pora na przykład:
#include <stdio.h> #include <conio.h> main (void) { int odp; clrscr(); while(odp!=7344) { printf("Ile jest 153 razy 48?\n"); scanf("%d",&odp); if (odp<7344) printf("Spróbuj jeszcze raz (wynik jest większy)"); else if (odp>7344) printf("Spróbuj jeszcze raz (wynik jest mniejszy)"); else printf("Dobrze!"); } getch(); return 0 ; }
Ekran po wykonaniu programu mógłby wyglądać tak:
Ile jest 153 razy 48? 5000 Spróbuj jeszcze raz (wynik jest większy) Ile jest 153 razy 48? 10000 Spróbuj jeszcze raz (wynik jest mniejszy) Ile jest 153 razy 48? 7000 Spróbuj jeszcze raz (wynik jest większy) Ile jest 153 razy 48? 8000 Spróbuj jeszcze raz (wynik jest mniejszy) Ile jest 153 razy 48? 7500 Spróbuj jeszcze raz (wynik jest mniejszy) Ile jest 153 razy 48? 7344 Dobrze!
W przykładzie warunkiem na to, aby pętla się wykonywała jest zła odpowiedź na pytanie. W momencie kiedy warunek ten przestanie być spełniany (użytkownik poda prawidłową odpowiedź) pętla zostanie zakończona. Zauważmy, że warunek sprawdzany jest jeszcze przed wykonaniem pętli! Może się więc zdarzyć, że pętla nie zostanie wykonana ani razu.
Ten sam program można napisać przy użyciu pętli do-while:
#include <stdio.h> #include <conio.h> main (void) { int odp; clrscr(); do { printf("Ile jest 153 razy 48?\n"); scanf("%d",&odp); if (odp<7344) printf("Spróbuj jeszcze raz (wynik jest większy)"); else if (odp>7344) printf("Spróbuj jeszcze raz (wynik jest mniejszy)"); else printf("Dobrze!"); } while(odp!=7344) getch(); return 0 ; }
Działanie programu będzie na pozór identyczne. Różnica polega jednak na tym, że najpierw zostanie wykonane ciało pętli (instrukcje, które mają się powtarzać), a dopiero później sprawdzony zostanie warunek. Oznacza to, że taka pętla zawsze wykona się przynajmniej raz.
Dla programu z naszego przykładu nie ma znaczenia, której z tych pętli użyjemy. Gdybyśmy jednak jeszcze przed samą pętlą przypisali do zmiennej odp wartość 7344, dopiero zauważylibyśmy różnicę. W pierwszym przypadku pętla nie uruchomiłaby się ani razu, ponieważ program najpierw sprawdza warunek, a potem dopiero przechodzi do wykonania pętli. W drugim przypadku program najpierw zapytałby nas o podanie prawidłowego wyniku. Podaną odpowiedź przypisałby do zmiennej odp i dopiero sprawdził warunek. Działanie programu w drugim przypadku nie zmieniłoby się.
Kiedy używać while a kiedy do-while? Wybór należy do programisty, który sam powinien ocenić, która będzie bardziej przydatna. Ja osobiście cześciej używam do-while.
Podsumowując: