[Cvičení 5] | [Obsah] | [Cvičení 7] |
Dokončete úlohy 5.3 a 5.4 z předchozího cvičení.
Napíšeme program, který vypíše na obrazovku adresy paměti, kde jsou umístěny jednotlivé položky jednak statického, jednak dynamického pole.Řešení:
Dev C++: adresy.dev, adresy.c CodeBlocks: adresy.cbp, adresy.c
Definice začíná specifikací návratové hodnoty, pak následuje identifikátor funkce (jméno funkce) se seznamem formálních parametrů v kulatých závorkách. Je-li parametrů více, oddělují se čárkou. Právě zde je první odlišnost mezi verzí K&R a ANSI. Rozdíl demonstrujeme na funkci, která počítá obsah obdélníka:
Definice podle K&R | Definice podle ANSI |
double obsah_obd(a,b) double a,b; { return a*b; } |
double obsah_obd(double a, double b) { return a*b; } |
Definovali jsme funkci obsah_obd
, která vrací hodnotu (číslo) typu
double
a má dva formální parametry a, b
.
Druhý způsob definice formálních parametrů (podle ANSI) je výhodnější, protože umožňuje překladači lépe kontrolovat skutečné parametry při volání funkce
(ve verzi K&R není nutné typy na dalším řádku uvádět, překladač pak předpokládá typ int
).
Návratová (výstupní) hodnota se vrací pomocí příkazu return vyraz;
,
resp. return (vyraz);
.
Uvnitř těla funkce je možné deklarovat lokální proměnné, které jsou platné pouze uvnitř těla. Deklarace se provádí běžným způsobem, např.:
double obsah_obd(double a, double b) { double obsah; obsah = a*b; return obsah; }Lokální proměnné jsou alokovány automaticky při vstupu do funkce (při volání) na zásobníku (označují se také jako proměnné ve třídě
auto
).
Pokud není v definici uveden typ návratové hodnoty nebo typ parametru, překladač předpokládá implicitně typ int
.
Následujícím zápisem definujeme funkci
obsah_obd(double a, double b) { return a*b; }vracející hodnotu typu
int
(nikoliv proceduru, jak by se mohlo na první pohled zdát). Při návratu se provede
automatická konverze výsledku a*b typu double
na typ int
. Funkce vrátí
celou část obsahu obdélníku.
Je vhodné podotknout, že moderní překladače, které mají nastaven překlad podle pravidel jazyka C++, vyžadují vždy uvést typ a ohlásí
v tomto případě chybu. Z toho také plyne, že je nutné uvádět typ parametru u každého z nich, nelze tedy psát
double obsah_obd(double a, b)
.
Volání funkce se provádí zápisem identifikátoru a uvedením skutečných parametrů v kulatých závorkách:
int main(int argc, char *argv[]) { double a=5.6, b=4; double obsah; // volani funkce se skutečnými parametry konstantami 3,4 obsah = obsah_obd(3,4); printf("Obsah obdelniku je %lf.\n",obsah); // volani funkce se skutečnými parametry proměnnými a,b printf("Obsah druheho obdelniku je %lf.",obsah_obd(a,b)); }Při deklaraci funkce je možné u formálního parametru uvést klíčové slovo
const
. Deklarujeme tím, že parametr
je konstatní a není možné uvnitř těla funkce jeho hodnotu modifikovat; při pokusu o změnu překladač ohlásí chybu.
Konstatní parametr má význam zejména při předávání polí, protože zabrání programátorovi např. v nechtěné změně prvků pole.
Důležité je uvedení klíčového slova const
u knihovních funkcí pracujících s řetězci, tj. s poli typu
char
, kde není znám kód funkce, pouze její prototyp. Programátor, který takto deklarovanou funkci ve svém
programu použije, má jistotu, že obsah řetězce (pole) není ve funkci změněn.
Mezi další vlastnosti definic funkcí v jazyce C platí:
void
:
void tisk_cisel(int a, int b) { printf("%d\t%d",a,b); }Příkaz
return
není nutné na konci procedur uvádět, pouze v případě předčasného (nuceného) ukončení.
Nemá-li funkce (procedura) žádný parametr, píše se klíčové slovo void
do závorek místo seznamu formálních parametrů.
Pokud klíčové slovo void
chybí, znamená to v „klasickém“ C, že o parametrech
není nic známo. V C++ se tyto dva případy nerozlišují, neuvedení klíčového slova znamená taktéž, že funkce parametry nemá.
Procedura nemá žádný parametr | O parametrech není nic známo |
void tisk_hlasky(void) { printf("CHYBA"); } |
void tisk_hlasky() { printf("CHYBA"); } |
Při volání funkce (procedury), která nemá parametry, je nutné psát prázdnou dvojici závorek, jinak bude překladač považovat zapsaný identifikátor za indentifikátor proměnné! Volání výše definované procedury musíme provést takto:
int main(void) { tisk_hlasky(); }
Prototypy funkcí (hlavičky)
Prototypem funkce (hlavičkou) rozumíme uvedení návratové hodnoty, identifikátoru a seznamu parametrů bez těla funkce. Prototypy
funkcí jsou např. uvedeny v hlavičkových souborech. Prototyp funkce je třeba uvést, není-li funkce definována před tím, než je poprvé volána.
Překladač musí při volání totiž vědět, jakou má funkce návratovou hodnotu a parametry, aby mohl provést kontrolu.
Někdy definice kódu funkce ve zdrojové podobě není vůbec k dispozici (některé knihovny existují pouze v přeloženém tvaru .obj, .lib).
Uvedení prototypu funkce funkce se také někdy označuje termínem deklarace, na rozdíl od její definice. Uvedení prototypu je ukončeno středníkem, ale při definici se za identifikátorem středník neuvádí!
Definice funkce před místem volání | Definice funkce za místem volání, uvedení prototypu před voláním |
double obsah_obd(double a, double b) { return a*b; } void main(void) { double S; S = obsah_obd(3,4); } |
double obsah_obd(double a, double b); void main(void) { double S; S = obsah_obd(3,4); } double obsah_obd(double a, double b) { return a*b; } |
(Poznámka: Ve verzi K&R deklarace funkce spočívala pouze v uvedení návratové hodnoty a identifikátoru funkce bez uvedení parametrů.)
Na následujícím obrázku je příklad jednoduché procedury tisk
a odpovídající stav zásobníku těsně před voláním procedury,
kdy jsou již na vrcholu zásobníku kopie hodnot skutečných parametrů. Dále je zobrazen stav zásobníku těsně před ukončením volané funkce.
Hodnota parametru a na zásobníku je změněna, ale hodnota skutečného parametru x (proměnná v hlavním programu) se nemění.
Po návratu z procedury jsou parametry ze zásobníku odstraněny.
Zdrojový kód | Zásobník před voláním procedury tisk |
Zásobník před ukončením procedury tisk |
void tisk(int a, int b) { a++; printf("%d\t%d",a,b); } void main(void { int x = 5; tisk(x,3); } |
// obsah obdelniku jako funkce double obsah_obd_fce(double a, double b) { return a*b; } // obsah obdelniku jako procedura s vystupni promennou void obsah_obd_proc(double a, double b, double *obs) { *obs = a*b; } void main(void) { double obsah; // vypocet pomoci funkce obsah = obsah_obd_fce(3,5); // vypocet pomoci procedury s vystupni promennou // musim predat adresu promenne, kam se ulozi vysledek obsah_obd_proc(3,7,&obsah); // nelze napsat obsah = obsah_obd_proc(3,7,&obsah); // protoze procedura nevraci (nevyhazuje) zadnou hodnotu }Někdy může mít výstupní proměnnou i funkce, tj. vracet de-facto dvě hodnoty. Nastavení hodnoty jiné proměnné mimo návratové hodnoty se často označuje termínem vedlejší, resp. stranový efekt funkce (side effect). Může jít např. o nastavení chybové proměnné, viz následující ukázka
double obsah_obd(double a, double b, int *chyba) { if (a<0 || b<0) { *chyba = 1; return 0; } *chyba = 0; return a*b; } int main() { int ch; double o; o = obsah_obd(3,-5,&ch); if (ch==1) printf("Zaporne strany!"); }
(Poznámka: V jazyce C++ byl přidán další způsob předávání parametrů funkcím, a sice pomocí reference, což odpovídá parametrům předávaným odkazem v jazyce Pascal.)
Napište dvě funkce pro převod úhlu zadaného ve stupních na radiány a naopak. Přepište je také jako procedury s výstupními proměnnými.
Funkce a procedury napište nejprve do jednoho souboru se zdrojovým kódem, potom si vyzkoušejte tvorbu vlastní knihovny a hlavičkového souboru (stačí pouze pro funkce).Postup, jak vytvořit v překladači Dev C++ a připojit k projektu vlastní knihovnu a hlavičkový soubor, který bude obsahovat zmíňované funkce, si popíšeme.
Pokud si soubory
- V hlavní nabídce zvolíme Soubor - Nový - Zdrojový kód. Vytvoří se nový soubor Beze jména 1.
- Uložíme si soubor pod názvem
uhly.c
.- V hlavní nabídce opět zvolíme Soubor - Nový - Zdrojový kód.
- Uložíme si soubor pod názvem
uhly.h
(hlavičkový soubor) .- Do hlavičkového souboru
uhly.h
napíšeme prototypy funkcí (nezapomeneme na středník):float deg2rad(float u); float rad2deg(float u);- Implementaci napíšeme do souboru
uhly.c
, resp. zkopírujeme z předchozího programu.- Vytvoříme nový projekt: Soubor - Nový - Projekt a uložíme jej např. pod názvem prevody.
- Na začátek hlavního programu vložíme pomocí direktivy hlavičkový soubor:
#include "uhly.h"
- Do projektu vložíme naši knihovnu: stiskneme pravé tlačítko myši na názvu projektu a zvolíme položku Připojit k projektu.
- Přeložíme knihovnu (Zkompilovat tento soubor). Všimněte si, že v adresáři je přeložená knihovna -
uhly.o
.- Doplníme hlavní program a vyzkoušíme knihovnu.
uhly.c
auhly.h
, resp.uhly.o
uložíme do archivu, můžeme obě funkce použít např. za půl roku v jiném programu, aniž bychom je znovu programovali. V tom je výhoda deklarace procedur a funkcí v knihovnách.Chceme-li někomu poskytnout naši knihovnu a přitom utajit implementaci (zdrojový kód), poskytneme uživateli pouzé soubory
uhly.h
auhly.o
. Pak musíme nastavit parametry překladače tak, aby sestavovací program používal při sestavení knihovnuuhly.o
. V prostředí Dev C++ to provedeme následovně: Zobrazíme vlastnosti projektu (stiskem pravého tlačítka myši na názvu projektu) a zvolíme Vlastnosti projektu. Zobrazíme kartu Parametry a do posledního editačního okénka Linker přidáme pomocí tlačítka Připojit knihovnuuhly.o
Řešení:
Kompletní kód v jediném souboru Dev C++: stupne.dev, stupne.c CodeBlocks: stupne.cbp, stupne.c Funkce pro převod v samostatné knihovně Knihovna: uhly.c, uhly.h Dev C++: stupne2.dev, stupne2.c CodeBlocks: stupne2.cbp, stupne2.c
void razeni(float *pole, int n) { // kód } |
void razeni(float pole[], int n) { // kód } |
Uvedení velikosti pole ve tvaru void razeni(float pole[10])
není možné, překladač hodnotu 10 ignoruje.
float max_prvek(float pole[][3], int n) { // kód // jako skutečný parametr mohu předat // libovolné statické pole, které má první // dimenzi libovolnou a druhou pouze 3 } |
Napište funkci, která zjistí, zda je zadaný prvek v neseřazeném poli. Funkce má tři parametry: pole, počet prvků pole a hledaný prvek. Pokud je prvek nalezen, funkce vrátí jeho index, jinak vrátí hodnotu -1. Dále naprogramujte dvě procedury, které seřadí prvky pole. První procedura seřadí pole vzestupně bublinkovým řazením, druhá procedura seřadí pole sestupně výběrem maximálního prvku.Prezentace s popsanými algoritmy řazení a vyhledávání: algoritmy_vyhledavani_razeni.ppt
Polotovar:Řešení:
Dev C++: hledani_vzor.dev, hledani_vzor.c CodeBlocks: hledani_vzor.cbp, hledani_vzor.c
Dev C++: hledani.dev, hledani.c CodeBlocks: hledani.cbp, hledani.c
[Cvičení 5] | [Obsah] | [Cvičení 7] |