Programozási Nyelvek (C++) Gyakorlat Gyak 01. Török Márk tmark@caesar.elte.hu D-2.620
Jelmagyarázat Elméleti vizsgán lesz! Interjúkérdés lehet Haladó C++-n kifejtve!
Tartalom Hello Világ! Fordítás, futtatás, debuggolás Interpretált vs bájtkód, fordítás Névtér printf vs cout Fejállományok Streamek
Tartalom – Hello Világ C vs C++ Mi az a belépési pont? Mikor van vége? Különbségek, okai Egyszerűsítés (?) Mi az a belépési pont? Mikor van vége? stdio, stdlib
Hello Világ (C) Példa 1 : #include <stdio.h> /* * Több soros komment */ int main() { printf("Hello World\n"); return 0; // egy soros komment }
Hello Világ (C) Példa 2 : #include <stdio.h> #include <stdlib.h> /* * Több soros komment */ int main() { printf("Hello World\n"); system("pause"); return 0; // egy soros komment }
Hello Világ (C) – Fordítás $ gcc main.c $ ls main.c, a.out (ez futtatható) $ ./a.out Hello Világ! $ gcc main.c -o main $ ls main.c, main (ez futtatható) $ ./main Hello Világ! $
Hello Világ (C++) #include <iostream> /* * Komment maradt a régi */ int main() { std::cout << "Hello World\n" << std::endl; }
Hello Világ (C++) Kiiratáshoz: std::ostream::operator<< Hol van az ostream include? include-oltuk az iostream-et. Az iostream-en belüli include-ok: istream, ostream, …
Hello Világ (C++) – Fordítás $ g++ main.cpp $ ls main.cpp, a.out (ez futtatható) $ ./a.out Hello Világ! $ g++ main.cpp -o main $ ls main.cpp, main (ez futtatható) $ ./main Hello Világ! $
Hello Világ – C vs C++ C : procedurális, strukturális C++: objektumorientált paradigmával bővítve (később) stdio.h iostream printf std::cout A változások okai az objektumorientált programozási paradigmák követése, új könyvtárak, állományok implementálása Belépési pont: int main() vagy int main(int argc, char* argv[]) vagy int main(int argc, char** argv)
Hello Világ – C vs C++ ISO C++ standard 3.6.1 (mind a kettő helyes) void main(/*…*/) { … } Error: main must return int main(/*…*/) { … } Fordul, szép, jó és fut! Kilépési pont, visszatérési érték típusa mindig int. Visszatérés legyen 0! Minden más érték a hibáké! C++ esetében: Nem kötelező megadni visszatérési értéket main() esetében ez mindig 0 lesz! Más esetén egy memóriaszemét.
Hello Világ – stdio.h C Standard Input and Output Library C-ben stdio.h C++ cstdio Az input-output műveletekért felelős macrok, típusok, függvények, konstansok Billentyűzet, nyomtató, egyéb perifériaeszközök stream: lehetőséget biztosít ezek írására, olvasására. stdin, stdout, stderr (később)
Hello Világ – stdlib.h C Standard General Utilities Library Memóriahasználathoz szükséges metódusok calloc, malloc, realloc, free Véletlenszám generálás Környezettel kommunikáló metódusok: exit, system, … Konverzió Keresés, rendezés ...
Fordítás, futtatás, debuggolás Ahogy már láttuk: $ g++ main.cpp -o main $ ./main Parancssori paraméterekkel $ ./main param1 param2 argv[0] maga a futtatható állomány; argv[1], ... a tényleges paraméterek Hibákat irassuk ki: $ g++ -Wall main.cpp -o main Error, warning Hiba kiírása: fájlnév:sor száma:hibaüzenet
Fordítás, futtatás, debuggolás printf – format specifiers int a = 9; int b = 10; int c = 100; printf("%d|%d|%d\n", a, b, c); // 9|10|100 printf("%3d|%3d|%2d\n", a, b, c); // 9| 10|100 printf("%03d|%03d|%02d\n", a, b, c); // 009|010|100 %i or %d int %c char %f float %lf double %s string
Fordítás, futtatás, debuggolás Nézzünk egy példát: #include <iostream> bool f() { } int main() { std::cout << f(); }
Fordítás, futtatás, debuggolás $ g++ main.cpp -Wall -o main main.cpp: In function ‘bool f()’: main.cpp:4: warning: control reaches end of non-void function
Fordítás, futtatás, debuggolás Nézzünk egy példát: #include <cstdio> int main() { printf("%f", 99); } $ g++ main.cpp -Wall -o main main.cpp: In function ‘int main()’: main.cpp:8: warning: format ‘%f’ expects type ‘double’, but argument 2 has type ‘int’
Fordítás, futtatás, debuggolás Több állomány fordítása: void sayhello (const char* name); // hello.h #include <stdio.h> // hello.c #include "hello.h” void sayhello(const char* name) { printf ("hello, %s", name); } #include "hello.h” // main.c int main() { sayhello ("world"); return 0; }
Fordítás, futtatás, debuggolás $ gcc -Wall main.c hello.c -o hmain Header: Két féle include-olást különböztetünk meg. #include <fájl.h>: system header fájlok között nézi meg. (linuxon: /usr/include/stdio.h) #include ”fájl.h”: a lokális mappában keres, majd a system headerek között.
Fordítás, futtatás, debuggolás Fordítás lépései: Forrásból object: először nem is futtatható fájl keletkezik, hanem egy object. Ennek kiterjesztése .o. A második lépés a linkelés: a linker az objectfájlok összefésülését végzi függőségek szerint. Ebből lesz a futtatható állomány. Csak fordítás -c kapcsolóval: $ gcc -Wall -c main.c Eredmény egy main.o, mely a főprogramunk gépi kódját tartalmazza. $ gcc main.o -o main
Fordítás, futtatás, debuggolás Külső könyvtárakból: Archive állományok, kiterjesztése: .a // statikus könyvtárak Matematikai függvények Math.h-ban, implementációja viszont a libm.a könyvtárban (fordított állomány!). $ gcc -Wall calc.c /usr/lib/libm.a -o calc Kapcsoló: elkerülhető a hosszú elérésiút: $ gcc -Wall calc.c -lm -o calc Ez annyit jelent, hogy lm = libm.a Elmondható, hogy lNAME = libNAME
Fordítás, futtatás, debuggolás Library-k: Static library és shared library (dynamic) Static library kiterjesztése: .a A linkelést követően a használt függvény gépi kódja a library-ból bemásolódik a futtatható állományba. Shared library kiterjesztése: .so Dinamikus kötés (dynamic linking): a shared library táblázatot tartalmaz hivatkozással az egyes függvényekre. Fordításkor a linker egy ilyen hivatkozást rak be a futtatható állományba, a teljes gépi kód helyett. A futtatáskor a gépi kód bemásolódik a memóriába a megadott hivatkozás alapján.
Fordítás, futtatás, debuggolás -Wall kapcsoló: -Wreturn-type: figyelmeztet, hogy az adott függvény definíció szerint kér visszatérési értéket (azaz nem void), de ezt az implementációjában nem tettük meg. -Wformat: hibás formatstring a printf, scanf függvényekben. Eltér a format a paraméter típusától. -Wunused: figyelmeztet, ha használatlan változók vannak a kódban. -Wimplicite: ha előzőleg nem adtuk meg a függvény specifikációját.
Fordítás, futtatás, debuggolás Preprocesszor: A fordító által meghívott, a tényleges fordítás előtt lefutó program. Mit csinál? Kezeli az alábbi direktívákra: #include : forrásmegjelölés #define: macro definiálása #if: feltételes forrásbetöltés, macrodefiniálás
Fordítás, futtatás, debuggolás Macro-k: C-ben fontosabb, C++-ban kevésbé fontos szerepet töltenek be. Ha tehetjük, akkor kerüljük őket. (Nagyon erősen ajánlott!) Mivel a fordítóprogram futása előtt a macro-k meghívásra kerülnek, és belenyúlnak a kódba, nem ajánlatos használni őket. Csökken a hatékonysága azon eszközöknek, melyekkel a programunk hatékonyságát, biztonságát tudjuk mérni. Pl.: hibakeresők, kereszthivatkozás-vizsgálók.
Fordítás, futtatás, debuggolás Macro-k: #define CSERELD_EZT erre csere = CSERELD_EZT A macro lefutását követően az eredmény ez lesz: csere = erre #define SQUARE(a) a*a Ebből: int b = 0; int i = SQUARE(b + 2); Igen ám! De ebből: b + 2*b + 2 => 3b + 2 lesz!
Fordítás, futtatás, debuggolás Macro-k: Feltétel: … #ifdef AAA printf(”ez az ág lefutott!”); #endif … Fordítás: $ gcc -Wall -DAAA main.c A -D kapcsoló prefixe az adott AAA nevű macro-nak. Így tudunk a macro-nak értéket is adni. Nyilván ha ezt a kapcsolót kihagyjuk, az adott ág lefutása is elmarad.
Fordítás, futtatás, debuggolás Debuggoláshoz: Fordítsunk a -g kapcsolóval. Hogy miért? Amikor a programunk abnormális futást produkál (elszáll menetközben), az operációs rendszer elmenti a program memóriabeli állapotát egy core nevű fájlba. Nézzük meg, hogy mi van a core fájlba.
Fordítás, futtatás, debuggolás Töltsük be a core fájlt a GNU Debuggerbe az alábbi módon: $ gdb futtatható-állomány core-állomány Csak együtt tudjuk őket használni, külön nem tudjuk betölteni a core-állományt. $ gdb a.out core Core was generated by ‘./a.out’. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x080483ed in a (p=0x0) at null.c:13 13 int y = *p; (gdb)
Fordítás, futtatás, debuggolás Stack backtrace kilistázása: (gdb) backtrace Kilistázza a hívási listát.
Interpretált vs bájtkód, fordítás C++-forráskód lefordításával hagyományos natív kódot kapunk. A natív kód a processzornak megfelelő módon lefordított gépi kód. Ha natív C++-alkalmazásunkat több különböző környezetben (32bites, 64bites platformon) is szeretnénk futtatni, akkor külön le kell fordítanunk.
Interpretált vs bájtkód, fordítás Hogy megy ez máshol? C# kód fordításával (interpretálásával) kezelt kódot kapunk. Ez egy közbenső nyelv (intermediate language, IL). A magas szintű (C#) és a legalacsonyabb szintű nyelv (assem., gépi kód) között helyezkedik el. A közös nyelvű futási idejű környezet (Common Language Runtime, CLR) futási időben, menet közben fordítja le a kódot a Just-in-time (JIT) fordító alkalmazásával.
Interpretált vs bájtkód, fordítás Mit csinál a JIT fordító? Nem túl hatékony a kódot futásidőben fordítani? A JIT-fordító nem fordít le egy adott metódust vagy függvényt annak minden egyes meghívásakor, ezt csak az első esetben teszi meg, és ekkor a platformnak megfelelő gépi kódot állít elő. Használatával csökken az alkalmazás munkahalmaza, a közbenső kód munkaigénye kisebb lesz.
Interpretált vs bájtkód, fordítás Hogy megy ez Java-ban? A forrásfájlokban (.java) definiált minden egyes típus interpretálásának eredményeként külön létrejövő típusnév.class fájl tartalmazza a végrehajtható bináris bájtkódot. A hordozható bájtkódot interpretálás helyett, közvetlenül futtatás előtt platformfüggő gépi kódra fordítja át, mely kód sokkal gyorsabban fut majd, mint a bájtkód interpretálása. Előnye, a biztonság és a hordozhatóság, hátránya, hogy néhol lassú.
Interpretált vs bájtkód, fordítás Jittelés Java-ban: A gyorsabb futás érdekében dinamikus fordítási technika használata: JIT (Just-in-time compiler). Minden osztály bájtkódjának betöltése után az egész osztály bájtkódját lefordítja gépi kódra. (A további használatban a kód már nem interpretált.) Előny: többszörösen is gyorsabb lehet a Java kód végrehajtása, viszont egy osztály bájtkódjának (vagy egy metódus végrehajtása) betöltési ideje növekszik.
Fejállományok Azaz a header-ök! Lehetővé teszik a kód elkülönítését. Tartalma: forward declaration (forward reference) Osztályok: class Clazz; Alprogramok: void get(Clazz &clazz); Váltózókat: hivatkozhatsz egy változóra, mielőtt definiálnád. class Clazz { public: int get() { return value; } private: int value; } Egyéb azonosítók.
Fejállományok Definiálása: Használata: Programok kisebb részekre bontása Osztályokra Alprogramokra Definiálása: #define … Használata: #include <fajl.h> #include ”fajl.h”
Fejállományok A többszörös include-ok: Ezek megelőzésére: Több fejállományt include-oltunk, melyek között van olyan, mely include-ol egy másikat. Így akár egy fejállomány kétszer vagy többször is include-olásra kerülhet. A fordítás tovább tart, mert lesznek állományok, melyek többször kerülnek feldolgozásra. Hatalmasra nő a szemantikus gráfunk. Ezek megelőzésére: include guardok
Fejállományok Include guard: Más: #ifndef, azaz ha még nincs definiálva #define, akkor definiáljuk … jön a kód #endif lezárom az if-et Más: #pragma once
Fejállományok Egyéb kulcsszavak: #ifdef #if #elif #else
Fejállományok Nézzük meg, hogy épül fel: // get.h #include <vector> #ifndef GET_H_GUARD #define GET_H_GUARD void get(int a); vector<int> getAll(int a); #endif
Fejállományok // a masik allomany #include <vector> #include ”get.h” int f() { vector<int> getAll(5); return get(10); }
Névtér Programok tagolásának egy eszköze Mindig valamilyen logikai csoportosítást fejeznek ki. (Egyes deklarációk valamilyen jellemzői alapján összetartoznak.) Külön hatókört alkotnak Másik névtérből származó osztályt a névtér nevének minősítésével tudjuk elérni. namespace kulcsszó. Konvenció szerint : lowelCamelCase Legismertebb, legtöbbet használt (a félév során!): std
Névtér std: Osztályok és függvények gyüjteménye C++ Standard Library Osztályok és függvények gyüjteménye Containers (array, list, map,…) General (algorithm, memory, iterator,…) Streams …
Névtér Más nyelveknél: Java: package Ada: csomag C#: namespace Itt láthatósági módosító is van! C++-ben nincs Ada: csomag Félúton az osztály és a namespace fogalma között. Működhet úgy is, mint egy C++-s namespace. C#: namespace Még fejlettebb láthatóságikör
Névtér Példa névtér definiálására: // a.h namespace NS { class A // functions… } int f(char a) // …
Névtér Fordítás: $ g++ a.h -c -c kapcsoló: Csak fordít, de nem keres main belépési pontot. -c kapcsoló nélkül fordítási hiba!
Névtér Használata: :: operator NS:: az NS névtérben lévő neveket (fgv, osztály, …) akarjuk használni
Névtér Használat: #include ”a.h” … NS::A a; a.f(); NS::f();
Névtér Egymásba ágyazhatóság namespace Outer { //… namespace Inner }
Névtér Lehetőség nevesítés feloldására Ha egy nevet sokat használunk saját névterén kívül, egyszerűsíthetünk. Használjuk a using direktívát. using namespace std;
Névtér using más nyelvekben: Ada: with (kicsit más) Java: import C#: using Ruby: require (kicsit más)
Névtér Aliasing: namespace NameSpaceLongName { //… } namespace nsln = NameSpaceLongName;
Névtér Névtér kiterjesztése: namespace X namespace X { { int a; int a; } int b; namespace X { int a; int b; }
Névtér namespace NS { int a = 10; } void f() { std::cout << a; }
Névtér … NS::f(); // 10 NS::a = 20; NS::f(); // 20
Névtér Veszélyek kiterjesztés esetén (namespace extending) Átláthatatlan kód kialakulása Névterek feldolgozása sorrendben történik! Ha hamarabb használok valamit, mint deklaráltam: Hiba!
Névtér Nem fordul, nem látja az a változót! namespace NS { void f() { std::cout << a; } } int a = 10;
Névtér using használata nem csak névtérre: namespace NS { int x = 5; int y = 10; } // using NS::x; int main() using NS::x; std::cout << x << std::endl; std::cout << NS::y << std::endl;
Névtér overloading: namespace NS { int f(int i) { return i; } } int f(char c) { return c; }
Névtér Deklaráció és definíció szétválasztása Sérült design, körültekintően, csak indokolt esetben! namespace A { namespace B void f(); } void B::f() { /* defined outside of B */ }
Névtér Unnamed namespace Név nélküli névtér Globális statikus változók helyett!
Névtér namespace { int i = 10; } namespace NS int i = 15; int i = 20; void f() { return i; } // return 20; std::cout << i << std::endl; // 10; std::cout << NS::f(); // 20
Névtér Fordul? namespace foo { int f() { return 1; } } namespace using namespace foo; int a() { return f(); }
Névtér Fordul, fut. A using feloldja a nevesítést, így a nevesítetlen névtérben elérhetővé válik nevesítés nélkül is az f függvény. Vigyázz, hibás design, nagy kód esetén hibához, hibás logikához vezethet.
Stream istream, ostream, kimeneti és bemeneti műveletekért felelős ifstream, ofstream : (fstream), fájlműveletek Írás, olvasás Flagek: in, out, app, … istringstream, ostringstream: stringek írása, olvasása, feldolgozása Stream manipulátor iterátorok
Stream Kimenet: #include <iostream> using namespace std; Az iostream minden beépített típusra meghatároz kimenetet. Alapértelmezésben a cout-ra kerülő kimeneti értékek karaktersorozatra alakítódnak át. cout << ”hello vilag!” << 10 << endl;
Stream Bemenet: istream: beépített típusok karaktersorozatként történő ábrázolásával dolgozik. A >> jobb oldalán álló típus határozza meg, hogy bemenet fogadható el, és mi a beolvasó művelet célpontja. int i; cin >> i;
Stream Stringstream Stringek kezeléséhez Elérése: #include <sstream> iostream-ből származtatva Azaz stream műveletek! Valójában: typedef basic_stringstream<char> stringstream; Flagek, statek, műveletek
Stream Stringek: string vs stringstream? Karakterláncok reprezentálására #include <string> Az std namespace-ben : std::string typedef basic_string<char> string; string vs stringstream? A string reprezentál, a stream használja a reprezentációt.
Stream Műveletek: Konkatenáció: + Írás, olvasás a streamről/re: >>, <<
Stream // stringstream::str #include <iostream> // std::cout #include <sstream> // std::stringstream, std::stringbuf #include <string> // std::string int main () { std::stringstream ss; ss.str ("Example string"); std::string s = ss.str(); std::cout << s << '\n'; return 0; }
Stream Többszörös öröklődésnél példa: istream, ostream osztályokból: iostream Diamond!
HF Mi a legrövidebb helyes c++ program? Fordul, fut hiba nélkül Írj egy egyszerű hellovilag programot, ahol minden lehetséges whitespace helyén újsor karakter van.
Megoldás 4.8.1-es fordítóval: main() {} 4.8.1-es fordítóval: #include <iostream> int main () { std::cout << "hello vilag"; return 0; }