Покажувачи

 

Типовите на податоци што се користат во програмирањето можат да бидат: 
 статички -  оние чија големина е однапред дефинирана (најчесто на почетокот на програмата). Тие се сместени на фиксни локации во меморијата
 динамички -  оние чија големина и/или структура се менува во текот на извршувањето на програмата. Тие не се сместуваат на фиксна локација во меморијата, туку на локација која во моментот на нивното креирање е слободна. 
Динамичките типови на податоци можат да бидат:
  со променлива големина (низа битови, низа знаци, општа низа, множество, куп, ред и датотека) 
 со променлива структура (листа, дрво, покажувач и објект). При преведувањето на програмата потребно е преведувачот да ги знае сите променливи и нивниот тип. Променливите кои се создаваат за време на извршување на програмата, а потоа се бришат се нарекуваат динамички променливи. 
Динамичките променливи кои добиваат податоци од тип покажувач се наречени променливи од тип покажувач или само покажувачи. При креирањето, динамичките променливи се сместуваат во слободната внатрешна (динамичка) меморија, наречена heap-меморија. Користењето на променливи од тип покажувач нуди две битни погодности во однос на меморијата: 
1. Се проширува меморискиот простор што може да се користи за податоци во една програма 
2. Со користење на покажувачките променливи во динамичката меморија, програмата може да се извршува со помала количина неопходна меморија. 
На пример, програмата може да поседува две многу сложени структури на податоци што не се користат истовремено. Ако овие две структури се декларираат како глобални променливи, тогаш тие се наоѓаат во сегментот за податоци и завземаат меморија за цело време на извршувањето на програмата, без оглед дали се користат во моментот или не. Но, ако се дефинирани преку покажувачи (динамички), тие се наоѓаат во динамичката меморија (heap) и ќе бидат избришани од неа по престанокот на нивното користење 



Кога дефинираме некоја променлива ( пример int a), таа има одредена мемориска вредност и сместена е на некое одредено место во меморијата, наречено мемориска локација.
Адресата всушност е бинарен запис за местоположбата на променливата. За приказ на адресата на променливата ја користиме &a. Адресите ќе бидат различни при секое стартување на програмот.

Покажувачите најпросто кажано  се типови податоци кои чуваат адреса. Се дефинираат на следниов начин
tip_promenliva *ime_pokazuvac;
* - ознака за покажувач;
tip_promenliva - типот на променливата на која покажувачот покажува, односно чија адреса ја памети
ime_pokazuvac име на покажувачот дадено од програмерот 

 Забелешка: важно е да се каже дека, при креирање на покажувачи (int *pok), позицијата на која се наоѓа знакот '*' нема никакво влијание на извршувањето на програмата. Имено, знакот '*' може да се наоѓа веднаш по податочниот тип (int* pok), помеѓу податочниот тип и името на покажувачот (int * pok) или до името на покажувачот (int *pok) - сите наредби ќе имаат ист ефект.

#include<iostream>
using namespace std;
int main()
{
int a = 5;
cout << &a << endl; // prikaz na adresata na promenliva a
int* pok_a;        // pokzauvac
pok_a = &a;     // ja smestuvame adresata na promenlivata a vo pokazuvac pok_a
cout << pok_a << endl; // prikaz na vrednosta na pokazuvacot, identicno so prethodniot prikaz
cout << *pok_a << endl; // prikaz na vrednosta na promenlivata na koja pokazuvacot pokazuvae
return 0;
}
Output 
0x7ffe795e1b6c  // razlicno e kaj sekogo 
0x7ffe795e1b6c
5

Aдресен оператор & (ampersand)
Операторот & се нарекува адресен оператор. Тој е унарен оператор, кој ја враќа адресата на операндот по него. 
 Пример: int y=5; int *yPok; yPok=&y; 
// на покажувачот yPok му се доделува адресата на променливата у 
 
Оператор за дереференцирање * asterisk

 Во претходниот случај операторот * се нарекува индиректен оператор или оператор за дереференцирање – тогаш тој ја враќа вредноста на променливата на која покажува неговиот операнд. 

