2. Указатели

** Дялове в оперативната памет при изпълнение на програма на С++. 
 

CODE 
транслираната програма 
(от exe файла)
DATA 
глобални данни
STACK 
локални променливи и константи 
имена на извикани функции
HEAP
Динамична памет

** Тип указател - съхранява адрес от оперативната памет на компютъра. 
* Дефиниране на променлива тип указател: 
синтаксис: <име на тип> * <име на променлива>
Employee *boss; 
int *pn;

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

new <име на тип>
- запазва място за една променлива от зададения тип 
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) - унарна операция *: 
синтаксис: *<указател>
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 е грешка!

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

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

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

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

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

* Операция адрес на променлива или обект &. 
синтаксис: & <име на променлива>

* Указатели в системния стек (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 = a;
}

void swap2(int *a, int *b) // разменя стойностите на променливите с адреси указателите a и b
{
    int w = *a;
    *a = *b;
    *b = *a;
}
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);