-
Notifications
You must be signed in to change notification settings - Fork 0
1. Funkcje
Funkcja jest specjalnie wydzielonym fragmentem kodu, który możemy wykorzystywać wielokrotnie w różnych miejscach programu, a jego działanie jest konkretnie sprecyzowane. W trakcie trwania tego kursu wykorzystywaliśmy już kilka funkcji z biblioteki standardowej, takich jak: printf() czy scanf(). Nie trzeba było ich pisać samodzielnie, bo zrobił to już za nas inny programista. Wystarczyło, żebyśmy wiedzieli jak one działają i jak z nich korzystać. Bez funkcji praca zespołowa nad nawet średnim programem byłaby prawie niemożliwa. Większość funkcji wymaga informacji z zewnątrz. Są to liczby, tekst i inne obiekty. Większość z nich również pewne informacje zwraca.
W języku C podprogram główny, czyli zdefiniowany w kodzie źródłowym, wybrany podprogram, od którego rozpocznie się wykonanie gotowego, całego programu, definiuje się jako funkcję o nazwie (identyfikatorze) main. Dana funkcja jest obowiązkową składową każdego programu napisanego w języku C. Dla porządku przyjrzymy się jej bliżej.
int main()
{
// instrukcje
return 0;
}W C zwykle przyjmuje się, że 0 oznacza poprawne zakończenie funkcji, dlatego na sam koniec funkcji main wywołujemy instrukcję return 0. Zwracane 0 jest liczbą całkowitą, a to tłumaczy zastosowanie instrukcji int main() na początku samej definicji.
W C definicję funkcji powinna poprzedzić jej deklaracja. W deklaracji funkcji określamy nazwę funkcji, typ danej zwracanej przez funkcję oraz typ(y) argumentu(ów), podążając za schematem:
<zwracany typ> nazwa_funkcji(<typ 1. argumentu>, <typ 2. argumentu>, ...);Zmienne, które pojawiają się między () to właśnie argumenty, w przypadku definicji funkcji wystarczy, że podamy sam typ (bez nazwy zmiennej). Jeśli efektem działania funkcji nie jest bezpośrednio jakiś typ danych, wtedy zwracany typ określamy jako void, podobnie możemy się zachować jeśli chodzi o przyjmowane argumenty - (void) lub po prostu (). Przekazywanie tablicy jako argument do funkcji polega na przekazaniu wskaźnika na pierwszy element tablicy, np. dla tablicy znaków będzie to char[] lub char*. W przypadku definicji zmiennej musimy już zaprogramować jej działanie, ogólny schemat zprojektowania funkcji prezentuje się następująco:
<typ zwracany> nazwa_funkcji(<typ 1. argumentu> nazwa_1_argumentu, <typ 2. argumentu> nazwa_2_argumentu, ...){
instrukcja;
instrukcja;
...
}Deklarację funkcji umieszcza się przed funkcją główną programu, podczas gdy jej definicja zwyczajowo znajduje się zaraz po funkcji main(). Możesz pominąć krok związany z deklarowaniem funkcji, ale wtedy całościowo (wraz z definicją) powinna się ona znaleźć przed główną funkcją programu. Przez wywołanie funkcji rozumiemy podanie jej nazwy wraz z niezbędnymi argumentami. Funkcje wywołujemy tam gdzie spodziewamy się wykonać operacje, które są wewnatrz niej zawarte. Poniżej przykład programu, w którym zdefiniowano dwie funkcje, jedna z nich przyjmuje jeden argument, a druga nie przymuje ich wcale.
#include <stdio.h>
// Deklaracja funkcji powitanie()
void powitanie();
// Deklaracja funkcji powitanie_goscia()
void powitanie_goscia(char[]);
// Funkcja glowna programu
int main(){
char nazwa[] = "Sylwia";
powitanie(); // wywołanie funkcji powitanie - bez jakichkolwiek argumentów jak w powyższej definicji
powitanie_goscia(nazwa); // wywołanie funkcji powitanie_goscia z wykorzystaniem zmiennej nazwa
powitanie_goscia("Czytelnik"); // wywołanie funkcji powitanie_goscia z wykorzystaniem napisu "Czytelnik"
return 0;
}
// Definicja funkcji powitanie()
// Funkcja wypisuje na ekranie napis "Witam w moich skromnych progach!"
// Przyjmowane argumenty: nic
// Zwracane argumenty: nic
void powitanie(){
printf("Witam w moich skromnych progach!");
}
// Definicja funkcji powitanie_goscia()
// Funkcja wypisuje na ekranie napis "Witam <imie> w moich skromnych progach!"
// Przyjmowane argumenty: imie - literał znakowy
// Zwracane argumenty: nic
void powitanie_goscia(char imie[]){
printf("Witam %s w moich skromnych progach!", imie);
}Zwróć uwagę, że zmienna imie wykorzystywana jest tylko wewnątrz ciała funkcji powitanie_goscia(), czyli tam gdzie została utworzona (przekazana jako argument). Natomiast w funkcji main() korzystamy już z zadeklarowanej zmiennej nazwa oraz napisu Czytelnik.
Pozostał nam jeszcze przypadek funkcji, które zwracają wartość. Jest to dokonywane w ciele funkcji za pomoca instrukcji return. Ponownie popatrzmy na konkretny przykład:
#include <stdio.h>
// deklaracja funkcji suma()
int suma(int,int);
// deklaracja funkcji roznica()
int roznica(int,int);
int main(){
char nazwa[20];
printf("Podaj swoje imie: ");
scanf("%s",nazwa); // zwroc uwage braku znaku & - zmienna nazwa jest już wskaźnikiem (na pierwszy element tej tablicy)
printf("Nazywasz się %s", nazwa); // wypisujemy komunikat z imieniem na ekran stosujac funkcje print
// Prosimy uzytkownika o podanie 2 liczb zmiennoprzecinkowych
int a, b;
printf("Podaj pierwsza liczbe: "));
scanf("%d", &a);
printf("Podaj druga liczbe: "));
scanf("%d", &b);
// wykonujemy obliczenia - wywolujemy zdeklarowane wczesniej funkcje suma i roznica
int suma_liczb = suma(a,b);
// Wyswietlamy wyniki
print("%d + %d = %d", a, b, suma_liczb);
print("%d - %d = %d", suma_liczb, b, roznica(suma_liczb,b));
return 0;
}
/* Funkcja dodaje dwie liczby
Przyjmowane argumenty: skladnik1 - zmienna liczbowa, skladnik2 - zmienna iczbowa
Zwracane argumenty: suma dwoch liczb */
int suma(int skladnik1, int skladnik2){
return skladnik1+skladnik2;
}
/* Funkcja odejmuje dwie liczby
Przyjmowane argumenty: odjemna - zmienna liczbowa, odjemnik - zmienna iczbowa
Zwracane argumenty: wynik - wynik odejmowania odjemnika od odjemnej */
int roznica(int odjemna, int odjemnik){
int wynik = odjemna - odjemnik;
return wynik;
}Funkcje suma() oraz roznica() przyjmuja dwa argumenty liczbowe oraz zwracaja wynik odpowiedniego dzialania korzystajac z instrukcji return. Zwróć uwagę na to, co zostało napisane wewnatrz funkcji main() - z wynikiem zwracanym przez którąkolwiek z zastosowanych powyżej funkcji musimy dalej coś zrobić. Możemy go bezpośrednio wyświetlić na ekran jak w przypadku linijki kodu print("%d - %d = %d", suma_liczb, b, roznica(suma_liczb,b)) lub przechować w nowej zmiennej suma_liczb = suma(a,b) i następnie wyświetlić lub wykorzystać do dalszych obliczeń.
W języku C istnieją dwa sposoby przekazywania argumentów do funkcji:
- przez kopię (przez wartość),
- przez wskaźnik (przez adres).
Gdy wywołujemy funkcję, wartości argumentów, z którymi ją wywołujemy, są kopiowane do funkcji.
Kopiowana - to znaczy, że do wywoływanej funkcji przekazywana jest wartość zmiennej, a nie sama zmienna; zmienna jest kopiowana. Formalnie mówi się wtedy, że argumenty są przekazywane przez wartość, czyli wewnątrz funkcji operujemy tylko na ich kopiach. Możliwe jest też modyfikowanie zmiennych przekazywanych do funkcji - ale do tego potrzebne są już wskaźniki.
Do wywoływanej funkcji przekazywany jest adres zmiennej. Oznacza to, że zmiany wartości zmiennej wykonywane są na oryginalnej zmiennej. Z jedną z funkcji, do której argument przekazywanie jest przez wskaźnik mieliście już do czynienia - jest to funkcja scanf. Zmienne, do których funkcja scanf() wpisuje dane, przekazywane są przez wskaźnik (stąd stosujemy znak dereferencji). Poniżej przykład funkcji half, do której argumenty przekazywane są przez kopię.
// Autor: Karol Tarnowski
#include <stdio.h>
void half(int a);
int main(){
//deklaracja i inicjalizacja zmiennej a
int a = 13;
//wyświetlenie zmiennej a
//zmienna a = 13
printf("(main) a = %d\n",a);
//zmienna a przekazana do funkcji half() przez wartość
//funkcja half() wykonuje działania określone w definicji
half(a);
//ponowne wyswietlenie zmiennej a
//a = 13
//ponieważ funkcja half() działała na kopii
//czyli a nie zmieniła wartości
printf("(main) a = %d\n",a);
return 0;
}
void half(int a){
//automatyczna zmienna lokalna a
//przyjmuje wartość 13 - kopia wartości a z funkcji main()
//zmienna a podzielona przez 2
a = a/2;
//wyswietlenie zmiennej a = 6
printf("(half) a = %d\n",a);
}A tutaj znów funkcja half, ale tym razem przekazujemy do niej zmienne przez adres.
// Autor: Karol Tarnowski
#include <stdio.h>
void half(int * pa);
int main(){
//deklaracja i inicjalizacja zmiennej a
int a = 13;
//wyświetlenie zmiennej a
//zmienna a = 13
printf("(main) a = %d\n",a);
//zmienna a przekazana do funkcji half() przez wskaźnik
//do funkcji() half przekazywany jest adres zmiennej
//adres zmiennej odczytywany jest operatorem adresu &
half(&a);
//ponowne wyswietlenie zmiennej a
//ponieważ funkcja half działała na oryginale
//czyli a zmieniła wartość
//a = 6
printf("(main) a = %d\n",a);
return 0;
}
void half(int * pa){
//automatyczna zmienna lokalna pa
//jest wskaźnikiem na int
//jako argument przekazano adres a,
//zatem pa wskazuje na oryginalną zmienną a
//zmienna pod adresem pa jest dzielona przez 2
//wykorzystano operator wskaźnikowy *
//aby uzyskać dostęp do zmiennej a
*pa = *pa/2;
//wyswietlenie zmiennej wskazywanej przez pa (czyli a)
printf("(half) a = %d\n",*pa);
}Tablica natomiast jest zawsze przekazywana do funkcji przez adres (wskaźnik na jej pierwszy element).
void foo(int *arg); // argument przekazany przez wskaźnik
void foo(int arg[5]); // argument przekazany przez tablicę z podaną wielkością
void foo(int arg[]); // argument przekazany przez tablicę bez podanej wielkościJęzyk C ma możliwość tworzenia tzw. funkcji rekurencyjnych. Jest to funkcja, która w swojej własnej definicji (ciele) wywołuje samą siebie. Najbardziej klasycznym przykładem może tu być silnia. Napiszemy teraz funkcję definiującą silnię w wersji rekurencyjnej (wywołując funkcję w jej własnej definicji) oraz iteracyjnej (przy pomocy pętli).
// definicja rekurencyjna
int silnia_rek (int liczba)
{
int sil;
if (liczba<0) return 0; // wywołanie jest bezsensowne, zwracamy 0 jako kod błędu
if (liczba==0 || liczba==1) return 1;
sil = liczba*silnia(liczba-1);
return sil;
}
// definicja iteracyjna
int silnia_iter (int liczba)
{
int sil = 1;
if (liczba<0) return 0; // wywołanie jest bezsensowne, zwracamy 0 jako kod błędu
if (liczba==0) return 1;
int i;
for(i=1; i<=liczba; i++){
sil *= liczba;
}
return sil;
}