KURZUS: Programozás alapjai

MODUL: I. modul

3. lecke: Alapismeretek 2/2

A harmadik leckében folytatjuk a C nyelv legalapvetőbb elemeinek megismerését. Ennek során megnézzük, hogyan lehet tömböket és karakterláncokat használni, saját függvényeket készítünk, de nem állunk meg a modularitásnak ezen a szintjén. Azt is megvizsgáljuk, hogyan tudjuk a programunkat több forrásfájlból elkészíteni. Megvizsgáljuk a lokális és globális változók közötti főbb különbségeket, és megismerkedünk a változók inicializálásának lehetőségeivel is.

A lecke végére:

  • A hallgató képes tömbök használatára, méretének meghatározására, egyszerű leszámlálási feladatok elvégzésére tömbökkel.
  • Képes lesz a hallgató saját függvényeket készíteni (néhány speciális paraméterezési lehetőségtől eltekintve), megérti a paraméterek átadásának és az eredményének visszaadásának módját.
  • Projektek segítségével képes lesz akár több forrásfájlból álló, összetettebb programok megírására.
  • Néhány egyszerű karakterlánc kezelő függvénnyel is megismerkedik.
  • Képes lesz globális és lokális változók használatára, mindig a feladatnak megfelelő megoldás kiválasztására.
  • A változók inicializálásával egyszerűbbé válik a kezdőérték adás elvégzése.

A lecke kulcsfogalmai: alul- és túlcsordulás, projektfájl, implicit függőség, folytatássor, lokális és globális változó, hatáskör, élettartam, tárolási osztály, inicializátor.

Tömbök

Készítsünk programot, mely a szabvány bemenetet olvassa EOF-ig! Megállapítandó és kijelzendő, hogy hány A, B, C stb. karakter érkezett! A kis- és nagybetűk között nem teszünk különbséget! A betűkön kívüli többi karaktert tekintsük egy kategóriának, s ezek darabszámát is jelezzük ki!

Megoldásunkban az elvalaszto karakteres változó, a k, a tobbi és a betu viszont egész típusú. A k tartalmazza a beolvasott karaktert, és ciklusváltozói funkciókat is ellát. A tobbi és a betu számlálók. A betu annyi elemű tömb, mint ahány betű az angol ábécében van. A tobbi a betűkön kívüli többi karakter számlálója. Az elvalaszto karakteres változóra azért van szükség, mert az eredmény csak két oszlop páros listaként közölhető egy képernyőn. Az algoritmus:

  • Deklaráljuk a változókat, és a tömböt! A számlálók nullázandók! Az elvalaszto induljon szóköz kezdőértékkel!
  • Jelentessük meg a program címét, és tájékoztassunk a használatáról!
  • Működtessük addig a ciklust, míg EOF nem érkezik a bemenetről!
  • A ciklusmagban háromirányú szelekcióval el kell ágazni három kategória felé: nagybetű, kisbetű és más karakter. Megnövelendő eggyel természetesen a megfelelő számláló!
  • A ciklus befejeződése után két oszlop páros táblázatban megjelentetendők a betűszámlálók értékei, és végül egy külön sorban a "többi karakter" kategória számlálója!
/* PELDA8.C: Betuszamlalas a bemeneten */
#include <stdio.h>
#define BETUK 26  /* Az angol abece betuszama */
void main(void){
  char elvalaszto;/* Listelvalaszto karakter. */
  int k,          /* Bemeneti kar. és ciklusvaltozo. */
      tobbi,      /* Nem betuk szamlaloja. */
      betu[BETUK];/* Betuszamlalok. */
  tobbi=0;        /* Kezdoertek adas. */
  for(k=0; k<BETUK; ++k) betu[k]=0;
  elvalaszto=' ';
  printf("Bemenet betuinek leszamlalasa\n"
        "EOF-ig, vagy Ctrl+Z-ig.\n");
  while((k=getchar())!=EOF)
    /* Nagybetűk: */
    if(k>='A'&&k<='Z')++betu[k-'A'];
    /* Kisbetűk: */
    else if(k>='a'&&k<='z')++betu[k-'a'];
    /* Más karakterek: */
    else ++tobbi;
  /* Eredmények közlése: */
  printf("\nBetu|Darab Betu|Darab\n"
          "----+----- ----+-----\n");
  for(k=0; k<BETUK; ++k){
    printf("%4c|%5d%c", k+'A', betu[k], elvalaszto);
    if(elvalaszto==' ') elvalaszto='\n';
    else elvalaszto=' '; }
  printf("\nTobbi karakter: %5d\n", tobbi); }

A char típusú változó egyetlen karakter tárolására alkalmas. A char ugyanakkor 8 bites, alapértelmezés szerint előjeles (signed), fixpontos belsőábrázolású egész típus is. Ennek is létezik előjel nélküli változata, sőt, a legtöbb programfejlesztő rendszerben az unsigned char alapértelmezésként is beállítható karakter típus.

A pelda8.c-ből kitűnően látszik, hogy a tömb definíciója

típus tömbazonosító[méret];

alakú. Pontosabban a deklarációs utasítás azonosítólistája nem csak egyszerű változók azonosítóiból állhat, hanem tömbazonosító[méret] konstrukciók is lehetnek köztük. A tömbdefinícióban a méret pozitív, egész értékű állandó kifejezés, és a tömb elemszámát határozza meg. Állandó kifejezés az, aminek fordítási időben (a program fordítása során) kiszámítható az értéke.

A tömb egy elemének helyfoglalása típusától függ. Az egész tömb a memóriában összesen

sizeof(tömbazonosító)  méret*sizeof(típus)

bájtot igényel. Például 16 bites int-et feltételezve a sizeof(betu) 26*sizeof(int) pontosan 52. A magas prioritású, egyoperandusos sizeof operátor megadja a mögötte zárójelben álló objektum, vagy típus által elfoglalt bájtok számát.

Vegyük észre, hogy a betu[0]-ban a program az A, a betu[1]-ben a B, ..., és a betu[25]-ben a Z karaktereket számlálja! Tételezzük fel, hogy k értéke 68! Ez ugyebár a D betű ASCII kódja. Ilyenkor a betu[k-'A'] számláló nő eggyel. Az A ASCII kódja 65. Tehát betu[68-65]-ről, azaz betu[3] növeléséről van szó!

Figyeljünk fel még arra, hogy az eredményeket közlő ciklusbeli printf-ben a k+'A' egész kifejezés értékét %c formátumspecifikációval jelentetjük meg, azaz rendre 65-öt, 66-ot, 67-et stb. íratunk ki karakteresen, tehát A-t, B-t, C-t stb. látunk majd.

Lássuk még be, hogy az elvalaszto változó értéke szóköz és soremelés karakter közt váltakozik, s így két betű-darab pár képes megjelenni egy sorban. Tehát az elvalaszto változó segítségével produkáljuk a két oszlop páros eredménylistát.

Listázni viszont csak azt érdemes, ami valamilyen információt hordoz! Tehát a zérus darabszámú betűk kijelzése teljesen felesleges! Magyarán a for ciklusbeli printf-et így kéne módosítani:

