Kihagyás

C++ alapok

Első program

Készítsük el első c++ programunkat! Ennek a programnak a forrását az elso.cpp fájlba írjuk bele. Az egyszerű programunk egy Hello world! sort fog kiírni a képernyőre. Ennek a forráskódja legye az alábbi kód:

elso.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>

// Egy soros komment
/*
 * Tobb soros komment
 */
int main() {
  std::cout << "Hello world!" << std::endl;
  return 0;
}

Ahhoz, hogy ezt a programot végre tudjuk majd hajtani elpször le kell fordítanunk.

Fordítás (compile)

Pythonnal ellentétben (ahol közvetlenül lehet a forráskódot végrehajtani), minden c++ programkódot először le kell fordítani (compile) futtatható binárissá.

A forráskód lefordításához használjuk a g++ programot:

# Linux esetén
$ g++ -o elso -Wall elso.cpp

# Windows esetén
$ g++ -o elso.exe -Wall elso.cpp

Mi az a $ jel itt?

A fenit két sor példában szereplő $ jel azt jelzi, hogy ezeket a parancsokat teriminálban kell kiadni, ahol a $ mutatja a promptot és ezt a karaktert külön nem kell begépelni.

A kapcsolókról:

  • -o nev kapcsolóval adhatjuk meg a kimeneti fájl nevét, ahol a nev tetszőleges lehet
  • -Wall kapcsolóval további fordítóprogram figyelmeztetést (warningot) kapcsolhatunk be
    • Ilyen például az, hogy egy boolean értéket egy nem boolean-nek megfelelő egésszel hasonlítunk össze
    • vagy mondjuk a kiírt, de üresen hagyott if/else blokkokról is kaphatunk warningot.
  • elso.cpp a fordítandó forrás fájl neve

A lefordított, linkelt alkalmazást ezután már futtathatjuk elso.exe vagy ./elso utasítással operációs rendszertől függetlenül. A fordító -o paraméter nélkül a.exe vagy a.out nevű futtatható állományt készít.

Kis kényelem

Hogy ne kelljen mindenhova kiírni az std:: előtagot, a program elejére beírhatjuk a using namespace std; sort. (Ennek a jelentéséről majd később beszélünk.)

1
2
3
4
5
6
7
8
#include <iostream>

using namespace std;

int main() {
  cout << "Hello World!" << endl;
  return 0;
}

Fontos!

Ilyen using namespace ..; elemeket csak .cpp forrás fájlokban használjunk. Header fájlokban (.h, .hpp, .hxx) célszerű kerülni!

A felkommentelt fájl letöltése

Fordítás fázisai

Amikor egy C++ forrásfájlt lefordítunk, valójában több lépésen keresztül jutunk el a kész futtatható programig. Ezek a lépések:

  1. Előfeldolgozás (preprocessing)

  2. Fordítás (compilation)

  3. Összekapcsolás (linking)

Előfeldolgozás (Preprocessing)

A C++ programokban gyakran vannak olyan elemek melyek mögöttes tartalmat hordoznak. Ezek olyan részek amik valamilyen rövidítést vagy fealdatot látnak el. Ezek nem C++ utasítások, így a fordítóprogram nem tudná értelmezni azokat, de mégis, C++ forráskód lesz belőlük. Ezt az átalakítást látja el az Előfeldolgozó vagy Preprocessor.

Mik helyettesíthetők be? Minden olyan sor ami a # jellel kezdődik, a preprocesszornak szól.

#include

Ahogy azt már a fenti kódrészletekben is láttuk, vannak olyan sorok, melyek #include ként kezdődnek. Az include során olyan elemeket adunk a forráskódhoz, melyeket valakik előre elkészítettek vagy mi készítettünk, de egy másik fájlban van. Mintha mások C++-ban elkészített munkáját a mi kódunkba illesztenénk annak érdekében, hogy azt felhasználjuk. Ez elképzelhető úgy, mintha valaki kimásolná azt a kódot és a saját forráskódjába beillesztené.

Ezt meg is tudjuk nézni, hogy valóban így történik-e. A fordítás során a -E kapcsoló segítségével a fordító nem binárist (futtatható) fog gyártani, hanem csakis a preprocesszált eredményt fogja nekünk megmutatni.

-E kapcsoló használata
$ g++ -E -o elso.preproc elso.cpp

Ekkor azt láthatjuk, hogy rengeteg minden került a rövidke programunk elé. Ez mind az, amit átmásolt a preprocesszor.

