Minden számítógépes program figyeli és változtatja a
memóriában tárolt értékeket. Irányítja, hogy hol, milyen sorrendben és
milyen értékek vannak, hogy a kimenet megjósolható legyen. Emiatt szükség van
olyan adattípusra, ami a memóriacímet képviseli. A magasabb absztrakciós szintű
programozási nyelvek már nem igénylik címtípusok használatát, de a háttérben akkor is ott vannak és automatikusan végzik a dolgukat. A C++ két címtípussal
dolgozik: a referenciával és a mutatóval.
A referenciákkal létező változókra hivatkozunk, ezért minden referencia
deklarálásnál rögtön meg is kell határozni (inicializálni kell), hogy mire megy
a hivatkozás:
int x = 2;
int &r = x;
A fenti példában r az x
memóriacímén tárolt értékre hivatkozik. Ha x memóriacíme 0x22fe37, akkor az r memóriacíme is az lesz és a benne tárolt
érték is.
#include<iostream>
using std::cout;
int main()
{
int x = 2;
int &r = x;
cout
<< "x = " << x;
cout
<< "\nr = " << r;
cout
<< "\n&x: " <<
&x;
cout
<< "\n&r: " <<
&r;
return 0;
}
A kimenet pedig:
x = 2
r = 2
&x: 0x22fe34
&r: 0x22fe34
Más szóval a referencia egy másolat a
változóról, x és r teljesen ugyanolyan című és értékű. A
másolat nem szószerint értendő, ugyanis továbbra is egy változó és egy memóriahely van, csupán két nevet használunk. Ha elvégezzük az r = x + 10; műveletet, mindkét változó értéke 12
lesz. Ha egyiknek új értéket adunk, a másik értéke is megváltozik; úgy is montható, hogy r az x másik neve vagy álneve.
A referenciák a függvények
paramétereiben bizonyulnak igazán hasznosnak, mert memóriát spórolhatunk velük. Alap esetben, mikor egy változót küldünk paraméterként a
függvénynek, arról a háttérben egy másolat készül új memóriacímmel és azt
használja a függvény, majd a végén letörli. Ha referenciát, azaz memóriacímet
adunk meg, akkor közvetlenül azzal a változóval dolgozik, és nem készít
másolatot. A függvény meghívása során viszont vigyázni kell, hogy a referencia nevét (az álnevet) írjuk
a paraméterek listájába és ne egy konkrét értéket vagy kifejezést:
Deklarálás: void Fuggveny(double
szam, int& szamlalo);
Helyes hívás: Fuggveny(y, i);
Fuggveny(9.81, i);
Fuggveny(4.9*sqrt(y),
i);
Helytelen hívás: Fuggveny(y, 3);
A következő program értékkel és referenciával használt függvényekre ad példát:
#include <iostream>
using std::cout;
using std::endl;
int Negyzet_ertekkel(int);
void Negyzet_referenciaval(int&);
int main()
{
int x = 2;
cout
<< "x= " << x <<
endl
<< "Negyzet_ertekkel(x):
" << Negyzet_ertekkel(x) << endl
<< "x=
" << x << endl
<< "Negyzet_referenciaval(x)..."
<< endl;
Negyzet_referenciaval(x);
cout
<< "x= " << x;
return 0;
}
int Negyzet_ertekkel(int
a)
{
return a * a;
}
void Negyzet_referenciaval(int&
b)
{
b = b
* b;
}
A kimenet:
x= 2
Negyzet_ertekkel(x):
4
x= 2
Negyzet_referenciaval(x)...
x= 4
A mutató a referenciák továbbfejlesztett változata. A mutatók a
referenciákkal ellentétben külön változók és nem értéket, hanem memóriacímet tárolnak. Bármely más
változó memóriacímét tárolhatják. Amíg egy változó közvetlenül hivatkozik
az értékére, addig a mutató közvetett (indirekt) módon, a memóriacím révén hivatkozik arra ez
értékre. Mivel a mutatók külön változók, nem kell őket inicializálni a
deklarálás során.
double *x, *y;
Az x és y double típusú mutatók, azaz csak double típusú
változók memóriacímeit tartalmazhatják. Ha azt szeretnénk, hogy a mutató sehová
se mutasson, akkor a zéróra vagy NULL-ra kell állítani:
double *x = NULL;
Mivel a mutató is változó, ezért neki is
megvan a maga címe, azaz létezik az &x.
#include <iostream>
using std::cout;
int main()
{
int a;
int* aP;
a =
7;
aP =
&a;
cout
<< "a= " << a
<< "\n&a=
" << &a;
cout
<< "\naP= " << aP
<< "\n&aP="
<< &aP
<< "\n*aP="
<< *aP
<< "\n*&aP="
<< *&aP
<< "\n&*aP="
<< &*aP;
return 0;
}
A kimenet:
a= 7
&a= 0x22fe2c
aP= 0x22fe2c
&aP=0x22fe20
*aP=7
*&aP=0x22fe2c
&*aP=0x22fe2c
A fenti programban látszik, hogy a
mutatónak csakis memóriacímet lehet értékként adni, és hogy a tárolt
memóriacímen lévő értékre a csillag operátorral lehet hivatkozni. Éppen ezért a
csillag és a referenciajel kioltja egymást (a csillagot dereferenciáló
operátorként is nevezik). A *aP az a értékére való hivatkozás, ezért értékadásra is
használható: *aP = 5. Akár a referenciák esetén, a *aP = a + 10; műveletet után, mindkét változó értéke 17
lesz. A memóriacím mint érték tároló lehetővé tesz néhány alapvető műveletet
a mutatókkal, például hozzáadhatunk vagy kivonhatunk egész számokat belőlük,
vagy akár mutatókat is, és összehasonlíthatóak. Ezek segítségével például a
szomszédos memóriacímre léphetünk, ami a tömbök vagy listák esetén a következő
elemet jelenti.
#include <iostream>
using std::cout;
using std::endl;
int Negyzet_ertekkel(int);
void Negyzet_referenciaval(int*);
int main()
{
int x = 2;
cout
<< "x= " << x <<
endl
<< "Negyzet_ertekkel(x):
" << Negyzet_ertekkel(x) << endl
<< "x=
" << x << endl
<< "Negyzet_referenciaval(&x)..."
<< endl;
Negyzet_referenciaval(&x);
cout
<< "x= " << x;
return 0;
}
int Negyzet_ertekkel(int
a)
{
return a * a;
}
void Negyzet_referenciaval(int*
b)
{
*b =
(*b) * (*b);
}
A kimenet:
x= 2
Negyzet_ertekkel(x):
4
x= 2
Negyzet_referenciaval(&x)...
x= 4
A fő különbség a program referenciás
változatához képest, hogy a Negyzet_referenciaval függvény memóriacímet kapott paraméterként, amit a mutató *b vett fel.
#include <iostream>
using std::cout;
int main()
{
int v[5];
int* vPtr = &v[0]; //vPtr
a v[0] címet tartalmazza
cout
<< "vPtr= " << vPtr;
vPtr
+= 2;
cout
<< "\nvPtr += 2 = " <<
vPtr;
vPtr--;
cout
<< "\nvPtr-- = " <<
vPtr;
--vPtr;
cout
<< "\n--vPtr = " <<
vPtr;
int* v2Ptr = &v[4];
cout
<< "\nv2Ptr-vPtr = "
<< v2Ptr-vPtr;
return 0;
}
A kimenet:
vPtr= 0x22fe10
vPtr += 2 =
0x22fe18
vPtr-- =
0x22fe14
--vPtr =
0x22fe10
v2Ptr-vPtr = 4
Mivel int típusról van szó, a 2-vel való növelés nem 2
byte-ot, hanem 2x4 byte-ot növel, hisz az int a 32 bites rendszeren 4 byte. A mutatókat akkor lehet összevonni, ha
ugyanolyan típusúak, különben típusátalakítást kell végezni. Egyetlen kivétel
ez alól a void* típusú mutató, amely bármilyen típusú pointerrel
képes együtt működni. A többi mutatóval ellentétben ez az általános mutató
csupán memóriacímeket tárol, így nem rendelhető érték a megcímzett változóhoz
(mert a fordító nem tudja előre meghatározni az adott címen lévő byte-ok számát). Az értékadás ebben az esetben csakis típusátalakítással lehetséges.
#include<iostream>
using namespace std;
int main()
{
int x = 10;
void* ptr = &x;
cout
<< "ptr = " << ptr;
//cout << "*ptr= " << *ptr; // hiba
cout
<< "\n*(int *)ptr = "
<< *(int *)ptr;
typedef int * iptr;
cout
<< "\n*iptr(ptr) = "
<< *iptr(ptr);
cout
<< "\n*static_cast<int *>(ptr) =
" << *static_cast<int *>(ptr);
cout
<< "\n*reinterpret_cast<int
*>(ptr) = " << *reinterpret_cast<int *>(ptr);
}
A kimenet:
ptr = 0x22fe34
*(int *)ptr = 10
*iptr(ptr) = 10
*static_cast<int
*>(ptr) = 10
*reinterpret_cast<int
*>(ptr) = 10
A tömbök és a mutatók a C++ nyelvben
nagyon közel állnak egymáshoz. Ha a v[] tömb
nevének memóriacímét nézzük (&v) és
ezt átadjuk egy mutatónak (*vPtr = &v), akkor az az első elem címét fogja tartalmazni. A harmadik elemre a következőképp lehet hivatkozni: *(vPtr+3). Nem kötelező mutatót használni, elegendő a csillag operátor is: *(v+3). Alap esetben [] operátor segít az adott elemre hivatkozni, például a harmadik elemre a v[3] utal és ez alkalmazható a tömb mutatóján is: vPtr[3] . A különbség a v és a vPtr
között, hogy a v-vel nem lehet aritmetikai műveletet
végezni, mint például a v+=3. Ezt
leszámítva elmondható, hogy a tömb ugyanaz mint egy konstans mutató. Ha a
karakterekből álló stringet vesszük példának, akkor nem másról van szó, mint a
string első karakterére mutató mutatóról:
#include<iostream>
using std::cout;
int main()
{
const char s[] = {"Char típusú változókból felépített string..."};
const char* p[] = {"Char típusó mutatókból felépített string..."};
const char* szamok[4]
= {"Egy", "Ketto",
"Harom", "Negy"};
cout
<< "s = " << s
<< "\ns[0]
= " << s[0]
<< "\n*p
= " << *p
<< "\n*p[0]
= " << *p[0] << "\n"
<< "\n*szamok[0]
= " << *szamok[0]
<< "\nszamok[0]
= " << szamok[0]
<< "\n&szamok[0]
= " << &szamok[0]
<< "\n*(szamok)
= " << *szamok
<< "\nszamok
= " << szamok << "\n"
<< "\n*szamok[1]
= " << *szamok[1]
<< "\nszamok[1]
= " << szamok[1]
<< "\n&szamok[1]
= " << &szamok[1]
<< "\n*(szamok+1)
= " << *(szamok+1)
<< "\nszamok+1
= " << szamok+1;
return 0;
}
A kimenet:
s = Char típusú változókból felépített string...
s[0] = C
*p = Char típusú mutatókból felépített string...
*p[0] = C
*szamok[0] = E
szamok[0] = Egy
&szamok[0] =
0x22fde0
*(szamok) = Egy
szamok =
0x22fde0
*szamok[1] = K
szamok[1] =
Ketto
&szamok[1] =
0x22fde8
*(szamok+1) =
Ketto
szamok+1 =
0x22fde8
A mutatók lehetővé teszik például azt is,
hogy különböző hosszúságú stringekből álló tömböt alkossunk. A tömb mindenik
eleme az ott lévő string első karakterére mutat. Ezért a *szamok[0] értéke E, a *szamok[1] értéke pedig K. Mindenik string más memóriacímen van, ám
egymással szomszédosak. Az E és a K és
a többi karakter is nyolc byte távolságra van egymástól, mert egy memóriacím (0x22fde0) összesen 8 byte-ból áll. Ha ugyanezt két
dimenziós tömbbel valósítanánk meg, akkor a tömb mindenik elemét a leghosszabb
string hosszúságára kéne szabni és az üres karakterek fölöslegesen
foglalnák a memóriát.
Mivel a mutató egy változó, ezért rá is
lehet mutatni egy másik mutatóval:
#include<iostream>
using std::cout;
int main()
{
int x = 2;
int* p = &x;
// int típusú mutató
int** q = &p;
// int típusú mutatóra mutathat.
int*** z = &q; // int
típusú mutatóra mutató mutatóra mutathat.
cout
<< "x = " << x
<< "\n*p
= " << *p
<< "\n**q
= " << **q
<< "\n***z
= " << ***z;
return 0;
}
A kimenet:
x = 2
*p = 2
**q = 2
***z = 2
A konstansok esetén a mutató is szigorúan
konstans kell legyen. Konstans mutatója pedig lehet a változóknak is és a
mutató deklarálása határozza meg, hogy mit lehet és mit nem:
#include<iostream>
using std::cout;
int main()
{
int x = 2;
int y = 7;
const int* p =
&x; // nem engedi az x módosítását, engedi a
mutató módosítását
int *const q =
&x; // engedi az x módosítását, nem engedi a
mutató módosítását
const int* const z = &x; // nem
enged semmit sem
//*p = 4; // hiba
cout
<< "x = " << x
<< "\n";
p =
&y;
*q =
4;
cout
<< "x = " << x
<< "\n";
//q = &x; // hiba
//*z = 8; // hiba
cout
<< "x = " << x <<
"\n";
// z = &y; // hiba
return 0;
}
A kimenet:
x = 2
x = 4
x = 4
Nincsenek megjegyzések:
Megjegyzés küldése