if(betu[k]>0) printf("%4c|%5d%c", k+'A', betu[k], elvalaszto);
Függvények

A függvényeket többféleképpen csoportosíthatnánk, de a legpraktikusabb úgy, hogy:

  • Vannak előre megírtak. Könyvtárakban (.a, .lib), vagy tárgymodulokban (.o, .obj) találhatók, s a kapcsoló-szerkesztő kapcsolja be őket a végrehajtható fájlba. Például: a printf, a getchar, a putchar, vagy a main stb. Minden végrehajtható programban kell lennie egy függvénynek, az indító programnak (a main-nek), mely az egész program belépési pontját képezi.
  • Mi írjuk őket. Forrásfájlokban helyezkednek el, s kódjukat a fordító generálja.

A nyelv központi eleme a függvény. A más programozási nyelvekben szokásos eljárás (procedure) itt explicit módon nem létezik, mert a C szellemében az egy olyan függvény, aminek nincs visszaadott értéke:

void eljárás();

Jelezzük ki egy táblázatban az 1001 és 1010 közötti egész számok köbét!

/* PELDA9.C: Kobtablazat */
#include <stdio.h>
#define TOL 1001  /* A tartomany kezdete. */
#define IG 1010  /* A tartomany vege. */
long kob(int);    /* Fuggveny prototipus. */
void main(void){
  int i;
  printf(" Szam|%11s\n-----+-----------\n", "Kobe");
  for(i=TOL; i<=IG; ++i) /* Fuggvenyhivas: */
    printf("%5d|%11ld\n", i, kob(i)); }
long kob(int a){  /* Fuggvenydefinicio. */
  return (long)a*a*a; }

A függvénydefiníció és a függvényhívás fogalmával megismerkedtünk már a Kapcsoló-szerkesztés fejezetben. A függvénydefinícióban van meg a függvény teste, azaz az a kód, amit a függvény meghívásakor végrehajt a processzor. Egy függvényre a programban csak egyetlen definíció létezhet, és ennek nem mondhatnak ellent a prototípusok (deklarációk)! A függvénydefinícióban előírt visszaadott érték típusának egyeznie kell ebből következőleg a programban bárhol előforduló, e függvényre vonatkozó prototípusokban (deklarációkban) megadott visszatérési érték típussal. A meghívott függvény akkor ad vissza értéket a hívó függvénynek a hívás pontjára, ha a processzor kifejezéssel ellátott return utasítást hajt végre benne. A "valamit" szolgáltató függvényben tehát lennie kell legalább egy return kifejezés; utasításnak, és rá is kell, hogy kerüljön a vezérlés. A visszaadott érték meghatározatlan, ha a processzor nem hajt végre return utasítást, vagy a return utasításhoz nem tartozott kifejezés. A visszaadott érték típusa bármi lehet végül is eltekintve a tömbtől és a függvénytől. Lehet valamilyen alaptípus, de el is hagyható, amikor is az alapértelmezés lesz érvényben, ami viszont int.

Nézzük a return szintaktikáját!

return <kifejezés>;

A fordító kiértékeli a kifejezést. Ha a függvény visszatérési típusa típus, akkor a kifejezés típusának is ennek kell lennie, vagy implicit konverzióval ilyen típusúvá alakítja a kifejezés értékét a fordító, és csak azután adja vissza.

Lássuk be, hogy a pelda9.c-beli return-ben az explicit (long) típusmódosítás nem azért van, hogy megtakarítsuk a kifejezés értékének visszaadás előtti implicit konverzióját! Az igazi ok az, hogy egy 16 bites int köbe nem biztos, hogy elfér az int-ben! Gondoljunk 1000 köbére, ami 1000000000! Ez jóval meghaladja a 32767-es felsőábrázolási korlátot.

C-ben az egész típusok területén nincs sem túlcsordulás, sem alulcsordulás! Pontosabban, amelyik művelet eredménye túl magas vagy túl alacsony lenne az adott típusban történő reprezentáláshoz, az mindenféle üzenet nélkül elveszik.

A függvényhívás átruházza a vezérlést a hívó függvényből a hívottba úgy, hogy az aktuális paramétereket is átadja - ha vannak - érték szerint (azaz az aktuális paraméterek másolatait elhelyezi a verembe, és a hívott függvény ezekkel a másolatokkal dolgozik). A vezérlést a függvénytest első végrehajtható utasítása kapja meg. void visszatérésű függvény blokkjában aztán a végrehajtás addig folytatódik, míg kifejezés nélküli return utasítás nem következik, vagy a függvény blokkját záró }-re nem kerül a vezérlés. Ezután a hívási ponttól folytatódik a végrehajtás.

Vegyük észre, hogy a return utasítás szintaktikájában az elhagyható kifejezés a paraméter nélküli return-t kívánta jelölni!

Belátható, hogy a függvény prototípusnak mindig meg kell előznie a hívást a forrásszövegben. A fordító így tisztában van a hívás helyén a függvény paramétereinek számával, sorrendjével és típusával, ill. ismeri a függvény visszatérési értékének típusát is.

A fordító a prototípus ismeretében implicit típuskonverziót is végrehajt az aktuális paraméter értékén a függvénynek történő átadás előtt, ha az aktuális paraméter típusa eltérő.

Ha nincs prototípus, akkor nincs implicit konverzió, és csak a "csoda" tudja, hogy mi történik az átadott nem megfelelő típusú értékkel. Például a kob(3.0) hívás eredménye zérus, ami remélhetőleg kellően szemlélteti a prototípus megadásának szükségességét. Ha nincs prototípus, akkor a fordító azt feltételezi (tehát olyan hívási kódot generál), hogy a függvénynek az alapértelmezés miatt int visszaadott értéke van. Ez ugyebár eléggé érdekes eredményre vezet void, vagy lebegőpontos visszatérésű függvények esetében. A nem int visszaadott értékű függvényt legalább deklaráni kell a hívó függvényben!

A függvénydeklaráció bemutatásához átírjuk a pelda9.c-t:

/* PELDA9.C: Kobtablazat */
#include <stdio.h>
#define TOL 1001  /* A tartomany kezdete. */
#define IG 1010  /* A tartomany vege. */
void main(void){
  int i;
  long kob();    /* Fuggveny deklaracio. */
  printf(" Szam|%11s\n-----+-----------\n", "Kobe");
  for(i=TOL; i<=IG; ++i) /* Fuggvenyhivas: */
    printf("%5d|%11ld\n", i, kob(i)); }
long kob(int a){  /* Fuggvenydefinicio. */
  return (long)a*a*a; }

Vegyük észre rögtön, hogy deklarációs utasításunk szintaktikája ismét módosult! Az azonosítólista nem csak egyszerű változók azonosítóiból és tömbazonosító[méret] konstrukciókból állhat, hanem tartalmazhat

függvénynév()

alakzatokat is. Természetesen a teljes függvény prototípus is beírható a deklárációs utasításba,

long kob(int);     /* Fuggveny deklaracio. */

de ilyenkor a függvény prototípus csak ebben a blokkban lesz érvényben.

