Wstęp

edytuj

W matematyce pod pojęciem funkcji rozumiemy twór, który pobiera pewną liczbę argumentów i zwraca wynik[1]. Jeśli dla przykładu weźmiemy funkcję sin(x) to x będzie zmienną rzeczywistą, która określa kąt, a w rezultacie otrzymamy inną liczbę rzeczywistą - sinus tego kąta.

W C funkcja (czasami nazywana podprogramem, rzadziej procedurą) to wydzielona część programu, która przetwarza argumenty i ewentualnie zwraca wartość, która następnie może być wykorzystana jako argument w innych działaniach lub funkcjach. Funkcja może posiadać własne zmienne lokalne. W odróżnieniu od funkcji matematycznych, funkcje w C mogą zwracać dla tych samych argumentów różne wartości.

Po lekturze poprzednich części podręcznika zapewne mógłbyś podać kilka przykładów funkcji, z których korzystałeś. Były to np.

  • funkcja printf(), drukująca tekst na ekranie, czy
  • funkcja main(), czyli główna funkcja programu.

Główną motywacją tworzenia funkcji jest unikanie powtarzania kilka razy tego samego kodu. W poniższym fragmencie:

for(i=1; i <= 5; ++i) {
  printf("%d ", i*i);
}
for(i=1; i <= 5; ++i) {
  printf("%d ", i*i*i);
} 
for(i=1; i <= 5; ++i) {
  printf("%d ", i*i);
}

widzimy, że pierwsza i trzecia pętla for są takie same. Zamiast kopiować fragment kodu kilka razy (co jest mało wygodne i może powodować błędy) lepszym rozwiązaniem mogłoby być wydzielenie tego fragmentu tak, by można go było wywoływać kilka razy. Tak właśnie działają funkcje.

Innym, nie mniej ważnym powodem używania funkcji jest rozbicie programu na fragmenty wg ich funkcjonalności. Oznacza to, że jeden duży program dzieli się na mniejsze funkcje, które są "wyspecjalizowane" w wykonywaniu określonych czynności. Dzięki temu łatwiej jest zlokalizować błąd. Ponadto takie funkcje można potem przenieść do innych programów.


Formalnie wyróżniamy:

  • deklaracja funkcji
  • definicja funkcji
  • wywołanie funkcji

Tworzenie funkcji

edytuj

Dobrze jest uczyć się na przykładach. Rozważmy następujący kod:

int iloczyn (int x, int y)
{
  int iloczyn_xy;
  iloczyn_xy = x*y;
  return iloczyn_xy;
}

int iloczyn (int x, int y) to nagłówek funkcji, który opisuje, jakie argumenty przyjmuje funkcja i jaką wartość zwraca (funkcja może przyjmować wiele argumentów, lecz może zwracać tylko jedną wartość)[2]. Na początku podajemy typ zwracanej wartości - u nas int. Następnie mamy nazwę funkcji i w nawiasach listę argumentów.

Ciało funkcji (czyli wszystkie wykonywane w niej operacje) umieszczamy w nawiasach klamrowych. Pierwszą instrukcją jest deklaracja zmiennej - jest to zmienna lokalna, czyli niewidoczna poza funkcją. Dalej przeprowadzamy odpowiednie działania i zwracamy rezultat za pomocą instrukcji return.

Cel i zasady nadawania nazw:[3]

  • Wybierz słowo mające znaczenie (podaj kontekst): jednoznacznie i precyzyjnie opisywać koncept który nazywają[4]
  • Unikaj nazw ogólnych (takich jak tmp)
  • Nazwa funkcji nie może być taka sama jak słowo kluczowe języka C oraz jak nazwa innej funkcji, która została wcześniej zdefiniowana w programie[5]
  • Dołącz dodatkowe informacje do nazwy (użyj sufiksu lub prefiksu)
  • Nie rób zbyt długich ani zbyt krótkich imion
  • Używaj spójnego formatowania


Przykłady W nazwie są trzy człony określające:[6]

module_object_create()

