Skočiť na obsah


Programovanie v jazyku C a C#

Návody pre začiatočníkov a pokročilých



Fórum: Škôlka jazyka C
- - - - -
Fotografia

4. lekcia C (Funkcie a práca s pamäťou) základná škola


Funkcie a práca s pamäťou

Jazyk C je založený na funkciách.
Funkcia main() musí byť v každom programe, pretože to je vykonávacia funkcia programu. Môže byť v programe aj ako jediná, ale to je veľmi jednoduchý program. Väčšinou je v programe funkcií viac a funkcia main() si ich volá aby vykonali svoju časť práce. Funkcie sa môžu volať medzi sebou navzájom.
Okrem funkcie main() sa používajú (už hotové) funkcie z rôznych knižníc a funkcie, ktoré si vytvoríme sami.

Ak je teda v programe viac funkcií, je dobré umiestniť funkciu main() pred ďalšími vlastnými funkciami kvôli prehľadnosti.

Príklad:

#include <stdio.h> /* hotový hlavičnový súbor */



/* deklarácia vlastných funkcií, aby sa dali volať z funkcie main() a navzájom (úplný funkčný prototyp) */

int obvod(int a, int c);

int obsah(int a, int c);



/* funkcia main() */

int main(void)

{

int i, j;



system("cls"); /* volanie hotovej funkcia z knižnice stdio.h */

printf("vypocet obvodu obdlznika:\n\n"

		 "dlzka strany a\n");

scanf("%d", &i);

printf("\ndlzka strany c\n");

scanf("%d", &j);

printf("\no = 2(a + c): %d = 2(%d + %d).\n\n", obvod(i, j), i, j); /* volanie funkcie obvod() */

system("pause"); /* volanie hotovej funkcia z knižnice stdio.h */



system("cls"); /* volanie hotovej funkcia z knižnice stdio.h */

printf("vypocet obsahu obdlznika:\n\n"

		 "dlzka strany a\n");

scanf("%d", &i);

printf("\ndlzka strany c\n");

scanf("%d", &j);

printf("\np = a x c: %d = %d x %d\n\n", obsah(i, j), i, j); /* volanie funkcie obsah() */

system("pause"); /* volanie hotovej funkcia z knižnice stdio.h */

return 0;

}



/* definícia vlastných funkcií */

int obvod(int a, int c) /* vlastná funkcia na výpočet obvodu obdĺžnika */

{

return (2 * (a + c)); /* vrátenie výsledku */

}



int obsah(int a, int c) /* vlastná funkcia na výpočet obsahu obdĺžnika */

{

return(a * c);		 /* vrátenie výsledku */

}

Teraz to skúsim vysvetliť.
#include <stdio.h> /* hotový hlavičnový súbor */
Prečo hotový? Pretože už ho niekto niekedy napísal a v súčasnosti sa už len využíva.
Hlavičkové súbory si ale budeme písať aj vlastné pre svoje potreby. Ale o tom neskôr.
/* deklarácia vlastných funkcií, aby sa dali volať z funkcie main() a navzájom (úplný funkčný prototyp) */

int obvod(int a, int c);

int obsah(int a, int c);
V tomto programe sú volané funkcie len z funkcie main(). Na to by sa tu nemuseli deklarovať.
Stačilo by, aby boli napísané v poradí skôr ako funkcia main() a nie až po nej, ako je to
v tomto prípade.
Ale sú časté prípady, kedy sa funkcie volajú navzájom. Keby neboli deklarované, nefungovalo by to.

Z funkcie main() si preberieme len niekoľko riadkov.
int main(void)
Funkcia main() zásadne vracia hodnotu typu int. Síce nie inej funkcii, pretože ju žiadna
nevolá ani nemôže, ale vracia ju operačnému systému s tým, že ak je to hodnota 0 (nula),
funkcia main(), teda vlastne samotný program prebehol v poriadku.
int i, j;
Deklarácia potrebných premenných v tele funkcie. Všimni si, že v ostatných funkciách v tomto
programe, deklarácia premenných nie je v tele funkcie.
system("cls"); /* volanie hotovej funkcia z knižnice stdio.h */
Prečo hotovej? Pretože už ju niekto niekedy napísal a v súčasnosti sa už len využíva.
printf("\no = 2(a + c): %d = 2(%d + %d).\n\n", obvod(i, j), i, j); /* volanie funkcie obvod() */
Volanie funkcie obvod(i, j).
return 0;
Ak program dojde až sem, vráti sa operačnému systému 0 (nula) a ten bude vedieť, že prebehol
v poriadku.
/* definícia vlastných funkcií */

int obvod(int a, int c) /* vlastná funkcia na výpočet obvodu obdĺžnika */
int = typ hodnoty, ktorá sa vráti tam, odkieľ bola táto funkcia volaná
obvod = názov funkcie
(int a, int c) = premenné aj s typmi, ktoré do funkcie vstupujú. V tomto prípade
sú to hodnoty premenných i, j z funkcie main() odkiaľ je funkcia
obvod volaná.
return (2 * (a + c)); /* vrátenie výsledku */
Výsledok výpočtu je typu int a vráti sa tam, odkiaľ bola funkcia volaná.


Procedúry a dátový typ void (prázdny)

Formálne síce v C procedúry neexistujú, ale sú dve cesty ako to obísť:

1. Funkcia síce návratovú hodnotu vracia, ale nikto ju nepotrebuje.
Typickým príkladom je
getchar(); /* čakanie na stlačenie klávesy */

Novšie prekladače dokonca vyžadujú explicitné pretypovanie ny typ void, teda:
(void) getchar(); /* čakanie na stlačenie klávesy */

2. Funkcia sa definuje ako funkcia vracajúca typ void:
void tlac_int(int i)

{

printf("%d", i)<img src='http://forum.freespace.sk/public/style_emoticons//wink.png' class='bbc_emoticon' alt=';)' />

}