Ekkor kérdés lehet, hogy hol van ez az információ letárolva, milyen fájlt másol oda és az a fájl merre található. Az, hogy az include során hol kell keresni a fájlokat több dologtól függ:

  • #include <...> forma használata esetén, a standard könyvtárak között fogja keresni, amiknek megvan a megfelelő helye az operációs rendszeren belül. Ezt a formát tipikusan a standard könyvtárak illetve egyéb mások által megírt fájlok include-olásához használjuk amik nem a mi projektünk szoros részei.
  • #include "..." forma használataok (pl. #include "szuperfunkciok.h"), akkor a feldolgozott fájlhoz nézve relatív útvonalat használja a preprocesszor. Így ez is érvényes útvonal lehet: #include "../../../fontos/szukseges.h". Ezt a változatot pedig a program saját részét képző header-jeihez alkalmazzák.

A különféle jelek mellett a preprocesszornak azt is meg lehet mondani, hogy hol keresse a fájlokat. Alapból a standard könyvtárakat használja, de a -I kapcsoló segítségével mi magunk is adhatunk meg olyan könyvtárakat amikben keres.

Vegyük példának az alábbi fontos.h és pelda.cpp fájlokat:

/home/user/project/secret/fontos.h:
1
2
3
/*
Fontos C++ elemek
*/
/home/user/peldak/pelda.cpp:
1
2
#include <secret/fontos.h>
int main() {/*KOD*/ }

Ez a program most nem fordulna le, hiszen a standard könyvtárak között nem található secret/fontos.h, de ha megadjuk a -I kapcsoló segítéségvel a /home/user/project mappát, akkor ott már megtalálja.

$ g++ -I /home/user/project -o pelda pelda.cpp

A példában az is látszódik, hogy hiába csak a project mappáig van megadva a könyvtár ahol a fájlokat kell keresni, az include során mappát is megadhatunk. Természetesen erre nem lenne szükség, ha a secret mappa is része lenne a keresési könyvtáraknak.

Mivel ez a bemásolás is csak egy szöveges rész, így ez is tartalmazhat include elemeket, így azokat is fel kell oldani. A probléma akkor van, ha egy fájl olyan fájlt húz be, akár indirekt módon is, ami az eredeti fájlra hivatkozik. Ekkor a behelyettesítés során a preprocesszor egy végtelen körbe kerül. Ennek a megoldása az, hogy számontartjuk, hogy mely fájlok kerültek már be, és ha egy fájl már bemásolásra került, azt nem másoljuk be újra. Erre két megoldás létezik:

  1. #pragma once: Ha ezt belerakjuk a fájl elejére, akkor a preprocesszor tudni fogja, hogy ezt csak egyszer kell bemásolni. A probléma, hogy habár a legtöbb és elterjedt fordító támogatja, nem része a C++ szabványnak.

  2. Include guardok / védőfeltételek használata: Ekkor saját magunk tudunk változókat definiálni és feltételes másolást garantálni. Ehhez azonban kettő dologra van szükségünk: Változókra és feltétel vizsgálatra. Ezek elvezetnek a makrókig, így nézzük is meg azokat majd visszatérünk az include guardokhoz.

#define - Makrók

A makrók is a preprocesszorhoz köthetők, így ezek is behelyettesítést látnak el. A különbség annyi, hogy ezeknek a nevét és értékét mi adhatjuk meg. Ahhoz, hogy létre tudjunk hozni egy makrót, definiálnunk kell a define preprocesszor kulcsszóval.

Makró definálás példa (a makró tartalma üres)
1
#define SAJAT_MAKROM

Látható, hogy # jellel kezdődik, tehát a preprocesszornak szól, illetve, hogy definíció. Innentől kezdve létezik a SAJAT_MAKROM nevű makró. Ez csak annyit jelent, hogy létezik, de érték nincsen mögötte. Ha értéket is akarunk adni neki, azt a következő képpen adhatjuk meg:

Makró definálás példa
1
#define SAJAT_MAKROM std::cout << "Ez a sajat makrom viselkedese";

A makrónak bármilyen értéket adhatunk. Lehet ez a fentebb látott módon egy egész hosszú kifejezés, de lehet egy string #define STRING_MAKRO "Ez a stringem" vagy egy szám is #define SZAM_MAKRO 12542. A makrók azonosítójaira hasonló megkötések van mint változó nevekre, de nagyon sok esetben csupa nagybetűvel írják a makró neveket. Ezzel jobban elkülönülnek normál azonosítótktól a forráskódban.

