Az Objective-C programozási nyelv

Utasítások, vezérlési szerkezetek

Mint azt már tudhatjuk, az Objective-C a C nyelv objektum-orientált kiterjesztése. Itt megemlíteném azt is, hogy ennek következtében minden Objective-C fordító le tudja fordítani a tisztán C nyelven írott programokat is. Így gondolom az sem meglepő, hogy az Objective-C (mivel kiterjesztés), örökölte a vezérlési szerkezeteket is. Így ezeket csak nagyon röviden mutatnám be.

Az if-else kifejezés

Lehetőséget nyújt, hogy bizonyos feltételek teljesülése vagy nem teljesülése esetén más-más kódblokkok hajtódjanak végre. Szintaxisa a következő:

if (/* első feltétel */) { /* ha a feltétel nem nulla (true), akkor ez a kód fog végrehajtódni */ } else if( /* második feltétel*/ ) { /* ha a megelőző feltétel nulla (false) és ezen feltétel nem nulla (true), akkor ez a kód fog végrehajtódni */ } else { /* ha mindegyik feltétel 0 (false), akkor ez a kód fog végrehajtódni */ }

Érdemes megvizsgálni, hogy több feltétel teljesülése esetén mely blokk hajtódik végre. A válasz, hogy a feltételek fentről lefelé kerülnek kiértékelésre. Az első igaz feltételhez tartozó blokk végrehajtódik és a vezérlés az if-else szerkezet végére ugrik. Azaz minden esetben egynél több blokk nem hajtódhat végre - az else ág elhagyható, így ha egyik feltétel sem teljesül, akkor a vezérlési szerkezet végétől folytatódik a program futása. Ha több feltételt vizsgálunk meg és lehetséges, hogy nem minden esetben csak az egyik értékelődhet igaznak, akkor gondosan kell megválasztani a feltételek sorrendjét.

Feltételes kifejezés

A feltételes kifejezés egy olyan módja a vezérlésnek, mellyel feltételesen állíthatunk át értékeket. A szintaxis:

(/* ide jön a logikai kifejezés */) ? (/* ha nem nulla (true) */) : (/* ha nulla (false) */)

A logikai kifejezés kiértékelődik, ami ha nem nulla (true), a logikai kifejezés lefuttatja a ? és : közti részt, különben, a : utáni részt.

Ebben a példában két érték maximumát írjuk c-be:

c = (a > b) ? a : b;

while

A while ciklus a legalapvetőbb ciklus. Addig fut, amíg a feltétel nem nulla (true).

Például, ha a most következőt kipróbáljuk, a program úgy fog tűnni, mintha megállna a futása, és saját kezűleg kell leállítani. Az olyan esetet, amiben a feltétel sosem lesz hamis, végtelen ciklusnak nevezzük - ilyen esetben a ciklusmag újra és újra lefut.

int a=1; while(42) { a = a*2; }

Itt egy másik while ciklusos példa: A 100-nál kisebb kettő hatványokat írja ki.

int a=1; while(a<100) { printf("a is %d \n",a); a = a*2; }

Minden ciklus lefolyását irányíthatjuk még break, és continue kifejezésekkel is. A break kifejezés azonnal kilép a ciklusmagból. A continue utasítás pedig kihagyja a blokk további részét, és a blokk kezdeténél folytatja a futást. Példa:

int a=1; while (42) { // Addig fut, amíg a break kifejezésig nem ér printf("a = %d ",a); a = a*2; if(a > 100) break; else if(a==64) continue; // A while blokk kezdetén folytatja a futást printf("a nem 64\n"); }

Ebben a példában a számítógép az 'a' értékét írja ki, és azt, hogy nem 64 (hacsak nem lépi át ezt a continue). Hasonlóan a fenti if-hez, a while loop blokkjairól is elhagyható a {}. Példa:

int a=1; while(a < 100) a = a*2;

Ez addig növeli az 'a'-t, amíg nem kevesebb, mint 100. Nagyon fontos megemlíteni, hogy amint a while ciklus vezérlő feltétele 0-vá válik (false), a ciklus nem terminál addig, amíg a blokk futása be nem fejeződött, és el nem érkezik annak az ideje, hogy újra kiértékelődjön a feltétel

Ha egy while ciklust szeretnénk termináltatni azonnal, amint egy feltétel bekövetkezik, a break-et használhatjuk. Példa:

int i = 5; while(i) printf("A Java és a C# nem képes erre.\n");

Ez a ciklusmagot 5-ször hajtja végre, úgy, hogy az i 4-től 0-ig vesz fel értékeket, csökkenően.

do..while

A do..while ciklus utólagos ellenőrzést végző while ciklus. Ez azt jelenti, hogy a feltételt minden egyes futás után értékeli ki. Ennek eredményeképp, még ha a feltétel nulla (false) is volt, akkor is legalább egyszer lefut. Szintaktikája:

do { /* utasítások */ } while (feltétel);

Itt felhívnám a figyelmet, a termináló }-ra! . Itt a korrekt szintaktikához szükség van rá. Mivel ez a ciklus is a while egy alakja, így erre is ugyanúgy működik a break és a continue. A continue hatására a feltétel ellenőrzésére ugrik a végrehajtás szála. A break hatására kilép a ciklusból.

Érdemes megjegyezni, hogy a do..while és a while funkciója nagyjából azonos, egy fontos kivétellel: a do..while ciklusok mindig garantáltan végrehajtódnak legalább egyszer, míg a while ciklusok nem, ha a ciklus-feltételük 0 (false) kezdetben.

for

a for ciklusok általános alakja:

for(inicializáció; teszt; növelés) { /* utasítások */ }

Az inicializáció pontosan egyszer hajtódik végre, pontosan a teszt legelső kiértékelése előtt Tipikusan arra használják, hogy kezdőértéket adjanak egy változónak, habár ez nem feltétlen szükséges. Az inicilizáló kifejezés használható arra is, hogy deklaráljuk, és inicializáljuk a ciklusban használt változókat. A teszt kifejezés minden, a ciklusmagba való lépéskor kiértékelődik. Ha ez a kifejezés 0 (false) az ellenőrzéskor (azaz ha nem true), akkor nem lép bele a ciklusmagba, és a futás a for után folytatódik. Ha a feltétel nem nulla (true), akkor a ciklusmag hajtódik végre. Minden egyes iteráció után az inkrementáló utasítás fut le. Ezt leggyakrabban arra használják, hogy a ciklusváltozó értékét növeljék (a ciklusváltozó az, aminek az értékét a teszt kifejezésben ellenőrizzük, és az inicializáló kifejezésben inicializáljuk). Ha egy continue kifejezést hajtunk végre a ciklusmagban, akkor annak hatására lefut az inkrementáló utasítás. Minden eddig említett rész opcionális a for ciklusban, így elhagyható. Ennek okán sokféle érdekes dolog hajtható végre ennek segítségével. Gyakran használják a for ciklust egy tömb elemein való végiglépkedéshez.

int myArray[12]; int ix; for (ix = 0; ix<12; ix++) myArray[ix] = 5 * ix + 3;

A fenti for ciklus inicializálja a myArray mind a 12 elemét. A ciklusváltozó bármelyik értéknél kezdődhet. Az alábbi példában 1-nél kezdődik:

for(ix = 1; ix <= 10; ix++) { printf("%d ", ix); }

ennek hatására ez íródik ki: 1 2 3 4 5 6 7 8 9 10 A leggyakrabban a ciklusváltozót 0-ra inicializáljuk (mert a tömbök indexelése 0-nál kezdődik), de néha más értékeket is használunk. Az inkrementáló utasítás más dolgokra is alkalmas, például dekrementálásra. Így gyakori a for ciklus eme formája:

for (i = 5; i > 0; i) { printf("%d ",i); }

ennek hatására ez íródik ki: 5 4 3 2 1. Itt van egy olyan példa, amiben a teszt kifejezés egy egyszerű változó: Ha a változó értéke nulla, vagy NULL, a ciklus kilép, máskülönben a ciklusmag hajtódik végre.

for (t = list_head; t; t = NextItem(t) ) { /*a ciklusmag*/ }

A while ciklussal is megírhatjuk ugyanezt, de a for ciklus elterjedtebben használt intervallumokon való bejáráshoz, mert minden hozzá szükséges információ egy sorban helyezhető el. Mint már említettem, a for ciklusnak minden utasítása opcionális, így például ilyen is lehetséges, és legális:

for(;;) { /* utasítások */ }

Ez is egy végtelen ciklus, mert egészen addig, amíg a ciklusmagban nem érünk el egy lehetséges break kifejezéshez, addig iterálódik Az üres kifejezés mindig true-ként értékelődik ki. Gyakran használatos még a vessző operátor is:

int i, j, n = 10; for(i = 0, j = 0; i <= n; i++,j+=2) printf("i = %d , j = %d \n",i,j);

foreach

Ez nem a C-ből örökölt, ez csak Objective-C-ben használható. Valójában a for ciklusnak létezik még egy típusú szintaxisa. Ezt a legtöbb programozási nyelvben foreach néven tudjuk elérni, de Objective-C-ben for maradt a neve. Először emlékezzünk vissza a már megismert for ciklusra, amivel előre meghatározott lépésszámú iterációt készíthetünk: //Ez a ciklus egyszerűen ismétel egy utasítást //előre meghatározott lépésszámban:

for (int i=0; i<=3; i++) NSLog([NSString stringWithFormat:@"i=%i", i]);

Most lássuk a foreach típusú for ciklust. A foreach ciklust általában arra használják, hogy végiglépkedjenek vele egy objektumokat tartalmazó listán. Általános szintaxisa: 1.:

for ( Típus újVáltozó in kifejezés ) { utasítások }

vagy 2.:

Típus létezőElem; for ( létezőElem in kifejezés ) { utasítások }

1. példa:

//Hozzunk létre egy string-eket tartalmazó tömböt a következő példához: NSMutableArray *bunchOfThings = [[NSMutableArray alloc] init]; [bunchOfThings addObject:@"Zero"]; [bunchOfThings addObject:@"One"]; [bunchOfThings addObject:@"Two"]; [bunchOfThings addObject:@"Three"]; // Az alábbit, mint már említettem gyakran hívják foreach ciklusnak // Ezt a listát használva, minden // elemre végrehajtunk egy utasítást. // Tipp: ezt a típusú ciklust bármilyen típusú, objektumokat tartalmazó listán használható // így ha van egy listád, ami saját típusú objektumokat tartalmaz // ez egy egyszerű módszer az elemein való végiglépkedéshez // egyszerre. for(NSString *s in bunchOfThings) NSLog([NSString stringWithFormat:@"s=%@", s]); // Fontos megjegyezni, hogy a tömböt, amit lefoglaltunk, fel kell szabadítani, // mert valamikor korábban alloc/init-et hívtunk rá. [bunchOfThings release];

2. példa:

NSArray *array = [NSArray arrayWithObjects: @"One", @"Two", @"Three", @"Four", nil]; for (NSString *element in array) { NSLog(@"element: %@", element); }

3. példa:

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: @"quattuor", @"four", @"quinque", @"five", @"sex", @"six", nil]; NSString *key; for (key in dictionary) { NSLog(@"English: %@, Latin: %@", key, [dictionary valueForKey:key]); }

4. példa:

NSArray *array = [NSArray arrayWithObjects: @"One", @"Two", @"Three", @"Four", nil]; NSEnumerator *enumerator = [array reverseObjectEnumerator]; for (NSString *element in enumerator) { if ([element isEqualToString:@"Three"]) { break; } } NSString * next = [enumerator nextObject]; // next = "Two"

switch-case

Tegyük fel, hogy egy olyan programot szeretnénk írni, amiben a felhasználó beír egy számot, és mi az annak megfelelő angol osztályzatot szeretnénk kiíratni Ha ezt if-else szerkezettel írnánk meg, valahogy így nézne ki:
if(grade == 1) { printf("A\n"); } else if(grade == 2) { printf("B\n"); } else if /* stb... */

A hosszú if-else-if-else-if-else-stb... használata nehézkes lehet. Szerencsére van erre egy megoldás: ez a switch-case. Ennek alapvető szintaxisa:

switch(/* integer vagy enum változó jöhet ide */) { case /* az előbb említett változó lehetségesen vizsgált értékei */ : /* kód */ break; case /* egy másik lehetséges érték */: /* más kód */ break; ... default: /* még több kód */ break; }

