KURZUS: Programozás alapjai

MODUL: III. modul

10. lecke: Mutatók 2/2

Ebben a leckében folytatjuk a mutatókkal kapcsolatos tudnivalók megismerését. Készítünk többdimenziós tömböket, megnézzük, hogyan adhatjuk ezeket át függvényeknek, és hogyan kezelhetjük őket mutatók segítségével. A kódmutatók használatáról is ejtünk szót. Közben olyan területekre is kitérünk, mint a véletlenszámok generálása, a dinamikus memóriakezelés, a parancssori paraméterek és környezeti változók használata. Elmélyítjük a típusdefiníciókkal kapcsolatos ismereteinket, hogy összetett, mutatókkal kapcsolatos típusokat áttekinthetőbben tudjunk definiálni.

A lecke végére a hallgatónak tisztában kell lennie

  • A többdimenziós tömbök deklarálásának, definiálásának, inicializálásának módjával.
  • A véletlenszám generálás mikéntjével.
  • A dinamikus memóriakezelés módjával.
  • Hogyan kell tömböket függvénynek átadni, azt felhasználni, visszaadni.
  • A parancssori paraméterek és környezeti változók tartalmának olvasásával.
  • A programleállítás különféle lehetőségeivel.
  • Kódmutatók és azok tömbjei készítésével, használatával.
  • A typedef használatával.

A lecke kulcsfogalmai: többdimenziós tömb, véletlenszám generátor, elsődleges adatterület, verem, heap, dinamikus memóriakezelés, parancssori paraméter, környezeti változó, kilépési állapot, kódmutató.

Többdimenziós tömbök

A többdimenziós tömböket a tömb típus tömbjeiként konstruálja meg a fordító. A deklaráció

típus azonosító[<méret1>][méret2]. . .[méretN] <={inicializátorlista}>;

alakú. Az elhelyezésről egyelőre annyit, hogy sorfolytonos, azaz a jobbra álló index változik gyorsabban. Például a

double matrix[2][3];

sorfolytonosan a következő sorrendben helyezkedik el a tárban:

matrix[0][0], matrix[0][1], matrix[0][2], matrix[1][0], matrix[1][1], matrix[1][2]

Egy elem elérése például

azonosító[index1][index2]. . .[indexN]

módon megy, ahol az indexekre igazak a következők:

0 <= index1 < méret1
0 <= index2 < méret2
. . .
0 <= indexN < méretN

A tömbök inicializálásáról korábban mondottak most is érvényben vannak, de a többdimenziós tömb további tömbökből áll, s így az inicializálási szabályok is rekurzívan alkalmazandók. Az inicializátorlista egymásba ágyazott, egymástól vesszővel elválasztott inicializátorok és inicializátorlisták sorozata a sorfolytonos elhelyezési rend betartásával. Például a 4x3-as t tömb első sorának minden eleme 1 értékű, második sorának minden eleme 2 értékű, s így tovább.

int t[4][3]={{1, 1, 1}, {2, 2, 2}, {3, 3, 3}, {4, 4, 4}};

Ha az inicializátorlistában nincs beágyazott inicializátorlista, akkor az ott felsorolt értékek az alaggregátumok, s azon belül az elemek sorrendjében veszik fel az aggregátum elemei. Az előző példával azonos eredményre vezetne a következő inicializálás is:

int t[4][3] = {1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4};

Kapcsos zárójelek akár az egyes inicializátorok köré is tehetők, de ha a fordítót nem kívánjuk "becsapni", akkor célszerű őket az aggregátum szerkezetét pontosan követve használni! Az

int t[4][3]={{1, 1, 1}, {2, 2, 2}, {3, 3, 3}};

hatására a t mátrix utolsó sorára (a t[3] tömbre) nem jut inicializátor, így a t[3][0], t[3][1] és t[3][2] elemek mind zérus kezdőértéket kapnak. Az

int t[4][3]={{1}, {2}, {3}};

eredményeként a t[0][0] 1, a t[1][0] 2, a t[2][0] 3 lesz, és a tömb összes többi eleme zérus.

Tudjuk, hogyha nem adjuk meg, akkor az inicializátorlista elemszámából állapítja meg a tömbméretet a fordító. Többdimenziós tömb deklarációjában ez azonban csak az első méretre vonatkozik, a további méreteket mind kötelező előírni.

int a[][]={{1, 1}, {2, 2}, {3, 3}};           /* HIBÁS */
int t[][3]={{1, 1, 1}, {2, 2, 2}, {3, 3, 3}}; /* OK */

Folytassuk tovább a pelda23.c- ben megkezdett magyar kártyás példánkat! Újabb programunknak legyen az a feladata, hogy megkeveri a paklit, s kioszt öt lapot! Aztán újra kioszt öt lapot, s így tovább mindaddig, míg a kártya el nem fogy. Újabb keverés következik és újabb osztások. A szoftvernek akkor van vége, ha már nem kérnek több lapot.

Azt, hogy milyen kártyákat osztott már ki a pakliból, statikus élettartamú, csak a kartya.c forrásfájlban elérhető, kétdimenziós, SZINDB* KTYDB-s, kty tömbben tartja nyilván a program. A megfelelő tömbelem zérus, ha még pakliban van a lap, és 1-gyé válik, ha kiosztják. A statikus, a kartya.c forrásmodulra ugyancsak lokális, ktydb változóban a pakli aktuális kártyaszámát őrzi a szoftver.

A következő sorokkal mindig a kartya.c bővítendő!

static int kty[SZINDB][KTYDB];
static int ktydb=SZINDB*KTYDB;

Az UjPakli függvény ugyanezt az állapotot állítja elő.

void UjPakli(void){
  int i, j;
  for(i=0; i<SZINDB; ++i)
    for(j=0; j<KTYDB; ++j) kty[i][j]=0;
  ktydb=SZINDB*KTYDB; }

A Mennyi rutinnal lekérdezhető, hogy még hány kártya van a pakliban.

int Mennyi(void){
  return ktydb; }

Az int UjLap(void) függvény egy lapot ad a pakliból, azonban ezt véletlenszerűen teszi a következő technikával:

  • Ha nincs kártya a pakliban, -1-et szolgáltat.
  • Ha egyetlen lapból áll a pakli, akkor azt adja.
  • Ha ktydb<KTYDB, akkor előállít egy 1 és ktydb közti véletlenszámot. Végigjárja a paklit, és visszaadja a véletlenszámadik, még nem kiosztott kártyát.
  • Ha ktydb>=KTYDB, akkor véletlen színt és véletlen kártyát választ. Kiadja a lapot, ha még eddig nem osztotta ki. Ha a kártya már nincs a pakliban, újat választ, és így tovább.

Látszik, hogy szükségünk lesz véletlenszám generátorra!

Véletlenszám generátor

Használatához az stdlib.h fejfájlt kell bekapcsolni. Egész számok pszeudóvéletlen sorozatát generálja közel egyenletes eloszlással 0 és RAND_MAX között az

int rand(void);

függvény. A rutinnak nincs hibás visszatérése. A véletlenszám generálás kezdőértékét lehet beállítani a

void srand(unsigned int kezd);

függvénnyel. Az alapértelmezett induló érték 1, így ilyen értékű kezd paraméterrel mindig újrainicializáltathatjuk a véletlenszám generátort, azaz a rand hívások ugyanazt a véletlenszám sorozatot produkálják srand(1) után, mintha mindenféle srand megidézés nélkül használtuk volna a rand-ot.

