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

6. lekcia C (Jednorozmerné polia) základná škola


6 Jednorozmerné polia

Pole ako dátová štrukúura zložená z rovnakých prvkov, je vo všetkých programovacích jazykoch často využívaná. Jazyk C zavádza pre prácu s poľom oproti Pascalu niektoré ohraničenia - týkajúce sa predovšetkým stanovenia hraníc poľa, ale vďaka tesnej súvislosti polí a pointerov umožňuje robiť s poľami oveľa väčšie "kúzla" ako Pascal. Hneď v začiatkoch je ale nutné poznamenať, že základná práca s poľami v C je veľmi podobná Pascalu, napr. prístup k jednotlivým prvkom poľa je úplne rovnaký. Pokiaľ teda nechceme pri práci s poľami využívať pointery, nikto nás k tomu nenúti.

6.1 Základné schopnosti

Pri akejkoľvek práci s poľami v C je nutné mať neustále na pamäti, že polia v C nemajú voliteľnu dolnú hranicu tak, ako je to v Pascale. V C je dolná medza poľa vždy 0 (nula), teda pole vždy začína prvkom s indexom 0! Tento spôsob práce s poľami je zavedený kvôli zvýšeniu efektívnosti pri prístupe do poľa a taktiež kvôli spolupráci polí a pointerov.

TYP x[pocet];
Tento príkaz staticky alokuje blok pamäti pre počet objektov typu TYP, pričom rozsah indexov je od 0 do počet-1. Hodnota počet musí byť známa v čase prekladu, teda musí to byť konštantný výraz - najčastejšie sa používa symbolická konštanta - viď ďalej príklady.

Pozor:
Častou chybou, ktorú určite aspoň raz urobíte, je že si síce uvedomujete, že pole v C má nulovú dolnú hranicu, ale zabudnete na to, že index posledného prvku poľa je o jedničku menší, než je hodnota uvedená v definícii poľa!

int x[10];		/* definicia pola x o desiatich prvkoch typu int		 */

x[0] = 5;		 /* spravny pristup k prvku pola - v poradi k prvemu z x */

x[9] = 7;		 /* spravny pristup k poslednemu (desiatemu) prvku pola x */

x[10] = 1;		/* chybny pristup k jedenastemu prvku pola x			 */
x[10] nie je posledný prvok poľa, pretože pre pole bolo vyhradených iba 10 prvkov. Tento príkaz priradí hodnotu 1 do pamäti bezprostredne za poľom x.

Poznámky:

Vyššie uvedený príklad chyby je o to zradnejší, že jazyk C zásadne nekontroľuje hranice polí, teda ani dolnú ani hornú hranicu. Tieto kontroly sú totiž časovo veľmi náročne a C, ako jazyk nižšej úrovne, ich z dôvodu efektivity neuskutočňuje. Pokiaľ teda predpokladáme, že by mohla v programe nastať situácia, kde by hranice polí mohli "pretiecť" alebo "podtiecť", potom to musíme sami ošetriť.
Pretečenie alebo podtečenie hraníc je väčšinou zdrojom ťažko odhaliteľných chýb. Najčastejšie sú tzv. "chyby + 1". To sú chyby, kde index o jedničku prekračuje rozsah poľa. Z toho plynie poučenie, že keď program pracuje zle a je podozrenie na chybnú prácu s poľami, je potrebné sa najskôr zamerať na "chybu + 1".

Dobrým programátorským zvykom je vyhýbat sa kódom zavislým na inkrementácii. Často uvádzaný prípad takého kódu je:
a[i] = i++;
Príklad:

"Klasická" definícia statického poľa:
#define MAX	10

int x[MAX], y[MAX*2], z[MAX + 1];
Boli definované tri polia:
x o desiatich prvkoch s indexami od 0 do 9
y o 20-tich prvkoch s indexami od 0 do 19
z o 11-tich prvkoch s indexami od 0 do 10

Spôsob definície poľa z je často používaný, ak potrebujeme, aby horný index bola hodnota uvedená v definícii poľa a nemuseli sme pri práci s poľom stále myslieť na to, že je potrebné odpočítat jedničku.

Ak potrebujeme zaviesť nový typ poľa - čo robíme väčšinou len vtedy, keď nový typ nie je základným dátovým typom - definujeme ho nasledujúcim spôsobom:
typedef ZNAMY_TYP NOVY_TYP[pocet];
Príklad:
Definicia novych typov:
typedef int VEC2[5];