Lássuk be, hogy az utóbbi módszer nem ajánlható olyan több függvénydefinícióból álló forrásfájlra, ahol a kérdéses függvényt több helyről is meghívják! Sokkal egyszerűbb a forrásszöveg elején megadni egyszer a prototípust, mint minden őt hívó függvényben külön deklarálni a függvényt.

A függvény definíciója prototípusnak is minősül, ha megelőzi a forrásszövegben a függvényhívást.

/* PELDA9.C: Kobtablazat */
#include <stdio.h>
#define TOL 1001  /* A tartomany kezdete. */
#define IG 1010  /* A tartomany vege. */
long kob(int a){ /* Fuggvenydefinicio. */
  return (long)a*a*a; }
void main(void){
  int i;
  printf(" Szam|%11s\n-----+-----------\n", "Kobe");
  for(i=TOL; i<=IG; ++i) /* Fuggvenyhivas: */
    printf("%5d|%11ld\n", i, kob(i)); }

C-ben tilos függvénydefiníción belül egy másikat kezdeni, azaz a függvénydefiníciók nem ágyazhatók egymásba!

Projekt

Ha a végrehajtható program forrásszövegét témánként, vagy funkciónként külön-külön forrásfájlokban kívánjuk elhelyezni, akkor C-s programfejlesztő rendszerekben ennek semmiféle akadálya sincs. Be kell azonban tartani a következő szabályokat:

  • Egy és csak egy forrásmodulban szerepelnie kell az indító programnak (main).
  • Projektfájlt kell készíteni, melyben felsorolandók a program teljes szövegét alkotó forrásfájlok.

Szedjük szét két forrásmodulra: foprog.c-re és fuggv.c-re, a pelda9.c programunkat!

/* FOPROG.C: Kobtablazat */
#include <stdio.h>
#define TOL 1001  /* A tartomany kezdete. */
#define IG 1010  /* A tartomany vege. */
long kob(int);    /* Fuggveny prototipus. */
void main(void){
  int i;
  printf(" Szam|%11s\n-----+-----------\n", "Kobe");
  for(i=TOL; i<=IG; ++i) /* Fuggvenyhivas: */
    printf("%5d|%11ld\n", i, kob(i)); }

/* FUGGV.C -- A fuggvenydefinicio. */
long kob(int a){
  return (long)a*a*a; }

A projektfájl neve, szerkezete fejlesztőkörnyezetenként eltérő lehet. A VS-ban eleve nem is lehet projekt nélkül dolgozni, ezért most csak annyi a teendő, hogy a korábban beletett fájlokat kivesszük belőle és betesszük az újakat. A "Solution explorer"-ben a "Source files" mappában láthatóak a projektben lévő forrásfájlok. Ezek valamelyikén a jobb kattintással előhívható "Remove" menüponttal lehet a fájlt eltávolítani. Tanulmányaink során, amikor is nem jellemzőek az összetett szoftverfejlesztési tevékenységek, hosszabb távon érdemes lehet csak egyszer készíteni egy projektet, majd abban cserélgetni a forrásfájlokat, hogy elkerüljük az új projektek létrehozásával járó nehézségeket.

Projekt forrásainak fordítása
3.1. ábra
Projekt kapcsoló-szerkesztése
3.2. ábra

Fedezzük fel, hogy a végrehajtható fájl a projekt nevét kapja meg! A projektet alkotó fájlok között implicit függőség van. Ez azt jelenti, hogy a projekt futtatásakor csak akkor történik meg a tárgymodul alakjában is rendelkezésre álló forrásfájl fordítása, ha a forrásfájl utolsó módosításnak ideje (dátuma és időpontja) későbbi, mint a vonatkozó tárgymodulé. A kapcsoló-szerkesztés végrehajtásához az szükséges, hogy a tárgymodulok, ill. a könyvtárak valamelyikének ideje későbbi legyen a végrehajtható fájlénál. Az implicit függőség fennáll a forrásfájl és a bele #include direktívával bekapcsolt fájlok között is. A tárgymodult akkor is újra kell fordítani, ha valamelyik forrásfájlba behozott fájl ideje későbbi a tárgymodulénál.

Implicit függőség a projektnél
3.3. ábra

A projektfájlban a forrásmodulokon kívül megadhatók tárgymodulok és könyvtárak fájlazonosítói is. A kapcsoló-szerkesztő a tárgymodulokat beszerkeszti a végrehajtható fájlba. A könyvtárakban pedig függvények tárgykódjait fogja keresni.

Karaktertömb és karakterlánc

A karaktertömbök definíciója a Tömbök fejezetben ismertetettek szerint:

char tömbazonosító[méret];

Az egész tömb helyfoglalása:

méret*sizeof(char) méret

bájt. A tömbindexelés ebben az esetben is zérustól indul és méret-1-ig tart.

A C-ben nincs külön karakterlánc (sztring) adattípus. A karakterláncokat a fordítónak és a programozónak karaktertömbökben kell elhelyeznie. A karakterlánc végét az őt tartalmazó tömbben egy zérusértékű bájttal ('\0') kell jelezni. Például a "Gizike" karakterláncot így kell letárolni a nev karaktertömbben:

Karakterlánc tárolása
3.4. ábra

Vegyük észre, hogy a karakterlánc első jele a nev[0]-ban, a második a nev[1]-ben, s a legutolsó a nev[5]-ben helyezkedik el, és az ezt követő nev[6] tartalmazza a lánczáró zérust! Figyeljünk fel arra is, hogy a karakterlánc hossza (6) megegyezik a lezáró '\0' karaktert magába foglaló tömbelem indexével! Fedezzük még rögtön fel, hogy a zérus egész konstans (0) és a lánczáró '\0' karakter értéke ugyanaz: zérus int típusban! Hiszen a karakter konstans belsőábrázolása int.

A C-ben nincs külön karakterlánc adattípus, s ebből következőleg nem léteznek olyan sztring műveletek sem, mint a karakterláncok

  • egyesítése,
  • összehasonlítása,
  • hozzárendelése stb.

Ezeket a műveleteket bájtról bájtra haladva kell kódolni, vagy függvényt kell írni rájuk, mint ahogyan azt a következő példában bemutatjuk. (A szabványos függvénykönyvtár igazából tartalmaz előre elkészített függvényeket ezekre a gyakran használt feladatokra, de többet tanulunk abból, ha kezdetben magunk készítjük el ezeket.)

Készítsen programot, mely neveket olvas a szabvány bemenetről EOF-ig vagy üres sorig! Megállapítandó egy fordítási időben megadott névről, hogy hányszor fordult elő a bemeneten! A feladat megoldásához készítendő

  • Egy intstrcmp(char s1[], char s2[]) függvény, mely összehasonlítja két karakterlánc paraméterét! Ha egyeznek, zérust ad vissza. Ha az első hátrébb van a névsorban (nagyobb), akkor pozitív, egyébként meg negatív értéket szolgáltat.
  • Egy int getline(char s[], int n) függvény, mely behoz a szabvány bemenetről egy sort! A sor karaktereit rendre elhelyezi az s karaktertömbben. A befejező soremelés karaktert nem viszi át a tömbbe, hanem helyette lánczáró '\0'-t ír a karakterlánc végére. A getline második paramétere az s karaktertömb méreténél eggyel kisebb egész érték, azaz a lánczáró zérus nélkül legfeljebb n karaktert tárol a tömbben a függvény. A getline visszatérési értéke az s tömbben végül is elhelyezett karakterlánc hossza. (Bár a VS-ban éppen nem definiált, de más fejlesztőkörnyezetekben létezhet beépített getline függvény, ami azonban a miénktől eltérő prototípusú, és hívni is másképp kell. Ha ebből probléma adódna, egyszerűen adjunk más nevet a saját függvényünknek.)