Másképp fogalmazva, a rand nem véletlen módon, hanem nagyon is determinisztikusan ad vissza hívásonként egy-egy számot egy véges sorozat egymást követő tagjai közül. Az srand azt szabályozza, hogy melyik tagtól kezdjük el felhasználni a rendelkezésre álló számokat. Ha a sorozat tagjai elfogytak, akkor a rand elölről kezdve újra felhasználja a számokat. Nyilvánvaló, hogy a rand által visszaadott értékek csak akkor fognak véletlenszerűnek hatni, ha az srand is véletlenszerű paramétert kap, lehetőleg a program futása során egyetlen alkalommal, az első rand hívást megelőzően.

Az srand-ot a véletlenszerű indulást is biztosítandó rendszerint a time.h-ban helyet foglaló

time_t time(time_t *timer);

függvény kezd paraméterrel szokták meghívni. A time rutin az aktuális rendszer időt 1970. január elseje éjfél óta eltelt másodpercek számában, time_t (long) típusban szolgáltatja. Nincs hibás visszatérés. A visszatérési értéket a timer címen is elhelyezi, ha a paraméter nem NULL mutató. NULL mutató aktuális paraméter esetén viszont nem tesz ilyet. Az srand függvény szokásos hívása tehát:

srand(time(NULL))

Véletlenszám generátorral kockadobás értéket a következőképp produkálhatunk:

rand()%6 + 1

Ha a fejlesztő rendszerben nincs lebegőpontos véletlenszámot generáló függvény, akkor 0 és 1 közötti véletlenszámokat a következő kifejezéssel állíthatunk elő:

(double)rand()/RAND_MAX

Folytassuk az UjLap függvényt!

int UjLap(void){
  int i, j, db;
  if(ktydb==SZINDB*KTYDB) srand(time(NULL));
  if(ktydb){
    if(ktydb>=KTYDB)
      while(kty[i=rand()%SZINDB][j=rand()%KTYDB]);
    else{
      db=ktydb>1?rand()%ktydb+1:1;
      for(i=0; i<SZINDB; ++i){
        for(j=0; db&&j<KTYDB; ++j)
          if(!kty[i][j]&&!(--db)) break;
        if(!db) break; } }
    --ktydb;
    kty[i][j]=1;
    return i*OSZTO+j; }
  else return (-1); }

Elkészültünk a kibővített kartya.c-vel, s most írjuk meg a működtető pelda24.c programot!

/* PELDA24.C: Ot lap osztasa. */
#include <stdio.h>
#include <ctype.h>
#include "kartya.h"
#define LAPDB 5
void main(void) {
  int i, c;
  printf("Zsugazas: %d lap leosztasa:\n", LAPDB);
  while(printf("Ossza mar (I/N)? "),
        (c=toupper(getchar()))!='N') {
    if(c=='I') {
      putchar('\n');
      if(Mennyi()<LAPDB) UjPakli();
      for(i=0; i<LAPDB; ++i)
        printf("%-15s", KartyaNev(UjLap()));
      printf("\n\n"); }
    while(c!=EOF&&c!='\n') c=getchar(); } }
Dinamikus memóriakezelés

A C a memóriát három részre osztja. Az elsődleges adatterületen helyezi el a fordító a konstansokat és a statikus objektumokat. A lokális objektumokat és a függvényparamétereket a verembe (stack) teszi. A harmadik memóriaterületet - nevezzük heap-nek, bár más névvel is szokták illetni - futás közben éri el a C program, és változó méretű memória blokkok dinamikus allokálására való. Például fák, listák, tömbök vagy bármi más helyezhető el rajta. Az ismertetett, ANSI szabványos függvények prototípusai az stdlib.h fejfájlban találhatók.

void *calloc(size_t tetelek, size_t meret);

A calloc tetelek*meret méretű memória blokkot foglal, feltölti 0x00-val és visszaadja kezdőcímét. Tulajdonképpen tetelek elemszámú tömbnek foglal helyet, ahol egy elem mérete meret bájt.

Ha nincs elég hely, vagy a tetelek*meret szorzat értéke zérus, NULL mutatót kapunk vissza.

void *malloc(size_t meret);

A malloc legalább meret bájtos memória blokkot foglal, nem tölti fel semmivel és visszaadja kezdőcímét. A blokk nagyobb lehet meret bájtnál a tárillesztéshez igényelt, plusz terület és a karbantartási információ elhelyezése miatt.

Ha nincs elég hely a heap-en, NULL mutatót kapunk vissza a függvénytől. Ha a meret 0, a malloc zérusméretű blokkot allokál, és érvényes mutatót ad vissza erre a területre.

A calloc-kal, a malloc-kal lefoglalt, vagy a rögtön ismertetendő realloc-kal újrafoglalt terület tárillesztése olyan, hogy bármilyen típusú objektum elhelyezésére alkalmas. A függvényektől visszakapott cím explicit típusmódosító szerkezettel bármilyen típusúvá átalakítható.

Tegyük fel, hogy a program futása közben derül ki egy double tömb mérete! Ezt az értéket az int típusú, N változó tartalmazza. Hogyan lehet létrehozni, kezelni, s végül felszabadítani egy ilyen tömböt?

/* . . . */
int N;
double *dtomb;
/* . . . */
/* Itt kiderül, hogy mennyi N. */
/* . . . */
/* Ettől kezdve szükség van a tömbre. */
if((dtomb=(double *)malloc(N*sizeof(double)))!=NULL){
  /* Sikeres a memóriafoglalás, azaz használható a tömb.
    Például a 6. eleme dtomb[5] módon is elérhető. */
  /* . . . */
  /* Nincs szükség a továbbiakban a tömbre. */
  free(dtomb);
  /* . . . */ }
else /* Hibakezelés. */
/* . . . */

void *realloc(void *blokk, size_t meret);

A realloc meret méretűre szűkíti vagy bővíti a korábban malloc, calloc vagy realloc hívással allokált memória blokkot, melynek kezdőcímét megkapja a blokk paraméterben. Sikeres esetben visszaadja az átméretezett memória blokk kezdőcímét. Ez a cím nem feltétlenül egyezik meg a blokk paraméterben átadottal. Címeltérés esetén a függvény a korábbi memória blokk tartalmát átmozgatja az újba. Az esetleges rövidüléstől eltekintve elmondható, hogy az új blokk megőrzi a régi tartalmát.

Ha az újraallokálás sikertelen memória hiány miatt, ugyancsak NULL mutatót kapunk, de az eredeti blokk változatlan marad.

void free(void *blokk);

A free deallokálja (felszabadítja) a korábban malloc, calloc vagy realloc hívással allokált memóriaterületet, melynek kezdőcímét megkapja a blokk paraméterben. A felszabadított bájtok száma egyezik az allokációkor (vagy a realloc esetén) igényelttel. Ha a blokk NULL, a mutatót elhagyja a free, és rögtön visszatér.

Az érvénytelen mutatós (nem calloc, malloc, vagy realloc függvénnyel foglalt memória terület címének átadása) felszabadítási kísérlet befolyásolhatja a rákövetkező allokációs kéréseket, és fatális hibát is okozhat.

A többdimenziós tömbök belső szerkezetének megértéséhez hozzunk létre dinamikusan egy tömböt, a példában most egy mátrixot!

