FizWeb

Porkoláb Zoltán - C programozás 2017.10.16. előadás jegyzet

5. előadás: Utasítások, vezérlési szerkezetek, deklarációk.

a=b //kifejezés
a=b; //utasítás

ELÁGAZÁSOK:

if (kif) ut1
[else {ut2 ut3...}]

pl.
if (5 != printf("Hello"))
{
  printf("Meglepo.");
}
else
{
  printf("Ok");
}

//utasításokat összefoglaló blokk: {} után nincs ;
--> programozási nyelvenként változó a ; használata pl. Pascal: a ";" nem része az utasításnak -> elválasztó
--> kiderült: kevesebb hiba, ha a ; hozzá tartozik (utasítás lezáró)

if (a>b)
  a=b;

--> C-ben nem kötelező a {}
-> Go-ban kötelező
-> Pythonban az számít, hogy mit hova írok

DE: érdemes kitenni:
pl. security bug:
if (/* feltétel */)
  goto /*hibakezelés*/
  goto /*hibakezelés*/
//megkétszereződött, így akkor is hibát keresett, ha a feltétel nem teljesült
//nem lett volna baj, ha kiírja a {}-t

+tanács:
if (5==n) //defensive, érdemes így
if(5=n) // hibát dob
if(n=5) //valid értékadás lenne...

C-ben nincs beépített ELIF:
pl. egy fgv., ami egy magánhangzót a következőre cserél
char conv(char ch) //paraméter: lokális változó: felül tudom írni
{
  if ('a'==ch) {
    ch='e';
  }
  else if ('e'==ch) {
    ch='i';
  }
  else if ('i'==ch) {
    ch='o';
  }
  else if ('o'==ch) {
    ch='u';
  }
  else if ('u'==ch) {
    ch='a';
  }
  return ch;
}

--> ez így rusnya -->ami csúnya program, az majdnem biztos h rossz (ez nem jelenti h ami szép, az jó)
ugyanez switch-csel: //switch után egész értékű kifejezés kell: int, bool, long, longlong, char...

char conv(char ch)
{
  switch (ch)
  {
    case 'a': ch='e'; break; //ha nem írnám a breaket, utána folytatná a konverziót...
    case 'e': ch='i'; break;
    case 'i': ch='o'; break;
    case 'o': ch='u'; break;
    case 'u': ch='a'; break;
    default: /* nothing to do */ break; //ha egyik case sem
  }
}
Tanács: mindig a default megírásával kezdjük!
--> ha van default és mindenhol van break, akkor nem számít a sorrend

Miért nincs automatikusan break?
pl.
switch (day) {
  case 1 :
  case 2 :
  case 3 :
  case 4 :
  case 5 : munka(); break;
  case 6 :
  case 7 : pihi(); break;
}
________________________________________

CIKLUSOK:

while (kif) ut
for (kif1; kif2; kif3) ut //inicializáló, ciklusfeltétel, a továbbléptető kif
//van egy harmadik, nem ajánlott:
dowhile -> nem elölvizsgáló, egyszer legalább lefut -> veszélyes

//a for ciklus ekvivalens ezzel:
kif1;
while (kif2) {
  ut
  kif3;
}

Pl.
for (i = 0; i < 10; ++i) {
  /* code */
}
VAGY:
int i = 0;
while (i<10) {
  /* code */
  i++;
}

az előbbi karaktercserélő program ciklusokkal:

char conv(char ch)
{
  char from[5] = {'a','e','i','o','u'}; //inicializálja a blokkot -> kell ;
  char to[5] = {'e','i','o','u','a'};
  int i;
  for (i = 0; i < 5; ++i) {
    if (from[i] == ch) {
      ch=to[i]; break; //break nélkül hibás: a->e->i->o->u...
    }
  }
}

//break: kiugrik a ciklusból
//alternatív mo.: másik változó pl.
int found;
found = 0; //break helyett átírom

from[5] = {'a','e','i','o'}; // nem szólna, feltölti bináris nullákkal a fennmaradó helyeket
// 6 elemet nem engedne
//nem célszerű egy információt kétszer megadni --> konfúzió lehet
érdemes így:
from[] = ... //persze most ez hibát adna, mert a for-ban 5-ig megyek
-->más programozási nyelvben a tömbnek van hossza (len: Java, C#, python; size: C++)
-->a C-ben nincs: ha a tömb hossza automatikusan eltárolódna, mindig helyet foglalna:
sokszor felesleges infó, ha szükség van rá, el lehet tárolni (takarékosság pl. Mars-rover, pacemaker)
erre trükkök:

sizeof operátor:
for (i = 0; i < sizeof(from)/sizeof(from[0]); ++i) { //ha más méretű elemek vannak, jó marad
  /* code */
}

még lehet hiba: ha from és to mérete különb:
erre: assert művelet

assert(sizeof(from)==sizeof(to)); //ez egy macro, include-olni kell assert.h-t

ugyanezt máshogy: a from és to elemek tartozzanak össze (pythonban class lenne)
új típus deklarálása (kell a végén ;):
struct conv_t
{
  char from;
  char to;
};

char conv{char ch}
{
  struct conv_t t[]={{'a','e'}, {'e','i'}, {'i','o'}, {'o','u'}, {'u','a'}};
  int i;
  for (i = 0; i < sizeof(t)/sizeof(t[0]); ++i) {
    if (ch == t[i].from) {
      ch = t[i].to; break;
    }
  }
}

Mindig az adott feladattól függ h melyik módszert használom.
- gyors
- könnyen módosítható

Ha az első függvényhívásnál inicializálom a tömböt: többször nem kell
static char from[] //ezt a fordító fordítási időben inicializálja

Ha a futási időben kell beolvasni: egy tömböt be lehet

Leggyorsabb mo.: "brutálisan más" -> tfh. van egy tömb, ami az összes lehetőséget tartalmazza, a tömböt az ASCII kód szerint indexeljük
char conv(char ch)
{
  return t[ch];
}
--> ez a legkevésbé menedzselhető, módosítható

az előbbieken lehet gyorsítani:
static cast: //fordítási időben inicializálja (lokális statikus változó --> jövő órán)
ha nem írom elé: a conv() fgv meghívásakor újra meg újra létrehozza a tömböt
pl. static const from[] = {...}; //const: saját hülyeség ellen, hogy ne tudjam átírni egy elemét

Még néhány utasítás:
1.
goto cimke; //soha ne használd! (csak esetleg fel kell ismerni)
cimke: ut1 ... //mint egy case cimke a switch-ben

2.
break <-> continue
--> ciluson belülre írható: a ciklus végére ugrik
- break: a cikluson kívülre
- continue: a cikluson belülre (pl. a for ciklusban jön a kif3 pl. ++i, utána újra kiértékel)
--> hasznos lehet: ha egy nagy tömb elemei között egy tulajdonság esetén semmit nem kell csinálni
                   -> egyszerűbb, mint mindig kikötni h "ha(B és nem A)"
break: egy ciklusból lép ki (pl. switchből a cikluson belül)
UNIX shell: van break3, breakN (annyi szintből lép ki)

3.
return //visszatér a függvényből
pl. a switch-ben lehet: case 'a': return 'e';
DE: sokszor kiderül h még vmit el kell intézni a kilépés előtt: akkor a több return nehézség lehet