Ten sposób podkreśla miejsce funkcji w strukturze modułu ( tak jak katalog)

Definicja

edytuj

Funkcję w języku C tworzy się następująco:

 typ identyfikator (typ1 argument1, typ2 argument2, typ_n argument_n)
 {
   /* instrukcje */
 }

Oczywiście istnieje możliwość utworzenia funkcji, która nie posiada żadnych argumentów. Definiuje się ją tak samo, jak funkcję z argumentami z tą tylko różnicą, że między okrągłymi nawiasami nie znajduje się żaden argument lub pojedyncze słówko void - w definicji funkcji nie ma to znaczenia, jednak w deklaracji puste nawiasy oznaczają, że prototyp nie informuje jakie argumenty przyjmuje funkcja, dlatego bezpieczniej jest stosować słówko void.

Funkcje definiuje się poza główną funkcją programu (main). W języku C nie można tworzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji).


Struktura definicji

return_type function_name( parameter_list ) {
   // body_of_the_function
}

Stary sposób definiowania funkcji

edytuj

Zanim powstał standard ANSI C, w liście parametrów nie podawało się typów argumentów, a jedynie ich nazwy. Również z tamtych czasów wywodzi się oznaczenie, iż puste nawiasy (w prototypie funkcji, nie w definicji) oznaczają, że funkcja przyjmuje nieokreśloną liczbę argumentów. Tego archaicznego sposobu definiowania funkcji nie należy już stosować, ale ponieważ w swojej przygodzie z językiem C Czytelnik może się na nią natknąć, a co więcej standard nadal (z powodu zgodności z wcześniejszymi wersjami) dopuszcza taką deklarację to należy tutaj o niej wspomnieć. Otóż wygląda ona następująco:

typ_zwracany nazwa_funkcji(argument1, argument2, argumentn)
  typ1 argumenty /*, ... */;
  typ2 argumenty /*, ... */;
  /* ... */
{
  /* instrukcje */
}

Na przykład wcześniejsza funkcja iloczyn wyglądałaby następująco:

int iloczyn(x, y)
  int x, y;
{
  int iloczyn_xy;
  iloczyn_xy = x*y;
  return iloczyn_xy;
}

Najpoważniejszą wadą takiego sposobu jest fakt, że w prototypie funkcji nie ma podanych typów argumentów, przez co kompilator nie jest w stanie sprawdzić poprawności wywołania funkcji. Naprawiono to (wprowadzając definicje takie jak je znamy obecnie) najpierw w języku C++, a potem rozwiązanie zapożyczono w standardzie ANSI C z 1989 roku.

Deklarowanie funkcji

edytuj

Czasami możemy chcieć przed napisaniem funkcji poinformować kompilator, że dana funkcja istnieje. Niekiedy kompilator może zaprotestować, jeśli użyjemy funkcji przed określeniem, jaka to funkcja, na przykład:

int a()
{
  return b(0);
}

int b(int p)
{
  if( p == 0 )
    return 1;
  else
    return a();
}
 
int main()
{
  return b(1);
}

W tym przypadku nie jesteśmy w stanie zamienić a i b miejscami, bo obie funkcje korzystają z siebie nawzajem. Rozwiązaniem jest wcześniejsze zadeklarowanie funkcji. Deklaracja funkcji (zwana czasem prototypem) to po prostu przekopiowana pierwsza linijka funkcji (przed otwierającym nawiasem klamrowym) z dodatkowo dodanym średnikiem na końcu. W naszym przykładzie wystarczy na samym początku wstawić:

int b(int p);

W deklaracji można pominąć nazwy parametrów funkcji:

int b(int);

Bardzo częstym zwyczajem jest wypisanie przed funkcją main samych prototypów funkcji, by ich definicje umieścić po definicji funkcji main, np.:

int a(void);
int b(int p);

int main()
{
  return b(1);
}

int a()
{
  return b(0);
}

int b(int p)
{
  if( p == 0 )
    return 1;
  else
    return a();
}