/* PELDA25.C: Ketdimenzios tomb letrehozasa dinamikusan. */
#include <stdio.h>
#include <stdlib.h>
typedef long double TIPUS;
typedef TIPUS **OBJEKTUM;
int m=3, n=5; /* Sorok es oszlopok szama. */
int main(void) {
  OBJEKTUM matrix;
  int i, j;
  printf("%d*%d-s, ketdimenzios tomb letrehozasa "
        "dinamikusan.\n", m, n);
  /* A sorok letrehozasa: */
  if(!(matrix=(OBJEKTUM)calloc(m, sizeof(TIPUS *)))) {
    printf("Letrehozhatatlanok a matrix sorai!\n");
    return 1; }
  /* Az oszlopok letrehozasa: */
  for(i = 0; i < m; ++i)
    if(!(matrix[i]=(TIPUS *)malloc(n*sizeof(TIPUS)))) {
      printf("Letrehozhatatlan a matrix %d. sora!\n", i);
      while(--i>=0) free(matrix[i]);
      free(matrix);
      return 1; }
  /* Mesterseges inicializalas: */
  for(i = 0; i < m; ++i)
    for(j = 0; j < n; ++j)
      matrix[i][j] = rand();
  /* Kiiras: */
  printf("A matrix tartalma:\n");
  for(i = 0; i < m; ++i) {
    for(j = 0; j < n; ++j)
      printf("%10.0Lf", matrix[i][j]);
    printf("\n"); }
  /* Az oszlopok felszabaditasa: */
  for(i = m-1; i >= 0; --i) free(matrix[i]);
  /* Sorok felszabaditasa: */
  free(matrix);
  return 0; }

Vegyük észre, hogy a main-nek van visszaadott értéke, mely zérus, ha minden rendben megy, és 1, ha memóriafoglalási probléma lép fel! Figyeljük meg azt is, hogy a memória felszabadítása foglalásával éppen ellenkező sorrendben történik, hogy a heap-en ne maradjanak foglalt "szigetek"! A heap C konstrukció, s ha a program befejeződik, akkor maga is felszabadul, megszűnik létezni.

Összesítve: A matrix azonosító a tömb kezdetére mutató, változó mutató. A tömb kezdete viszont m változó mutatóból álló mutatótömb. A mutatótömb egy-egy eleme n elemű, long double típusú tömb kezdetére mutat. A példa a részek memóriabeli elhelyezkedését is szemlélteti, azaz:

  • előbb az m elemű mutatótömböt allokáljuk, aztán
  • a mátrix első sora (0-s indexű) n elemének foglalunk helyet.
  • A mátrix második sora (1-s indexű) n elemének helyfoglalása következik.
  • . . .
  • Legvégül a mátrix utolsó (m-1 indexű) sorának n elemét helyezzük el a memóriában.

32 bites címeket feltételezve, 1000-ről indulva, m=3 és n=5 esetén, a matrix a következőképpen helyezkedhet el a memóriában:

Háromdimenziós tömböt úgy valósíthatunk meg, hogy létrehozunk előbb egy mutatótömböt, melynek mindenegyes eleme egy, az előzőekben ismertetett szerkezetű mátrixra mutat.

Néhány szó még a többszörösen alkalmazott index operátorról! A kifejezés1[kifejezés2][kifejezés3]...-ban az index operátorok balról jobbra kötnek, így a fordító először a legbaloldalibb kifejezés1[kifejezés2] kifejezést értékeli ki. A született mutató értékhez aztán hozzáadva kifejezés3 értékét új mutató kifejezést képez, s ezt a folyamatot a legjobboldalibb index kifejezés összegzéséig végzi. Ha a végső mutató érték nem tömb típust címez, akkor az indirekció művelete következik. Tehát például:

matrix[2][3]  (*(matrix+2))[3]  *(*(matrix+2)+3)

A fordító hasonló módszerrel dolgozik, de az általa létrehozott mátrixszal kapcsolatban az összes mutató - ideértve a mátrix azonosítóját is - konstans.

A többdimenziós tömbök többféleképpen is szemlélhetők. A fordító által létrehozott mátrix (ú.n. statikus tömb) példájánál maradva:

long double matrix[m][n];
  • A matrix egy m elemű vektor (tömb) azonosítója. E vektor minden eleme egy n elemű, long double típusú vektor.
  • A matrix[i] (i = 0, 1, ..., m-1) az i-edik, n long double elemű vektor azonosítója.
  • A matrix[i][j] (i = 0, 1, ..., m-1 és j = 0, 1, ..., n-1) a mátrix egy long double típusú eleme.

A dolgokat más oldalról tekintve!

  • A matrix konstans mutató, mely a mátrix kezdőcímét - tehát matrix[0], n darab long double elemből álló tömb kezdőcímét - tartalmazza.
  • A matrix+i a matrix[i], n darab long double elemű tömb címe. A matrix+imatrix+i+1-től épp n darab long double típusú változó méretével tér el.
  • A matrix+i cím tartalma ugye *(matrix+i) vagy matrix[i].
  • A matrix[i] tehát az i-edik, n darab long double elemből álló tömb azonosítója: konstans mutató, mely az i-edik, n long double elemű tömb kezdetére mutat.
  • A matrix[i]+j vagy *(matrix+i)+j az i-edik, n long double elemű tömb j-edik elemének címe. E cím tartalma elérhető a következő hivatkozásokkal: matrix[i][j], *(matrix[i]+j) vagy *(*(matrix+i)+j).
  • A &matrix[0][0], a matrix[0], a &matrix[0] és a matrix ugyanaz az érték, azaz a mátrix kezdetének címe, de a matrix[0][0] egy long double azonosítója, a matrix[0] egy n long double elemű tömb azonosítója, és a matrix a tömbökből álló tömb azonosítója. Így:

    &matrix[0][0] + 1 &matrix[0][1],
    matrix[0] + 1 *matrix + 1 &matrix[0][1],
    &matrix[0] + 1 matrix + 1 és
    matrix + 1 &matrix[1] &matrix[1][0].

Többdimenziós, statikus tömbök esetén a fordító nem generál mutatótömböket a memóriába, csak a puszta tömbelemeket helyezi el a tárban a dinamikus tömbnél tárgyalt módon és sorrendben. A kód azonban előállít konstans mutatóként minden, a dinamikus tömbnél megszokott mutatóhivatkozást. E számításokba a fordító fixen beépíti a többdimenziós tömb méreteit (az elsőtől eltekintve). Az indexelés is módosul kicsit ebben az értelemben. Például a matrix[i][j] elérése a következő:

*((long double *)matrix + i*n + j)

Leegyszerűsítve: A fordító a többdimenziós, statikus tömböt egy akkora egydimenziós tömbben helyezi el, melyben annak minden eleme elfér. A többdimenziós tömb méretei, és az indexek segítségével ez után meghatározza, hogy melyik egydimenziós tömbelemet jelenti a többdimenziós tömbhivatkozás.

Tömbök mint függvényparaméterek

Ha van egy

float vektor[100];

tömbünk, és kezdőcímével meghívjuk az

Fv(vektor)

függvényt, akkor a függvény definíciójának

void Fv(float *v) { /* . . . */ }

vagy

void Fv(float v[]){ /* . . . */ }

módon kell kinéznie. (Az utóbbi alakról tudjuk, hogy a fordító rögtön és automatikusan átkonvertálja az előző (a mutatós) formára.)

Ne feledjük, hogy ugyan a vektor konstans mutató a hívó függvényben, de v (címmásolat) már változó mutató a meghívott függvényben. Vele tehát elvégezhető például a v++ művelet. A *v, a *(v+i) vagy a v[i] balérték alkalmazásával a vektor tömb bármelyik eleme módosítható a meghívott függvényben.

Emlékezzünk arra is, hogy a meghívott függvénynek is tudnia kell valahonnét a tömb méretét! Például úgy, hogy a méretet is átadjuk paraméterként neki. Az itt elmondottak többdimenziós tömbök vonatkozásában is igazak legalább is az első méretre, de ott már nem teszünk említést erről!

Ha van egy

float matrix[10][20];

definíciónk, és az azonosítóval meghívjuk

Fvm(matrix)

módon az Fvm függvényt, akkor hogyan kell az Fvm definíciójának kinéznie? A

void Fvm(float **m) { /* . . . */ }

próbálkozás rossz, mert a formális paraméter float mutatót címző mutató. A

void Fvm(float *m[20]) { /* . . . */ }

