Język C
Informacje wstępne
Struktura programu
Program w języku C zazwyczaj składa się z deklaracji plików nagłówkowych,
definicji funkcji i procedur oraz wyróżnionej funkcji main
, która jest
domyślnie wywoływana w momencie uruchomienia programu. Funkcja ta zwraca wartość typu int
.
// deklaracje plików nagłówkowych #include <stdio.h> // definicje funkcji i procedur void procedure1(){ // ciało procedury } int function1(int arg){ // ciało funkcji } int main(){ // ciało głównej funkcji }
Komentarze w kodzie mogą przyjmować postać dwóch slashy // i wtedy obowiązują do końca linijki.
Kompilacja i uruchamianie programu
Aby wykonać program napisany w języku C, należy najpierw dokonać jego kompilacji, tzn. przetłumaczenia
jego kodu źródłowego na ciąg instrukcji niskiego poziomu, zrozumiałych przez procesor. W systemach
uniksopodobnych kompilacja odbywa się za pomocą polecenia gcc
, które wymaga podania nazwy pliku do skompilowania, np.
$ gcc prog1.c
W wyniku wywołania tego polecenia, tworzony jest plik a.out, który może zostać bezpośrednio uruchomiony. Przy uruchamianiu programu, jego Nazwa musi zostać poprzedzona kropką i slashem, np.
$ ./a.out
Aby określić nazwę generowanego pliku, możemy skorzystać z opcji -o, np.
$ gcc prog1.c -o prog1
Wtedy można wywołać program korzystając z tej nazwy:
$ ./prog1
Podstawowe operacje wejścia/wyjścia
Aby móc komunikować się z użytkownikiem uruchamiającym program, konieczna jest
znajomość co najmniej dwóch wywołani ze standardowej biblioteki C – printf
oraz
fgets
. Oba wywołania są zdefiniowane w bibliotece sdtio
.
printf
Wywołanie printf
pozwala wypisać na ekran wartości różnych typów zmiennych.
Pierwszym argumentem tego wywołania jest format komunikatu, który może składać
się ze zwykłych znaków oraz wyrażeń, które są zastępowane wartościami
pozostałych argumentów tego wywołania.
Najprostsze wywołanie wygląda następująco:
printf("Hello world.\n")
- s – łańcuch znaków
- c – pojedynczy znak
- d – liczba całkowita
- ld – długa liczba całkowita
- f – liczba zmiennopozycyjna
int i = 10; float f = 20.0; printf("Zmienna i: %d, zmienna f: %f",i,f);
fgets
Odczytanie komunikatu użytkownika jest nieco bardziej skomplikowane. Ze względów
bezpieczeństwa nie wykorzystuje się wywołania gets
, które ma najprostszą postać.
Wywołanie fgets
wymaga aż trzech argumentów: zmiennej, która reprezentuje tablicę,
w której ma być umieszczony wynik, maksymalnej liczby czytanych znaków oraz nazwy
strumienia, z którego odczytujemy znaki. W domyślnej postaci czyta znaki do końca
linii (o ile nie przekroczona byłaby maksymalna liczba znaków przekazana w wywołaniu).
char napis[31]; fgets(napis,31,stdin);
Przykładowy program wczytujący komunikat użytkownika i wypisujący go na ekran wygląda następująco:
#include <stdio.h> int main(){ char napis[31]; fgets(napis,31,stdin); printf("%s\n",napis); }
Podstawowe operacje konwersji
Ponieważ język C nie pozwala na automatyczną konwersję pomiędzy typami danych, aby móc operować na danych liczbowych dostarczanych przez użytkownika, konieczne jest przeprowadzenie konwersji pomiędzy łańcuchem znaków, a odpowiednim type liczbowym.
Metody konwersji definiowane są w bibliotece stdlib
, dlatego wymagają załączenia
pliku nagłówkowego <stdlib.h>
atoi, atol
Metody atoi
oraz atol
pozwalają na konwersję łańcucha znaków na liczbę
całkowitą typu int
oraz typu long
. Jako swój argument przyjmują łańcuch
znaków w postaci tablicy i zwracają wartość całkowitą.
Przykładowo, jeśli chcemy obliczyć sumę wartości wprowadzonych przez użytkownika możemy napisać następujący program:
#include <stdio.h> #include <stdlib.h> int main(){ int liczba1, liczba2; char napis[11]; printf("Wprowadź pierwszą liczbę: "); fgets(napis,11,stdin); liczba1 = atoi(napis); printf("Wprowadź drugą liczbę: "); fgets(napis,11,stdin); liczba2 = atoi(napis); printf("Suma wprowadzonych liczb wynosi %d\n",liczba1 + liczba2); }
atof
Funkcja atof
jest odpowiednikiem funkcji atoi
, ale służy do konwersji liczb zmiennopozycyjnych.
Jako argument przyjmuje łańcuch znaków, natomiast zwracana wartość jest typu double
.
Typy danych
Podstawowe typy danych
Każda zmienna używana w języku C musi mieć określony typ, który określa zbiór wartości, jakie może ona przyjmować oraz wyznacza operacje, jakie mogą być wykonywane na wartościach przypisanych do zmiennej.
Przykładowo – typ int to typ reprezentujący liczby całkowite (właściwie – pewien ich zakres). Operacje jakie można wykonać na tym typie, to:- dodawania +, odejmowanie -
- mnożenie * , dzielenie całkowice /
- dzielenie modulo %
- int – typ reprezentujący liczby całkowite
- long – jak int reprezentujacy wartosci całkowiet, ale o większym zakresie wartości
- float – typ reprezentujący liczby rzeczywiste pojedynczej precyzji
- double – typ reprezentujący liczby rzeczywiste podwójnej precyzji
- char – typ reprezentujący znaki
Zmienne w programach napisanych w C deklarowane są za pomocą słowa kluczowego reprezentującego typ zmiennej, po którym następuje list nazwa_zmiennych zakończona średnikami.
Przykład
int liczba_calkowita; float liczba_rzeczywista; char znak1,znak2;
W powyższym przykładzie definiowanych są 4 zmienne: liczba_calkowita, liczba_rzeczywista, znak1 oraz znak2. Pierwsza zmienna jest typu całkowitego, druga rzeczywistego, a trzecia i czwarta znakowego.
Typ tablicowy
Typ tablicowy pozwala na definiowanie zmiennych, które składają się z tablic elementów jednakowego typu. Elementy tablicy mogą być typu prostego lub złożonego (np. tablicowego). Pozwala to na definiowanie np. tablic tablic (którym w matematyce odpowiada pojęcie macierzy).
Każda zmienna typu tablicowego musi określać swój rozmiar oraz typ elementów. Typ elementów określany jest przez nazwę typu składowego występującą przed nazwą zmiennej. Rozmiar tablicy podawany jest jako liczba całkowita ujęta w nawiasy kwadratowe po nazwie zmiennej. Elementy tablicy indeksowane są od 0.
Przykład
int wektor[10]; int macierz[10][10];
W powyższym przykładzie definiowane są dwie zmienne, spośród których pierwsza jest tablicą składającą się z 10 elementów typu całkowitego, druga zaś jest tablicą 10 tablic składających się z 10 elementów typu całkowitego.
Dostęp do elementów tablicy odbywa się za pomocą operatora indeksowania [], który pojawia się po nazwie zmiennej i zawiera indeks elementu. Za pomocą tego operatora można zarówno odczytywać jak i przypisywać wartości poszczególnym elementom tablicy. Indeks elementu, do którego następuje dostęp, musi być większy lub równy 0 oraz mniejszy od rozmiaru tablicy.
Przykład
int main(){ int wektor1[10]; int pierwszy_element; //przypisanie wartości do pierwszego elementu tablicy wektor1 wektor1[0] = 1; //odczyt wartości pierwszego elementu tablicy wektor1 pierwszy_element = wektor1[1] //poniższe przypisanie jest niepoprawne, gdyż wartość przypisywana dla indeksu spoza górnego limitu wektor1[20] = 100; //poniższe przypisanie nie spowoduje błędu, ponieważ znak niejawnie konwertowany jest //na liczbę całkowitą, na podstawie swojego położenia w tablicy ASCII wektor1[0] = 'a'; }
Jak to zostało wcześniej wskazane, elementy tablicy mogą być typu złożonego, w szczególności typu tablicowego. Konstrukcje tego rodzaju pozwalają na definiowanie np. tablic tablic, inaczej zwanych tablicami dwuwymiarowymi. Na tej samej zasadzie można tworzyć tablice trój-, cztero- czy n-wymiarowe.
Aby odwołać się do podstawowego elementu takiej struktury, należy określić dwa indeksy – tablicy pierwszego i drugiego rzędu. Można to zrobić zestawiając ze sobą dwa operatory indeksowania.
W przypadku tablic dwuwymiarowych, pierwszy indeks zwany jest indeksem wierszowym, natomiast drugi – kolumnowym.
Przykład
int main(){ int macierz1[10][3]; // przypisanie wartości 1 elementowi stojącemu w pierwszym wierszu i pierwszej kolumnie zmiennej macierz1 macierz1[0][0] = 1; //przypisanie wartości 2 elementowi stojącemu w pierwszym wierszu i trzeciej kolumnie zmiennej macierz1 macierz1[0][2] = 2; //przypisanie wartości 3 elementowi stojącemu w trzecim wierszu i pierwszej kolumnie zmiennej macierz1 macierz1[2][0] = 3; //przypisanie poprawne macierz1[3][0] = 4; //przypisanie niepoprawne - drugi indeks nie jest mniejszy niż rozmiar tablicy macierz1[0][3] = 5; }
Instrukcje
Instrukcje występujące w kodzie programu napisanego w języku C odpowiadają blokom lub grupom bloków połączonych strzałkami, występującym na schematach blokowych.
Wśród instrukcji występują:- instrukcja pusta
- instrukcja grupująca
- instrukcje decyzyjne
- pętle
Instrukcja pusta składa się z jednego średnika ”;” i jak wskazuje jej nazwa, jest ignorowana. Pozostałe typy instrukcji omówione są poniżej.
Instrukcja grupująca
Instrukcja ta pozwala na traktowanie sekwencji instrukcji jak pojedynczej instrukcji. Instrukcja ta składa się z otwierającego nawiasu klamrowego “{”, listy instrukcji oddzielonych średnikami (najczęściej zapisywanych w kolejnych wierszach programu), a kończy nawiasem klamrowym zamykającym “}”.
Ogólna postać instrukcji grupującej przedstawiona jest poniżej:
{ //instrukcja_1; //instrukcja_2; //... }
Przykład
W poniższym przykładzie instrukcja grupująca obejmuje trzy operacje przypisania wartości do zmiennych, odpowiednia a, b i c.
int main(){ int a,b,c; { a = 5; b = 2; c = a * b; } }
Instrukcji grupująca nie posiada swojego bezpośredniego odpowiednika w schematach blokowych. Każdy fragment schematu blokowego, w którym wszystkie ścieżki wykonania zaczynają się i kończą w tych samych punktach może być objęty instrukcją grupującą.
Powyższemu przykładowi odpowiada po prostu sekwencja bloków obliczeniowych:
Instrukcja warunkowa i instrukcja selekcji
W języku C, podobnie jak w schematach blokowych, występują dwie instrukcje decyzyjne (odpowiadają one blokowi warunkowemu oraz blokowi selekcji): warunkowa i selekcji.
Instrukcja warunkowa
Instrukcja warunkowa pozwala na wykonanie alternatywnych ścieżek programu, w zależności od spełnienia określonego warunku logicznego. Instrukcja ta składa się ze słowa kluczowego if, po którym następuje ujęte w nawiasy okrągłe wyrażenie logiczne, po nim zaś następuje instrukcja, która wykonywana jest, gdy warunek logiczny jest prawdziwy. Po tej instrukcji może nastąpić słowo kluczowe else, wraz z instrukcją, która jest wykonywana, jeśli warunek logiczny nie zachodzi. W poprawnej instrukcji warunkowej nie musi jednak występować klauzula else.
Ogólny schemat instrukcji warunkowej przedstawiony jest poniżej.
if(wyr_log) //instrukcja_1; else //instrukcja_2; //instrukcja_3;
Kod który następuje po drugiej instrukcji (w przypadku wystąpienia klauzuli else) lub pierwszej (w przypadku przeciwny) wykonywany jest niezależnie od tego, czy wyrażenie logiczne jest prawdziwe, czy fałszywe. Powyższemu przykładowi na schemacie blokowym odpowiadałaby następująca grupa bloków:
Ponieważ w instrukcji warunkowej w poszczególnych alternatywnych ścieżkach wykonania programu może wystąpić tylko jedna instrukcja, w celu wykonania większej liczby instrukcji, konieczne jest wykorzystanie instrukcji grupującej (patrz poniższy przykład).
Wyrażenia logiczne występujące w instrukcji warunkowej mogą stanowić alternatywę lub koniunkcję wyrażeń logicznych. Alternatywa logiczna zapisywana jest za pomocą operatora ||, natomiast koniunkcja za pomocą operatora &&. Negacja zapisywana jest za pomocą operatora !.
Przykład
#include <stdio.h> #include <stdlib.h> int main(){ int rok, liczba_dni, dni_w_lutym; char napis[31]; printf("Wprowadź rok: "); fgets(napis,31,stdin); rok = atoi(napis); if ((rok % 4 == 0 && ((rok % 100 != 0) || (rok % 400 == 0))){ liczba_dni = 366; dni_w_lutym = 29; } else { liczba_dni = 365; dni_w_lutym = 28; } printf("W roku %d jest %d dni.\n",rok,liczba_dni); printf("Luty ma w tym roku %d dni.\n",dni_w_lutym); }
Powyższy fragment kodu ustala wartość zmiennych liczba_dni oraz dni_w_lutym na podstawie wartości zmiennej rok. (Lata przestępne wypadają co cztery lata, z wyjątkiem lat będących wielokrotnością 100 ale nie będących wielokrotnością 400). Zastosowano w nim wyrażenie logiczne składające się z alternatywy, negacji oraz koniunkcji, a także przedstawiono zastosowanie instrukcji grupującej w kontekście instrukcji warunkowej.
Ponadto w przykładzie tym wykorzystane są funkcje i procedury biblioteczne printf, fgets oraz atoi (o funkcjach i procedurach czytaj dalej). Pierwsza procedura pozwala wyprowadzać komunikaty na zewnątrz wykonywanego programu (zazwyczaj na ekran monitora). Druga procedura pozwala wczytać wartość wprowadzoną z klawiatury do zmiennej. Natomiast funkcja atoi pozwala na konwersję napisu do wartości liczbowej.
Przykładowe wykonanie programu mogłoby wyglądać następująco:
Wprowadź rok: 2000 W roku 2000 jest 366 dni. Luty ma w tym roku 29 dni.
Instrukcja selekcji
Instrukcja selekcji służy do wyboru jednej spośród wielu możliwych ścieżek realizacji algorytmu. Instrukcja ta składa się ze słowa kluczowego switch, po którym następuje nazwa zmiennej ujęta w nawiasy okrągłe oraz otwierający nawias klamrowy. Każda alternatywna ścieżka realizacji algorytmu zaczyna się od słowa kluczowego case, po którym występuje stała oraz dwukropek oznaczający początek listy instrukcji, które mają zostać zrealizowane dla tej opcji. List instrukcji kończy się słowem kluczowym break.
W ogólnym przypadku można łączyć kilka opcji w jedną (jeśli
wprowadzi się kolejną instrukcję case
bez słowa kluczowego break
lub jeśli poda się zakres stałych w postaci dolnego i górnego ograniczenia
i trzykropka występującego pomiędzy nimi).
Ogólna postać instrukcji selekcji przedstawiona jest poniżej:
switch(zmienna){ case stala1 : //instr_1; break; case stala2 : //instr_2; break; case stala3 : case stala4 : //instr_3; break; }
Instrukcji selekcji na schematach blokowych odpowiada blok selekcji:
Przykład
#include <stdio.h> int main(){ char napis[2]; printf("Wprowadź znak: "); fgets(napis,2,stdin); switch(napis[0]){ case '.' : case ',' : case '!' : printf("znak przestankowy\n"); break; case '0' ... '9' : printf("cyfra\n"); break; case 'a' ... 'z' : case 'A' ... 'Z' : printf("litera\n"); break; case ' ' : printf("spacja\n"); break; } }
W powyższym przykładzie instrukcja selekcji wykorzystywana jest do opisywania niektórych typów znaków występujących jako pierwszy znak zmiennej napis.
Przykładowe wykonanie programu mogłoby wyglądać następująco:
Wprowadź znak: .
znak przestankowy
Pętle
Schematy blokowe nie dostarczają żadnych specjalnych mechanizmów do tworzenia pętli (czyli fragmentów algorytmu, które wykonywana są wielokrotnie). W większości języków programowania istnieją jednak instrukcje, które pozwalają na łatwe definiowanie pętli, gdyż są one stosowane bardzo często.
W języku C istnieją trzy instrukcje służące do tworzenia pętli:while
do
...while
for
W zasadzie każda pętla może być skonstruowana przy użyciu dowolnej z tych instrukcji. Tym niemniej, zazwyczaj użycie wybranej instrukcji jest wygodniejsze w tych albo innych przypadkach.
Pętla while
Pętla tworzona z wykorzystaniem instrukcji while jest najprostszym typem pętli. Pozwala ona na wykonywanie pewnej instrukcji tak długo, jak długo spełniony jest określony warunek logiczny.
Pętla tego rodzaju składa się ze słowa kluczowego while, po którym występuje warunek logiczny, a po nim instrukcja, która ma być wykonywana.
Ogólna postać pętli while
przedstawiona jest w poniższym przykładzie:
while(wyr_log) //instrukcja_1;
Dosyć jasne jest, że jeśli początkowo warunek logiczny jest spełniony i w instrukcji wykonywanej w ramach pętli nie dokonywane jest żadne obliczenie, które może spowodować zmianę wartości warunku logicznego, to pętla ta będzie wykonywała się w nieskończoność.
Pętli while
odpowiada następujący schemat blokowy:
Przykład
#include <stdio.h> int main(){ char znak; while(!feof(stdin)){ znak = getchar(); printf("%c\n",znak); } }
znak = getchar()
) oraz wypisywania ich na ekran (printf("%c\n",znak)
).
Funkcja feof zwraca wartość fałsz
, tak długo, jak długo na wejściu dostępne
są dane, zatem pętla będzie powtarzała się tak długo, jak długo użytkownik
nie zakończy wprowadzania znaków (np. w systemach uniksowych wprowadzanie danych
można zakończyć wprowadzając sekwencję Ctrl+D). Powtórne wystąpienie funkcji getchar
związane jest z faktem, że po wprowadzeniu znaku konieczne jest naciśnięcie klawisza
Przykładowe wykonanie programu mogłoby wyglądać następująco:
a a 1 1 g g [Ctrl+D]
Pętla do … while
Pętla tworzona z wykorzystaniem instrukcji do … while wykonywana jest co najmniej raz i kończy się wykonywać, jeśli nie jest spełniony występujący w niej warunek logiczny.
Pętla tego rodzaju składa się ze słowa kluczowego do, po którym występuje instrukcja przeznaczona do wykonania, a kończy się kluczowym while i warunkiem logicznym ujętym w nawiasy okrągłe.
Ogólna postać pętli do ... while
przedstawiona jest poniżej:
do //instrukcja_1; while(wyr_log);
Należy zwrócić uwagę, że pętla do ... while
różni się od pętli while
pod
tym względem, że pętla do ... while
wykonuje się co najmniej 1 raz, natomiast pętla while
może się
nie wykonać wcale.
Pętli do ... while
odpowiada następujący schemat blokowy:
Przykład
#include <stdio.h> int main(){ char znak; do { printf("Wprowadź znak i naciśnij [enter]. Aby zakończyć wprowadź 'k'\n"); znak = getchar(); printf("Wprowadziłeś %c\n",znak); getchar(); } while(znak != 'k'); }
W powyższym kodzie użytkownik proszony jest o wprowadzenie znaku. Znak wprowadzony przez użytkownika jest ponownie wyprowadzany na ekran. Procedura ta powtarza się do momentu, w którym użytkownik wprowadzi znak ‘k’.
Przykładowe wykonanie programu mogłoby wyglądać następująco:
Wprowadź znak i naciśnij [enter]. Aby zakończyć wprowadź 'k' a Wprowadziłeś a G Wprowadziłeś G k Wprowadziłeś k
Pętla for
Pętla realizowana przy użyciu słowa kluczowego for zwana jest pętlą numeryczną, gdyż liczba jej wykonań zazwyczaj określana jest przez wartości numeryczne. Pętla ta jest często stosowana do wykonywania określonych operacji na tablicach, gdyż w typowej postaci zawiera zmienną indeksującą, która w trakcie jej wykonywania przyjmuje wartości z pewnego zakresu, który może odpowiadać zakresowi tablicy.
Pętla ta definiowana jest za pomocą słowa kluczowego for, po którym następuje wyrażenie inicjujące, warunek kontynuacji pętli oraz krok pętli. W jej ciele występuje instrukcja, która ma zostać wykonana.
Ogólna, typowa postać pętli for
przedstawiona jest poniżej.
for(indeks = war_1; indeks < war_2; indeks++) //instrukcja_1;
Typowa pętla for
, wykonywana jest w następujący
sposób: najpierw zmiennej indeksującej przypisywana jest wartość pierwszego
(w tym wypadku – dolnego) zakresu, następnie wartość zmiennej porównywana
jest z wartością drugiego (górnego) zakresu i jeśli jest ona mniejsza,
wykonywana jest instrukcja będąca jej ciałem. Po
wykonaniu tej instrukcji, zmienna indeksująca zwiększana jest o 1 i ponownie
porównywana z górnym zakresem.
W ogólności – na samym początku raz wykonywane jest wyrażenie inicjujące, potem sprawdzany jest warunek kontynuacji pętli, następnie wykonywane jest ciało pętli, a po nim ewaluowane jest wyrażenie stanowiące krok pętli. Jeśli warunek kontynuacji pętli jest dalej spełniony, ponownie wykonuje się ciało pętli i krok, itd.
Typowej pętli for
odpowiada następujący schemat blokowy:
Aby odwrócić kierunek wykonywania pętli, w kroku pętli, zamiast inkrementować (++) indeks, można go dekrementować (—). Konieczne jest wtedy zmienienie warunku kontynuacji pętli.
Przykład
#include <stdio.h> int main(){ const n = 5; int indeks; int liczby[n]; char napis[11]; for(indeks = 0; indeks < n; indeks++){ printf("Wprowadź %d. liczbę całkowitą: ",indeks+1); fgets(napis,11,stdin); liczby[indeks] = atoi(napis); } for(indeks = n-1; indeks >= 0; indeks--){ printf("%d\n",liczby[indeks]); } }
W powyższym przykładzie użytkownik na początku proszony jest o wprowadzenie 5 liczb całkowitych, które są następnie wyświetlane w odwrotnej kolejności.
W przykładzie tym zastosowano, nieomawiane wcześniej słowo kluczowe const,
które pozwala na definiowanie stałych. W przykładzie definiowana jest
stała
Wprowadź 1. liczbę: 10 Wprowadź 2. liczbę: 2 Wprowadź 3. liczbę: 4 Wprowadź 4. liczbę: 1 Wprowadź 5. liczbę: 12 12 1 4 2 10
Podprogramy
W trakcie tworzenia złożonych algorytmów niejednokrotnie zdarza się, że pewne zadania przez nie realizowane powtarzają się w wielu miejscach. Kiedy dotyczy to sekwencji prostych operacji, to można oczywiście zastosować pętlę, w której algorytm zostanie wykonany wielokrotnie.
Taka sytuacja nie zawsze ma jednak miejsce – zazwyczaj powtarzający się fragment kodu znajduje się w różnych miejscach algorytmu i zastosowanie wcześniej opisanych konstrukcji nie pozwala rozwiązać tego problemu.
Dlatego jednym z podstawowych udogodnień dostarczanych przez języki programowani
są podprogramy czyli wydzielone algorytmy, które mogą być wielokrotnie
wywoływane. W rzeczywistości procedury opisane wcześniej takie
jak printf
czy fgets
są właśnie podprogramami. Jednakże używanie tylko i
wyłącznie wbudowanych procedur nie pozwoliłoby rozwiązać omawianego tutaj
problemu, gdyż do efektywnego korzystania z podprogramów poza
mechanizmem ich wywoływania potrzebny jest również
mechanizm ich definiowania.
Podprogramy nie byłyby również zbyt użyteczne, gdyby nie to, że mogą być wywoływane z różnymi parametrami, które modyfikują działanie podprogramu. Na parametry podprogramu można również spojrzeć jak na dane wejściowe, które mają ulec przetworzeniu.
W języku C można wyróżnić dwa warianty podprogramów: procedury i funkcje. Różnią się od siebie tym, że funkcje, w przeciwieństwie do procedur, zwracają wartość (produkują dane wyjściowe). Ich działanie podobne jest zatem do funkcji matematycznych, które dla określonych argumentów produkują pewien określony wynik.
Funkcje
Definicja funkcji składa się z nagłówka oraz ciała procedury.
Nagłówek funkcji składa się z typu wartości zwracanej przez funkcję,
jej nazwy oraz ujętej w nawiasy okrągłe list oddzielonych przecinkami
argumentów postaci typ nazwa_argumentu
.
Ciało funkcji składa się z ciągu instrukcji języka C ujętych w nawiasy klamrowe. Ogólna postać definicji funkcji przedstawiona jest poniżej:
typ_rezultatu nazwa_funkcji(typ_1 argument_1, typ_2 argument_2){
//ciało funkcji
}
W ciele funkcji dostępne są wszystkie zmienne globalne programu, a także zmienne, które są jej parametrami. Te ostatnie zmienne przyjmują wartości takie jak wartości argumentów przekazanych w wywołaniu funkcji.
Nazwa funkcji wraz z listą jej parametrów stanowi jej sygnaturę.
Jeśli chcemy wywołać wcześniej zdefiniowaną funkcję musimy podać jej nazwę oraz ujęte w nawiasy okrągłe argumenty. Argumenty mogą być stałymi lub zmiennymi, których ilość i typ odpowiada dokładnie ilości i typowi argumentów pojawiających się w sygnaturze funkcji.
Aby określić wartość zwracaną przez funkcję należy skorzystać ze słowa kluczowego return
.
Warunkiem poprawności funkcji jest to, aby dla każdej możliwej ścieżki wykonania
funkcji została zwrócona wartość.
Ogólna, typowa postać wywołania funkcji jest następująca:
zmienn = nazwa_funkcji(argument_1, argument_2, ...);
Konieczne jest, aby typ zmiennej, do której przypisujemy wartość wywołania funkcji odpowiadał typowi zwracanemu przez tę funkcję. Jeśli typy te będą niezgodne, program nie zostanie skompilowany.
Przykład wykorzystania funkcji:
#include <stdio.h> int potega(int podstawa, int wykladnik){ int indeks, rezultat; rezultat = 1; for(indeks = 0; indeks < wykladnik; index++){ rezultat = rezultat * podstawa; } return rezultat; } int main(){ int wartosc; wartosc = potega(2,3); printf("Trzecią potęgą liczby 2 jest : %d\n",wartosc); wartosc = potega(3,4); printf("Czwartą potęgą liczby 3 jest : %d\n",wartosc); }
Trzecią potęgą liczby 2 jest : 8 Czwartą potęgą liczby 3 jest : 81
Warto zwrócić uwagę, że sam program definiowany jest jako funkcja, która również zwraca pewną wartość. W przeciwieństwie do typowej funkcji, nie musi on jednak zwracać żadnej wartości. Wartość zwracana przez program dostępna jest na zewnątrz i jeśli jest ona różna od zera, oznacza to, że wykonanie programu zakończyło się błędem. Zwrócona wartość stanowi kod błędu wykonania.
Procedury
Definicja procedury nie różni się bardzo od definicji funkcji. Jedyna różnica
polega na tym, że zamiast określonego typu zwracanej wartości, pojawia się słowo kluczowe viod
oraz, że procedura nie zwraca wartości, zatem nie zawiera klauzuli return
.
Ogólna postać definicji procedury jest następująca:
void nazwa_procedury(typ_1 argument_1, typ_2 argument_2){ //ciało procedury }
W ciele procedury dostępne są wszystkie zmienne, które wymienione były w opisie funkcji.
Wywołanie procedury musi spełniać te same założenia co wywołanie funkcji. Należy jednak pamiętać, że skoro procedura nie zwraca wartości, nie może pojawić się po prawej stronie instrukcji przypisania.
Przykład wykorzystania procedury:
void wypisz_liczbe(int liczba){ printf("Liczba posiada wartość %d\n",liczba); } int main(){ wypisz_liczbe(5); wypisz_liczbe(10); //wywołanie niepoprawne - brak argumentu wypisz_liczbe(); //wywołanie niepoprawne - niewłaściwy typ argumentu wypisz_liczbe("aa"); }
W powyższym programie definiujemy procedurę wypisz_liczbe
, która
wypisuje komunikat “Liczba posiada wartość ” oraz podaje
wartość liczby. Dwa wywołania występujące w programie są
niepoprawne. Pierwsze posiada inną liczbę argumentów niż sygnatura
procedury, w drugim – typ argumentu przekazanego w wywołaniu
nie pokrywa się z typem argumentu występującym w sygnaturze.
Liczba posiada wartość 5 Liczba posiada wartość 10