PB071: Úvod do jazyka C - 5

Funkce, preprocesor 2

Funkce

Deklarace funkcí

2 typy deklarace:

Prototypy

Jsou nutné, pokud je funkce použita dříve (nebo v jiném zdrojovém souboru), než je definována. Mohou být deklarovány i tehdy, kdy nejsou nutné. Často se (spolu s jinými deklaracemi) seskupují do uživatelských hlavičkových souborů.

typ název(typ parametr, typ parametr ...); (Názvy parametrů zde mohou chybět, typy ne.)

Příklady:

Prototyp: Parametry: Vrací:
void f1(int); 1. int Nic
int f2(char *, double); 1. řetězec
2. double
int
void *f3(int); 1. int Ukazatel na nespecifikovaný typ
int (*f4())[10]; žádné Ukazatel na pole 10 veličin int
long (*f5(char))(); 1. char Ukazatel na funkci typu long bez parametrů

Chybějící seznam parametrů znamená, že typ parametrů není zadán (rozdíl proti C++)
Chybějící návratový typ ve starších verzích Céčka znamená, že funkce vrací hodnotu int (v C99 stejně jako v C++ však chybějící návratový typ znamená, že funkce nevrací nic, tedy formálně vrací void)

Definice

V klasickém C (Kernighan-Ritchie) byla syntax jiná:

typ název(parametr, parametr ...)
typ parametr; typ parametr; ...
{ /* tělo funkce */ }

V hlavičce se tedy pouze vyjmenovaly parametry a jejich typ se uvedl mezi hlavičkou a tělem funkce. Tato forma je stále přípustná (i v C99, ale ne v C++), ale nedoporučuje se. Překladač potom nekontroluje souhlas skutečných parametrů (s nimiž je funkce volána) a formálních parametrů (s nimiž je definována).

Pole jako parametr

Je-li parametrem pole, lze vynechat číslo v []. V C99 je rovněž možno zapsat jako délku pole hvězdičku [*], ale pouze v prototypu (starší verze překladače gcc však hlásily varování). Častěji se však parametr deklaruje jako ukazatel (i ten lze normálně indexovat.). Ve většině případů má název pole podobnou platnost jako ukazatel na jeho počáteční prvek (viz předchozí přednášku). Formálnímu parametru typu char[] nebo *char může proto jako skutečný parametr odpovídat i textová konstanta.

POZOR! Rozdíl je v operátoru sizeof. sizeof pole vrací délku pole, zatímco sizeof ukazatel délku samotného ukazatele.

To ovšem platí jen pro skutečné pole, ne pro formální parametr typu pole předaný funkci. Takový parametr se do funkce předává vždy jako ukazatel, ne jako pole a jeho sizeof udává délku ukazatele.

Příklad:

Lze proto psát:

double A[N];
int
pocet=sizeof A/sizeof(double);
double suma=0;
for(i=0;i<pocet;i++)suma+=A[i];

(A je skutečné pole, ne parametr)

Nemůžeme však napsat:

double prumer(double A[])
{ int i,pocet=sizeof A/sizeof(double);
  double suma=0;
  for(i=0;i<pocet;i++)suma+=A[i];
  return suma/pocet;
}

(A je zde formální parametr a jeho délka je délkou ukazatele - ve většině implementací bude mít délku 4 byty. Nepomůže ani zapsat v hlavičce do hranatých závorek délku pole, pořád půjde o ukazatel.)

Pokud bychom chtěli funkci prumer() skutečně naprogramovat, museli bychom počet prvků předat jako parametr. Funkce by pak měla hlavičku např.

double prumer(int pocet, double *A)

Rekurzivní funkce

Funkce může volat sama sebe (obvykle s jinými parametry). Typickým učebnicovým příkladem je rekurzivní verze funkce pro výpočet faktoriálu:

long int fakt(int n)
{return n==0?1:n*fakt(n-1);}

