14. Указатели

План:
Дялове в оперативната памет при изпълнение на програма на С++
Тип указател
Използване на указатели в класове
Указатели и масиви, адресна аритметика
Указатели и С-низове

** Дялове в оперативната памет при изпълнение на програма на С++. 
CODE 
транслираната програма 
(изпълним файл)
DATA 
глобални данни (константи и променливи)
STACK 
локални данни (константи и променливи)
параметри-променливи
върнати стойности на функции
HEAP (динамична памет)
стойности на указатели

** Тип указател
* Съхранява адрес от оперативната памет на компютъра. 
* Дефиниране на променлива тип указател: 
Синтаксис: <име на тип> * <име на променлива>
Employee *boss;   // без инициализация (начална стойност)
int *pn = NULL; // с начална стойност

* Запазване на място в динамичната памет - унарна операция new (C++) - синтаксис: 

Синтаксис: new <име на тип>
- запазва място за една променлива от зададения тип
Синтаксис: new <име на тип>[<число>]
- запазва място за няколко променливи (колкото е числото) от зададения тип

Пример 1:
-дефиниране на променлива тип указател (pn) и задаване на начална стойност на указателя (операция new): 
int *pn = new int; 

Пример 2:
-дефиниране на масив (arr) в динамичната памет с помощта на указател:
int *arr = new int[100];

Пример 3:
-дефиниране на променлива тип указател (boss), задаване на начална стойност на указателя (операция new) и задаване на начална стойност (конструктор) на сочения от него обект (Employее("John", 3200)):
Employee *boss = new Employее("John", 3200);

-конструиране на обект от тип int:
int *pn = new int(10);

STACK 
 
Име: boss
Тип: Employee* (указател)
Стойност: адрес XXX

 
Име: pn
Тип: int* (указател)
Стойност: адрес YYY
Динамична памет 

адрес XXX

Име: *boss (временно!)
Тип: Employee
Стойност:
"John"
3200

адрес YYY

Име: *pn (временно!)
Тип: int
Стойност: 10

* Операция стойност на указател (dereference) - унарна операция * (C): 
Синтаксис: *<указател>
Employee *boss = new Employее("John", 3200); // началник е John
Employee harry("Harry", 1500);
*boss = harry; // началник вече е Harry; 

int *pn = new int(10);
cout << *pn;

*pn = 12;
 
cout << *pn;

* Обекти и променливи без имена:
Employee *boss = new Employее("John", 3200); // *boss е John
Еmployee *h;

h = boss;                                  // *boss и *h е все John
boss = NULL;
                               // само *h е John, *boss е грешка!

* Операция стрелка - бинарна операция -> (C):
Синтаксис: <указател към клас> -> <член на класа>
cout << (*boss).get_salary();

cout << boss->get_salary();
cout << *boss.get_salary(); // синтактична грешка! *(boss.get_salary())!

* Освобождаване на памет (унищожаване на обекта) - унарна операция delete (C++):
Синтаксис: delete <указател>
delete boss; 
delete pn;

* Невалидни указатели
- опит за работа със стойност на указател след освобождаване на заетата памет.
int *pk = new int(10); 
cout << *pk; 
delete pk; 
*pk = 100; // невалиден указател
Опасна грешка! 

- неопределена (незаредена) променлива.
int *pk;
*pk = 100; // невалиден указател
Опасна грешка!

* Адрес на променлива или обект - унарна операция & (C):
Синтаксис: & <име на променлива>
int *p = new(20);
int *pk = &(*p); // същото като int *pk = p;

* Указатели в системния стек (run-time stack).
Employee harry("Harry", 4300); 
Employee *h = &harry;

int k = 10; 
int *pk = &k; 
cout << k; 
cout << (*pk);
STACK 
Адрес XXX
Име: k
Тип: int
Стойност: 10
STACK 
 
Име: pk
Тип: int* (указател)
Стойност: Адрес XXX

* Задължително освобождаване на заета динамична памет.
    void g()
    {
    Employee* boss;
     // 1-st allocation: pointer variable allocated on the stack (name boss)
     boss = new Employee(. . .);
     // 2-nd allocation: Employee object allocated on the heap (no name!)
     . . .
    } // memory for boss automatically reclaimed
    // NO! Memory leak!

* Променлива-псевдоним - друго име на същата променлива.
int k = 10; 
int &ak = k; // дефиниране на променлива-псевдоним
if ( ak == k ) cout << "YES, of course";

* Параметри-псевдоними и параметри указатели. 
void swap1(int &a, int &b) // разменя стойностите на променливите a и b
{
    int w = a;
    a = b;
    b = w;
}

