Kurz C++ (5.)


Vφtßm Vßs na dalÜφm pokraΦovßnφ naÜeho kurzu o C++. Dnes si povφme n∞co o funkcφch.

Zßklady

Funkce nßm umo₧≥ujφ napsat k≤d, kter² provßdφ n∞jakou operaci, pouze jednou, a pak se na n∞j odkazovat z jakΘhokoli mφsta v programu (programßtorsky se tomu °φkß "zavolat funkci"). M∙₧eme tak provßd∞t stejnou Φinnost na r∙zn²ch mφstech v programu ani₧ bychom poka₧dΘ museli napsat stejn² k≤d. Funkce takΘ vnßÜejφ do k≤du modularitu a °ßd a d∞lajφ program p°ehledn∞jÜφm.

Funkce m∙₧e zφskat od volajφcφho programu n∞jakΘ informace (°φkß se jim "parametry funkce"), a m∙₧e mu n∞jakou informaci vrßtit (to je "nßvratovß hodnota"). Jak parametry, tak nßvratovß hodnota mohou b²t jakΘhokoli datovΘho typu.

P°ed pou₧itφm funkcφ si musφme ujasnit pojmy deklarace a definice. Deklaracφ °φkßme kompilßtoru, ₧e n∞kde dßle v programu volßme takovou funkci, a dßvßme mu k dispozici p°φsluÜnΘ ·daje o nφ (jmΘno, seznam parametr∙, typ nßvratovΘ hodnoty). P°φklad dvou deklaracφ:

int min(int a, int b);    // poΦφtß minimum dvou Φφsel
void vypisCopyright();    // vypisuje ·daje o autorovi