Každé volání funkce znamená nezanedbatelný čas (manipulace se zásobníkem) a má jisté paměťové nároky (všechny lokální nestatické proměnné se pro každé volání deklarují znovu), proto je takový způsob výpočtu faktoriálu nevhodný a mnohem lepší je nerekurzivní způsob použitý v příkladu pro 1. cvičení. Rekurzivní funkce se však u mnoha složitějších úloh vyplatí.

Funkce se mohou volat i vzájemně rekurzivně, např. funkce A volá funkci B a ta zase funkci A. V tom případě jedna z funkcí musí být volána dříve, než je definována a je nutno předem deklarovat její prototyp.

Funkce s proměnným počtem parametrů

Parametry na konci seznamu lze nahradit výpustkou, např.

int funkce(int param,...)

Může být potom volána s 1 nebo více parametry. Výslovně uvedené parametry používá normálně, s dalšími pracuje pomocí maker va_start, va_arg a va_end definovanými v hlavičkovém souboru stdarg.h

Použití těchto maker vyžaduje deklarovat pro každou takovou funkci proměnnou speciálního typu va_list a její název uvést jako první parametr při každém volání jednoho z následujících maker: inicializačního makra va_start (jeho druhým parametrem je poslední explicitně uvedený parametr. Po provedení této inicializace je možno postupným voláním makra va_arg získávat jednotlivé výslovně neuvedené parametry (různých typů). Celá práce se zakončí makrem va_end

Jazyk C nemá prostředky, jak zjistit počet parametrů, s nimiž byla funkce vyvolána. Ten musí být proti možné zjistit z jiných údajů (například z některého z explicitně uvedených parametrů; třeba u standardních funkcí printf a scanf ho lze odvodit z obsahu formátovacího řetězce.)

Příklad:

#include <stdarg.h>
void varfce(int pocet,...)
{ va_list arg;
  va_start(arg,pocet);
  printf("%d dynamicke parametry jsou: %d, %s\n",
         pocet,va_arg(arg,int),va_arg(arg,char*));
  va_end(arg);
  return;
}
int main(void)
{ varfce(2,123,"konec");
  return 0;
}

V C99 lze používat i makra s proměnným počtem parametrů.

Předávání parametrů

Každému formálnímu parametru funkce odpovídá při jejím volání skutečný parametr. Funkce v C nemůže měnit hodnoty svých parametrů (neexistuje možnost volat parametr jménem nebo referencí - odkazem jako v jiných jazycích). To však lze obejít druhým ze způsobů předávání parametrů:

  1. volání hodnotou - parametr se nemůže změnit (resp. změní se jen jeho lokální kopie ve funkci)
  2. volání hodnotou ukazatele - parametrem je ukazatel, který se nemůže změnit, ale hodnota, na kterou ukazuje, ano

Příklad: Vyměň hodnoty proměnných i a j

Deklarace:

void vymen(int *p, int *q)
{ int pom=*p; *p=*q; *q=pom;
}

Volání:

vymen(&i, &j);

Poznámka: Výměnu hodnot lze naprogramovat i úsporněji bez pomocné proměnné - viz konec 2. přednášky.

Pořadí vyhodnocování parametrů

Není definováno!!!

printf("%d %d %d\n",i++,j++,i+j);

Norma nepředepisuje, co se vytiskne jako poslední hodnota.

Vložené (inline) funkce (jen C99)

Zapíše-li se před funkcí klíčové slovo inline, překládá se funkce tak, aby její vyvolání bylo co nejrychlejší. Prakticky překladač obvykle funkci zpracuje podobně jako makro, tj. na místo, kde je volána, zkompiluje tělo funkce s patřičnými úpravami podle skutečných parametrů. Tím se obejde relativně pomalý mechanismus volání funkce (za cenu většího přeloženého programu). Norma však striktně nepředepisuje, jak se má překladač ke vloženým funkcím chovat.

Obyčejná funkce, inline funkce nebo makro?

Každé řešení má své výhody a nevýhody:

  Obyčejná funkce: Inline funkce: Makro:
Rozsah kódu: Menší (je v programu jen 1x) Obvykle jako u makra Větší (pro každé volání se kód generuje znovu)
Časová náročnost: Větší (režie kolem volání, manipulace s parametry a návratem zpět) Menší (norma vyžaduje rychlé provádění) Menší (bez dodatečné režie)
Další vlastnosti: Parametry mají vždy určený typ. Pro různé typy musíme mít různé funkce (s různými názvy) Rozumný kompromis mezi makry a funkcemi.
Překladač může přeložit i jako obyčejnou funkci.
Nezávislost na typu parametrů
Může se chovat nečekaně

Název funkce (jen C99)

V C99 je v každé funkci deklarován identifikátor __func__ (dvě podtržítka na začátku i na konci), jehož hodnotou je název příslušné funkce jakožto textový řetězec.

Funkce main()

Této funkci se předá řízení při spuštění programu.

int main(int argc, char *argv[])

argc ... počet parametrů při volání z příkazového řádku zvýšený o 1
argv ... pole ukazatelů na jednotlivé parametry z příkazového řádku (pole ukazatelů na  char, čili textových řetězců). Parametr bývá deklarován též jako char **argv

Vždy aspoň 1 parametr argv[0] ... název volaného programu. V mnoha operačních systémech je možno témuž souboru dát různé názvy; podle hodnoty argv[0] se potom program může chovat různě v závislosti na tom, pod kterým názvem je vyvolán! Jsou-li při volání programu uvedeny parametry, jsou dostupné jako argv[1], argv[2] atd. Poslední z nich argv[argc] obsahuje nulový ukazatel NULL.

Například voláme-li náš program prog příkazem
prog 1 leden
bude:

argc 3
argv[0] "prog"
argv[1] "1" (textový řetězec, ne int!)
argv[2] "leden"
argv[3] NULL

U mnoha překladačů může main mít (nad rámec normy) ještě 3. parametr char *env[] ... tzv. proměnné prostředí (proměnné shellu). Na každou proměnnou je jeden řetězec ve tvaru název=hodnota, např. LINES=24 nebo USER=xnovak99, za posledním opět následuje ukazatel NULL.

Příklad:

Jednoduchá analogie unixovského příkazu echo (vypsání parametrů, s nimiž je program vyvolán):

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

I funkce main() vrací hodnotu (obvykle do shellu, kde ji lze testovat). V unixu je návratová hodnota posledního volaného programu uložena v proměnné shellu $?, takže ji lze vypsat příkazem echo $?
Pozor však na to, že velká většina unixovských příkazů jsou také programy, takže pokud mezi váš program a příkaz echo vsunete jiný příkaz, vypíše se návratová hodnota tohoto příkazu.

Návratová hodnota je vždy typu int (nikoli void!) Je zvykem vracet nulu, pokud zpracování prošlo normálně a (malé) nenulové číslo, pokud program narazil na problémy. Hodnotou tohoto čísla je vhodné rozlišit typ chyby, kterou program zjistil. U mnoha operačních systémů ji však systém zkracuje, a tak na unixových počítačích většinou lze bez nebezpečí zkreslení vracet jen nezáporné hodnoty od 0 do 255

I když norma ANSI ani C99 přímo nezakazuje, aby funkce main byla volána rekurzivně, není to dobrá programátorská praktika (C++ to výslovně zakazuje)

Preprocesor - 2. část

Direktivy (příkazy preprocesoru) a hlavičkové soubory

Bylo probráno z větší části v předchozí přednášce

Další direktivy

#error zpráva
Ukončí překlad po výpisu zadané zprávy, případně dalších informací.

Podobně v době provádění  programu lze použít makro assert definované v hlavičkovém souboru assert.h:

assert(výraz) ukončí program, pokud zadaný výraz má hodnotu 0 (false). Předtím vypíše zprávu obsahující název zdrojového souboru a odpovídající číslo řádku (v C99 také jméno funkce, z níž je makro voláno).

Pokud je však v místě, kde je zapsána direktiva #include <assert.h>, definováno makro NDEBUG, je makro assert definováno jinak (tak, že je neúčinné). Proto se assert používá zejména při ladění, dodefinováním makra NDEBUG se platnost každého výskytu makra assert prostě zruší.

Standardní knihovna

V ANSI C je realizována 15 hlavičkovými soubory, v C99 je jich 24. Hlavičkové soubory obsahují deklarace proměnných a konstant, prototypů funkcí, maker apod. Výběr z hlavičkových souborů známých pouze v C99 je uveden kurzívou.
V pořadí přibližně podle důležitosti:

<stdio.h> Vstupy a výstupy (probráno v samostatné přednášce)
<math.h> Matematické funkce a konstanty
<string.h>, <ctype.h>   Manipulace s textovými řetězci a znaky (probráno v jiné přednášce)
<stdlib.h> Konverze, náhodná čísla, hospodaření s pamětí, vyhledávání a třídění aj.
<stdbool.h> Práce s booleovskými proměnnými a výrazy (jen C99)
<stdcomplex.h> Práce s komplexními čísly (jen C99)
<limits.h>,<float.h> Konstanty popisující mezní hodnoty celých čísel a přesnost reálných čísel
<errno.h> Zpracování chyb
<time.h> Manipulace s časem a datem
<stdarg.h> Manipulace s parametry, jejichž počet není znám v době překladu
<assert.h> Makro assert usnadňující kontrolu chybových stavů
<signal.h> Komunikace mezi procesy
<stddef.h> Deklarace některých dalších typů a maker
<locale.h> Specifické vlastnosti národního prostředí
<setjmp.h> Skoky mezi moduly obcházející mechanismus volání funkcí

Vybrané důležité funkce:

Knihovna math.h

(Není-li uvedeno jinak, mají 1 parametr double a vracejí double, úhly se měří v radiánech). V C99 je většina těchto funkcí navíc doplněna analogickými s názvem doplněným f (parametr i výsledek float) nebo l (long double), např. kromě sin je sinf a sinl

Na rozdíl od ostatních hlavičkových souborů se funkce z matematické knihovny nelinkují k programu automaticky. Proto je při použití kterékoli funkce z tohoto hlavičkového souboru nutno na konec příkazu pro vyvolání překladače na konec příkazu volajícího překladač přidat parametr -lm

Trigonometrické, cyklometrické a hyperbolické funkce:

sin, cos, tan, asin, acos, atan, atan2(y,x), sinh, cosh, tanh

atan2 počítá arkustangens y/x, podle znamének určí správně kvadrant (výsledek je v intervalu [-pi,pi])

Exponenciální a logaritmické funkce:

exp, log, log10, pow(x,y), sqrt

log počítá přirozený logaritmus, log10 dekadický, pow počítá xy

Další funkce:

floor, ceil (zaokrouhlení na celé číslo dolů, resp. nahoru, výsledek je však double!)
fabs (absolutní hodnota double; analogie pro celá čísla je v knihovně stdlib.h)
fmod(x,y) (zbytek po dělení x/y, avšak parametry i výsledek jsou double!)

V C99 je sortiment funkcí bohatší, např erf (Gaussova chybová funkce), lgamma (logaritmus gama funkce)

Na mnoha platformách obsahuje tato knihovna rovněž další funkce (např. Besselovy) a některé matematické konstanty (M_PI, M_E), ale norma ANSI ani ISO to nepředepisuje.

Knihovna stdlib.h

Je popsána jen stručně pro úplnost, protože zatím nejsou probrány jazykové prostředky, které umožní ji plně používat.

Konverze:

atoi, atol, atof, v C99 také atoll (převede textový řetězec obsahující zápis čísla na int, long int, double, long long int)

Náhodná čísla:

rand (vrátí pseudonáhodné celé číslo v intervalu od 0 do RAND_MAX, hodnota RAND_MAX je definována jako makro rovněž v stdlib.h
srand (inicializuje generátor pseudonáhodných čísel podle hodnoty předaného parametru. Různým hodnotám tohoto parametru odpovídají různé posloupnosti čísel generovaných funkcí rand; je proto záhodno jako parametr srand zadat celé číslo, které bude při každém spuštění programu jiné - např. okamžitý čas nebo pod Unixem PID procesu, v němž program běží)

Absolutní hodnota:

abs, labs (absolutní hodnota int, resp. long int; pro double je určena funkce fabs v knihovně math.h)

Ukončení programu:

exit(číslo) ukončí běh programu. Používá se zejména, je-li třeba program zastavit uvnitř nějaké funkce bez návratu do funkce main. Zadané číslo se vrátí jako návratový kód programu do místa, odkud byl program spuštěn (obvykle tedy do shellu operačního systému). Tato funkce se používá zejména, zjistí-li program nějakou chybu uvnitř funkce. Pro normální ukončení programu se používá příkaz return číslo; provedený ve funkci main.

POZOR! exit() je funkce, proto se číslo musí zapsat do závorek. return je příkaz, číslo v závorce být nemusí. V obou případech může na místě čísla být uvedena proměnná nebo výraz celočíselného typu int

abort() podobně ukončí program okamžitě nouzovým způsobem. Při tomto ukončení není zajištěno spořádané ukončení (mohou zůstat otevřené soubory, obsah výstupního bufferu se nemusí zapsat na výstupní zařízení, norma nedefinuje ani, co program vrátí jako výsledkový kód.)

Dynamická alokace paměti:

Bude probrána v jiné přednášce.

Služby času:

Budou probrány v jiné přednášce.

Knihovny limits.h, float.h

Obsahují makra, která vracejí možný rozsah čísel celočíselných typů, např. INT_MAX, INT_MIN (největší a nejmenší číslo typu int), ULONG_MAX (největší unsigned long int), DBL_MAX (největší double), FLT_EPSILON (nejmenší číslo, které přičteno k 1.0f typu float dá výsledek různý od 1) apod.

Úplný seznam těchto konstant pro aisu najdete v souboru /usr/include/internal/limits_core.h (celočíselné typy a další limity) a /usr/include/float.h (reálné typy).

Knihovny stdbool.h, complex.h (jen C99)

Zjednodušují práci s booleovskými (logickými) veličinami a komplexními čísly.

Definují zjednodušené názvy pro tyto datové typy:

Bez knihovny: S knihovnou: Poznámka:
_Bool bool  
_Complex complex Nutno předeslat typ reálné a imaginární složky (float, double, long double)
_Imaginary imaginary

Navíc knihovna stdbool.h definuje konstanty true a false, knihovna complex.h definuje řadu funkcí pro komplexní parametry (např. sinus nebo logaritmus komplexního čísla).

Další informace

Mnohé funkce ze standardní knihovny jsou (v závislosti na překladači) ve skutečnosti realizovány jako makra. (Některé dokonce existují i jako makro, i jako funkce; použití funkce lze pak vynutit pomocí direktivy #undef.)

Na unixových počítačích je možno získat informace o standardních funkcích příkazem man, např. man log10. Pozor na to, že na některých počítačích (aisa) man zobrazí nejdříve stejnojmenné funkce z manuálové kapitoly 3F (Fortran), a teprve po zadání :n z kapitoly 3M (knihovna math). U některých funkcí je nutno :n opakovat několikrát, dokud se nedostaneme na kapitolu 3C, 3S nebo 3M odpovídající standardním knihovnám C.

V některých implementacích je možno použít jako parametr příkazu man také jméno hlavičkového souboru (bez přípony .h), např. man math, avšak na aise to v současné implementaci funguje jen pro některé hlavičkové soubory. Seznam implementovaných funkcí lze získat prohlédnutím samotného hlavičkového souboru (viz dále). Kromě toho příkaz man pro řadu funkcí zobrazí i funkce příbuzné, např. man sin popisuje současně všechny trigonometrické funkce.

Některé funkce vyžadují, aby kromě #include příslušného hlavičkového souboru ve zdrojovém programu byl při překladu dodán další parametr pro překladač (např. při použití funkce z math.h je nutno překládat s parametrem -lm), i tyto informace bývají uvedeny v popisu funkce poskytnutém příkazem man.

Dost informací o knihovně i o programování hlavičkových souborů lze získat prohlédnutím těchto souborů (vřele doporučuji!) Pod Unixem jsou v adresáři /usr/include čitelné samotné hlavičkové soubory, např. /usr/include/stdlib.h; u mnoha hlavičkových souborů jsou zde však includovány další soubory a vlastní implementace je až v nich.

Podrobnější seznam standardních funkcí

Platí pro C99, uvedeny jen názvy funkcí a maker s parametry bez popisu, seřazeno podle hlavičkových souborů. (soubory však kromě funkcí obsahují i deklarace externích proměnných a maker definujících konstanty; ty zde neuvádím). Ani tento seznam však není úplný, u málo používaných hlavičkových souborů nejsou funkce uvedeny.
Zápis sin[fl] znamená, že existují funkce sin, sinf, sinl, podobně [v][fs]printf znamená printf, fprintf, sprintf, vprintf, vfprintf, vsprintf

assert.h: assert

complex.h: cacos[fl] casin[fl] catan[fl] ccos[fl] csin[fl] ctan[fl] cacosh[fl] casinh[fl] catanh[fl] ccosh[fl] csinh[fl] ctanh[fl] cexp[fl] clog[fl] cabs[fl] cpow[fl] csqrt[fl] carg[fl] cimag[fl] conj[fl] cproj[fl] creal[fl]

ctype.h: isalnum isalpha isblank iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper

errno.h

fenv.h

float.h

inttypes.h

iso646.h

limits.h

locale.h

math.h: fpclassify isfinite isinf isnan isnormal signbit acos[fl] asin[fl] atan[fl] atan2[fl] cos[fl] sin[fl] tan[fl] acosh[fl] asinh[fl] atanh[fl] cosh[fl] sinh[fl] tanh[fl] exp[fl] exp2[fl] expm1[fl] frexp[fl] ilogb[fl] ldexp[fl] log[fl] log10[fl] log1p[fl] log2[fl] logb[fl] modf[fl] scalbn[fl] scalbln[fl] cbrt[fl] fabs[fl] hypot[fl] pow[fl] sqrt[fl] erf[fl] erfc[fl] lgamma[fl] tgamma[fl] ceil[fl] floor[fl] nearbyint[fl] rint[fl] lrint[fl] llrint[fl] round[fl] lround[fl] llround[fl] trunc[fl] double fmod[fl] remainder[fl] remquo[fl] copysign[fl] nan[fl] nextafter[fl] nexttoward[fl] fdim[fl] fmax[fl] fmin[fl] fma[fl] isgreater isgreaterequal isless islessequal islessgreater isunordered

setjmp

signal: signal

stdarg.h: valist va_arg va_copy va_end va_start

stdbool.h

stddef.h

stdint.h

stdio.h: remove rename tmpfile tmpnam fclose fflush fopen freopen setbuf setvbuf [v][fs]printf [v][fs]scanf [f]get[cs] getchar putchar fread fwrite fgetpos fsetpos fseek ftell rewind clearerr feof [fp]error 

stdlib.h: ato[fil] atoll strto[dfl] strtol[dl] strtoul[l] [s]rand calloc malloc realloc free abort exit _Exit getenv system bsearch qsort abs labs llabs div ldiv lldiv mblen mbtowc wctomb mbstowcs wcstombs 

string.h: memcpy memmove str[n]cpy str[n]cat memcmp str[n]cmp strcoll strxfrm memchr str[r]chr str[c]spn strpbrk strtok strerror strlen  

tgmath.h

time.h: clock difftime mktime [c]time asctime gmtime localtime strftime 

wchar.h

wctype.h


Předchozí Předchozí přednáška Další Další přednáška Hlavní stránka Hlavní stránka