SOISK - SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE
Tomasz Puchała

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!