Miskolci Egyetem
Általános Informatikai Tanszék
Bevezetés a C programozási nyelvbe
Oktatási segédlet mûszaki informatikus
hallgatók számára
Készítette:
Ficsor Lajos
Miskolc
1999
Ez az oktatási segédlet a Miskolci Egyetem mûszaki informatikus hallgatói részére készült, a Software fejlesztés I. címû tárgy elsajátításának megkönnyítésére.
A jegyzet feltételezi, hogy olvasója rendelkezik alapvetõ programozástechnikai alapismeretekkel (alapvetõ vezérlési szerkezetek, típusok stb), így célja nem programozástechnikai bevezetés, hanem a C programozási nyelv áttekintése. Ugyanakkor az egyes C nyelvi szerkezetek használatát igyekszik példákkal szemléltetni, amelyek egyben az alapvetõ algoritmusok ismeretének felelevenítését is segítik.
A jegyzet alapja a szabványos (ANSI C) nyelv. Terjedelmi okok miatt nem törekedhettünk a teljességre már az egyes nyelvi elemek ismertetésénél sem, fõleg pedig a nyelv használatához egyébként alapvetõen szükséges szabványos függvénykönyvtár esetén.
A jegyzetet példaprogramok egészítik ki. A példaprogramok között megtalálható minden olyan kód, amely a szövegben szerepel, de találunk további példákat is.
A C programozási nyelvet eredetileg a UNIX operációs rendszer részére fejlesztette ki Dennis M. Ritchie 1972-ben, az AT&T Bell Laboratories-ben, részben a UNIX rendszerek fõ programozási nyelvének szánva, részben magának az operációs rendszernek és segédprogramjainak a megírására használva. Az idõk során ezen a szerepen messze túlnõve kedvelt általános célú programozási nyelvvé vált.
Brian W. Kernighan és Dennis M. Ritchie 1978-ban adták ki a The C programming Language (A C programozási nyelv) címû könyvüket, amely hamarosan a nyelv kvázi szabványává vált. Valamennyi fordítóprogram írója - és ezért valamennyi programozó is - ezt tekintette a nyelv definíciójának. Ez volt a "K&R C". A nyelv folyamatos használatának tapasztalatait is figyelembe véve az ANSI (az amerikai szabványügyi hivatal) 1989-ben hivatalosan is szabványosította a C nyelvet. Ezt hívjuk ANSI C-nek. Ma már valamennyi fordítóprogramnak ezzel a szabvánnyal összhangban kell készülnie. A jegyzet további részében C programozási nyelv alatt mindig az ANSI C-t értjük.
A nyelv elterjedését mutatja, hogy ma valamennyi elterjedt hardware-re (mikrogépektõl a nagy main-frame rendszerekig) és operációs rendszer alá elkészítették a fordítóprogramját, az esetek többségében többet is.
egyszerû alapobjektumok: karakterek, számok,címek
nincsenek összetett objektumokat kezelõ utasítások
nincsenek input-output utasítások
a fentiekbõl következõen viszonylag kicsi, és könnyen elsajátítható
végül ezek miatt kicsi és hatékony fordítóprogram készíthetõ hozzá.
minden fejlesztési környezetben rendelkezésre álló szabványos függvények
adott alkalmazási terület speciális feladatainak megoldását segítõ függvények
gépspecifikus, de az adott hardware speciális kezelését is lehetõvé tevõ függvények
A fenti függvények használata - a feladat jellegétõl függõen - lehetõvé teszi a gépfüggetlen programozást és az adott hardware sajátosságait maximálisan kihasználó - és ezért nehezen hordozható - programok írását is.
Minden C program az alábbi alapvetõ felépítést mutatja:
preprocesszor direktívák, pragmákglobális deklarációk main(){lokális deklarációkutasítások}függvény - definiciók
A C programok az alábbi alapvetõ szintaktikai egységekbõl épülnek fel:
azonosítók
kulcsszavak
állandók
karakterláncok
operátorok
egyéb elválasztók
megjegyzések
Betûk és számjegyek sorozata, betûvel vagy _ (aláhúzás) karakterrel kell kezdõdnie. A nagy -és kisbetûk különbözõek. Az azonosítók tetszõleges hosszúságúak lehetnek, de implementációtól függõen csak az elsõ meghatározott számú karakterük vesz részt a megkülönböztetésben. (Például a BORLAND C++ 3.1 esetén alapértelmezésben az elsõ 32 karakter szignifikáns. Ez az érték változtatható.)
Csak meghatározott célra használható azonosítók. Kisbetûvel kell írni. (Tehát int kulcsszó, INT vagy Int általános célú azonosító - bár nem illik használni.)
A hivatkozási nyelv az alábbi azonosítókat tartja fenn kulcsszónak:
auto | double | int | struct |
break | else | long | switch |
case | enum | register | typedef |
char | extern | return | union |
const | float | short | unsigned |
continue | for | signed | void |
default | goto | sizeof | volatile |
do | if | static | while |
Az egyes implementációk még újabb azonosítókat is kezelhetnek fenntartott kulcsszavakként.
A számjegyek sorozatát tartalmazó, nem nullával kezdõdõ egész szám decimális állandó. Az egész állandó megadható még az alábbi alakokban:
oktális, ha vezetõ 0-val kezdõdik
hexadecimális, ha 0X vagy 0x vezeti be. A 10-15-ig terjedõ számjegyek jelölésére az a-f vagy A-F karakterek használhatók.
Ha a decimális állandó értéke az adott implementáció elõjeles "int" típusának ábrázolási tartományán, vagy ha az oktális illetve hexadecimális állandó az "unsigned int" típus ábrázolási tartományán kívül esik, az állandó automatikusan "long" típusúvá alakul. (Részletesebben lásd az alaptípusok ismertetésénél.) Ha az egész állandót l vagy L betû követi, mindenképpen "long"- nak megfelelõ helyet foglal el.
Aposztrófok között egy karakter. Értéke az adott karakter kódja.
Bizonyos, nem nyomtatható karakterek helyett írható úgynevezett "Escape -szekvencia", az alábiak szerint:
\n |
újsor(LF) |
\t |
vízszintes tabulátor |
\v |
függõleges tabulátor |
\b |
backspace (BS) |
\r |
kocsivissza (CR) |
\f |
lapdobás (FF) |
\a |
hangjelzés (BELL) |
\\ |
backslash (\) |
\? |
Kérdõjel |
\' |
aposztróf |
\" |
idézõjel |
\ooo |
az oktális ooo kódú karakter (ooo egy, két vagy három jegyû oktális szám) |
\xhh |
a hexadecim'ális hh kódú karakter |
Ha a \ karaktert nem a fentiek valamelyike követi, a \ figyelmen kívül marad.
Az
egészrész.törtrész E kitevõ
vagy
egészrész.törtrész e kitevõ
alakú konstans lebegõpontos számot jelent.
Nem hiányozhat egyszerre az egészrész és a törtrész. A tizedespont vagy a kitevõrész közül az egyik elmaradhat, de a kettõ egyszerre nem. A kitevõrészben az e vagy E betû és az azt követõ szám csak együtt hiányozhat. Utótagként megadható az f vagy F, amely egyszeres pontosságot, illetve az l vagy L, amely dupla pontosságot ír elõ. Utótag hiányában duplapontosságú.
Idézõjelek közé zárt karaktersorozat. Az utolsó karaktere után 0 byte kerül. Használhatók az Escape-szekvenciák. Karakterláncon belül idézõjel \" alakban írható. Ha több sorban fér ki, a sort \ -el kell zárni.
A karakterláncot a fordítóprogram olyan static tárolási osztályú karaktertömbként kezeli, amelyet a megadott karakterek inicializálnak.
A C nyelvben nagyon sokféle operátor van, egy- két- és háromoperandusúak lehetnek. Az operátorokat teljes részletességgel a kifejezéseknél ismertetjük.
Legfontosabb a ; (pontosvesszõ), amely az utasítás végét jelzi, de ilyen a { és } is.
/* -al kezdõdõ, */ -el végzõdõ karaktersorozat. Nem skatulyázható egymásba, de tetszõleges hosszúságú lehet.
A C nyelvben elõre definiált alaptípusokat, és ezekbõl származtatott összetett típusokat (aggregátumokat) használhatunk.
Ebben a pontban felsoroljuk az összes elõre definiált típust és a helyfoglalásukat.
Típusnév |
Hossz |
char |
1 byte |
int |
gépfüggõ, a "természetes"hossz (szóhossz) |
short (int) |
(legalább) 16 bit |
long (int) |
(legalább) 32 bit |
A nyelv csak azt írja elõ, hogy a típusok hosszára a
short <= int és int <= long
teljesüljön.
Mindegyik típusnév elé írható egy kulcsszó, ami az elõjeles/elõjel nélküli ábrázolást írja elõ, az alábbiak szerint:
signed (elõjeles) - a char kivételével ez a default
unsigned (elõjel nélküli)
A char típus elõjelessége gépfüggõ. Csak az biztos, hogy minden nyomtatható karakter pozitív, mint egész szám.
A char típus a neve szerint karakterek kezelésére szolgál. Mivel azonban egy karakter tárolása a kódjának, mint egész számnak a tárolását jelenti, ez a típus minden további nélkül használható rövig egész számként, és alkalmazható minden aritmetikai kifejezésben.. Valójában a C nyelv tehát nem tartalmaz karakter típust.
Típusnév |
Hossz |
float |
gépfüggõ (általában 4 byte) |
double |
gépfüggõ (általában 8 byte) |
long double |
gépfüggõ (általában 16 byte) |
A void típusnevet a C nyelv speciális célokra tartja fenn.
Objektumnak nevezzük a C nyelvben a memória valamely mûveletekkel kezelhetõ részét.
A balérték objektumra hivatkozó kifejezés. (Tulajdonképpen amelynek meghatározott címe van a memóriában.) Kézenfekvõ példa a változó, de - mint késõbb látni fogjuk - bonyolultabb szerkezet is lehet. Nevét onnan kapta, hogy állhat értékadás baloldalán. (De nem csak ott!)
A kifejezés operandusok és operátorok sorozata. A kiértékelés sorrendjét az operátorok precedenciája határozza meg, és az, hogy balra vagy jobbra kötnek.
A precendencia - sorrendtõl eltérõ kiértékelési sorrendet zárójelezéssel írhatunk elõ.
Kommutatív és asszociatív operátorokat (*, +, &, |, ~) tartalmazó kifejezések kiértékelési sorrendje (még zárójelezés esetén is!) meghatározatlan.
A kiértékelés során konverziók történ(het)nek: ezt nevezzük szokásos aritmetikai konverziónak. A konverzió pontos szabályait a nyelv definíciója rögzíti (lásd pl. [2] 215. oldal), de itt nem idézzük, mert elsõ olvasásra bonyolultnak tûnik. A lényege az, hogy mindig a pontosabb számolás és az adatvesztés elkerülésének irányába konvertálódnak a típusok. Így például az egész lebegõpontossá, a rovid hosszabbá, az elõjeles elõjel nélkülivé alakul, ha szükséges.
Az operátorok felsorolása a precedenciájuk csökkenõ sorrendjében történik. Egy alponton belül a precedencia azonos és a felsorolás teljes.
( ) : zárójelek
[ ] : indexelés
. : hivatkozás struktúra-tagra
-> : hivatkozás struktúra-tagra struktúra-mutatóval
Csoportosítás balról jobbra.
Csoportosítás jobbról balra.
*kifejezés |
indirekció (hivatkozás adott címen levõ értékre) |
&balérték |
mutató - képzés |
+kifejezés |
egyoperandusú + (szimmetria okokból) |
-kifejezés |
egyoperandusú - |
!kifejezés |
logikai nem: ha a kifejezés 0, az eredmény 1, ha a kifejezés nem 0, az eredmény 0. Egész értéket ad. |
~kifejezés |
az egész kifejezés értékének 1-es komplemense. (Bitenkénti negációja) |
++ balérték |
inkrementálás |
-- balérték |
dekrementálás |
(típusnév)kifejezés |
a kifejezés a megadott típusúvá alakul át ("cast") |
sizeof kifejezés |
az operandus mérete byte-ban |
sizeof (típusnév) |
az adott típus mérete byte-ban |
A balérték ++ és a balérték-- speciális C nyelvi operátorok. Hatására a balérték értéke eggyel nõ, vagy csökken. Ha a ++ vagy -- a balértéktél balra van, az érték megváltozik, és ezután ez a megváltozott érték kerül felhasználásra. Ha a ++ vagy -- a balérték után helyezkedett el, a balérték felhasználódik, majd utána változik meg az értéke.
Csoportosítás balról jobbra.
* : szorzás
/ : osztás
% : maradékképzés
Pozitív egészek osztása esetén az eredmény csonkul, ha valamelyik negatív, az eredmény gépfüggõ lehet. (Általában az osztandó és a maradék elõjele megegyezik.)
Az a%b az a- nak b- vel való osztása során kapott maradékot jelenti. Az a-nak és b-nek itegrális típusúnak kell lennie.
Mindig igaz, hogy
(a/b)*b + a%b = a (ha b # 0)
Csoportosítás balról jobbra.
+ : összeadás
- : kivonás
Csoportosítás balról jobbra.
a<<b az a-t, mint bitmintát balra lépteti b bittel, a jobboldalon 0 bitek lépnek be.
a>>b mint fennt, de jobbra léptet. A belépõ bit 0, ha a unsigned, egyébként az elõjel bit lép be.
A mûvelet végzése elõtt az elõzõekben említett aritmetikai konverziók végrehajtódnak. Az eredmény int típusú lesz. a és b csak egész típusú lehet, b-nek pozitívnak kell lennie. Ha b negatív, vagy értéke túl nagy, az eredmény határozatlan.
Csoportosítás balról jobbra.
< > <= >=
Értékük int típusú, és 0 (hamis) vagy 1 (igaz). Megjegyzés: mutatók is összehasonlíthatók!
Csoportosítás balról jobbra.
== : egyenlô
!= : nem egyenlô
Értékük int típusú, és 0 (hamis) vagy 1 (igaz). Megjegyzés: mutatók is összehasonlíthatók!
Jele: & Mindkét operandusnak integrális típusúnak kell lennie.
Jele: ^ Mindkét operandusnak integrális típusúnak kell lennie.
Jele: | Mindkét operandusnak integrális típusúnak kell lennie.
Jele: && Mindkét operandusnak valamilyen alaptípusnak vagy mutatónak kell lennie. Az eredmény 0 (hamis) vagy 1 (igaz). Balról jobbra hajtódik végre, és a második operandus nem értékelõdik ki, ha az elsõ értéke 0!
Jele: || Mindkét operandusnak valamelyik alaptípusnak vagy mutatónak kell lennie. Az erednmény 0 (hamis) vagy 1 (igaz). Balról jobbra értékelõdik ki, és a második operandus nem értékelõdik ki, ha az elsõ értéke nem nulla.
A feltételes kifejezés formája:
k1 ? k2: k3
Balról jobbra csoportosít. Végrehajtása: Kiértékelõdik az elsõ kifejezés, és ha annak értéke nem 0, az eredmény a k2 lesz, egyébként a k3. A k2 és a k3 közül csak az egyik (a szükséges) értékelõdik ki.
Például az
a = k1? k2 : k3
értékadás egyenértékû az
if (k1) a=k2;else a=k3;
programrészlettel.
Mindegyik értékadó operátor jobbról balra csoportosít. Két fajtája van:
balérték = kifejezés
A kifejezés kiértékelõdik, a balérték (esetleg konverzió után) felveszi ezt az értéket, és ez lesz a mûvelet értéke is.
balérték x= kifejezés
ahol x az alábbi mûveletek egyike lehet:
+, -, *, /, %, >>, <<, &, ^, !
Az
E1 x= E2
(E1, E2 kifejezések) hatása ugyanaz, mint az
E1=E1 x E2
kifejezésnek, de az E1 csak egyszer értékelõdik ki.
Megjegyzés
A C nyelvben az értékadó kifejezés kétarcú: pontosvesszõvel lezárva értékadó utasításként viselkedik, de írható bárhová, ahová kifejezést lehet írni, és ilyenkor értéke a baloldal értéke.
Példa: az alábbi két programrészlet egyenértékû:
a=kifejezés if(a>10) utasítás; if ( (a=kifejezés)>10 ) utasítás;
Itt az a=kifejezés körüli zárójel nem fölösleges, mert az = operátor precedenciája kisebb, mint a > operátoré. Az a=kifejezés > 10 egyenértékû az a=(kifejezés>10) alakkal, aminek hatására a a 0 vagy 1 értéket veszi föl.
Formája:
K1,K2
ahol K1 és K2 kifejezések. Hatására elõbb a K1, majd a K2 kifejezés kiértékelõdik. A mûvelet eredménye a K2 értéke.
A könnyebb áttekinthetõség kedvéért táblázatba foglalva megismételjük az operátor jeleket.
Csoport |
Operátorok |
Asszocia- tivitás |
elsõdleges |
(), [], ->, . |
b-j |
egyoperandusú |
cast, sizeof, &, *, ++, --, ~ ! |
j-b |
multiplikatív |
*, /, % |
b-j |
additív |
+, - |
b-j |
eltolás |
<<, >> |
b-j |
relációs |
<, <=, >, >= |
b-j |
egyenlõség |
==, != |
b-j |
AND |
& |
b-j |
XOR |
^ |
b-j |
OR |
| |
b-j |
logikai AND |
&& |
b-j |
logikai OR |
|| |
b-j |
értékadás |
=, +=, -=, /=, %=, >>=, <<=, &=, ^= |
j-b |
kif. lista |
, (vesszõ) |
b-j |
Állandó kifejezés az, amelyben
egész állandók
karakterállandók
sizeof kifejezések
valamint az alábbi operátorok szerepelhetnek:
+ - * / % & I ^ << >> == != < > <= >=
Zárójelezés és feltételes kifejezés a fenti elemekbõl megengedett. Valós konstans vagy változó kezdõértékadásához valós operandusok is lehetnek.
Használhatók:
kezdeti értékként
tömbdeklarációban
case szerkezetben
A tömb lehetõvé teszi, hogy egy név alatt összefoglalhassunk több értéket. A tömb valamennyi eleme azonos típusú. Az egyes értékekre indexeléssel (vagy mutatókifejezéssel) hivatkozhatunk. Az indexek számától függõen a tömb lehet egy- vagy többdimenziós.
A tömböket deklarálni kell. A deklarációban meg kell adni a tömb nevét, indexeinek számát és az elemek darabszámát. Az indexek alsó határa 0!
A deklaráció formája:
tipus név[dbszám]{[dbszám]...}
ahol "dbszám" az elemek száma, tetszõleges állandó kifejezés. Példák:
char line[80];int matrix[50][60];
A tömbelemek száma inicializálással közvetetten is megadható! (Részletesen lásd késõbb!)
A tömb egyes elemeire való hivatkozásnál minden index helyére egy egész kifejezés írható. A kifejezés deklarált határok közé esését általában nem ellenõrzi a program!
Az alábbiakban felsoroljuk a C nyelv valamennyi utasítását, szintaktikai és szemantikai leírásával. Az utasítások formájának leírásánál a kulcszavakat és az utasítás egyéb kötelezõ "tartozékait" vastagított szedéssel emeljük ki.
Formája:
kifejezés;
A kifejezés legtöbbször értékadás vagy függvényhívás.
Formája:
összetett uasítás:
{utasítások}
blokk:
{deklarációkutasítások}
Összetett utasítás mindenütt lehet, ahol a szintaktikában "utasítás" szerepel. Lehetséges (de kerülendõ) az összetett utasítás belsejébe való ugrás.
Formái:
if (kifejezés ) utasítás1; if ( kifejezés ) utasítás1;else utasítás2;
Kiértékelôdik a kifejezés. Ha értéke nem nulla, utasítás1 hajtódik végre. Ha a kifejezés 0, akkor a 2. formánál az utasítás2 hajtódik végre, majd mindkét formánál a következõ utasítással folytatódik a program.
utasítás1 és utasítás2 újabb feltételes utasításokat tartalmazhat. Az egymásba skatulyázás szabálya: egy else mindig az utoljára talált else nélküli if-hez kapcsolódik!
Speciális esete az alábbi
if (kif1) ut1else if (kif2) ut2else if (kif3) ut3 . . .else utn;
feltétellánc, amelyben a feltételek sorban értékelõdnek ki az elsõ nem nulla eredményig. Csak az ehhez a feltételhez tartozó utasítás hajtódik végre.
Formája:
while ( kifejezés ) utasítás;
Az utasítás mindaddig ismétlõdik, míg kifejezés értéke nem nulla. A kifejezés kiértékelése az utasítás végrehajtása elõtt történik.
Formája:
do utasítás while ( kifejezés );
Hasonlóan mûködik, mint a while, de a vizsgálat az utasítás végrehajtása után történik.
Formája:
for ( k1; k2; k3) utasítás;
Egyenértékû az alábbi programrészlettel:
k1;while (k2) { utasítás; k3; }
Ha k2 hiányzik, helyére 1 íródik. Ha k1 vagy k3 elmarad, a fenti kifejtésbõl is elmarad.
Formája:
switch (kif) { case (ak1): u1; case (ak2): u2; case (akn): un; default: <-- ez nem kötelezõ! un+1; };
ahol kif egy int értéket adó kifejezés kell legyen, ak1, ak2 stb. állandó kifejezések, u1, u2 stb. pedig utasítások.
Mûködés:
Formája:
break;
Hatására befejezõdik a break-et körülvevõ legbelsõ while, for, do vagy switch utasítás végrehajtása, és a vezérlés átadódik a következõ utasításra.
Formája:
continue;
Hatására a vezérlés a körülvevõ legbelsõ while, do vagy for utasítás ciklusvégére adódik át.
Formája:
return;
A függvény végrehajtása befejezõdik, és a hívó függvényhez tér vissza a vezérlés. A függvény értéke definiálatlan.
return kifejezés;
Visszatérés a hívó függvényhez, a függvény értéke a kifejezés lesz. (Típuskonverzióval, ha szükséges.)
Formája:
goto azonosító;
A vezérlés az azonosító címkéjû utasításra kerül.
Bármelyik utasítást megelõzheti az
azonositó:
alakú címke, és így goto utasítás célpontja lehet.
Egy magában álló pontosvesszõ. Legtöbbször azért használjuk, mert címkéje lehet, vagy mert a ciklustörzs üres.
A C nyelv nem tartalmaz input-output utasításokat, ezeket szabványos függvényekkel oldja meg. Az úgynevezett standard input (ami alapesetben a billentyûzet) és a standard output (alapesetben a képernyõ) kezelésére szolgáló legegyszerûbb függvényeket ismertetjük itt, hogy a példaprogramokban az adatkezelést meg tudjuk oldani.
Az alábbi függvényeket használó forrásfile elején a
#include <stdio.h>
sornak (aminek jelentését majd csak késõbb tudjuk megmagyarázni) szerepelnie kell.
A standard inputról egy karakert olvas be a
getchar()
függvény. Visszatérési értéke a beolvasott karakter kódja, vagy az EOF elõre definiált állandó, ha elértük a file végét. Az EOF állandó értéke gépfüggõ, és nem biztos, hogy "belefér" a char típusba.
Erre szolgál a
putchar(char c)
függvény, amely a paramétereként megadott karaktert a standard outputra írja.
A printf függvényt használhatjuk erre a célra.
Formája:
printf ("formátum-specifikáció",arg1,arg2, ...);
A függvény az arg1, arg2, ... argumentumok értékét az elsõ paraméterének megfelelõ módon konvertálja és kiírja a standard outputra.
A formátum-specifikáció tartalmazhat:
közönséges karaktereket
ezeket változtatás nélkül kinyomtatja. Escape szekvenciákat is tartalmazhat.
konverzió-specifikációkat
ezek a soron következõ argumentum nyomtatási formátumát határozzák meg.
A legfontosabb formátum-specifikációk:
%nd |
Egész érték kiírása n karakter széles helyre, balra igazítva |
%d |
Egész érték kiírása a szükséges szélességben |
%s |
Karakterlánc kiírás végig (a \0-t tartalmazó byte-ig) |
%ns |
Karakterlánc elsõ n karakterének kiírása, ha kell, balra igazítva |
%n.mf |
Lebegõpontos szám fixpontos kiírása n szélességben, m tizedesjeggyel |
%n.me |
Lebegõpontos szám kiírása lebegõpontos formában, n szélességben, a karakterisztikában m tizedesjegyet használva |
A függvény az argumentumok számát az elsõ paraméterébõl határozza meg. Ha ez az általunk megadott paraméterek számával nem egyezik meg, a program viselkedési kiszámíthatatlan! Hasonló problémát okozhat egy karaktertömb kiírása a %s formátummal, ha nem gondoskodtunk a záró 0 byte-ról.
Egyszerû példák:
printf ("Egy egy szöveg\n"); int a;int i;float b;char c,ch[5];a=1;b=2;c='A';for (i=0; i<5; i++) ch[i] = 'a' + i;ch[5] = '\0';printf ("a=%3d b=%5.1f c=%c ch=%s\n",a,b,c,ch);
A C nyelv eddig megismert elemei segítségével írjunk meg néhány egyszerû programot.
Írjunk programot, amely kiírja a kisbetûk kódjait!
/* CPELDA1.C *//* Készítette: Ficsor Lajos */ /************************************************** A program kiírja a kisbetûk kódjait.***************************************************/ #include <stdio.h> void main(void){char i; /* Fejléc írása */printf ("\n A kisbetuk kodjai:\n"); /* A ciklus végigmegy a kisbetûkön */for (i='a'; i<='z'; i++) { printf ("Betu: %c Kodja: %d\n", i, i); }}
A fenti kis program megmutatja, hogy ugyanazon változó különbözõ konverziókkal is kiírható. Egyben szemlélteti, hogy a char típus valójában egészként kezelhetõ.
Fontos megjegyezni, hogy a program megírásához nem kellett ismerni a kódtáblát, csak azt kellett feltételezni róla, hogy a kisbetûk folyamatosan, egymás után helyezkednek el benne. Ez a legáltalánosabban használt ASCII kódtáblára és az ékezet néküli betûkre igaz.
Írjunk programot, amely a standard bemenetrõl beolvasott szöveget csupa nagybetûvel írja ki.
/* CPELDA2.C *//* Keszitette: Ficsor Lajos */ /***************************************************** A program a standard bemenetrõl beolvasott szöveget nagybetûsen írja ki******************************************************/ #include <stdio.h> void main(void){int c; /* int típusú, hogy az EOF is ábrázolható legyen! */ while ( (c=getchar()) != EOF) /* Olvasás file végéig */ { if (c >= 'a' && c<= 'z') /* Ha kisbetût olvastunk be */ { c = c - ('a' - 'A'); /* Konvertálás nagybetûre */ } putchar(c); /* Karakter kiírása */ }
A fenti program azt tételezi fel, hogy a kódtábla mind a kisbetûket, mind a nagybetûket folyamatosan tárolja, és az azonos kisbetû és nagybetû közötti "távolság" (a kódjaik különbsége) állandó. Ez az ASCII kódtáblára és az ékezet nélküli betûkre igaz.
A
while ( (c=getchar()) != EOF) { utasítások }
szerkezet egy file karakterenkénti beolvasását és feldolgozását végzõ szokásos megoldás.
A függvény (mint minden programozási nyelvben) utasítások egy csoportja, amelyek megadott paramétereken képesek mûveleteteket végezni. A tipikus C nyelvû program sok, viszonylag egyszerû függvény összessége.
Formája:
típus név (formális paraméterlista) { lokális deklarációk utasítások }
A formális paraméterlista
típus azonosító
vagy
típus tömbnév[]
párok, vesszõvel elválasztva. Ha nincs paramérere, a paraméterlista helyére a void alapszó írandó.
A visszatérési érték típusa bármely típusnév lehet. Ha a függvény nem ad vissza értéket, a visszatérési érték típusa void.
A függvényt a használata elõtt deklarálni kell. A függvény deklaráció a függvény definíció fejével egyezik, és pontosvesszõ zárja. Formája tehát:
típus név (formális paraméterlista);
Megjegyzés
A C nyelv a fentiektõl enyhébb szabályokat ír elõ, de a helyes programozási stílus elsajátítása érdekében fogadjuk el ezt a szigorúbb szabályozást.
név (aktuális paraméterlista)
A függvényhívás állhat magában, pontosvesszõvel lezárva, (ekkor a visszaadott érték - ha volt - elvész), vagy kifejezés részeként. Az aktuális paraméterlista kifejezések vesszõvel elválasztott listája. A zárójelpár kiírása akor is kötelezõ, ha nincs paraméterlista!
A C nyelv csak az érték szerinti paraméterátadási mechanizmust ismeri. Ez a következõ folyamatot jelenti:
A fenti szabályok értelmében a formális paraméterek a függvényre nézve lokális változóknak tekinthetõk (a fogalom pontos magyarázatát csak késõbb tudjuk megadni), amelyek az aktuális paraméter kifejezés értékével inicializálódnak. A formális paraméterek a függvényen belül kaphatnak más értéket is, de ennek az aktuális paraméterre semmi hatása nincs.
Bevezetõ példaként írjunk egy függvényt, amely a faktoriális értékét számítja ki. Írjunk egy teljes programot, amely ezt a függvényt használja.
/* CPELDA3.C *//* Készítette: Ficsor Lajos *//***************************************************** Függvény n! számításához. Próbaprogram a függvényhez.******************************************************/ #include <stdio.h>long faktor (int n); /* Ez a függvény deklarációja */ void main(void) /* Fõprogram */{int n; n= 10;/* az alábbi sor tartalmazza a fgv hívását */printf ("\n%d faktorialisa: %ld",n, faktor(n));} long faktor (int n) /* Függvény definíció fejrésze *//**************************************************** A függvény n! értéket számítja. Nem rekurzív.****************************************************/{long fakt;int i; /* lokális deklarációk */ for (fakt=1, i=2; i<=n; i++) fakt *= i; return fakt;}
Érdemes megnézni a ciklusutasítás megoldását:
fakt=1, i=2; |
Kezdõérték beállítása. A vesszõ operátor teszi lehetõvé egynél több kifejezés írását. |
i<=n; |
A ciklus leállításának a feltétele. |
i++; |
Léptetés. Ebben az esetben a ++i is ugyanazt a hatást érte volna el. |
fakt *= i; |
A ciklus magja. Összetett értékadó operátort használ a fakt = fakt*i kifejezés egyszerûsítésére. |
Megjegyezzük még, hogy ezt a ciklusutasítást a gyakorlottabb C programozó az alábbi tömörebb formában írta volna fel:
for (fakt=1, i=2; i<=n; fakt *= i++);
Ebben az alakban kihasználtuk a léptetõ operátor azon tulajdonságát, hogy elõbb az i értéke felhasználódik a kifejezés kiszámításánál, majd utána növelõdik eggyel az értéke. A ciklusváltozó léptetése tehát most egy mûveletvégzõ utasítás mellékhatásaként történik meg. Ez az egyetlen kifejezés tehát egyenértékû az alábbi utasításokkal:
fakt = fakt *i;i = i+1;
Ezzel a megoldással minden feladatot a cilusutasítás vezérlõ részére bíztunk, így a ciklustörzs üres: ezt jelzi az utasítás után közvetlenül írt pontosvesszõ.
A függvényírás gyakorlásása írjunk egy következõ függvényt, amely beolvas egy sort a standard inputról, és azt egy stringként adja vissza. Ehhez a következõket érdemes végiggondolni:
Egy sor végét a \n (sorvég) karakter jelzi.
A string (karaktersorozat) tárolására a char típusú tömb a legalkalmasabb.
Érdemes betartani azt a C nyelvi konvenciót, hogy a string végét egy 0 tartalmú byte jelzi, mert ekkor azt a szokásos módon kezelhetjük (pl %s konverzióval kiírathatjuk, használhatjuk rá a szabványos string kezelõ függvényeket).
Mindezek figyelembevételével a függvény és azt azt használó fóprogram például az alábbi lehet:
/* CPELDA4.C *//* Készítette: Ficsor Lajos */ /***************************************************** Egy sor beolvasása függvénnyel. Próbaprogram a függvényhez.******************************************************/ #include <stdio.h> #define MAX 100 int getstr (char s[]); void main(void){int n;char szoveg[MAX+1]; printf ("\nEgy sor beolvasasa a standard inputrol\n\n"); n = getstr(szoveg);printf ("%s\n",szoveg);printf ("A szoveg hossza: %d\n",n); } int getstr (char s[])/**************************************************** A függvény egy sort olvas be a standard inputról, es az s tömbbe helyezi el, string-ként. Visszatérési értéke a beolvasott karakterek száma, vagy EOF****************************************************/{int c;int i; i=0;while ( (c=getchar()) != '\n' && c !=EOF) { s[i++] = c; }s[i] = '\0'; /* Záró 0 elhelyezése */return c==EOF ? c : i;}
Megjegyzések a programszöveghez:
A mutató (pointer) olyan változó, amely egy másik objektum címét tartalmazza. (Ezért belsõ ábrázolási módja erõsen gépfüggõ!) A mutató értéket kaphat az & operátorral, a mutató által megcímzett tárrész pedig a * operátorral. Igy tehát a
px = &x;
utasítás, ha px mutató, ahhoz az x változó címét rendeli. Ha ezután az
y = *px;
utasítást írjuk, a két utasítás együttes hatása azonos az
y=x;
értékadással.
A mutatók deklarációjának tartalmaznia kell, hogy milyen típusú objektumra mutat. Formálisan:
típus *azonosító;
Például:
int *px;
A *mutató konstrukció balérték, tehát szerepelhet értékadó utasítás baloldalán. Például a *px=0 a px által megcímzett egész értéket 0-ra állítja be, a (*px)++ pedig inkrementáltja.
Megjegyzés
A *px++ nem azonos a fentivel mivel az egyoperandusú operátorok jobbról balra csoportosítanak, így ennek zárójelezése a *(px++) lenne, ami azt jelenti, hogy a px inkrementálódik (ennek pontos jelentését lásd a következõ alpontban), majd az így keletkezett címen levõ értékre hivatkozunk.
Mivel a mutató is változó, így értéket kaphat, és mûveletek végezhetõk rajta. A mûveletek definíciója figyelembe veszi azt a tényt, hogy a mutató címet tárol, az eredményt pedig befolyásolja az, hogy a mutató milyen típusra mutat.
Az alábbi mûveletek megengedettek:
mutató és egész összeadása, kivonása
mutató inkrementálása, dekrementálása
két mutató kivonása
mutatók összehasonlítása
mutatónak "0" érték adása
mutató összehasonlítása 0-val
mutató indexelése
A felsorolt mûveleteken kívül minden más mûvelet tilos.
Az egyes mûveletek definíciója:
A mutató és egész közötti mûvelet eredménye újabb cím, amely ugyanolyan típusú objektumra mutat. Például
tipus *p
int n;
esetén a p+n egy olyan cím, amelyet úgy kapunk, hogy a p értékéhez hozzáadunk egy
n * sizeof (típus)
mértékû eltolást. Ezáltal az eredmény az adott gép címzési rendszerében egy újabb cím, amely ugyanolyan típusú, de n elemmel odébb elhelyezkedõ objektumra (például egy tömb n-el nagyobb indexû elemére) mutat. Így például, ha
int *px,n
akkor a
px+n
kifejezés eredménye mutató, amely a px által megcímzett egész utáni n. egészre mutat.
Hasonlóan értelmezhetõk a p-n p++ p-- kifejezések is.
Két azonos típusú mutató kivonása mindig engedélyezett, de általában csak azonos tömb elemeire mutató pointerek esetén van értelme. Eredménye int típusú, és a két cím között elhelyezkedõ, adott típusú elemek számát adja meg.
Az azonos típusú p1 és p2 mutatókra a p1 < p2 reláció akkor igaz, ha p1 kisebb címre mutat (az adott gép címzési rendszerében), mint p2. Ez abban az esetben, ha mindkét mutató ugyanazon tömb elemeit címzi meg, azt mutatja, hogy a p1 által címzett elem sorszáma kisebb, mint a p2 által címzetté. Más esetben általában az eredmény gépfüggõ, és nem értelmezhetõ. A többi reláció értelemszerûen hasonlóan mûködik.
A 0 értékû mutató speciális jelentésû: nem mutat semmilyen objektumra. Ezzel lehet jelezni, hogy a mutató még beállítatlan. Ezért engedélyezett a 0-val való összehasonlítás is.
A mutató indexelésérõl bõvebben a mutatók és tömbök összefüggésének tárgyalásánál beszélünk.
A C- ben a tömbök elemei indexeléssel és mutatókkal egyaránt elérhetõk. Ennek alapja az, hogy egy tömb azonosítóját a fordító mindig a tömb elsõ elemét megcímzõ mutatóként kezeli. Ennek következményeit szemlélteti a következõ összeállítás:
Legyen a deklarációs részben az alábbi sor:
int *pa,a[10],i;
és tételezzük fel, hogy végrehajtódott a
pa = &a[0];
utasítás.
Ekkor értelmesek az alábbi kifejezések, és a megadott jelentéssel rendelkeznek.
Kifejezés |
Vele egyenértékû |
Jelentés |
pa=&a[0] |
pa = a |
A pa mutató az a tömb els& otilde; elemére mutat |
a[i] |
*(pa+i) *(a+i) pa[i] |
Hivatkozás az a tömb i ind exû elemére |
&a[i] |
pa+i a+i |
Az a tömb i indexû elem&eac ute;nek címe |
Megjegyzés
Bár a tömb azonosítója a fordítóprogram számára mutatóként viselkedik, mégsem változó, ebbõl következik, hogy nem balérték, így az
a=pa pa++ p=&a
jellegû kifejezések tilosak!
A fordítóprogram a karakterlánc-állandót (stringet) karakter típusú tömbként kezeli, és megengedi, hogy egy karaktertömböt karakterlánccal inicializáljunk. A karakterlánc végére a záró 0 byte is odakerül. Ugyanezen okból megengedett egy char* mutató és egy string közötti értékadás, hiszen ez a fordító számára két mutató közötti értékadást jelent. Például:
char *string; string="Ez egy szoveg"
használható, és ez után *string egyenlû 'E' -vel, *(string+3) vagy string[3] egyenlõ 'e'-vel *(string+14) egyenlû '\0'-val, *(string+20) pedig határozatlan.
Mivel a mutató is változó, így
tömbökbe foglalható,
címezheti mutató.
A
char *sor[100];
deklaráció egy 100 darab char típusú mutató tárolására alkalmaz tömböt definmiál. A sor [1] például a sorban a második mutató.
Mivel a tömb azonosítója is mutató, így a sor is az, de egy mutatót (a sor[0]-át) címez meg, azaz típusa char**. Igy sor[1] azonos a sor+1- el, és ha px char típusú mutató, akkor a
px=sor[1]px=sor+1
kifejezések értelmesek.
A kétdimenziós tömb úgy fogható fel, mint egy egydimenziós tömb, amelynek minden eleme tömb. (Ennek következménye az, hogy a C programban használt tömbök elemei sorfolytonosan tárolódnak a memóriában.)
A tömbazonosító pedig mutató, így például
int a[10][10]int *b[10]
esetén a[5][5] és b[5][5] egyaránt írható, mindkét kifejezés egy- egy egész értéket ad vissza. Azonban az a[10][10] 100 egész szám tárolására szükséges helyet foglal el, a b[10] csak 10 cím számára szükségeset, és a b[5][5] felhasználása csak akkor értelmes, ha a b[5] elemeit elõzõleg valahogyan beállítottuk.
A fentiek illusztrálására nézzünk egy példát. Legyen a deklarációban
char s[5][10];char *st[5];
Ekkor írható
s[0]= "Elsõ";s[1]= "Második";s[2]= "Harmadik";s[3]= "Negyedik";s[4]= "Ötödik";
Ebben az esetben az s tömb 50 byte-nyi helyet foglal le, és minden string hossza korlátozott 10- re. Az s[1][5] a fenti utasítások után az i betût jelenti.
Mivel a deklarációban az
char *st[5];
is szerepel, írható
st[0] = "Elsõ";st[1] = "Második";st[2] = "Harmadik";st[3] = "Negyedik";st[4]="Ötödik"
Ebben az esetben az st[5] 5 címnek szükséges helyet foglal el, amihez még hozzáadódik a karaktersorozatok tárolására szükséges hely (de csak a feltétlenül szükséges), ráadásul tetszõleges hosszúságú stringek tárolhatók. Az st[1][5] a fenti utasítások után is az i betût jelenti.
Ha egy függvény argumentuma tömb, ebben az esetben - mint a legtöbb összefüggésben - a tömbazonosító mutatóként viselkedik, azaz a függvény a tömb elsõ elemének címét kapja meg. Ez azt is jelenti, hogy a deklarációban mind a tömb-szerû, mind a mutató típusú deklaráció alkalmazható, és a deklaráció formájától függetlenül a tömbelemekre mutatókon keresztül vagy indexeléssel is hivatkozhatunk. Ennek megfelelõen az alábbi formális paraméter-deklarációk egyenértékûek:
int a[] int *aint b[][5] int (*b)[5]
Függvény argumentuma lehet egy változó mutatója. Így indirekt hivatkozással megváltoztatható a függvény argumentumának értéke. Példaként egy függvény, amely megcseréli két argumentumának értékét:
void csere (int* x,int* y){int a;a = *x;*x = *y;*y = a;}
A függvény hívása:
main(){int a,b; . .csere (&a,&b); . .}
Bár a függvény nem változó, de van címe a memóriában,. így definiálható függvényt megcímzõ mutató. Ezáltal lehet függvény más függvény paramétere, sõt ilyen mutató függvényérték is lehet. (Az ennek megfelelõ deklarációkra példákat a késõbbiekben, a deklarációkkal kapcsolatban adunk.)
Az alábbi példaprogram egy string-másoló függvény (a standard strcpy függvény megfelelõje) három lehetséges megoldását mutatja. A C nyelv lehetõségeit kihasználva egyre tömörebb programszöveget kaphatunk.
/* CPELDA5.C *//* Készítette: Ficsor Lajos */ /***************************************************** Példa string másolás különbözõ változataira******************************************************/ #include <stdio.h> void strmasol1 (char* cel, char* forras);void strmasol2 (char* cel, char* forras);void strmasol (char* cel, char* forras); void main(void){char szoveg[50]; printf ("\n String masolas\n\n"); strmasol1(szoveg,"Ficsor Lajos");printf("Elso verzio: %s\n",szoveg); strmasol2(szoveg,"Ficsor Lajos");printf("Masodik verzio: %s\n",szoveg); strmasol(szoveg,"Ficsor Lajos");printf("Legtomorebb verzio: %s\n",szoveg); } void strmasol1 (char* cel, char* forras){/******************************************** Tömb jellegû hivatkozás, teljes feltétel********************************************/ int i; i=0;while ( (cel[i] = forras[i]) != '\0') i++;} void strmasol2 (char* cel, char* forras){/******************************************** Tömb jellegû hivatkozás, kihasználva, hogy az értákadás érteke a forrás karakter kódja, ami nem nulla. A másolás le- állításának feltétele a 0 kódú karakter átmásolása. (0 => "hamis" logikai érték!)********************************************/ int i; i=0;while ( cel[i] = forras[i] ) i++;/* A BORLAND C++ reakciója a fenti sorra: Warning: Possibly incorrect assigment Figyelem: lehet, hogy hibás értékadás. Magyarázat: felhívja a figyelmet arra a gyakori hibára, hogy = operatort írunk == helyett. Itt természetesen a sor hibátlan!*/} void strmasol (char* cel, char* forras){/******************************************** Pointer jellegû hivatkozas, kihasználva, hogy a paramértátadás érték szerinti, tehát a formális paraméterek segéd- változóként használhatók.********************************************/while ( *cel++ = *forras++ );/* A BORLAND C++ reakciója a fenti sorra ugyanaz, mint az elõzõ verziónál. A sor természetesen ebben a függvényben is hibátlan!*/
}
Készítsünk egy függvényt, amely a paraméterként kapott string-rõl megállapítja, hogy csak helyköz és számjegy karaktereket tartalmaz-e. Írjunk main függvényt a kipróbáláshoz. A string beolvasására használjuk a CPELDA4.C-ben megírt getstr függvényt!
/* CHF1.C *//* Készítette: Ficsor Lajos */ /********************************************************** A feladat olyan függveny írása, amely egy stringrõl megállapítja, hogy csak helyköz és számjegy karaktereket tartalmaz-e. Egy sor beolvasása a getstr függvénnyel történik. Próbaprogram a függvényhez.**********************************************************/ #include <stdio.h> #define MAX 100#define IGAZ 1#define HAMIS 0 int getstr (char s[]);int szame1(char* s);int szame(char* s); void main(void){int n;char szoveg[MAX+1]; printf ("\nEgy sorol megallapitja, hogy csak\n");printf ("helykozt es szamjegyet tartalmaz-e\n"); printf ("Elso verzio:\n");getstr(szoveg);if ( szame1(szoveg) ) printf("\nNumerikus!\n");else printf("\nNem numerikus!\n"); printf ("Masodik verzio:\n");getstr(szoveg);if ( szame(szoveg) ) printf("\nNumerikus!\n");else printf("\nNem numerikus!\n"); } int getstr (char* s)/**************************************************** A fuggveny egy sort olvas be a standard inputrol, es az s tombbe helyezi el, string-kent. Visszateresi erteke a beolvasott karakterek szama, vagy EOF****************************************************/{int c;int i; i=0;while ( (c=getchar()) != '\n' && c !=EOF) { s[i++] = c; }s[i] = '\0';return c==EOF ? c : i;} int szame1(char* s)/************************************************************* A függvény IGAZ (1) értékkel tér vissza, ha a paraméter string csak helyköz vagy számjegy karaktereket tartalmaz, HAMIS (0) értekkel egyébként. Az üres sztringre IGAZ értéket ad. Tömb stílusú hivatkozásokat használ.*************************************************************/{int szamjegy_e;int i;char c; szamjegy_e = IGAZ;i =0;while ( c=s[i++] ) /* Kihasználja, hogy a záró 0 leállítja*/ { /* a ciklust. Az i++ kifejezés lépteti */ /* a ciklusváltozót. */ if ( !(c==' ' || c>='0' && c<='9') ) { szamjegy_e = HAMIS; break; } }return szamjegy_e;} int szame(char* s)/************************************************************* A függvény IGAZ (1) értékkel tér vissza, ha a paraméter string csak helyköz vagy számjegy karaktereket tartalmaz, HAMIS (0) értekkel egyébként. Az üres sztringre IGAZ értéket ad. Pointer stílusú hivatkozásokat használ. Nem használ break utasítást.*************************************************************/{int szamjegy_e;char c; szamjegy_e = IGAZ;while ( (c=*s++) && szamjegy_e) { if ( !(c==' ' || c>='0' && c<='9') ) szamjegy_e = HAMIS; }return szamjegy_e;}
Írjunk függvényt, amely egy csak helyközt és számjegyeket tartalmazó string-bõl kiolvas egy egész számot. (Szám határoló karakter a helyköz vagy a string vége). Visszatérési érték a szám, és paraméterben adja vissza a feldolgozott karakterek számát is.
A fenti függvény segítségével olvassuk be egy stringben levõ valamennyi számot.
/* CPELDA6.C *//* Készítette: Ficsor Lajos */ /********************************************************** Egy csak helyközt és számjegyeket tartalmazó stringbõl egész számokat olvas be. Szám határoló jel: helyköz.***********************************************************/ #include <stdio.h> int aboli (char* s, int* hossz);int getstr (char* s); void main(void){char szoveg[100];int sorhossz,kezdes,sorszam,szam,hossz; sorhossz = getstr(szoveg); kezdes = 0;sorszam = 0;while ( kezdes < sorhossz) /* Mindaddig, amíg a string */ /* végére nem érünk */ { /* A soron következõ szám beolvasása */ szam = aboli(szoveg+kezdes, &hossz); printf ("A(z) %d. szam: %d, hossza: %d\n", ++sorszam, szam,hossz); /* A következõ szám kezdetének beállítása */ kezdes += hossz; }} int aboli(char* s, int* hossz)/*********************************************************** Az s stringbõl egész számokat olvas be. Határoló jel: legalább egy helyköz vagy a string vége.. A string elején álló helyközöket átugorja. Visszatérési érték a beolvasott szám, a második paraméterben pedig a feldolgozott karakterek hossza. (típusa ezért int*!)***********************************************************/{int i;int szam; i=0;szam = 0;/* Vezetõ helyközök átugrása /*while ( s[i] == ' ') i++;/* Szám összeállítása */while ( s[i] != ' ' && s[i] != '\0' ) { szam = 10*szam + s[i++] - '0'; }*hossz = i; /* Mutatón keresztüli indirekt hivatkozás */return szam;} int getstr (char* s)/**************************************************** A fuggveny egy sort olvas be a standard inputrol, es az s tombbe helyezi el, string-kent. Visszateresi erteke a beolvasott karakterek szama, vagy EOF****************************************************/{int c;int i; i=0;while ( (c=getchar()) != '\n' && c !=EOF) { s[i++] = c; }s[i] = '\0';return c==EOF ? c : i;}
Megjegyzések:
A deklaráció határozza meg, hogy a C fordító hogyan értelmezze az azonosítókat (azaz milyen objektumok jelölésére használatosak.) A jegyzetben már több helyen volt szó deklarációkról. Ebben a pontban összefoglaljuk a szükséges ismereteket.
A definició meghatározza valamely objektum típusát, méretét, és hatására helyfoglalás történik. A definició egyben deklaráció is.
A deklaráció valamely objektumnak a típusa, mérete (azaz alapvetõ tulajdonságainak) jelzésére szolgál.
Például:
char matrix [10][20]
definició
char matrix [][20] deklaráció
A teljes programot tekintve minden objektumot pontosan egyszer kell definiálni (hacsak nem akarjuk újra definiálni), de lehet, hogy többször kell deklarálni.
[tárolási_osztály] [típusnév] deklarátor_specifikátor
A szögletes zárójelek azt jelzik, hogy a tárolási osztály és a típusnév közül az egyik elmaradhat, ilyenkor a megfelelõ alapértelmezés lép életbe.
A deklarációban a típusnév lehet:
az alaptípusok ismertetésénel felsoroltak valamelyike
struktúra- és unió definíciók vagy címkék (nevek)
typdef- el definiált típusnevek
A deklarátor specifikátor az alábbiak valamelyike lehet:
Formája |
Jelentése |
azonosító |
alaptípus |
azonosító [állandó kifejezés] |
tömb |
azonosító [ ] |
tömb |
azonosító ( ) |
függvény |
a fentiek, elôttük *- al |
fenti objektumok mutatói |
(* azonosító ) () |
függvény-mutató |
A fentiek az alábbi korlátozásokkal érvényesek:
tömb
unió
függvény
de lehet a fentiek bármelyikét megcímzõ mutató!
Mindezek megértésének megkönnyítésére nézzük az alábbi példákat:
int t[] |
egészeket tartalmazó tömb |
int *t[] |
egészeket megcímzõ mutatókat tartalmazó tömb |
int f() |
egészt visszaadó függvény |
int *f() |
egészt megcímzô mutatót visszaadó függvény |
int (*f)() int *(f()) |
egészt visszaadó függvényt megcímzõ mutató |
int (*f())[] |
olyan tömb, amelyeknek elemei fenti típusú függvény-mutatók |
A definiált objektum érvényességi körét és élettartamát (vagy tárolási módját) határozza meg.
A következõ tárolási osztályok léteznek:
auto |
Lokális változó ("automatikus változó") egy függvényre vagy egy blokkra nézve. Értéke a függvénybe (blokkba) val&o acute; belépéskor határozatlan, a kilépéskor megszûnik. |
regiszter |
Olyan auto változó, amelyet gyakran kívánunk használni. Utasítás a ford& iacute;tóprogramnak, hogy "könnyen elérhetõ" módon (például regiszterekben) tárolja az értéküket. Mivel ez a mód gépfüggõ lehet, nem alkalmazhat&o acute; rájuk az & operátor, hiszen lehet, hogy nincs is valódi értelemben vett címük. |
extern |
Általános érvényû változó, a program különbözõ részeiben is érvényes. |
static |
Értéke megmarad, nem jön létre és szûnik meg a függvényhívással. (Lehet belsõ vagy külsõ.) Érvényess&eacut e;gi köre korlátozott. |
Külsõ definició az, amely minden függvényen kívül helyezkedik el. A függvénydefiníció mindig külsõ.
A C program külsõ definíciók sorozata. A külsõ definícióban az extern és static tárolási osztály használható. Az alapértelmezés az extern tárolási osztály.
Az egyes függvényekben extern deklarációval jelezni lehet a külsõ változókat. Ez azonban csak akkor kötelezõ, ha a deklaráció egy forrásszövegben megelõzi a definíciót, vagy a definíció más forrásállományban van, mint a függvény.
A gyakorlottabb programozók által használt konvenció: valamennyi külsõ definiciót helyezzük el a forrás-file elején, és ne használjunk extern deklarációt a függvényeken belül.
Belsõ definíció az, amely függvényeken vagy blokkon belül helyezkedik el. Tárolási osztálya lehet auto (feltételezett), register vagy static.
Egy C program több forrás-file- ból állhat, és könyvtárakban elõre lefordított rutinokra is hivatkozhat. Ezért szükséges tisztázni az azonosítók érvényességi tartományát.
Kétféle érvényességi tartományról beszélhetünk:
lexikális érvényességi tartomány
a külsõ változók érvényességi tartománya
A programnak az a része, amelyben "definiálatlan azonosító" hibajelzés nélkül használhatjuk az azonosítót. Részletesebben:
Megjegyzés
Ha egy azonosítót egy blokk vagy függvény fejében explicit módon deklarálunk, akkor annak végéig az adott azonosító összes blokkon kívüli deklarációja felfüggesztõdik (újradefiniálás).
A program egészére nézve tisztázza, hogy ugyanarra az azonosítóra vonatkozó hivatkozás ugyanazt az objektumot jelenti-e.
Egy extern-ként deklarált változónak a teljes programot alkotó forrásállományok és könyvtárak valamelyikében pontosan egyszer definiáltnak kell lennie. Minden rá hivatkozó függvényt tartalmazó állományban (esetleg magában a függvényben - bár ez nem szokásos) szerepelnie kell az azonosító deklarációjának. Így ezzel az azonosítóval minden függvény ugyanarra az objektumra hivatkozik. A deklarációknak ezért kompatibiliseknek kell lenniük. (Típus és méret szempontjából.)
A legfelsõ szinten static-ként deklarált azonosító csak az adott állományra érvényes, a többi állományban szereplõ függvények nem ismerik. Függvény is deklarálható static-ként.
Ebben a pontban ismertetjük azokat az alapértelmezéseket, amelyek a deklarációkkal kapcsolatosak.
A tárolási osztály alapértelmezése:
Típus alapértelmezése: int.
Nem hiányozhat egyszerre a tárolási osztály és a típus.
Kifejezésekben azt a nem deklarált azonosítót, amelyet "(" követ, a fordító int- et visszaadó fügvénynek értelmezi. ( Ezért ilyeneknek a deklarációja elhagyható - bár a helyes programozási stílus megköveteli a függvények deklarálását, azaz a prototípusok alkalmazását.)
A deklarációban (bizonyos korlátozásokkal) az objektumoknak kezdõérték adható. Inicializálás nélkül:
a külsõ és a statikus változók értéke garantáltan 0
az automatikus és regiszterváltozók értéke határozatlan.
Egyszerû változó inicializálása:
tár_o típus azonosító = kif
Összetett objektum inicializálása:
deklaráció = { kif, kif,....,}
A kifejezések az elemek sorrendjében adódnak át. Ha számuk kevesebb, mint amit deklaráció meghatároz a maradék elemek 0-val töltõdnek fel. Ha több, hibajelzést kapunk.
Tömbök esetén a deklarációból az elsõ index felsõ határa elmaradhat, ezt ekkor a kifejezések száma határozza meg.
Az inicializálás egyszer, fordítási idõben, a helyfoglalással együtt történik. A kifejezés lehet
állandó kifejezés
mint az állandó kifejezés, de szerepelhet operandusként már deklarált változó címe (+ eltolás) is.
Minden alkalommal végrehajtódik, amikor a vezérlés belép a blokkba. A kifejezés tetszõleges, korábban definiált értéket tartalmazhat. (Változókat, függvényhívásokat stb.) Valójában rövidített formában írt értékadás.
Példák:
int t[] = {0,1,2,13,26,45}int m[5][3] = {1,3,5,2,4,6,7,9,11,8,10,12,13,15,17}
Karaktertömb inicializálására használható karakterlánc is, ezzel egyszerûsíthetõ a kezdõértékadás.
Példa:
char s[] = "szoveg";
ami egyenértékû a "szabályos"
char s[] = {'s','z','o','v','e','g','\0'};
formával.
A standard inputról formattált beolvasást végez a scanf függvény. Formája:
scanf("Konverziós karakterek", p1, p2, ...)
A függvény a konverziós karakterek által megadott módon adatokat olvas be, majd azok konvertálással kapott értékét sorban a paraméterekhez rendeli. A p1, p2, ... paramétereknek mutatóknak kell lenniük. A pontos mûködése meglehetõsen bonyolult, itt csak egy egyszerûsített leírását adjuk meg.
A függvény adatok és üreshely karakterek (helyköz, tabulátor, új sor, kocsi vissza, soremelés, függõleges tabulátor és lapemelés) váltakozásaként tekinti a bemenetet. Az üreshely-karaktereket átlépi, a köztük levõ karaktereket pedig konvertálja és az így kapott értéket az aktuális paraméter által megcímzett változóban tárolja.
A konverziós karakterek az alábbiak lehetnek (a felsorolás nem teljes!):
Konverzió |
Az argumentum típusa |
A beolvasott adat |
%d |
int* |
Egész szám |
%f |
float* |
Valós szám |
%lf |
double* |
Valós szám |
%c |
char* |
Egy karakter |
%s |
char* |
Karaktersorozat (a záró 0-át elhelyezi) |
Ügyeljünk a használat során arra, hogy a konverziós karakterek közöt más karakterek ne legyenek (még helyközök sem), mert azoknak is van jelentésük, amelyet itt most nem részletezünk.
A feladat egy valós tömb elemszámának és elemeinek beolvasása és a tömb rendezése a kiválasztásos és a buborék rendezés segítségével.
/* CPELDA7.C *//* Készítette: Ficsor Lajos */ /* A program beolvas egy double tömböt és rendezi kétféle módszerrel */#include <stdio.h> #define MAXELEM 100 void rendez_cseres(double* tomb, int elemszam);void rendez_buborek(double* tomb, int ele4mszam); void main(void){int i,n;double a[MAXELEM]; /* Elemszam beolvasasa */scanf ("%d",&n); /* Tombelemek beolvasasa */for (i=0; i<n; i++) scanf("%lf",a+i); rendez_cseres (a,n);for (i=0; i<n; i++) printf("%lf ",a[i]);printf("\n"); rendez_buborek (a,n);for (i=0; i<n; i++) printf("%lf ",a[i]);printf("\n"); } void rendez_kiv(double* tomb, int elemszam)/* A függvény növekvõ sorrendbe rendezi a tömb tömböt */{int i,j,minindex;double min, seged; for (i=0; i<elemszam-1; i++) { /* legkisebb elem keresese az aktualis elemtol a tomb vegeig */ min = tomb[i]; minindex = i; for (j=i+1; j<elemszam; j++) { if (tomb[j] < min) { min = tomb[j]; minindex = j; } } /* Ha nem az aktualis elem a legkisebb, csere */ if (minindex != i) { seged = tomb[i]; tomb[i] = min; tomb[minindex] = seged; } } } void rendez_buborek(double* tomb, int elemszam)/* A függvény növekvõ sorrendbe rendezi a tömb tömböt */{int i,csere;double seged; csere =1;while (csere) /* Mindaddig, amíg csere szükséges volt */ { csere = 0; for (i=0; i<elemszam-1; i++) if (tomb[i] > tomb[i+1]) /* Ha a szomszédos elemek */ { /* sorrendje nem jó */ seged = tomb[i]; /* Csere */ tomb[i] = tomb[i+1]; tomb[i+1] = seged; csere++; } }
}
A feladat egy program írása, amely a standard inputról pozitív egész számokat olvas, és ezeket elhelyezi nagyság szerint növekvõ sorrendben egy tömb egymás után következõ elemeibe. A beolvasás végét az elsõ nem pozitív szám jelzi.
/* CPELDA10.C *//* Készítette: Ficsor Lajos */ /* A program a standard inputról pozitív egész értékeket olvas be, és egy tömbben nagyság szerint növekvõ sorrendben helyezi el. A beolvasás végét az elsõ nem pozitív szám jelzi.*/ #include <stdio.h> #define MAX 100#define JOKICSISZAM 0 void main(void){int tomb[MAX+1]; /* A tomb, amely a 1. indexu elemetol */ /*kezdve tartalmazza a beolvasott szamokat */int elemek; /* A tombben elhelyezett elemek szama */int szam,i; elemek = 0;/* A tomb 0. elemebe elhelyezunk egy olyan kis szamot, amely az adatok kozott nem fordulhat elo */tomb[0] = JOKICSISZAM; scanf("%d",&szam);while (szam > 0) { /* A tomb vegetol kezdve minden elemet eggyel hatrebb (nagyobb indexu helyre) helyezunk, amig a beszurando szamnal kisebbet nem talalunk. Ekkor az uj elemet ez utan az elem utan tesszuk be a tombbe. Az ures tombre is jol mukodik. */ for (i=elemek; i>0 && szam<tomb[i]; i--) { tomb[i+1] = tomb[i]; } tomb[++i] = szam; elemek++; printf ("A tomb elemszama: %d\n",elemek); for (i=0; i<=elemek; i++) printf (" %d",tomb[i]); printf("\n"); /* Kovetkezo szam beolvasasa */ scanf("%d",&szam); } /* while szam > 0 */printf ("\nA vegleges tomb:\n");for (i=1; i<=elemek; i++) printf (" %d",tomb[i]);}
Egy C program a gyakorlatban mindig több forrásfile-ból áll, mert a túlságosan hosszú szövegek nehezen kezelhetõk. Ezzel lehetõvé válik az is, hogy egy forrásfile-ban csak a logikailag összetartozó függvények definíciói legyenek.
Ha azonban egy függvény definíciója és hívása nem azonos forrásfile-ban van, a hívást tartalmazó file-ban azt deklarálni kell. A szükséges deklarációk nyilvántartása nem egyszerû feladat, különösen ha figyelembe vesszük, hogy egy változtatást minden helyen át kell vezetni.
Ugyanez a probléma akkor, ha könyvtárban tárolt (például szabványos) függvényeket hívunk. Ezeknek a deklarációját is meg kell adnunk a hívást tartalmazó forrásban.
A probléma megoldására szolgál az
#include <filenév>
vagy
#include "filenév"
direktíva. Jelentése: a filenév által meghatározott file tartalma a direktíva helyén bemásolódik a forrásfile-ba. Az elsõ esetben a bemásolandó file-t a fejlesztõ környezetben beállított szabványos helyen, a második forma esetén az aktuális katalógusban keresi a rendszer.
Ezt a direktívát használhatjuk fel a deklarációk egyszerûsítésére. Az elterjedt programozói gyakorlat szerint minden forrásfile (szokásos kiterjesztése: .C) csak a függvények definícióit tartalmazza, a deklarációikat egy ugyanolyan nevû. de .H kiterjesztésû file-ba (header file) gyûjtjük össze. A definíciókat tartalmazó forrásfile-ba ez a header file egy #include direktíva segítségével kerül be, és ugyanezt az eljárást használjuk a hívásokat tartalmazó forrásfile-ok esetén is.
Az eddigi mintapéldák elején található
#include <stdio.h>
sor magyarázata az. hogy a szabványos input-output függvények deklarációit a (szabványos) stdio.h header file tartalmazza. A függvények leírását tartalmazó dokumentáció minden függvényre megadja, hogy milyen nevû hader file tartalmazza az adott függvény deklarációját.
A header file-ok a függvény-deklarációkon kívül egyéb deklarációkat, szimbolikus konstansokat stb. is tartalmazhatnak. Például az eddigi példákban is már használt EOF az stdio.h file-ban deklarált szimbolikus konstans.
Írjunk programot, amely meghatározza a standard inputról beolvasott file sorainak, szavainak és karaktereinek számát. Szó: nem helyközzel kezdõdõ, helyközzel vagy sor végével végzõdõ karaktersorozat. A file-t soronként olvassuk be, a már elõzõleg megírt getstr függvény segítségével.
Bár a feladat mérete ezt most nem indokolja, gyakorlásképpen a fõprogramot és a függvényeket (a már ismert getstr és a most megírandó egyszo függvényt) tegyük külön forrásfile-okba.
Legyen tehát az STR.C file-ban a két függvény definíciója, deklarációik pedig az STR.H file-ban. A fõprogram forrásfile-ja legyen a SZAVAK.C .
/* CPELDA8 *//* Készítette: Ficsor Lajos */ STR.H int getstr (char* s); void strmasol (char* cel, char* forras); STR.C#include <stdio.h>#include "str.h" int getstr (char* s)/**************************************************** A fuggveny egy sort olvas be a standard inputrol, es az s tombbe helyezi el, string-kent. Visszateresi erteke a beolvasott karakterek szama, vagy EOF****************************************************/{int c;int i; i=0;while ( (c=getchar()) != '\n' && c !=EOF) { s[i++] = c; }s[i] = '\0';return c==EOF ? c : i;} int egyszo(char*s, char* szo, int* szohossz)/********************************************************** A fuggveny beolvas az "s" stringbol egy szot, es elteszi a "szo" stringbe, a szo hosszat pedig a "szohossz" parameterbe. Szo definicioja: nem helykozzel kezdodo, helykozel vagy string vegevel hatarolt karaktersorozat. A string elejetol kezdi a keresest. Fuggvenyertek: az "s" stringbol feldolgozott karakterek szama.***********************************************************/{int i; i=0;*szohossz = 0;while ( s[i] == ' ') i++; /* Szokozok atlepese, a szo */ /* elejenek keresere */while ( s[i] != ' ' && s[i] != '\0') /* Szo belsejeben */ { szo[(*szohossz)++] = s[i++]; } /* vagyunk */szo[*szohossz] = '\0'; /* Zaro 0 elhelyezese */return i;} /* SZAVAK.C *//* Keszitette: Ficsor Lajos */ /******************************************************** A program a standard inputrol beolvasott szovegfile sorainak, szavainak es karaktereinek szamat hatarozza meg. Ismert hiba: Ha az utolso sor nem sorveggel, hanem EOF-el er veget, figyelmen kivul marad!*********************************************************/ #include <stdio.h>#include "str.h" #define MAXSORHOSSZ 200#define MAXSZOHOSSZ 200 void main(void){char sor[MAXSORHOSSZ+1];char szo[MAXSZOHOSSZ+1];int sorszam, szoszam, karakterszam;int sorhossz, szohossz;int kezdet;int i; sorszam=0;karakterszam=0;szoszam=0;/* Soronkenti beolvasas a file vegeig */while ( (sorhossz=getstrl(sor)) != EOF) { sorszam++; karakterszam += sorhossz; /* A sor szetszedese szavakka */ kezdet = 0; while (kezdet < sorhossz) { i = egyszo(sor+kezdet, szo, &szohossz); kezdet += i; if (szohossz) szoszam++; /* Ha valodi szo */ } } printf ("Sorok szama: %d\n",sorszam);printf ("Szavak szama: %d\n",szoszam);printf ("Karakterek szama szama: %d\n",karakterszam); }
A struktúra olyan összetett típus, amelynek elemei különbözõ típusúak lehetnek. (A tömb azonos típusú elemek összefogására alkalmas!)
Deklarációja az alábbi formák valamelyikével lehetséges:
struct {elemek deklarációja} változólista
Deklarálja, hogy a változólista elemei adott szerkezetû stuktúrák.
struct név {elemek deklarációja}
Létrehoz egy név nevû típust, amely a megadott szerkezetû struktúra. Ezután a struct név típusnévként használható deklarációkban. Ez a forma az ajánlott.
Az elemek deklarációja pontosvesszõvel elválasztott deklarációlista.
Példák:
stuct { int év; charhonap[12]; int nap; } szulinap, ma, tegnap;
vagy
struct datum { int év; char honap[12]; int nap; };struct datum szulinap, ma, tegnap,*pd;
Struktúra tagja lehet már definiált stuktúra (vagy annak mutatója) is, de nem lehet tagja önmaga. Lehet viszont tagja önmaga mutatója (önhivatkozó struktúra).
A struktúrákkal az alábbi mûveletet végezhetjük:
az & operátorral képezhetjük a címét
hivatkozhatunk valamelyik elemére a "." (pont) és a "->" operátorral
megengedett két azonos típusú struktúra közötti értékadás
struktúra lehet függvény paramétere vagy visszatérési értéke
A struktúra elemére (tagjára vagy mezõjére) hivatkozhatunk a
struktúra_azonosító.tagnév
konstrukcióval. Például:
szulinap.ev = 1951;
A hivatkozás történhet struktúra-mutató segítségével is. Mivel a C nyelvben ez a forma gyakori, erre külön operátor is van. A hivatkozás formája ebben az esetben:
(*pd).név
vagy
pd -> nev
A fenti két forma egyenértékû.
A struktúra-típus lehet tömb alaptípusa. Például:
struct datum napok[20]
Az unió olyan változó, amely (különbözõ idõpontban) különféle típusú és méretû objektumokat tartalmazhat, ezáltal ugyanazon tárterületet különféleképpen használhatunk.
Az unió deklarációja formálisan ugyanolyan, mint a struktúráé, de a struct alapszó helyett a union alapszót kell használni, azonban míg a struktúrában az elemek felsorolása az adott elemek sorozatát jelenti, az unionban a felsorolás "az alábbiak egyike" értelmû. A programozó felelõsége, hogy következetesen használja ezt a konstrukciót.
Az alábbi kis példaprogram segítségével illusztráljuk a unió és a struktúra használatát. A datum struktúra elsõ eleme egy egész változó, amellyel jelezni lehet, hogy a második eleme, amely egy unió, a lehetséges értékek közül éppen milyen típusút használ.
#include <stdio> main() { struct angol_datum { int day; int month; int year;}; struct usa_datum { int mm; int dd; int yy; }; struct magyar_datum { int ev; char honap[12]; int nap; }; struct datum { int tipuskod; union { struct angd d1; struct usad d2; struct magyd d3;}dat;}; struct datum a,b,c; a.tipuskod = 1;a.dat.d1.day = 10;a.dat.d1.month = 9;a.dat.d1.year = 1951; printf ("Angol datum: %d %d %d \n", a.dat.d1.day,a.dat.d1.month,a.dat.d1.year); a.tipuskod = 2;a.dat.d2.dd = 10;a.dat.d2.mm = 9;a.dat.d2.yy = 1951; printf ("USA datum: %d %d %d \n", a.dat.d2.mm,a.dat.d2.dd,a.dat.d2.yy); a.tipuskod = 3;a.dat.d3.nap = 10;a.dat.d3.honap = "szeptember";a.dat.d3.ev = 1951; printf ("Magyar datum: %d %s %d \n", a.dat.d3.ev,a.dat.d3.honap,a.dat.d3.nap); }
Ebben az alpontban a cél egy összetettebb példa megoldása: határozzuk meg az input szövegfile szavainak gyakoriságát!
Elsõ lépés: a szavak elhatárolása. Ehhez a CPELDA8 program egyszo függvényének továbbfejlesztése szükséges, a szó-határoló karakterek pontos meghatározásával. A továbbfejlesztett változat a NYTSZO.C file-ban található.
Második lépés: a szavak tárolási módszerének meghatározása. Az elsõ ötlet lehet két, párhuzamosan kezelendõ tömb:
char szavak[MAXSZOHOSSZ+1][MAXDB] int db[MAXDB]
Ez nem túl jó megoldás, mert logikailag összetartozó adatok különbözõ adatszerkezetekben találhatók.
Jobb megoldás: struktúratömb használata. A szavak tárolására szolgáló adatszerkezet így:
struct szodb { char szo[MAXSZOHOSSZ+1]; int db; }; struct szodb szavak[MAXDB];
Ennek az adatszerkezetnek is van hátránya:
fölösleges helyfoglalás, mert a maximális szómérethez kell lefoglalni a helyet
fölösleges helyfoglalás, mert a maximális szószámhoz kell lefoglalni a helyet
mivel keresésre lesz szükség, a szavakat rendezetten kell tartani, ami sok adatmozgatást igényel
A késõbbiekben a fenti adatszerkezetet úgy fogjuk módosítani, hogy a fenti hátrányok csökkenjenek, most azonban fogadjuk el ezt a megoldást.
Az algoritmus az alábbi lehet:
Ha megtaláltuk, a gyakoriságot növeljük eggyel
Ha nem találtuk meg, szúrjuk be a tömbbe, 1 gyakorisággal.
Az 1. és 2. pontban leírt mûveletek a CPELDA8 programban kialakítottak szerint megoldhatók, tehát ennek a programnak a módosításával lehet a legkönnyebben a feladatot megoldani.
A 3. pont keresési mûveletét a CPELDA10 programban implementált algoritmus finomításával implementálhatjuk. Az elõfordulás vagy beszúrás helyének megkeresését különválasztva erre egy keres nevû függvényt írunk, a beszúráshoz tartozó adatmozgatást a fõprogramra bízzuk.
Mivel a keres nevû függvény és a fõprogram egyaránt használja a szodb struktúrát, ennek deklarációját külsõ deklarációként oldjuk meg, és a konstans definíciókkal együtt a SZODEF1.H header file-ba telepítjük.
Ezek alapján a program az alábbi modulokat tartalmazza:
SZODEF1.C amely hivatkozik a SZODEF1.H, az STR.H, az NYTSZO.H és a SZOFGV1.H header file-okra
NYTSZO.C
SZOFGV1.C, amely hivatkozik a SZODEF1.H header file-ra
STR.C, amely korábban lett kifejlesztve, és a soronkénti beolvasáshoz szükséges getstrl függvény miatt szükséges.
/* NYTSZO.C *//* Keszitette: Ficsor Lajos */ #include "nytszo.h" int nytszo(char*s, char* szo, int* szohossz)/******************************************************* A fuggveny beolvas az "s" stringbol egy szot, es elteszi a "szo" stringbe, a szo hosszat pedig a "szohossz" parameterbe. Szo definicioja: nem hatarolo karakterrel kezdodo, hatarolo karakterrel vagy string vegevel vegzodo karaktersorozat. A string elejetol kezdi a keresest. Hatarolo karakterek: amit a "hatarolo" fgv. annak jelez. Fuggvenyertek: az "s" stringbol feldolgozott karaktere szama. Hivott fuggvenyek: hatarolo*********************************************************/{int i; i=0;*szohossz = 0;while ( hatarolo(s[i])) i++; /* Szo elejenek keresese */while ( !hatarolo(s[i]) && s[i] != '\0') { szo[(*szohossz)++] = s[i++]; }szo[*szohossz] = '\0';return i;} int hatarolo(char c)/*{int hatar; hatar = 0;if (c==' ' || c=='\t' || c=='.' || c==',' || c== '(' || c==')' || c=='!' || c =='?') hatar = 1;return hatar;} Persze ez irhato sokkal rovidebben: */ {return c==' ' || c=='\t' || c=='.' || c==',' || c==':' || c==';' || c== '(' || c==')' || c=='!' || c =='?' || c=='"';} /* SZODEF1.H *//* Definiciok a szo-statisztikahoz. 1. valtozat*/#define MAXSORHOSSZ 300#define MAXSZOHOSSZ 50#define MAXDB 800 struct szodb { char szo[MAXSZOHOSSZ+1]; int db; }; /* SZOFGV1.C */ /* Keszitette: Ficsor Lajos */ /* A szo statisztika keszitesehez szukseges fuggvenyek */ #include <string.h>#include "szodef1.h"#include "szofgv1.h" int keres (struct szodb tabla[], /* A szavak tablazata */ int tablahossz, /* A tabla elemeinak szama */ char* szo, /* A keresendo szo */ int* bennevan) /* =1, ha a szot megtalaltuk, =0 ha nem *//* A fgv. a "szavak" tombben keresi a "szo" szot. Visszateresi ertek: a keresett szo sorszama (vagy helye) a tombben*/{int i,j;int hely = 0;int talalt = 0; *bennevan = 0; for (i=0; !talalt && i<=tablahossz; i++) { j = strcmp(szo, tabla[i].szo); if (j<=0) { hely = i; talalt = 1; if ( j == 0) *bennevan = 1; } }return hely;} /* SZOSTAT1.C */ /* Szostatisztika program 1. valtozat */ /* Keszitette: Ficsor Lajos */ #include <stdio.h>#include <string.h>#include "szodef1.h"#include "str.h"#include "nytszo.h"#include "szofgv1.h" void main(void){char sor[MAXSORHOSSZ+1];char szo[MAXSZOHOSSZ+1];struct szodb szavak[MAXDB+1];char* soreleje;int sorhossz, szohossz;int kezdet;int hely;int tablahossz;int bennevan; int i; /* Szo-tabla elokeszitese */szavak[0].szo[0] = 'z'+1;szavak[0].szo[1] = '\0';tablahossz = 0; /* Soronkenti beolvasas a file vegeig */while ( (sorhossz=getstrl(sor, MAXSORHOSSZ)) != EOF) { /* A sor szetszedese szavakka */ kezdet = 0; while (kezdet < sorhossz) { i = nytszo(sor+kezdet, szo, &szohossz); kezdet += i; if (szohossz) { /* Valodi szot talalt */ printf ("\n%s",szo); /* Kereses a tablaban */ hely = keres (szavak, tablahossz, szo, &bennevan); if (bennevan) { /* Megtalalta */ szavak[hely].db++; } else { /* Nem talalta meg. Betesszuk a tablaba */ for (i=tablahossz; i>=hely; i--) { szavak[i+1] = szavak[i]; } strcpy(szavak[hely].szo,szo); szavak[hely].db = 1; tablahossz++; } /* If bennevan */ printf ("\n%d %d %s\n",tablahossz, hely, szavak[hely].szo); for (i=0; i<tablahossz; i++) /* Csak ellenorzo kiiras */ { printf ("\n%50s %5d\n", szavak[i].szo, szavak[i].db); } } /* If szohossz */ } /* while kezdet < sorhossz */ } /* while ...!-EOF *//* Statisztika kiirasa */for (i=0; i<tablahossz; i++) { printf ("\n%50s %5d", szavak[i].szo, szavak[i].db); }printf("\n");}
Ha egy változót vagy tömböt definiálunk, annak hely foglalódik a memóriában, ami az adott változó vagy tömb teljes élettartama alatt rendelkezésre áll, akár kihasználjuk, akár nem. A tömbök esetén a méretet konstans kifejezésként kell megadnunk, tehát az nem függhet a program közben meghatározott értékektõl.
Mindkét problémát megoldja a dinamikus memóriafoglalás lehetõsége. Segítségével csak akkor kell lefoglalni az adatok tárolásához a helyet, amikor azt használni akarjuk, és csak annyit, amennyi szükséges. Ha az adatokra már nincs szükség, a lefoglalt memória felszabadítható és más célra újra lefoglalható.
A lefoglalt memória a kezdõcímét tartalmazó pointer segítségével kezelhetõ.
Használatukhoz szükséges az stdlib.h header file.
void* malloc(size_t meret)
Lefoglal meret byte nagyságú memóriaterületet. Visszatérési értéke a lefoglalt terület kezdõcíme, vagy NULL, ha a helyfoglalás sikertelen volt.
A visszaadott pointer void* típusú, ha tehát a memóriaterületet tömb-szerûen akarjuk használni (mint általában szokás), azt a tárolni kívánt adatok típusára kell konvertálni.
A size_t elõre definiált típus (olyan egész, amelyben biztosan elfér a lefoglalandó terület mérete), NULL pedig a 0 mutató számára definiált szimbolikus konstans.
void free(void* blokk)
Felszabadítja a blokk kezdõcímû memûriateületet. A paraméterének elõzõleg malloc, calloc vagy a realloc függvény segítségével kellett értéket kapnia.
void* calloc (size_t db, size_t meret)
Lefoglal db*meret byte nagyságú memóriaterületet. Visszatérési értéke mint az malloc függvénynél.
void* realloc(void* blokk, size_t meret)
A blokk egy már korábban lefoglalt terület kezdõcíme. Ennek a területnek a méretét meret nagyságúra állítja. Ha a méret csökken, adatok vesznek el. Ha nõ, a terület kezdõcíme (ami a visszatérési érték) megváltozhat. Ekkor a korábbi adatok az új helyre másolódnak.
A program elsõ változatában alkalmazott adatszerkezet minden szó számára valamilyen maximális szóhossznak megfelelõ helyet foglalt le. Ez fölösleges memóriapocséklás, ráadásul a program emiatt a MAXSZOHOSSZ-nál hosszabb szavakat nem tudja kezelni.
A feladat most a program módosítása úgy, hogy a helyfoglalása kedvezõbb legyen. Ehhez a szavak adatait tartalmazó struktúrát átalakítjuk úgy, hogy ne a szót, hanem annak pointerét tartalmazza. Minden szó számára dinamikusan foglalunk helyet, annyit, amennyi szükséges.
Az új struktúra:
struct szodb { char* szo; int db;}
Az átalakítás miatt meglepõen kevés helyen kell módosítani a programot! (Ez is mutatja a pointer hasznát a C nyelvben.)
Továbbra is fölösleges helyfoglalást okoz az, hogy a struktúra-tömböt fix mérettel kell deklarálni. Ezt csak úgy kerülhetjük el, hogy más adatszerkezetet használunk. A feladat legelegánsabb megoldása a bináris fa adatszerkezet segítségével készíthetõ el. Ez egyben a beszúráshoz szükséges keresésre is jó megoldást ad.
/* SZODEF2.H *//* Definiciok a szo-statisztikahoz. 2. valtozat*/#define MAXSORHOSSZ 300#define MAXSZOHOSSZ 50#define MAXDB 100 struct szodb { char* szo; int db; }; /* SZOSTAT2.C */ /* Szostatisztika program 2. valtozat */ /* Keszitette: Ficsor Lajos */ #include <stdio.h>#include <string.h>#include <alloc.h>#include "str.h"#include "nytszo.h"#include "szodef2.h"#include "szofgv2.h" Mindössze két helyen van változás az 1. változathoz képest: /* Szo-tabla elokeszitese *//* Itt a különbseg!*/szavak[0].szo = (char*) malloc(MAXSZOHOSSZ+1);/* Innentõl már változatlan /*szavak[0].szo[0] = 'z'+1);szavak[0].szo[1] = '\0';tablahossz = 0; ... { /* Nem talalta meg. Betesszuk a tablaba */ for (i=tablahossz; i>=hely; i--) { szavak[i+1] = szavak[i]; } /*Masik kulonbseg!! */ szavak[hely].szo = (char*) malloc(strlen(szo)+1); /* Innen újra változatlan! */ strcpy(szavak[hely].szo,szo); szavak[hely].db = 1; tablahossz++; } /* If bennevan */ /* SZOFGV2.C *//* 2. valtozat */ /* Keszitette: Ficsor Lajos */ /* A szo statisztika keszitesehez szukseges fuggvenyek Csak a struktúra definícióját tartalmazó szodef2.h miatt változik, maga a függvény azonos!*/ #include <string.h>#include "szodef2.h"#include "szofgv2.h"
A teljes változat a lemezmellékleten megtalálható.
Ha az általunk megírt programot parancssorból indítjuk el, van lehetõségünk arra, hogy a parancssorban adatokat adjunk meg számára.
A main függvény prototípusa az eddigi példákban
void main(void)
volt. Teljes alakja azonban
int main(int argc, char* argv[], char* env[])
A main függvény visszatérési értékét a programot aktivizáló program (általában az operációs rendszer) kapja meg. A 0 visszaadott érték megállapodás szerint a sikeres végrehajtást jelenti. Az ettól eltérõ értékkel a program befejezõdésének okára utalhatunk. A visszatérési értéket a main függvényben levõ return utasítás, vagy a bárhol meghívható exit függvény állíthatja be.
Az exit függvény használatához az stdlib.h vagy process.h fejlécfile szükséges. Prototípusa:
void exit(int status)
A main függvény elsõ két paramétere a program aktivizálásához használt parancssorban található paramétereket adja vissza, a harmadik (ami el is maradhat) pedig az operációs rendszer környezeti változóit tartalmazza, az alábbiak szerint:
argc a paraméterek száma + 1
argv[0] a parancssorban a prompt után gépelt elsõ stringre (a program neve, esetleg elérési úttal) mutató pointer
argv[1] az elsõ paraméterre mutató pointer
.
.
.
argv[argc-1] az utolsó paraméterre mutató pointer
argv[argc] = NULL
Hasonlóan az env[0], env[1], ... , env[n-1] a program hívásakor érvényes környezeti változókat tartalmazza, (ahol n a környezeti változók száma), env[n]=NULL.
A parancssor paramétereit helyköz választja el. Ha egy paraméterben helyköz lenne, azt idézõjelek közé kell írni. (A program az idézõjelet nem kapja meg!)
A C nyelv használatát számos szabványos függvény segíti. Ezek ismertetésére itt természetesen nincs hely, ezért csak felsoroljuk a legfontosabb témacsoportokat, és azokat a header file-okat, amelyet a szükséges deklarációkat és egyéb elemeket tartalmazzák.
Nem teljeskörûen ismertetjük a string kezeléshez és a file kezeléshez szükséges függvényeket.
Header file neve |
Funkció |
stdio.h |
Szabványos adatbevitel és adatkivitel |
ctype.h |
Karakter-vizsgálatok |
string.h |
String kezelõ függvények |
math.h |
Matematikai függvények |
stdlib.h |
Kiegészítõ rendszerfüggvények |
assert.h |
Programdiagnosztika |
setjmp.h |
Nem lokális vezérlésátadások |
time.h |
Dátum és idõ kezelése |
signal.h |
Jelzések (UNIX signal-ok) kezelése |
limits.h float.h |
A gépi megvalósításban definiált határértékek |
stdarg.h |
Változó hosszúságú argumentumlisták kezelése |
Természetesen minden gépi megvalósítás további, rendszer-specifikus függvényeket is tartalmaz. (Ilyen például DOS alatt a conio.h a direkt képernyõkezelés függvényeivel.)
Itt ismertetjük a legfontosabb karakterkezelõ függvényeket. A felsorolás nem teljes!
Szükséges a ctype.h header file.
int tolower(int ch)
A paraméterét kisbetûsre konvertálja, ha az az A - Z tartományba esik, változatlanul hagyja egyébként.
int toupper(int ch)
A paraméterét nagybetûsre konvertálja, ha az az a - z tartományba esik, változatlanul hagyja egyébként.
Szükséges a string.h header file.
int strcmp(char* s1, char*s2)
Összehasonlítja az paramétereit, és a visszatérési értéke
negatív, ha s1 < s2
0, ha s1 = s2
pozitív, ha s1 > s2
int strncmp(char* s1, char*s2, int maxhossz)
Mint az strcmp, de legfeljebb maxhossz karaktert hasonlít össze.
char* strcpy (char* cel, chsr* forras)
A forras stringet a cel stringbe másolja, beleéertve a záró 0-át is. Visszatérési értéke a cel string mutatója.
char* strncpy (char* cel, chsr* forras, int maxhossz)
Mint az strcpy, de legfeljebb maxhossz karaktert másol.
Az stdlib.h header file kell!
int atoi(char* s)
Az s stringet egész számmá konvertálja. A konverzió megáll az elsõ nem megfelelõ karakternél.
long atol(char* s)
Az s stringet hosszú egész számmá konvertálja. A konverzió megáll az elsõ nem megfelelõ karakternél.
double atof(char* s)
Az s stringet lebegõpontos számmá konvertálja. A konverzió megáll az elsõ nem megfelelõ karakternél.
A C alapvetõen byte-ok (pontosabban char típusú egységek) sorozataként tekinti a file-okat. A programozó feladata a file tartalmát értelmezni. Egyetlen kivétel: ha a file-t text típusúnak deklaráljuk, a '\n' ("sorvég") karakter operációs rendszer függõ kezelését elfedi elõlünk.
Minden file-t egy FILE elõredefiniált típusú struktúra azonosít. Erre mutató pointert (FILE* típusút) használnak a file-kezelõ föggvények. Van három elõre definiált FILE* változó:
stdin : standard input
stdout : standard output
stderr : standard hibacsatorna
Minden file-t a használata elõtt meg kell nyitni. Ezzel kapunk egy egyedi azonosítót, amellyel a késõbbiekben hivatkozunk rá, illetve beállíthatjuk bizonyos jellemzõit. Erre szolgál az fopen függvény:
FILE* fopen(char* filenev, char* mod)
ahol
filenév paraméter a file neve (az adott operációs rendszer szabályai szerint)
mod egy, két vagy három karakteres string, amely meghatározza a file használatának a módját, az alábbiak szerint:
Mód jele |
Használat módja |
A megnevezett file-lal való kapcsolat |
r |
Csak olvasásra |
Léteznie kell! |
w |
Csak írásra |
Ha létezik, elõzõ tartalma elvész, ha nem létezik, létrejön egy új (üres) file. |
a |
Hozzáírás |
Ha létezik, az írás az elõzõ tartalom végétól kezdõdik. Ha nem létezik, létrejön egy új file. |
r+ |
Olvasás és írás |
Léteznie kell! |
w+ |
Olvasás és írás |
Ha létezik, elõzõ tartalma elvész, ha nem létezik, létrejön egy új (üres) file. |
a+ |
Olvasás és hozzáírás |
Ha létezik, az írás az elõzõ tartalom végétõl kezdõdik. Ha nem létezik, létrejön egy új file. |
A fenti hat mód jel bármelyike után írható a b vagy t karakter, jelezve a bináris vagy text (szövegfile) jelleget. A különbség csak az, hogy a bináris filet mindig byte-onként kezeli a program, míg szövegfile esetén a '\n' karakter kíírása az operációs rendszer által sorvégjelként értelmezett byte-sorozatot szúr be a file-ba, az ilyen byte-sorozat olvasása pedig egy '\n' karaktert eredményez.
Példák:
"a+t" szövegfile megnyitása hozzáfûzésre
"rb" bináris file megnyitása csak olvasásra.
A függvény visszatérési értéke a file leíró struktúrára mutató pointer, ha a megnyitás sikeres, a NULL pointer egyébként. Ezért szokásos használata:
FILE* inputfile; . . . if ( (inputfile=fopen("bemeno.txt","rt")) == NULL) { fprintf (stderr,"File nyitási hiba az input file-nál!\n"); exit(1); /* A program futásának befejezése */ }else { /* A file feldolgozása */ }
Minden file-t a használata után le kell zárni. Erre szolgál az
int fclose(FILE* f)
függvény, amelynek paramétere a lezárandó file leírója, visszatérési értéke 0 sikeres lezárás esetén, EOF egyébként.
Megjegyzések:
A file lezárása után az azonosítására használt változó újra felhasználható.
A fõprogram (main függvény) végén minden nyitott file automatikusan lezáródik, mielõtt a program befejezi a futását.
A program nem közvetletlenül a file-ba (többnyire a lemezre) ír, hanem egy bufferbe. A bufferbõl a file-ba az adatok automatikusan kerülnek be, ha a buffer megtelik. A file lezárása elõtt a nem üres buffer is kiíródik a file-ba. Ha azonban a program futása rendellenesen áll le, nem hajtódik végre automatikusan file lezárás, így adatok veszhetnek el.
int fprintf(FILE* f, char*s, . . . )
Mint a printf, csak az f file-ba ír ki.
int fscanf(FILE* f, char*s, . . . )
Mint a scanf, csak az f file-ból olvas.
int getc(FILE* f)
Mint a getchar, csak az f file-ból olvas.
int putc(char c, FILE* f)
Kiírja a c karaktert az f file-ba. Visszatérési értéke a kiírt karakter, hiba esetén EOF.
int fputs(char*s, FILE* f)
Kiírja az s stringet, a záró nulla karakter nélkül. Visszatérési értéke az utoljára kiírt karakter, vagy EOF.
char* fgets(char*s, int n, FILE* f)
Beolvas az s stringbe a sorvége karakterig, de maximum n-1 karaktert. A string végére teszi a záró nulla karaktert. Visszatérési értéke a beolvasott stringre mutató pointer, vagy NULL.
Megjegyzés:
A fenti függvények elsõsorban szöveges file-ok kezelésére szolgálnak, bár a getc és putc mindkét típus esetén használható, de sorvég-karakter esetén a mûködésük a file típusától függ.
int fread(void*p, int meret, int n, FILE* f)
A függvény n db, meret méretû adatot olvas be, és elhelyezi azokat a p címtõl kezdõdõen. A p tetszõleges objektumot megcímezhet. A programozó felelõssége, hogy a feltöltött memóriaterület értelmezhetõ-e. Visszatérési értéke a beolvasott adatok száma. Hiba vagy file vége esetén ez n-tõl kisebb.
int fwrite(void*p, int meret, int n, FILE* f)
Mint az fread, de kiír.
Megjegyzés:
A fenti függvények a bináris file-ok kezelésére szolgálnak.
Minden file-hoz tartozik egy aktuális pozíció, amit byte-ban számítunk. Egy file megnyitása után az aktuális pozíció 0, azaz a file elejére mutat. A végrehajtott írási és olvasási mûveletek az aktuális pozíciót is állítják, az átvitt byte-ok számának megfelelõen. Az aktuális pozíciót a programból közvetlenül is állíthatjuk, amivel a file feldolgozása tetszõleges sorrendben megvalósítható.
int fseek(FILE* f, long offset, int viszonyitas)
Hozzáadja (elõjelesen!) az offset értékét a viszonyitas által meghatározott értékhez, és ez lesz az új aktuális pozíció. viszonyitas lehetséges értékei (elõredefiniált konstansok):
SEEK_SET a file eleje
SEEK_CUR az aktuális file pozíció
SEEK_END a file vége
Visszatérési értéke 0 sikeres végrehajtás esetén, nem 0 egyébként.
long ftell(FILE* f)
Visszadja az aktuális file pozíció értékét, a file elejétõl számolva. Sikertelenség esetén a visszatérési érték -1.
void rewind(FILE* f)
A file elejére állítja a file pozíciót.
Megjegyzés:
A file pozíció állítása a buffer kiírását is jelenti, ha az új pozíció nem a bufferen belül van.
Írjunk programot, amely az elsõ paraméterében megadott stringet megkeresi a második paraméterében megadott file-ban. Ha a harmadik paraméter "p", akkor pontos (kis/nagybetû érzékeny) egyezést keres, ha elmarad, vagy bármi más, akkor csak "betûegyezést" keres. A program csak akkor kezdje el a mûködését, ha a paraméterek száma megfelelõ. Jelezze ki, ha a megadott file megnyitása nem sikerült. A visszaadott státuszkód jelezze a hiba okát!
A program minden egyezésnél írja ki a sor és a soron belül az elsõ egyezõ karakter számát!
/* KERES.C */ /* Készítette: Ficsor Lajos */ /* A program megkeresi egy szövegfile-ban egy string elõfordulásait*/ #include <stdio.h>#include <stdlib.h>#include <string.h> #define MAXSORHOSSZ 300 int fgetstrl (FILE* inp, char* s, int max); FILE* inp; int main(int argc, char* argv[]){int pontos;int sorhossz;char sor[MAXSORHOSSZ+1];int mintahossz;int sorszam = 0;int i; if (argc < 3 || argc > 4 ) { fprintf(stderr, "\nHasznalata: keres minta filenev [p]\n"); exit(1); } if ( (inp=fopen(argv[2], "r")) == NULL) { fprintf(stderr, "\nFile nyitasi hiba!\n"); exit(2); } if (argc == 4 && argv[3][0] == 'p') { /* Pontos kereses kell! */ pontos = 1; }else { /* nem kell pontos kereses */ pontos = 0; /* A minta nagybetusre konvertalasa */ strupr(argv[1]); } /* Minta hossza */mintahossz = strlen(argv[1]); /* Olvasas soronkent a file vegeig */while ( (sorhossz=fgetstrl(inp, sor, MAXSORHOSSZ)) != EOF) { sorszam++; /* Ha nem kell pontos egyezes, nagybetusre konvertalas */ if ( !pontos) { strupr(sor); } /* kereses a soron belul */ for (i=0; i<=sorhossz-mintahossz; i++) { if ( strncmp(argv[1], sor+i, mintahossz) == 0) { /* Egyezes van! */ printf("\n Sorszam: %d, karakter: %d", sorszam, i); } } } /* while */ fclose (inp);return 0;} int fgetstrl (FILE* inp, char* s, int max)/****************************************************** A fuggveny egy sort olvas be az inp file-bol, vagy annak az elso max karakteret, ha attol hosszabb, es az s tombbe helyezi el, string-kent. Visszateresi erteke a beolvasott karakterek szama, vagy EOF*******************************************************/{int c;int i; i=0;while ( (c=getc(inp)) != '\n' && c !=EOF && i<max) { s[i++] = c; }s[i] = '\0';return c==EOF ? c : i;}