Volanie funkcie je potom napr.: tlac_int(a + b - c)
V takom prípade sa príkaz return ako návrat výsledku nepoužíva.

Poznámky:
Typ void sa používa aj vtedy, ak funkcia nemá žiadne formálne parametre,
aby o tom bol prekladač uistený.
int spocitaj(void)

{

int a, c;



scanf("%d%d", &a, &c);

return (a + c);

}

Volanie funkcie je potom: spocitaj();

Procedúra úplne bez parametrov teda vyzerá takto:
void ahoj(void)

{

printf("ahoj\n");

}

Volanie funkcie je potom: ahoj();

Rekurzívne funkcie

Často krát najjednoduchšou cestou ako vyriešiť daný problém je, že použijeme princíp:
funkcia alebo procedúra zavolá samú seba.
Inak povedané "rekurzívne volanie funkcie."

Napr. program na výpočet faktoriálu:
#include <stdio.h>



int fakt(int n)

{

return ((n <= 0) ? 1 : n * fakt(n - 1));

}



int main(void)

{

int i;



printf("Zadaj cele cislo: ");

scanf("%d", &i);

printf("Faktorial je %d\n", fakt(i));

return 0;

}

Funkcia pracuje tak, že do premennéj n sa vloží hodnota z premennéj i.
Ak napr. n = i = 3; vtedy prebehne vo funkcii fakt() nasledovný cyklus:
3 * 2 = 6 * 1 = 6 * 1 = 6
Čiže funkcia volá samu seba a tým vznikne cyklus, ktorý beží do vtedy, kým je výraz platný.
Potom sa výsledok vráti tam, odkiaľ bola funkcia volaná.

Konverzia návratovej hodnoty funkcie
int konverzia(double d)

{

return d;

}

Volanie funkcie je potom: k = konverzia(4.5);
Hodnota double 4.5 sa pretypuje na int, teda sa oreže na hodnotu 4.

Oblasť platnosti identifikátorov

Globálne premenné
Sú definované mimo funkcií.
int platna_v_celom_súbore; /* plati vo všetkych troch funkciach */



prva_funkcia()

{

...		 /* telo funkcie */

}



int platna_v_casti_súboru; /* plati v druhej a tretej funkcii */



druha_funkcia()

{

...		 /* telo funkcie */

}



tretia_funkcia()

{

...		 /* telo funkcie */

}

Lokálne premenné
Sú definované vnútri funkcií
prva_funkcia()

{

int i;	 /* lokálna premenna plati od miesta Definície do konca funkcie */

...		 /* telo funkcie */

}



druha_funkcia()

{

...		 /* 1 cast tela funkcie */

int i;	 /* lokálna premenna plati od miesta Definície do konca funkcie, teda len v druhej casti tela funkcie */

...		 /* 2 cast tela funkcie */

}

Niektoré globálne premenné môžu byť prekryté lokálnymi.
int i1, i2;



prva_funkcia()

{

int i1, j1; /* vo funkcii platia premenné i2 globalna, i1 a j1 lokálne */

...		 /* telo funkcie */

}



druha_funkcia()

{

...		 /* 1 cast tela funkcie, tu platia aj globalne premenné i1 a i2 */

int i2, j1; /* z tohto miesta platia premenné i1 globalna, i2 a j1 lokálne */

...		 /* 2 cast tela funkcie */

}

Príklad:
Program vypíše dĺžku najdlhšieho riadku zo súboru DOPIS.TXT
#include <stdio.h>

#define CHYBA(sprava) { printf("%s\n", sprava); return 1; } /* makro s parametrami */



FILE *fr; /* globalna premenna platna v celom programe */



/*

* funkcia otvara súbor DOPIS.TXT

* vracia nenulovu hodnotu pri uspesnom otvoreni, inaksie vracia 0

*/

int otvor_súbor(void)

{

return ((fr = fopen("DOPIS.TXT", "r")) != NULL);

}



/*

* funkcia zatvara súbor DOPIS.TXT

* vracia nenulovu hodnotu pri uspesnom zavreti, inaksie vracia 0

*/

int zatvor_súbor(void)

{

return (fclose(fr) != EOF);

}



/*

* funkcia cita riadok súboru

* vracia dlzku precitaneho riadku, alebo EOF na konci súboru

*/

int citaj_riadok(void)

{

int dlzka = 0, c; /* lokálne premenné len pre tuto funkciu */



while ((c = getc(fr)) != EOF) {

	if (c == '\n')

	 return (dlzka + 1);

	else

	 dlzka++;

}

return EOF;

}



/* funkcia main() */

int main(void)

{

int najdlhsi = 0, aktualny;



if (otvor_súbor() == 0)					 /* vola funkciu otvor_súbor() */

	CHYBA("Neda sa otvorit súbor DOPIS.TXT")	/* vola makro CHYBA() */



while ((aktualny = citaj_riadok()) != EOF) { /* vola funkciu citaj_riadok() */

	if (aktualny > najdlhsi)

	 najdlhsi = aktualny;

}

printf("Najdlhsi riadok ma %d znakov.\n", najdlhsi);

if (zatvor_súbor() == 0)					 /* vola funkciu zatvor_súbor() */

	CHYBA("Neda sa zatvorit súbor DOPIS.TXT") /* vola makro CHYBA() */



return 0;

}

Pamäťové triedy

- auto
- extern
- static
- register


auto
Je to implicitná trieda pre lokálne premenné a je uložená v zásobníku.
Existuje od vstupu do funkcie a zaniká pri výstupe z funkcie.
Čiže:

funkcia()

{

auto int i;

auto int j = 10;

}

je to isté ako
funkcia()

{

int i;

int j = 10;

}

a to už používame dlhú dobu.

