1
A program lefordítása
forrásprogram: a lefordítandó program o általában fájlok tartalmazzák o a C++ forrásfájlok leggyakoribb kiterjesztései fejléc fájl vagy fejállomány: .h implementáció: .cpp fordítási egység: a nyelvnek az az egysége, ami a fordítóprogram egyszeri lefuttatásával, a program többi részétől elkülönülten lefordítható o így nem kell mindig a teljes programot újrafordítani
Előfordító
egy fordítási egység több fájlból is állhat előfordító (preprocessor): a tényleges fordítóprogram futása előtt szövegesen átalakítja a forráskódot o #include direktíva: fejállomány szöveges beillesztése sztenderd könyvtárak: kiterjesztés nélkül saját fejléc fájlok: elérési úttal, kiterjesztéssel
#include
#include "sajat.h" Előfordító
fejléc fájl állomány-őrszeme (header guard) o ugyanazt a fejállományt több #include is beillesztheti o a többszörös beillesztés problémát okozhat o kivédése: szintén az előfordító felhasználásával
// sajat.h tartalma #ifndef __SAJAT_H_ #define __SAJAT_H_ // sajat.h érdemi tartalma #endif Előfordító
az előfordító számos más feladatra is használható o makrók feldolgozása: szöveges helyettesítések o feltételes fordítás: kódrészletek beillesztése és eltávolítása az előfordító nem értelmezi a forrásszöveget a nyelv eszközei általában biztonságosabb megoldásokat kínálnak o célszerű minél kevesebb feladatra az előfordítót használni
A fordítás menete g++ verem.cpp verem.h -c g++ verem.o foprogram.cpp
⇒ ⇒
verem.o a.out
A fordítás menete Fordítási idő
az az idő, amikor a programkód fordítása folyik
2
ez a fordítóprogram futási ideje kimenete: tárgykód (object code) o a program gépi kódjának részleteiből áll o gépi kód (machine code): a processzor által közvetlenül végrehajtott kód
A fordítás menete Szerkesztési idő
a futtatható állomány előállítása a lefordított kódrészletekből az összes forrásfájl lefordítása utáni fázis eszköz: szerkesztő (linker) o általában a fordítóprogramba bele van építve
Futási idő (runtime): az az idő, amikor a program utasításainak végrehajtása folyik Szerkesztés
statikus szerkesztés (static linking): általában csak a fordítás utolsó szakaszában történik a program összeszerkesztése dinamikus szerkesztés (dynamic linking): néha szükség van további szerkesztésre futási időben o nem feltétlenül futtatható programot állít elő a fordító (Java) o szükség lehet dinamikus kódbetöltésre (dinamikus könyvtárak, plugin-ek)
Szerkesztés
statikus o csak egyszer kell szerkeszteni, nem minden futtatáskor dinamikus o nagy könyvtárak megoszthatók a programok között kisebb méretű futtatott programok o csak az szerkesztődik be, amire szükség van o a végrehajtási idő terhére történik o verziókhoz könnyebben alkalmazkodó program hot swapping vagy hot plugging: a program komponenseinek futás közbeni cseréje o futási hibát okoz, ha nem található vagy rossz verziójú a betöltendő
A g++ fordítóprogram
a g++ program tartalmazza a fordítás minden fázisának programját o alapesetben megpróbálja futtatható állománnyá fordítani a kódot o különböző kapcsolókkal meg lehet állítani egy-egy fázis után: -E -S -c további fontos kapcsolók o optimalizálás: -Ox x minél nagyobb, annál jobban optimalizál o figyelmeztetések: -Wall -Wextra -pedantic legalább az első kettő mindig javasolt man g++
További fejlesztői eszközök
statikus hibaelemző o a programszöveget elemzi o sokfajta hibát képes kiszűrni o pl. cppcheck hibakereső (debugger) o a program futásának követésére o pl. gdb fejlesztői környezet (IDE)
3 o o o o o
syntax highlighting kódkiegészítés program szerkezetének ábrázolása hibakereső pl. Code::Blocks
A program futtatása 1. 2. 3.
a program betöltődik a memóriába o nem biztos, hogy fordításkor ismert, hová töltődik be relatív címek feloldása (relokáció): a programkód betöltési címtől függő részeinek frissítése tényleges programvégrehajtás o virtuális gép: előtte ellenőrzi a betöltött kódot o a main függvény indul el
A program futásának vége
visszatérés a main függvényből return utasítással o a main visszatérési értéke a program visszatérési értéke a rendszer felé a main függvényből tovaterjedő kivétel esetén az exit vagy az abort függvény meghívásával o a cstdlib fejállomány tartalmazza őket o nem minden destruktor fut le, kerülendőek
Szintaxis és szemantika
nyelv szintaxisa: azoknak a szabályoknak az összessége, amelyek a nyelven írható összes lehetséges, formailag helyes programot (jelsorozatot) definiálják o a szintaxis befolyásolja a programok olvashatóságát és megbízhatóságát nyelv szemantikája: a nyelv programjainak jelentését leíró szabályok összessége
A szabályok ellenőrzése
a fordítóprogram végzi fordítási hibák o formai lexikai szintaktikus o statikus szemantikai például típushelyesség futási hibák: dinamikus szemantikai hibák o ezeket a fordítóprogram nem mindig tudja ellenőrizni o pl. tömb túlcímzése, nullával való osztás a professzionális programfejlesztéshez fontos o minél előbb derüljön ki (inkább fordításkor) o szigorú nyelv (pl. erősen típusos)
Lexikai szerkezet Lexikai szerkezet: a programszöveg alapelemeit írja le
ábécé azonosítók számok karakterek, literálok elválasztó jelek operátorok tördelés
4
megjegyzések
Ábécé
csak a latin ábécé és néhány grafikus karakter a karakterkódolás rögzített kis- és nagybetű különbsége mindenhol számít Java, C#: minden Unicode karakter
Azonosítók
latin ábécé betűi, aláhúzás, számjegyek o számjeggyel nem kezdődhet konvenciók o azonosito_neve o Osztaly_neve o makró, konstans: CSUPA_NAGYBETU o aláhúzással kezdődő azonosítókat csak speciális célokra szoktak használni o speciális elnevezési konvenciók "magyar jelölés": a változó neve rövidítve tartalmazza a típusát a legtöbbször nincs rá szükség, vagy kiváltható névterek vagy beágyazott osztályok használatával
Azonosítók
kulcsszó: fenntartott jelentésű név o if, for, while, ... előre definiált szó: gyakran használt, előzetes jelentéssel bíró név o pl. a főprogram neve main o könyvtári elemek neveit sem célszerű felhasználni min, max, cout, endl, ... a fordító mindig a leghosszabb karaktersorozatot ismeri fel névként o abc123 egy azonosító, nem az abc azonosító és az 123 egész szám
Számok Egész számok
tízes: 3241312 tizenhatos: 1ABCDEFh, 0xABCDEF nyolcas: 04512 utótagok o U: unsigned o L: long
Lebegőpontos számok
12., .34, 12.34 utótagok o kitevő: e-3 o F: float o U: unsigned
Karakterek, szövegek Karakterek
5
a karakter kódja aposztrófok között: 'a' speciális karakterek: escape szekvenciák o sorvége: '\n' o tabulátor: '\t' o perjel: '\\' o aposztróf: '\'' o idézőjel: '\"'
Literálok
karakterek idézőjelek között: "abc\n"
Pontosvessző
egységek lezárása o deklarációk o utasítások o osztályok gyakori hiba ennek a pontosvesszőnek a lehagyása
struct S { int f() { return i; } int i; }; Tördelés (indentation)
a tördelésnek nincsen hatása a program jelentésére egyes konstrukciók behúzása megtévesztő lehet
if (i > 0) j = i; // ha ide is kerül egy utasítás, // az a feltételtől függetlenül végrehajtódik
obfuscation: szándékos olvashatatlanná tétel
Megjegyzések vagy kommentek (comment)
több soros: /* ... */ o nem ágyazhatóak egymásba, az első */ lezárja egysoros: // o a sor végéig tart újabb nyelvekben dokumentációs komment speciális szerkezettel
Operátorok int int int int
előre meghatározott szerkezetek o egyes nyelvekben felvehető új operátor (C++ nyelven nem) néha kötelező szóközzel tagolni őket, különben mást jelentenek i = *p/*q; i = *p / *q; f(char*=0); f(char* = 0);
// *=
6 Adattípus szerepe
egy fogalom konkrét ábrázolása o a típusba egyedek vagy objektumok tartoznak megadja a típusba tartozó egyedekre alkalmazható műveleteket
2 / "abc"
a fordítóprogram számára o méret: mekkora tárhelyet foglal o ábrázolás (reprezentáció): az adott bitminta hogyan értelmezendő összetett típus esetén: szerkezet o szemantika: a műveletek jelentése
Nyelv típusozása Nyelvek típusozási lehetőségei
típusozás erőssége: az azonos reprezentációjú értékek szerepe egyértelmű-e minden kifejezésben o erős típusozás (strong typing): minden kifejezéshez hozzárendelhető egy típus, és a nyelv szabályai biztosítják, hogy csak az elvárt műveleteket lehessen rá végrehajtani garancia: futási időben nincsen típushiba o gyenge típusozás (weak typing): a kifejezésekben az értékekre nem biztosítható, milyen művelet hajtódik végre az automatikus konverziók például gyengítik a nyelvet a C++ általában erős típusozású, a gyengeségeit főleg a C öröksége adja
Nyelv típusozása
statikus típusozás (static typing): fordítási idejű típusbiztosítás o típusok kiírása alapján kötelezően megadott típusokkal a C++ ilyen típuskikövetkeztetéssel (type inference) leggyakrabban funkcionális nyelveken C++0x: változók auto minősítővel o biztonság: az érvénytelen konstrukciók fordítási időben kiderülnek dinamikus típusozás (dynamic typing): futási idejű típusellenőrzés o csak az értékeknek vannak típusai, a változóknak nincsenek o nagyobb rugalmasság
Nyelv típusozása Példák nyelvekre statikus C++, Java, Haskell C, PHP
erős gyenge Adattípus Adattípus szerepe
biztonság növelése: több hiba felderítése fordítási időben optimalizációs lehetőség a fordítóprogram számára absztrakció: a programozó megszabadítása az implementációs részletektől dokumentáció
dinamikus Erlang assembly
7 Adattípusok csoportosítása
void elemi típusok: logikailag felbonthatatlanok o skalár típusok: egyetlen mennyiséget ábrázolnak diszkrét típusok felsorolási típusok egész karakter valós lebegőpontos fixpontos (ilyen nincsen a C++-ban) o
8 A bitműveletek igazságtáblái x&y 0 1 x|y 0 1 x^y 0 1
0 0 0
1 0 1
0 0 1
1 1 1
0 0 1
1 1 0
Skalár típusok műveletei Példák a bonyolultabb műveletekre 100 / 100 % 1 << 100 >> 12 & 12 | 12 ^ 1100 1010 & ---1000
3 3 5 3 10 10 10
⇒ ⇒ ⇒ ⇒ ⇒ ⇒ ⇒
⇒ 8
33 1 32 12 8 14 6 1100 1010 | ---1110 ⇒ 14
1100 1010 ^ ---0110 ⇒ 6
Skalár típusok műveletei ++i
prefix növelés/csökkentés (mellékhatásos) // i változó értéke megnő eggyel // a művelet értéke: i + 1
i++
posztfix növelés/csökkentés (mellékhatásos) // i változó értéke megnő eggyel // a művelet értéke: i
előjel: -x
Egész (integer)
típus neve: int előjel-módosító: unsigned o alapértelmezés szerint az int előjeles hossz-módosítók: short, long, long long a módosítók önmagukban leírva is típust jelölnek
int i; unsigned u; unsigned int ui; // ugyanaz a típus
9 Logikai típus (Boolean)
típus neve: bool o George Boole nevéből két érték: true, false műveletek o negáció (tagadás): !x o konjunkció (és): x && y o diszjunkció (vagy): x || y automatikus konverzió egész számra o false ⇆ 0 o true ↦ 1 o nem 0 egész ↦ true
1 + (2 && 3)
// sok nyelven hibás, C++ nyelven nem
Karakter (character)
char o általában egy bájton tárolja a karakter ASCII kódját o automatikus konverzió egészre, értéke a karakterkód wchar_t o "széles karakter", pl. Unicode karakterek tárolására
Lebegőpontos (floating point)
float: egyszeres pontosságú double: kétszeres pontosságú long double: kiterjesztett pontosságú
float f = -1.34f;
egy rögzített hosszúságú mantissza és egy előjeles egész exponens o numerikusan: m ⋅ 2e o a fenti típusokban m és e értékes jegyeinek száma különbözik speciális értékek o NaN (not a number): pl. 0/0 eredménye o pozitív, negatív végtelen
Felsorolási típus (enumeration)
névvel rendelkező konstansokat vezet be, amelyekhez egy-egy számot rendel a típusnak is lehet neve alapértelmezés szerint a hozzárendelt számok nullától indulnak o megadott értéktől is lehet folytatni a számozást (érték előfordulhat újra)
enum { A, B, C, D, E }; enum Abece { A, B = 3, C, D, E }; enum Abece { A, B = 3, C, D = 2, E };
automatikusan konvertálódik egész számra
int i = D; Mutató (pointer)
a memória bájtok sorozataként képzelhető el
10
o bájt: 8 bit együttese, a memória legkisebb egyszerre elérhető egysége o memóriacím: a memória egy bájtjának sorszáma o objektum memóriacíme: az objektumok összefüggő memóriaterületet alkotnak, ennek az első bájtjának a címe a mutató egy adott típusú objektum memóriacímét tartalmazza
Mutató (pointer) int*
0: speciális, sehová sem mutató mutató o NULL: általában 0-ra kifejtődő makró o C++0x: nullptr automatikus konverzió logikai típusra o 0 ↦ false o érvényes mutató ↦ true
Mutató (pointer) int i = 3; int j; int* pi; pi = &i;
// objektum címének képzése // most: pi i-re mutat
j = *pi;
// mutató feloldása // most: j felveszi i értékét
Konstans mutató int i, j; const int* pi1 = &i; // a mutatott érték a konstans: // *pi1 nem állítható int*const pi2 = &i; // a mutató a konstans: pi2 nem állítható át *pi1 = 3; *pi2 = 3; pi1 = &j; pi2 = &j;
// helytelen
// helytelen
Tömb (array)
a tömb adott típusú elemek sorozatát tartalmazza
int ai[3] = { 1, 2, 3 }; int bi[] = { 4, 5, 6, 7 }; // bi hossza: 4 // automatikusan állapítódik meg
az elemek index szerinti elérése hatékony o index: int o a tömb nullától indexelődik
int c = ai[2]; // ai tömb harmadik eleme, 3
a tömb méretét nem lehet növelni
11 Tömb (array)
bár hasonló kinézetű, értékadás nem megengedett tömbtartalomra
int aa[3]; aa = { 1, 2, 3 };
// helytelen
a feltöltetlenül maradt pozíciók nullát tartalmaznak
int a1[6] = { -1, -2, -3 }; int a2[6] = { -1, -2, -3, 0, 0, 0 }; // megegyezik a tartalmuk Tömb (array) int ai[3] = { 1, 2, 3 }; int bi[] = { 4, 5, 6, 7 };
a tömb nem ismeri az elemszámát o buffer overflow: a program könnyen túlcímezhet a tömbön o pl. a cppcheck eszközzel lehet vizsgálni
bi[5] = 8; // futás közben nem biztos, hogy kiderül // lehet, hogy ai első elemét módosítja Tömb (array)
a tömb konvertálódik az első elemére mutató mutatóra o az értékadás csak a mutatót másolja, a tartalmat nem
char ai[] = { 'a', 'b', 'c' }; char* b = ai; // ai[0]-ra mutat
az == operátor a mutatók címét hasonlítja össze, a tartalmukat nem o azaz azt adja meg, ugyanott helyezkedik-e el a két objektum
char ai[] = { 'a', 'b', 'c' }; char bi[] = { 'a', 'b', 'c' }; ai == bi ⇒ false Tömb (array)
nincs többindexű tömb, csak tömbök tömbje
int ai[3][2] = { { 1, 2 }, {3, 4}, {5, 6} };
jagged array: mutatók és tömbök kombinációja o a belső tömbök hossza eltérhet o dinamikus memóriafoglalásnál kézzel kell biztosítani, hogy a belső tömbök egyforma hosszúak legyenek
int a0[] = {1,2}; int a1[] = {3,4,5}; int* a[2] = { a0, a1 }; // elérhető elemek: // a[0][0], a[0][1] // a[1][0], a[1][1], a[1][2]
12 Tömb (array) C sztringek
char elemek tömbjei
char s[] = "abc";
a sztring végét 0 kódú karakter ('\0' karakter) jelzi o ez nem a '0' karakter, annak 48 az ASCII-kódja o a szöveg akkor is az első 0 karakterig tart, ha a tömbben vannak további elemek is
Tömb (array) char s1[] = "abc"; char s2[] = { 'a', 'b', 'c', 0 }; char s3[] = { 'a', 'b', 'c', '\0' }; // mindegyik tömb tartalma azonos // mindegyik hossza 4 char s4[] = { 'a', 'b', 'c', 0, 'd', 'e' }; // C sztringként értemezve azonos az előzőekkel // hossza 6 char* s5 = "abc"; // megengedett, de a tartalma nem módosítható char ures[1] = ""; Tömb (array) Tömbök és mutatók deklarációjának értelmezése int **a[2][3][4]; int** (((a[2])[3])[4]); // kételemű tömb, // amely három elemű tömböket tartalmaz, // amelyek négyelemű tömböket tartalmaznak, // amelyek kétszeres indirekción keresztül // egész számokra mutatnak
a legkülső (sorrendben legelső) tömbnek nem kötelező megadni a dimenzióját, ha inicializáljuk
int b[][3] = { {0,1,2}, {10,11,12} }; Tömb (array) Mutató-aritmetika
p+1 a következő tömbelem címét adja meg o akkor is, ha ezzel kifutunk a tömb területéről
Tömb (array) Mutató-aritmetika
hasonlóan: p-1, p++, ++p, p--, --p (azonos tömbön dolgozó) mutatók kivonása: a mutatók közötti elemek száma általában a mutató-aritmetika veszélyes és kerülendő
13 Tömb (array) Hasznos típusok a tömb kiváltására a sztenderd könyvtárból
szöveg: string tömb: vector
Tulajdonságaik
lekérdezhető a hossza/elemszáma az értékadás helyesen működik a vektorra át lehet méretezni őket
Hivatkozás (referencia) int i; int& ri = i;
referencia: álnév egy objektumra o konstans mutatóval kiváltható, annál kényelmesebb konstrukció
Hivatkozás (referencia)
kötelező kezdő értékadás: a nemkonstans hivatkozott objektum innentől minden művelet, amit a referencián hajtunk végre, olyan, mintha az eredeti objektumon végeznénk o ezután az eredeti eléréssel is az új objektumot kapjuk konstans referencia o az objektumot megváltoztató műveletek nem megengedettek referenciákból nem készíthető tömb és nem állítható rá mutató
Típus mérete
sizeof operátor: egy típusról vagy egy elemről megadja, hány bájt hosszan tárolódik a nyelv nem rögzíti, hány bájton tárolandóak a típusok, csak bizonyos korlátokat ad
1 == sizeof(char) 1 <= sizeof(FelsorolásiTípus) <= sizeof(int) 2 <= sizeof(short) <= sizeof(int) <= sizeof(long) 4 <= sizeof(long) sizeof(float) <= sizeof(double) <= sizeof(long double) Áltípus (typedef)
más néven: típusszinoníma új típusnevet vezet be az új típus nem különböztethető meg a régitől o Ada: a km és a kg ábrázolása ugyanaz, de nem keverhetőek össze
typedef int suly; typedef const char* szovegtomb[100];
a kód olvashatóságát növeli o különösen alkalmas függvénymutatók esetén
Típuskonverzió (cast, type conversion)
reprezentáció megváltoztatása o más bitsorozat reprezentálja ugyanazt az értéket
14
értelmezés megváltoztatása o a reprezentáció azonos o a típusrendszer "megnyugtatására", kijátszására való leszármazott osztály konverziója ősosztályra o itt is megmarad a reprezentáció o ez biztonságos
Típuskonverzió (cast, type conversion)
bővítő konverzió: értékvesztés nélküli
double d = 13;
szűkítő konverzió: előfordulhat értékvesztés
int i = 13.6;
automatikus konverzió: külön jelölés nem szükséges hozzá o lehet bővítő vagy szűkítő (pl. a fenti double → int) explicit konverzió: konverziós operátorral o C stílusú o C++: többfajta konverziós operátor, visszatérünk rájuk
Deklaráció
deklaráció: egy objektumhoz hozzárendel egy nevet o változó: a deklaráció egy konkrét memóriaterületet azonosít, amelynek megváltozhat a tartalma hatókör vagy láthatósági tartomány (scope): a programszöveg azon összefüggő része, ahol a deklaráció által bevezetett név használatban van o lokális név: függvényen vagy blokkon belül bevezetett név, hatóköre a deklaráció helyétől a blokk (a függvény blokkja) végéig tart o globális név: függvényen, osztályon, névtéren kívül bevezetett név, hatóköre a deklaráció helyétől a tartalmazó fájl végéig tart
Deklaráció láthatósága
közvetlen láthatóság: a deklarációra egyetlen azonosítóval (műveleti jellel) lehet hivatkozni láthatóság: a deklaráció közvetlenül vagy minősítéssel (:: operátor) látható using: név közvetlenül láthatóvá tétele másik névtérből vagy osztályból átlapolás vagy elfedés (overlapping): a programszöveg egy részén több, azonos nevet bevezető deklaráció látható
void f(int, int); void f(double, double); Deklaráció láthatósága Névelfedés: azonosító bevezetése vele azonos nevű deklaráció hatókörében
elfedett globális név használata
::x = 1; Deklaráció láthatósága
elfedett lokális név nem használható o hatóköre azonban itt is tart
15 { int x; { x = 1; int x; x = 2; } }
// x hatókörének kezdete // // // // // //
az első x változik hatókör kezdete a második x változik a blokk végéig az első x nem látható a második x hatókörének vége az első x hatókörének vége
Deklaráció láthatósága
a függvényparamétereket nem lehet elfedni a függvény törzsében (de a függvény törzsén belüli blokkban már igen)
int f(int x) { { int x = 1; } return x;
// az eredeti x az eredmény
} Deklarációk és szerkesztés Szerkesztési osztály (linkage): meghatározza, hogy melyik fordítási egységekre terjed ki a deklaráció hatóköre
külső (external linkage): a deklaráció fordítási egységben látható o nem statikus függvények o globális objektumok o extern const objektumok o osztályok o felsorolási típusok és értékeik o sablonok o névterek
Deklarációk és szerkesztés Szerkesztési osztály (linkage)
belső (internal linkage): a deklaráció csak a fordítási egységben látható o statikus objektumok o statikus függvények o névtelen névtér elemei o typedef-fel megadott áltípus-deklaráció o nem extern konstansok nincsen (no linkage): a deklaráció csak lokálisan látható o lokális objektumok (amelyek nem tartoznak az előző két kategóriába)
Objektum élettartama Élettartam (extent, lifetime): az objektum lefoglalása és felszabadítása közötti időtartam
statikus objektumok o élettartamuk a program teljes futása o fajtái globális változó névtérbe tartozó változó lokális statikus változó (static kulcsszó) o ha nincsen kezdőérték-adás, a típusnak megfelelő 0 értékkel inicializálódnak
16 Objektum élettartama Élettartam
lokális változók (automatikus objektumok): a deklaráció helyétől a hatókör végéig tart dinamikusan lefoglalt objektumok: a lefoglalástól a felszabadításig mindkét fentire vonatkozó tulajdonságok o nem inicializálódnak, ha nincsen kezdőérték-adás o használat előtt inicializálni kell őket, különben definiálatlan a program működése tömbök elemei, osztályok adattagjai o élettartamuk a tömb/osztály élettartamával azonos
Deklaráció szerkezete
alaptípus o opcionális minősítő kulcsszavak deklarátor o bevezetett név o opcionális deklarátor-operátorok o opcionális kezdőérték-adó rész konstans esetén kötelező, egyébként elmaradhat deklarátor-operátor mutató
szerkezet *név
konstans mutató
*const név
referencia
&név
tömb
név[]
függvény
név(paraméterek)
Deklaráció szerkezete
opcionális minősítő kulcsszavak o const, extern, inline, static, virtual, ... o const: az objektum alaptípusa állandó, a hatókörén belül az értéke nem változik meg o az alaptípus elé és mögé is lehet írni őket o tetszőleges sorrendben
const int i = 3; int const j = 6; const extern int*const bonyolult[3]; Deklaráció szerkezete
több név bevezetése egy deklarációban o vesszővel elválasztva o az operátorok és az értékadás csak egy névre vonatkoznak
int i = 1, j = 2; int j, *p, **pp;
célszerű csak egy nevet bevezetni egy deklarációban
int j; int* p; int** pp; Deklaráció szerkezete
17
konstans és konstans mutató
const int* pi1 = ...; // pi átállítható // *pi nem kaphat értéket int*const pi2; // pi mindig ugyanoda mutat // *pi értékét meg lehet változtatni const int*const pi3 = ...; // pi nem állítható, és *pi értéke sem