Na prvnφm mφst∞ je typ nßvratovΘ hodnoty, v naÜem p°φpad∞ funkce min vracφ celΘ Φφslo, funkce vypisCopyright nevracφ v∙bec nic - to je v²znam klφΦovΘho slova void. Dßle nßsleduje jmΘno funkce (min, vypisCopyright) a pak v kulat²ch zßvorkßch seznam parametr∙ (uvßdφ se datov² typ a jmΘno parametru - funkce min mß tedy dva celoΦφselnΘ parametry (a, b), funkce vypisCopyright nemß ₧ßdn² parametr. TakovΘmu °ßdku se °φkß "hlaviΦka funkce".


Poznßmka: V n∞kter²ch programech se m∙₧ete setkat s deklaracφ, kterß vypadß nßsledovn∞:

void vypisCopyright(void);

tedy mφsto prßzdnΘho seznamu parametr∙ je klφΦovΘ slovo void. Je to naprosto totΘ₧ jako p°φklad vypisCopyright v²Üe (funkce bez parametr∙), ale v jazyce C (p°edch∙dci jazyka C++).



VÜimn∞te si, ₧e deklaracφ jeÜt∞ neuvßdφme, co vlastn∞ bude funkce provßd∞t. To kompilßtoru °φkßme a₧ definicφ:
int min(int a, int b) {   // vracφ nejmenÜφ ze dvou Φφsel
    if (a < b)
        return a;         // a < b, vracφme tedy jako v²sledek a
    return b;             // pokud jsme se dostali az sem, 
                          // tak a >= b, vracime tedy b
}
    
void vypisCopyright() {
    cout << "(c) Andrei Badea, 2001\n";
}

Definice tedy zaΦφnß op∞tovn²m uvedenφm hlaviΦky, ale mφsto st°ednφku na konci ji₧ pφÜeme k≤d, kter² se ve funkci vykonßvß, uzav°en do slo₧en²ch zßvorek ("t∞lo" funkce). V p°φkladu jsme pou₧ili dalÜφ klφΦovΘ slovo jazyka C++, a sice p°φkaz return. Tφmto p°φkazem °φkßme: tady skonΦi provßd∞nφ funkce, vra¥ hodnotu a p°edej °φzenφ nad°azenΘmu programu (kter² funkci zavolal). Prob∞hne-li p°φkaz return, dalÜφ p°φkazy, kterΘ za nφm mohou nßsledovat, se neprovedou. Nßvrat do nad°azenΘho programu takΘ probφhß samovoln∞ na konci funkce. Pokud funkce nic nevracφ, return tam ani nemusφ b²t (viz funkci vypisCopyright). V opaΦnΘm p°φpad∞ je pou₧itφ return i s n∞jakou nßvratovou hodnotu povinnΘ (funkce min).

Pou₧φtφ funkce v programu vypadß takto:

#include <iostream.h>
// deklarace
int min(int a, int b);

// definice
void vypisCopyright() {
    cout << "(c) Andrei Badea, 2001\n";
}

// funkce main - to je takΘ definice
void main() {
    int cislo1 = 2, cislo2 = 3;
    int vysledek;

    vypisCopyright();     // volßme funkci
    vysledek = min(cislo1, cislo2);    // volßme funkci - parametry uvedeny v hlaviΦce funkce a 
                                       // ty, kterΘ p°edßvßme, nemusφ mφt stejnΘ jmΘno
    cout << vysledek;
}

// definice
int min(int a, int b) {
    if (a < b) {
        return a;
    return b;
}

Prom∞nnΘ v²sledek jsme p°i°adili nßvratovou hodnotu funkce min. Funkci m∙₧eme pou₧φt na vÜech mφstech, kde kompilßtor oΦekßvß v²raz stejnΘho datovΘho typu, jako je jejφ nßvratovß hodnota, p°iΦem₧ funguje implicitnφ i explicitnφ konverze. Funkci s nßvratov²m typem void m∙₧eme pou₧φvat jako p°φkaz, (v jazyce Pascal existuje pom∞rn∞ v²sti₧n² termφn "procedura") ale nesmφme zapomenout na zßvorky. Vlastn∞ jakoukoli funkci m∙₧eme pou₧φvat jako proceduru, tedy ignorujeme nßvratovou hodnotu. V minulΘm dφle jsme mluvili o funkci strcpy. Ta je deklarovßna takto:

char *strcpy(char *str1, char *str2);

Funkce spojφ oba °et∞zce do °et∞zce str1, kter² pak vracφ. Nßs pravd∞podobn∞ nezajφmß nßvratovß hodnota, proto₧e °et∞zec str1 mßme n∞kde deklarovan², tak₧e ji m∙₧eme ignorovat a zavolat funkci takto:

strcpy(str1, str2);

Dßle si vÜimn∞te, ₧e funkci m∙₧eme deklarovat i definovat kdekoli v programu (ale mimo jinou funkci - deklarovat nap°φklad ve funkci main jinou funkci by neÜlo).


Poznßmka: z tohoto p°φkladu je vid∞t, ₧e veÜker² k≤d programu je uzav°en v n∞jakΘ funkci, mimo ni nem∙₧ete psßt k≤d, pouze deklarovat prom∞nnΘ a konstanty. Tak₧e tvrzenφm "program volß funkci" jsem se dopustil malΘ nep°esnosti (z d∙vodu jednoduchosti v²kladu): ve skuteΦnosti je v₧dy funkce zavolßna jinou funkci (a₧ na funkci main, kterß je zavolßna operaΦnφm systΘmem).

V²Üe uveden² p°φklad na pou₧φvßnφ funkce je mo₧nß trochu zarß₧ejφcφ, proto₧e chybφ deklarace funkce vypisCopyright. Deklarace funkcφ jsou ve skuteΦnosti nepovinnΘ. Pokud funkci definujeme p°ed tφm, ne₧ ji poprvΘ zavolßme, tak deklarace je vlastn∞ zbyteΦnß, proto₧e v okam₧iku zavolßni funkce kompilßtor ji₧ mß vÜechny ·daje o nφ k dispozici. Kdybychom posunuli funkci min p°ed funkci main, mohli bychom vynechat i jejφ deklaraci. NicmΘn∞ existujφ i p°φpady, kdy se bez deklaracφ neobejdeme. Typick²m p°φkladem jsou dv∞ funkce, z nich₧ ka₧dß volß tou druhou:

void a(bool stop) {
    cout << "Ve funkci a\n";
    if (stop == false)
        b();
}

void b() {
    cout << "Ve funkci b\n";
    a(true);
}

void main() {
    a(false);
}

Pokusφte-li se zkompilovat tento program, kompilßtor ohlßsφ chybu ve funkci a na °ßdku b(), proto₧e o funkci b zatφm nevφ v∙bec nic (postupuje shora dol∙).


Poznßmka: pokud Vßm nenφ jasn² v²znam parametru stop, ten tam je pouze kv∙li tomu, aby vzßjemnΘ volßnφ funkcφ a a b v∙bec skonΦilo. Jinak by a stßle volala b, nßsledn∞ b volala a a nikdy by to neskonΦilo (tedy ano: "program zp∙sobil neplatnou operaci a bude ukonΦen", ale to asi nechcete).

Aby nßm p°φklad v²Üe fungoval, musφme ho vylepÜit o deklaraci funkce b p°ed funkcφ a:

void b();

void a(bool stop) {
    cout << "Ve funkci a\n";
    if (stop == false)
        b();
}

void b() {
    cout << "Ve funkci b\n";
    a(true);
}

void main() {
    a(false);
}

TakovΘ p°φpady, kdy budete pot°ebovat deklarace, ovÜem nejsou p°φliÜ ΦastΘ, tak₧e se deklaracemi nemusφte obt∞₧ovat. StaΦφ, kdy₧ budete v∞d∞t, ₧e existujφ.

 

P°edßvßnφ parametr∙

P°edstavte si, ₧e byste pot°ebovali funkci poΦφtajφcφ aritmetick² a geometrick² pr∙m∞r (ob∞ najednou). To by znamenalo, ₧e funkce bude muset vracet dv∞ hodnoty, a p°itom mßme k dispozici pouze jednu nßvratovou hodnotu. Nabφzφ se mo₧nost pou₧φt k vracenφ v²sledk∙ parametry funkce. Deklarace funkce by vypadala takto:

void prumery(double a, double b, double aritm, double geom);

Mß tedy 2 vstupnφ parametry (Φφsla a, b), a dva v²stupnφ (do parametru aritm budeme uklßdat spoΦten² aritmetick² pr∙m∞r, do geom geometrick²):

void prumery(double a, double b, double aritm, double geom) {
    aritm = (a + b) / 2;
    geom = sqrt(a * b);            // sqrt poΦφtß druhou odmocninu
}

Pokusφte-li se funkci pou₧φt, zjistφte, ₧e se vypisujφ n∞jakΘ nesmyslnΘ hodnoty (a takΘ dostanete p°i p°ekladu upozorn∞nφ ₧e pou₧φvßte neinicializovanΘ prom∞nnΘ aritm a geom):

#include <math.h>
#include <iostream.h>

void prumery(double a, double b, double aritm, double geom) {
    aritm = (a + b) / 2;
    geom = sqrt(a * b);            // sqrt poΦφtß druhou odmocninu
}

void main() {
    double a = 2, b = 3;
    double aritm, geom;

    prumery(a, b, aritm, geom);

    cout << aritm << "\n";
    cout << geom << "\n";
}

Je to tφm, ₧e existujφ dva zp∙soby p°edßvßnφ parametr∙: hodnotou a odkazem. P°edßvßnφ odkazem se pou₧φvß standardn∞, a znamenß to, ₧e hodnoty parametr∙ se zkopφrujφ a funkci se p°edajφ prßv∞ kopie. Tφm pßdem jakΘkoli zm∞ny, kterΘ funkce provede, ₧e ve skuteΦnosti provedou na kopiφch, kterΘ se p°i nßvratu zahazujφ. DalÜφ mo₧nostφ je p°edßvßnφ odkazem. V tomto p°φpad∞ se funkci p°edajφ odkazy na parametry, tak₧e se veÜkerΘ zm∞ny do hodnot parametr∙ promφtnou. U parametr∙ aritm a geom pot°ebujeme tedy p°edßvßnφ odkazem, to se udßvß operßtorem & (naz²vß se operßtor reference) u t∞chto dvou parametr∙:

void prumery(double a, double b, double &aritm, double &geom) {
    aritm = (a + b) / 2;
    geom = sqrt(a * b);            // sqrt poΦφtß druhou odmocninu
}

P°et∞₧ovßnφ funkcφ

Funkce min, kterou jsme si napsali, je pom∞rn∞ u₧iteΦnß a mo₧nß se Vßm bude hodit i v dalÜφm programovßnφ. Mß ale jednu nev²hodu: umφ poΦφtat nejmenÜφ hodnotu jen pro celß Φφsla. Chceme-li funkce min i pro datov² typ double, char atd., musφme si je napsat. Ve starΘm C muselo jmΘno funkce b²t unikßtnφ, z Φeho₧ vypl²vß, ₧e nap°. funkce min pro double by se vlastn∞ nemohla jmenovat min, ale t°eba min_double. C++ je vysp∞lejÜφ a tak v n∞m existuje mechanismus, kter² umo₧≥uje deklarovat vφce funkcφ se stejn²m jmΘnem - je to p°et∞₧ovßnφ funkcφ (angl. function overloading), nap°φklad:

int min(int a, int b) {
    if (a < b)
        return a;
    return b;
}

double min(double a, double b) {
    if (a < b)
        return a;
    return b;
}

void main() {
    double dbl_a = 1.0, dbl_b = 1.5;
    int int_a = 1, int_b = 2;

    cout << min(int_a, int_b) << "\n";
    cout << min(dbl_a, dbl_b) << "\n";
}

Spustφte-li tento program, vypφÜe se nejd°φv 1, pak 1.5. Kompilßtor poznal jakou funkci chceme volat podle typ∙ parametr∙. Prav∞ takhle to funguje - dv∞ funkce se stejn²m jmΘnem se musφ liÜit alespo≥ typem jednoho parametru. Nenφ mo₧nΘ p°et∞₧ovat funkce pouze na zßklad∞ rozdφlnΘho typu nßvratovΘ hodnoty. P°edstavte si tento p°φklad:

void min(int a, int b);
int min(int a, int b);

// definici t∞chto funkcφ vynechßvßm, nejsou pro tento p°φklad d∙le₧itΘ

void main() {
    min();
}

Kdy₧ volßte funkci min jako proceduru, kompilßtor by nev∞d∞l, kterou funkci zavolat: tu bez parametru, nebo tu vracejφcφ int.

Inline funkce

Ka₧dΘ volßnφ funkce spot°ebuje n∞jak² Φas procesoru. Nenφ to moc, ale volßte-li funkci Φasto (nap°φklad v n∞jakΘm cyklu) a zßle₧φ-li na ka₧dΘm taktu, m∙₧e to b²t poznat. Prßv∞ u takov²ch funkcφ m∙₧e b²t v²hodnΘ je definovat jako inline. To znamenß, ₧e funkce se nezavolß, ale celΘ jejφ t∞lo se vlo₧φ na mφsto, odkud ji volßme (inline funkce fungujφ prakticky stejn∞ jako makra). Re₧ie spojenß se zavolßnφm odpadne a program tak pob∞₧φ rychleji:

inline int main(int a, int b) {
    if (a < b)
        return a;
    return b;
}

Pozor, funkci deklaruje jako inline pouze tehdy, kdy₧ mßte jistotu, ₧e to pot°ebujete. V p°evß₧nΘ v∞tÜin∞ p°φpad∙ to pot°ebovat nebudete, a program se nezrychlφ, ale bude vetÜφ.

P°φklad

Na zßv∞r tohoto dφlu si ukß₧eme rozsßhlejÜφ p°φklad. Bude se jednat o program poΦφtajφcφ, kolika zp∙soby je mo₧no vybrat ze skupiny n prvk∙ skupinu k prkv∙, bez opakovßnφ (poΦet k-Φlenn²ch variacφ bez opakovßnφ z n prvk∙). Nap°φklad je-li n = 4 a k = 2 jednß se o poΦet mo₧n²ch v²b∞r∙ dvou prvk∙ ze cty°, p°iΦem₧ zßle₧φ na po°adφ - (a,b) je n∞co jinΘho ne₧ (b,a):

 

a,b a,c a,d
b,a b,c b,d
c,a c,b c,d
d,a d,b d,c


Pro v²poΦet variacφ existuje matematick² vzorec:



kde

(faktorißl z Φφsla n). TakΘ platφ 0! = 1.

 

Vybaveni t∞mito znalostmi se m∙₧eme pustit do prßce: nejd°φv budeme pot°ebovat p°eΦφst parametry z p°φkazovΘ °ßdky a p°ipravit je pro v²poΦet:

#include <stdlib.h>
#include <iostream.h>

// deklarace °et∞zc∙ s chybov²mi hlßÜenφmi
char *szMaloParametru = "Parametry programu musφ b²t dv∞ Φφsla.";
char *szNeplatne = "Parametry nejsou platnΘ.";

// dalÜφ °et∞zce pou₧itΘ v programu
char *szVysledek = "V²sledek: ";

void main(int argc, char **argv) {
    int n, k;

    // program musφ mφt alespo≥ 3 parametry (cesta k programu (ta tam je vzdy), n a k)
    if (argc < 3) {
        cout << szMaloParametru;
        return;
    }

    // p°evod parametru ulo₧en²ch jako °et∞zce na Φφsla funkcφ atoi (v hlaviΦkovΘm souboru stdlib.h)
    // argv[1] je k (prvnφ parametr)
    // argv[2] je n (druh² parametr)
    k = atoi(argv[1]);
    n = atoi(argv[2]);

    // kontrola sprßvnosti parametr∙
    if (n <= 0 || k <= 0 || n < k) {
        cout << szNeplatne;
        return;
    }

    // v²pis v²sledku
    cout << szVysledek << variace(k, n);
}

Na konci programu vypisujeme v²sledek funkcφ variace, kterß spoΦte po₧adovan² poΦet variacφ na zßklad∞ parametr∙ n a k. Tuto funkci si musφme definovat (p°ed funkci main, abychom nemuseli psßt i deklaraci), podle matematickΘho vzorce:

unsigned int variace(unsigned int k, unsigned int n) {
    // v²sledek poΦφtßme rovnou, nemusφme pro n∞j deklarovat prom∞nnou
    return faktorial(n) / faktorial(n - k);
}

Ve funkci variace pou₧φvßme funkci faktorial, tak₧e ji p°idßme:

unsigned int faktorial(unsigned int n) {
    unsigned int i, vysledek = 1;

    if (n == 0)
        return 1;
		
    for (i = 2; i <= n; i++)
        vysledek *= i;    // vysledek = vysledek * i6
    return vysledek;
}

A to je vÜechno. ZajφmavΘ je snad jen to, ₧e funkce variace a faktorißl dostßvajφ parametry typu unsigned int (celΘ Φφslo bez znamΘnka) a takΘ takovou hodnotu vracejφ. To je v po°ßdku, variaci ani faktorißl ze zßporn²ch Φφsel poΦφtat nelze. Ale p°i Φtenφ parametr∙ z p°φkazovΘ °ßdky ve funkci main pou₧φvßme prom∞nnΘ typu int, abychom mohli zachytit pokus u₧ivatele o zadßnφ zßporn²ch Φφsel (kdybychom mφsto int pou₧ili i tady unsigned int doÜlo by p°i zadßnφ zßpornΘho Φφsla k p°eteΦenφ a nap°. mφsto Φφsla -1 by se do prom∞nnΘ ulo₧ilo 4294967295).

 

D∞kuji za pozornost a t∞Üφm se na dalÜφ dφl.

 Andrei Badea