/* PELDA10.C: Nevszamlalas */
#include <stdio.h>
#define NEV "Jani" /* A szamlalt nev. */
#define MAX 29    /* A bemeneti sor maximalis merete.
                      Most egyben a leghosszabb nev is. */
int getline(char s[],int n); /* Fuggveny prototipusok */
int strcmp(char s1[], char s2[]);
void main(void){
  int db;        /* Nevszamlalo */
  char s[MAX+1]; /* Az aktualis nev */
  db=0;          /* A szamlalo nullazasa */
  printf("A(z) %s nev leszamlalasa a bemeneten.\nAdjon\
meg soronkent egy nevet!\nProgramveg: ures sor.\n",NEV);
  /* Sorok olvasasa ures sorig a bemenetrol: */
  while(getline(s,MAX)>0)
    /* Ha a sor epp a NEV: */
    if(strcmp(s,NEV)==0) ++db;
  /* Az eredmeny kozlese: */
  printf("A nevek kozt %d darab %s volt.\n",db,NEV); }
int strcmp(char s1[], char s2[]){
  int i;
  for(i=0; s1[i]!=0&&s1[i]==s2[i]; ++i);
  return(s1[i]-s2[i]);}
int getline(char s[],int n){
  int c,i;
  for(i=0;i<n&&(c=getchar())!=EOF&&c!='\n';++i) s[i]=c;
  s[i]='\0';
  while(c!=EOF&&c!='\n') c=getchar();
  return(i); }

Ha a forráskód egy sorát lezáró soremelés karaktert közvetlenül \ jel előzi meg, akkor mindkét karaktert elveti a fordító (pontosabban az előfeldolgozó), s a két fizikai sort egyesíti, és egynek tekinti. Az ilyen értelemben egyesített sorokat már a másodiktól kezdve folytatássornak nevezik. A main első printf-jét folytatássorral készítettük el.

A két elkészített függvény prototípusából és definíciójából vegyük észre, hogy a formális paraméter karaktertömb (és persze más típusú tömb is) méret nélküli!

Az strcmp megírásakor abból indultunk ki, hogy két karakterlánc akkor egyenlő egymással, ha ugyanazon pozícióikon azonos karakterek állnak, és ráadásul a lánczáró zérus is ugyanott helyezkedik el. Az i ciklusváltozót zérusról indítva, s egyesével haladva, végigindexeljük az s1 karaktertömböt egészen a lánczáró nulláig (s1[i]!=0) akkor, ha közben minden i-re az s1[i]==s2[i] is teljesült. Ebből a ciklusból tehát csak két esetben van kilépés:

  • Ha elértünk s1 karaktertömb lánczáró zérusáig (s1[i]==0).
  • Ha a két karakterlánc egyazon pozícióján nem egyforma érték áll (s1[i]!=s2[i]).

A visszaadott s1[i]-s2[i] különbség csak akkor:

  • Zérus, ha s1[i] zérus és s2[i] is az, azaz a két karakterlánc egyenlő.
  • Negatív, ha s1 kisebb (előbbre van a névsorban), mint s2.
  • Pozitív, ha s1 nagyobb (hátrébb van a névsorban), mint s2.

Az strcmp-t már megírták. Tárgykódja benne van a szabvány könyvtárban. Nem fáradtunk azonban feleslegesen, mert a szabvány könyvtári változat ugyanúgy funkcionál, mint a pelda10.c-beli. Használatához azonban be kell kapcsolni a prototípusát tartalmazó string.h fejfájlt.

Írjuk be a pelda10.c #include direktívája után a

#include <string.h>

és töröljük ki a forrásszövegből az strcmp prototípusát és definícióját! Végül próbáljuk ki!

Feltéve, hogy s1 és s2 karaktertömbök, lássuk be, hogy C programban, ugyan szintaktikailag nem helytelen, semmi értelme sincs az ilyen kódolásnak, hogy:

s1==s2, s1!=s2, s1>=s2, s1>s2, s1<=s2, s1<s2

E módon ugyanis s1 és s2 memóriabeli elhelyezkedését hasonlítjuk össze, azaz bizton állíthatjuk, hogy az s1==s2 mindig hamis, és az s1!=s2 pedig mindig igaz.

A getline ciklusváltozója ugyancsak zérusról indul, és a ciklus végéig egyesével halad. A ciklusmagból látszik, hogy a szabvány bemenetről érkező karakterekkel tölti fel az s karaktertömböt. A ciklus akkor áll le,

  • ha az s tömb kimerült, és nem tölthető tovább (i>=n), vagy
  • ha a beolvasott karakter EOF jelzés (c==EOF), vagy
  • ha a bejövő karakter soremelés (c=='\n').

Ezután kitesszük a tömb i-ik elemére a lánczáró zérust, s visszaadjuk i-t, azaz a behozott karakterlánc hosszát.

getline függvényünk jó próbálkozás az egy sor behozatalára a szabvány bemenetről, de van néhány problémája, ha a bemenet a billentyűzet:

1.A bemeneti pufferben levő karakterek csak soremelés (Enter) után állnak rendelkezésére, s addig nem.
2.Az EOF karakter (Ctrl+Z vagy Ctrl+D) érkezése nem jelenti tulajdonképpen a bemenet végét, mert utána még egy soremelést is végre kell hajtani, hogy észlelni tudjuk.
3.Ha a bemeneti pufferben az Enter megnyomásakor n-nél több karakter van, akkor a puffer (az egy sor) csak több getline hívással olvasható ki.

Írjuk csak át a pelda10.c main-jében a

while(getline(s,MAX)>0)

sort a következőre

while(getline(s,4)>0)

és a program indítása után gépeljük be a

GiziJaniKataJani<enter>
<enter>

sorokat!

Az első két probléma az operációs rendszer viselkedése miatt van, s jelenleg sajnos nem tudunk rajta segíteni. A második gondhoz még azt is hozzá kell fűznünk, hogy fájlvéget a billentyűzet szabvány bemeneten a getline-nak csak üres sor megadásával tudunk előidézni.

A harmadik probléma viszont könnyedén orvosolható, csak ki kell olvasni a bemeneti puffert végig a getline-ból való visszatérés előtt. Tehát:

int getline(char s[],int n){
  int c,i;
  for(i=0;i<n&&(c=getchar())!=EOF&&c!='\n';++i)s[i]=c;
  s[i]='\0';
  while(c!=EOF&&c!='\n') c=getchar();
  return(i); }
Lokális, globális és belső, külső változók