Amennyiben a break utasítást elhagyjuk, úgy a vezérlés nem a switch-case szerkezet utánra kerül, hanem az alatta elhelyezkedő ágra. A default ágban elhagyható lenne ezen utasítás, de konvenció szerint feltüntethető Amennyiben a case utasítás után az adott blokkot egy változó deklarálásával kezdjük, úgy fordítási hibát fogunk kapni. Ezt a hibát feloldani, azzal lehet, ha a case utáni blokkot { } szimbólumok közé helyezzük. Ezt az esetet jól szemlélteti a következő példa:

NSUInteger i; switch (i) { case 0: /* Do something */ break; default: NSString *s = @"something"; s; break; }

A következő hibákról fog minket értesíteni a fordító:

Expected expression before 'NSString' 's' undeclared (first use in this function)

Az alábbi módosított kód pedig hiba nélkül fog lefordulni:

NSUInteger i; switch (i) { case 0: { /* Do something */ break; } default: { NSString *s = @"something"; s; break; } }

goto

A goto nem hogy az objektumorientált szemléletet de még a struktúrált programoás fogalmát is sérti. Gyakori inkonzisztens használatával a kód olvashatatlanná és érthetetlenné válhat, ezért használtatát nagymértékban nem javasolják

A goto egy nagyon egyszerű, és tradicionális irányító mechanizmus. Ez egy olyan kifejezés, amit azonnali, és feltétel nélküli kód-béli ugrásra használhatunk. Ahhoz, hogy a goto-t használni tudjuk, szükségünk van a kódban egy címkére. A címke egy névből, és egy :-ból áll. Ha ez meg van, akkor használható a "goto név;" utasítás a programban. Ennek hatására a futtatás a "név" nevű címke sorától folytatódik. A szintaxisa tehát így néz ki:

MyLabel: /* utasítások */ goto MyLabel;

Annak képessége, hogy ilyen fajta módon tudjuk a végrehajtást irányítani, nagyon erőteljes eszköz a többi vezérlési szerkezethez képest. (Ez azonban egy kétélű fegyver!) Nézzük az alábbi példát:

if (feltétel) { S; } else { T; } /* ... */

Ugyanez a kifejezés érhető el az alábbi módon, két goto-t, és két címkét használva:

if (feltétel) goto Label1: T; goto Label2; Label1: S; Label2: /* ... */

Itt az első goto utasítás feltételes. A második feltétel nélküli. Ezt az alábbi módon is elérhetjük:

while (feltétel1) { S; if (feltétel2) break; T; } /* ... */

Amit így is írhatunk:

Start: if (!feltétel1) goto End; S; if (feltétel2) goto End; T; goto Start; End: /* ... */

Amint azt ezek az esetek is bemutatták, a goto kifejezés használata kiváltható más utasítások használatával. A goto-k zabolátlan használata olvashatatlan, és karbantarthatatlan kódot eredményez, miközben ezek kiváltása if-else-ekkel, és for ciklusokkal jobb Kifejezőképességet eredményez. Így a goto kifejezés használata elhagyható, de vannak esetek, amikor pont, hogy elősegíti a könnyebb olvashatóságot, és segítségével elkerülhető a kódduplikálás, illetve a vezérlő változók használata szükségtelenné tehető. Tartsuk szem előtt, hogy a C stílus irányelvei szigorúan tiltják a goto használatát, az alábbi példákat kivéve.

A goto egyik felhasználása a többszörös, mély ciklusokból való ki-break-elés. Mivel a break nem működik (csak egy ciklusból lehet vele kilépni), a goto használható arra, hogy teljesen kilépjünk vele egy mély ciklusból. Goto nélkül is ki tudnánk lépni egy ilyen fajta, mély ciklusból, de ehhez extra változókra, és elágazásokra lenne szükségünk, ami sokkal kevésbé olvasható kódot eredményez, mint goto-val lenne.

A goto használatával könnyen vissza tudunk vonni akciókat, régi vágású stílusban, tipikusan annak elkerülésére, hogy olyan memóriaterületet szabadítsunk fel, ami már le lett allokálva.

Egy másik elfogadott felhasználás az állapot-automata implementációja. Ez egy elég speciális terület, ezért ennek ismertetésére nem térünk ki.