extern
Je to implicitná trieda pre globálne premenné a je uložená v dátovej oblasti.
Kľúčové slovo extern sa používa pri oddelenom preklade súborov, keď je potrebné,
aby viac súborov zdielalo tú istú premennú.
Táto globálna premenná je v jednom súbore definovaná zásadne bez slova extern,
ale v ostatných musí byť deklarovaná s použitím slova extern.

Príklad:
súbor S1.c
int velkost; /* definícia */

súbor S2.c
extern int velkost; /* Deklarácia */

súbor S3.c
extern int velkost; /* Deklarácia */

static
Neexistuje pre ňu implicitná definícia, preto slovo static musí byť pri definícii
vždy uvedené.

Tieto premenné sú uložené v dátovej oblasti pamäte.

Má dve oblasti použitia:

1) Pre globálne premenné, alebo funkcie, čo má ten význam, že sú viditeľné len v module,
v ktorom sú definované.

2) Pre lokálne premenné, kedy pamäťovú triedu static využívajú lokálne premenné,, ktoré
si ponechajú svoju hodnotu aj medzi jednotlivými volaniami funkcie v ktoréj sú definované.
To je zásadný rozdiel medzi lokálnimi auto (zásobník - po skončení funkcie sa stráca)
a static (dátová oblasť - po skončení funkcie ostáva)

Statická lokálna premenná existuje od prvého volania príslušnej funkcie až do ukončenia programu,
ale ako lokálna funkcia nie je prístupná mimo funkcie, v ktoréj je definovaná.
Príklad:
#include <stdio.h>



void funkcia(void)

{

int x = 2;

static int i = 1;



printf("funkcia bola volana %d krat, x = %d\n", i, x);

i++;

x++;

}

int main(void)

{

int j;



for (j = 0; j < 3; j++)

	funkcia();

}

Tým že x je auto lokálna premenná, vždy po zavolaní funkcie je do nej v zásobníku priradená hodnota 2.
( int x = 2; )
( x++; platí vnútri funkcie, ale výsledok sa stratí s ukončením funkcie )

premennéj i je pri prvom vstupe do funkcie priradená hodnota 1, potom je zväčšená o 1, teda na 2
a jej hodnota sa nestratí, ale prechádza do ďalšieho volania funkcie.
Inicializácia na 1 riadkom:

static int i = 1;

sa už viac krát nevykoná.

Výstup programu bude:
funkcia bola volana 1 krat, x = 2
funkcia bola volana 2 krat, x = 2
funkcia bola volana 3 krat, x = 2

Poznámka.
Ak potrebujeme statických premenných jedného typu viac, treba ich definovať takto:
static int i;
static int j;
a nie
static int i, j;
Pretože niektoré kompilátory zoberú ako static iba premennú i a premennú j ako auto.

register
Je uložená v registry, to má za výhodu oveľa rýchlejšieho prístupu k nej a teda aj rýchlejšieho programu.
Problém je ale v tom, že systém môže uložiť premennú do registru len vtedy, ak je nejaký voľný.
To však nemusí byť vždy.
Preto sa registrované premenné používajú napr. na jednoduchý cyklus, alebo často používaný formálny parameter.
Môže sa použiť len pre lokálnu premennú, nie pre globálnu.

Príklad:
Funkcia pre výpis malej násobilky:
#include <stdio.h>



void nasobilka(register int k)

{

register int i;



for (i = 1; i <= 10; i++)

	printf("%2d x %d = %2d\n", i, k, i * k);

}



int main(void)

{

nasobilka(5);

return 0;

}

Typové modifikátory
- const
- volatile


const
Jeho použitie špecifikuje, že definovanému objektu nemôže byť po jeho inicializácii už zmeneá hodnota.
Dôkaz:

Obrázok

Kompilácia sa nepodarí a hláška "error: assignment of read-only variable 'pi'" znamená, že
uvedená premenná je len na čítanie, teda nie na zápis.

volatile
Používa sa len zriedka a hlavne pri programovani nejakych zariadeni a driverov.
Tu sa sním asi ani nestretneme.
Koho zaujíma viac, čítaj tu: http://forum.builder...php?123,2726391


Bloky

Blok je uzatvorený medzi kučeravými zátvorkami { a } a môže obsahovať definície lokálnych premenných a príkazy.
Väčšinou však obsahuje len príkazy a vtedy sa nazáva "zložený príkaz".

Príklad:
Program číta jedno celé číslo a ak je kladné, číta ešte ďalšie číslo a vytlačí ich súčet. Ak je prvé číslo
záporné, vtedy číta reálne (ďalšie) číslo a vytlačí ich súčin.

#include <stdio.h>

int main(void)

{

int i;

scanf("%d", &i);

if (i > 0) {

	int j;						 /* j definovane vnutri bloku */

	scanf ("%d", &j);

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

}

else {

	double f;					 /* f definovane vnutri bloku */

	scanf("%lf", &f);

	printf("Sucin je %f\n", i * f);

}

return 0;

}

Dva dôvody definovania premenných vo vnútornom bloku:

1) Šetrenie pamäťou
Vlastne sa pamäť alokuje len v konkrétnych blokoch. Pri jednotlivých premenných to nemá prektický význam,
ale ak by sa jednalo o väčšie pole, vtedy je možné o tomto spôsobe uvažovať.

2) Čistota a prehľadnosť kódu
Ak sa držíme chvályhodnej zásady, že premenná sa definuje len tam, kde sa použije, vtedy je definícia
vo vnútornom bloku vhodná.


Oddelený preklad súborov - II

Prvé, čo musíme spraviť, je zamysleť sa nad rozdelením úlohy (programu) na viac, na sebe čo možno najmenej
závislých častí. Niekedy to vyplýva zo samotnej úlohy, niekedy je treba nad tým popremýšľať.
V každom prípade je vhodné stráviť premýšľaním nejaký čas, pretože pri nevhodnom rozdelení programu do viac
súborov, si pravdepodobne viac problémov pridáme ako ušetríme.