Ha alaposan áttanulmányozzuk a pelda10.c-t, akkor észrevehetjük, hogy mind az strcmp-ben, mind a getline-ban van egy-egy, int típusú i változó. Feltehetnénk azt a kérdést, hogy mi közük van egymáshoz? A rövid válasz nagyon egyszerű: semmi. A hosszabb viszont kicsit bonyolultabb:

  • Mindkét i változó lokális a saját függvényblokkjára.
  • A lokális változó (local variable) hatásköre az a blokk, amiben definiálták.
  • A lokális változó élettartama az az idő, míg saját függvényblokkja aktív, azaz benne van a vezérlés.

Tehát a két i változónak a névegyezésen túl nincs semmi köze sem egymáshoz. A változó hatásköre (scope) az a programterület, ahol érvényesen hivatkozni lehet rá, el lehet érni. Nevezik ezt ezért érvényességi tartománynak is.

A lokális változó hatásköre az a blokk, és annak minden beágyazott blokkja, amiben definiálták. A blokkon belülisége miatt a lokális változót belső változónak is nevezik.

A változó élettartama (lifetime) az az időszak, amíg memóriát foglal.

A lokális változó akkor jön létre (rendszerint a veremben), amikor a vezérlés bejut az őt definiáló blokkba. Ha a vezérlés kikerül onnét, akkor a memóriafoglalása megszűnik, helye felszabadul (a veremmutató helyrejön).

A függvényekben, így a main-ben is, definiált változók mind lokálisak arra a függvényre, amelyben deklarálták őket. Csak az adott függvényen belül lehet hozzájuk férni. Ez a lokális hatáskör. Futási időben a függvénybe való belépéskor jönnek létre, és kilépéskor meg is semmisülnek. Ez a lokális élettartam. A lokális változó kezdőértéke ebből következőleg "szemét". Pontosabban az a bitkombináció, ami azokban a memóriabájtokban volt, amit most létrejövetelekor a rendszer rendelkezésére bocsátott. Tehát a belső változónak nincs alapértelmezett kezdőértéke. Az alapértelmezett kezdőértéket implicit kezdőértéknek is szokták nevezni.

Mi dönti el, hogy a belső változó melyik blokkra lesz lokális? Nyilvánvalóan az, hogy az őt definiáló deklarációs utasítást melyik blokk elején helyezik el.

Tisztázzuk valamennyire a változó deklarációja (declaration) és definíciója (definition) közti különbséget! Mindkét deklarációs utasításban megadják a változó néhány tulajdonságát, de memóriafoglalásra csak definíció esetén kerül sor.

A lokális változó az előzőekben ismertetetteken kívül auto tárolási osztályú is.

Egészítsük ki újból a deklarációs utasítás szintaktikáját!

<tárolási-osztály> <típusmódosítók> <alaptípus> azonosítólista;

A tárolási-osztály a következő kulcsszavak egyike lehet: auto, register, static, vagy extern. E lecke keretében nem foglalkozunk a register és a static tárolási osztállyal!

A szintaktikát betartva most felírható, hogy

auto i;

ami az auto signed int típusú, i azonosítójú változó definíciója. Hogyan lehet auto tárolási osztályú a lokális változó? Nyilván úgy, hogy a lokális helyzetű deklarációs utasításban az auto tárolási osztály alapértelmezés. Az auto kulcsszó kiírása ezekben az utasításokban teljesen felesleges.

Foglaljuk össze a lokális változóval, vagy más néven belső változóval, kapcsolatos ismereteinket!

Neve:Bármilyen azonosító.
Típusa:Valamilyen típus.
Hatásköre:Az a blokk, ahol definiálták.
Élettartama:Amíg a blokk aktív, azaz a vezérlés
benne van.
Alapértelmezett tárolási osztálya:auto
Alapértelmezett kezdőértéke:Nincs.
Deklarációs utasításának helye:Annak a blokknak a deklarációs
része, ahol a változót használni kívánjuk.
Memóriafoglalás: Futási időben, többnyire a veremben.

Meg kell említeni, hogy a függvények formális paraméterei is lokális változóknak minősülnek, de

  • deklarációjuk a függvénydefiníció fej részében és nem a blokkjában helyezkedik el, és
  • értékük az aktuális paraméter értéke.

Az előfeldolgozáson átesett forrásmodult fordítási egységnek nevezik. A fordítási egység függvénydefiníciókból és külső deklarációkból áll. Külső deklaráció alatt a minden függvény "testén" kívüli deklarációt értjük. Az így definiált változót külső változónak, vagy globális változónak (global variable) nevezik. Az ilyen változó:

  • Hatásköre a fordítási egységben a deklarációs utasításának pozíciójától - az ún. deklarációs ponttól - indul, és a modul (forrásfájl) végéig tart. Ezt a hatáskört fájl hatáskörnek, vagy globális hatáskörnek nevezik.
  • Élettartama a program teljes futási ideje. A memória hozzárendelés már fordítási időben megtörténik. Rendszerint az ún. elsődleges adatterületen helyezkedik el, és biztos, hogy nem a veremben. Ez a statikus élettartam.
  • Alapértelmezett (implicit) kezdőértéke: minden bitje zérus. Ez az aritmetikai típusoknál zérus. Karaktertömb esetében ez az üres karakterlánc, hisz rögtön a lánczáró nullával indul.
  • Alapértelmezett tárolási osztálya extern.

Bánjunk csínján az extern kulcsszó explicit használatával, mert deklarációs utasításban való megadása éppen azt jelenti, hogy az utasítás nem definíció, hanem csak deklaráció! Más fordítási egységben definiált külső változót kell ebben a forrásmodulban így deklarálni a rá való hivatkozás előtt.

Extern deklaráció
3.5. ábra

Projektünk álljon e két forrásmodulból! Az int típusú i változót és a t karaktertömböt Forrásmodul 2-ben definiálták. Itt történt tehát meg a helyfoglalásuk. Ezek a változók Forrásmodul 1-ben csak akkor érhetők el, ha a rájuk történő hivatkozás előtt extern kulcsszóval deklarálják őket. Vegyük észre, hogy a Forrásmodul 1 elején elhelyezett deklarációs utasítások egyrészt globális szinten vannak, másrészt valóban csak deklarációk, hisz a tömb deklarációjában méret sincs! Persze azt, hogy a tömb mekkora, valahonnét a Forrásmodul 1-ben is tudni kell!

Írjuk át úgy a pelda10.c-t, hogy a beolvasott sor tárolására használatos tömböt globálissá tesszük, majd nevezzük át pelda11.c-re!

/* PELDA11.C: Nevszamlalas */
#include <stdio.h>
#define NEV "Jani" /* A szamlalt nev. */
#define MAX 29    /* A bemeneti sor maximalis merete.
                      Most egyben a leghosszabb nev is. */
int getline(void); /* A fuggveny prototipusok. */
int strcmp(char s2[]);
void main(void){
  int db; /* Nevszamlalo. */
  db=0;  /* A szamlalo nullazasa. */
  printf("A(z) %s nev leszamlalasa a bemeneten.\nAdjon\
meg soronkent egy nevet!\nProgramveg: ures sor.\n",NEV);
  /* Sorok olvasasa ures sorig a bemenetrol: */
  while(getline()>0)
    /* Ha a sor epp a NEV: */
    if(strcmp(NEV)==0) ++db;
  /* Az eredmeny kozlese: */
  printf("A nevek kozt %d darab %s volt.\n",db,NEV); }
