Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
A C++ PROGRAMOZÁSI NYELV ISMERETÉNEK SZÁMONKÉRÉSE GAUGING THE KNOWLEDGE OF THE C++ PROGRAMING LANGUAGE
Pataki Norbert, Porkoláb Zoltán, Szűgyi Zalán Eötvös Loránd Tudományegyetem Informatikai Kar, Programozási Nyelvek és Fordítóprogramok Tanszék
Összefoglaló A C++ egy multiparadigmás programozási nyelv: támogatja az objektum-orientált, generikus, strukturális megközelítést. Rengeteg kihívást jelent mind a programozó hallgatóknak, mind az oktatóknak oktatástechnikai szempontokból ez a tulajdonsága. Külön kihívást jelent megfelelő számonkéréseket kitalálni. Nehéz elválasztani a feladatok kapcsán az algoritmikus gondolkodást és a nyelvi ismereteket. Léteznek alapvető nyelvi fogalmak, melyeket a programozó hallgatónak tudniuk kell a C++ programozási nyelvből (pl. paraméterátadás, stb.). Fontos, hogy a feladat szintén multiparadigmás legyen, azaz azaz ne legyen elég egyetlen paradigma ismerete. A különböző nyelvi konstrukciókat súlyozni kell a bonyolultságuk alapján. Jelen előadásban az alábbi kérdésekre adunk választ az ELTE C++ oktatása alapján: hogyan kérhetjük számon egyszerre az objektum-orientált, a generikus, a procedurális paradigmákat úgy, hogy közben a nyelvi konstrukciókra helyezzük a hangsúlyt az algoritmikus gondolkodás helyett. Hogyan fejezhetjük ki az ötfokozatú értékelést a C++ programozási nyelv tükrében? Milyen típusú feladatok lehetnek érdekesek a C++ szabványos sablon könyvtára (STL – Standard Template Library) mellett? Mely nyelvi konstrukciók könnyebbek és nehezebbek a C++-ban? Példát mutatunk olyan korábbi zárthelyi példára, melyeket ezeken az elveken dolgoztunk ki.
Kulcsszavak C++, vizsga, osztályozás
Abstract C++ is a multiparadigm programming language that supports structural, object-oriented and generic approach. This features makes learn and teach C++ much more harder . However, it is difficult to figure out exercises for the termal examinations since not easy to separate the algorithmic cogitation from the knowledge of the programming language. There are some basic elements that programmer students have to know (e.g. parameter passing). This results in that we have to distinguish the different linguistic constructs on the basis of its complexity and importance. The exercise should be multiparadigm too. Many questions are arisen in connection with the exercises of termal examinations. How can we gauge more paradigms at the same time? How can we gauage students’ C++ knowledge when we do not lay stress on the algorithmic cogitation? What kind of exercises may be interesting by the Standard Template Library? Which C++ constructs are considered to be more difficult and which ones considered to be easier? What are the most important ones? In this paper we give answers to the previous questions, we describe our methodology to assessment of students’ C++ knowledge in a semiautomatic grading way. We also present exercise examples that worked out according to our methodology.
Keywords C++, examination, grading
1
Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
1. Bevezetés A szoftvertechnológiában egy paradigma jelenti az irányvonalat, amely mentén az absztrakciókat megalkotjuk. Egy paradigma egy alapelv, amely meghatározza, hogy egy problémát hogyan szedhetünk kezelhetőbb részekre. A paradigma irányít minket, hogy azonosítsuk azokat a pontokat, amely mentén a problémát felbonthatjuk. A paradigmák szabályokat határoznak meg, de eszközöket is adnak a kezünkbe a fejlesztéshez. A C++ programozási nyelvet sokszor objektum-orientált nyelvként kezelik, pedig valójában egy igazi multipardigmás nyelv. A procedurális programozási paradigma a C örökségből származik jobb paraméterátadási stratégiákkal és a túlterhelés lehetőségével. Osztályokat kifinomult módon hozhatunk létre, a C++ például az öröklődés három különböző árnyalatát képes megkülönböztetni. A C++ támogatja sablonok írását is, mellyel egészen egyedülálló módon jelent meg a nyelvben a generikus és a generatív programozási paradigma. A C++ Standard Template Library (STL) volt a legelső könyvtár, amelyet a generikus paradigma alapján készítettek, de használata gyakran a funkcionális paradigmára emlékeztet. A C++-t nem könnyű tanulni és tanítani sem. A C alapokat muszáj megismerniük a hallgatóknak, mert a hiánya nagyon veszélyes lehet. Egyszerre kell megismerkedni több paradigmával és a szabványos könyvtárral. A hallgatók tudását felmérni sem egyszerűbb, mert egyszerre több aspektusból kell vizsgálni. Sok alapvető konstrukció található a nyelvben. A hallgatóknak ismerniük kell ezeket a fogalmakat: mi a függvény, az osztály, a sablon. A hallgatóknak ezeket a konstrukciókat kifinomultan kell használnia. Nagyon fontos, hogy jól kezeljék a másolásokat, a paraméterátadásokat, konstruktorokat, konstansokat, stb. Egy programozási feladat kapcsán általában nehéz szétválasztani az algoritmikus gondolkodást és a nyelvi eszközök használatát. Fontos szempont, hogy a C++ tudás osztályozása ne függjön az algoritmikus képességektől, ezért a feladatokat úgy érdemes kidolgozni, hogy a megoldása csak minimális szinten függjön az algoritmikus képességektől. A C++ programozási nyelvi kurzusok során fontos, hogy a hallgatók megismerjék a szabványos könyvtárat is. A hallgatók a vizsgákon is használhatják a szabványos könyvtárat. Emiatt számos feladatot nem lehet feladni a specifikáció lényeges megváltoztatása nélkül, ilyenek például a listák, vektorok, map-ek. Ezek ebben a formában túl egyszerűek lennének. A szokásos ötfokozatú értékelést használjuk az osztályozáshoz. A kétfokozatúak egyszerűbbek, de nem elég árnyaltak. A C++ nyelvi konstrukcióknak tükröződnie kell az ötfokozatú értékelésben. Ehhez súlyozni kell a nyelv különböző eszközeit a fontosságuk és a bonyolultságuk alapján. Az előadásban bemutatjuk a vizsgáztatási módszerünket, mellyel a hallgatók C++ tudását osztályozzuk. Korábbi példák alapján bemutatjuk a rendszer struktúráját. A cikket az alábbi módon tagoljuk: a következő fejezet ismerteti a vizsgák általános leírását. Azután egy példát elemzünk ki, majd kevésbé részletesen ismertetünk néhány további ötletet is. Végül összefoglaljuk az előadást.
2. A vizsgák általános leírása
2
Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
A hallgatók gépteremben írják a vizsgát. Használhatnak könyveket, jegyzeteket, az internetet, de egyedül kell megoldaniuk a feladatot. A vizsgát körülbelül 3,5 órán keresztül írhatják. Általában a feladat egy sablon osztály implementációja számos tagfüggvénnyel. A hallgatók megkapják a kliens kódot, amely példányosítja a sablont. A kliens tesztesetek sorozataként leírja a sablon specifikációját. Az elégséges osztályzathoz a hallgatóknak minimális funkcionalitást kell implementálniuk. Jobb jegyekért egyre több műveletet kell megvalósítani. Kezdetben az összes funkcionális teszt ki van kommentezve. Amikor a hallgatók implementálják az összes műveletet egy adott jegyért, akkor visszateszik a kódba a kikommentezett tesztek megfelelő részét, hogy megnézzék, helyes-e az implementációjuk. Ezek funkcionális és szemantikus tesztek ugyan nem bizonyítják a kód helyességét, de jó visszajelzést ad a hallgatóknak. A program kiírja a képernyőre a hallgató jegyét, amennyiben lefordul a program. Amikor a hallgatók megkapják a programot, az elégtelent ír ki. A hallgatók nem ugorhatnak át jegyeket. A feladat általános célja egy olyan konténer sablon, ami hasonló az STL-ben lévőhöz. Az osztály reprezentációját a hallgatók maguknak választják meg. A hatékonyság nem elsődleges szempont, de nagyon rosszul megválasztott reprezentáció nem elfogadható. Általában a hallgatóknak írniuk kell egy osztály sablon, melynek megfelelő sablon paraméterei vannak, és egy triviális konstruktort. Támogatni kell adatok tárolását, és kinyerését akár konstans objektumokból is. A másolás műveletei is gyakran szükségesek az elégséges jegyhez. Közepes jegyért általában bővíteni kell az osztályt. A szokásos elvárás ilyenkor például az adat törlése, stb. Jó vagy jeles érdemjegyért gyakran kell iterator vagy const_iterator belső típust írni, amely megfelelően együttműködik az STL algoritmusaival. Jó jegyért const-on túlterhelt operátorok is alkalmas konstrukciók. Időnként polimorfizmus alkalmazása is itt jelenik meg. Jeles jegyért számos konstrukciót lehet számonkérni. Alkalmas lehet egy speciális sablon konstruktor tetszőleges iterátortípusokhoz vagy sablon másoló konstruktor. Néha olyan STLjellegű generikus algoritmusokat kérünk számon, amelyek nincsenek benne a szabványban (például copy_if). Jó elképzelés egy plusz sablon paraméter bevezetése, melynek van alapértelmezett értéke, de ezt a plusz információt a teljes osztály kihasználja. A kliens kód általános sémája a következőképpen néz ki: #include "work.h" #include
// szükséges osztályok és függvények int main() { int yourMark = 1; /* 2 Itt az osztály alapvető műveleteit használjuk: néhány teszteset ... Ha az implementáció megfelel a teszteseteknek akkor yourMark-ot növeljük */ /* 3
3
Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
Még több művelet: újabb tesztesetek ... Néhány sikeres teszteset után yourMark értéke 3 */ /* 4 Bonyolultabb részek következnek tesztesetekként ... Sikeres tesztek után yourMark-ot megnöveljük */ /* 5 Nehéz műveletek a következő blokkban: ... Ha az implementáció átmegy a teszten, akkor yourMark értéke 5 */ std::cout << "Your mark is " << yourMark << std::endl; return 0; }
3. Egy részletes példa Most bemutatunk egy konkrét példát, melyben egy rendezett láncolt lista sablont kellett megvalósítani. Ez rendezetten tárolja az elemeket. A publikus interfésze egészen hasonló az STL list konténeréhez, viszont az nem rendezett. A kliens kód tartalmaz egy Compare-nek nevezett funktor osztályt a felhasználói rendezéshez. #include #include #include #include #include #include #include
"sl.h" <deque> <string>
struct Compare: std::binary_function { bool operator()(int a, int b) const { return a > b; } };
A hallgatóknak az sl.h-nak nevezett file-lal kell dolgozniuk. Tudniuk kell, hogy a sablonok nem alkotnak fordítási egységet. A következő résznek működnie kell, hogy sikeres legyen a vizsga. Ha ez a rész nem működik, akkor a vizsga sikertelen. /* 2 SortedList li; SortedList<double> ls; ls.insert(5.6); ls.insert(3.2); li.insert(7); li.insert(2); li.insert(5); const SortedList cli = li; if (3 == cli.size()) yourMark = cli.front();
4
Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
*/
A default konstruktornak meghívhatónak kell lennie. Szükséges az elemek beszúrása és az, hogy az osztály valódi sablon legyen: az insert-nek olyan típusú paramétere legyen, amilyen elmeket a rendezett lista tárol. Másoló konstruktort szintén biztosítani kell. Ez nem probléma, ha valaki a szabványos listával valósítja meg ezt az osztályt. Még két metódust kell megvalósítani: a size a lista elemszámát kérdezi le, a front pedig a lista legelső elemét. Ennek a két függvénynek konstansokon is működniük kell, ezért ezeknek const tagfüggvényeknek kell lenniük a C++ konstans helyességének megfelelően. A legelső elem a legkisebb a listában ezért lesz a yourMark értéke 2. /* 3 li.insert(8); li.remove(5); if (7 == cli.back()) yourMark = cli.size(); */
Közepesért két új metódust kell megírni: a remove törli egy elem összes előfordulását a listából és a back visszaadja a lista utolsó elemét. Const-on túlterhelés nem alkalmazható ebben a feladatban, mert az megsérthetné a rendezettségi megszorítást. /* 4 const int N = std::accumulate(cli.begin(), cli.end(), 0); yourMark += (14 == N); */
Jó jegyért egy iterátor típust kell implementálni. Az std::accumulate STL algoritmust hívjuk a SortedList iterátorával. Emiatt az iterátornak helyesen kell működnie, mert az algoritmust nem lehet módosítani. Ha az std::accumulate 14-et ad vissza a változó 4 lesz. Nem egyszerű implementálni egy iterátor osztályt, mert sok operátort és typedef-et kell írni, de az std::list iterátorai használhatóak kézzel írt típusok helyett. /* 5 std::deque d; d.push_back(2); d.push_back(1); d.push_back(3); const SortedList lc1(d.begin(), d.end()); std::vector v; v.push_back(3); v.push_back(7); const SortedList lc2(v.begin(), v.end()); if (7 == lc2.front()) yourMark = lc1.front() + lc2.size(); */
Két speciális bővítés szükséges a jelesért. Tetszőleges rendezést lehessen megadni sablon paraméterként egy funktor típus képében. Itt egy új sablon paramétert kell bevezetni, aminek van alapértelmezett értéke is. Az std::less az a szabványos funktor, amely definiálja a megszokott rendezést funktor formában. Ezt nem bonyolult megvalósítani, de megtalálható a szabványos könyvtárban is. Ezt kell megadni default paraméternek. A másik elvárás, hogy egy speciális konstruktor sablont kell még megírni tetszőleges iterátor típushoz. Ebben a példában ezt std::vector::iterator-ral és std::deque::iterator-ral használjuk ezt a konstruktort. Az összes szabványos konténer biztosít ilyen konstruktort. A túlterhelés nem megengedett ebben a helyzetben.
5
Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
A tesztek a fenti kódrészletekben nem biztosítják az implementáció helyességét, de sok problémát lehet felderíteni ezúton. Ez a példa nem túl bonyolult algoritmikus szempontból, de egyre nehezebb C++-os konstrukciókat tartalmaz. 4. További példák A vizsgafeladatainkat az előző példában is látható vizsgarendszer alapján készítjük el. Egy osztály sablon implementációját várjuk el egyre nehezedő részfeladatok képében és számontartjuk a hallgató jegyét egy változóban. Ennek a változónak az értéke függ a feladat helyes megoldásától. Sok ötletet lehet találni az egyik legjobb STL-ről szóló könyvben (Meyers, 2001). Szokás, hogy a feladatok alapötlete az STL hiányosságain alapul. Pointerek konténereit nem támogatja a könyvtár és sok számos problémát vet fel (például memória-szivárgás, illetve másolással kapcsolatos gondok). Az STL egy másik hiányossága az, hogy a multimap konténer nem definiálja az azonos kulcshoz tartozó elemek relatív sorrendjét. Egy érdekes feladat lehet egy olyan multimap jellegű konténer, amely definiálja ezt a sorrendet is. A szabványos STL nem tartalmaz hasító konténereket. Hasítótáblák, hashmap-ek szintén ideális feladatoknak számíthatnak egy géptermi vizsgán. Gráfok sem találhatóak az STL-ben. A gráfokat számos különböző formátumban lehet számonkérni, pl. irányított, irányítatlan, vagy választható módon. Opcionálisan súlyozást lehet rendelni az élekhez, stb. 5. Összefoglalás A C++ egy olyan multiparadigmás programozási nyelv, melyet nehéz tanulni és nehéz tanítani. Függvények, osztályok és generatív konstrukciók ortogonálisan használhatóak. Vizsgafeladatokat sem könnyű kitalálni. Az ötfokozatú skálára bontjuk fel a C++ elemi konstrukcióit, és egy olyan feladatba ágyazzuk be, amely szintén multiparadigmás. A vizsgafeladatokban kihasználjuk az STL egyes hiányosságait, és egy félautomatikus osztályozást definiálunk a feladattal. Irodalomjegyzék [1]
Austern, M. H. (1999) Generic Programming and the STL, Addison-Wesley.
[2]
Cardelli L., Wegnep P. (1985) On Understanding Types, Data Structures, and Polymorphism. ACM Computing Surveys, 17(4), 471-522.
[3]
Cifuentes, C., Brannan, B. (1996) Teaching C/C++ to Computer Science Students with Pascal Programming Experience. The 1st Australasian conference on Computer science education, 189-196
[4]
Coplien, J. O.(1998) Multi-Paradigm Design for C++. Addison-Wesley.
[5]
Helmick, M. T.(2007) Interface-based Programming Assignments and Automatic Grading of Java Programs. 12th annual SIGCSE conference on Innovation and technology in computer science education, 63–67.
6
Informatika a felsőoktatásban 2008
Debrecen, 2008. augusztus 27-29.
[6]
Hext, J. B., Winings, J. W.(1969) An automatic grading scheme for simple programming exercises. Commun. ACM, 12(5),272-275.
[7]
Hitz, M., Kögeler, S.(1997) Teaching C++ on the WWW. 2nd conference on Integrating technology into computer science education, 11–13.
[8]
Horwitz, S. (1999) Addison-Wesleys Review for the Computer Science AP Exam in C++. Addison-Wesley.
[9]
Karlsson, B. (2005) Beyond the C++ Standard Library: An Introduction to Boost. Addison-Wesley Professional.
[10] Meyers, S. (2001) Effective STL, 3rd Edition. [11] Placer, J. (1993) The Promise of Multiparadigm Languages as Pedagogical Tools. ACM conference on Computer Science. 81–86. [12] Porkoláb, Z., Zsók, V. (2006) Teaching Multiparadigm Programming Based on ObjectOriented Experiences. Tenth Workshop on Pedagogies and Tools for the Teaching and Learning of Object Oriented Concepts (TLOOC). [13] Saikonnen, R., Malmi, L., Korhonen (2001) A. Fully Automatic Assessment of Programming Exercises, 6th annual conference on Innovation and technology in computer science education, 133–136. [14] Stroustrup, B. (1999) Learning Standard C++ as a New Language, C/C++ Users Journal, 43–54. [15] Stroustrup, B. (2000) The C++ Programming Language, Special Edition. AddisonWesley. [16] Tremblay, G., Labonte, E. (2003) Semi-automatic marking of java programs using junit. International Conference on Education and Information Systems: Technologies and Applications (EISTA 03), 42-47. [17] Westbrook, D. S. (1999) A Multiparadigm Language Approach to Teaching Principles of Programming Languages. 29th ASEE/IEEE Frontiers in Education Conference, 11b3-14. [18] Zave, P. (1989) A Compositional Approach to Multiparadigm Programming. IEEE Software VI(5), 15-25.
7