FAQ C: Pointeri. Variabile constante

EducatieOameni fainiTutoriale

Written by:

În săptămânile trecute, am discutat despre variabile. Ne-am întrebat inițial cum sunt reprezentate numerele în memorie și cum lucrăm la nivel de biți. Apoi, am continuat cu diferențierea variabilelor cu semn și fără semn și am văzut cum interacționează atunci când le folosim.

Imagine preluata de la https://xkcd.com/138/

Imagine preluată de la adresa: https://xkcd.com/138/

În continuare, în săptămânile ce urmează, vom aborda subiectul variabilelor în profunzime, începând prin a introduce conceptul de pointeri. Atunci când facem primii pași în programare, lucrul cu pointeri poate părea complicat. Totuși, aceștia oferă control asupra memoriei și flexibilitate în folosirea variabilelor.

Ce este un pointer? De ce sunt folositori? Cum se modifică valoarea unei variabile? Ce este o variabilă constantă? De ce nu îi putem schimba valoarea? Ce este un pointer constant?

Let’s begin!

 

Cum sunt păstrate în memorie variabilele?

Ne reamintim din articolele trecute faptul că numerele sunt reprezentate în calculator sub forma unor înșiruiri de biți de 0 și 1. De asemenea, există mai multe tipuri de date prin care pot fi reprezentate numerele, astfel încât putem avea numere pe 8, 16, 32 sau 64 de biți.

Totuși, variabilele trebuie să existe undeva în memorie pentru a putea fi folosite. Așadar, ca orice altă informație din calculator, variabilele ocupă o locație la o adresă în memorie. Cum putem afla această adresă a unei variabile? Să vedem un scurt exemplu:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main () {
    int a = 4;
    printf ("Adresa variabilei este: %p\n", &a);

    return 0;
}

OUTPUT
Adresa variabilei este: 0x7ffd841406c4

Ce observăm în programul de mai sus?

În primul rând, am folosit &a. Folosirea & înaintea unei variabile va oferi adresa variabilei. Ca o paranteză, ne amintim de atunci când am vorbit despre operațiile pe biți de faptul că operatorul & poate fi folosit și ca ȘI-LOGIC între doi operanzi (ex. a & b).

În al doilea rând, la formatul afișării am folosit %p. Ei bine, asta înseamnă că afișez o adresă și nu o valoare a unui număr. Către această adresă pot folosi un indicator, denumit pointer. De aceea, folosirea %p arată faptul că se afișează valoarea unui pointer, adică o adresă.

Notă: Adresa afișată la output poate să difere de la un calculator la altul și de la o rulare a programului la alta. Acest lucru se întâmplă întrucât adresa unei variabile în memorie se poate schimba la o altă rulare a programului, în funcție de spațiul disponibil din memorie. Vom detalia modul în care sunt reținute variabilele în memorie în articolele următoare.

Dacă ne gândim mai bine, folosim adrese chiar și atunci când realizăm citirea unei variabile, folosind scanf:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main () {
    int a;
    scanf ("%d\n", &a);

    return 0;
}

Ceea ce realizează funcția scanf în exemplul de mai sus este că citește o valoare întreagă pe 4 bytes și o stochează la adresa variabilei a.

Fiindcă am introdus conceptul de pointer să vedem mai exact definiția acestuia și importanța lucrului cu pointeri.

 

Ce este un pointer?

Un pointer reprezintă un tip de date special, în care putem păstra adresa altor variabile. Acesta se numește “pointer”, deoarece vine de la verbul din limba engleză „to point to”, adică indică locul, adresa, unde este păstrată o variabilă în memorie.

Deoarece un pointer poate fi stocat într-o variabilă, acesta se declară în mod asemănător celorlalte tipuri de variabile. Dacă avem declarația int a și dorim să reținem adresa variabilei a, putem folosi un pointer declarat astfel: int* p. Această declarație îi spune calculatorului că avem nevoie de o variabilă de tip pointer ce va reține adresa unei variabile de tip int. Putem declara pointeri către diferite tipuri de date (ex. char, short, int, etc.).

Odată ce avem adresa unei variabile reținută într-un pointer, putem avea acces și la valoarea efectivă de la acea adresă. Pentru a obține valoarea variabilei a cărei adresă o reținem în ptr, folosim *ptr. Această operație poartă numele de dereferențiere.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main () {
    int a = 5;
    int *ptr = &a;

    printf("a = %d", a);    // valoarea variabilei a

    printf("*ptr = %d", *ptr);   // valoarea de la adresa indicată de ptr

    printf("ptr = %p", ptr);   // adresa spre care indică ptr

    return 0;
}

OUTPUT
a = 5
*ptr = 5
ptr = 0x7ffc72faa9f4

 

De ce sunt folositori pointerii?

Poate vă gândiți la ce ar fi utili pointerii în afară de citirea de la tastatură și păstrarea adreselor unor variabile. Un exemplu ar fi faptul că dorim să avem acces la o variabilă de dimensiuni mari într-o altă zonă a programului (într-o funcție) și putem să folosim un pointer în care reținem adresa acelei variabile.

Astfel, dacă avem o variabilă de dimensiuni mari, nu va trebui să o copiem în zona în care vrem să o folosim (operație ce ar fi costisitoare), ci vom avea un pointer care reține adresa acelei variabile. Programul nostru va fi mai eficient.

De asemenea, un alt exemplu este atunci când dorim să avem un vector și folosim un pointer pentru a reține adresa de la care încep valorile reținute în vector. Folosind pointeri putem aloca memorie dinamic și astfel să avem vectori de dimensiune variabilă.

Despre toate acestea vom vorbi în articolele viitoare și vom vedea în exemple concrete avantajele folosirii pointerilor.

 