char s[MAX+1]; /* Az aktualis nev */
int strcmp(char s2[]){
  int i;
  for(i=0; s[i]!=0&&s[i]==s2[i]; ++i);
  return(s[i]-s2[i]);}
int getline(void){
  int c,i;
  for(i=0;i<MAX&&(c=getchar())!=EOF&&c!='\n';++i)
    s[i]=c;
  s[i]='\0';
  while(c!=EOF&&c!='\n') c=getchar();
  return(i); }

Vegyük észre, hogy a pelda10.c- s verzióhoz képest a getline paraméter nélkülivé vált, és az strcmp-nek meg egyetlen paramétere maradt! Miután a globális s karaktertömb deklarációs pontja megelőzi a két függvény definícióját, a függvényblokkok s hatáskörében vannak. Belőlük tehát a tömb egyszerű hivatkozással elérhető, s nem kell paraméterként átadni. A getline második paramétere tulajdonképpen a MAX szimbolikus konstans volt, s ezt beépítettük a for ciklusba.

Elemezgessük egy kicsit a "külső változó, vagy paraméter" problémát!

  • Globális változókat használva megtakarítható függvényhíváskor a paraméterek átadása, vagyis kevesebb paraméterrel oldható meg a feladat.
  • Külső változókat manipuláló függvények másik programba viszont csak akkor másolhatók át és hívhatók meg változtatás nélkül, ha ezeket a változókat is velük visszük. Látszik, hogy az ilyen függvények mobilitását, újrafelhasználhatóságát csökkentik a járulékos, globális változók.
  • A csak paramétereket és lokális változókat alkalmazó függvények átmásolás után változtatás nélkül használhatók más programokban. Legfeljebb az aktuális paraméterek lesznek mások a hívásban.

Világos, hogy nincs egyértelműen és általánosan ajánlható megoldás a problémára. A feladat konkrét sajátosságai döntik el, hogy mikor milyen függvényeket kell írni, kellenek-e külső változók stb.

Summázzuk a globális változóval, vagy más néven külső változóval, kapcsolatos ismereteinket!

Neve:Bármilyen azonosító.
Típusa:Valamilyen típus.
Hatásköre:Globális, vagy fájl. A deklarációs ponttól a forrásmodul végéig tart.
Élettartama:Statikus. A program teljes futási ideje alatt él.
Alapértelmezett tárolási osztálya:extern
Alapértelmezett kezdőértéke:Minden bitje zérus.
Deklarációs utasításának helye:A forrásmodul minden függvénydefinícióján kívül.
Memóriafoglalás: Fordítási időben, vagy legalább is az indító program kezdődése előtt.
Inicializálás

Az inicializálás kezdőérték adást jelent a deklarációban, azaz az inicializátorok kezdőértékkel látják el az objektumokat (változókat, tömböket stb.).

A statikus élettartamú objektumok egyszer a program indulásakor inicializálhatók. Implicit (alapértelmezett) kezdőértékük tiszta zérus, ami:

  • Zérus az aritmetikai típusoknál.
  • Üres karakterlánc karaktertömb esetén.

A lokális élettartamú objektumok inicializálása minden létrejövetelükkor megvalósul, de nincs implicit kezdőértékük, azaz "szemét" van bennük.

Módosítsuk újra a deklarációs utasítás szintaktikáját változókra és tömbökre!

típus azonosító<=inicializátor>;
típus tömbazonosító[<méret>]<={inicializátorlista}>;

ahol az inicializátorlista inicializátorok egymástól vesszővel elválasztott sorozata, és a típus a <tárolási-osztály> <típusmódosítók> <alaptípus>-t helyettesíti.

A változókat egyszerűen egy kifejezéssel inicializálhatjuk, mely kifejezés opcionálisan {}-be is tehető. Az objektum kezdeti értéke a kifejezés értéke lesz. Ugyanolyan korlátozások vannak a típusra, és ugyanazok a konverziók valósulnak meg, mint a hozzárendelés operátornál. Magyarán az inicializátor hozzárendelés-kifejezés. Például:

char y = 'z', k; /* y 'z' értékű, és a k-nak meg */
                /* nincs kezdőértéke. */
int a = 10000; /* a 10000 kezdőértékű. */

C-ben a statikus élettartamú változók inicializátora csak konstans kifejezés lehet, ill. csak ilyen kifejezések lehetnek tömbök inicializátorlistájában. A lokális objektumok inicializátoraként viszont bármilyen legális kifejezés megengedett, mely hozzárendelés kompatibilis értékké értékelhető ki a változó típusára.

#define N 20
int n = N*2; /* Statikus objektum inicializátora
                csak konstans kifejezés lehet. */
/* . . . */
void fv(int par){
  int i = N/par; /* A lokális objektumé viszont
                    bármilyen legális kifejezés.
. . . */ }

Emlékezzünk vissza, hogy globális változók csak egyszer kapnak kezdőértéket: a program indulásakor. A lokális objektumok viszont mindannyiszor, valahányszor blokkjuk aktívvá válik.

A szövegben használatos objektum fogalom nem objektum-orientált értelmű, hanem egy azonosítható memória területet takar, mely konstans vagy változó érték(ek)et tartalmaz. Minden objektumnak van azonosítója (neve) és adattípusa. Az adattípus rögzíti az objektumnak

  • lefoglalandó memória mennyiségét és
  • a benne tárolt információ belsőábrázolási formáját.

Tömbök esetén az inicializátorlista elemeinek száma nem haladhatja meg az inicializálandó elemek számát!

float tomb[3] = {0., 1., 2., 3.}; /* HIBÁS: több
                                    inicializátor van, mint tömbelem. */

Ha az inicializátorlista kevesebb elemű, mint az inicializálandó objektumok száma, akkor a maradék objektumok a statikus élettartamú implicit kezdőérték adás szabályai szerint kapnak értéket, azaz nullázódnak:

float tmb[3] = {0., 1.}; /* tmb[0]==0.0, tmb[1]==1.0 és
                            tmb[2]==0.0. */

Az inicializálandó objektum lehet ismeretlen méretű is, ha az inicializátorlistából megállapítható a nagyság. Például:

int itmb[] = { 1, 2, 3}; /* Az itmb három elemű lesz. */
char nev[] = "Lali",    /* A lezáró '\0' karakter */
  csaladnev[] = "Kiss";  /* miatt 5 eleműek a tömbök.*/
Feladatok

1. Mit kell írni a pelda4.c megjegyzésbe tett részébe, hogy a Forint 1000-től 100-ig csökkenjen 100-asával?

#include <stdio.h>
#define ALSO 100      /* A tartomany also hatara */
#define FELSO 1000    /* A tartomany felso erteke */
#define LEPES 100      /* A lepeskoz */
#define ARFOLYAM 310.  /* Ft/euro arfolyam */
void main(void){
  int ft;
  printf("%9s|%9s\n---------+---------\n",
        "Forint", "Euro");
  for(/* ? */)
    printf("%9d|%9.2f\n", ft, ft/ARFOLYAM); }