Vizsgáljuk meg az alábbi példát.

1
2
3
4
5
6
7
8
9
#include <iostream>

#define PRINT_MY_NAME std::cout << "My name is John Doe" << std::endl;
#define MY_FAV_NUM 42

int main() {
  PRINT_MY_NAME
  std::cout << "My fav number is: " << MY_FAV_NUM << std::endl;
}
1
2
3
4
5
6
7
8
9
#include <iostream>




int main() {
  std::cout << "My name is John Doe" << std::endl;
  std::cout << "My fav number is: " << 42 << std::endl;
}

Érdemes megjegyezni, hogy a preprocesszor direktívák kiértékelése/behelyettesítése után azok a sorok ahol voltak ilyen elemek megmaradnak mint üres sorok. Ez látható a fenti példában is.

Ha megfigyeljük a PRINT_MY_NAME használatakor nem kellett ; jelet rakni, hiszen az nem egy C++ utasítás, hanem csak egy behelyettesítendő rész. A behelyettesítendő részben pedig már szerepel a lezáró.

Mivel ezek a makrók a preprocesszor számára ismeretesek, tudja, hogy melyek léteznek és tudja, hogy mire kell helyettesíteni, így ezt kis is használhatjuk arra, hogy a forráskódunk máshogy forduljon attól függően, hogy milyen értékei vannak a makróknak.

#if - Feltételes direktívák

További preprocesszor direktívák között vannak különféle feltételes kifejezések is. Az egyik ilyen segítségével megvizsgálhatjuk, hogy egy makró definiálva van-e: #ifdef. Az #ifdef a #if defined(..) kifejezésnek a tömörebb változata és hasonlóan más feltételes szerkezetekhez lehet #else vagy #elif -et használni (elif az else if-et jelenti itt).

Ennek a segítségével például debug kiíratásokat tudunk a kódba rejteni. Vizsgáljuk meg az alábbi kódrészletet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>

#define DEBUG
#define VERSION 1