#include <iostream>
using namespace std;
int main()
{
int a = 5; //promenliva 'a' so vrednost 5
int *b; //pokazhuvach kon podatok od tip int
b = &a; //b ja sodrzhi adresata na a ("b pokazhuva kon a")
/*
double *k;
k = &a; //GRESHKA: pogreshen tip na pokazhuvach (double)
*/
cout << a << endl; //pechati '5' (vrednosta na a)
cout << b << endl; //pechati '0x27ff44' (adresata na a)
return 0;
}

 
Забелешка: Со знакот '*' всушност пристапуваме до податок на одредена мемориска локација и, не само што може да ја видиме неговата вредност, туку истата можеме и да ја промениме!

Разлика помеѓу операторите '&' и '*' :
'&' се чита "адреса на" и ја враќа адресата на одреден податок.
 На пример, "&x" ја враќа адресата на x.
'*' се чита "вредноста покажувана од" и ја враќа вредноста која се чува на одредена локација. 
На пример, "*p" служи за пристап до вредноста покажувана од покажувачот p.

Еден покажувач може да се користи повеќе пати (во една иста програма) и притоа (во различни моменти) да покажува кон различни променливи и мемориски локации. Доколку сакаме експлицитно да изразиме дека одреден покажувач не покажува кон ништо, него може да му доделиме вредност 0 (т.н. NULL покажувач). Стандардот гарантира дека не постои податок во компјутерската меморија со адреса 0.

#include <iostream>
using namespace std;
int main()
{
int a = 5, b = 2;
int *pa = &a, *pb; //inicijalizacija
pb = &b; //pb pokazhuva na b
*pa = 3;
cout << a << " " << *pa << " " << *pb << endl; //pechati '3 3 2'
*pb = -1;
cout << b << " " << *pb << endl; //pechati '-1 -1'
*pa = *pb;
cout << a << " " << b << endl; //pechati '-1 -1'
pa = 0;
double c = 8.0123, d = 0.0000;
double *px;
px = &c;
(*px) += 2.0;
cout << (*px) << endl; //pechati '10.0123'
px = &d;
cout << (*px) << endl; //pechati '0'
return 0;
}

Големината на еден покажувач зависи од архитектурата на компјутерскиот систем на кој се извршува програмата и од оперативниот систем: 32-битен компјутерски систем ќе користи 32-битни мемориски адреси (и, соодветно, покажувачите ќе зафаќаат 4 бајти податочен простор), додека 64- битен компјутерски систем ќе користи 64-битни мемориски адреси (и, соодветно, покажувачите ќе зафаќаат 8 бајти податочен простор). Ова на никој начин не влијае на однесувањето на покажувачите: тие и понатаму можат да се искористат за пристап до податокот на кој покажуваат - без разлика на неговата големина



#include<iostream>
using namespace  std;
int main()
{
   int a = 5;
   int* pok_a = &a;
   cout <<"a = "<< a<< endl;// a = 5
   *pok_a = a + 2;
   cout << "a = "<<a<<endl; // a = 7
   return 0;
}

a = 5

a = 7



void покажувачи

         void покажувачите се покажувачи кои покажуваат кон вредност која нема тип , не е дефиниран тип

       Тоа овозможува void покажувачите да покажуваат кон секој тип на податок од  int вредност или  float до  string.

       Но сепак се многу ограничени :

податокот на кој покажуваат не може да биде директно дереференциран  и заради тоа секогаш треба да ја доделиме адресата на void покажувачот на некој покажувач од друг тип на покажувач кој покажува на некој конкретен тип на податок пред да го дереференцираме . 

int main()
{
int x=56, y;
int *p, *q;
float f, *r;
void *v; // ne pokazuva kon tip na podatok
// p=123; ovaa komanda ne moze
p=&x; cout<<p<<endl;
q=p;
q=p+5; cout<<q<<endl;
// v se dodeluva na drug tip pokazuvac
v=p; cout<<v<<endl;
// q=v; ovaa komanda ne moze
q=(int *)v; cout<<q<<endl;
*p=3.21; cout<<*p<<endl;
*p=321; cout<<*p<<endl;
r=&f; cout<<r<<endl;
*r=7.8; cout<<*r<<endl;
*r=*q; cout<<*r<<endl;
// не може да биде директно дереференциран
// *v=456; ovaa komanda ne moze 
 return 0;
 }

Алокација на меморија 

