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