Z poprzednich rozdziałów pamiętasz, że na początku programu dołączaliśmy tzw. pliki nagłówkowe. Zawierają one właśnie prototypy funkcji i ułatwiają pisanie dużych programów. Dalsze informacje o plikach nagłówkowych zawarte są w rozdziale Tworzenie bibliotek.

argumenty funkcji

edytuj
  • liczba
    • stała liczba argumentów
      • bez argumentów ( zero argumentów) void
      • 1 i więcej argumentów
    • zmienna liczba argumentów (ang functions which take a variable number of arguments = variadic function)
  • sposób wczytywania[7]
    • za pomocą wartości (ang. call by value)
    • za pomocą wskaźnika (ang. call by reference)

Zobacz również

Zmienna liczba argumentów

edytuj

Zauważyłeś zapewne, że używając funkcji printf() lub scanf() po argumencie zawierającym tekst z odpowiednimi modyfikatorami mogłeś podać praktycznie nieograniczoną liczbę argumentów. Zapewne deklaracja obu funkcji zadziwi Cię jeszcze bardziej:

int printf(const char *format, ...);
int scanf(const char *format, ...);

Jak widzisz w deklaracji zostały użyte trzy kropki. Otóż język C ma możliwość przekazywania teoretycznie nieograniczonej liczby argumentów do funkcji (jedynym ograniczeniem jest rozmiar stosu programu). Cała zabawa polega na tym, aby umieć dostać się do odpowiedniego argumentu oraz poznać jego typ (używając funkcji printf, mogliśmy wpisać jako argument dowolny typ danych). Do tego celu możemy użyć wszystkich ciekawostek, zawartych w pliku nagłówkowym stdarg.h.

Załóżmy, że chcemy napisać prostą funkcję, która dajmy na to, mnoży wszystkie swoje argumenty (zakładamy, że argumenty są typu int). Przyjmujemy przy tym, że ostatni argument będzie 0. Będzie ona wyglądała tak:

 #include <stdarg.h>
 
 int mnoz (int pierwszy, ...)
 {
   va_list arg;
   int iloczyn = 1, t;
   va_start (arg, pierwszy);
   for (t = pierwszy; t; t = va_arg(arg, int)) {
     iloczyn *= t;
   } 
   va_end (arg);
   return iloczyn;
 }

va_list oznacza specjalny typ danych, w którym przechowywane będą argumenty, przekazane do funkcji. "va_start" inicjuje arg do dalszego użytku. Jako drugi parametr musimy podać nazwę ostatniego znanego argumentu funkcji. Makropolecenie va_arg odczytuje kolejne argumenty i przekształca je do odpowiedniego typu danych. Na zakończenie używane jest makro va_end - jest ono obowiązkowe!

Oczywiście, tak samo jak w przypadku funkcji printf() czy scanf(), argumenty nie muszą być takich samych typów. Rozważmy dla przykładu funkcję, podobną do printf(), ale znacznie uproszczoną:

#include <stdarg.h>

void wypisz(const char *format, ...) {
  va_list arg;
  va_start (arg, format);
  for (; *format; ++format) {
    switch (*format) {
    case 'i': printf("%d" , va_arg(arg, int)); break;
    case 'I': printf("%u" , va_arg(arg, unsigned)); break;
    case 'l': printf("%ld", va_arg(arg, int)); break;
    case 'L': printf("%lu", va_arg(arg, unsigned long)); break;
    case 'f': printf("%f" , va_arg(arg, double)); break;
    case 'x': printf("%x" , va_arg(arg, unsigned)); break;
    case 'X': printf("%X" , va_arg(arg, unsigned)); break;
    case 's': printf("%s" , va_arg(arg, const char *)); break;
    default : putc(*format);
    }
  }
  va_end (arg);
}

Przyjmuje ona jako argument ciąg znaków, w których niektóre instruują funkcję, by pobrała argument i go wypisała. Nie przejmuj się jeżeli nie rozumiesz wyrażeń *format i ++format. Istotne jest to, że pętla sprawdza po kolei wszystkie znaki formatu.