typedef char VEC3[3];

typedef float FVEC[9];
Definicia novych premennych:
VEC5 v5;

VEC3 v3;

FVEC f;
Syntax indexovanej premennej (prvku poľa) je napr.: v5[3].
Prvky poľa sú l-hodnotami, tzn. že majú adresu, ktorú je možné získať, a prvky poľa sa dajú samozrejme meniť.
( Zhruba povedané, l-hodnota je čokoľvek, čo môže byť na ľavej strane priradenia.)

Príklad:
Program zistí počet jednotlivých písmen v súbore TEXT.TXT. Malé a veľké písmena sú považované za totožné. Z dôvodu max. jednoduchosti je vynechaný test správnosti otvorenia a zavretia súboru.
#include <stdio.h>

#include <ctype.h> // pouzitie makier isalpha() a toupper()



#define POCET	('Z' - 'A' + 1) // rozsah vsetkych velkych pismen (bez diakritiky) + 1



int main(void) {

	FILE *fr;

	int c, i;

	int pole[POCET]; // pole na mnoztvo opakovani jednotlivych pismen



	for (i = 0; i < POCET; i++)

		pole[i] = 0;			// nulovanie pola - naplnenie pola nulami



	fr = fopen("TEXT.TXT", "r"); // otvorenie suboru



	while ((c = getc(fr)) != EOF) { // citanie jednostlivych znakov suboru

		if (isalpha( c )) // ak je znak pismeno

		pole[toupper( c ) - 'A']++; // v cykle zvacsuje o jedna prvok pola s indexom

								 // velke pismeno ( c ) - 'A', teda ak napr. c = 'A',

								 // 'A' - 'A' = 0, teda obsah pole[0] sa zvacsi o jedna

	}

	printf("V subore bol tento pocet jednotlivych pismen:\n");

	for (i = 0; i < POCET; i++)

		printf("%c - %d \n", i + 'A', pole[i]); // v cykle vypisuje jednotlive velke pismena

												// a k nim hodnoty z prislusnych prvkov pola



	fclose(fr);

	return 0;

}

6.2 Pole a pointery

Naše doterajšie akcie s poľami zrejmä neboli príliš prekvapujúce, pretože vyššie popísaný spôsob práce s poľom je podobný vo všetkých programovacích jazykoch. Ako už bolo povedané, tento spôsob pre prácu s poľami celkom postačuje. Ak však chceme naozaj využiť všetky možnosti, ktoré nám jazyk C dáva, je nutné preskúmať a naučiť sa vzťah medzi pointermi a poľami. Až potom totiž môžeme pracovať dobre aj s dynamickými poľami. Ak sa začina v C každé pole od 0, potom sa dá ľahko vypočítať adresa ľubovoľného prvku poľa podľa vzťahu:

&x[i] = bázova adresa x + i * sizeof(typ);
Premenná poľa - v našom prípade x - je považovaná za adresu do pamäti. Pretože pointery sú taktiež adresy do pamäti, je možné už vytušiť, ako C pracuje vnútorne s poľami. Dopredu si prezradíme, že práca s poľami pomocou indexov a pomocou pointerovej aritmetiky je zhodná, a teda že veľmi podobná je aj práca s poľami a práca s pointermi.

Ak sa ale vrátime k problémom indexovania poľa, potom výraz:
x[i]

je totožný s výrazom:

*(x + i)
pretože x + i je adresa daná súčtom bázovej adresy poľa predstaveného hodnotou x a indexu predstaveného hodnotou i. Operátor * potom umožňuje získať obsah na tejto adrese.


6.2.1 Dynamické polia

Majme definície:
TYP a_var[pocet];			 /* definicia statickeho pola */

TYP *p_var;					 /* definicia pointeru		*/
Ako a_var tak aj p_var sú pointery na typ TYP. Líšia sa ale v nasledujúcom:

a_var je konštanta (konštantný pointer) a jej hodnota sa nedá meniť. Nie je to l-hodnota. Hodnota a_var je adresa začiatku bloku pamäti alokovaného pre statické pole.
p_var je premenná s neurčenou počiatočnou hodnotou. Alokuje pamät iba pre seba (pre pointer), ale nealokuje žiadnu pamät pre pole. Je to l-hodnota.