void swap2(int *pa, int *pb) // разменя стойностите на променливите с адреси указателите a и b
{
    int w = *pa;
    *pa = *pb;
    *pb = w;
}
int main()
{
    int x = 10, y = 12;
    swap1(x, y); // параметрите са промелниви от тип int
    cout << x << " " << y << endl;
    swap2(&x, &y); // параметрите са адреси на променливи от тип int
    cout << x << " " << y << endl;
    return 0;
}


** Използване на указатели в класове
* Пример (optional attribute):
Класът Department представя департамент с име (string) и администратор (Employee), като в някой департамент може да няма администратор.
Решение без указатели с допълнителна данна.

class Department {
. . .
private:
string name;
bool exists; // true ако има администратор
Employee recept; // обектът съществува само ако има администратор
};

Решение с указател.
Ако в департамента има администратор, то указател ще сочи към този обект, ако не - указателят ще има стойност NULL.
class Department {
. . .
private:
string name;
Employee* receptionist;
};

Пример (sharing):
Някои департаменти могат да имат администратор и/или секретар, като някъде един и същи служител може да заема и двете длъжности.

class Department {
. . .
private:
string name;
Employee* receptionist;
Employee* secretary;
};


// department.cpp
#include
<string> #include <iostream> using namespace std; #include "ccc_empl.h" /**
A department in an organization. */ class Department { public: Department(string n); void set_receptionist(Employee* e); void set_secretary(Employee* e); void print() const; private: string name; Employee* receptionist; Employee* secretary; }; /** Constructs a department with a given name. @param n the department name */ Department::Department(string n) { name = n; receptionist = NULL; secretary = NULL; } /** Sets the receptionist for this department. @param e the receptionist */ void Department::set_receptionist(Employee* e) { receptionist = e; } /** Sets the secretary for this department. @param e the secretary */ void Department::set_secretary(Employee* e) { secretary = e; } /** Prints a description of this department. */ void Department::print() const { cout << "Name: " << name << "\n" << "Receptionist: "; if (receptionist == NULL) cout << "None"; else cout << receptionist->get_name() << " " << receptionist->get_salary(); cout << "\nSecretary: "; if (secretary == NULL) cout << "None"; else if (secretary == receptionist) cout << "Same"; else cout << secretary->get_name() << " " << secretary->get_salary();
cout << "\n"; } int main() { Department shipping("Shipping"); Department qc("Quality Control"); Employee* harry = new Employee("Hacker, Harry", 45000); shipping.set_secretary(harry); Employee* tina = new Employee("Tester, Tina", 50000); qc.set_receptionist(tina); qc.set_secretary(tina); tina->set_salary(55000); shipping.print(); qc.print(); return 0; }

** Указатели и масиви, адресна аритметика. 
Името на масив е константен указател. 
int a[3] = {10, 20, 30}; 
int *pa = a; 

Адресна аритметика
Синтаксис: <указател> + <число>

 /* отпечатва 3 пъти стойността на a[0] */ 
cout << a[0] <<" "<< pa[0] <<" "<< *pa;

/* отпечатва 4 пъти стойността на a[1] */ 
cout << a[1] <<" "<< pa[1] <<" "<< *(pa+1) <<" "<<(pa+1)[0];

Адрес pa или a pa+1 или a+1 pa+2 или a+2
Стойност 10 20 30
Индекс 0 1 2
Достъп 1 a[0] a[1] a[2]
Достъп 2 *pa *(pa+1) *(pa+2)
Достъп 3 pa[0] (pa+1)[0] (pa+1)[1]
Достъп 4 *a *(a+1) *(a+2)


** Указатели и С-низове
* C++ наследява от езика C по-ниско ниво за работа с низове (С-низове), когато низовете са представени като масиви от char стойности.
* Не се препоръчва използването на С-низове, указатели и масиви в програмите на С++, но понякога е удобно да се използват функции, които получават или да връщат char*  стойности.
char s[] = "Harry"; 
/* is the same as */
char *s = "Harry";
*s *(s+1) *(s+2) *(s+3) *(s+4) *(s+5)

s[0]

s[1] s[2] s[3] s[4] s[5]
'H' 'a' 'r' 'r' 'y' '\0'

Превръщане на С-низове в "C++ низове" - тип char* в тип string.
char* p = "Harry";
string name(p); // or string name = p;
Превръщане на обекти от тип string в тип char*.
string num = "123";
const char* cnum = num.c_str();
int n = atoi(cnum);