podstawy c
Podstawy programowania – Język C,
* Wstę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ą chyba jedyny sensowny sposób na
komunikację z userem na konsoli - przez klawiaturę ).
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 <stdio.h>
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 <stdio.h>
int main (void)
{
puts ("Jakiś bzdurny tekst zamiast osławionego 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 <stdio.h>
int main (void)
{
puts("Teraz jakiś przykładowy pojedynczy znaczek:");
putchar('z');
puts(" dalsza część 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 cudzysłowem (nie tym pod tylną,
tylko tym pod podwójnym cudakiem). 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 <stdio.h>
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 <stdio.h>
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 ostrzeżenia 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 <stdio.h>
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 <nie równe>, 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 <stdio.h>
int main (void)
{
int licznik;
for (licznik = 0; licznik < 10; licznik = licznik + 1)
{
if (licznik == 0 || licznik == 2 || licznik == 4 || licznik ==
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
int main (void)
{
printf("Teraz pierwsza linianTeraz druga linian");
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:
"Przykładowy tekst "w cudzysłowu" i poza nim." zapis w kodzie
Przykładowy tekst "w cudzysłowu" i poza nim. wydruk na ekranie
A jeśli chcesz wstawić jakąś zmienną do tekstu, to musisz użyć funkcji printf:
-------------------------------------------------------------------------------
#include <stdio.h>
int main (void)
{
int licznik;
for (licznik = 0; licznik < 20; licznik++)
printf("Licznik w pętli ma wartość: %dn", 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': %in", 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 %in", 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 %in", 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 <stdio.h>
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 <stdio.h>
int main (void)
{
printf("Teraz krotki przyklad: %s, a tu dalszy tekstn", "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 <stdio.h>
int main (void)
{
int a0, a1, a2, a3, a4;
a0 = 0; a1 = 2; a2 = 4; a3 = 6; a4 = 8;
printf("%dn%dn%dn%dn%dn",
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 <stdio.h>
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("%dn", 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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
#include <string.h>
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 <string.h>
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 <stdio.h>
#include <string.h>
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źć <Enter>,
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 <stdio.h>
#include <string.h>
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("%in", (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 <stdio.h>
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
<Enter>, 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 <stdio.h>
int main (void)
{
int liczba;
puts("Podaj jakas liczbe:");
scanf("%i", & liczba);
liczba += 10;
printf("Podales liczbe o 10 mniejsza od %in", 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 <stdio.h>
int main (void)
{
float rzecz;
puts("Podaj dowolna liczbe rzeczywista:");
scanf("%f", & rzecz);
printf("Wpisales: %fn", 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 <stdio.h>
int main (void)
{
char tekst[50];
puts("Podaj jakies slowo:");
scanf("%s", tekst);
printf("Wpisales: %sn", & 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 <stdio.h>
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 <stdio.h>
int main (void)
{
int liczba = 10;
int * wsk;
wsk = & liczba;
*wsk = 25;
printf("Zawartość zmiennej 'liczba': %in", 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 <stdio.h>
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, %in", 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 <stdio.h>
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 <stdio.h>
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("%in", *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 <stdio.h>
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 <stdio.h>
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!