Zobacz również:

  • Funkcje o zmiennej liczbie argumentów (wariadyczne) ( ang. Variadic functions or varargs functions )[8] [9]
  • makra ( ang. function macro) zdefiniowane w <stdarg.h>[10]
    • va_start - umożliwia dostęp do zmiennych argumentów funkcji
    • va_arg - dostęp do następnego argumentu funkcji wariadycznej
    • va_copy - makes a copy of the variadic function arguments (C99)
    • va_end - ends traversal of the variadic function arguments
  • typ : va_list - holds the information needed by va_start, va_arg, va_end, and va_copy
  • Wiele starszych dialektów języka C zapewnia podobny, ale niekompatybilny mechanizm definiowania funkcji ze zmienną liczbą argumentów przy użyciu varargs.h ( legacy). Nie zaleca się używania <varargs.h>


Pomoc:

  • offline:
    • W systemach uniksowych możesz uzyskać pomoc dzięki narzędziu man, przykładowo pisząc: man va_start

Wywoływanie funkcji

edytuj

Funkcje wywołuje się następująco:

identyfikator (argument1, argument2, argumentn);

Jeśli chcemy, aby przypisać zmiennej wartość, którą zwraca funkcja, należy napisać tak:

zmienna = funkcja (argument1, argument2, argumentn);

Przykładowo, mamy funkcję:

void pisz_komunikat()
{
  printf("To jest komunikat\n");
}

Jeśli teraz ją wywołamy:

pisz_komunikat;   /* ŹLE    */
pisz_komunikat(); /* dobrze */

to pierwsze polecenie nie spowoduje wywołania funkcji. Dlaczego? Aby kompilator C zrozumiał, że chodzi nam o wywołanie funkcji, musimy po jej nazwie dodać nawiasy okrągłe, nawet, gdy funkcja nie ma argumentów. Użycie samej nazwy funkcji ma zupełnie inne znaczenie - oznacza pobranie jej adresu. W jakim celu? O tym będzie mowa w rozdziale Wskaźniki.

Przykład

A oto działający przykład, który demonstruje wiadomości podane powyżej:

 #include <stdio.h>
 
 int suma (const int a, const int b)
 {
   return a+b;
 }
 
 int main ()
 {
   int m = suma (4, 5);
   printf ("4+5=%d\n", m);
   return 0;
 }

Zwracanie wartości

edytuj
  • za pomocą return ( tylko jedna wartość )
  • nie zwracanie żadnej wartość ( zwracany typ = void), bez return,
  • zwracanie kilku wartości
    • struktury
    • wskaźniki

słowo kluczowe return

edytuj

return to słowo kluczowe języka C.

W przypadku funkcji służy ono do:

  • przerwania funkcji (i przejścia do następnej instrukcji w funkcji wywołującej)
  • zwrócenia wartości.

W przypadku procedur powoduje przerwania procedury bez zwracania wartości.

Użycie tej instrukcji jest bardzo proste i wygląda tak:

return zwracana_wartość;

lub dla procedur:

return;

Możliwe jest użycie kilku instrukcji return w obrębie jednej funkcji. Wielu programistów uważa jednak, że lepsze jest użycie jednej instrukcji return na końcu funkcji, gdyż ułatwia to śledzenie przebiegu programu.

Standardowo zwracana wartość

edytuj

W C zwykle przyjmuje się, że 0 oznacza poprawne zakończenie funkcji:

return 0; /* funkcja zakończona sukcesem */

a inne wartości oznaczają niepoprawne zakończenie:

return 1; /*funkcja zakończona niepowodzeniem */

Ta wartość może być wykorzystana przez inne instrukcje, np. if .

Procedura czyli jak nie zwracać wartości

edytuj

Przyjęło się, że procedura od funkcji różni się tym, że ta pierwsza nie zwraca żadnej wartości. Zatem, aby stworzyć procedurę należy napisać:

void identyfikator (typ1 argument1, typ2 argument2, typn argument_n)
{
  /* instrukcje */
}