változat sem jó, mert így a formális paraméter 20 elemű, float típusú objektumokra mutató mutatótömb. Ennél a kísérletnél az az igazi probléma, hogy a [] operátor prioritása nagyobb, mint *-é. Nekünk formális paraméterként 20 float elemű tömbre mutató mutatót kéne átadni. Tehát a helyes megoldás:

void Fvm(float (*m)[20]) { /* . . . */ }

vagy a "tradicionális" módszer szerint:

void Fvm(float m[][20]) { /* . . . */ }

amiből rögtön és automatikusan előállítja a fordító az előző (a mutatós) alakot.

Meg kell még említenünk, hogyha a többdimenziós tömböt dinamikusan hozzuk létre, akkor az előzőleg ajánlott megoldás nyilvánvalóan helytelen. A mátrix "horgonypontját" ebben az esetben

float **matrix;

módon definiáljuk, ami ugye float mutatóra mutató mutató. Tehát ilyenkor a

Fvmd(matrix)

módon hívott Fvmd függvény helyes formális paramétere:

void Fvmd(float **m) { /* . . . */ }
Parancssori paraméterek

Minden C programban kell lennie egy a programot elindító függvénynek, mely konzol bázisú alkalmazások esetében a main függvény.

Most és itt csak a main paramétereivel és visszatérési értékével szeretnénk foglalkozni! A paraméterekről állíthatjuk, hogy:

  • elhagyhatóak és
  • nem ANSI szabványosak.

A main legáltalánosabb alakja:

int main(int argc, char *argv[]);

A paraméterek azonosítói bizonyos, C nyelvet támogató környezetekben ettől el is térhetnek, de funkciójuk akkor is változatlan marad.

Az argc a main-nek átadott parancssori paraméterek száma, melyben az indított végrehajtandó program azonosítója is benne van, és értéke így legalább 1.

Az argv a paraméter karakterláncokra mutató mutatótömb, ahol az egyes elemek rendre:

  • argv[0]: A futó program (meghajtónévvel és) úttal ellátott azonosítójára mutató mutató.
  • argv[1]: Az első parancssori paraméter karakterláncára mutató mutató.
  • argv[2]: A második paraméter karakterlánc kezdőcíme.
  • . . .
  • argv[argc - 1]: Az utolsó parancssori paraméter karakterláncára mutató mutató.
  • argv[argc]: NULL mutató.

A main lehetséges alakjai a következők:

void main(void);
int main(void);
int main(int argc);
int main(int argc, char *argv[]);

Vegyünk egy példát!

/* PELDA26.C: Parancssori parameterek. */
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[]) {
  int i;
  printf("Parancssori parameterek:\n");
  printf("Argc erteke %d.\n", argc);
  printf("Az atadott parancssori parameterek:\n");
  for(i=0; i<argc; ++i)
    printf("\targv[%d]: %s\n", i, *argv++);
  return 0; }

Tételezzük fel, hogy a programot a következő parancssorral indítottuk:

pelda26 elso_par "sodik par" 3 4 stop!

Ekkor a megjelenő kimenet a következő lehet:

Parancssori paraméterek:
Argc értéke 6.
Az átadott parancssori paraméterek:
argv[0]: C:\C\pelda26.exe
argv[1]: elso_par
argv[2]: sodik par
argv[3]: 3
argv[4]: 4
argv[5]: stop!

Beszéljünk kicsit a printf utolsó, *argv++ kifejezéséről! Az argv-t a main paraméterként kapja, tehát csak címmásolat, vagyis a main-ben akár el is rontható. Az argv típusa char **, és funkcionálisan a parancssori paraméter karakterláncok kezdőcímeit tartalmazó mutatótömb kezdetének címe. A rajta végrehajtott indirekcióval a típus char * lesz, s épp a mutatótömb első elemét (argv[0]) érjük el. Az utótag ++ operátor miatt eközben az argv már a második mutatótömb elemre (argv[1]) mutat. Elérjük ezt is, és mellékhatásként az argv megint előbbre mutat egy tömbelemmel. Tehát a ciklusban rendre végigjárjuk az összes parancssori paramétert.

Jusson eszünkbe, hogy a main-nek átadott parancssor maximális hosszát korlátozhatja az operációs rendszer!

A legtöbb operációs rendszerben léteznek

változó=érték

alakú, ún. környezeti változók, melyek definiálják a környezetet (információt szolgáltatnak) az operációs rendszer és a benne futó programok számára. Például a PATH környezeti változó szokta tartalmazni az alapértelmezett keresési utakat a végrehajtható programokhoz, a parancsinterpreter helyét írja elő a COMSPEC, és így tovább. Az operációs rendszer természetesen lehetőséget biztosít ilyen környezeti változók törlésére, megadására, és értékük módosítására.

A felhasználó pl. Windows 10 operációs rendszeren a Parancssor-t elindítva, a következő parancsokkal tudja manipulálni a környezeti változókat:

ParancsTevékenységPélda
setListázza az összes környezeti változót, értékével együtt.set
set kvnevCsak kvnev változó értékének megjelenítése.set JANI
set kvnev=ertekLétező kvnev változó értékének megváltoztatása ertek-re, vagy új változó létrehozása.set JANI=kovacs
set kvnev=kvnev környezeti változó törlése.set JANI=

Arra azonban ügyelni kell, hogy minden parancsértelmező a környezeti változóknak egy önálló készletével rendelkezik, azaz az egyik ilyen készlet változása nem befolyásolja a másikat. Magyarán, ha pl. a szoftverfejlesztő környezet indítja el a programunkat, akkor hiába gépelünk be új környezeti változókat egy parancssorban, abból a programunk valószínűleg semmit sem vesz majd észre.

A környezeti változók értékeit lekérdezni pl. az ANSI szabványos, nem kis-nagybetű érzékeny

char *getenv(const char *valtozo);

függvénnyel lehet, mely visszaadja az aktuális környezet alapján a valtozo nevű környezeti változó értékére mutató mutatót. Ha nincs ilyen változó az aktuális környezeti táblában, NULL mutatót kapunk. A visszakapott nem NULL mutatóval azonban nem célszerű és nem biztonságos dolog a környezeti változó értékét módosítani. Ehhez a nem szabványos putenv rutin használata ajánlott.

A környezeti változó nevének a végére nem kell kitenni az = jelet, azaz például a PATH környezeti változót a getenv("PATH") hívással kérdezhetjük le!

Ha expliciten nem deklaráljuk void-nak, akkor a main-nek int típusú státuszkóddal kell visszatérnie az őt indító programhoz (process), rendszerint az operációs rendszerhez. Konvenció szerint a zérus visszaadott érték (EXIT_SUCCESS) hibátlan futást, s a nem zérus státuszkód (EXIT_FAILURE 1) valamilyen hibát jelez. Magát a main-ből való visszatérést (mondjuk 1-es státuszkóddal) megoldhatjuk a következő módok egyikével:

return 1;
exit(1);

Foglalkozzunk kicsit a programbefejezéssel is!

Programbefejezés

A return 1 csak a main-ben kiadva fejezi be a program futását. Az stdlib.h bekapcsolásakor rendelkezésre álló, mindegyik operációs rendszerben használható

void exit(int statusz);
void abort(void);

függvények mind befejezik annak a programnak a futását, amiben meghívják őket akármilyen mély rutin szintről is. A statusz paraméter értékét visszakapja a befejezettet indító (várakozó szülő) program, mint kilépési állapotot (exit status). A statusz értéket átveszi persze az operációs rendszer is, ha ő volt a befejezett program indítója.

Az exit függvény a program befejezése előtt meghív minden regisztrált (lásd atexit) kilépési függvényt, kiüríti a kimeneti puffereket, és lezárja a nyitott fájlokat.