Кога меморијата се алоцира статички, тоа значи дека однапред се знае колку меморија ќе зафаќаат декларираните променливи.
Кога не е позната големината на меморија потребна за зачувување на променливи, истата се алоцира за време на извршување на   програмата. Меморијата динамички се алоцира со помош на операторот new. 
Соодветно, доколку е потребно да се креира низа од N елементи, каде што N не може да се специфицира пред самото извршување на програмата, тогаш решението на овој проблем е со т.н. динамичко алоцирање на меморија.
 Во C++, тоа се прави со користење на операторите
  new (доколку сакаме да алоцираме простор за само еден елемент) или
  new[N] (доколку сакаме да алоцираме простор за N елементи). 

Резултатот од овие операции е покажувач до блокот меморија кој бил резервиран. 
Многу е важно, по користењето на резервираната меморија (откако истата повеќе не е потребна), да се направи нејзино бришење. 
Во C++, ова се прави со користење на операторите
 delete (за еден елемент) и 
delete[] (за повеќе елементи). 

 Наредбата new има форма : покажувач= new тип на податок; 
 
Со неа во меморијата се креира променлива од соодветен тип и на покажувач му се доделува адресата на креираната променлива. Во спротивно покажувачот добива вредност NULL. 

Пример, ако е деклариран покажувач int *ip; тогаш со наредбата ip=new int; во меморијата се креира неименувана променлива и нејзината адреса му се доделува на покажувачот ip, а со delete ip; се ослободува меморијата на променливата на која покажувал покажувачот ip, при што тој останува недефиниран.

#include <iostream>
using namespace std;
int main()
{
int *parr;
parr = new int;
*parr = 2;
cout << *parr << endl; //pechati '2'
delete parr; //MNOGU VAZHNO!!!
int N = 100;
parr = new int[N]; //niza od 100 elementi
parr[5] = 3;
cout << parr[5] << endl; //pechati '3'
delete [] parr; //MNOGU VAZHNO!!!
return 0;
}



Pointer Arithmetic

We know that a pointer variable always points to the address in memory. Among the operations that we can perform, we have the following arithmetic operations that are carried out on pointers.

  • Increment operator (++)
  • Decrement operator (- -)
  • Addition (+)
  • Subtraction (-)
#include <iostream>
#include <string>
using namespace std;
 
int main()
 {
   int myarray[5] = {2, 4,6, 8,10};
   int* myptr;
   myptr = myarray;
   cout<<"First element in the array :"<<*myptr<<endl;
   myptr ++;
   cout<<"next element in the array :"<<*myptr<<endl;
   myptr +=1;
   cout<<"next element in the array :"<<*myptr<<endl;
   myptr--;
   cout<<"next element in the array :"<<*myptr<<endl;
   myptr -= 1;
   cout<<"next element in the array :"<<*myptr<<endl;
  
   return 0;
 }

Output:

First element in the array :2
next element in the array :4
next element in the array :6
next element in the array :4
next element in the array :2

#include <string>
#include <iostream>
using namespace std;

void pointerarithmetic(int a[], int size)
{
int *e, *t; //Declaring two int pointers variables
e = a; //assigning e to point the arrays initial element a[0]
t = a + size; // assigning variable t to the array last element
while(e != t)
{
cout << *e << " "; //displays the e
e++; // incrementing ( next element)
}
}

int main()
{
int a[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20};
pointerarithmetic (a, 15);
return 0;
}
Output
2  4  6  8  10  12  14  16  18  20  -350012160  -2088340161  0  0  404553859 







Низи  и покажувачи

Кога креираме низа со одредена големина, името на променливата која ја користиме за пристап до елементите на таа низа е покажувач до првиот елемент на низата.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;
int main()
{
int array[] = {1, 2, 3, 4, 5};
cout << *array << endl; //pechati '1'
*array = 0; //sega, array[] = {0, 2, 3, 4, 5}
int *parr;
parr = array;
cout << *parr << endl; //pechati '0'
return 0;
}
Во C++, овозможена е употреба на т.н. аритметика со покажувачи. Имено, секој покажувач во C++ е дефиниран со соодветен тип на податок кон кој покажува, и истиот може да се промени на начин што во иднина ќе покажува на некој претходен или следен податок во меморијата. Ова се прави со едноставно користење на операторите + и -.

На пример, доколку array покажува на првиот елемент од одредена низа, тогаш array+1 покажува на вториот елемент од низата, array+2 покажува на третиот елемент од низата, итн.