void (z ang. pusty, próżny) jest słowem kluczowym mającym kilka znaczeń, w tym przypadku oznacza "brak wartości".

Generalnie, w terminologii C pojęcie "procedura" nie jest używane, mówi się raczej "funkcja zwracająca void".


Czasem procedura nie zwraca wartości ale zmienia wartość argumentów.

Jak zwrócić kilka wartości?

edytuj

Jeśli chcesz zwrócić z funkcji kilka wartości, musisz zrobić to w trochę inny sposób. Generalnie możliwe są dwa podejścia:

  • "upakowanie" zwracanych wartości – można stworzyć tak zwaną strukturę, która będzie przechowywała kilka zmiennych (jest to opisane w rozdziale Typy złożone).
  • zwracanie jednej z wartości w normalny sposób ( return), a pozostałych jako parametrów. Jeśli chcesz zobaczyć przykład, możesz przyjrzeć się funkcji scanf() z biblioteki standardowej.

Za pomocą struktur

edytuj

Przykład [11]

#include<stdio.h>
 
typedef struct{
	int integer;
	float decimal;
	char letter;
	char string[100];
	double bigDecimal;
}Composite;
 
Composite f()
{
	Composite C = {1, 2.3, 'a', "Hello World", 45.678};
	return C;
}
 
 
int main()
{
	Composite C;
          

        C = f();
 
	printf("Values from a function returning a structure : { %d, %f, %c, %s, %f}\n", C.integer, C.decimal, C.letter, C.string, C.bigDecimal);
 
	return 0;
}

Przykład z SO[12]

Za pomocą wskaźników (parametrów)

edytuj

Gdy wywołujemy funkcję, wartość argumentów, z którymi ją wywołujemy, jest kopiowana do funkcji. Kopiowana - to znaczy, że nie możemy normalnie zmienić wartości zewnętrznych dla funkcji zmiennych. Formalnie mówi się, że w C argumentyprzekazywane przez wartość, czyli wewnątrz funkcji operujemy tylko na ich kopiach.

Możliwe jest modyfikowanie zmiennych przekazywanych do funkcji jako parametry - ale do tego w C potrzebne są wskaźniki.

Przykłady
edytuj

Funkcja swap wczytuje 2 wartości i zamienia je miejscami, korzystając ze wskaźników

#include <stdio.h>
// gcc s.c -Wall
// ./a.out
void swap (int *a, int *b)  {
    int temp = *a;
    *a = *b;
    *b = temp;
}


int main()
{
int x=3, y=4;

printf("x=%d ; y= %d\n", x,y); 
swap(&x, &y);
printf("x=%d ; y= %d\n", x,y); 


 return 0;

}

Wynik programu :


 x=3 ; y= 4
 x=4 ; y= 3

Jak widzimy funkcja zmienia wartości swoich argumentów. Porównaj: stałe argumenty.


Tutaj funkcja ma 2 argumenty

  • pierwszy jest stały = funkcja nie zmienia jego wartosci
  • drugi jest wskaźnikiem. Funkcja zmienia jego wartości
#include <stdio.h>
/ gcc s.c -Wall 
// ./a.out
void f (const double a, double *b)  {
    
  *b = - (*b)  * 2.0;  // zmieniamy wartość b korzystająć tylko z b
  *b += a;  // zmieniamy wartość b korzystająć ze zmiennych  a i b; zmienna a się nie zmienia 
    
}


int main()
{
  const double a = 0.2;
  double b= 1.0;

  printf("a = %f \t b =%+f\n", a, b); 
  f(a, &b);
  printf("a = %f \t b =%+f\n", a, b); 

  return 0;
}


Wynik:

 gcc s.c -Wall -lm
 ./a.out
 
 a = 0.200000 	 b =+1.000000
 a = 0.200000 	 b =-1.800000
Tablice jako parametr funkcji
edytuj

Istnieją 3 sposoby :

  • wskaźnik
  • tablica z podaną wielkością
  • tablica bez podanej wielkości