Az abort alapértelmezés szerint befejezi az aktuális programot. Megjelenteti például az

Abnormal program termination

üzenetet az stderr-en, és aztán SIGABRT (abnormális programbefejezés) jelet generál. Ha nem írtak kezelőt (signal) a SIGABRT számára, akkor az alapértelmezett tevékenység szerint az abort 3-as státuszkóddal visszaadja a vezérlést a szülő programnak. Szóval nem üríti a puffereket, és nem hív meg semmilyen kilépési függvényt (atexit) sem.

Függvény (kód) mutatók

A mutatók függvények ún. belépési pontjának címét is tartalmazhatják, s ilyenkor függvény vagy kódmutatókról beszélünk.

Ha van egy

int fv(double, int);

prototípusú függvényünk, akkor erre mutató mutatót

int (*pfv)(double, int);

módon deklarálhatunk. A pfv azonosító ezek után olyan változót deklarál, melyben egy double, s egy int paramétert fogadó és int-tel visszatérő függvények címeit tarthatjuk. A pfv tehát változó kódmutató. Kódmutató konstans is létezik azonban, s ez a függvénynév (a példában az fv).

Vigyázzunk a deklarációban a függvénymutató körüli kerek zárójel pár el nem hagyhatóságára, mert az

int *pfv(double, int);

olyan pfv azonosítójú függvényt deklarál, mely egy double, s egy int paramétert fogad, és int típusú objektumot címző mutatóval tér vissza. A probléma az, hogy a mutatóképző operátor (*) prioritása alacsonyabb a függvényképzőénél (()).

Hogyan lehet értéket adni a függvénymutatónak?

Természetesen a szokásos módokon, azaz hozzárendeléssel:

pfv = fv;

illetve a deklarációban inicializátor alkalmazásával:

int fv(double, int);
int (*pfv)(double, int) = fv;

Vigyázzunk nagyon a típussal, mert az most "bonyolódott"! Csak olyan függvény címe tehető be a pfv-be, mely a függvénymutatóval egyező típusú, azaz int-et ad vissza, egy double és egy int paramétert fogad ebben a sorrendben. A

void (*mfv)();

szerint az mfv meg nem határozott számú és típusú paramétert fogadó olyan függvényre mutató mutató, melynek nincs visszatérési értéke.

Vegyük észre, hogy a kérdéses függvények definíciója előtt függvénymutatók inicializálására is használható a megadott függvény prototípus!

Hogyan hívhatjuk meg azt a függvényt, melynek címét a kódmutató tartalmazza?

Alkalmaznunk kell a mutatókra vonatkozó ökölszabályunkat, ami azt mondja ki, hogy ahol állhat azonosító a kifejezésben, ott állhat (*mutatóazonosító) is. Vegyük elő újra az előző példát! Ha

int a = fv(0.65, 8);

az fv függvény hívása, és valamilyen módon lezajlott a pfv = fv hozzárendelés is, akkor az

a = (*pfv)(0.65, 8);

ugyanaz a függvényhívás.

Itt a pfv-re alkalmaztuk az indirekció operátort (*), de mivel ennek prioritása alacsonyabb a függvényhívás operátorénál (()), ezért a *pfv-t külön zárójelbe kellett tenni!

A kódmutatóval kapcsolatos alapismeretek letárgyalása után feltétlenül ismertetni kell a C fordító függvényekkel kapcsolatos fontos viselkedését: implicit konverzióját!

Ha a kifejezés típussal visszatérő függvény típusú, akkor hacsak nem cím operátor (&) mögött áll, típussal visszatérő függvénymutató típusúvá konvertálja automatikusan és azonnal a fordító.

Ez az implicit konverzió mindenek előtt megvalósul a

utótag-kifejezés(<kifejezéslista>)

függvényhívásban, ahol az utótag-kifejezésnek kell típussal visszatérő függvénycímmé kiértékelhetőnek lennie. A típus a függvényhívás értékének típusa. A dolog praktikusan azt jelenti, hogy a függvény bármilyen függvényre mutató kifejezéssel meghívható.

Milyen műveletek végezhetők a kódmutatókkal?

  • Képezhető a címük.
  • sizeof operátor operandusai lehetnek.
  • Végrehajtható rajtuk az indirekció művelete is, mint láttuk.
  • Értéket kaphatnak, ahogyan azt az előzőekben ismertettük.
  • Meghívhatók velük függvények. Ezt is áttekintettük.
  • Átadhatók paraméterként függvényeknek.
  • Kódmutatótömbök is létrehozhatók.
  • Függvény visszaadott értéke is lehet.
  • Explicit típuskonverzióval más típusú függvénymutatókká alakíthatók.

Kódmutatókra azonban nem alkalmazható a mutatóaritmetika az egyenlőségi reláció operátoroktól (== és !=) eltekintve.

Foglalkozzunk a kódmutató paraméterrel!

A függvényekre érvényes implicit típuskonverzió a függvényparaméterekre is vonatkozik. Ha a paraméter típussal visszatérő függvény, akkor a fordító automatikusan és rögtön típus típusú értéket szolgáltató függvényre mutató mutatóvá alakítja át.

A következő, kissé elvonatkoztatott példában kitűnően megszemlélhető a kódmutató paraméter függvény prototípusban, ill. függvény aktuális és formális paramétereként.

/* . . . */
long Emel(int);
long Lep(int);
long Letesz(int);
void Munka(int n, long (* fv)(int));
/* . . . */
void main(void){
  int valaszt=1, n;
  /* . . . */
  switch(valaszt){
    case 1: Munka(n, Emel); break;
    case 2: Munka(n, Lep); break;
    case 3: Munka(n, Letesz); }
  /* . . . */ }
void Munka(int n, long (* fv)(int)){
  int i;
  long j;
  for(i=j=0; i<n; ++i) j+=(*fv)(i); }

A kódmutató típusa szerint az ilyen függvény egy int paramétert fogad, és long értéket szolgáltat.

A szabványos függvénykönyvtár több olyan függvényt is tartalmaz, amely kódmutató paramétert vár. A korábban már általunk is megvalósított bináris keresés algoritmusa az stdlib.h fejfájl után elérhető:

void *bsearch(const void *kulcs, const void *bazis, size_t elemszam, size_t meret,
  int (*hasonlito)(const void *, const void *));

Ez a függvény megkeresi a kulcs címen tárolt adatot abban az elemszam elemből álló tömbben, amely a bazis címen helyezkedik el a tárban, és minden eleme meret méretű. A függvény utolsó paramétere egy olyan függvényt címez, mely két mutatót fogad, és int értékkel tér vissza. A mutatók a tömb két tetszőleges elemét címezhetik. A visszatérési érték pozitív, ha az első címen lévő érték nagyobb a második címen lévőnél, negatív, ha a második címen lévő érték nagyobb az első címen lévőnél, és zérus, ha a címzett helyeken lévő értékek egyformák. (Ebben a tulajdonságában tehát emlékeztet a karaktermutatóknál ismertetett strcmp függvényre.) A hasonlito függvényt nekünk kell megírnunk, mivel a bsearch-nek a const void* típusú mutatók miatt semmiféle fogalma nincs arról, hogy a tömb milyen jellegű adatokat tárol, és azokat hogyan kell értelmezni, összehasonlítani. Ez egyébként szándékos, és lehetővé teszi, hogy a függvényt típusfüggetlenül, a feladatok lehető legszélesebb körének megoldására használjuk. A bsearch visszatérési értéke a fellelt elem helye, vagy NULL mutató, ha a keresett elem nincs a tömbben.

Mint azt korábban megtárgyaltuk, bináris keresés csak rendezett tömbön hajtható végre. Szerencsére egy nagyon hatékony, ú.n. gyorsrendező (quick sort) algoritmus is megvalósításra került a szabvány könyvtárakban, így az sem okozhat gondot, ha egy tömb kezdetben rendezetlen. A

