11. Указатели

• Дефиниране на указател
• Действия с указатели
• Указатели и масиви
• Указатели и низове
• Указатели и структури
• Примери и задачи

преди план следва
 
 

11. Указатели


Всеки байт от ОП има адрес. Адресът е цяло неотрицателно число. Във всеки байт може да се съхрани една данна от тип char. За данни от другите типове се заделят по няколко байта. Например за тип double се заделят 8 байта. Адресът на един обект от тип double е адресът на първия байт от тези 8 байта. Указателите дават алтернативен начин за достъп до паметта. Те са променливи, които могат да съдържат като стойност само адреси от паметта / т.е. цели неотрицателни числа/. От съображения за сигурност обаче указателите са строго разграничени от целите числа. Строго се разграничават и указателите от различни типове, например указател, съдържащ адреса на данна от тип int и указател, съдържащ адреса на данна от тип double.

• Дефиниране на указател

Променливата указател се дефинира по следния начин:

тип * променлива_указател;

В тази дефиниция * /звездичката/ се поставя пред всяка променлива указател:

double * ptr1;
int * p1, *p2;

В примера p1 и p2 са указатели, които могат да приемат като стойност само адреси на данни от тип int, докато ptr1 е указател, който може да приемат само адреси на данни от тип double. Казва се още, че p1 и p2 сочат към обекти от тип int, а ptr сочи към обекти от тип double.

• Действия с указатели

Има две специални унарни /едноместни/ действия, които се използват с указатели: & и *. Приоритетът на действията & и * е еднакъв с приоритета на унарните действия /+, -, ++, --, ! /. Изпълнението им се извършва отдясно на ляво.

Действие & :
&операнд

Операндът трябва да бъде променлива. Операндът не може да бъде израз. Резултатът от действието & е адресът на операнда.

Действие * :
* операнд

Тук операнд може да бъде само указател или израз, в който участва указател. Действието * позволява да се намери стойността на обект, чийто адрес се съдържа в указателя.
Пример:
#include < iostream.h >
int main()
{
	int * p;
	int x = 10;
	p = & x;
	cout<<" x = " << *p<< endl;     // извежда  10
	*p = *p +1;                     // увеличава x с 1
	cout<<" x = " <<  x << endl;    // извежда 11
	return 0;
}
Забележки:
- ако V е правилно дефинирана променлива, * & V и V са равностойни записи;
- не трябва да се смесва операцията * , връщаща стойността на обект сочен от указател, със знака *, означаващ действие умножение. Двете действия трябва да се разграничават според контекста;
- често срещана грешка е използването на указател преди да е бил инициализиран. Като всяка друга променлива, в този случай указателят има неопределена стойност;
- указателят NULL е константен указател. NULL се използва за задаване стойност на указател, който не сочи никакъв обект. По този начин се нулира променливата указател. Указателят NULL е дефиниран във файла iostream.h.

Примери:
float  x, y, *px, *px1;	
px = & x;                 // px получава адреса на  x	  
y  = *px;                 // y = x						
px1 = &*px;               // px1 = px							  
y = *& x;	          // y = x	
Освен операциите & и *, с указателите могат да се извършват и действията + , -, ++, --. Унарните действия ++ и - - предизвикват увеличаване или намаляване на адреса, съдържащ се в указателя. Особеното в този случай е, че увеличението и намалението не е точно с 1, а с толкова байта, колкото заема в паметта обектът, към който сочи указателят. С други думи, автоматично се изчислява необходимия брой байтове, които се добавят или изваждат в зависимост от типа на данните и размера им в брой байтове.
Пример:
#include < iostream.h >
 int main()
{
	double *p, r;
	r = 3.456;
	p = & r;               // p взема адреса на  r
	cout<<*p<< endl;      // извежда 3.456
	cout<< p<< endl;      // извежда адреса на  r
	cout<<& p<< endl;      // извежда адреса на указателя  p
	cout<<++p<< endl;     // извежда адреса на r увеличен с 8
	cout<<--p<< endl;     // извежда адреса на  r
	cout<<*p;             // извежда 3.456
return 0; 
}
Когато действието & се приложи към операнд, представляващ указател, напр. & p, се извлича адресът /началният байт/, от който нататък се разполага в паметта самият указател. След дефинирането на променлива указател за нея се заделят 4 байта от паметта. Стойността, която се извежда от инструкцията
cout<< & p << endl;
е адресът на указателя p. Трябва да се направи разлика между адреса на указателя и стойността на указателя, която също е адрес на обект.
По аналогичен начин могат да се използват и действията + и - / събиране и изваждане/:
float *ptr, *p, x=3;
ptr = & x;
ptr = ptr + 1;    // адресът в  ptr се увеличава с 4
p = ptr – 1;
cout<< ptr << endl;
p = ptr - 1;      // адресът в p се намалява с 4
cout<< p;
Указателите могат да се сравняват чрез знаците за сравнение >, >= , < , <=, ==, !=.
Ако указателите сочат към данни от различни типове, инструкцията за присвояване между тях предизвиква грешка:
int x, *p1;
double  *p2;
p1=& x;
p2=p1;      // този код предизвиква грешка
В някои случаи е необходимо един указател да бъде съвместим с указатели от различни типове. В такъв случай може да се дефинира указател от тип void. Указателят от тип void може в различни моменти от изпълнението на програмата да сочи към данни от различни типове. Дефинирането на указател от тип void се извършва по следния начин: void * p;
 
 
 
 
преди план следва