Majme pointer na blok pamäti alokovanej pomocou funkcie malloc():
int *p_i;

p_i = (int *) malloc(4 * sizeof(int));
potom sa p_i dá považovať za dynamické pole, ktoré vzniká v čase behu programu, takže nasledujúce dvojice výrazov su celkom ekvivalentné:
p_i[0] == *p_i

p_i[1] == *(p_i + 1)

p_i[2] == *(p_i + 2)

p_i[3] == *(p_i + 3)

6.2.2 Podobnosť statických a dynamických polí

Z toho, čo už o poliach a pointeroch vieme, je možné tušiť, že práca so statickými a dynamickými poľami bude veľmi podobná. V skutočnosti je tomu tak, že rozdielny je iba spôsob definície a alokácia pamäti. Ďalšia práca je potom úplne rovnaká.

Poznámka:

Je síce skutočnosťou, že mnoho programátorov dodržuje konvenciu, že do statického poľa sa pristupuje pomocou indexov a do dynamického poľa pomocou pointerovej aritmetiky, ale to sa nedá v žiadnom prípade zovšeobecňovať ani dôrazne doporučovať. Celkom prístupný je aj opačný spôsob alebo použitie iba jedného spôsobu (indexov alebo pointerovej aritmetiky) pri obidvoch typoch polí.

Ak bude statické pole x definované ako:
int x[4];
potom:
(x + 1) == *((char *) x + sizeof(int) * 1)
ak použijeme adresný operátor & potom:
&x[i] == &*(x + i) == x + i
Majme definíciu:
int x[1], *p_x;
potom:
x je konštantný pointer a nemôže byť zmenený. Avšak *x nie je konštanta - je to obsah staticky alokovanej pamäti. Teda výraz: *x = 2; je správny, a znamená to isté, čo výraz: x[0] = 2;
Pointer p_x nie je inicializovaný, takže výraz: *p_x = 2; je správny iba syntakticky, ale schémanticky je chybný, pretože meníme obsah na neznámej adrese, ktorú sme predtým nealokovali. Takýto príkaz je možné napísat až po inicializácii p_x napr.:
p_x = (int *) malloc(sizeof(int));
Avšak je možné aj priradenie: p_x = x; kde p_x a x ukazujú na rovnaúu adresu v pamati. Opačné priradenie: x = p_x; možné nie je, pretože x je konštantný pointer.

Často sa vyskytne definícia s inicializáciou:
int x[i], *p_x = x;
Tu sa nepriraďuje adresa poľa x na nedefinovanú adresu v pamäti, ale do premennej p_x, pretože sa jedná o inicializáciu.


6.2.3 Ďalšie zvláštnosti a schopnosti pri práci s poľami

Okrem základnych zvláštností pri práci s poľami v C (zacínaju od 0, nekontroľujú sa hranice, ...), ktoré boli popísané vyššie, budú v tejto časti uvedené niektoré ďalšie.

Práca s celým poľom naraz

Tu je nutné bohužiaľ poznamenať, že pracovať s celým poľom naraz, na rozdiel od štandardného Pascalu, jazyk C neumožňuje. Operácie s celými poľami naraz sa nedajú uskutočňovať, aj keď sú polia rovnakého typu, takže:
typedef int VEC9[9];

VEC9 x, y;

x = y;			/* takto sa kopirovat neda! */

x == y;		 /* takto sa porovnavat neda! */
V týchto prípadoch je nutné použiť cyklus:
for (i = 0; i < 9; i++)

	x[i] = y[i];				 /* kopirovanie */
for (i = 0; i < 9; i++)

	if (x[i] != y[i])

		break;					 /* porovnavanie */
Prístup do poľa pomocou pointerov

Adresa poľa a pointer sa dajú porovnávať, teda ak:
double f[4], *p_f;
potom p_f ukazuje na prvok pola f, ak platí:
f <= p_f < f + 4
Rovnako je možné kedykoľvek nahradiť prechod prvkami poľa pomocou indexov a pointerov, napr.:
for (p_f = x, p_y = y; p_x < x + 9;)

	*p_x++ = *p_y++;				 /* if (*p_x++ != *p_y++) */
Poznámka:

Prístup k prvkom poľa pomocou pointerov býva obyčajne oveľa efektívnejší než prístup pomocou indexovania. V tomto prípade sa totiž pre získanie ďalšieho prvku poľa iba pripočítava konštanta - t. j. veľkosť prvku poľa - k aktuálnej adrese súčasného prvku, kdežto pri indexovaní je nutné najskôr touto konštantou vynásobiť index a výsledok potom pripočítať k bázovej adrese.

Priklad:
Program trochu modifikovaný, je posledný príklad, ktorý sme už uvádzali v predchadzajúcom texte. Je na ňom vidieť, že nie všade je použitie pointerov výhodne. Pri záverečnej tlači je síce prístup pomocou pointerov možno rýchlejší, ale získanie indexu (teda písmena) je komplikovanejšie a oveľa menej čitateľnejšie.
#include <stdio.h>

#include <ctype.h>



#define POCET ('Z' - 'A' + 1)



main() {

	FILE *fr;

	int c, *p_i;

	int pole[POCET];



	for (p_i = pole; p_i < pole + POCET; p_i++)

		*p_i = 0;			 /* nulovanie pola */



	fr = fopen("TEXT.TXT", "r");



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

		if (isalpha©)

			/* precitany znak je pismeno */

			(*(pole + toupper© - 'A'))++;

	}

	printf("V subore bol tento pocet jednotlivych pismen:\n");



	for (p_i = pole; p_i < pole + POCET; p_i++)

		printf("%c - %d \n", p_i - pole + 'A', *p_i);



	fclose(fr);

}

Ako zistiť veľkosť poľa

Tu je jedna z mála odlišností pri práci so statickým a dynamickým poľom a to v rozdielnej interpretácii operátora sizeof.

Majme dve polia definované ako:
int x[10], *p_x;

p_x = (int *) malloc(10 * sizeof(int));
Po alokácii dynamickej pamäti pomocou malloc() budú ako x, tak aj p_x považované za pointery na pole desiatich prvkov typu int, pričom x predstavuje statické pole a p_x pole dynamické. Na obidva pointery je možné použiť operátor sizeof, ktorý ale poskytne odlišné, ale správne a zdôvodniteľné výsledky:
sizeof(x) == 10 * sizeof(int)			 /* teda napr. 20				 */

sizeof(p_x) == sizeof(int *)			 /* teda napr. 4 - velkost adresy */
Poznámka:

Občas býva vhodné používať makrá na zistenie počtu prvkov poľa. Nasledujúce makro však pracuje správne iba so statickými poľami.
#define pocet(pole)	(sizeof((pole)) / sizeof(pole[0]))

6.3 Pole meniace svoju veľkosť

Občas sa v programe dostaneme do situácie, keď je potrebné väčšie pole, než sme si pôvodne mysleli. V jazyku C s využitím dynamických polí to nie je žiadny problém. Ukážeme si spôsob, ako môže pole behom výpočtu dynamicky meniť svoju veľkosť - zvačšovať sa alebo aj zmenšovať sa ("dýchať"). Výhodou tohoto spôsobu je, že sa pole nazýva stále rovnako a že sa využíva len toľko pamäti, koľko jej skutočne potrebuje.

Poznámka:

Možno si poviete, prečo pole zmenšovať? Je to opät z dôvodou šetrenia pamäťou, pretože sú programy, ktoré využívajú veľmi veľa dynamickej pamäti a potom je každý ušetrený KB dobrý.

Príklad:
Majme program, kde využívame dynamické pole o 10-tich prvkoch. Behom výpočtu sa stane, že nám počet 10-tich prvkov prestane stačiť a tak alokujeme miesto pre dalších 10 prvkov, takže pole bude mať teraz veľkosť 20 prvkov.
Táto užitočná vlastnosť sa urobí pomocou jednoduchého triku.
Vždy, keď nám nedostačuje veľkost poľa, alokujeme nové pole o 10 prvkov vačšie, pôvodné pole do neho prekopírujeme a potom toto pôvodné pole uvolníme pomocou funkcie free().
Začíname teda s poľom x.(Tu, pre zlepšenie názornosti operácií s indexami, porušíme zásadu, že identifikátor pointeru začína pomocou p_. Pre jednoduchosť tu taktiež netestujeme úspesnosť pridelenia pamäti)

