&
-operaattorin avulla.
int a = 5; cout << &a << "\n";Koodin tulostus voi olla vaikkapa seuraava:
0x7ffcb35f5724Tämä on heksamuodossa ilmoitettu muistiosoite, johon on tallennettu muuttujan sisältö.
*
-merkki.
Tämän jälkeen voimme laittaa osoittimen osoittamaan
johonkin muuttujaan ja käsitellä muuttujaa osoittimen kautta
*
-merkin avulla.
Esimerkiksi seuraava koodi luo osoittimen
p
ja laittaa sen osoittamaan muuttujaan a
.
Tämän jälkeen muutamme osoittimen kautta muuttujan arvoa.
int a = 5; int *p; p = &a; *p = 8; cout << a << "\n"; // 8Jos osoitin ei osoita mihinkään, tämän voi ilmaista arvolla
nullptr
(null pointer):
int *p; p = nullptr;Samaa tarkoittava merkintä on myös C-kielen mukainen
NULL
, jota käytettiin etenkin aiemmin.
testi
parametrina on osoitin, jonka avulla muutos
välittyy kutsukohtaan.
void testi(int *x) { *x = 5; } int main() { int x = 2; testi(&x); cout << x << "\n"; // 5 }Seuraava koodi puolestaan käyttää viittausta, ja sen toiminta on sama:
void testi(int &x) { x = 5; } int main() { int x = 2; testi(x); cout << x << "\n"; // 5 }Asiaa voi ajatella niin, että viittaus on osoitin, jonka käyttöä on rajoitettu. Osoittimen voi asettaa osoittamaan toiseen muuttujaan, mutta viittaus ei koskaan muutu sen luomisen jälkeen. Viittauksen käyttäminen on helpompaa, joten sitä kannattaa käyttää, jos ei ole tarvetta osoittimen ominaisuuksille.
t
tarkoittaa samaa kuin &t[0]
.
Voimme muuttaa osoittimen kohtaa muistissa
yhteen- ja vähennyslaskun avulla.
Esimerkiksi operaattorit ++
ja --
siirtävät osoitinta yhden alkion verran eteenpäin ja taaksepäin.
Seuraava koodi luo osoittimen p
,
joka viittaa aluksi taulukon alkuun.
Sitten koodi tulostaa osoittimen sijainnin ja vastaavan arvon,
siirtää osoitinta askeleen eteenpäin
ja toistaa tulostamisen.
int t[5] = {1,2,3,4,5}; int *p = t; cout << p << " " << *p << "\n"; p++; cout << p << " " << *p << "\n";Koodin tulostus voi olla seuraava:
0x7ffd17c1e070 1 0x7ffd17c1e074 2Huomaa, että koska
int
-muuttujan koko on 4 tavua,
muistiosoite kasvaa 4:llä.
x
on globaali muuttuja:
int x; // alkuarvo 0 void testi() { x++; cout << x << "\n"; } int main() { testi(); // tulostaa 1 testi(); // tulostaa 2 }Myös funktion sisällä voi määritellä staattisen muuttujan
static
-sanan avulla.
Tällainen muuttuja on käytettävissä vain funktion sisällä,
mutta sille varataan kiinteä määrä muistia ohjelman alussa.
Niinpä muuttujan arvo säilyy tallessa,
jos funktiota kutsutaan monta kertaa.
void testi() { static int x; // alkuarvo 0 x++; cout << x << "\n"; } int main() { testi(); // tulostaa 1 testi(); // tulostaa 2 }
Tarkastellaan esimerkkinä seuraavaa ohjelmaa:
void testi(int a) { int b = 2*a; cout << b << "\n"; } int main() { testi(1); testi(3); }Ohjelman suorituksen aikana pinon sisältö muuttuu suunnilleen näin:
a
arvo 1 pinoon)
b
arvo 2 pinoon)
a
arvo 3 pinoon)
b
arvo 6 pinoon)
Huomaa, että pinon enimmäiskoko on usein oletuksena melko pieni. Esimerkiksi Linuxissa pinon enimmäiskoko voi olla 8 megatavua. Tämän vuoksi ei ole hyvä idea varata funktiossa suurta taulukkoa tähän tapaan:
int main() { int t[10000000]; }Taulukko ei ehkä mahdu pinoon ja ohjelma päättyy virheeseen. Ohjelma toimii kuitenkin mainiosti, kun vastaava taulukko luodaan esimerkiksi globaalisti päätasolla.
new
varaa halutun määrän muistia keosta,
ja komento delete
vapauttaa aiemmin varatun muistin.
Esimerkiksi seuraava koodi varaa keosta muistia
int
-muuttujalle ja vapauttaa sitten muistin:
int *x = new int; *x = 5; delete x;Taulukolle voi varata muistia melko samalla tavalla. Esimerkiksi seuraava ohjelma varaa muistia 10-alkioiselle taulukolle ja vapauttaa sitten muistin.
int *x = new int[10]; x[3] = 5; delete[] x;Keosta varaamisen etuna on, että siinä voi hallita täysin, milloin muistia varataan ja vapautetaan. Lisäksi keosta varattavan muistin määrää ei ole rajoitettu pinon tavoin, vaan on mahdollista varata suuri määrä muistia funktion sisällä. Muistin varaaminen keosta on kuitenkin hankalaa, eikä sitä kannata käyttää turhaan. C++:n tietorakenteet, kuten merkkijono
string
,
varaavat sisäisesti muistia keosta.