/* ? */ helyére írandó kifejezés:
ft=FELSO; ft>=ALSO; ft=ft+LEPES
ft=FELSO; ft>=ALSO; ft=ft-LEPES
ft=FELSO; ft>=ALSO; ft=ft-
ft=FELSO; !(ft<ALSO); ft=ft-LEPES

2. Megpróbáltuk átalakítani a pelda4.c-t úgy, hogy most az Euró növekedjen 1-től 10-ig egyesével, de sajnos nem sikerült. Melyik sort kellene lecserélni, hogy jól működjön?

/*  1 */#include <stdio.h>
/*  2 */#define ALSO 1      /* A tartomany also hatara */
/*  3 */#define FELSO 10    /* A tartomany felso erteke */
/*  4 */#define LEPES 1      /* A lepeskoz */
/*  5 */#define ARFOLYAM 310.  /* Ft/euro arfolyam */
/*  6 */void main(void){
/*  7 */  int eur;
/*  8 */  printf("%9s|%9s\n---------+---------\n",
/*  9 */        "Euro", "Forint");
/* 10 */  for(eur=ALSO; eur<FELSO; eur=eur+LEPES)
/* 11 */    printf("%9d|%9.2f\n", eur, eur/ARFOLYAM); }
Melyik sort kellene lecserélni, hogy jól működjön?
/*  7 */  int ft;
/*  9 */         "Forint", "Euro");
/* 10 */  for(eur=ALSO; eur<=FELSO; eur=eur+LEPES)
/* 11 */    printf("%9d|%9.2f\n", eur, eur*ARFOLYAM); }

3. Megpróbáltuk átalakítani a pelda4.c-t úgy, hogy a Forint 100-tól 2000-ig növekedjen 100-asával, az eredmény pedig két oszloppárban, oszlopfolytonosan növekedjék. (Tehát az első oszlopban a Forint 100-tól 1000-ig nő, a másodikban pedig 1100-tól 2000-ig.) Sajnos nem jártunk teljes sikerrel.

/*  1 */#include <stdio.h>
/*  2 */#define ALSO 100      /* A tartomany also hatara */
/*  3 */#define FELSO 1000    /* A tartomany felso erteke */
/*  4 */#define LEPES 100      /* A lepeskoz */
/*  5 */#define ARFOLYAM 310.  /* Ft/euro arfolyam */
/*  6 */void main(void){
/*  7 */  int ft;
/*  8 */  printf("%9s|%10s %9s|%10s\n"
/*  9 */        "---------+--------- ---------+---------\n",
/* 10 */        "Forint", "Euro", "Forint", "Euro");
/* 11 */  for(ft=ALSO; ft<=FELSO; ft=ft+FELSO+LEPES)
/* 12 */    printf("%9d|%9.2f %9d|%9.2f\n",
/* 13 */          ft, ft/ARFOLYAM, ft+FELSO, (ft+FELSO)/ARFOLYAM); }
Melyik sort kellene lecserélni, hogy jól működjön a program?
/*  3 */#define FELSO 2000     /* A tartomany felso erteke */
/*  7 */  char ft;
/* 11 */  for(ft=ALSO; ft<=FELSO; ft=ft+LEPES)
/* 13 */           ft, ft/ARFOLYAM, ft+FELSO, ft+FELSO/ARFOLYAM); }

4. Megpróbáltuk átalakítani a pelda4.c-t úgy, hogy a Forint 100-tól 2000-ig növekedjen 100-asával, az eredmény pedig két oszloppárban, sorfolytonosan növekedjék. (Tehát az első oszlopban a Forint 100-tól 1900-ig nő 200-asával, a másodikban pedig 200-tól 2000-ig.) Sajnos nem jártunk teljes sikerrel.

/*  1 */#include <stdio.h>
/*  2 */#define ALSO 100      /* A tartomany also hatara */
/*  3 */#define FELSO 1900    /* A tartomany felso erteke */
/*  4 */#define LEPES 100      /* A lepeskoz */
/*  5 */#define ARFOLYAM 310.  /* Ft/euro arfolyam */
/*  6 */void main(void){
/*  7 */  int ft;
/*  8 */  char c;
/*  9 */  c = '\n';
/* 10 */  printf("%9s|%10s %9s|%10s\n"
/* 11 */        "---------+--------- ---------+---------\n",
/* 12 */        "Forint", "Euro", "Forint", "Euro");
/* 13 */  for(ft=ALSO; ft<=FELSO; ft=ft+LEPES) {
/* 14 */    printf("%9d|%9.2f%c", ft, ft/ARFOLYAM, c);
/* 15 */    if(c == ' ') c='\n'; else c=' '; } }
Melyik sort kellene lecserélni, hogy jól működjön a program?
/*  3 */#define FELSO 2000     /* A tartomany felso erteke */
/*  9 */  c = ' ';
/* 13 */  for(ft=ALSO; ft<=FELSO; ft=ft+2*LEPES) {
/* 15 */    if(c != ' ') c='\n'; else c=' '; } }

5. Alakítsa át úgy a pelda4.c programot, hogy a felhasználótól először bekéri, hány oszloppárban szeretné megjeleníteni az adatokat! Az oszloppárok száma legalább 1, legfeljebb 4 lehet. Ismételtesse meg a bevitelt, ha hibás adatot adtak meg! Végül jelenítse meg az eredményt sorfolytonosan, azaz egy soron belül a Forint 100-asával nőjön, a legkisebb Forint érték 100 legyen, a legmagasabb pedig az oszloppárok számától függően 1000, 2000, 3000 vagy 4000!

/* TOBBOSZL.C: Forint-euro atszamitas 1 - 4 oszlopban. */
#include <stdio.h>
#define ALSO 100      /* A tartomany also hatara. */
#define FELSO 1000    /* A tartomany felso erteke. */
#define LEPES 100    /* A lepeskoz. */
#define ARFOLYAM 310. /* Ft/euro arfolyam. */
#define MAX 4        /* Max. lista-oszlopparok szama. */
void main(void){
  int ft, i, max;
  printf("\nForint-euro atszamitas\n\n");
  max=0;
  while(max<1||max>MAX){
    printf("Oszlopparok szama(1 - %d)?",MAX);
    max=getchar()-'0';}
  for(i=0; i<max;++i)printf("%9s|%9s|","Forint", "Euro");
  if(max!=MAX)putchar('\n');
  for(i=0; i<max;++i)printf("---------+---------+");
  if(max!=MAX)putchar('\n');
  for(ft=ALSO,i=0;ft<=FELSO*max;ft=ft+LEPES){
    printf("%9d|%9.2f|", ft, ft/ARFOLYAM);
    if(++i==max) { i=0; if(max!=MAX)putchar('\n');}}}

6. Alakítsa át a pelda4.c-t úgy, hogy a forint 100-tól 10000-ig növekedjen 100-asával! A lista nem futhat el a képernyőről, azaz fejléccél ellátva lapozhatóan kell megjelentetni! Ez azt jelenti, hogy először kiíratjuk a lista egy képernyő lapnyi darabját, majd várunk egy gombnyomásra. A gomb leütésekor produkáljuk a lista következő lapját, és újból várunk egy gombnyomásra, és így tovább.