int *x,									 /* pole			 */

	pocet = 10,							 /* velkost pola	 */

	*p_pom1, *p_pom2, *p_nove;			 /* pomocne pointery */

	x = (int *) malloc(pocet * sizeof(int));
Teraz normálne pracujeme s poľom x, napr.:
x[5] = 3;
V tejto chvíli prestáva stačiť veľkosť poľa x a chceme ho zväčšit o ďalších desať prvkov.
p_nove = (int *) malloc((pocet + 10) * sizeof(int));

p_pom1 = x; p_pom2 = p_nove;

while (p_pom1 < x + pocet)				 /* kopirovanie stareho pola */

*p_pom2++ = *p_pom1++;					/* na novu adresu		 */

pocet += 10;

free((void *) x);							/* uvolnenie stareho pola */

x = p_nove;
Poznámky:

Prekopírovanie starého poľa je časovo náročné, takže než sa do tejto stratégie pustíme, je nutné sa rozhodnúť, či potrebujeme skôr rýchlosť alebo šetrenie pamäťou, poprípade urobiť vhodný kompromis medzi nimi, čo znamená toto pole používať, ale nemeniť jeho veľkosť príliš často.
Štandardná knižnica taktiež pamätá na možnosť práce s poľom o premennej dĺžke. Dáva nám k dispozícii funkciu realloc(). Tá je popísana v hlavičkovom súbore stdlib.h takto:
void *realloc(void *pole, unsigned int size);
kde pole je pointer na už skôr alokovanú oblasť pamäti a size je počet Bytov novo požadovaného pola. realloc() upravuje veľkosť alokovanej pamäti na hodnotu size - pri zmenšovaní, alebo alokuje inú väčšiu oblasť pamäti a pôvodnä pamäť do nej prekopíruje - pri zväčšovaní - a potom uvoľní. realloc() vracia pointer na novo alokovanú pamät alebo nulový pointer NULL, pokiaľ sa nedá pole alokovať.


6.4 Pole ako parameter funkcie

Pole môže byť samozrejme parametrom funkcie. Skutočný parameter je potom do funkcie predávaný odkazom, tzn. predá sa adresa začiatku poľa pomocou pointeru, t. j. pomocou mena poľa. To má samozrejme význam v tom, že položky poľa môžu byť vo funkcii menené a túto zmenu si ponechajú aj po opustení funkcie.

Príklad:
Nasledujúca funkcia nájde najväčší prvok z poľa o ROZSAH prvkoch.
double maxim(double pole[]) {

	double *p_max = pole, *p_pom;



	for (p_pom = pole + 1; p_pom < pole + ROZSAH ; p_pom++) {

		if (*p_pom > *p_max)

			p_max = p_pom;				 /* zmena pointeru na max. prvok */

	}



	return(*p_max);

}
Pole ako formálny parameter je špecifikované ako identifikátor nasledovaný prázdnymi zátvorkami "[]" alebo: double pole[] pričom je nutné poznamenať, že ten istý význam by mala aj špecifikácia pomocou pointeru: double *pole;

Poznamka:

Prvému spôsobu sa však dáva často prednosť, pretože je potom jasnejšie, že sa jedná o pole typu double a nie o pointer na double.


Volanie funkcie by bolo napr.:
max = maxim[pole_a];
kde skutočný parameter pole_a hovorí iba: "od symbolickej adresy pole_a začína pole s prvkami typu double.

Z vyššie uvedeného vyplýva fakt, že pokiaľ je vo funkcii pracujúcej s poľom nutné poznať jeho veľkosť, ktorá nie je konštantná, potom sa táto veľkosť musí odovzdať ako další formálny parameter. Z obyčajného skutočného parametru mena poľa nie je prekladač totiž schopný veľkosť poľa zistiť. Hlavička funkcie by v tomto prípade vyzerala napr. takto:
double maxim[double pole[], int pocet)
Telo funkcie by bolo vcelku rovnaké, len nový formálny parameter počet by v cykle for nahradil symbolickú konštantu ROZSAH.
Ako totiž už bolo povedané skôr:
sizeof(pole)					/* vracia velkost pointeru na typ double */

sizeof(*pole)				 /* vracia velkost premennej typu double */

sizeof(double)				 /* vracia velkost typu double			*/