Druhým krokom je , že po rozdelení simusíme stanoviť "interface" (rozhranie) alebo styčné body, teda spôsoby
komunikácie oddelených častí navzájom.
Vhodnejší spôsob komunikácie je pomocou volania funkcií druhého modulu ako pomocou zdielaných globálnych
premenných, viditeľných vo všetkých moduloch. (Toto je všeobecný trend.)


Rozšírenie platnosti globálnych premenných

Vytvoríme si prázdny projekt v Code:Blocks.
http://skolka-jazyka...c.php?f=3&t=439
Do projektu vložíme dva súbory:
hlavny.c
pomocny.c
a môžme začať.

Príklad:
Obsah súboru pomocny.c
/* začiatok súboru pomocny.c */

/* #include <stdio.h> sa v nom nepoužíva */

int x;			 /* Definícia globalnej premennéj */

extern double f;	/* Deklarácia globalnej premennéj definovanej v druhom súbore */

int funkcia(void) /* Definícia globalnej funkcie */

{

return (x + (int) f);

}

/* koniec súboru pomocny.c */

Obsah súboru hlavny.c
/* začiatok súboru hlavny.c */

#include <stdio.h>	 /* tu už je potrebna, pretože sa používaju I/O funkcie */

#include <stdlib.h>	 /* aby pracovala funkcia system("pause") */

extern int x;			 /* Deklarácia globalnej premennéj definovanej v druhom súbore */

extern int funkcia(void); /* Deklarácia funkcie definovanej v druhom súbore */

double f;				 /* Definícia globalnej premennéj */

int main(void)			 /* funkcia main() */

{

x = 3;

f = 3.5;

printf("%d\n", funkcia()); /* volanie globalnej funkcie z druheho súboru */

system("pause");

return 0;

}

/* koniec súboru hlavny.c */

Daj ho skompilovať a spusti ho.
Výsledok bude 6.

Dobre si prezri komentáre v súboroch a zamysli sa nad nimi. Skús niečo pomeniť ako sa to bude správať.

Pozor:
Dôležité je aby externé identifikátory mali krátke názvy, doporučuje sa max. 8 znakov.


Projekt môžeš spustiť aj tak, že si otvoríš zložku kde si projekt uložil, tam zložku bin, v nej
zložku Debug a tam bude súbor "Prvy_Projekt.exe".


Statické globálne premenné a funkcie

Tieto premenné a funkcie majú tú výhodu, že sú viditeľné, teda aj použiteľné len v module (súbore), v ktorom boli definované. Nepomôže ani keby boli v iných moduloch deklarované ako extern.

Static je veľmi výhodné používať v prípade, keď na veľkom programe (projekte) pracuje viac programátorov naraz.
Tí sa potom nemusia obávať, že by použili rovnaké názvy pre identifikátory ako ich kolegovia.
Na pomenovaní globálnych funkcií a premenných zaisťujúcich rozhranie (interface) sa ľahko dohovoria a všetky ostatné funkcie a globálne premenné označia ako static. Potom nemôže dôjsť ku kolízii názvov, pretože každý pracuje len s tými svojimi.

Vytvor si nový projekt (názov je tvoja záležitosť).
V ňom súbory "pomocny.c" a "hlavny.c".

Obsah súboru pomocny.c
/* začiatok súboru pomocny.c */

static int x;	 /* Definícia statickej globalnej premennéj */

extern double f;	/* Deklarácia globalnej premennéj definovanej v druhom súbore */

static double fun(double x) /* Definícia statickej funkcie */

{

return x;

}

int funkcia(void) /* Definícia globalnej funkcie */

{

return ((int) fun(f) + x);

}

/* koniec súboru pomocny.c */

Obsah súboru hlavny.c
/* začiatok súboru hlavny.c */

#include <stdio.h>	

#include <stdlib.h>	

extern int x;			 /* Deklarácia globalnej premennéj definovanej v druhom súbore */

extern int funkcia(void); /* Deklarácia funkcie definovanej v druhom súbore */

double f;				 /* Definícia globalnej premennéj */

static void fun(void)	 /* Definícia statickej funkcie */

{

printf("%d\n", funkcia());

}

int main(void)			 /* funkcia main() */

{

x = 3;

f = 3.5;

fun();

return 0;

}

/* koniec súboru hlavny.c */

Preklad týchto súborov prebehne úspešne, ale pri zostavovaní bude linker hlásit chybu, že nie je definovaná premenná x.
Tá bola totiž definovaná ako static v pomocny.c a deklarácia:

extern int x;

v hlavny.c jej pamäťovú triedu nezmení.

Po vynechaní static v definícii x v pomocny.c bude program pracovať správne a nebude zmätený tým, že sú tam dve funkcie rovnako pomenované fun(), ale rôznych typov. Funkcia fun() v súbore pomocný.c nemá nič spoločného s fun() z hlavny.c a každá je viditeľná (dostupná, volatelná) iba zo súboru, v ktorom je definovaná.


Ako udržat poriadok vo veľkom programe

Všeobecný spôsob, ako najlepšie udržat poriadok v externých a statických premenných a funkciách a ako všeobecne zorganizovať rozloženie programu do modulov tak, aby sa na nič nezabudlo, má zhruba nasledujúce časti:

•Zdrojové texty (telá) všetkych funkcií (definície funkcií) a definície všetkých premenných sú v súbore PGM_1.C - dôležitá je prípona .C.

•V tomto súbore je striktne rozlíšené, ktoré funkcie (poprípade nevyhnutné množstvo premenných) sa budú dávat k dispozícii vo vnutri modulu PGM_1. Snažíme sa navrhnúť program tak, aby bolo čo najmenej odovzdávaných (externých) premenných - k manipulácii s nimi poskytujeme skôr špeciálne funkcie.

