Tartalmi kivonat
Utoljára frissítve: 2003-10-02 15:00 1.0–s verzió Feladatok 1. Igazak-e a következő állítások? a, Valamennyi feladat megoldására a C++ nyelv a legalkalmasabb, mivel hatékony objektumorientált kódot lehet így készíteni. b, A C++ nyelv régen született, ezért mára nagyon kiforrott, így szinte tökéletes. c, A C++ nyelv használata során könnyű hibázni a programírás során, ami a nyelv egyik hátránya. d, Ha hatékony objektum orientált kódot kell írni, akkor a C++ (az egyik) legelőnyösebb választás. Emiatt a rendszerprogramozás (egyik) leggyakoribb eszköze és valószínűleg az is marad. e, A C++ egy „szép” programozási nyelv, csak meg kell tanulni bánni vele, meg kell ismerni a trükkjeit és csapdáit. Így C++-ban programozni jó Megoldás 2. Mit ír ki a következő programrészlet? int x = 3; int y = 2; printf("x/y=%f ", (double)x/y); printf("x/y =%f ", (double)(x/y)); printf("x/y=%f ", x/y);
Megjegyzés: a cast operátor precedenciája nagyobb, mint a / operátoré. A pontos formázást nem kell megadni (a %f alapértelmezésben 6 tizedesjegyig írja ki a számot). Megoldás 3. Válaszoljon az alábbi kérdésekre! a, Mi az m, n és o változók értéke az alábbi kódrészlet végrehajtása után? int m=12, n, o; n=m++; o=++n; Megoldás b, Zárójelezze, hogy hogyan értékelődnek ki az alábbi operátorok és indokolja döntését: szulev.datumnap; // a operátor a struktúramező elérését jelenti szulev.datum->pnap; *p[10]; ++p[12]; p->s(); v=g=h; Megoldás c, Az alábbi példában értelmezze a 4-8. sorokat és adja meg e1, e2, e3, e4, e5 értékét! int e1, e2, e3, e4, e5; int t[]={10, 20, 30, 40, 50}; int*p = t; e1 = *p++; e2 = ++*p; e3 = (*p)++; e4 = *++p; e5 = ++p[1]; Megoldás 4. Rejtenek-e valamilyen veszélyt az alábbi kifejezések? int j=10, k=10, m; m = osszead( k++, ++k); // az összead függvény visszatér a két paraméterének
összegével . if (k++> 2 || j--<4) { . } Megoldások 5. Van-e hiba az alábbi kódrészletben? . char* p; char* s=”abc”; strcpy(p, s); // s másolása p-be . char buff[3]; strcpy(buff, s); // s másolása buff-ba Megoldás 6. Írjon több forrásfájlból álló programot az alábbi feladatnak megfelelően! Írjon egy olyan max nevű függvényt, ami paraméterként egy integer tömböt és a tömb hosszát kapja meg. A függvény a tömbben megkapott elemek közül a legnagyobbal térjen vissza (felteheti, hogy valamennyi elem pozitív vagy nulla). A max függvény az m1cpp fájlban legyen definiálva Írjon egy olyan count1 nevű függvényt, ami azt számolja, hogy hányszor hívták meg (ez legyen a visszatérési értéke). A megoldáshoz NE használjon globális változót A count1 függvény az m1.cpp fájlban legyen definiálva Írjon egy olyan inc nevű függvényt, ami egy globális gCount változót növel egyel. Az inc függvény az m1.cpp fájlban legyen
definiálva A main függvény az m2.cpp fájlban legyen definiálva Ebben mutasson példát a fenti függvények és a gCount változó használatára. Megoldás 7. Írjon olyan függvényt, ami egy paraméterként megkapott sztringben a ’&’ karaktereket ‘%’-ra cseréli! Megoldás 8. Írjon olyan függvényt, ami az első paraméterként megkapott sztringet a második paraméterben megkapott helyre másol úgy, hogy a ”&&” szekvenciákat ‘&’-ra cseréli! Megoldás 9. Jellemezze az alábbi típusokat! Hogyan néznek ki a memóriában és mi történik a tömb ill. pointer műveletek végrehajtásakor? a, int* p; Megoldás b, int t[3]; Megoldás c, int* p; int t[3]; p=t; *p=1; *(p+2)=2; p++; Megoldás d, int* p[5]; p[0]=(int*)malloc(sizeof(int)); p[1]=(int*)malloc(3sizeof(int)); *p[0]=1; *p[1]=2; p[1][1]=3; *((p+1)+2)=3; free(p[0]); free(p[1]); Megoldás e, int t[3][2]; int (*p)[2]; p = t; *t[0]=1; t[0][0]=2; t[1][1]=3; *(t[1]+1)=4;
*((t+1)+1)=5; *p[0]=10; p[0][0]=11; p[1][1]=12; *(p[1]+1)=13; *((p+1)+1)=14; Megoldás 10. Összetett típusdeklaráció Adja meg egy-egy mondatban, hogy mit definiálnak az alábbi kifejezések! a, void (*(p)[10])(int); b, void (*t[10])(int); c, double (*(p)(int))(char); Megoldás 11. Írjon C vagy C++ programot a következőkre: Definiáljon egy olyan struktúrát, ami könyvek listájának kezelését teszi lehetővé. Minden könyvről tárolni szeretnénk a címét (max. 100 karakter) és egy szerzőjének nevét (max 50 karakter) Írjon meg egy olyan függvényt, aminek két bemenő paramétere egy könyv címe és szerzője, és ami hozzáadja egy globálisan tárolt láncolt listához a „könyvet” (természetesen miután dinamikusan helyet foglalt egy struktúrának és felparaméterezte azt). A megoldás során felhasználhatja az strcpy sztringmásoló függvényt, aminek deklarációja: char* strcpy(char dest, char src); , és ami a dest paraméter által mutatott
helyre átmásolja az src stringet, a termináló ‘ ’-t is beleértve. Megoldás Megoldások 1. Feladat a, nem b, nem c, igaz d, igaz e, igaz Vissza 2. Feladat Az első printf „x/y=1.500000”-öt ír ki, mivel a kiértékelés sorrendje a következő: Az x konvertálása double-ra, majd a / operátor végrehajtása előtt y-t double-ra konvertálja a rendszer (mivel a / operátor egyik argumentuma double, a másik int, így az int paraméter a bővebb típusra konvertálódik, ami double). Ezután meghívódik a / operátor a két double argumentummal és double eredményt ad vissza. A második printf „x/y=1.000000”-t ír ki, mivel a kiértékelés sorrendje a következő: Meghívódik a / operátor a két int argumentummal és egészek közötti maradékos osztást végrehajtva int-tel tér vissza, aminek értéke 1. Ezután ezt az 1-et castolja double-lá a rendszer A harmadik printf hibás! Meghívódik a / operátor a két int argumentummal és egészek közötti
maradékos osztást végrehajtva int-tel tér vissza, aminek értéke 1. A problémát az okozza, hogy a printf függvénynek így egy int típusú érték adódik át (ami általában négy bájtos), a formatáló sztringben viszont %f-et adtunk meg, amivel az mondjuk a fordítónak, hogy az eredményt 8 bájtos double-ként értelmezze. Emiatt a függvényhívás során a stack elkutyulódik, a program nem definiált módon hibásan fog működni. A példa jól demonstrálja, miért is veszélyes a printf és a hasonló változó argumentumú függvények használata. Vissza 3. Feladat a, Mind a három változó értéke 13. Vissza b, - azonos precedencia, balról jobbra kiértékeléssel (szulev.datum)nap; (szulev.datum)->pnap; - azonos precedencia szint, balról jobbra kiértékeléssel *(p[10]); - a [] operátor magasabb precedencia szinten van, mint a * operátor ++(p[12]); - a []operátor magasabb precedencia szinten van, mint a prefix operátor (p->)s(); - a -> és a
() operátor egy precedencia szinten vannak, balról jobbra kiértékeléssel v=(g=h); - az = operátor jobbról balra értékelődik ki Vissza c, int e1, e2, e3, e4, e5; int t[]={10, 20, 30, 40, 50}; int*p = t; e1 = *p++; // e1=10, p++-t kiértékeli, ami p eredeti meg nem növelt értéke (postfix ++ miatt), és az ezen a címen levő rekesz tartalmát írja e1-be e2 = ++*p; // e2=21, a p által mutatott értéket növeli és azt írja be e2-be (p-t az előző sorban növeltük!) e3 = (*p)++; // e3=21, p által mutatott értéket növeli, de a postfix ++ miatt az eredeti érték íródik e3-ba e4 = *++p; // e4=30, p-t lépteti egyel, és ahová ezután mutat, annak tartalmát írja e4-be e5 = ++p[1]; // e5=41, ekvivalens ++*(p+1)-el, így a p+1 által mutatott rekesz tartalmát egyel növeli és beírjja e5-be Vissza 4. Feladat Két rejtett veszélyforrás lapul a sorokban: Az m = osszead( k++, ++k); esetében a függvény paramétereinek kiértékelési sorrendje nem
definiált, így fordítótól függ az, hogy osszead(10, 11) vagy osszead(11, 11) ami ténylegesen lefut. Az if (k++> 2 || j--<4) esetében a feltétel csak addig értékelődik ki, amíg biztosan el nem dől. Így ha k>2, akkor a j-t nem csökkenti Vissza 5. Feladat Két hiba is van. Az strcpy(p, s); esetében az a gond, hogy az strcpy a p által mutatott területre másol, a p pedig nincs egy lefoglalt területre beállítva. Így a másolás egy nem definiált, véletlenszerű helyre (arra a címre, ami p értéke) történik, így véletlenszerűen felülíródik valami a memóriában (vagy kapunk egy memória védelmi hibát: „Access Violation at .”) Az strcpy(buff, s); már majdnem jó, mert it lefoglaltunk 3 karakternek helyet. Itt az a probléma, hogy 4 karakternek kellett volna helyet foglalni a sztringet lezáró ‘ ’ miatt. Vissza 6. Feladat m1.cpp: int max(int* p, int count) { int maxval = -1; int i; for (i=0; i<count; ++i, ++p) { if ( *p>maxval )
maxval = *p; } return maxval; } int count1() { static int cnt = 0; return ++cnt; } // definiáljuk a gCount globális változót (helyet is foglal) int gCount = 0; void inc() { ++gCount; } m2.cpp: #include <stdio.h> // deklarálni kell a globális változót az adott forrásfájlban használat előtt // itt helyfoglalás nem történik, az m1.cpp-ben definiált változót jelenti extern int gCount = 0; // deklarálni kell a függvényeket az adott forrásfájlban használat előtt int max(int* p, int count); int count1(); void inc(); main() { count1(); inc(); printf("A számlálók értéke: %d, %d", count1(), gCount); } Vissza 7. Feladat void strreplace(char* s) { while(*s!= ) { if (*s == &) { *s=%; } s++; } } // példa a használatra main() { char teststr[]="abc&d&f"; printf("teststr=%s ", teststr); strreplace(teststr); printf("teststr=%s ", teststr); } Megjegyzések: A „void strreplace(char* s)" helyett
írhattunk volna "void strreplace(char[] s)"-t, ami teljesen ugyanazt jelenti. Az strreplace függvény meghívásakor nem adódik át a tömb, csak az első elem (vagyis a tömb) címe. Ez egy 4 bájl átadását jelenti a stack-en (általában 32 bites OS alatt) Egy másik megoldás, ami tömbindexelést használ: void strreplace(char* s) { int i=0; while(s[i]!= ) { if (s[i] == &) { s[i]=%; } i++; } } Vissza 8. Feladat HF. Vissza 9. Feladat A lényeg, hogy emlékezzünk az alapszabályokra: • pointer definíció pl.: int* p; , ami egy pointernek foglal helyet • tömb definíció pl.: int t[10]; , ami 10 int-nek foglal helyet • a tömb neve (mint szimbólum) az első elem címét jelenti, de a címnek nem foglalódik külön hely • bármiből lehet tomb, pl. tömbből is, pl: int t[5][10];, ami olyan 5 elemű tömb, aminek elemei 10 elemű tömbök • tömb esetében a t[i] ekvivalens *(t+i)-vel, de ugyanez igaz a p pointerre is • p++, p+i, t+i, stb. a
pointert ill a tömböt mindig típusnyival lépteti (tömbnél ami az elem típusa, pointernél amire mutat) • ha tömböt adunk át függvénynek mindig csak a tömb (vagyis az első elem) címe adódik át, sohasem a teljes tömb • Mindig képzeljük el, hogy az adott változó hogy néz ki a memóriában. Memória rekeszekben gondolkodjunk, ami rekeszeknek címe van valahol a memóriában. • a többi kiderül a példákból a, int* p; p egy pointer int-re vagy egy int tömb első elemére. 4 bájtot foglal el a memóriában (32 bites OS alatt), ami egy címet tárol. Ez a pointer nincs semmi érvényes területre beállítva, így nem definiált, hogy hova mutat (véletlenszerű a tartalma). p ennek a rekesznek a tartalmát jelenti, ami mutat valahová ? Megjegyzés: minden pointer mérete (char*, int, stb.) megegyezik, vagyis ugyanannyi helyet foglal a memóriában. A mutatott érték típusát csak azért kell megadni, hogy ha növeljük/csökkentjük a pointer
értékét, akkor tudja a fordító, hogy hány bájttal kell pontosan növelni az értékét. Pl ha int*ról van szó, akkor a p++ 4-el növeli a pointer értékét, hiszen az int mérete négy bájt (általában). Így fog a pointer egy “elemnyivel” nőni. Vissza b, int t[3]; t egy 3 elemű tömb, melynek elemei int-ek. 3* sizeof (int)-et foglal el a memóriában (jellemzően 3*4 bájtot). A t szimbólum ugyanakkor az első elem címét is jelöli, így t felfogható egy konstans int* pointernek is. Vigyázat, ugyan t az első elem címét jelöli ugyan (t==&t[0]), de külön hely ennek a pointernek nem foglalódik, csak jelölésről van szó. Így t-t nem is lehet megváltoztatni (pl a t++ hibás), hiszen ha a pointernek nincs külön memóriarekesz, akkor mit is növelnénk? Mindezek jól megérthetők, ha a memóriabeli képét t-nek lerajzoljuk: t az első elem címét jelenti, de ennek a címnek nincs külön memória rekesz, csak az elemeknek foglalódik hely int int
int címek t[0] v. *t t[1] v. *(t+1) t[2] v. *(t+2) Az ábrán az is jól látható, hogy a t+i a t címről nem i bájtnyit, hanem i elemnyit „lép” a memóriában. Vissza c, int* p; int t[3]; p=t; Ez tulajdonképpen az előző példa, csak a p pointert beállítottuk a tömb címére. p int int int t ennek a címe Ezek után a következőket nézzük: *p=1; *(p+2)=2; p++; 3. A p++ ennek a rekesznek a tartalmát egy típusnyival (int* esetén sizeof(int)el) növeli, vagyis p ezután ide mutat. p int int int 1. A *p ennek a rekesznek a tartalmát jelenti, így a *p=1 ebbe a rekeszbe 1-et ír. Vissza d, int* p[5]; p egy olyan 5 elemű tömb, melynek elemei pointerek integerekre. Példa a használatra: int* p[5]; p[0]=(int*)malloc(sizeof(int)); p[1]=(int*)malloc(3sizeof(int)); *p[0]=1; *p[1]=2; p[1][1]=3; *((p+1)+2)=3; free(p[0]); free(p[1]); 2. A *(p+2) ill. a p[2] ennek a rekesznek a tartalmát jelenti, így a *(p+2)=2 ebbe a rekeszbe 2-t ír. p ezt a
tömböt jelenti (első elem címe) int* int* int* int* int* p[0] v. *p az p[1] v. *(p+1) az ebben a ebben a rekeszben levő rekeszben levő pointert jelenti pointert jelenti int int int int A p[1][2] v. *(p[1]+2) v. *((p+1)+2) ezt a reke A malloc-al lefoglaltuk és a p[0] pointert beállítjuk ennek a rekesznek a címére. A *p[0]=1 ide 1-et ír. A p[1][1] v. *(p[1]+1) v. *((p+1)+1) ezt a rekeszt jelenti. A *p[1] v. p[1][0] v *((p+1)) ezt a rekeszt jelenti, pl. a *p[1]=2 ide 2-t ír. A malloc-al lefoglaltuk és a p[1] pointert beállítjuk ennek a rekesznek a címére. Vissza e, int (*p)[2]; p egy pointer egy 2 elemű tömbre, melynek elemei int-ek. p növelése 2 int-nyit ugrik a memóriában. Ugyanis ehhez megnézi, hogy p milyen típusra mutat, és a típus méretével növel Itt a típus mérete egy 2-es int tömb, aminek mérete 2*4 bájt. Példa a használatra lokálisan (stack-en) vagy globálisan (adatszegmensben) foglalt memóriával: int t[3][2]; int (*p)[2];
helyre mutat p = t; állítjuk *t[0]=1; t[0][0]=2; t[1][1]=3; *(t[1]+1)=4; *((t+1)+1)=5; *p[0]=10; p[0][0]=11; p[1][1]=12; *(p[1]+1)=13; *((p+1)+1)=14; // ez lefoglal 3*2 int-nyi helyet // egy pointernek foglal helyet, ami nem definiált // p kompatíbilis t-vel, a pointert a tömb elejére p ennek a rekesznek a tartalmát jelenti t ezt a tömböt jelenti (első elem címe) int int t[0] v. *t ez a tömb (ebben az első elem címe) int int t[1] v. *(t+1) ez a tömb (ebben az első elem címe) A *t[0] v. t[0][0] v *t ezt a rekeszt jelenti, pl. a *t[0]=2 ide 2-t ír. int int t[2] v. *(t+2) ez a tömb (ebben az első elem címe) t helyébe mindenhol lehet p-t is írni, ugyanazt jelenti A t[1][1] v. *(t[1]+1) v. *((t+1)+1) ezt a rekeszt jelenti. Az példa a használatra dinamikus memória használattal (az előző példához képest csak az a különbség, hogy a hely lefoglalása dinamikusan történik és nem a tömb által, így az ábrán a t-helyébe mindenhol p-t kell
írni): tudni int (*p)[2]; p = (int(*)[2])malloc(23sizeof(int)); // ez nem volt előadáson, nem kell *t[0]=1; t[0][0]=2; t[1][1]=3; *(t[1]+1)=4; *((t+1)+1)=5; *p[0]=10; p[0][0]=11; p[1][1]=12; *(p[1]+1)=13; *((p+1)+1)=14; free(p); Vissza 10. Feladat a, void (*(p)[10])(int) - p egy pointer egy olyan 10 elemű tömbre, melynek elemei pointerek olyan függvényekre, melyek egy int* paramétert várnak és void-dal térnek vissza. A definíció során egy pointernek (4 bájt általában) foglalódik hely. b, void (*t[10])(int) - t egy 10 elemű tömb, melynek elemei olyan függvényekre mutató pointerek, melyek egy int* paramétert várnak és void-dal térnek vissza. A definíció során 10 pointernek (10*4 bájt általában) foglalódik hely. c, double (*(p)(int))(char) - p egy pointer egy olyan függvényre, amelyik egy int paramétert vár és visszatér egy pointerrel egy olyan függvényre, amelyik char paramétert vár és double-lal tér vissza. Vissza 11. Feladat
struct konyv { char cim[100]; char szerzo[50]; struct konyv* kov; }; struct konyv* first konyv = NULL; void uj konyv( char* cim, char szerzo ) { struct konyv* ujk = (struct konyv)malloc(sizeof(struct konyv)); strcpy(ujk->cim, cim); strcpy(ujk->szerzo, szerzo); // beszuras a lista elejere ujk->kov = first konyv; first konyv = ujk; } Vissza A segédletet készítette: Benedek Zoltán, BME Automatizálási és Alkalmazott Informatikai Tanszék 2003 A feladatokkal kapcsolatos észrevételekkel, bugokkal Benedek Zoltánt keressétek (benedek.zoltan@autbmehu)