// https://www.tutorialspoint.com/cprogramming/c_passing_arrays_to_functions.htm 

void myFunction(int *param) // Formal parameters as a pointer  = 

void myFunction(int param[10]) // Formal parameters as a sized array −

void myFunction(int param[]) //Formal parameters as an unsized array −

Ponieważ nazwa tablicy jest wskaźnikiem do jej pierwszego elementu, to możemy korzystać z tablic w ten sposób:[13]

#include<stdio.h>

void read(int c[],int i)
{
    int j;
    for(j=0;j<i;j++)
        scanf("%d",&c[j]);
    fflush(stdin);
}

void display(int d[],int i)
{
    int j;
    for(j=0;j<i;j++)
        printf("%d ",d[j]);
    printf("\n");
}
   
  
int main()
{
    int a[5];
    printf("Wprowadź 5 elementów listy \n");
    read(a,5);
    printf("Elementami listy są : \n");
    display(a,5);
    return 0;
}

Funkcje nie tylko mają dostęp do tablicy, ale i mogą ją zmieniać. Zobacz również :rozmiar tablicy przekazywanej do funkcji


Przekazywanie wielowymiarowych tablic:

Specjalne funkcje

edytuj

Funkcja main()

edytuj

Do tej pory we wszystkich programach istniała funkcja main(). Po co tak właściwie ona jest? Otóż jest to funkcja, która zostaje wywołana przez fragment kodu inicjującego pracę programu. Kod ten tworzony jest przez kompilator i nie mamy na niego wpływu.

Istotne jest, że każdy program w języku C musi zawierać funkcję main().

Jak napisać dobrą funkcję main ?[17]

Celem funkcji main() jest:[18]

  • zebranie argumentów programu podanych przez użytkownika
  • wykonanie minimalnej walidacji danych wejściowych
  • następnie przekazanie zebranych argumentów do funkcji, które będą ich używać


Prototypy

edytuj

Istnieją dwa możliwe prototypy (nagłówki) omawianej funkcji:

  • int main(void);
  • int main(int argc, char **argv); [19]

Argumenty

edytuj

Argumenty funkcji main:

  • Pierwszy argument argc (ang. argument count )[20] określa liczbę argumentów programu. Jest to liczba nieujemną. Argument programu to ciągów znaków przechowywanych jest w tablicy argv.
  • drugi argument argv (ang. argument vector ) to wskaźnik tablicy zawierającej argumenty programu

Właściwości tablicy argv

  • Pierwszym elementem tablicy czyli argv[0] (o ile istnieje[21]) jest nazwa programu[22] czy komenda, którą program został uruchomiony.
  • Pozostałe przechowują argumenty podane przy uruchamianiu programu
  • ostatnią wartością tablicy czyli argv[argc] ma zawsze wartość NULL

Zazwyczaj jeśli program uruchomimy poleceniem:

program argument1 argument2 

to argc będzie równe 3 (2 argumenty + nazwa programu), a argv będzie zawierać napisy program, argument1, argument2 umieszczone w tablicy indeksowanej od 0 do 2.

Weźmy dla przykładu program, który wypisuje to, co otrzymuje w argumentach argc i argv:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  int i;
  for (i = 0; i<argc; ++i) {
    printf("%s\n", argv[i]);
  }
  return EXIT_SUCCESS;
}

Uruchomiony w systemie typu UNIX poleceniem ./test foo bar baz powinien wypisać:

./test
foo
bar
baz

Na razie nie musisz rozumieć powyższych kodów i opisów, gdyż odwołują się do pojęć takich jak tablica oraz wskaźnik, które opisane zostaną w dalszej części podręcznika.

Jeśli program nie wczytuje  żadnych argumentów to : [23]

int main(int argc, char **argv)
{
  (void) argc;
  (void) argv;
  return 0;
}

rekurencja

edytuj