•Všetky ostatné funkcie a globálne premenné označíme ako static a tým ich ochránime pred nechceným či nevhodným použitím.

•Funkčné prototypy (hlavičky) týchto funkcií a definície globálnych premenných skopírujeme do súboru PGM_1.H - dôležitá je prípona .H a označíme ich ako extern.

•Pokiaľ budeme z modulu PGM_1 poskytovať i niektoré symbolické konštanty, uvedieme ich iba v súbore PGM_1.H a nie v PGM_1.C.

•súbor PGM_1.C potom zacina:
#include <stdio.h>

#include "PGM_1.H"

Týmto trikom máme zaručené, že v súbore PGM_1.C budú známe všetky funkcie pomocou funkčných prototypov i všetky symbolické konštanty. Deklarácia globálnych premenných ako extern v PGM_1.H nespôsobí žiadnu kolíziu s definíciami týchto premenných v PGM_1.C.

•Súbor PGM_2.C, ktorý obsahuje ďalší modul programu a využíva niektoré funkcie alebo premenné alebo symbolické konštanty z modulu PGM_1, potom začína opät:
#include <stdio.h>		

#include "PGM_1.H"		/* spolocne funkcie a premenné */

#include "PGM_2.H"		/* Deklarácia vlastneho modulu */

Programátor modulu PGM_2 sa vôbec nemusí starať o to, či má správne napisané a uvedené všetky potrebné spoločné identifikátory.
To je starosť programátora modulu PGM_1. Ten, kto programuje modul PGM_2, si iba prehliadne súbor PGM_1.H, kde má uložené všetky potrebné informácie.

Ak dodržia vyššie uvedený postup všetci, ktorí spolupracujú na projekte, zníži sa výrazne množstvo problémov.


Doporučený obsah .C súboru

Poznámka:
  • Pre ďalší popis sa predpokladá existencia súboru STDDFN.H s týmto obsahom:
/*
* STDDFN.H ver. 1.0
*
* Standardne definicie
*
* libcosenior maj.2012
*/

#define GLOBAL /* */
#define LOCAL static
#define IMPORT extern
#define EXPORT extern

Súbor .C by mal obsahovať postupne tieto časti:
  • Dokumentačná časť
  • meno súboru a verzia
  • stručný popis modulu
  • meno autora a dátum
  • všetky potrebne #include
    • iba súbory .H a nikdy súbory .C - využívame výhod oddeleného prekladu
    • najskôr systémové .H súbory príkazom: #include < >
    • potom vlastné .H súbory prikazom: #include " "
  • Deklarácia IMPORTovaných objektov
    • Iba v tom prípade, že sú v príslušnom .H súbore spolupracujúceho modulu, čomu dávame zásadne prednosť
    • Tieto objekty (funkcie alebo premenné) sú v iných moduloch definované ako GLOBAL.
  • Definícia GLOBAlnych premenných
    • Sú to premenné (definované vo vnútri funkcii), ktoré majú byť spoločné i pre ostatné moduly. V nich sú označené ako IMPORT, pokiaľ to už nie je uskutočnené v príslušnom .H súbore.
    • Počet týchto spoločných premenných sa snažíme obmedziť na čo najmenšiu mieru.
  • lokálne #define
    • Definície symbolických konštánt a makier s parametrami použitých iba v tomto module.
    • Pokiaľ by boli príliš rozsiahle (desiatky riadkov), je možné ich umiestniť do špeciálneho .H súboru - len pre zvýšenie prehľadnosti.
  • Definícia lokálnych typov
    • Zásadne sa používa Definícia nového typu pomocou typedef.
    • Pokiaľ by boli príliš rozsiahle, je možne ich umiestniť do .H súboru podobne, ako predchádzajúce Definície symbolických konštánt.
  • Definície LOCALnych premenných
    • Sú to globálne premenné využívané viac funkciami tohoto modulu. Aby neboli viditeľné v iných moduloch, majú pamäťovú triedu static.
  • Úplne funkčné prototypy LOCALnych funkcií
  • Funkcia main()
    • Uvádza sa samozrejme iba v prípade, že v danom module existuje.
  • Definícia GLOBALnych funkcií
    • Sú to funkcie, ktoré môžu byť volané aj z iných modulov
    • Ich úplne funkčné prototypy sú uložené v príslušnom .H súbore.
  • Definícia LOCALnych funkcii
    • Sú to funkcie, ktoré môžu byť volané iba v tomto module
    • Ich úplne funkčne prototypy sú uložené v tomto súbore
    Doporučený obsah .H súboru

    Rovnako tak, ako platia určité pravidlá pre súbory .C, platia podobné pravidlá aj pre súbory .H.
    Predtým, než bude uvedená doporučená štruktúra .H súboru, venujte pozornosť ešte dvom doporučeniam:
  • V .H súbore nesmie byť Zásadne žiadna Definícia (vymedzenie pamäti pre premenné alebo kód funkcii) alebo dokonca inicializácia spoločných premenných.
  • Nemali by tu byť žiadne príkazy #include
súbor .H by mal obsahovať postupne tieto časti:
  • Dokumentačná časť
    • meno súboru a verzia
    • stručný popis modulu
    • meno autora a dátum
  • Definícia symbolických konštánt
    • Symbolické konštanty, o ktorých sa dá predpokladať, že budú využívané aj v iných moduloch.
  • Definície makier s parametrami
    • Platí to isté, čo u symbolických konštánt z predchádzajúceho bodu.
  • Definície globálnych typov
    • Nové dátové typy využívatelné aj v iných moduloch definujeme Zásadne pomocou typedef.
  • Deklarácia globálnych premenných príslušného .C modulu
    • Sú tu označené ako EXPORT - exportujú sa do iného modulu.
  • Úplne funkčné prototypy globálnych funkcii príslušného .C modulu
    • Sú tu označené ako EXPORT - exportujú sa do iného modulu.