Печатење неколку елементи од низата array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
int main()
{
int array[] = {1, 2, 3, 4, 5, 6, 7};
int *parr = array;
//kje ispechati '2 3 4 5 6 '
for (int x=0; x<5; x++)
{
parr = parr+1; //parr kje pokazhuva na sledniot element
cout << (*parr) << " ";
}
parr--; //parr kje pokazhuva na prethodniot element
cout << (*parr) << endl; //kje ispechati '5'
parr = parr - 2;
cout << (*parr) << endl; //kje ispechati '3'
return 0;
}
Креирање на низа во динамичка меморија со помош на покажувач
 Името на низата може да се идентификува со покажувачка константа на првиот член на низата. Значи, за низата а важи:
 a ≡ &a[0] 
Бидејќи елементите се наоѓаат на последователни мемориски локации,
 a + 1 ≡ &a[1] 
општо a + i ≡ &a[i] 
Кога на изразот ќе го примениме операторот за дереференцирање добиваме: 
*( a + i) ≡ a[i] 
Името на низата е адреса на почетниот елемент на низата и сама по себе е покажувач (статички покажувач 

int a[5]={0};
int *p, *q;
а е статичка променлива. Не е дозволено а=а+2 или а++.
р е динамичка променлива. Дозволено е p=p+2; или p++; 
Пристап и печатење на елементите на еднодимензионална низа со покажувач:
#include <iostream>
using namespace std;
int main()
{
int x[5] = {1,2,3,4,5};
int *p;
p = x;
cout << x[0]<<x[1]<<x[2]<< endl;
cout << *p<< *(p+1)<<*(p+2)<< endl;
return 0;
}
#include <iostream>
using namespace std;

int main( )
{
   int x[5] = {3,2,6,4,5};
   int *p;
   p = x;
   //x=x+1; не е дозволено
  *x=*x+3; // vrednosta na prviot element ( 3 ) se sobira so 3
  cout<<*x<<endl;     // 6
  cout<<*x+1<<endl;   // 7
  cout<<*(x+1)<<endl; // 2 pristap do vtoriot element
  p=p+1;
  cout <<*x<<*(x+1)<<*(x+2)<< endl; // 626
// pristap do vtoriot element, pristap do tretiot element
  cout <<*x<<*(x+2)<<*(x+4)<< endl; // 665
// pristap do tretiot element, pristap do pettiot element
  cout << *p<< *(p+1)<<*(p+2)<< endl; // 264

  return 0;
}

#include<iostream>
using namespace std;
int main()
{
int niza[100] = { 2,3,5,4,6 }, n = 5;
int* pok;
for (pok = niza; pok < niza + n; pok++)
{ cout << *pok << " ";
}
cout << endl;
return 0;
}


#include <iostream>
#include <string>
using namespace std;
 
int main()
 {
   int myarray[5] = {1, 1, 2, 3, 5};
   int* ptrvar;
   ptrvar = myarray;
  
   for(int i=0;i<5;i++)
     {
       cout<<*ptrvar<<"\t";
       ptrvar++;
     }
      return 0;
 }

Output:

1             1             2              3              5

 Array of pointers

#include <iostream>
#include <string>
using namespace std;
 
int main()
{
   int myarray[5] = {2,4,6,8,10};
   int *ptr[5]; //array of pointers
   for(int i=0;i<5;i++){
     ptr[i] = &myarray[i];
   }
  
   for (int i = 0; i < 5; i++)
   {
     cout << "Value of myarray[" << i << "] = ";
     cout << *ptr[i] << endl;
   }
  
   return 0;
}

Output:

Value of myarray[0] = 2
Value of myarray[1] = 4
Value of myarray[2] = 6
Value of myarray[3] = 8
Value of myarray[4] = 10

//Dekrementing pointer
#include <iostream>
using namespace std;
const int MAX = 3;

int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// let us have address of the last element in pointer.
ptr = &var[MAX-1];
cout<<ptr<<endl<< " "<<*ptr<<endl;
for (int i = MAX; i > 0; i--)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;
cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;
// point to the previous location
ptr--;
}
return 0;
}
//Incrementing pointer
#include <iostream>
using namespace std;
const int MAX = 3;

int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;
// let us have array address in pointer.
ptr = var;
cout<<ptr<<" "<<*ptr<<endl;
for (int i = 0; i < MAX; i++)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;
cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;
// point to the next location
ptr++;
}
return 0;
}

Направи разлика помеѓу следните кодови 




Покажувачи и низи ( Филип Вичороски )