Obsah lekce:
Procedura je samostatná část programu řešící určitý úkol (viz termín procedurální programování). Procedury se použíají zejména v těchto případech:
V jazyce C, narozdíl od programovacího jazyku Pascal, procedury neexistují. Existují zde pouzr funkce. Pokud bychom chtěli dosáhnout stejného efektu jako u procedur, je možné použít funkci bez návratové hodnoty. V dalším textu se proto budeme držet pojmu funkce bez návratové hodnoty.
Zápis procedury se skládá z hlavičky (klíčové slovo void, název funkce a případně v závorce skupina parametrů procedury). Následují složené závorky, ve kterých lze zapsat jednotlivé příkazy funkce, popřípadě zde lze definovat lokální proměnné (ty které budou viditelné pouze uvnitř funkce).
void název (parametry funkce); //parametry jsou volitelné
{
programový kód funkce
}
Uvažujme program z předchozí lekce o dvojrozměrném poli, který uměl načíst zvolený počet čísel a vypsat je.
#include "stdafx.h"
#define N 5000
int _tmain(int argc, _TCHAR* argv[])
{
int i, pocet, p[N];
printf("Zadej pocet vkladanych cisel (1- %d)\n",n);
scanf("%d", &pocet);
while ((pocet<1) || (pocet>n))
{
printf("Zadej znovu:");
scanf("%d", &pocet);
}
for (i=0; i < pocet; i++)
{
scanf("%d", &p[i]);
}
for (i=0; i < pocet; i++)
{
printf("%d\n", p[i]);
}
fflush(stdin);
getchar();
return 0;
}
Tento program lze funkčně rozdělit na následující posloupnost kroků:
Program je sice poměrně krátký, ale když jej uvidíme poprvé, pak nám bude chvíli trvat než pochopíme, k čemu program slouží a odhalíme princip a základní kroky v jeho funkcionalitě. Toto je z praktického hlediska problém zejména v případě, že vzniká větší program v týmu programátorů. Ti by měli psát program tak, aby mu pokud možno rozumněl v krátké době kdokoli ze zúčastněných. Pojďme tedy program rozložit na jednotlivé kroky.
Rozložení na kroky bude znamenat přepis (přeskládání) jednotlivých částí programu do funkcí:
void zadanipoctucisel()
{
printf("Zadej pocet vkladanych cisel (1-%d)",n);
scanf("%d", &pocet);
while ((pocet<1) || (pocet>n))
{
printf("Zadej znovu:");
scanf("%d", &pocet);
}
}
void vlozenicisel()
{
for (i=0; i < pocet; i++)
{
scanf("%d", &p[i]);
}
}
void vypiscisel()
{
for (i=0; i < pocet; i++)
{
printf("%d\n", p[i]);
}
}
Tímto rozkladem na dílčí kroky jsme získali tři jednoduché funkce (programy), ze kterých je teď možno složit celý přehledný program. Funkce přesuneme nad funkci main hlavního programu a v hlavním funkci místo daného kódu funkce jen napíšeme na požadovaném místě její jméno, což způsobí vykonání všech instrukcí obsažených ve funkci stejně jako kdyby byly napsány v místě volání procedury v hlavním programu.
Protože však budeme používat proměnné ve všech funkcích, nebudeme je definovat lokálně ve funkci main, ale globálně před definicí jednotlivých funkcí.
#include "stdafx.h"
#define N 5000
int i, pocet, p[N];
void zadanipoctucisel()
{
printf("Zadej pocet vkladanych cisel (1-%d)",n);
scanf("%d", &pocet);
while ((pocet<1) || (pocet>n))
{
printf("Zadej znovu:");
scanf("%d", &pocet);
}
}
void vlozenicisel()
{
for (i=0; i < pocet; i++)
{
scanf("%d", &p[i]);
}
}
void vypiscisel()
{
for (i=0; i < pocet; i++)
{
printf("%d\n", p[i]);
}
}
int _tmain(int argc, _TCHAR* argv[])
{
zadanipoctucisel();
vlozenicisel();
vypiscisel();
fflush(stdin);
getchar();
return 0;
}
Vidíme, že program obsahuje hlavičku s globálně definovanými proměnnými, pak deklarace jednotlivých funkcí a nakonec hlavní funkce, která podle potřeby funkce spouští. Hlavní program samozřejmě může obsahovat libovolné další příkazy (nejen volání funkcí).
Výsledkem našeho úsilí je program, v jehož těle jsou pouze tři pokyny: zadanipoctucisel, vlozenicisel, vypiscisel. Je tak velmi rychle jasné, co se v celém programu děje a v případě modifikace některého z těchto kroků již dojde k modifikaci pouze patřičné funkce. Na tomto příkladu jsme si ukázali první ze tří situací, kdy se nejčastěji vyplatí použít funkce: chceme-li program zpřehlednit rozložením jeho celého kódu na části (funkce)).
Pojďme si dále ilustrovat druhou možnost využití funkcí (opakuje-li se v programu jeden složitější příkaz či posloupnost příkazů). Uvažujme jednoduchý program pro součet dvou uživatelem zadaných čísel (místo jednoduchého součtu by funkce mohla realizovat daleko složitější úlohu jako například výpočet nějakého složitého vzorce).
#include "stdafx.h"
int a, b;
int _tmain(int argc, _TCHAR* argv[])
{
printf("Zadejte prosim cislo a: ");
scanf("%d", &a);
printf("Zadejte prosim cislo b: ");
scanf("%d", &b);
printf("Součet čísel %d a %d je %d.\n", a, b ,a+b);
fflush(stdin);
getchar();
return 0;
}
Pokuste se tento program popsat slovně v krocích a následně jej rozložte do funkcí jako v předchozím případě.
Tento program lze funkčně rozdělit na následující posloupnost kroků (varianta A):
případně varianta B:
Ukažme si další postup na obou variantách, které porovnáme.
void zadanicisel()
{
printf("Zadejte prosim cislo a: ");
scanf("%d", &a);
printf("Zadejte prosim cislo b: ");
scanf("%d", &b);
}
void vypocetsouctucisel()
{
printf("Součet čísel %d a %d je %d\n", a, b, a+b);
}
void zadanicislaa()
{
printf("Zadejte prosim cislo a: ");
scanf("%d", &a);
}
void zadanicislab()
{
printf("Zadejte prosim cislo b: ");
scanf("%d", &b);
}
void vypocetsouctucisel()
{
writeln("Součet čísel %d a %d je %d\n", a, b, a+b);
};
Která z variant se vám zdá vhodnější? Proč?
Ať už jsme se rozhodli pro řešení A nebo B, tak se nám v programu vyskytuje kód, který se opakuje. To není vhodné, neboť v případě potřeby upravit v programu proces zadávání čísel je nutno vše opravovat 2x (případně i vícekrát - záleží na tom, kolikrát jsme kód pro zadávání opakovali). Snadno se tak stane, že program upravíme jen v některých částech a tak se program stává přinejmenším nejednotným a proces úpravy zdlouhavým (musíme jej celý procházet a hledat všechna místa nebo obdobné funkce, kde vyžadujeme úpravu). Pokud bychom například chtěli místo "Zadejte prosim cislo b:", aby program napsal jen "Zadejte pozadovane cislo:", pak musíme tuto skutečnost opravit na dvou řádcích funkce ve variantě A nebo v obou procedurách varianty B. V každém případě tak stejnou věc řešíme na dvou (případně více) místech.
Pokuste se upravit předchozí program (libovolnou ze dvou variant) tak, že program načte dvě různé dvojice čísel (řekněme dvojici a,b a dvojici c,d). Následně program spočítá součty obou dvojic (spočte tedy číslo a+b a číslo c+d).
Výsledný program může vypadat třeba nějak takto:
#include "stdafx.h"
int a, b, c, d;
void zadanicisel()
{
printf("Zadejte prosim cislo a: ");
scanf("%d", &a);
printf("Zadejte prosim cislo b: ");
scanf("%d", &b);
printf("Zadejte prosim cislo c: ");
scanf("%d", &c);
printf("Zadejte prosim cislo d: ");
scanf("%d", &d);
}
void vypocetsouctucisel()
{
printf("Součet čísel %d a %d je %d.\n",a, b, a+b);
printf("Součet čísel %d a %d je %d.\n",c, d, c+d);
};
int _tmain(int argc, _TCHAR* argv[])
{
zadanicisel()
vypocetsouctucisel()
fflush(stdin);
getchar();
return 0;
}
#include "stdafx.h"
int a, b, c, d;
void zadaniciselab()
{
printf("Zadejte prosim cislo a: ");
scanf("%d", &a);
printf("Zadejte prosim cislo b: ");
scanf("%d", &b);
}
void zadaniciselcd()
{
printf("Zadejte prosim cislo c: ");
scanf("%d", &c);
printf("Zadejte prosim cislo d: ");
scanf("%d", &d);
}
void vypocetsouctucisel()
{
printf("Součet čísel %d a %d je %d.\n",a, b, a+b);
printf("Součet čísel %d a %d je %d.\n",c, d, c+d);
}
int _tmain(int argc, _TCHAR* argv[])
{
zadanicisel()
vypocetsouctucisel()
fflush(stdin);
getchar();
return 0;
}
Podívejme se na uvedené varianty řešení:
Ve variantě A dochází k tomu, že ve funkci zadanicisel se nám 4x opakuje identický pokyn pro zadání čísla (ignorujeme-li označení čísel a,b,c,d což je obecně přípustné) a zároveň se ve funkci vypocetsouctucisel opakuje dvakrát stejný výpočet (pokaždé pouze s jinou dvojicí čísel).
Ve variantě B dochází k obdobným problémům jako v předchozím, jen je první problé rozdělen do dvou v podstatě identických funkcí zadaniciselab a zadaniciselcd.
Program se nám po přepsání do funkcí sice zpřehlednil, ale nahromadily se nám duplicitní posloupnosti příkazů. A představme si program, který by například uměl sečíst naprosto libovolnou dvojici ze zadaných čísel (ab, cd, ca, bd... - celkem 8 možných dvojic). Případně bychom měli například 10 proměnných cislo1,..,cislo10 případně bychom chtěli funkci, která sečte (nebo provede libovolný jiný výpočet) s libovolnými n čísli v poli (které má například 100 prvků). Program by se naustále prodlužoval o všechny možnosti kombinací a postupně by se stal velmi dlouhým, nepřehledným a postupně v podstatě nerealizovatelným (viz požadavek na součet libovolné dvojice čísel v poli o např. 15000 prvcích). Řešení těchto problémů představují tzv. parametry a návratové hodnoty. Parametry budou sloužit pro zadání vstupních hodnot funkce, návratové hodnoty pro předání výsledku nadřazené funkci.
Parametr je proměnná, kterou posíláme do funkce a ta ji použije pro výpočet. Ukažme si příklad:
#include "stdafx.h"
int a, b, c, d;
int zadanicisla();
{
int cislo;
printf("Zadejte prosim cislo: ");
scanf("%d", &cislo);
return cislo;
};
void vypocetsouctudvoucisel(int cislo1, int cislo2);
{
printf("Součet čísel je %d.\n",(cislo1+cislo2));
};
int _tmain(int argc, _TCHAR* argv[])
{
a = zadanicisla();
b = zadanicisla();
vypocetsouctucisel(a,b);
fflush(stdin);
getchar();
return 0;
}
V hlavičce funkce zadanicisla byl vyměněn typ návratové hodnoty void za int. Tato změna způsobí, že funkce bude vracet celočíselný výsledek. Dále byla vytvořena lokální proměnná cislo, do které bude načtena hodnota a pomocí return cislo; bude předána do cílové proměnné v nadřazené funkci.
Funkce vypocetsouctucisel nebude vracet žádné vstupní hodnoty, bude sloužit pouze pro výpis hodnot, které ji předáme jako vstupní parametry. Vstupní parametry převáváme tak, že v závorce uvedeme datový typ mezera jméno parametru. Pokud je vstupních parametrů více, oddělíme je čárkou.
#include "stdio.h"
int a,b,c,d;
{
int cislo;
printf("Zadejte prosim cislo: ");
scanf("%d", &cislo);
return cislo;
};
void vypocetsouctudvoucisel(int cislo1, int cislo2);
{
printf("Součet čísel je %d.\n",(cislo1+cislo2));
};
int _tmain(int argc, _TCHAR* argv[])
{
a = zadanicisla();
b = zadanicisla();
vypocetsouctucisel(a,b);
c = zadanicisla();
vypocetsouctucisel(a,c);
vypocetsouctucisel(c,b);
fflush(stdin);
getchar();
return 0;
}
Pokuste se přepsat program hry piškvorky z minulé lekce do funkcí (účelem je rozdělení programu na jednotlivé části - neberte zde v úvahu parametry).
Napište program, který načte od uživatele 5 čísel do pole p1 a do pole p2 . Následně obě pole vypíše a pole p1 setřídí a vypíše znovu po setřízení.