V tejto chvíli vám to musí pripadať zložité, ale táto zložitosť je iba zdanlivá. Pokiaľ budete vytvárať programy o desaťtisícoch riadkoch, potom sa vám bohato vyplatí zaviesť túto alebo podobnú štruktúru súborov. Je ale nutné dodržiavať poriadok už od začiatku. Predsavzatie, že to opravíte nabudúce, vás bude stáť veľa času a nervov.



Priklad:

Vyššie popísané usporiadanie bude demonštrované nasledujúcim príkladom. Program bude počítať obvod


a obsah kružnice a bude zloženy z modulov KRU_MAIN.C a KRU_IO.C. K modulu KRU_MAIN.C patrí hlavičkový súbor KRU_MAIN.H a ku KRU_IO.C patrí hlavičkový súbor KRU_IO.H. Využíva sa vyššie uvedeny hlavičkový súbor definícii STDDFN.H.

Poznámka:
  • Tento program je samozrejme vyumelkovaný a teda aj zbytočne zložitý, pretože bol písaný so snahou ukázať v maximálnej možnej miere možnosti vzájomnej previazanosti rôznych modulov. V skutočnosti sa doporučuje, aby previazanosť bola iba jedným smerom, tzn. že hlavný modul (v našom prípade KRU_MAIN.C) môže využívať globálne funkcie podriadených modulov (KRU_IO.C), ale nie naopak. Pokiaľ by bolo ťažké tomu zabrániť, mali by podriadené moduly využívať iba minimálny počet spoločných globálnych premenných hlavných modulov.
Vytvor si v Code:Blocks nový prázdny projekt a vlož tam nasledovné súbory (source a header)


/*

* KRU_MAIN.C		 ver. 1.0

*

* Program pre vypocty obvodu a obsahu kruznice

* ============================================

*

* libcosenior, maj 2012

*

*/



/* systemove hlavickove subory */

#include <stdio.h>

#include <stdlib.h>



/* vlastne hlavickove subory */

#include "STDDFN.H"				 /* natiahnutie standardnych definicii */



#include "KRU_MAIN.H"				/* natiahnutie symbolickych konstant,

									 * funkcnych prototypov GLOBAL funkcii a

									 * globalnych typov vlastneho modulu */



#include "KRU_IO.H"				 /* natiahnutie symbolickych konstant,

									 * funkcnych prototypov GLOBAL funkcii a

									 * globalnych typov spolupracujuceho modulu

									 */



/* deklaracia IMPORTovanych objektov

* ---------------------------------

* nie je nutna, pretože importovane

* objekty su uz zname vdaka prikazu #include "KRU_IO.H"

* pokial by boli pouzite, vyzerali by napr.: IMPORT int i;

*/



/* definícia GLOBALnych premenných */

GLOBAL double obvod;



/* lokalne definicie symbolickych konstant */

#define PI	 3.1415



/* lokalne definicie novych typov

* ------------------------------

* tu nie su ziadne typedef pouzite

* pokial by boli pouzite, vyzerali by napr.:

* typedef MOJE_INT int;

*/



/* definicie LOCALnych premenných */

LOCAL double polomer;

LOCAL double obsah;



/* uplne funkcne prototypy LOCAL funkcii */

LOCAL void vyp_obsahu(double polomer);

LOCAL double vyp_obvodu(void);

LOCAL void vypocet(void);



/* funkcia main() */

/*

* funkcia pre vypocet obsahu kruznice

* polomer kruznice je globalna staticka premenna

*/

int main(void)

{

	polomer = vstup_dat();



	if (polomer == CHYBA_DAT) {

		/* chybne zadane vstupne udaje - zaporna hodnota */

		printf("Polomer kruznice chybne zadany!\n");

	} else {

		vypocet();

		vystup_dat(obsah);

	}

	system("pause");

	return 0;

}



/* definicie LOCAL funkcii */



/*

* funkcia pre vypocet obsahu kruznice

* polomer kruznice je globalna staticka premenna

*/

LOCAL void vyp_obsahu(double polomer)

{

	obsah = PI * polomer * polomer;

}



/*

* funkcia pre vypocet obvodu kruznice

* polomer kruznice je globalna staticka premenna

*/

LOCAL double vyp_obvodu(void) {

	return(PI * vyp_priemeru(polomer));

}



/*

* koncova funkcia pre main()

* polomer kruznice je globalna staticka premenna

*/

LOCAL void vypocet(void) {

	obvod = vyp_obvodu();

	vyp_obsahu(polomer);

}



/* definícia GLOBAL funkcii */



/*

* funkcia pre vypocet priemeru kruznice

* polomer kruznice je globalna staticka premenna

*/

GLOBAL double vyp_priemeru(double polomer) {

	return(2 * polomer);

}





/*

* KRU_MAIN.H	 ver. 1.0

*

* Hlavickovy subor pre modul KRU_MAIN.C

* =====================================

*

* libcosenior, maj 2012

*

*/



#ifndef KRU_MAIN_H_INCLUDED

#define KRU_MAIN_H_INCLUDED



/* definicie symbolickych konstant využivanych i v inych moduloch

* --------------------------------------------------------------

* v tomto module ziadne nie su

*/



/* definicie makier s parametrami

* ------------------------------

* v tomto module ziadne nie su

*/



/* definicie globalnych typov

* --------------------------

* v tomto module ziadne nie su

*/



/* deklaracie globalnych premenných modulu KRU_MAIN.C */

EXPORT double obvod;



/* uplne funkcne prototypy GLOBAL funkcii modulu KRU_MAIN.C */

EXPORT double vyp_priemeru(double polomer);