void qsort(void *bazis, size_t elemszam, size_t meret,
  int (*hasonlito)(const void *, const void *));

paramétereinek értelmezése pontosan megegyezik a bsearch-nél elmondottakkal. A qsort nem ad vissza értéket, a bazis címen kezdődő tömböt helyben rendezi.

A fentiek kipróbálására hozzunk létre egy olyan programot, ami egy egészekből álló, kezdetben rendezetlen tömbben keres meg értékeket!

/* PELDA27.C: rendezes es kereses */
#include<stdio.h>
#include<stdlib.h> /* bsearch, qsort miatt */
#define N 5 /* A tomb elemszama. */
/* Osszehasonlitja a tomb ket elemet. */
int hasonlit(const void* a, const void* b) {
  return *((const int*)a) - *((const int*)b); }
void kiir(const int* t, int n) {
  int i;
  for(i=0; i<n; i++)
    printf("%d%s", t[i], i==n-1?".\n":", "); }
void keres(int k, const int* t, int n) {
  int* p = (int*)bsearch(&k, t, n, sizeof(int), hasonlit);
  if(p) printf("%d indexe: %d.\n", k, (int)(p-t));
  else printf("%d nincs a tombben.\n", k); }
void main(void) {
  int szamok[N] = { -3, 54, -29, 12, 47 };
  printf("Szamok rendezes elott:\n");
  kiir(szamok, N);
  qsort(szamok, N, sizeof(int), hasonlit);
  printf("Szamok rendezes utan:\n");
  kiir(szamok, N);
  keres(12, szamok, N);
  keres(1234, szamok, N); }

A program futtatásának eredménye:

Szamok rendezes elott:
-3, 54, -29, 12, 47.
Szamok rendezes utan:
-29, -3, 12, 47, 54.
12 indexe: 2.
1234 nincs a tombben.

Azt mondottuk, hogy kódmutatók tömbökben is elhelyezhetők. Visszatérve az első pfv-s példánkhoz, az

int (*pfvt[])(double, int) = {fv1, fv2, fv3, fv4, fv5};

deklarációval létrehoztunk egy pfvt azonosítójú, olyan ötelemű tömböt, mely int-et visszaadó, egy double, és egy int paramétert fogadó függvények címeit tartalmazhatja. Feltéve, hogy fv1, fv2, fv3, fv4 és fv5 ilyen prototípusú függvények, a pfvt tömb elemeit kezdőértékkel is elláttuk ebben a deklarációban.

Hívjuk még meg, mondjuk, a tömb 3. elemét!

a = (*pfvt[2])(0.65, 8);

Alakítsuk át függvénymutató tömböt használóvá a kódmutató paraméternél ismertetett példát!

Az új megoldásunk main-en kívüli része változatlan, a main viszont:

void main(void){
  int valaszt=1, n;
  long (*fvmt[])(int) = {Emel, Lep, Letesz};
  /* . . . */
  Munka(n, fvmt[valaszt]);
  /* . . . */ }

A kódmutató visszatérési értékhez elemezzük ki a következő függvény prototípust!

void (*signal(int jel, void (* kezelo)(int)))(int);

A signal első paramétere int, a második (void (* kezelo)(int)) viszont értéket vissza nem adó, egy int paramétert fogadó függvénymutató típusú. Kitűnően látszik ezek után, hogy a visszatérési érték void (*)(int), azaz értéket nem szolgáltató, egy int paramétert fogadó függvénymutató. A visszaadott érték típusa tehát a signal második paraméterének típusával egyezik.

A signal.h fejfájlban elhelyezett prototípusú signal függvénnyel különben a program végrehajtása során bekövetkező, váratlan eseményeket (megszakítás, kivétel, hiba stb.), ún. jeleket vagy jelzéseket lehet lekezeltetni. Többféle típusú jel létezik. A void (*)(int) típusú kezelőfüggvényt a manipulálni kívánt jelre külön meg kell írni. A signal rutinnal hozzárendeltetjük a kezelőt (2. paraméter) az első paraméter típusú jelhez, és a signal az eddigi kezelő címével tér vissza.

Egy bizonyos típusú függvénymutató explicit típuskonverzióval

(típusnév) előtag-kifejezés

átalakítható más típusú kódmutatóvá. Ha az e módon átkonvertált mutatóval függvényt hívunk, akkor a hatás a programfejlesztő rendszertől, a hardvertől függ. Viszont, ha visszakonvertáljuk az átalakított mutatót az eredeti típusra, akkor az eredmény azonos lesz a kiindulási függvénymutatóval.

Szedjük csak megint elő az

int fv(double, int), a;
int (*pfv)(double, int) = fv;

példánkat, és legyen a

void (*vpfv)(double);
vpfv=(void (*)(double))pfv;

A

(*vpfv)(0.65);

eredményessége eléggé megkérdőjelezhető, de a

pfv=(int (*)(double, int))vpfv;

után teljesen rendben lesz a dolog:

a=(*pfv)(0.65, 8);

Emlékezzünk csak! Explicit típusmódosított kifejezés nem lehet balérték.

Foglalkozzunk csak újra egy kicsit a típusnevekkel!

Típusnév

Explicit típusmódosításban, függvénydeklarátorban a paramétertípus rögzítésekor, sizeof operandusaként stb. szükség lehet a típus nevének megadására. Ehhez kell a típusnév, mely szintaktikailag a kérdéses típusú objektum olyan deklarációja, melyből elhagyták az objektum azonosítóját.

típusnév:
  típusspecifikátor-lista<absztrakt-deklarátor>

absztrakt-deklarátor:
  mutató
  <mutató><direkt-absztrakt-deklarátor>

direkt-absztrakt-deklarátor:
  (absztrakt-deklarátor)
  <direkt-absztrakt-deklarátor>[<konstans-kifejezés>]
  <direkt-absztrakt-deklarátor>(<paraméter-típus-lista>)

Az absztrakt-deklarátorban mindig lokalizálható az a hely, ahol az azonosítónak kéne lennie, ha a konstrukció deklaráción belüli deklarátor lenne. Lássunk néhány konkrét példát!

int *

int típusú objektumra mutató mutató.

int **

int típusú objektumot címző mutatóra mutató mutató.

int *[]

Nem meghatározott elemszámú, int típusú mutatótömb.

int *()

Ismeretlen paraméterlistájú, int-re mutató mutatóval visszatérő függvény.

int (*[])(int)

int típussal visszatérő, egy int paraméteres, meghatározatlan elemszámú függvénymutató tömb.

int (*(*())[])(void)

Ismeretlen paraméterezésű, int típussal visszatérő függvénymutatókból képzett, meghatározatlan méretű tömbre mutató mutatót szolgáltató, paraméterrel nem rendelkező függvény.

A problémán: a sok zárójelen, a nehezen érthetőségen typedef alkalmazásával lehet segíteni.

Típusdefiníció (typedef)

Az elemi típusdefinícióról szó volt már a Típusok és konstansok lecke végén. Az ott elmondottakat nem ismételjük meg, viszont annyit újra szeretnénk tisztázni, hogy a típusdefiníció nem vezet be új típust, csak más módon megadott típusok szinonimáit állítja elő. A typedef név, ami egy azonosító, szintaktikailag egyenértékű a típust leíró kulcsszavakkal, vagy típusnévvel.

A típusdefiníció "bonyolításához" először azt említjük meg, hogy a

typedef típus azonosító;