int main() {
#ifdef DEBUG
  std::cout << "My debug line" << std::endl;
#else
  std::cout << "My non debug line" << std::endl;
#endif

#if VERSION == 2
  std::cout << "Rossz verzio" << std::endl;
  return 1;
#endif
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>




int main() {

  std::cout << "My debug line" << std::endl;





  std::cout << "Rossz verzio" << std::endl;
  return 1;

}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>




int main() {



  std::cout << "My non debug line" << std::endl;






}

Ezekkel a direktívákkal irányíthatjuk, hogy a makrók függvénéyben mi kerüljön bele a fordítás során a végső programunkba.

Ezzel vissza is térhetünk az include problémájához. Ha include guardokat használunk, akkor szükség van egy változóra, mellyel számontartjuk, hogy egy fájl már bemásolásra került-e vagy sem illetve egy tesztelésre.

Ez teljesen megoldható makrók és direktívák használatával. Ha láttuk már a fájlt, akkor definiálunk egy makrót, és ha a makró definiálva van, akkor megszakítjuk a láncot, azaz nem másolunk semmit. Vegyük az alábbi kedvencek.h példát.

kedvencek.h
1
2
3
4
5
6
#ifndef _KEDVENCEK_H_
#define _KEDVENCEK_H_
/*
Masolando tartalom akar tobb include-al.
*/
#endif
  • Mi történik itt? Ha először include-oljuk a kedvencek.h fájlt, akkor a _KEDVENCEK_H_ makró még nem került definiálásra, így a feltétel #ifndef (ha nem lett még definiálva) igaz lesz, vagyis az igaz ágban lévő részt bemásoljuk.
  • Mit tartalmaz az igaz ág? A _KEDVENCEK_H_ makró definiálását, pontosan azt amit vizsgáltunk, hogy létezik-e már. Valamint a további bármilyen egyéb hasznos program kódsorokat.
  • Hogyan szakad meg a lánc? Mivel a _KEDVENCEK_H_ makró definiálásra került az első esetben amikor a fájlt include-oltuk, a rákövetkező include-ok esetén az első sorban található #ifndef hamis lesz majd. Így az igaz ág tartalmát kihagyja a preprocesszor. Ezzel konkrétan mintha egy üres fájlt include-olt volna a fordítás során a fordító. Az üres szöveg pedig biztosan nem vezet több include-ra.

Preprocesszor direktívák és makrók (folytatás)

Mivel sok direktíva tesztelésre hazsnálható jogos a kérdés, hogy mindig a forráskódban kell-e megadni a vizsgálandó makrók értékét? Megtehetjük, hogy fordításkor adunk makróknak értéket.

Makró értékek forrásfájlon kívül (secret.cpp)
1
2
3
4
5
6
7
#include <iostream>

int main() {
#ifdef SECRET
  std::cout << "My password is: " << MY_PWD << std::endl;
#endif
}

Ez a program fordítás után nem ír ki semmit, hiszen nincsen difinálva a SECRET makró (így az #ifdef hamis lesz). Ezt azonban megváltoztathatjuk, ha másként fordítunk. A -D fordítási kapcsoló segítéségvel megadhatunk makrókat. Ekkor is lehetőség van csak definiálásra és érték megadására is:

1
$ g++ -DSECRET -DMY_PWD=Alma -Wall secret.cpp -o secret

Látható, hogy több makró definiálásakor több -D kapcsolót adunk meg és érték adásához csak egyenlőségjelre van szükség. Fontos, hogy nincsen szóköz!

Ezt a funkcionalitást a ZH tesztelés során is kihasználjuk. Minden programban ahol van kiinduló main függvény, a függvény preprocesszor direktívák között szerepel. Ezek azt tesztelik, hogy a biro makro definiálva van-e. Ha definiálva van, akkor biro környezetben fut és az a tartalom eltávoolításra kerül annak érdekében, hogy biztosan ne legyen zaj a biro tesztelésben.

Példa `BIRO_TEST` használatára
1
2
3
4
5
6
7
8
9
#include <iostream>

#ifndef BIRO_TEST
int main() {

  /*Kiadott kodreszlet. */
  std::cout << "Biro, make my output wrong, please!";
}
#endif

Mivel a biro a -DTEST_BIRO=1 kapcsolóval fordít, így ennek nem lesz hatása a biro kimenetre. A probléma az lehet, ha valaki ilyen részbe írja a megoldását, akkor a biro azt sem fogja látni, hiszen a preprocesszor kidobja.

Paraméteres makrók

Gyakran találkozhatunk olyan makrókkal is, melyek paramétert várnak, mintha csak függvények lennének. Ezek igen hasznosak tudnak lenni, de óvatosan kell kezelni ezeket, hiszen a preprocesszor nem végez kiértékellést, csak behelyettesítést!

1
2
3
4
5
6
7
#include <iostream>

#define SQUARE(x) x * x

int main() {
  std::cout << SQUARE(3) << " " << SQUARE(1 + 2) << std::endl;
}
1
2
3
4
5
6
7
#include <iostream>



int main() {
  std::cout << 3 * 3  << " " << 1 + 2 * 1 + 2 << std::endl;
}

Gondolhatnánk, hogy a makró ugyanazt jelenti mindkét esetben, hiszen 3*3 = 9 és 1+2 = 3 az is 3*3, így 9. Azonban ha szigorúan követjük, hogy a preprocesszor nem értékel ki semmit csak behelyettesít, a második esetben a valós jelentés a következő lesz: 1 + 2 * 1 + 2 ami pedig 5. Természetesen megfelelő zárójelezéssel ez orvosolható #define SQUARE(x) (x) * (x), azonban nagyon jól mutatja, hogy a makrók mint függvények használata során nagyon elővigyázatosan kell eljárnunk!

Fordítás (Compilation)

Ebben a fázisban a preprocesszor már elvégezte a dolgát, nincsen semmilyen direktíva, azok alapján a megfelelő kódrészlet került bemásolásra vagy éppen kivágásra, és a makrók is lecserélődtek a megfelelő tartalmukra. Ekkor a fordító legyártja az object kódot (tipikusan .o vagy .obj fájlok). Ez olyan kód, ami már a gép számára értelmezhető, azonban még mindig nem futtatható. Ez csak egy nagy utasítás halmaz, azonban hogy futtatható programot kapjunk, ahhoz még több másik utasítás is kell.

Ezeket a további utasításokat a fordító a linkelési fázisban köti a programunkhoz. Mi értelme van, ha ezt a fordító úgysi megcsinálja, miért kell object kódot generálni? Sokszor nem akarunk teljes futtatható programot írni, mi csak egy funkcionalitást akarunk elkészíteni, pl. cicás képeket elforgató függvényt írunk. Ha azt akarjuk, hogy mások ezt a függvényt fel tudják használni a saját programjuk során, akkor nem kell egy teljes programot írnunk, csak ezt a pár funkcionalitást kell gép számára értelmezhető utasításokká alakítanunk és majd egy másik programozó csak annyit mond: "Csináld azt ami ott található a leírásban", utalva a mi object kódunkra.

Természetesen, hogy egy programozó tudja, hogy hogyan hívható meg az object kódban lévő függvény, tudnia kell, hogy annak mi a neve, milyen paramétereket vár stb. Erre megoldás ha a függvények (interfészek) definíciója és deklarációja külön van választva. Erről részletesebben itt olvashatsz.

C++ input/output

C++-ban az input/output műveletek streamek (folyamok) segítségével vannak megvalósítva és a sztandard osztálykönyvtár részei (std namespace):

  • cout: az alapértelmezett kimenet
  • cerr: az alapértelmezett hibakimenet
  • cin: az alapértelmezett bemenet

A streamekbe (folyamokba) a << operátor segítségével írhatunk, és a >> operátor segítségével olvashatunk belőlük.

Ezek használatához C++-ban használatos iostream header fájlt kell include-olni. Ezzel az új, stream-eken alapuló műveleteket adjuk hozzá programunkhoz.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include <iostream>

using namespace std;

int main(int argc, char* argv[]) {
  int kor = 0;
  float magassag = 0.0;
  char nev[10];

  cout << "Adja meg a korat (evek szama) es magassagat szokozzel elvalasztva: ";
  cin >> kor >> magassag;

  cout << "Adja meg a nevet: ";
  cin >> nev;

  cout << "Nev: " << nev << " kor: " << kor << " ev, magassag: " << magassag << " cm" << endl;
  return 0;
}

Sorvéget az endl segítségével írhatunk.

A fájl letöltése

C++ string

Mi történik az alábbi programmal akkor, ha a fix méretű karakter tömbbe hosszabb méretű karakterláncot adunk meg?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>

using namespace std;

int main() {
  char nev[10];
  cout << "Adja meg a nevet: ";
  cin >> nev;
  cout << "A nev: " << nev << endl;
}

Az eredmény (ha a program lefut):

1
2
Adja meg a nevet: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Nev: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Azonban ha túl hosszú a bemenet, akkor a program hibával megállhat. A megoldás az, hogy az inputnak megfelelő méretű területet foglalunk, és oda írjuk be a felhasználó által megadott nevet. A C++-ban a "szövegeknek" van egy hatékonyabb, kevésbé körülményes megvalósítása is, a string osztály (osztályokról később lesz szó, most csak azt mutatjuk be, hogyan lehet használni a string-et). A string objektumok dinamikusan változtatják a méretüket a tartalmazott karakterek számától függően. A félév folyamán stringek alatt ezt a reprezentációt értjük, nem pedig a char*-ot. A string objektumok használatához, string műveletekhez szükségünk lesz a string header include-olására is (és nem string.h). Ezt követően kényelmesen használhatjuk a szövegeket, ahogy már Javaban is megszoktuk. Itt csak a fontosabb használati eseteket emeljük ki, bővebb leírásokhoz linkek:

A string osztály fontosabb metódusai, amelyeket a leggyakrabban használunk:

Létrehozás, azaz a konstruktorok

1
2
3
4
string s1;           // ures string
string s2("alma");   // string, amely az "alma" szoveget tartalmazza
string s3 = "korte"; // string, amely az "korte" szoveget tartalmazza
string s4(s2);       // string, amely az "alma" szoveget tartalmazza, mert az s2 "alma"

Beolvasás, kiírás

1
2
3
string str;
cin >> str;
cout << "A beolvasott szoveg: " << str << endl;

A scanf nem használható a string beolvasására, de a cin segítségével be tudjuk olvasni, és a cout segítségével ki tudjuk írni.

Összehasonlítás

1
2
3
4
5
6
7
8
9
string s1, s2;

cin >> s1 >> s2;

if (s1 == s2) {
    cout << "A ket string azonos" << endl;
} else {
    cout << "A ket string nem azonos" << endl;
}

A string-eket az == operátor segítéségével össze lehet hasonlítani.

Összefűzés

1
2
3
4
5
6
7
8
string s1("Hello")
string s2("World")
string s3;

s3 = s1 + " " + s2 + "!";

cout << s3 << endl;
// kimenet: Hello World!

Méret, üres-e, tartalom törlése

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
string s("alma");
// size() es length() ugyanaz, length olvasmanyosabb
cout << "meret: " << s.size() << " " << s.length() << endl; // meret: 4 4

if (s.empty()) {        // van benne karakter -> false
    cout << "s ures" << endl;
}

s.clear();

if (s.empty()) {
    cout << "s ures" << endl; // most kiirja, hogy "s ures"
}

Indexelés / i-edik elem

1
2
3
4
5
string s("alma");
cout << s[2] << endl; // m
s[0] = 'l';
s[1] = 'a';
cout << s << endl;    // lama

Ha olyan indexre hivatkozunk, amelyik érvénytelen, azaz a string hosszánál nagyobb, akkor nem definiált viselkedés lesz az eredménye.

Konverzió szám és string között

Számból string-gé a to_string segítségével konvertálhatunk.

1
2
3
4
int i = 5;
string s1 = to_string(i);
string s2 = to_string(3.14);
cout << s1 << " " << s2 << endl; // 5 3.140000

string-et számmá konvertálni az stoi függvénnyel lehet.

1
2
3
string s1("12345");
int i = stoi(s1);
cout << i << endl; // 12345
Konverziók a különböző típusokra:
Típus Függvény név
int std::stoi
long std::stol
long long std::stoll
unsigned long std::stoul
unsigned long long std::stoull
float std::stof
double std::stod
long double std::stold

Mi történik,

  • ha a szöveg elején van szám, de van mögötte ,,egyéb'', akkor a számot átkonvertálja
1
2
string s("123 alma");
cout << stoi(s) << endl; // 123
  • ha nem fér bele a tartományba, akkor std::out_of_range kivétel dobódik (kivételkezelés, const és referencia később)
1
2
3
4
5
6
try {
  string s1("999999999999999999999999999999");
  cout << stoi(s1) << endl;
} catch (const out_of_range& e_out) {
  cout << "out_of_range exception" << endl;
}
  • ha nem lehet konvertálni, akkor std::invalid_argument kivétel dobódik
1
2
3
4
5
6
try {
  string s1("alma");
  cout << stoi(s1) << endl;
} catch (const invalid_argument& e_inv) {
  cout << "invalid_argument exception" << endl;
}

Hibakeresés

Debugger használata

Az IDE eszközök általában tartalmaznak debuggert is, ami lehetővé teszi, hogy a program végrehajtását utasításról utasításra lépve kövessük végig úgy, hogy közben a változók állapotát, értékét folyamatosan monitorozni tudjuk. Fontos, hogy ahhoz, hogy a debugger megfelelően tudjon működni, a kódot úgy kell fordítani, hogy abba belekerüljenek a debugger számára szükséges információk, illetve a kódot ne optimalizálja a fordító, amellyel esetlegesen kihagy belőle utasításokat, amely miatt egy kicsit nehezebben követhető lehet, hogy a program miért úgy fut, ahogy.

Assertek

C++-ban lehetőség van a assert preprocesszor makró által arra, hogy bizonyos feltételeket ellenőrizzünk a program futtatása során, amelyek ha nem teljesülnek, a program végrehajtása megszakad. Ennek használatához importálnunk kell a cassert headert.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
#include <cassert>

int main() {
  int even_num = 3;

  // assert ami ellenőrzi, hogy az even_num páros-e
  // (ahogy azt a neve alapján elvárjuk tőle)
  assert((even_num % 2) == 0);

  return 0;
}

Kiíratás

Sok esetben persze a legegyszerűbb és leggyorsabb megoldás, ha a programunkban kiirató utasításokat használunk egy-egy változó értékének lekérésére és ellenőrzésére. Annak érdekében, hogy ne vesszünk el esetlegesen a sok kiíratásban, illetve adott esetben ezeket a saját debugolásra használt üzeneteket könnyen ki tudjuk kapcsolni, érdemes a feltételes fordítás lehetőségét alkalmaznunk, illetve a megfelelő makrókkal jelezhetjük azt is, hogy az adott kiiratás a kódunk melyik részén történt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <iostream>
using namespace std;

int main() {
  int even_num = 3;

  #define DEBUG
  #ifdef DEBUG
  cout << "LOG: [" << __FILE__ << ": " << __LINE__ << "]:"
       << " az even_num valtozo erteke: " << even_num << endl;
  #endif

  return 0;
}

A #define DEBUG sor hozzáadásával, vagy a -DDEBUG fordítási kapcsolóval tudjuk elérni, hogy a programba bekerüljün a megfelelő kiirató utasítás futtatása. Ezek elhagyásával a program a kiiratás nélkül fut le.


Utolsó frissítés: 2025-10-01
Létrehozva: 2025-10-01