#endif // KRU_MAIN_H_INCLUDED





/*

* KRU_IO.C	ver. 1.0

*

* Vstupy a vystupy programu pre vypocty kruznice

* ==============================================

*

* libcosenior, maj 2012

*/



/* systemove hlavickove subory */

#include <stdio.h>



/* vlastne hlavickove subory */

#include "STDDFN.H"				 /* natiahnutie standardnych definicii

*/

#include "KRU_IO.H"					 /* natiahnutie symbolickych konstant,

										 * funkcnych prototypov GLOBAL funkcii

										 * a globalnych typov vlastneho modulu

										 */

#include "KRU_MAIN.H"			 /* natiahnutie symbolickych konstant,

										 * funkcnych prototypov GLOBAL funkcii

										 * a globalnych typov spolupracujuceho

										 * modulu

										 */



/* deklaracia IMPORTovanych objektov

* ---------------------------------

* nie su nutne, pretože importovane

* objekty su uz zname vdaka prikazu:

* #include "KRU_MAIN.H"

*/



/* definícia GLOBALnych premenných

* -------------------------------

* v tomto module ziadne nie su

*/



/* lokalne definicie symbolickych konstant a makier */

#define kontrola(x) ((x >= 0.0) ? x : CHYBA_DAT)



/* lokalne definicie novych typov

* ------------------------------

* v tomto modulu ziadne nie su

*/



/* definicie LOCALnych premenných */

LOCAL double polomer;



/* uplne funkcne prototypy LOCAL funkcii

* -------------------------------------

* v tomto module ziadne nie su

*/



/* funkcia main()

* --------------

* v tomto module

* ziadna nie je

*/



/* definicie LOCAL funkcii

* -----------------------

* v tomto module ziadne nie su

*/



/* definicie GLOBAL funkcii */



/*

* funkcia pre vstup dat

* polomer kruznice je globalna staticka premenna

*/

GLOBAL double vstup_dat(void) {

	printf("Zadaj polomer kruznice (kladne realne cislo) : ");

	scanf("%lf", &polomer);



	return(kontrola(polomer));

}



/*

* funkcia pre vystup dat

* polomer kruznice je globalna staticka premenna

* vyp_priemeru() je globalna funkcia zo suboru KRU_MAIN.H

*/

GLOBAL void vystup_dat(double obsah) {

double priemer;



printf("Obsah kruhu o polomere %6.2f je %.2f \n", polomer, obsah);



priemer = vyp_priemeru(polomer);

printf("Obvod kruhu o priemere %6.2f je %.2f \n", priemer, obvod);

}





/*

* KRU_IO.H	 ver. 1.0

*

* Hlavickovy subor pre modul KRU_IO.C

* ===================================

*

* libcosenior, maj 2012

*

*/



#ifndef KRU_IO_H_INCLUDED

#define KRU_IO_H_INCLUDED



/* definicie symbolickych konstant využivanych i v inych moduloch */

#define CHYBA_DAT	 -1.0



/* definicie makier s parametrami

* ------------------------------

* v tomto module ziadne nie su

*/



/* definicie globalnych typov

* --------------------------

* v tomto module ziadne nie su

*/



/* deklaracia globalnych premenných modulu KRU_IO.C

* ------------------------------------------------

* v tomto modulu ziadne nie su

*/



/* uplne funkcne prototypy GLOBALnych funkcii modulu KRU_IO.C */

EXPORT double vstup_dat(void);

EXPORT void vystup_dat(double obsah);



#endif // KRU_IO_H_INCLUDED





/*

* STDDFN.H		 ver. 1.0

*

* Standardne definicie

*

* libcosenior, maj 2012

*/

#ifndef STDDFN_H_INCLUDED

#define STDDFN_H_INCLUDED



#define GLOBAL /*	*/

#define LOCAL	 static

#define IMPORT	 extern

#define EXPORT	extern



#endif // STDDFN_H_INCLUDED

Skompiluj a spusti.

Prečítaj si poriadne komentáre aby si pochopil súvislosti.



Inicializácia jednoduchých premenných

Inicializácia premenných je definícia s určením počiatocnej hodnoty premennej. Tento spôsob sa využíva pomerne často, pretože skracuje a sprehľadňuje program. Inicializácia je pre všetky typy premenných a pamäťových tried syntakticky rovnaká, ale pri pozornejšom pohlade sa dajú nájsť drobné odchylky, ktoré budú popísane ďalej.
Lokálne premenné jednoduchých typov môžu byť explicitne inicializované. Pokiaľ sa tejto možnosti nevyužije, potom majú náhodne hodnoty. Výnimkou je pamäťová premenna v pamäťovej triede static, ktorá je inicializovaná na nulu. Globálne premenné sú pred začiatkom programu inicializované na nulu, ale je dobrým zvykom explicitne inicializovať tie premenné, ktoré to vyžadujú. Tento prístup zvyšuje čitateľnosť programu.
ANSI C sa líši od K&R C v tom, že akonáhle je u globálnej premennej uvedená inicializácia, potom nezáleží na predchádzajúcom kľúčovom slove extern. V nasledujúcich dvoch príkladoch je premenná f vždy definovaná s úplne rovnakým výsledkom.

float f = 1.0;

main() {
...
}
extern float f = 1.0;

main() {
...
}

Poznámka:
  • ANSI C v tomto prípade v skutočnosti rozlišuje pojmy deklarácia, predbežná definícia a skutočná definícia. Sú to jemnosti takej úrovne, že sa nie je treba nimi zaoberat.
Inicializácia automatických premenných môže byť prevedená pomocou komplexného výrazu. To znamená, že môže obsahovať premenné alebo aj funkcne volania - teda to, čo sa môže objaviť na ľavej strane priraďovacieho príkazu, napr.:
auto int c = getchar();
V podstate sa však doporučuje iba inicializácia pomocou konštantného výrazu. Globálne a statické premenné môžu byt inicializované iba pomocou konštantného výrazu, t. j. konštánt s rôznymi operátormi, alebo výraz musí byť možné vypočítať v čase prekladu, napr.:
static int i = 5 * 1024;