szerkezetben az azonosító a prioritás sorrendjében lehet:

  • azonosító(): függvény típust képző, utótag operátor. Például:

    typedef double dfvdi(double, int);
    dfvdi hatvany;

    ahol a hatvany egy double, s egy int paramétert fogadó és double-t visszaadó függvény azonosítója.
  • azonosító[]: tömb típust képző, utótag operátor. Például:

    typedef double dtomb[20];
    dtomb t;

    ahol a t 20 double elemből álló tömb azonosítója.
  • *azonosító: mutató típust képző, előtag operátor. Például:

    typedef short *shptr;
    shptr sptr;

    ahol az sptr short típusú objektumra mutató mutató azonosítója.
  • Ezek az operátorok egyszerre is előfordulhatnak az azonosító-val. Például:

    typedef int *itb[10];
    itb tomb;

    ahol a tomb 10 elemű int objektumokra mutató mutatótömb azonosítója.

Megemlítendő még, hogy az előzőek alkalmazásával "csínján" kell bánni, hisz az így típusdefiniált azonosítóknak éppen a jellege (függvény, tömb, mutató) veszik el.

A típusdefiníció további komplexitása abból fakad, hogy a

typedef típus azonosító;

szerkezetbeli típus korábbi typedef típus azonosító;-ban definiált azonosító is lehet. Tehát a típusdefinícióval létrehozott típuspecifikátor típus lehet egy másik típusdefinícióban.

Nézzünk néhány példát!

typedef int *iptr;
typedef char nev[30];
typedef enum {no, ferfi, egyeb} sex;
typedef long double *ldptr;
ldptr ptr2; /* long double objektumra mutató mutató.*/
ldptr fv2(nev); /* 30 elemű karaktertömb paramétert fogadó,
              long double objektumra mutató mutatóval
              visszatérő függvény. */
typedef iptr (*ipfvi)(sex);
ipfvi fvp1; /* int-re mutató mutatót visszaadó, egy
              sex típusú enum paramétert fogadó
              függvényre mutató mutató. */
typedef ipfvi ptomb[5];
ptomb tomb; /* 5 elemű, int-re mutató mutatót
              szolgáltató, egy sex típusú paramétert
              fogadó függvényre mutató mutatótömb. */
iptr fugg(ptomb); /*int-re mutató mutatót visszaadó, 5
              elemű, int-re mutató mutatóval
              visszatérő, egy sex enum paraméteres
              függvényre mutató mutatótömböt
              paraméterként fogadó függvény. */

A típusdefinícióval a program típusai parametrizálhatók, azaz a program portábilisabb lesz, hisz az egyetlen typedef módosításával a típus megváltoztatható. A komplex típusokra megadott typedef nevek ezen kívül javítják a program olvashatóságát is.

Lokális szinten megadott típusdefiníció lokális hatáskörű is. Az általánosan használt típusdefiníciókat globálisan, a feladathoz tartozó fejfájlban szokták előírni.

Egy utolsó kérdés: Mikor ekvivalens két típus?

  • Ha a két típusspecifikátor-lista egyezik, beleértve azt is, hogy ugyanaz a típusspecifikátor többféleképpen is megadható. Például: a long, a long int és a signed long int azonosak.
  • Ha az absztrakt-deklarátoraik a typedef típusok kifejtése, és bármely függvényparaméter azonosító törlése után ekvivalens típusspecifikátor-listákat eredményeznek.

A típusekvivalencia meghatározásánál a tömbméretek és a függvényparaméter típusok is lényegesek.

Feladatok

1. Kezdő programozó olyan programot szeretett volna írni, ami abécé sorrendbe rendezve megjeleníti a szabvány kimeneten a szavak tömbben tárolt szavakat.

/*01*/#include <stdio.h>
/*02*/#include <stdlib.h>
/*03*/#include <string.h>
/*04*/#define DB 5
/*05*/int hasonlit(const void *p1, const void *p2) {
/*06*/  return strcmp((const char*)p1, (const char*)p2); }
/*07*/int main(void) {
/*08*/  int i;
/*09*/  char szavak[DB] = {"egy", "ket", "har", "negy", "ot"};
/*10*/  qsort(szavak, DB, sizeof(char*), hasonlit);
/*11*/  for(i=0; i<DB; i++) printf("%s\n", szavak[i]);
/*12*/  return 0; }
Jelölje meg azokat a sorokat, amelyekkel a hibás program működésre bírható!
/*06*/  return strcmp((const char*)p2, (const char*)p1); }
/*06*/  return strcmp(*(const char**)p1, *(const char**)p2); }
/*07*/void main(void) {
/*09*/  char* szavak[DB] = {"egy", "ket", "har", "negy", "ot"};
/*09*/  char (*szavak)[DB] = {"egy", "ket", "har", "negy", "ot"};

2. Egyszeri programozó szeretett volna egy olyan függvényt írni, ami véletlenszerű, [min, max] intervallumba eső egész értékekkel tölti fel a paraméterként kapott, statikusan allokált mtx tömb első sor sorát és oszlop oszlopát.

/*1*/#include <stdlib.h>
/*2*/#define MAX 10
/*3*/void mtxgen(int *mtx[MAX], int sor, int oszlop,
/*4*/            int min, int max) {
/*5*/  int i, j;
/*6*/  for(i=0; i<sor; i++)
/*7*/    for(j=0; j<oszlop; j++)
/*8*/      mtx[i][j] = min + rand()%(max-min+1); }
Jelölje meg azokat a programsorokat, amelyekkel a hibás program helyesen fog működni!
/*3*/void mtxgen(int (*mtx)[MAX], int sor, int oszlop,
/*3*/void mtxgen(int *(mtx)[], int sor, int oszlop,
/*3*/void mtxgen(int **mtx, int sor, int oszlop,
/*8*/      mtx[i][j] = min + rand()%(max-min); }
/*8*/      mtx[i][j] = min + rand()%(max-min) + 1; }
/*8*/      mtx[i][j] = min + rand()/(max-min+1); }

3. Kezdő programozónk szándéka az volt, hogy 10 db. valós, -2 és +2 közé eső, véletlen számot jelenítsen meg a szabvány kimeneten.

/*01*/#include <stdio.h>
/*02*/#include <stdlib.h>
/*03*/#include <time.h>
/*04*/#define MIN -2.
/*05*/#define MAX 2.
/*06*/void main(void) {
/*07*/  int i;
/*08*/  srand(time(NULL));
/*09*/  for(i=0; i<10; i++) {
/*10*/    srand(time(NULL));
/*11*/    printf("%.3f ", MIN + (rand()/RAND_MAX)*(MAX-MIN)); } }
Jelölje meg azokat a programsorokat, amelyekkel programjának működése helyessé tehető!
/*08*/  /* srand(time(NULL)); */
/*10*/  /* srand(time(NULL)); */
/*11*/    printf("%.3f ", MIN + rand()/RAND_MAX*(MAX-MIN)); } }
/*11*/    printf("%.3f ", MIN + ((double)rand()/RAND_MAX)*(MAX-MIN)); } }
/*11*/    printf("%.3f ", MIN + ((double)rand()/RAND_MAX)*(MAX-MIN+1.)); } }
/*11*/    printf("%.3f ", MIN + (rand()/RAND_MAX)%(MAX-MIN)); } }

4. Készítsen programot, mely a JANI, fordítási időben változtatható azonosítójú, környezeti változóról megállapítja, hogy létezik-e! Ha létezik, akkor eldönti, hogy értéke "lo", "szamar", "birka" vagy más.