sizeof(double *)				/* vracia velkost pointeru na typ double */
Prakticky to znamená, že pole ako skutočný parameter stráca vo funkcii status poľa a jeho veľkosť - nech už sa jedná o statické alebo dynamické pole - sa nedá vo funkcii nijako zistiť.

Poznámka:

Častý, ale chybný, pokus o predanie poľa a súčasne jeho dĺžky ako jediného formálneho parametra je:
double maxim(double pole[10])
Hodnota 10 tu nemá žiadny význam a prekladač ju ignoruje. Formálny parameter pole bude aj naďalej považovaný za:
double pole[]
Ďalšou dôležitou odlišnosťou formálneho parametra poľa od skutočného poľa je to, že formálny parameter nie je konštanta, ale je to l-hodnota. To znamená, že ho je možne vo funkcii zmeniť, pretože je vytvorená jeho lokálna kópia v stacku (zásobníku), pričom nezáleží na tom, či bude skutočný parameter statické alebo dynamické pole.

Príklad:
Nasledujúci program ukazuje využitie skôr definovanej funkcie maxim().
#include <stdio.h>

#define POCET	10



/* funkcny prototyp funkcie maxim() je nutny - maxim() vracia typ double */

double maxim(double pole[], int pocet);



main() {

	double f[POCET];

	int i;



	for (i = 0; i < POCET; i++) {

		printf("Zadaj %d. cislo : ", i + 1);

		scanf("%lf", &f[i]);

	}



	printf("Max. z %d cislic je %f \n",POCET, maxim(f, POCET));

}
Poznámka:

Z faktu, že sa funkcii predáva pomocou skutočného parametra iba adresa začiatku poľa, vyplýva, že je možné veľmi ľahko funkciu použiť aj pre prácu s úsekmi tohoto poľa. Pokiaľ by sme si niekedy priali nájsť maximum iba z výseku poľa, potom je možné len jednoducho určiť hranice, napr. pre maximum z tretieho až siedmeno prvku poľa je volanie:
max = maxim(f + 2, 5);
alebo:
max = maxim(&f[2], 5);
Priklad:
Ak prepišeme funkciu maxim() ako procedúru maxim(), potom je nutné maximálnu hodnotu vrátiť pomocou jedného z parametrov, ktorý musí byť samozrejme volaný odkazom.
void maxim(double pole[], int pocet, double *p_max) {

	double *p_pom;



	*p_max = pole[0];

	for (p_pom = pole + 1; p_pom < pole + pocet; p_pom++) {

		if (*p_pom > *p_max)

			*p_max = *p_pom;		 /* zmena hodnoty na adrese p_max */

	}

}
Tu je pointer p_max formálny parameter volaný odkazom, takže sa do funkcie maxim() odovzdáva adresa premennej, na ktorú maxim() uloží nájdenú hodnotu najväčšieho prvku poľa. Častou chybou je vo funkcii maxim() priradenie:
p_max = p_pom;		/* namiesto *p_max = *p_pom */
Potom by sa totiž stratila adresa premennej, do ktorej má byť uložená hodnota maximálneho prvku poľa. Nič by sa síce nezničilo, pretože sa iba v stacku prepíše lokálna kópia adresy skutočného parametra. Pretože táto lokálna kópia zaniká po opustení funkcie, nič ďalšieho sa nestane, a teda procedúra maxim() nebude mať žiadny účinok.

Hlavná funkcia by potom pre procedúru maxim() vyzerala nasledovne:
main() {

	double f[POCET], max;

	int i;



	for (i = 0; i < POCET; i++) {

		printf("Zadaj %d. cislo : ", i + 1);

		scanf("%lf", &f[i]);

	}



	maxim(f, POCET, &max);

	printf("Maximum z %d cislic je %f \n", POCET, max);

}
Príklad:
Program ukazuje ďalšiu častú chybu s odovzdávaním adresy neexistujúceho poľa. Funkcia init() načíta do poľa 5 double čísiel a vráti adresu tohoto poľa.
void init(double **p_f) {

	double a[5];

	int i;



	for (i = 0; i < 5; i++) {

		printf("Zadaj %d. cislo : ", i + 1);

		scanf("%lf", &a[i]);

	}



	*p_f = a;

}