Co ciekawe, funkcja main nie różni się zanadto od innych funkcji i tak jak inne może wołać sama siebie (patrz rekurencja niżej), przykładowo powyższy program można zapisać tak[24]:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv) {
  if (*argv) {
    puts(*argv);
    return main(argc-1, argv+1);
  } else {
    return EXIT_SUCCESS;
  }
}


Wartość

edytuj

Ostatnią rzeczą dotyczącą funkcji main jest zwracana przez nią wartość.[25] Już przy omawianiu pierwszego programu wspomniane zostało, że jedynymi wartościami, które znaczą zawsze to samo we wszystkich implementacjach języka są 0, EXIT_SUCCESS i EXIT_FAILURE[26] zdefiniowane w pliku nagłówkowym stdlib.h. Wartość 0 i EXIT_SUCCESS oznaczają poprawne zakończenie programu (co wcale nie oznacza, że makro EXIT_SUCCESS ma wartość zero), natomiast EXIT_FAILURE zakończenie błędne. Wszystkie inne wartości są zależne od implementacji.


Co się dzieje przed funcją main?

edytuj

Zawsze dołączamy funkcję usage(), którą wywołuje główna funkcja programu main(), gdy nie rozumie czegoś, co przekazałeś z wiersza poleceń[27]

void usage(char *progname, int opt) {
   fprintf(stderr, "failure\n");
   exit(EXIT_FAILURE);
   /* NOTREACHED */
}
if(argc!=2){printf("Usage:...");exit(1);}

Funkcje rekurencyjne

edytuj

Poniżej przekażemy ci parę bardziej zaawansowanych informacji o funkcjach w C, jeśli nie masz ochoty wgłębiać się w szczegóły, możesz spokojnie pominąć tę część i wrócić tu później.

Ję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. Napiszmy sobie zatem naszą funkcję rekurencyjną, która oblicza silnię:

int silnia (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;
}

Musimy być ostrożni przy funkcjach rekurencyjnych, gdyż łatwo za ich pomocą utworzyć funkcję, która będzie sama siebie wywoływała w nieskończoność, a co za tym idzie będzie zawieszała program. Tutaj pierwszymi instrukcjami if ustalamy "warunki stopu", gdzie kończy się wywoływanie funkcji przez samą siebie, a następnie określamy, jak funkcja będzie wywoływać samą siebie (odjęcie jedynki od argumentu, co do którego wiemy, że jest dodatni, gwarantuje, że dojdziemy do warunku stopu w skończonej liczbie kroków).

Warto też zauważyć, że funkcje rekurencyjne czasami mogą być znacznie wolniejsze niż podejście nierekurencyjne (iteracyjne, przy użyciu pętli). Flagowym przykładem może tu być funkcja obliczająca wyrazy ciągu Fibonacciego:

 #include <stdio.h>
 
 unsigned count;
 
 unsigned fib_rec(unsigned n) {
   ++count;
   return n<2 ? n : (fib_rec(n-2) + fib_rec(n-1));
 }
 
 unsigned fib_it (unsigned n) {
   unsigned a = 0, b = 0, c = 1;
   ++count;
   if (!n) return 0;
   while (--n) {
     ++count;
     a = b;
     b = c;
     c = a + b;
   }
   return c;
 }
 
 int main(void) {
   unsigned n, result;
   printf("Ktory element ciagu Fibonacciego obliczyc? ");
   while (scanf("%d", &n)==1) {
     count = 0;
     result = fib_rec(n);
     printf("fib_rec(%3u) = %6u  (wywolan: %5u)\n", n, result, count);
 
     count = 0;
     result = fib_it (n);
     printf("fib_it (%3u) = %6u  (wywolan: %5u)\n", n, result, count);
   }
   return 0;
 }

W tym przypadku funkcja rekurencyjna, choć łatwiejsza w napisaniu, jest bardzo nieefektywna.

Funkcje zagnieżdżone

edytuj

W języku C nie można tworzyć zagnieżdżonych funkcji (funkcji wewnątrz innych funkcji).

Ezoteryka C

edytuj