Čo je dobré si uvedomiť:
  • Funkcie nemôžu byť vkladané (vhniezdené).
  • Funkcia, ktorá vracia iný typ než int, musí byť aspoň deklarovaná vo funkcii, ktorá ju volá. Lepším riešením je uvedenie jej funkčného prototypu pred definíciou volajúcej funkcie. Funkčný prototyp sa uvádza buď na začiatku súboru, keď program pozostáva z jedného súboru alebo "inkludovaním" príslušného .H súboru pri oddelenom preklade súborov.
  • Používajte typ void pre označenie funkcie, ktorá nevracia hodnotu alebo nemá parametre.
  • Externé identifikátory by mali byť čo najkratšie - max. 8 znakov.
  • Externé premenné by mali byť inicializované iba raz, a to v mieste definície.
Úlohy:

1. Napíš funkciu tabulka_mocnin(double x, int n), ktorá vypíše tabuľku mocnín x od 1 do n.
Použi v programe.
Spoiler

2. Napíš funkciu troj(char c, int n), ktorá zobrazí na obrazovke trojuholník o n riadkoch zložený zo znakov,
napr.:
o
ooo
ooooo
Použi v programe.
Spoiler


3. Napíš funkciu exist(), ktorá vráti 1, ak súbor DOPIS.TXT existuje, v opačnom prípade vráti 0.
Nezabudni súbor uzavrieť.
Použi v programe.
Spoiler

4. Napíš funkciu vyskyt() s dvomi parametrami. Prvý je ukazovateľ na súbor, druhý je char. Funkcia vráti počet výskytu znaku (definovaného char) v súbore.
Použi v programe.
Spoiler

5. Napíš funkciu vypis(FILE *fr), ktorá vypíše na obrazovku obsah súboru.
Použi v programe.
Spoiler

6. Vylepši funkciu vypis() tak, aby zaisťovala stránkovanie po zaplnení obrazovky.
Použi v programe.
Spoiler

7. Napíš funkciu double vypis_subor(void), ktorá vypíše pomocou funkcie výpis() na obrazovku obsah ľubovolného súboru. Meno súboru čítaj z klávesnice a vhodne reaguj na prípad, keď súbor neexistuje.
Funkcia vypis_subor() vracia 2.2, ak je meno súboru len K alebo k.
Ak nebol súbor otvorený, vracia hodnotu 1.1, inak vracia 0.5
Spoiler

8. Napíš program, ktorý s využitím funkcie vypis_subor() bude vypisovať na obrazovku ľubovolné súbory dovtedy, kým užívateľ nezadá meno súboru ako K.
Spoiler

9. Funkcie vypis() a vypis_subor() ulož do suboru FCE.C a funkciu main() do súboru HLAVNY.C.
10. Pomocou #include pripoj súbor FCE.C do súboru HLAVNY.C.
11. Vyskúšaj funkciu programu vytvoreného v predchádzajúcich úlohách spustením súboru HLAVNY.C.

FCE.C
Spoiler

HLAVNY.C
Spoiler


12. V súbore HLAVNY.C uveď úplný funkčný prototyp funkcie vypis_subor().
HLAVNY.C
Spoiler

13. Vytvor súbor FCE.H a vlož doňho úplný funkčný prototyp funkcie vypis_subor(). Pomocou #include
zabezpeč spojenie medzi súbormi FCE.H a HLAVNY.C. Vyskúšaj funkčnosť. (projekt)

http://databaza.free...ypisSuborov.zip

14. Vytvor dva súbory A.C a B.C, ktoré budú zdieľať premennú iii. Premenná iii sa v A.C definuje
a inicializuje, v B.C sa vypíše jej hodnota. (Projekt)

http://databaza.free...ekciaZS/iii.zip

15. Zmeň predchádzajúci program tak, aby iii bola definovaná ako globálna static premenná
a vyskúšaj chovanie programu. Popíš chovanie.
Spoiler

16. Napíš program, ktorý prečíta znak z klávesnice. Ak to bude znak 'd' vtedy prečíta a vypíše celé číslo,
ak to bude 'f', spraví to isté s číslom float.
Premenné, do ktorých bude číslo uložené, definujte až vnútri bloku za if alebo else.
Spoiler

17. Dopíš tento program: (MAX skús minimálne 100000)
	for (i = 0; i < MAX; i++) {

	 for (j = 0; j < MAX; j++) {

		i = i;

		j = j;

	 }

	}

tak, že i a j budú najprv globálne int a potom lokálne register int.
Pokús sa zmerať rýchlosť obidvoch programov a napíš.
Spoiler


Spoiler
  • 0

Popis: Funkcie a práca s pamäťou

- procedúry a dátový typ void (prázdny)
- rekurzívne funkcie
- konverzia návratovej hodnoty funkcie
Oblasť platnosti identifikátorov
- globálne premenné
- lokálne premenné
Pamäťové triedy
- auto
- extern
- static
- register
- úlohy k lekcii
Poznámky: V prvej časti lekcie "Funkcie a práca s pamäťou" som bol v códe donútený použiť namiesto premennej int b, premennú int c. Teda aj vzorec na výpočet obvodu je o = 2(a + c). Je to spôsobené tým, že znaky b) vytvárali smajlíky.
Odkazy:


0 Komentárov


Najlepšie lekcie


Najnovšie pridané lekcie


Najnovšie komentáre


Najviac komentované lekcie


Najviac zobrazené lekcie


Náhodné lekcie


Na tejto stránke bolo užívateľ(ov) za posledných 30 minút

členov, návštevníkov