/* LAPOZ.C: Forint-euró átszámítási táblázat lapozással. */
#include <stdio.h>
#define ALSO 100      /* A tartomany also hatara. */
#define FELSO 10000  /* A tartomany felso erteke. */
#define LEPES 100    /* A lepeskoz. */
#define ARFOLYAM 310. /* Ft/euro arfolyam. */
#define LAPSOR 20    /* Max. sorok szama egy lapon. */
void main(void){
  int ft, sor;
  sor=LAPSOR+1; /* A lista induljon uj lappal! */
  for(ft=ALSO; ft<=FELSO; ft=ft+LEPES){
    /* Ha elertuk a lap veget, akkor meg kell jelentetni a
      lista fejlecet, és nullazni kell a sorszamlalot. */
    if(sor>=LAPSOR){
      printf("\n\n%9s|%9s\n---------+---------\n",
            "Forint", "Euró");
      sor=0; }
    /* Megjelentetunk egy sort, tehat leptetni kell a
      sorszamlalot. */
    ++sor;
    printf("%9d|%9.2f\n", ft, ft/ARFOLYAM);
    /* A lap legutolso soranak kiirasa utan varni kell egy
      Enter leutesere. */
    if(sor==LAPSOR){
      printf("\nA tovabblistazashoz nyomjon Enter-t! ");
      while(getchar()!='\n');} } }

7. Készítsen programot, mely a szabvány bemenetet EOF-ig olvassa, és közben megállapítja, hogy hány sor volt a bemeneten! A bemenet karakterei közt a '\n'-eket kell leszámlálni. Az utolsó sor persze lehet, hogy nem '\n'-nel végződik, hanem EOF-fal.

#include <stdio.h>
void main(void) {
  int c, nl;
  nl = 0; /* A sorok szama kezdetben 0 */
  printf("A bemenet sorainak leszamlalasa:\n\n");
  printf("Gepeljen Ctrl+Z -ig!\n\n");
  while((c=getchar()) != EOF)
    if(c == '\n') ++nl;
  printf("\nA bemeneten %d sor volt.\n", nl); }

8. Készítsen programot, mely a szabvány bemenetet EOF-ig olvassa, és közben megállapítja, hogy hány szó volt a bemeneten! A szó nem fehér karakterekből áll. A szavakat viszont egymástól fehér karakterek választják el. Az utolsó szó lehet, hogy nem fehér karakterrel zárul, hanem EOF-fal.

#include <stdio.h>
#define YES 1
#define NO 0
void main(void) {
  int c, nw, inword;
  inword = NO; /* Kezdetben nem vagyunk szo belsejeben. */
  printf("A bemenet szavainak leszamlalasa\n");
  printf("A bemenet vege: Ctrl+Z vagy EOF.\n\n");
  nw = 0; /* A szavak szama kezdetben 0 */
  while((c=getchar()) != EOF) {
    /* Feher karakter eseten kilepunk a szobol */
    if(c==' ' || c=='\n' || c=='\t') inword=NO;
    /* Ha nem feher karakter erkezik es nem vagyunk szo belsejeben, */
    else if(inword == NO) {
      /* akkor most belelepunk, */
      inword=YES;
      /* es egyidejuleg a szavak szama eggyel no. */
      ++nw;
}}
printf("Szavak szama: %d\n", nw); }

9. Megpróbáltuk úgy módosítani a pelda8.c programot, hogy összeszámolja a különféle számjegy karaktereket. Az összes többi karaktert egy kategóriának tekintettük, és ezek számát is megjelenítettük, de sajnos az átalakítás nem sikerült tökéletesen.

/* 1*/#include <stdio.h>
/* 2*/#define SZAMOK 10  /* Szamjegyek szama */
/* 3*/void main(void){
/* 4*/  int k;          /* Utolso karakter es ciklusv. */
/* 5*/      egyeb,      /* Egyeb jelek szamlaloja. */
/* 6*/      szam[SZAMOK];/* Szamjegyszamlalok. */
/* 7*/  egyeb=0;        /* Kezdoertek adas. */
/* 8*/  for(k=0; k<SZAMOK; ++k) szam[k]=0;
/* 9*/  printf("Bemenet szamjegyeinek leszamlalasa\n"
/*10*/        "EOF-ig, vagy Ctrl+Z-ig.\n");
/*11*/  while(k=getchar()!=EOF)
/*12*/    /* Szamjegyek: */
/*13*/    if(k>'0'&&k<'9')++szam[k-'0'];
/*14*/    /* Mas karakterek: */
/*15*/    else ++egyeb;
/*16*/  /* Eredmények közlése: */
/*17*/  printf("\nBetu|Darab\n----+-----\n");
/*18*/  for(k=0; k<SZAMOK; ++k)
/*19*/    printf("%4c|%5d\n", k+'0', szam[k]);
/*20*/  printf("\nEgyeb karakter: %5d\n", egyeb); }
Jelölje meg azokat a sorokat, amiket le kellene cserélni, hogy a program az elvárásnak megfelelően működjön!
/* 8*/  for(k=0; k<SZAMOK; ++k) szam[k]='0';
/*11*/  while((k=getchar())!=EOF)
/*13*/    if(k>='0'&&k<='9')++szam[k-'0'];
/*15*/    else ++(egyeb-'0');

10. Megpróbáltunk elkészíteni egy copy függvényt, ami egy karakterlánc tartalmát egy másik tömbbe másolja. Sajnos nem lett tökéletes.

/*1*/#include <stdio.h>
/*2*/void copy(char cel[], char forras[]) {
/*3*/  /* forras masolása celba. celt elég nagynak tételezi fel. */
/*4*/  int i = 0;
/*5*/  while((forras[i]=cel[i]) != '\0') ++i; }
/*6*/void main(void){
/*7*/  char honnan[] = "Rozi", hova[4];
/*8*/  copy(hova, honnan);
/*9*/  printf("%s --> %s\n", honnan, hova);}
Kérem, jelölje meg, hogy melyik sorokat kellene megváltoztatni!
/*1*/#include <studio.h>
/*5*/  while((cel[i]=forras[i]) != '\0') ++i; }
/*5*/  while((cel[i]==forras[i]) != '\0') ++i; }
/*7*/  char honnan[] = "Rozi", hova[5];

11. Megpróbáltunk elkészíteni egy length függvényt, ami egy karakterlánc hosszát adja eredményül. Sajnos nem lett tökéletes.

/*1*/#include <stdio.h>
/*2*/int length(char s[]) {
/*3*/  int i;
/*4*/  while(s[i]) ++i;
/*5*/  return i;}
/*6*/void main(void){
/*7*/  char nev[] = "Rozi";
/*8*/  printf("%s hossza: %d\n", nev, length(nev));}
Kérem, jelölje meg az összes olyan sort, amivel helyessé lehetne tenni a programot!
/*3*/  int i = 0;
/*3*/  int i; i = 0;
/*4*/  while(s[i] == '\0') ++i;
/*8*/  printf("%c hossza: %d\n", nev, length(nev));}