main() {

double *p_dbl;



init(&p_dbl);

...

}
Volanie init() spôsobí, že pointer p_dbl bude skutočne ukazovať na pole piatich double čísiel načítaných z klávesnice. Problém je však v tom, že toto pole bolo vytvorené v stacku po vstupe do funkcie init() a tento stack je po návrate z funkcie init() vratený späť systému. Pointer p_dbl teda ukazuje na blok pamäti, ktorý už nám nepatrí a ktorý môže byť kdekoľvek legálne prepísaný.
Pokiaľ by sme použili tento nepríliš šťastný spôsob riešenia, potom by bolo nutné vo funkcii init() alokovať pole a[] pomocou funkcie malloc() - teda trvalo.


6.5 Pole pointerov na funkcie

Tak, ako môže byť pole zložené z jednoduchých premenných, môžu byť prvky poľa aj pointery. Pokiaľ sú to pointery opäť na jednoduché premenné, potom sa väčšinou jedná o viacrozmerné polia, ktoré budú preberané v ďalšej lekcii. Zvláštnym a občas využívaným poľom pointerov je pole pointerov na funkcie. Všetky funkcie musia byt samozrejme rovnakého typu a pole pointerov na ne sa definuje takto:
typedef void (* P_FCE) (); /* definicia pointeru na funkciu vracajucu typ void */

P_FCE funkcia[10];		 /* definicia pola 10-tich pointerov
*/

Toto pole je potom nutné naplniť adresami existujúcich funkcií, čo sa robí úplne rovnako ako pri priraďovaní adresy funkcie do pointeru na funkciu.
Možná praktická aplikácia poľa pointerov na funkcie, je program riadený pomocou menu.
Adresy jednotlivých funkcií uskutočňujúcich príslušné príkazy menu sú uložené v poli a odtiaľ môžu byť priamo volané pomocou indexu. Ak využijeme predchádzajúce definície nového typu P_FCE potom je možné definovať pole pointerov na funkcie vrátane jeho inicializácie.
P_FCE funkcie[] = {file, edit, search, compile, run};
kde identifikátory file, edit, search, ... sú názvy jednotlivých funkcií. Volanie funkcie je potom:
(* funkcia[1]) ();
alebo len v ANSI.C:
funkcia[1] ();
V našom prípade by bolo pravdepodobne ešte definované pole prístupových znakov, v ktorom by sa hľadal príslušný jednopísmenový príkaz, a podľa jeho indexu by sa volala pomocou toho istého indexu aji príslušná funkcia. Takto by vyzerala definícia poľa prístupových znakov vrátane inicializácie:
char prikaz[] = {'F', 'E', 'S', 'C', 'R'};
Poznámky:

Toto je jeden zo spôsobov inicializácie reťazca. Tento inicializačný príkaz je v tomto prípade prehľadnejší než obvyklá inicializácia:
char prikaz[] = {"FESCR"}
Asi už vás správne napadlo, že by sa tento problém dal taktiež - a pravdepodobne jednoduchšie a prehľadnejšie - riešiť pomocou prepínača switch bez akéhokoľvek použitia pointerov na funkcie.
To je pravda, ale možno, že sa niekedy stretnete s problémom, ked sa vám bude pole pointerov na funkcie zdať najvhodnejšie.


6.6 Ako čítať komplikované definície - II

V lekcii o pointeroch sme sa dozvedeli, akým spôsobom môžeme prečítat ľubovolne komplikovanú definíciu. Vtedy sme ale ešte nepoznali pole, ktore do definicii vnáša dalšie komplikácie. Avšak nie je treba sa ich obávať. Postup čítania, ktorý už poznáme, platí úplne rovnako aj pre pole, len je potrebné pole do tohto čítania začleniť.

Príklady zápisov definícií pomocou pointerov a polí.
double (*f[]) ();	 /* f je pole pointerov na funkcie vracajuce typ double	 */

double (*f()) [];	 /* f je funkcia vracajuca pointer na pole prvkov typu double */

double *(f[]) ();	 /* v C toto neexistuje									 */

double *f() [];	 /* v C toto neexistuje									 */
Pre ukážku, ako sa tieto zápisy čítaju, budeme skúmať uvedený príklad definície fukcie vracajúci pointer na pole prvkov typu double.
double (* f()) [];
Postupujeme nasledujúcim spôsobom:

V spleti okrúhlych zátvoriek, hranatých zatvoriek a hviezdičiek sa nájde identifikátor, teda 'f' a povieme: f je...
Od neho sa číta doprava, pokiaľ nenarazíme na prázdne okrúhle zátvorky "()" a pridáme: ... funkcia vracajúca ...
Čítame ďalej doprava a pravá okrúhla zátvorka nás vracia doľava až na zodpovedajúcu ľavú okrúhlu zátvorku "(" a od nej sa číta zase doprava, teda "*" a pridáme: ... pointer na ...
Preskakujeme meno premennej, prázdne okrúhle zátvorky a pravú okrúhlu zátvorku ")", ktoré nám už poslúžili, a čítame stále doprava, pokiaľ nenarazíme na pravú okrúhlu zátvorku ")" alebo na bodkočiarku ";", ktoré nás vždy vrátia doľava. Prečítajme teda prázdne hranaté zatvorky "[]" a pridáme: ... pole prvkov typu ...
Čítajme stále doprava až nás ukončovacia bodkočiarka vráti doľava pred už spracovanú "(". Teraz čítajme opačne - doľava - teda "double" a dodáme: ... double a sme hotoví.

Výsledok prieskumu teda dohromady znie: f je funkcia vracajúca pointer na pole prvkov typu double

Časté chyby
int b[3];

b[3] = 5;	 /* rozsah b je od 0 do 2									 */



int b[3];

b = 5;		 /* b je konstatny pointer, ktory sa nesmie menit			 */



int b[3];

x = &b;		/* b nie je l-hodnota (b predstavuje priamo adresu)		 */



f (float b[]) {

	...

	sizeof(b ) /* sizeof nevrati rozmer pola b, ale velkost pointeru na float */

}
Čo je dobré si uvedomiť

Identifikátor poľa je konštantný pointer.
Identifikátor poľa ako formálny parameter je premenný pointer.
Ak je x jednorozmerné pole a ak je predávané ako parameter, potom je deklarácia v halvičke funkcie: int x[] alebo int *x.


Úlohy:

1. Zisti počet jednotlivých písmen v súbore TEXT.TXT, Zistené počty vypíš číslom, ale aj pomocou histogramu, napr.:
A:   5 *****
B:  10 **********
Spoiler

2. Napíš funkciu void ahoj(void), ktorá vytlačí na obrazovku číslo a slovo ahoj, pričom číslo bude poradové číslo volania funkcie ahoj().
Ďalej definuj premennú p_ahoj ako pointer na funkciu ahoj(). V cykle volaj funkciu ahoj() pomocou pointeru p_ahoj.
Spoiler

3. V súbore POZDRAVY.C napíš niekoľko funkcií typu void ahoj(void), ktoré vytlačia vždy jeden príslušný pozdrav (ahoj, nazdar, servus, cauko, ...).
V súbore HLAVNY.C definuj pole pointerov na tieto funkcie a inicializuj ho adresami týchto funkcií. Ďalej definuj pole znakov, ktoré inicializuj počiatočnými (alebo prístupovými) znakmi jednotlivých pozdravov. Vytvor program, ktorý bude čítať znaky z klávesnice, rozpozná ich a vytlačí príslušný pozdrav pomocou pointeru na funkciu.
Nepoužívaj prepínač switch!
Spoiler

Spoiler

4. Napíš program, ktorý zistí počet jednotlivých písmen v každom riadku súboru TEXT.TXT. Výsledky vytlač v prehľadnej tabuľke, kde vo vodorovnom smere budú písmená (malé a veľké nerozlišuj) a v zvislom smere čísla riadkov.
Spracuj len prvých 20 riadkov súboru.
Spoiler

5. Napíš funkciu void zorad(int x[], int y[], int pocet), ktorá zoradí pole x podľa veľkosti vzostupne do poľa y.
Spoiler

6. Napíš funkciu int parne(int x[], int y[], int pocet), ktorá skopíruje z poľa x do poľa y len párne prvky a vráti počet prvkov pola y.
Spoiler
  • 0

Popis: Jednorozmerné polia

- Základné schopnosti
- Pole a pointery
- Dynamické polia
- Podobnosť statických a dynamických polí
- Ďalšie zvláštnosti a schopnosti pri práci s poľami
- Pole meniace svoju veľkosť
- Pole ako parameter funkcie
- Pole pointerov na funkcie
- Ako čítať komplikované definície - II
- Úlohy k lekcii
Poznámky:
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