* Indeks rozdziałów 15 Zstępniak 28 Pierwszy program - wypiszemy na monitorze jakieś bzdury 111 Jesteśmy leniwi 304 Zmienna zmiennej niepodobna, czyli... 390 Co ma konwersja do dzielenia? 426 Wariat-cje na temat tekstu 594 Jak utrudnić sobie życie, czyli kilka wiadomości o tablicach 659 Łańcuchy w okowach... czy jakoś tak 911 Zagadka enigmy, czyli co znaczył znaczek & przy funkcji scanf 1131 Funkcjonowanie programu 1211 Nareszcie odpocząć można? * Zstępniak Po co komu jeszcze jeden kurs języka C, zwłaszcza, że każdy przyzwoity programista pisze albo w Pascalu, albo w C++, albo innej Javie? Ano po nic, po prostu naszedł mnie taki drobny schiz, napisać nieco o podstawach C. Z resztą, mam to na zajęciach, więc od razu może komuś ze znajomkuff się przysłużę. Zakładam oczywiście u czytelnika (czyt.: u ciebie) minimalną wiedzę o komputerze, konsoli (linii poleceń), programowaniu (znajomość paru terminów) oraz obsługiwaniu edytora tekstu. Jeśli tej wiedzy nie posiadasz, to nawet najlepsza książka o programmingu niewiele ci da, więc najpierw pobaw się obsługą kompa. Tyle tytułem wstępu. * Pierwszy program - wypiszemy na monitorze jakieś bzdury Na początek dwie sprawy: - Kompilatorowi nie robi żadnej różnicy, czy władujesz jedną spację, enter, tabulator czy dowolną kombinację powyższych. Odstęp to odstęp. W związku z tym od razu naucz się odpowiednio wcinać tekst programów, żebyś widział, na jakim poziomie się znajdujesz. - Język C jest wrażliwy na wielkość liter, czyli 'int' nie jest tym samym, co 'INT'. Musisz dokładnie przyglądać się, jakie wielkości liter były użyte. Najkrótszym programem w C, który nie spowoduje żadnych ostrzeżeń kompilatora (na gcc, na produktach Borlanda można zrobić krótszy) jest takie cóś: int main(void){return 0;} Tylko 25 znaków. Jedna linijka. Ale program jest średnio czytelny, a robi w zasadzie nic. A dokładnie: po wywołaniu od razu zwraca sterowanie systemowi operacyjnemu, czyli kończy działanie. Z czytelnością poradzimy sobie np. tak: ------------------------------------------------------------------------------- int main (void) { return 0; } ------------------------------------------------------------------------------- Prawda, że ładniejsze? To jest w zasadzie nasz szkielet programu, będziemy go używać praktycznie zawsze. ( I jeszcze drobna uwaga: jeśli otwierasz jakikolwiek nawias czy cudzysłów, to od razu go zamknij, przesuń kursor między oba znaczki i dopiero tam pisz to, co miało być wewnątrz. Unikniesz wtedy zastanawiania się, czy już zamknąłeś już ten nawias, czy dopiero będziesz musiał. ) No dobra, ale on nic nie robi, ten nasz program. Dobrze by było, żeby program jakoś usera (czyli uruchamiającego program) powiadomił, że w ogóle zadziałał. Wypiszemy zatem jakiś tekst (prawdę mówiąc chyba jedyny sensowny sposób na komunikację z userem na konsoli - przez klawikaturę :)). Cały trik polega na tym, że C nie ma żadnego słowa kluczowego na wypisywanie czegokolwiek na ekran. Musiała zostać napisana specjalna funkcja, która ten tekst wyprowadza. Tylko jak się do niej dobrać? Są dwa sposoby: przepisać ją do naszego programu (niezbyt wygodne), albo wstawić plik z funkcją do naszego programu: ------------------------------------------------------------------------------- #include int main (void) { return 0; } ------------------------------------------------------------------------------- To znaczy mniej więcej tyle: znajdź (w pewnym katalogu) plik 'stdio.h' i przy kompilacji wstaw go tutaj. Czyli że już możemy użyć funkcji wypisującej znaki: ------------------------------------------------------------------------------- #include int main (void) { puts("Jakis bzdurny tekst zamiast oslawionego Hello world"); return 0; } ------------------------------------------------------------------------------- Funkcja puts robi tak: wypisuje zadany tekst (UWAGA! Tekst, czyli ciąg znaków, musi być ujęty w ", a nie w ') i przechodzi do nowej linii. Możesz to sprawdzić, umieszczając dwie funkcje puts, jedna po drugiej. Dobra, fajnie. Ale to nie wszystko, co możesz zrobić. Możesz wypisać POJEDYNCZY ZNAK (!) :))). Możesz to zrobić funkcją putchar: ------------------------------------------------------------------------------- #include int main (void) { puts("Teraz jakis przykladowy pojedynczy znaczek:"); putchar('z'); puts(" dalsza czesc bzdurnego tekstu"); return 0; } ------------------------------------------------------------------------------- UWAGA: wywołując funkcję putchar chciałeś tylko jeden znak wypisać, a nie cały ciąg, zatem ograniczamy go tylko pojedynczym czudzysłowiem (nie tym pod tyldą, tylko tym pod podwójnym cudzakiem). Później wyjaśnię różnicę. Na razie zwracaj uwagę na to, jakiego cudzaka użyłem. Wracając do funkcji putchar, to wypisuje ona JEDEN znaczek, po czym NIE PRZECHODZI do nowej linii, czym się różni od puts (sprawdź). Ślicznie. * Jesteśmy leniwi, czyli jak nakłonić program do odwalenia wielu powtarzających się operacji za nas Teraz chcemy wypisać 25 gwiazdek w jednej linii. Możemy to zrobić na kilka sposobów: {puts("*** ... *");} albo 25 razy wywołać {putchar('*');}. Jest trzecia metoda, której stosowanie w tak banalnych przykładach jest (prawie) idiotyzmem, ale w końcu kto powiedział, że pierwsze programy nie mogą być idiotyczne? Z góry wyjaśnię, co chcę zrobić: otóż zrobię tak: :) dwadzieścia pięć razy wykonam funkcję putchar. Proste, nie? ;) To popatrz: ------------------------------------------------------------------------------- #include int main (void) { int licznik; for (licznik = 0; licznik < 25; licznik = licznik + 1) putchar('*'); return 0; } ------------------------------------------------------------------------------- I tu pojawia się parę rzeczy nowych, którymi zajmiemy się przez parę dalszych ekranów. Najpierw linijka 'int licznik;'. Widzisz, 'licznik' jest tzw. zmienną, która może się zmieniać w trakcie działania programu. Każdą zmienną należy zadeklarować na początku programu (jest to drobne uproszczenie, ale na razie wystarczy). Zadeklarować, czyli powiedzieć, jakiego typu jest nasza zmienna: czy jest znakiem, czy liczbą całkowitą, czy może rzeczywistą. Kompilator musi wiedzieć, jak traktować słowo 'licznik' - i po to jest deklaracja. Słówko 'int' oznacza liczbę całkowitą, liczba rzeczywista to 'float', natomiast znak to 'char'. Teraz kilka słów o tym, co się dzieje po słowie 'for'. Instrukcja 'licznik = 0;' mówi, że do zmiennej licznik ma zostać zapisana wartość 0. Podobnie byłoby dla 'licznik = 5;' - do zmiennej licznik wpisz wartość 5. Instrukcja 'licznik < 25;' jest chyba jasna. Ostatnie jest wyrażenie 'licznik = licznik + 1;'. Znaczy tyle: do zmiennej licznik wpisz wartość, jaka ci wyjdzie po stronie prawej, a tam mamy wyrażenie 'licznik + 1'. W efekcie w zmiennej 'licznik' będzie liczba o 1 większa, niż przed przypisaniem. To się nazywa inkrementacja i dzięki temu pętla w ogóle działa. Teraz struktura samej pętli for: 'for (inic; war; inkr) instrukcja;' 'inic' to inicjalizacja pętli, czyli nadajemy pewnej zmiennej jakąś wartość początkową, a później już nie zaglądamy do tej przegródki. U nas inicjalizacją było nadanie zmiennej licznik wartości 0. 'war' to warunek. Pętla będzie wykonywana dopóki warunek będzie prawdą (czyli dopóki licznik będzie mniejszy od 25). Warunek wcale nie musi być związany ze zmienną licznika, może być jakimkolwiek wyrażeniem (choć ze zrozumiałych względów najczęściej zależy od wartości licznika). 'inkr' to inkrementacja, czyli zwiększenie licznika. Mógłbyś zapytać, po co tu takie coś, skoro zawsze będziemy zwiększać licznik o 1. Otóż nie zawsze. Czasem chcielibyśmy licznik zmniejszać, a nie zwiększać, a czasem zwiększać o na przykład 2, albo w ogóle robić coś innego. Na koniec pętli następuje 'instrukcja'. To jest to, co ma być powtarzane przez pętlę. Musi być zakończona średnikiem - jak każda instrukcja. UWAGA: Pętla for jest traktowana jako polecenie w C, ale tylko RAZEM z instrukcją, którą ma powtarzać. Oznacza to, że broń cię Panie Boże przed stawianiem średnika zaraz po nawiasie for(;;). Chyba, że chcesz, aby pętla nic nie robiła. UWAGA 2: Instrukcja po pętli for jest tylko jedna. Ale jako jedna instrukcja traktowany jest też tzw. blok instrukcji, czyli parę komend (zakończonych standardowym średnikiem) ograniczonych nawiasami klamrowymi (takimi, jak w szkielecie programu): ------------------------------------------------------------------------------- #include int main (void) { int licznik; for (licznik = 0; licznik < 20; licznik = licznik + 1) { putchar('*'); putchar('-'); } return 0; } ------------------------------------------------------------------------------- I oczywiście WCIĘCIA!!! Jak myślisz, co nam wypisze program? Wypisze nam to: *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- Taki sobie szlaczek. Po bloku instrukcji nie następuje średnik (chociaż może, to wcale nie spowoduje błędu czy ostrzerzenia kompilatora) - przecież ostatnia instrukcja w bloku już jest zakończona średnikiem. Jak się pewnie domyślasz (choć nie koniecznie, może po prostu nie zwróciłeś na to uwagi), kod naszego programu też jest blokiem instrukcji. I drobna uwaga. W bloku może być dowolna ilość instrukcji, od żadnej, przez jedną, do mnóstwa (Kali umieć liczyć ;]]]). Jedną instrukcję TEŻ MOŻNA umieścić w klamrach! No to mamy pętlę for. Coś by się jeszcze przydało. Może instrukcja if? Piszemy: ------------------------------------------------------------------------------- #include int main (void) { int licznik; for (licznik = 0; licznik < 10; licznik = licznik + 1) { if (licznik == 2) puts("Teraz licznik jest równy 2!"); } return 0; } ------------------------------------------------------------------------------- Przypominam o WCIĘCIACH!!! Efekt działania programu to wypisanie tekstu. Niedużo, a do tego można było się obyć bez pętli i if-a. Ale nie o to chodzi. Przyjrzyj się, co robi program od kuchni. Dziesięć razy wykonuje pętlę, a za każdym razem sprawdza, czy licznik nie jest czasem równy 2. Instrukcja if jest nieco podobna do for, jeśli chodzi o traktowanie średnika, obie uwagi z for-a tyczą się if-a (zajrzyj tam). Zauważ jeszcze jedno: porównanie dwóch liczb to DWA znaki '=' NIE ODDZIELONE ŻADNĄ PRZERWĄ. Jeden znak to wstawienie liczby z prawej do zmiennej z lewej. Inne tzw. operatory porównań to: < > <= >= !=. To chyba wszystkie. Ostatni znaczy , czyli polecenie {if (2 != 3) puts("2 nie jest równe 3");} wykona się (warunek 2 != 3 jest zawsze spełniony). A jak zrobić, żeby program wypisywał ten sam komunikat dla liczb 0, 2, 4 i 8? Ano tak: ------------------------------------------------------------------------------- #include int main (void) { int licznik; for (licznik = 0; licznik < 10; licznik = licznik + 1) { if (licznik == 0 || licznik == 2 || licznik == 4 || licznik == 8) puts("Jedna z naszych liczb!"); } return 0; } ------------------------------------------------------------------------------- Znaczek '||' to logiczne OR, czyli cała instrukcja mówi: jeśli licznik jest równy 0 ALBO l. jr. 2 ALBO l. jr. 4 ALBO l. jr. 8, to wykonaj instrukcję wypisania tekstu. Instrukcja puts("..."); zostanie wykonana, jeśli KTÓRYKOLWIEK warunek będzie spełniony. Są jeszcze dwa inne operatory: '!' i '&&'. Najpierw &&. Działa podobnie do ||, ale oznacza logiczne AND (wszystkie warunki muszą być spełnione jednocześnie). Odmieńcem jest '!'. Oznacza negację. [ !(3 <= 2) ] oraz [ (3 > 2) ] są sobie równoważne. Pierwsze oznacza "nie prawda, że (3 jest mniejsze lub równe 2)", czyli 3 musi być większe. Z negacją można konstruować dłuuuuuugie wyrażenia logiczne. Wypróbuj wszystkie operatory logiczne, pamiętając, że możesz do nich stosować nawiasy '(' ')' (do ustalenia kolejności działań). Teraz pora na jeszcze jedną pętelkę, bardzo podstawową. ------------------------------------------------------------------------------- #include int main (void) { int zmienna; zmienna = 10; while (zmienna != 0) { puts("Wykonujemy pętlę"); zmienna = zmienna - 1; } return 0; } ------------------------------------------------------------------------------- Pętla while zachowuje się ze średnikiem identycznie, jak for i if (zajrzyj do opisów). Pętla while przypomina działaniem instrukcję if, z tym, że instrukcja po 'while (warunek)'jest wykonywana, dopóki warunek jest spełniony. Pętla sprawdza warunek, a potem, jeśli jest spełniony, to wykonuje instrukcję i ponownie sprawdza warunek, po czym, jeśli jest spełniony, to wykonuje instrukcję i ponownie sprawdza warunek ... itd. W naszym przypadku wykona się 10 razy. Gdybyśmy nie dodali linijki {zmienna = zmienna - 1;}, to kiedy pętla by się zakończyła? Nigdy, bo warunek byłby zawsze spełniony (zmienna równa 10, czyli różna od 0). A tak, to po każdym wywołaniu zmienna zostaje zmniejszona o 1 (co już było omawiane, tylko że dla dodawania jedynki). Uff... sporo tego. A jeszcze nie omówiłem paru rzeczy, mianowicie operatorów "liczbowych" (+-*/%) i operatorów typowych dla C i języków na nim wzorowanych. Pierwsze cztery (+-*/) to normalne operatory, jak w kalkulatorze. Oczywiście kolejność działań jest przestrzegana. Operator '%' to tzw. dzielenie modulo, co dla programmera oznacza, że zwraca resztę z dzielenia, np. 5 % 3 == 2; 8 % 2 == 0; 13 % 10 == 3; 10 % 3 == 1; itd. Łapiesz, o co chodzi? Priorytet dzielenia modulo jest taki sam, jak zwykłego dzielenia. Jest parę operatorów specyficznych dla C. Są dość dziwne, ale przy odrobinie wprawy są BARDZO wygodne. Pierwsze to coś takiego: {zmienna++;} Co to znaczy? To znaczy to samo, co {zmienna = zmienna + 1;} Efektem jest zwiększenie o 1 zmiennej 'zmienna'. Analogicznie {zmienna--;}, oznacza zmniejszenie zmiennej 'zmienna' o 1. Teraz ciekawe operatory przypisania, gdy mamy zmienną 'zm': zm -= 15; znaczy zm = zm - 15; zm += 1; znaczy zm = zm + 1; (co można zapisać jeszcze zm++;) zm *= 2; znaczy zm = zm * 2; zm /= 4; znaczy zm = zm / 4; zm %= 5; znaczy zm = zm % 5; Mam nadzieję, że łapiesz o co chodzi. Dla wyrażenia 'zm *= 2;' robimy tak: najpierw mnożymy aktualną zawartość zmiennej zm przez 2, a potem wstawiamy do zmiennej zm. W zmiennej zm będzie więc 2 razy więcej, niż przed operacją. * Zmienna zmiennej niepodobna, czyli... ...conieco o różnych rodzajach danych i zamianach jednego typu na drugi. W C mamy w zasadzie trzy podstawowe typy danych plus ich tablice i wskaźniki. Dwa ostatnie, zwłaszcza wskaźniki, omówię później. W C są dane typu 'int', 'float' i 'char'. 'int' już poznałeś. Tu mogą być liczby całkowite. 'float' to liczby rzeczywiste, czyli także ułamki. 'char' zawiera znak, a w zasadzie jego kod ASCII. Tak po prawdzie, to typ char można podciągnąć pod int, skoro to liczba całkowita. Jakie są tego konsekwencje? Pamiętasz funkcję putchar() ? Możesz zamiast znaku podać funkcji jego kod ASCII, dla funkcji nie będzie to miało żadnego znaczenia. Wywołanie putchar('A'); oraz putchar(65); da ten sam efekt (litera "duże a" ma kod ASCII równy 65). A co będzie oznaczać wyrażenie {'A' * 2;} ? Oczywiście 65 * 2, czyli 130. Pojedynczy znak ujęty w cudzysłów ' jest traktowany przez kompilator identycznie, jak liczba. Ujęty w podwójny cudzysłów już nie będzie pojedynczym znakiem, ale tablicą, która jest traktowana nieco inaczej, co jest powodem, dlaczego rodzaj cudzysłowia robi znaczenie, ale o tym później. Teraz zrobimy tak: mamy zmienną 'znak' typu char, a chcemy tam umieścić znak o kodzie 97, czymkolwiek by ten znak nie był. Robimy to w ten sposób: char znak; znak = 97; Proste, co? Podobnie robimy, gdy mamy zmienną 'liczba' typu int, a chcemy tu umieścić wartość kodu ASCII znaku '%'. int liczba; liczba = '%'; Jeszcze jedno przypisanie. Mamy obie zmienne, 'znak' i 'liczba'. Załóżmy, że w zmiennej znak już coś jest, np. 'g'. Przepiszemy to do zmiennej liczba: int liczba; char znak; znak = 'g'; liczba = znak; Łatwiej być nie może. W Pascalu czy Basicu/VisualBasicu trzeba specjalnej funkcji na konwersje znaków na kody i na odwrót. W C nie potrzeba takich rzeczy. Zagadka: czy wyrażenie {'B' >= 68;} jest prawdą? Odpowiedź: nie, bo skoro {'A' == 65;}, to {'B' == 66;}, a 66 jest mniejsze od 68. Konwersja liczby typu int na float jest równie banalna. Po prostu ją wstawiasz w odpowiednią zmienną. Konwersja odwrotna również jest identyczna, tylko że wtedy tracisz wszystko, co było po przecinku: float rzecz; float rzecz2; int calk; rzecz = 18.2345; calk = rzecz; rzecz2 = calk; UWAGA: znakiem pozycji dziesiętnej jest KROPKA, nie przecinek!!! Przecinek jest używany do innych celów. Najpierw wpisujemy zawartość zmiennej rzecz do calk i tracimy część po przecinku, następnie taką obciętą liczbę wpisujemy do drugiej rzeczywistej. W tym momencie w 'rzecz' jest liczba 18.2345, a w 'rzecz2' jest 18 . A tak na marginesie, zamiast dwóch linijek float rzecz; float rzecz2; można wstawić jedną: float rzecz, rzecz2; Jeśli deklarujesz parę zmiennych tego samego typu, to zamiast pisać parę(naście) osobnych słówek 'int', 'float' czy innych możesz oddzielić przecinkami nazwy zmiennych. Misja dla ciebie: jak wyświetlić 20 kolejnych dużych liter zaczynając od 'A', wiedząc, że 'A' ma kod 65, 'B' ma 66 itd. ? Podpowiem, że chodzi mi o pętlę for. A teraz odpowiedź: char znak; for (znak = 'A'; znak < 'A' + 20; znak++) putchar(znak); Może cię zdziwić, czemu używam zmiennej nie int, a char. A czemu nie? Przecież formalnie char to też liczba, więc można ją zwiększać o 1 (przypominam, że 'znak++' znaczy zwiększ zmienną znak o 1), a dwadzieścia pierwszych znaków ma kody od 'A' do 'A' + 19. No a potem oczywiście wypisuję znak. * Co ma konwersja do dzielenia? Otóż coś ma. Mamy taki pseudoprogram (pseudo, bo bez żadnych szkieletów) int a, b, c; a = 10; b = 6; c = (a / b) * b; Ile będzie wynosić 'c'? Będzie wynosić 6. Dziwne. Ale obejrzyjmy to dokładniej: dzielimy całkowitą 'a' przez całkowitą 'b', otrzymujemy liczbę całkowitą równą 1 (1.666 i obcinamy przecinek). Teraz nasze 1 mnożymy przez 'b' i otrzymujemy 6. Należy ci się wyjaśnienie, czemu otrzymujemy liczbę całkowitą. Wszystkie dane w wyrażeniu są zamieniane na najwyższy typ, jaki występuje w tym wyrażeniu (najniższy to char, później int, najwyższy to float), a wynik jest właśnie tego typu. Skoro więc najwyższy typ to int, to wynik jest tego samego typu, a zatem obcinamy cyfry po przecinku. Teraz już wiemy, gdzie leży błąd, ale jak go usunąć, nie zmieniając typu zmiennych? Tu nam pomoże rzutowanie (konwersja) jednego typu na drugi. Jeśli zmusimy program, żeby nam wynik podał jako liczbę rzeczywistą, to nie będzie problemu, 10 / 6 * 6 będzie równe 10. Zastanówmy się, jak to zrobić. Może tak? c = (float)(a / b) * b; Najpierw wyjaśnię, co oznacza pierwszy nawias. To jest konstrukcja w stylu [ (float)a ], która mówi, że zmienną 'a' ma traktować, jakby 'a'była typu float (konwersja na char czy int wygląda identycznie, zmieniasz tylko typ). Wracając do linijki [ c = ... ], mamy takie coś: podziel a przez b (wynik jest liczbą całkowitą), a następnie zamień go na float. Czyli już straciliśmy przecinek. Trzeba to zrobić wcześniej, czyli tak: c = ((float)a / b) * b; Teraz zmienna 'b' też jest zmieniona na float, podobnie jak wynik. Teraz, gdy pomnożymy wynik przez 6, otrzymamy 10 (z drobnym haczkiem, bo liczby typu 1/3 zapisują się tylko z pewną dokładnością). Teraz jest dokonywana zamiana wyniku z float na int, czyli w 'c' będzie dokładnie 10. * Wariat-cje na temat tekstu Do tej pory omijałem nieco ten temat, bo chciałem najpierw wytłumaczyć tablice i wskaźniki, ale chyba nie będzie to miało większego sensu. Niech będzie zatem rozdział o tekście i paru operacjach na nim. Najpierw dowiemy się, jak wypisać linijkę tekstu i nie przechodzić do nowej linii (puts przechodzi): ------------------------------------------------------------------------------- #include int main (void) { printf("Znowu jakis bzdurny tekst."); puts("Teraz dalszy ciag"); return 0; } ------------------------------------------------------------------------------- Efekt działania będzie taki: Znowu jakis bzdurny tekst.Teraz dalszy ciag Na końcu oczywiście nowa linia, bo użyliśmy puts (gdyby nie to, pod Linuksem znak zachęty byłby przesunięty, co daje niemiły oku obraz). Zwróć uwagę, że przed 'Teraz dalszy ciag' NIE MA spacji. Kursor po użyciu funkcji printf zostaje DOKŁADNIE w tym miejscu, w którym zakończył pisanie. Pytanie więc brzmi: czy funkcja printf może schodzić do nowej linii? Odpowiedź brzmi: tak. Notabene można ten sam sposób zastosować do puts, żeby napisać kilka wierszy jedną funkcją. A sposób wygląda tak: ------------------------------------------------------------------------------- #include int main (void) { printf("Teraz pierwsza linia\nTeraz druga linia\n"); return 0; } ------------------------------------------------------------------------------- Znaczek '\n' mówi "zejdź kursorem do nowej linii". Drugi znaczek, jaki się stosuje, to '\t', czyli tabulator. Pojawia się pytanie: jak wobec tego napisać pojedynczy backslash? Musisz użyć kombinacji '\\'. Odpowiednio dwa backslashe otrzymasz wpisując '\\\\' itd. To się tyczy WSZYSTKICH tekstów w kodzie źródłowym w C, o czym czasem można sobie zapomnieć, a to daje różne ciekawe (czyt. nieprzewidywalne) efekty. Gdy chcesz uzyskać na ekranie podwójny cudzysłów, identyczny z tymi, jakie odgraniczają tekst, wpisujesz '\"'. W Pascalu trzeba było się zdrowo nakombinować, żeby dostać takie coś. Przykład: "Przykladowy tekst \"w cudzyslowiu\" i poza nim." zapis w kodzie Przykladowy tekst "w cudzyslowiu" i poza nim. wydruk na ekranie A jeśli chcesz wstawić jakąś zmienną do tekstu, to musisz użyć funkcji printf: ------------------------------------------------------------------------------- #include int main (void) { int licznik; for (licznik = 0; licznik < 20; licznik++) printf("Licznik w pętli ma wartość: %d\n", licznik); return 0; } ------------------------------------------------------------------------------- Co nam wypisze pętla? Dwadzieścia linijek (znak '\n' na końcu!!), w każdej jakiś tekst zakończony liczbą, jaką aktualnie ma zmienna licznik, czyli w pierwszej linii będzie 0, w drugiej 1, w trzeciej 2, ... w ostatniej 19. Tylko jak to się dzieje, że wstawia się wartość zmiennej? W naszym tekście mamy jedno dziwne wyrażenie: %d. Oznacza ono, że po naszym tekście będzie zapisana jakaś zmienna, której zawartość wypiszemy, a wypiszemy ją jako liczbę w systemie dziesiętnym (decimal, stąd litera d po procencie) analogicznie wypisanie liczby w formacie ósemkowym będzie o - od Octal, a w systemie szesnastkowym x - od heXadecimal (wielkość litery po znaku '%' MA znaczenie) Możesz też posłużyć się pierwszą literą typu, czyli jeśli chcesz wypisać zmienną całkowitą (int), będzie '%i', dla rzeczywistej '%f' (float), dla znakowej (char) - '%c'. Znak procenta to (podobnie jak backslash) dwa procenty '%%'. I jeszcze drobna uwaga: możesz podać inny typ, niż zmienna ma faktycznie, ale będziesz musiał się pogodzić z konsekwencjami rzutowania (konwersji) na ten typ, np.: float rzecz; rzecz = 18.255; printf("Liczba 'rzecz': %i\n", rzecz); wydruk: Liczba 'rzecz': 18 Dobrze jest pamiętać przy każdym zakończeniu używania funkcji printf dorzucić znak nowej linii, bo wtedy nie ryzykujesz, że zaraz za dopiero wypisanym tekstem pojawi się nowy tekst (np. w Linuksie znak zachęty). A jak się ma sprawa z typem znakowym? Skoro to liczba, to czemu wydzielono specjalny typ? Otóż właśnie. Różnica między char i int ujawnia się dopiero przu drukowaniu. Drukując int dostaniesz na monitorze liczbę. Drukując char dostaniesz na ekranie znak o takim kodzie. Przykład: char znak; int kod_znaku; znak = 'A'; kod_znaku = znak; printf("Znak %c ma kod %i\n", znak, kod_znaku); wydruk: Znak A ma kod 65 Oczywiście możemy skorzystać z dobrodziejstw konwersji (czyli rzutowania): char znak; znak = 'A'; printf("Znak %c ma kod %i\n", znak, znak); Wydruk na ekranie jest identyczny, jak poprzednio. Zwróć uwagę, że zmienna znak została wpisana DWA razy w funkcji printf. Do KAŻDEGO wstawienia zmiennej musi być osobny argument. { Krótkie wyjaśnienie, o co mi chodzi. Otóż funkcję wywołuje się z argumentami. Dla puts był to tekst, który wypisywaliśmy, dla putchar był to znak (a w zasadzie jego kod ASCII), dla printf jest to tekst, jaki wypiszemy, wraz z paroma zmiennymi, które wstawimy do tekstu. To są argumenty. Funkcje puts i putchar muszą mieć dokładnie jeden argument, natomiast printf może mieć zmienną ich ilość. } Jeszcze drobna sztuczka w funkcji printf: możesz zarezerwować szerokość, na jakiej będzie wypisana liczba, wstawiając przed literę typu szerokość w znakach. Prawdę mówiąc, niezbyt potrafię to wyjaśnić słownie, więc odwołam się do przykładu: ------------------------------------------------------------------------------- #include int main (void) { int licznik; for (licznik = 0; licznik < 15; licznik++) printf("%2i,"); putchar('\n'); return 0; } ------------------------------------------------------------------------------- wydruk (od nowej linii) 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14 Zauważ, że liczby jednocyfrowe są wydrukowane na DWÓCH miejscach, a nie na dokładnie tylu, ile potrzebują. To jest szerokość pola, w którym wpisane są te liczby (przynajmniej ja to nazywam szerokością pola). Pewnie się domyślasz, że gdybyśmy wpisali nie 2, a np. 3, to od przecinka do przecinka byłoby trzy miejsca. A jak ma się sprawa z liczbami, które się w naszym polu nie zmieszczą? Czy zostaną obcięte z którejś strony? Nie. Zostaną wydrukowane w zasadzie tak, jakby szerokość naszego pola nie była podana, czyli od lewej do prawej. Teraz pewna sprawa z funkcją printf, której zasadność objaśnię później. Spójrz na to: ------------------------------------------------------------------------------- #include int main (void) { printf("Teraz krotki przyklad: %s, a tu dalszy tekst\n", "tu jest wstawienie"); return 0; } ------------------------------------------------------------------------------- Jak to wyjdzie na wydruku? Teraz krotki przyklad: tu jest wstawienie, a tu dalszy tekst Dziwactwo, nie? Przyjrzyjmy się temu z bliska: Podajemy funkcji printf dwa parametry, oba to teksty. W pierwszym mamy tzw. łańcuch formatujący (tak samo się to nazywa w innych zastosowaniach tej funkcji, ale mi to wcześniej umknęło), w drugim tekst, który wstawimy w odpowiednie miejsce. O tym, gdzie go wstawimy, mówi znaczek '%s' (s od string, czyli po polskiemu łańcuch znaków). Na razie nie ma większego sensu wstawiać w ten sposób tekstu, łatwiej po prostu wpisać ten brakujący kawałek. Ale co zrobić, gdy ten właśnie kawałek może się zmieniać? Tu by się przydał typ zmiennej przechowujący tekst, a znaczek '%s' właśnie ten typ wstawia. A jeśli chodzi o przechowywanie tekstu, to najpierw musisz się nieco dowiedzieć o tablicach, do czego niezwłocznie przechodzę. * Jak utrudnić sobie życie, czyli kilka wiadomości o tablicach Mamy takie zadanie (nie wolno omijać żadnego warunku): - program ma mieć pięć zmiennych typu int, indeksowanych od 0 do 4 - w każdej zmiennej ma mieć podwojony numer indeksu - ma wypisywać je wszystkie, od indeksu 0 do 4 A oto realizacja: ------------------------------------------------------------------------------- #include int main (void) { int a0, a1, a2, a3, a4; a0 = 0; a1 = 2; a2 = 4; a3 = 6; a4 = 8; printf("%d\n%d\n%d\n%d\n%d\n", a0, a1, a2, a3, a4); return 0; } ------------------------------------------------------------------------------- Pamiętaj o jednej rzeczy: to ŚREDNIK kończy instrukcję, nie znak nowej linii!! Kompilator nie widzi różnicy między "białymi" znakami (tab, enter, spacja)! Dlatego dozwolone jest napisanie w jednej linii kilku komend, oczywiście każda zakończona średnikiem. Dlatego również można złamać wiersz przy pisaniu funkcji (byle nie w środku wyrazu, ale np. po przecinku czy zamiast spacji). Wygodne, jeśli parametry nie mieszczą się w linii (w przykładzie oczywiście zmieściłyby się, ale chodziło mi o to, żeby pokazać, że takie coś nie jest błędem). Teraz słowo o tym, co wypisze program. Wypisze liczby 0, 2, 4, 6 i 8, każda w nowej linii. A teraz misja: napisz podobny program, ale wypisujący 20 zmiennych! Masakra. Tu się przydadzą tablice. Popatrz teraz na to: ------------------------------------------------------------------------------- #include int main (void) { int tablica[20]; int i; for (i = 0; i < 20; i++) tablica[i] = i * 2; for (i = 0; i < 20; i++) printf("%d\n", tablica[i] ); return 0; } ------------------------------------------------------------------------------- Prawda, że proste? (Tym razem użyłem zmiennej 'i', bo po pierwsze krócej się pisze, a po drugie zmienna o nazwie 'licznik' zaciemniłaby sposób odwołania do tablicy, co możesz sam sprawdzić) Deklaracja: deklarujemy coś typu int, ma mieć nazwę 'tablica', ma być tablicą (znaki '[' i ']'), ma mieć rozmiar 20. W tym miejscu muszę zaznaczyć, że nieprzypadkowo w poprzednim zadaniu kazałem numerować elementy od 0. W C tablice są indeksowane właśnie od ZERA, czyli ostatni indeks w naszej tablicy ma wartość 19! Dobra, a tablicy używa się tak, że w nawiasie klamrowym podajesz indeks elementu, który chcesz użyć. Poza tym, że wpisujesz ten indeks, to i-ty element tablicy jest pełnoprawną liczbą, nie trzeba żadnych kombinacji na nim, np. { tab[15] += 8; } jest analogiczne do { liczba += 8; }, a wyrażenie { wynik = tab[15] * 2; } jest równoważne wyrażeniu { wynik = liczba * 2; }, oczywiście z dokładnością do wartości liczbowej samego wyrażenia (w komórce 'tab[15]' może być coś innego, niż w zmiennej 'liczba'). To w zasadzie wszystko o tablicach w ogóle. Zaraz przejdziemy do tablic tekstowych (tablic typu char). * Łańcuchy w okowach... czy jakoś tak W wielu językach programowania jest specjalny osobny rodzaj danych na tekst. Jak się pewnie domyślasz, w C czegoś takiego nie ma. Nie znaczy to oczywiście, że nie da się pracować na sporych ilościach tekstu. Po prostu, do takiej zabawy trzeba troszkę więcej praktyki. Zaczniemy może od tego, że tekst można przechowywać w TABLICACH typu char. Ale zanim dojdziemy do takich dziwolągów, to może pewna krótka informacja: przy deklaracji zmiennej można nadać jej wartość (bo czemu nie?): int liczba = 586; liczba += 2; Oczywiście w zmiennej 'liczba' będzie wartość 588 (586 + 2). Taka operacja nazywa się inicjalizacją, choć sporo osób określa to mianem inicjacja (ja jestem osobiście za pierwszą wersją, do czego mnie przekonała lektura słownika języka polskiego; dalej w tekście będę używał tej wersji). Inicjalizacja to w zasadzie nic innego, jak zapisanie w jednej linijce tego, co można zapisać w dwóch, ale przy tablicach inicjalizacja ma pewne dodatkowe prawa. Może pokażę je na łańcuchu tekstowym: ------------------------------------------------------------------------------- #include int main (void) { /* 0123456789 123456789 1234 */ char tekst[25] = "Przypisujemy jakis tekst"; puts(tekst); return 0; } ------------------------------------------------------------------------------- Deklarujemy tablicę o rozmiarze o 1 większym, bo każdy ciąg znaków, czyli nasz tekst, musi w C kończyć się znakiem o kodzie ASCII równym 0, oznaczającym koniec tekstu (używany jest m.in. przez printf i puts). Oczywiście my tu nic nie napisaliśmy, że chcemy taki znaczek, ale kompilator go wstawia za nas na końcu każdego podwójnego cudzysłowia. Wróćmy do przykładu. Funkcja puts wypisze nam dokładnie to, co jest w podwójnym cudzysłowiu. Natomiast taka operacja, że do tablicy wstawiamy kawałek tekstu znakiem '=', jest możliwa TYLKO podczas inicjalizacji tej tablicy, czyli w czasie jej deklaracji. Taka komenda już nie będzie poprawna: {tekst = "Drugi tekst";} i oczywiście kompilator zgłosi błąd. Ale skoro to tablica, to możemy zrobić inaczej, będziemy każdej komórce tablicy przypisywać odpowiednią literę: ------------------------------------------------------------------------------- #include int main (void) { char tablica[6]; tablica[0] = 'T'; tablica[1] = 'e'; tablica[2] = 'k'; tablica[3] = 's'; tablica[4] = 't'; tablica[5] = 0; puts(tablica); return 0; } ------------------------------------------------------------------------------- Przypominam, że po pierwsze tablica jest numerowana od zera, czyli wartość ostatniego indeksu jest równa wielkości tablicy zmniejszonej o 1, a cały ciąg znaków jest zakończony znakiem o kodzie zero (inaczej liczbą 0). Funkcja puts wypisze na ekranie tekst 'Tekst'. Ale czy nie ma prostszej metody na to, żeby umieścić tekst w tablicy? Jest. Wystarczy użyć jednej z bibliotecznych funkcji C: ------------------------------------------------------------------------------- #include #include int main (void) { char tekst[50]; strcpy(tekst, "To sie powinno znalezc w tablicy 'tekst'"); puts(tekst); return 0; } ------------------------------------------------------------------------------- Funkcja strcpy kopiuje znak po znaku z naszego ciągu do tablicy. Jeśli właśnie skopiowała znak 0, to kończy swoje działanie (0 oznacza koniec testu). Zwróć jeszcze uwagę na drugą linijkę: #include Niezbyt skomplikowane, nie? Oczywiście, w Basicu czy Pascalu są specjalne typy zmiennych do przechowywania tekstu, ale C miało być jak najprostsze w konstrukcji języka, czyli wycięto takie typy, jak char odróżniony od liczby (wygodniej operować już na liczbie, niż specjalnie wywoływać osobną funkcję do zmiany znaku na jego kod ASCII), czy też osobny typ zmiennej na ciągi znaków (w końcu tekst to uporządkowany ciąg znaków, czyli do jego przechowywania świetnie nadaje się zwykła tablica znaków). Co dzięki temu zyskaliśmy my jako programiści? Na przykład takie coś: ------------------------------------------------------------------------------- #include #include int main (void)/* 0 10 20 30 */ { /* 0123456789 123456789 123456789 123 */ char tekst[50] = "Przykladowy tekst, ktory zmienimy"; int i; tekst[0] = 'p'; tekst[13] = 'T'; tekst[20] = 'K'; tekst[26] = 'Z'; puts(tekst); /* na ekranie: przykladowy Tekst, Ktory Zmienimy */ for (i = 32; i >= 0; i--) putchar( tekst[i] ); putchar('\n'); /* na ekranie: ymineimZ yrotK ,tskeT ywodalkyzrp */ return 0; } ------------------------------------------------------------------------------- Jeszcze jedno, które pojawiło się ze dwa przykłady temu: tekst ograniczony znaczkami /* i */ jest traktowany jako komentarz, czyli zupełnie ignorowany przez kompilator. Komentarz jest informacją TYLKO I WYŁĄCZNIE DLA PROGRAMISTY. Kompilatora nie interesuje. Wewnątrz komentarza może się znaleźć , kompilatorowi to wisi. Ignoruje cały tekst od '/*' aż do '*/'. Jeśli piszesz jakiś program, to często używaj komentarzy, żeby opisać, co robi i w jakim celu dany kawałek kodu (zdarzyło mi się parę razy, że zastanawiałem się, co fragment napisany kilka dni wcześniej miał robić :))). Taki nadmiar stukania w klawikaturę naprawdę potrafi zaoszczędzić sporo czasu. Wróćmy do przykładu: pętelka for wypisze nam cały tekst od tyłu. Ale my znowu jesteśmy leniwi, nie chemy liczyć, ile ma nasz tekst liter. Jak w takiej sytuacji dowiedzieć się tego? Jest inna funkcja w bibliotece C: ------------------------------------------------------------------------------- #include #include int main (void) { char tekst[50] = "Przykladowy tekst, ktory zmienimy"; int i = strlen(tekst) - 1; for (; i >= 0; i--) putchar( tekst[i] ); putchar('\n'); return 0; } ------------------------------------------------------------------------------- Ale zamieszanie! Po kolei: - inicjalizujemy tablicę tekst - inicjalizujemy zmienną 'i' tym, co policzy funkcja strlen zmniejszonym o 1 - w pętli for możemy opuścić początkową część administracyjną, czyli nadanie początkowej wartości i (już to wcześniej zrobiliśmy) - wypisanie tekstu - zejście do nowej linii - dla ludzi pod Linuksem W pętli for możemy opuścić którąś z rzeczy w nawiasie, o ile oczywiście zadbaliśmy o to, żeby pętla się kiedyś skończyła ;) Można zatem zapisać {for(;;);} (średniki w nawiasie MUSZĄ być oba!!!). Taka pętla nie robi nic, jest to prawie najprostszy przykład pętli nieskończonej. W przykładzie mogliśmy zrobić jeszcze inaczej: int i; for (i = strlen(tekst) - 1; i >= 0; i--) ... To jest z naszego punktu widzenia dokładnie to samo. Czemu użyłem {strlen(tekst) - 1} ? Bo funkcja policzy długość łańcucha znaków, dając nam tą właśnie długość. Ale my wiemy, że indeksy są numerowane od 0, czyli ostatni nas interesujący znak ma indeks (długość - 1). Indeks zwrócony przez strlen ma znak końca tekstu (o kodzie ASCII 0). Możesz to oczywiście sprawdzić: /* 0123456789 1234567 */ char tekst[25] = "Przykladowy tekst"; printf("%i\n", (int)tekst[ strlen(tekst) ] ); Funkcja printf wypisuje kod liczbowy (czyli ASCII) znaku podanego dalej (pamiętasz konwersje?), a znak podany dalej to znak z tablicy 'tekst' mający indeks strlen(tekst). Mam nadzieję, że jarzysz, o co biega. Na ten przykład, ostatnia litera tego tekstu to { tekst[ strlen(tekst) - 1 ] }, przedostatnia to { tekst[ strlen(tekst) - 2 ] } itd. Ostatnie, co powinieneś wiedzieć o tekście, to jak "pobrać tekst z konsoli", czyli jak przyjąć od użytkownika programu jakiś tekst: ------------------------------------------------------------------------------- #include int main (void) { char tekst[50]; puts("Wpisz teraz jakis tekst (do 50 znakow):"); gets(tekst); printf("Wpisales \"%s\"\n", tekst); /* ekran: Wpisales "..." */ return 0; } ------------------------------------------------------------------------------- Funkcja gets jest w pliku stdio.h, więc nie potrzebujemy pliku string.h. Dlatego też nie włączyliśmy go tutaj do programu. Na ekranie powinno pojawić się coś jak w komentarzu, przy czym '...' oznacza tekst wpisany przez użytkownika. Funkcja gets pobiera od użytkownika jakiś kawałek tekstu zakończony klawiszem , wpisując go do miejsca podanego przez parametr. To jest ciąg znaków. A co, jeśli chcemy liczbę? Tu powinniśmy użyć innej funkcji: ------------------------------------------------------------------------------- #include int main (void) { int liczba; puts("Podaj jakas liczbe:"); scanf("%i", & liczba); liczba += 10; printf("Podales liczbe o 10 mniejsza od %i\n", liczba); return 0; } ------------------------------------------------------------------------------- Funkcja scanf wygląda nieco dziwnie. Ta funkcja, podobnie jak printf, używa łańcucha formatującego. Najczęściej będzie się ograniczał do dwóch znaków: '%' i litery (i trzeciego, kończącego łańcuch - przyzwyczajaj się do tego ;))). Oczywiście jeśli będziesz chciał pobrać od usera liczbę rzeczywistą (float), to użyjesz odpowiedniej litery, znanej ci z funkcji printf (czyli tutaj 'f'). Podobnie, jeśli chodzi o znak czy ciąg (łańcuch) znaków (odpowiednio 'c' i 's') Wyglądać będzie to mniej więcej tak: ------------------------------------------------------------------------------- #include int main (void) { float rzecz; puts("Podaj dowolna liczbe rzeczywista:"); scanf("%f", & rzecz); printf("Wpisales: %f\n", rzecz); return 0; } ------------------------------------------------------------------------------- Do wytłumaczenia zostało mi jeszcze jedno: czemu przed nazwą zmiennej jest znaczek. Otóż funkcja ta, żeby zapisać dane w naszej zmiennej, musi mieć ten znaczek (zwany "amperstand"). Zobaczmy, jak się pobiera tekst: ------------------------------------------------------------------------------- #include int main (void) { char tekst[50]; puts("Podaj jakies slowo:"); scanf("%s", tekst); printf("Wpisales: %s\n", & tekst); return 0; } ------------------------------------------------------------------------------- Zauważ, że pobrany zostanie tylko kawałek przed pierwszą spacją. Funkcja scanf nie jest zatem idealna do pobierania większych fragmentów tekstu, ale (w odróżnieniu od gets) nadaje się do pobrania liczb. * Zagadka enigmy, czyli co znaczył znaczek & przy funkcji scanf Żeby do tego dojść, musimy najpierw wywiedzieć się o wskaźnikach, czyli tym, czym C i pochodne górują nad innymi językami programowania. Co to jest wskaźnik w rzeczywistym życiu? To coś, co wskazuje. Podobnie jest w C. Oczywiście nie mamy w komputerze żadnego patyka, żeby nim wskazywać. Mamy natomiast liczby. Jak pewnie wiesz, każdy bajt pamięci operacyjnej (czyli RAMu) ma przyporządkowaną liczbę, mówiącą, który to bajt z kolei. To jest ADRES tej komórki. Wskaźnik to w zasadzie też liczba, tyle że zawierająca ten właśnie adres. Drobna uwaga: przez cały ten "rozdział" pamiętaj, że wskaźnik to właśnie liczba. Z tego wynikają pewne konsekwencje mające wpływ na używanie wskaźników. ------------------------------------------------------------------------------- #include int main (void) { int liczba; int *wskaznik_do_liczby; wskaznik_do_liczby = & liczba; /* 1 */ liczba = 10; if (liczba == 10) puts("W zmiennej 'liczba' jest wartosc 10!"); if (*wskaznik_do_liczby == 10) /* 2 */ puts("W zmiennej wskazywanej przez wskaznik jest liczba 10!"); liczba = 15; if (*wskaznik_do_liczby == 15) puts("W zmiennej wskazywanej przez wskaznik jest liczba 15!"); return 0; } ------------------------------------------------------------------------------- Czym się różni wskaźnik od zwykłej zmiennej? Wskaźnik ma przed nazwą gwiazdkę. Ta gwiazdka nie jest oczywiście częścią nazwy, ale czasem się przydaje. Jak ustawić wskaźnik, żeby wskazywał na jakąś zmienną? Przykład jest w linijce z numerem 1. Musi się pojawić amperstand ('&'). Oznacza pobranie adresu zmiennej 'liczba'. Ten adres to jakaś liczba, która zostanie wpisana do wskaźnika (który TEŻ jest liczbą!). Teraz "wskaźnik wskazuje na zmienną", czyli zawiera jej adres. Oczywiście z tego, że zawiera liczbę będącą adresem, jeszcze nic nie wynika. Gdybyśmy porównywali w linijce z numerem 2 tak: if (wskaznik_do_liczby == 10) ... to instrukcja po 'if' wykonałaby się jedynie przy GIGANTYCZNYM szczęściu. Pamiętaj, że wskaźnik to liczba z adresem zmiennej, czyli adres musiałby być równy 10 (co jest praktycznie niewykonalne). Musimy zatem poinformować kompilator, że nie chcemy porównywać z dziesiątką adresu, ale liczbę spod tego adresu. Do tego służy gwiazdka. Teraz zmieniamy zawartość zmiennej 'liczba', na przykład na 15. Porównujemy to, co jest pod adresem ze wskaźnika z liczbą 15. Jak myślisz, czy instrukcja po 'if' się wykona? Pewnie, że tak! Po zmianie zmiennej 'liczba' zmieni się jej zawartość, ale czemu miałby się zmieniać adres? Czyli pod adresem, który jest zapisany we wskaźniku, jest dalej ta sama zmienna, ale z inną zawartością. Porównanie tej właśnie zawartości z liczbą 15 da wynik pozytywny (dopiero co tam 15 wpisaliśmy). Przydługie i przynudne wyjaśnienie. Może nowy przykład: ------------------------------------------------------------------------------- #include int main (void) { int liczba = 10; int * wsk; wsk = & liczba; *wsk = 25; printf("Zawartość zmiennej 'liczba': %i\n", liczba); return 0; } ------------------------------------------------------------------------------- Jak myślisz, co zostanie wypisane? 10? Nie. Zostanie wypisane 25. I znowu przydługie wyjaśnienie. Nasz wsk wskazuje na zmienną liczba. Teraz zapisujemy liczbę 25 do tej komórki, która jest wskazywana przez wsk, czyli do zmiennej liczba. Stąd wniosek, że wartość zmiennej liczba zmieni się. Oczywiście w naszym przypadku zapis {*wsk = 25;} jest równoważny {liczba = 25;} A teraz dwie zmienne i dwa wskaźniki: ------------------------------------------------------------------------------- #include int main (void) { int liczba1 = 10, liczba2 = 20; int *wsk1, *wsk2; wsk1 = &liczba1; *wsk1 += 8; wsk1 = &liczba2; *wsk1 += 4; wsk2 = wsk1; /* 1 */ *wsk2 += 6; printf("Wartosci zmiennych: %i, %i\n", liczba1, liczba2); return 0; } ------------------------------------------------------------------------------- Chyba dość jasny jest zapis funkcji printf ? Program wypisze takye cóś: Wartosci zmiennych: 18, 30 Chyba się domyślasz, dlaczego. Przed użyciem wskaźnika wsk2 (linia 1) komórki miały wartość 18 i 24. Teraz do wskaźnika wsk2 wpisujemy adres ze wskaźnika wsk1, czyli oba wskazują na ten sam element. Możemy oczywiście porównać dwa wskaźniki: if (wsk1 == wsk2) puts("Oba wskaźniki wskazują na ten sam element"); Taka instrukcja {if ... puts ...;} w naszym programie się wykona i wypisze tekst. Wskaźniki mogą równie dobrze wskazywać na komórki w tablicy: int tablica[10]; int *wsk; wsk = & ( tablica[4] ); Tutaj dla bezpieczeństwa użyłem nawiasów, a dlaczego, to wyjaśni się samo za chwilę. Teraz wskaźnik 'wsk' wskazuje na (wskazywany element :))))))) piąty element (numerujemy od zera!) tablicy 'tablica'. A teraz pewien trick: jak najprościej wskazać na pierwszy element tablicy? Ano tak: wsk = tablica; /* ?!? */ Otóż tablica tak naprawdę nie jest jakimś specjalnym badziejstwem, ale właśnie wskaźnikiem do pierwszego elementu (czyli tego o indeksie 0). Z tego zaś wynika, że można zrobić takie cuda: int tablica[10]; int *wsk; wsk = tablica; wsk[8] = 15; printf("%i", tablica[8] ); co wypisze nam dopiero wstawioną tam liczbę 15. Można też jeszcze inaczej (!): int tablica[10]; *tablica = 100; Wstawi nam to do elementu z indeksem 0 liczbę 100. Fajne, co? Teraz jeszcze jedna fajna rzecz: tekst. ------------------------------------------------------------------------------- #include int main (void) { /* 0123456789 123456789 123 */ char tekst[23] = "przykladowy test tekstu" char *wsk; wsk = & ( tekst[12] ); puts(wsk); return 0; } ------------------------------------------------------------------------------- Skoro tablica jest wskaźnikiem, to funkcji puts (czy też printf) nie robi różnicy, czy władujemy wskaźnik, czy tablicę. A teraz zagadka: co nam wypisze puts? test tekstu A czemu? Bo odwołuje się po kolei do wsk[0], wsk[1], wsk[2], ... wsk[n] itd. dopóki wsk[n] nie zawiera znaku kończącego (o kodzie 0). Czujesz? A teraz pewien dość często stosowany trick: skoro wskaźnik jest liczbą, to czemu nie dodać do niego np. jedynki? Możemy i to zrobić!!!! ------------------------------------------------------------------------------- #include int main (void) { int liczby[15], i; int *wsk; for (i = 0; i < 15; i++) /* 1 */ liczby[i] = 2 * i; wsk = liczby; /* 2 */ wsk += 3; /* 3 */ /*******/ /* 4 */ printf("%i\n", *wsk); return 0; } ------------------------------------------------------------------------------- Parę słów wyjaśnienia. W linijce 1 i następnej mamy wpisanie do tablicy danych 0,2,4,6,...28,30 . W linijce 2 ustawiamy wskaźnik wsk na początek tablicy. Zwykłe czynności administracyjne. całe piękno języka c mieści się w linijce 3: do wskaźnika wsk dodajemy liczbę 3! Co w tym pięknego? Ano to, że teraz wsk wskazuje na element tablicy 'liczby' z indeksem TRZY! Wypadałoby w tym miejscu zaznaczyć, że liczbę dodajemy nie do zmiennej WSKAZYWANEJ przez wskaźnik, tylko właśnie do WSKAŹNIKA (napisaliśmy nazwę wskaźnika bez gwiazdki). Jeszcze jedno: właściwie wskaźnik do liczby int nie może wskazywać liczby float czy też znaku char. Musi wskazywać liczbę typu int (tyczy się to wszystkich rodzajów wskaźników). A czemuż to? Otóż każdy typ zmiennej ma inny rozmiar: char ma rozmiar 8b = 1B, int ma wielkość 32b = 4B, podobnie jak float (oczywiście na różnych typach maszyn może się to różnić, ale na standardowym PC z procesorem PENTIUM/CELERON/ATHLON/DURON będą to właśnie takie wartości). Liczba int musi być zapisana w czterech bajtach w pamięci, co oznacza, że w tablicy następna liczba będzie 4 bajty dalej. Wskaźnik wskazuje na pierwszy bajt w pamięci, więc tylko typ wskaźnika mówi nam, ile bajtów jest użytych do zapisania naszej liczby. Podobnie dodawanie liczby do wskaźnika: jest ona najpierw pomnożona przez rozmiar danego typu zmiennej, dopiero potem jest dodawana do adresu ze wskaźnika. Dlatego wskaźniki mogą wskazywać tylko na dane swojego typu. Ale dość na tym. Już pewnie wiesz, co napisać w linijce z numerem 4, żeby wskaźnik wskazywał na element z indeksem 8. Musisz wstawić tam {wsk += 5;}, bo już wsk wskazuje na element 3, a element 8 jest 5 elementów dalej. A teraz zagadka: jak przesunąć wskaźnik o 1 element? {wsk += 1;}, co nie? Ale my chyba znamy krótszy zapis? {wsk++;} Prawda, że ładny? * Funkcjonowanie programu Jeszcze nie napisałem o jednej rzeczy: o funkcjach. A to są ciekawe a wielce przydatne rzeczy. Na szczęście nie ma tu nic specjalnego do powiedzenia. Najpierw musi być deklaracja, potem definicja. Może zademonstruję: ------------------------------------------------------------------------------- #include void xxx(void) { puts("Wlasnie wywolales funkcje \"xxx\""); // ekran: Wlasnie wywolales funkcje "xxx" // gdy chcesz napisac na ekranie podwojny cudzyslow, poprzedzasz go znakiem \ } int main (void) { xxx(); return 0; } ------------------------------------------------------------------------------- Odrobina wyjaśnień. Gdzie tu deklaracja? Od razu z definicją. Często to jest wygodna praktyka. Funkcja nie jest szczególnie interesująca, po prostu wypisuje jakiś bzdurny tekst na ekranie. Ale to w końcu pierwsza nasza funkcja (no, druga - pierwsza była main). Dwa void-y w "nagłówku" mówią, że funkcja nie zwraca nic i nic nie dostaje. Teraz przykład innych funkcji: ------------------------------------------------------------------------------- int dodaj_5(int a) { return a + 5; } int dodaj_dwa_inty(int a, int b) { return a + b; } int potega(int podstawa, int wykladnik) { int i, wynik = 1; for (i = 0; i < wykladnik; i++) wynik *= podstawa; return wynik; // ewentualne polecenia znajdujące się tutaj NIE ZOSTANĄ wykonane!!! } ------------------------------------------------------------------------------- Dwie pierwsze funkcje nie mają chyba nic trudnego. Nazwa trzeciej mówi sama za siebie. Teraz co to jest to w okrągłym nawiasie: to są parametry. Mogą być wszelkich możliwych typów (char, short, int, unsigned, float, a nawet wskaźniki). Instrukcja "retrun" każe wyjść z funkcji i oddać wynik jak stoi dalej (znaczy za return-em). Parametry to takie specjalne zmienne, których nie trzeba deklarować, samo ich wystąpienie obok nazwy funkcji je deklaruje. No to coś innego: ------------------------------------------------------------------------------- #include int dodaj_2(int a); // deklaracja funkcji int main (void) { printf("7 + 2 = %i", dodaj_2(7) ); return 0; } int dodaj_2(int a) // definicja funkcji { return a + 2; } ------------------------------------------------------------------------------- Najpierw funkcję zadeklarowaliśmy, później użyliśmy, a na końcu zdefiniowaliśmy, co ma robić (wygodna praktyka, jeśli funkcja ma znaczenie poboczne, czyli jej wygląd nas zupełnie nie interesuje). Jak pewnie widzisz, funkcji można użyć jak najzwyklejszej liczby. To też jest wygodna praktyka (nie musisz deklarować zmiennej na każdy wynik). To by chyba było na tyle, jeśli chodzi o funkcje. * Nareszcie odpocząć można? Niestety, nie mam żadnego pomysłu na to, co jeszcze mógłbym dopisać do kursu, żeby nie był zbyt długi, a żeby mówił coś jeszcze ciekawego (z założenia miał być to koors krutki). Jeśli dotrwałeś aż do tego momentu i nie skasowałeś tego pliku, to znaczy, że już umiesz co nieco pisać w C. Brawo! Nauczyłeś się podstawowych podstaw w zaledwie kilka godzin! (tak, tak! spojrzyj na zegarek!) Jeśli natomiast jeszcze nie umiesz wszystkiego, co tu wpisałem, to się nie przejmuj. Mądrzejsi od ciebie z lepszymi koorsami męczyli się dłużej z C. Nie pozostaje mi już nic innego, niż... zachęcić cię do przeczytania porządnej książki o C/C++. A dlaczemu? Bo ja tu napisałem tylko WPROWADZENIE, w pełnoprawnym podręczniku jest znacznie więcej ciekawych sztuczek i komend niż w moim skromnym koorsie. A programowanie naprawdę może się przydać! Miłej nauki! Dozzie