C ma wiele niuansów, o których wielu programistów nie wie lub łatwo o nich zapomina:

  • jeśli nie podamy typu wartości zwracanej w funkcji, zostanie przyjęty typ int (według najnowszego standardu C99 nie podanie typu wartości jest zwracane jako błąd);
  • jeśli nie podamy żadnych parametrów funkcji: int funkcja() to funkcja będzie używała zmiennej liczby parametrów (inaczej niż w C++, gdzie przyjęte zostanie, że funkcja nie przyjmuje argumentów). Aby wymusić pustą listę argumentów, należy napisać int funkcja(void) (dotyczy to jedynie prototypów czy deklaracji funkcji);
  • jeśli nie użyjemy w funkcji instrukcji return, wartość zwracana będzie przypadkowa (dostaniemy śmieci z pamięci).
  • W języku C nie jest możliwe przekazywanie typu jako argumentu.

Kompilator C++ użyty do kompilacji kodu C najczęściej zaprotestuje i ostrzeże nas, jeśli użyjemy powyższych konstrukcji. Natomiast czysty kompilator C z domyślnymi ustawieniami nie napisze nic i bez mrugnięcia okiem skompiluje taki kod.

Zobacz też

edytuj

Przypisy

  1. Aby nie urażać matematyków sprecyzujmy, że chodzi o relację między zbiorami X i Y (X jest dziedziną, Y jest przeciwdziedziną) takie, że każdy element zbioru X jest w relacji z dokładnie jednym elementem ze zbioru Y.
  2. Bardziej precyzyjnie można powiedzieć, że funkcja może zwrócić tylko jedną wartość typu prostego lub jeden adres do jakiegoś obiektu w pamięci.
  3. how-to-better-name-your-functions-and-variables by Friskovec Miha
  4. Nazwy zmiennych, notacje i konwencje nazewnicze - Mateusz Skalski
  5. Kurs języka C. Autor artykułu: mgr Jerzy Wałaszek
  6. How I program C by Eskil Steenberg
  7. geeksforgeeks: c-function-argument-return-values
  8. Variadic functionsin cppreference
  9. gnu libc manual: Variadic-Functions
  10. variadic w cppreference
  11. Przykład z rosettacode
  12. stackoverflow question : passing-struct-to-function
  13. Pass array to function at java2s
  14. stackoverflow question: correct-way-of-passing-2-dimensional-array-into-a-function
  15. comp.lang.c FAQ list · Question 6.18
  16. 2 wymiarowa tablica jako argument funkcji - java2s.com
  17. how-write-good-c-main-function by By Erik O'Shaughnessy May 27, 2019
  18. opensource article: how-write-good-c-main-function
  19. Czasami można się spotkać z prototypem int main(int argc, char **argv, char **env);, który jest definiowany w standardzie POSIX, ale wykracza już poza standard C.
  20. Command-line Arguments: main( int argc, char *argv[] ) by Douglas Wilhelm Harder from University of Waterloo Canada
  21. Inne standardy mogą wymuszać istnienie tego elementu, jednak jeśli chodzi o standard języka C to nic nie stoi na przeszkodzie, aby argument argc miał wartość zero.
  22. Stackoverflow : Is “argv[0 = name-of-executable” an accepted standard or just a common convention? ]
  23. stackoverflow question: what-does-voidvar-actually-do
  24. Jeżeli ktoś lubi ekstrawagancki kod ciało funkcji main można zapisać jako return *argv ? puts(*argv), main(argc-1, argv+1) : EXIT_SUCCESS;, ale nie radzimy stosować tak skomplikowanych i, bądź co bądź, mało czytelnych konstrukcji.
  25. quora: If-we-use-void-main-why-does-the-function-give-same-output-like-int-main
  26. Uwaga! Makra EXIT_SUCCESS i EXIT_FAILURE te służą tylko i wyłącznie jako wartości do zwracania przez funkcję main(). Nigdzie indziej nie mają one zastosowania.
  27. opensource article: how-write-good-c-main-function
  28. Kompilator GCC do wersji 4.3 nie obsługuje tychże rozszerzeń