/* JANI.C: A KORNYVALT kornyezeti valtozo a megadott ertekek egyike,
          vagy mas, vagy nincs is? (getenv-es valtozat) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define KORNYVALT "JANI"
char *ertekek[]= {"lo","szamar","birka"};
#define IG sizeof(ertekek)/sizeof(char*)
int main(void) {
  int i;
  char *ertek;
  printf("A(z) %s kornyezeti valtozo ertekenek lekerdezese\n\n", KORNYVALT);
  if(ertek=getenv(KORNYVALT)) {
    for(i=0; i<IG; i++)
      if(!strcmp(ertek, ertekek[i])) {
        printf("A(z) %s %s!\n", KORNYVALT, ertekek[i]);
        return EXIT_SUCCESS; }
    printf("A(z) %s mas!",KORNYVALT);
    return EXIT_SUCCESS; }
  printf("Nincs is %s!", KORNYVALT);
  return EXIT_FAILURE; }

5. Készítsen programot két mátrix összeadására! Az összeadandó mátrixoknak ne, de az eredménymátrixnak dinamikusan foglaljon helyet a memóriában! Az összeadandó mátrixok mérete csak futás időben válik konkréttá. A programban használjon függvényeket az összeadandó mátrixok méretének és elemeinek bekéréséhez, valamint a két mátrix összeadásához!

/*  MATRIXOS.C - Ket matrix osszeadasa */
#include <stdio.h>
#include <stdlib.h>
#define MERET 10
int getline(char *s, int lim) {
  int c;
  char *t=s;
  while(lim-->0&&(c=getchar())!=EOF&&c!='\n')*s++=c;
  *s='\0';
  while(c!=EOF&&c!='\n') getchar();
  return(s-t); }
#define MAX 15
int meret(char* uzenet) {
  int i=0;
  char buf[MAX+1];
  while(i<1||i>MERET) {
    printf("%-8s szama (1-%d)? ", uzenet, MERET);
    getline(buf, MAX);
    i = atoi(buf); }
  return i; }
void beolvas(int (*M)[MERET], int o, int s, char* uzenet) {
  int i, j;
  char buf[MAX+1];
  printf("Kerem %s matrix elemeit a megadott sorrendben!\n", uzenet);
  for(i=0; i<o; ++i) {
    for(j=0; j<s; ++j) {
      printf("[%d][%d] : ",i+1,j+1);
      getline(buf, MAX);
      M[i][j] = atoi(buf); } } }
long (*osszead(int (*A)[MERET], int B[][MERET], int s, int o))[MERET] {
  long (*C)[MERET];
  int i, j;
  if(!(C = (long(*)[MERET])malloc(s*sizeof(long*)))) {
    printf("Nem tudtam memoriat foglalni a matrixnak.\n");
    exit(1); }
  for(i=0; i<s; i++)
    for(j=0; j<o; j++)
        C[i][j] = (long)*(*(A+i)+j) + B[i][j];
  return C; }
void main(void) {
  int i,j,    /* Indexek */
      s=0,o=0, /* Sorok, oszlopok szama */
      A[MERET][MERET], B[MERET][MERET]; /* A matrixok */
      long (*C)[MERET]; /* Az eredmeny matrix */
  printf("Ket matrix osszeadasa:\n");
  s = meret("Sorok");
  o = meret("Oszlopok");
  beolvas(A, s, o, "az elso");
  beolvas(B, s, o, "a masodik");
  C = osszead(A, B, s, o);
  printf("\nAz osszegmatrix:\n\n");
  for(i=0; i<s; ++i) {
    for(j=0; j<o; ++j)
      printf("[%2d][%2d]:%6ld ", i+1, j+1, C[i][j]);
    putchar('\n'); }
  free(C); }

6. Készítsen programot, mely neveket olvas a szabvány bemenetről! Egy sorban egy név érkezik, s az üres sor a bemenet végét jelzi. A név nagybetűvel kezdődik, és a többi karaktere kisbetű. A feltételeket ki nem elégítő név helyett azonnal másikat kell kérni a probléma kijelzése után! A neveket rendezze névsorba, s listázza ki őket lapokra bontva!

/* NEVREND.C: Beolvasott, dinamikusan mutatotombben tarolt
              nevek rendezese. */
#include <stdio.h>
#include <string.h> /* A karakterlanc-kezeles miatt! */
#include <stdlib.h> /* A dinamikus tarolas miatt! */
#include <ctype.h> /* A karaktervizsgalatok miatt! */
#define SOROK 100 /* A tarolhato nevek max.szama. */
#define LAPSOR 20 /* Egy lapra irhato nevek szama. */
int nevekbe(char *nevmutomb[], int maxnevdb);
void rendez(char *t[], int n);
void main(void) {
  char *nevmutomb[SOROK]; /* Neveket megcimzo mutatotomb.*/
  int nevdb; /* A beolvasott nevek szama. */
  printf("A beolvasott nevek rendezese:\n");
  printf("A neveket Enter-rel kell lezarni!\n");
  printf("A bemenet veget ures sor jelzi!\n\n");
  if((nevdb=nevekbe(nevmutomb, SOROK))>=0) { /* Beolvasas. */
    int i;
    rendez(nevmutomb, nevdb); /* Rendezes. */
    printf("\n");
    for(i=0; i<nevdb; ++i) { /* Kiiras lapozva. */
      if(i%LAPSOR==0&&i!=0) {
        printf("A tovabblistazashoz usson Enter-t! ");
        while(getchar()!='\n');
        printf("\n\n"); }
      printf("%s\n", nevmutomb[i]); }
  } else printf("\nTul sok nev, vagy elfogyott a "
                "memoria!\n"); }
#define MAXNEV 40 /* Egy nev max. hosszusaga */
int getline(char *, int);
int nevekbe(char *nevmutomb[], int maxnevdb) {
  /* Nevek beolvasasa
  es dinamikus elhelyezese a parameter mutatotombben
  es a letarolt nevek szamanak visszaadasa. -1-et szolgaltat,
  ha kimerul a mutatotomb, vagy a heap elfogy. */
  int hsz, /* A beolvasott nev hossza. */
      nevdb=0; /* Az eddig beolvasott nevek szama. */
  char *p, /* A dinamikus terulet cime. */
      nev[MAXNEV+1];/* Az aktualisan beolvasott nev. */
  while((hsz=getline(nev, MAXNEV))>0) {
    /* Nev ellenorzese: Elso nagy, a tobbi kisbetű. */
    p=nev+1;
    while(*p&& islower(*p++));
    if(isupper(*nev)&&!(*p)) {
      /* Kimerult-e mutatotomb? */
      if(nevdb>=maxnevdb) return(-1);
      /* Van-e meg memoria? */
      else if((p=(char *)malloc(hsz+1))==NULL) return(-1);
      /* Minden OK: nev a heapre, cime a mutatotombbe. */
      else {
        strcpy(p, nev);
        nevmutomb[nevdb++]=p; }
    } else printf("Ervenytelen nev!\n"); }
  return(nevdb); }
int getline(char *s, int n) { /* Sor beolvasasa s-be. */
  int c; /* A hosszat adja vissza. */
  char *t=s; /* Indexeles helyett a t mutatoval
    haladunk az s karaktertombon. A t mindig a kovetkezo
    szabad helyre mutat a tombben, melynek tulirasat az n
    elfogyasa akadalyozza meg. */
  while(n-->0&&(c=getchar())!=EOF&&c!='\n') *t++=c;
  *t='\0';
  while(c!=EOF&&c!='\n') c=getchar();
  /* A karakterlacot zaro '\0' cimebol a tomb kezdocimet
    kivonva, eppen a karakterlanc hosszat kapjuk. */
  return(t-s); }
void rendez(char *v[], int n) { /* A v[0],v[1],..,v[n-1] */
  int i, j, m; /* karakterlancok rendezese novekvo */
  char *cs; /* sorrendben a mutatok cserejevel. */
  for(i=0; i<n-1; ++i) {
    for(j=i+1, m=i; j<n; ++j) if(strcmp(v[j],v[m])<0) m=j;
    if(i!=m) {
      cs=v[i];
      v[i]=v[m];
      v[m]=cs; } } }