3 A C programozási nyelv szintaktikai egységei 3.1
Azonosítók
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 Helyes azonosítók például: Ez ezIs es_meg_ez_is _a4 _3 Helytelen azonosítók például: 6ez /* mert számmal nem kezdődhet. */ ez is /* mert szóköz nem számít betűnek. */ És_ez /* mert az É nem szerepel az angol abc betűi közt. */ Ez[10] /* mert a „[10]” a deklaráció (típusmegadás) része, nem az azonosítóé (ld. tömbök). */ Ez-is /* mert a kötőjel sem számít betűnek. */
3.2
Kulcsszavak
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.)
3.3
Állandók
3.3.1
Egész állandók
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. 3.3.2
Karakterállandó
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ábbiak szerint: \n \t 3.3.3
újsor vízszintes tabulátor
Lebegőpontos állandó
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
3.4
Változók
A változóknak van érték része, attribútumai (számunkra ez a típus), címe, és neve. A név egy azonosító. A változóra a nevével hivatkozhatunk a program szövegében. A cím az a memóriaterület, amelyen a változó értéke elhelyezkedik. Az érték pedig a változó típusához tartozó tartomány egy eleme. Mielőtt használunk egy változót, deklarációs utasításban a nevéhez típust kell rendelnünk. A deklarációs utasításban lehetőségünk van kezdőértéket adni a változóknak. Pl. char c; //Karakter típusú változó int x; //Egész típusú változó int tomb[10]; //Egészeket tartalmazó tömb típusú változó (a változónév: tomb) int x= 5, y= 6; Az =-t értékadó operátornak nevezzük. Hatására a bal oldalán álló változó felveszi a jobb oldalán álló kifejezés értékét. Ezt követően ha a változó megjelenik a kódban, mindig az utoljára felvett értéket képviseli. A következőkben megnézzük, milyen műveleteket végezhetünk a változókkal és a konstansokkal. int x= 5, y= 6; //kezdőérték adás z= 3 + 4; //értékadó utasítás x= y + 2; A fenti utasítások kiadása után az x értéke 8 lesz. A változókban tárolt értékekkel és a kódban megjelenő literálokkal (számok) számos művelet végezhető. Az gyakorlatokon leggyakrabban használt műveletek a következők: int x = 5, y = 6, osszeg, kulonbseg, szorzat, hanyados, maradek; osszeg = x + y; kulonbseg = x - y; szorzat = x * y; hanyados = x / y; maradek = x % y; A műveleti jeleket operátoroknak nevezzük. Az utasítások lefutása után az osszeg, kulonbseg, szorzat, hanyados változók rendre az x és y változó értékeinek összegét, különbségét szorzatát és hányadosát tartalmazzák, míg a maradek változó az x változó értékét y értékével elosztva kapott maradékot tartalmazza. Egy változó értékét önmagához képest, relatívan is megváltoztathatjuk: int x= 5; x= x + 2; A következő példában adott 2 változónk, amelyek két számot tartalmaznak. A feladat: cseréljük meg a változók tartalmát. A megoldáshoz felhasználunk egy harmadik változót is, amelyben ideiglenesen fogunk tárolni egy értéket: int x= 3, y= 4, z; z= x; x= y; y= z;
3.5
Nevesített konstans
Olyan konstans, amely névvel ellátott, és ezzel a névvel tudunk hivatkozni az értékére a program szövegében. Pl.: const int N = 4; //Itt az N egy nevesített konstans, értéke az egész program futása során 4
C-ben jellemző, hogy makrót használunk a konstans definiálására. Ezt a program elején a main függvényen kívül tehetjük meg. #define N 4 A makrózás annyit jelent, hogy a program szövegében előforduló név (itt: N) minden előfordulását az adott sor további részével (itt: 4) helyettesíti az előfordító. Figyeljük meg, hogy nincs pontosvessző a sor végén!
3.6
Kifejezés
A kifejezések operandusokból, és operátorokból, valamint zárójelekből épülnek fel. Az operandus lehet konstans, nevesített konstans, változó, vagy függvényhívás. 3.6.1
Operátorok
A C egyszerűsített precedencia táblázata a következő: ( ) [] ! ++ -*/% +< > <= >= == != && || = += -= *= /= %=
→ ← → → → → → → ←
A táblázat felső sorában lévő műveleti jelek kötnek legerősebben (vagyis a kiértékelés során ezek hajtódnak végre először), és soronként gyengülnek. Az azonos sorban lévő operátorok kiértékelése a nyíl által mutatott sorrendben történik. Az operátorok lehetnek prefixek, ami azt jelenti, hogy az operátor az operandus elé kerül, ilyenek például a matematikában az előjelek. Lehetnek infixek, ekkor az operátor két operandusa közé kerül, ilyen például az összeadás, és lehetnek postfixek, amikor az operandus mögött áll az operátor. A következőkben kifejezésekre láthatunk példákat: int x= 3, y= 2; a= (x + y) * (x + y); /* Vesd össze: a = x + y * x + y */ b= x*x + 2*x*y + y*y; c= x*x - 2*x*y + y*y;
3.7
Hasonlító és logikai operátorok
Mivel a programozás során egy-egy feladat megoldásához, csak úgy, mint a matematikában, szükségünk lehet kikötések, feltételek megadására. Ilyen feltételeket változókra, vagy kifejezésekre adhatunk meg hasonító és logikai operátorok segítségével. A C nyelv hasonlító operátorai: numerikus értékek egyenlőségvizsgálata: == numerikus értékek nem egyenlőségének vizsgálata: ! = numerikus értékek összehasonlítása: <, >, <=, >=
Hasonlításokat literálok és változók között végezhetünk, a hasonlítás eredménye egy logikai érték. A C nyelvben kétértékű logika van, azaz egy logikai érték igaz, vagy hamis lehet. A C a logikai értékeket egész számokként kezeli a következő módon: ha egy egész típusú változó, vagy kifejezés értéke nem nulla, akkor azt igaznak tekintjük ha egy egész típusú változó, vagy kifejezés értéke nulla, akkor azt hamisnak tekintjük int a= 4, b= 5, c;
c= (a < b); c= (a > b); c= 10; c= 0; c= (a == b); c= (a == 4); c= (a != 5); c= (a <= 10);
/*c erteke igaz*/ /*c erteke hamis*/ /*c erteke igaz*/ /*c erteke hamis*/ /*c erteke hamis*/ /*c erteke igaz*/ /*c erteke igaz*/ /*c erteke igaz*/
Összetettebb feltételek megadására használhatóak a C logikai operátorai, amelyek a matematikából ismert konjukció, diszjunkció és negáció megfelelői: és-kapcsolat: && vagy-kapcsolat: || tagadás: ! A logikai operátorokkal tehát bonyolultabb logikai kifejezések is felépíthetőek: int a= 4, b= 5, c; c= (a < b) && (a == b); c= (a < b) || (a == b); c= (((a < b) || (a > 10)) && ((a != 6) || (a < 2))); c= a && 0; c= a && 1; c= a && 25; c= !a; c= ! (a && 0 ); c= ! (a && 25 );
/*c erteke hamis*/ /*c erteke igaz*/ /*c erteke igaz*/ /*c erteke hamis*/; /*c erteke igaz*/; /*c erteke igaz*/; /*c erteke hamis*/; /*c erteke igaz*/; /*c erteke hamis*/;
Most lássunk néhány példát arra, hogy a hasonlító és logikai operátorok segítségével hogyan tudjuk változóknak bizonyos intervallumokba esését, és más speciális tulajdonságait vizsgálni (ezt a későbbiekben gyakran fogjuk használni):
a kisebb, mint 10: a < 10; a 10 és 20 közé esik: 10 <= a && a <= 20; a 10 és 20 közé, vagy 30 és 40 közé esik: (10 <= a && a <= 20) || (30 <= a && a <= 40); a osztható kettővel: (a % 2) == 0 a 100 osztója: (100 % a) == 0
3.8
Szelekció
Ahogy arról korábban már szó esett, az általunk írt programok futása szekvenciális, ami azt jelenti, hogy az utasítások egymás után kerülnek végrehajtásra,mindazonáltal nagyon gyakran kerülhetünk olyan helyzetbe, hogy egy kiszámított értéktől függően más és más kódot kellene végrehajtanunk. Például: gondoljunk arra, hogy egy nagyon egyszerű programot szeretnénk készíteni, ami kiszámítja egy szám abszolút értékét. Ha a szám pozitív, akkor önmaga az abszolút értéke, ha viszont negatív, akkor a -1-szerese. Érezhető, hogy már ezt a nagyon egyszerű feladatot sem tudjuk megoldani, anélkül, hogy a program futását ketté nem választanánk. Ugyanis attól függően, hogy a beolvasott szám negatív vagy pozitív, más-más utasításokat kell végrehajtanunk. Az ilyen esetek megoldására szerepel a C-ben a szelekciós (vagy elágaztató) utasítás: if(egesz_erteku_kifejezes) utasitas; [ else if(egesz_erteku_kifejezes) utasitas; ]... [ else utasitas; ]
Az utasítás szemantikája a következő: Az if alapszó után álló egész értékű kifejezés értéke meghatározásra kerül. Ha logikailag igaz, tehát nem 0, abban az esetben végrehajtódik az utána szereplő utasítás, és a program a szelekció utáni utasításon folytatódik, ha hamis, és van else if ág, akkor kiértékelődik az utána szereplő egész értékű kifejezés. Ha ez igaz, akkor végrehajtódik az utasítás, és a program a szelekció utáni utasításon folytatódik. Ha hamis, akkor és van még else if ág, akkor az is kiértékelésre kerül a fenti módon, ha nincs több else if ág, és nincs else ág sem, akkor a program a szelekció utáni utasításon folytatódik, ha van else ág, akkor végrehajtódik az else után álló utasítás, majd a program a szelekció utáni utasításon folytatódik. Példaként nézzük meg a fenti abszolútérték kiszámító programot, amely bemenete egy szám, kimenete pedig az abszolútértéke. int a,b; scanf( ”%d”, &a ); if(a >= 0) b= a; else b= (-1)*a; printf( ”%d\n”, a ); Ebben a példában a beolvasott szám abszolútértékét egy külön változóba számítottuk ki, a b-be. Ha az a nem negatív, akkor a b egyszerűen értékül kapja a-t, ha negatív, akkor a -1-szeresét. Ugyanezt megoldhatjuk a b változó használata nélkül is: int a; scanf( ”%d”, &a ); if(a < 0) a= (-1)*a; printf( ”%d\n”, a ); Válasszuk ki két változó közül azt, amelyik a kisebb értéket tartalmazza, és adjuk ezt értékül a min változónak! int a, b, min; scanf( ”%d %d”, &a, &b ); if ( a < b ) min= a; else min= b; printf( ”%d\n”, min ); Adott két változó a és b, változtassuk meg az értékeiket úgy, hogy a tartalmazza a kisebbet, és b a nagyobbat! int a, b, temp; scanf( ”%d %d”, &a, &b ); if ( b < a ) { temp= a; a= b; b= temp; }
A következő példában olyan programot készítünk, amely beolvas egy számot, és a kimenete a beolvasott szám legkisebb olyan prím osztója, amely kisebb, mint 10. Ha nincs neki ilyen, akkor a kimenet -1 lesz. int a,b; scanf( ”%d”, &a ); if ( (a % 2) == 0 ) b= 2; else if ( (a % 3) == 0 ) b= 3; else if ( (a % 5) == 0 ) b= 5; else if ( (a % 7) == 0 ) b= 7; else b= -1; printf( ”%d\n”, b );
3.9
Iteráció
A következő feladatot kell megoldanunk: beolvasunk egy számot, és számítsuk ki a faktoriálisát. Eddigi ismereteink alapján ezt a következő módon tehetjük meg: int a,b; scanf( ”%d”, &a ); if (a == 0) b= 1; else if (a == 1) b= 1; else if (a == 2) b= 1*2; else if (a == 3) b= 1*2*3; else if (a == 4) b= 1*2*3*4; else if (a == 5) b= 1*2*3*4*5; . . . printf( ”%d\n”, b ); Ezzel a megoldással az a probléma, hogy nagyon hosszú, és egyáltalán nem hatékony. Észre lehet venni, hogy minden esetben ugyanazt a műveletet (szorzás) kell a bemenettől függően általában nagyon sokszor végrehajtanunk. Az ehhez hasonló problémák megoldására szerepel a C-nyelvben az iteráció, mint vezérlési szerkezet. Több formája van, most az előírt lépésszámú ciklussal (for-ciklus) ismerkedünk meg. Szintaktikája: for(utasitas_1;egesz_erteku_kifejezes;utasitas_2) utasitas_3; A for-ciklus szemantikája a következő: végrehajtásra kerül az utasitas_1. Ezt követően kiértékelődik az egesz_erteku_kifejezes, ami logikai feltételként szolgál. Amennyiben ez logikailag hamis, a vezérlés az iterációt követő utasítással folytatódik. Ha a feltétel igaz, akkor végrehajtódik az utasitas_3, majd lefut az utasitas_2, ezt követően ismét kiértékelődik az
egesz_erteku_kifejezes, és így tovább, egészen addig, amig az egesz_erteku_kifejezes hamissá nem válik. Ekkor a vezérlés az iteráció utáni utasítással folytatódik. Lássuk példaként a faktoriális számítást: int a,b,i; scanf( ”%d”, &a ); b= 1; for(i= 2; i <= a; ++i) b= b*i; printf( ”%d\n”, b ); Hangsúlyozzuk, hogy egy programot nagyon sok módon meg lehet írni, például az alábbi program ugyanúgy faktoriálist számít: int a,b; scanf( ”%d”, &a ); b= 1; for(;a > 0; --a) b= b*a; printf( ”%d\n”, b );
3.10 Gyakorló feladatok Számítsuk ki a t változóba az a oldalú és m magasságú háromszög területét! t= a * m / 2; Számítsuk ki az a változóba az x1, x2, x3, x4, x5 változókban tárolt értékek számtani közepét: a= (x1 + x2 + x3 + x4 + x5) / 5; Írjunk fel egy logikai feltételt, amely akkor igaz, ha az x, y változókban tárolt koordinátájú pont benne van a A(0; 0);B(6; 0);C(6; 2);D(0; 2) csúcsokkal rendelkező téglalapban. b= ( 0 <= x ) && ( x <= 6 ) && ( 0 <= y ) && ( y <= 2 ); Írjunk fel egy logikai feltételt, amely akkor igaz, ha az x, y változókban tárolt koordinátájú pont nincs benne a A(0; 0);B(6; 0);C(6; 2);D(0; 2) csúcsokkal rendelkező téglalapban. b= !(( 0 <= x ) && ( x <= 6 ) && ( 0 <= y ) && ( y <= 2 )); Írjunk fel olyan utasítást, amely ha az a változó páros, megnöveli egy b változó értékét 1-el, ha a páratlan, csökkenti a b értékét 1-el. if ( (a \% 2) == 0 ) ++b; else --b; Írjunk fel egy olyan utasítást, amely ha az a változó értéke benne van a (1; 10) tartományban, megnöveli a b értékét 1-el, ha benne van a (11; 20) tartományban, akkor 2-vel, különben csökkenti b értékét 1-el. if ( ( 1 <= a && a <= 10 ) ) b+= 1; else if ( ( 11 <= a && a <= 20 ) ) b+= 2; else --b;
Írjunk olyan utasítást, amely 0-tól n-ig összeadja a számokat: int sum= 0, i; for ( i= 0; i <= n; ++i ) sum+= i; Írjunk olyan utasítást, amely 0-tól n-ig összeadja a páros számokat: int sum= 0, i; for ( i= 0; i <= n; ++i ) if ( (i % 2) == 0 ) sum+= i; Másik megoldás: int sum= 0, i; for ( i= 0; i <= n; i= i + 2 ) sum+= i; Írjunk programot, amely kiszámolja az egész számok összegét, és szorzatát (faktoriális) 1-tol n-ig. int sum= 0, fakt= 1, i; for ( i= 0; i <= n; ++i ) { sum+= i; fakt*= i; }
3.11 Tömb A C nyelvben adott m méretű egészekből álló t tömböt a következő módon hozhatunk létre: int t*m+, ahol m nem-negatív egész LITERÁL! Pl. int t[20]; A tömb elemeinek számozása 0-tól indul. A tömb első, második, harmadik, stb. elemeire a következő módon hivatkozhatunk: t*0+, t*1+, t*2+, … A tömb i indexű elemére a következő módon hivatkozhatunk: t[i]. int t[10], a; t[0]= 0; t[1]= 1; t[2]= 1; t[3]= 2; t[4]= 3; t[5]= 5; t[6]= 8; t[7]= 13; t[8]= 21; t[9]= 34; a= t[0] + t[2] + t[4] + t[6] + t[8]; A fenti kódrészletben egy tíz elemből álló t tömböt töltöttünk fel a Fibonacci-sorozat első tíz elemével, majd a sorozat páratlan indexű elemeinek összegét értékül adtuk az a változónak. Figyeljük meg, hogy a 0-val kezdődő indexelés miatt a sorozat első eleme a tömb 0 indexű eleme lesz, míg a Fibonacci-sorozat tízedik eleme a tömb 9 indexű helyére kerül. A fenti módon, statikusan lefoglalt tömbökben negatív indexű elemre, illetve a tömb felső határán túl mutató elemre hivatkozni nem szabad, a program hibás működéséhez vezet.
3.12 Gyakorló feladatok Töltsünk fel egy 100 elemű tömböt az első száz természetes számmal: int t[100]; int i; for ( i= 1; i < 100; ++i ) t[i]= i; Töltsünk fel egy 100 elemű tömböt az első 100 páros számmal: int t[100]; int i,j; for ( i= 0; i < 100; ++i ) t[i]= i*2; Töltsünk fel egy 100 elemű tömböt az első száz olyan számmal, amelyek 3-al, és 7-el sem oszthatóak. int t[100]; int i= 0,j; for ( j= 0; 1; ++j ) { if ( ((j % 3) != 0 ) && ((j % 7) != 0 ) ) t[i++]= j; if ( i >= 100 ) break; } Bekérünk a standard input-ról 100 egész számot egy 100 elemű tömbbe. Adjuk össze a tömbben szereplő értékeket a sum változóba. int t[100]; int i,sum= 0; for ( i= 0; i < 100; ++i ) scanf( ”%d”, &t[i] ); for ( i= 0; i < 100; ++i ) sum+= t[i]; printf( ”%d\n”, sum ); Számold ki t átlagát, az atlag nevű változóba: int i, szum = 0; double atlag = 0.0; for(i=0; i
Add meg a t tömbben a páratlan indexű helyeken lévő, nullától különböző elemek szorzatát a szorzat változóba: int i, szorzat = 1; for(int i=1; i