Cum se modifică valoarea unei variabile?

Începem cu exemplul unei variabile obișnuite. Să urmărim cum se modifică valoarea ei într-un program:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main () {
    int a;

    a = 5;
    printf ("Valoarea lui a este: %d.\n", a);

    a = 10;
    printf ("Valoarea lui a este: %d.\n", a);

    return 0;
}

OUTPUT
Valoarea lui a este: 5.
Valoarea lui a este: 10.

Putem de asemenea să modificăm valoarea unei variabile folosind pointeriAșa cum am observat și mai sus, putem dereferenția un pointer și astfel să avem acces la valoarea de la adresa către care indică. Acest lucru ne oferă, de asemenea, posibilitatea schimbării acelei valori.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main () {
    int a = 5;

    int *ptr = &a;
    printf ("Valoarea lui a este: %d.\n", *ptr);

    *ptr = 10;
    printf ("Valoarea lui a este: %d.\n", *ptr);

    return 0;
}

OUTPUT

Valoarea de la afresa indicata este: 5.

Valoarea de la adresa indicata este: 10.

Astfel, valorile variabilelor pot fi schimbate în mai multe moduri. Cu toate acestea, există variabile constante, a căror valoare nu poate fi modificată.

 

Ce este o variabilă constantă?

O variabilă constantă este o variabilă a cărei valoare nu poate fi modificată, însă poate fi folosită în program (numită și “read-only”).

Pentru ca o variabilă să fie constantă folosim const în declarația acesteia. Orice încercare de a modifica o variabilă constantă va produce o eroare la compilare.

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main () {
    const int a = 5;
    printf ("Valoarea variabilei constante este: %d\n", a);

    a = 10;
    printf ("Valoarea variabilei constante este: %d\n", a);

    return 0;
}

OUTPUT COMPILE-TIME

error: assignment of read-only variable ‘a’:

[…]

Un exemplu de utilizare a variabilelor constante este atunci când avem nevoie de valoarea numărului PI în mai multe locuri în programul nostru. În loc să scriem 3.141592 de mai multe ori, putem declara o variabilă constantă în care punem această valoare. O facem constantă pentru ca ulterior să nu fie modificată din greșeală.

Cuvântul cheie const folosit în declararea variabilelor obișnuite poate fi pus atât înaintea tipului de date, cât și după. Cele două forme de scriere au o comportare identică. Astfel, următoarele două declarații sunt echivalente:

const int a;

int const a;

Deoarece și pointerii sunt stocați în variabile, putem folosi const în declararea lor. Totuși, în acest caz, locul în care apare cuvântul cheie const contează. Să urmărim următoarele exemple pentru a vedea diferențele de comportament în funcție de locul în care acesta apare.

const tip_date* ptr

Adresa care este păstrată în pointer poate fi schimbată, însă valoarea de la adresa respectivă nu poate fi modificată. Cu alte cuvinte, pointerul va putea indica spre o altă zonă de memorie, însă la acele zone de memorie spre care indică, valoarea va rămâne constantă.

Schimbarea adresei către care indică pointerul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main () {
    int a = 5;
    int b = 10;

    const int* ptr = &a;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    ptr = &b;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    return 0;
}

OUTPUT

Valoarea de la afresa indicata este: 5.

Valoarea de la adresa indicata este: 10.

 

Schimbarea valorii de la adresa către care indică pointerul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main () {
    int a = 5;
    int b = 10;

    const int* ptr = &a;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    *ptr = b;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    return 0;
}

OUTPUT COMPILE-TIME

error: assignment of read-only location ‘*ptr’:
[…]

 

tip_date* const ptr

În acest caz, putem schimba valoarea variabilei a cărei adresă o păstrează pointerul, însă acesta nu va putea indica decât spre acea zonă de memorie pe tot parcursul programului.

Schimbarea valorii de la adresa indicată de pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main () {
    int a = 5;
    int b = 10;

    int* const ptr = &a;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    *ptr = b;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    return 0;
}

OUTPUT
Valoarea de la adresa indicata este: 5.
Valoarea de la adresa indicata este: 10.

 

Schimbarea adresei către care indică pointerul

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main () {
    int a = 5;
    int b = 10;

    int* const ptr = &a;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    ptr = &b;
    printf ("Valoarea de la adresa indicata este: %d.\n", *ptr);

    return 0;
}

OUTPUT COMPILE-TIME
error: assignment of read-only variable ‘ptr’:
[…]

 

Sumar. Cuvinte cheie

În final, e timpul să facem o scurtă recapitulare a noțiunilor prezentate în această săptămână.

    • Fiecare variabilă este păstrată în calculator la o adresă de memorie
    • Pentru a afla adresa unei variabile, folosim & împreună cu aceasta (ex. &a)
    • Această adresă poate fi păstrată într-o variabilă pe care o numim pointer
    • Un pointer se declară folosind * împreună cu menționarea tipului de date al variabilei
    • O variabilă ce conține cuvântul cheie const în declarație are valoare constantă și după inițializare, aceasta nu mai poate fi schimbată
    • În declararea unei variabile obișnuite, apariția cuvântului const înainte sau după numele tipului de date are comportament identic: valoarea variabilei nu se poate modifica după inițializare
    • La pointeri, locul apariției cuvântului const contează și se remarcă următoarele situații:
      • const tip_date* ptrvaloarea de la adresa reținută în pointer NU poate fi modificată, însă adresa pointerului poate fi modificată
      • tip_date* const ptrvaloarea de la adresa reținută în pointer poate fi modificată, însă adresa pointerului NU poate fi modificată

Cuvinte cheie: pointer, adresă, constantă

Spread the love