25. Указатели. Основни операции.
Указателят е променлива, чиято стойност е адрес от паметта;
Синтаксис:
име_на_тип *име_на_указател;
указателите са елементи от езика, които са характерни за асемблерните езици; те позволяват да се работи с адресите на променливите, като не е нужно да се слиза на най-ниското машинно ниво, както е при асемблерните езици; дефинираният тип за указател трябва да съответства на типа на обекта, към който сочи указателя; ако това изискване се наруши, компилаторът дава предупреждение; възможно е чрез явно преобразуване на типовете да се укаже на компилатора, че това се налага и в такъв случай той няма да даде предупреждение;
Примери:
int *p; float *p1; double z, *p2;
след като един указател е дефиниран той съдържа произволна стойност и може да бъде използват след като бъде инициализиран;
Примери:
p = 0; p = NULL;
p = (int *) 1507;
Нулата (или макрос NULL от stdio.h) означава нулев адрес; няма част от паметта с нулев адрес, той се използва за указване, че указателят не сочи към адрес; нулевият адрес има и други приложения – може да се използва като знак за край или признак за грешка и др.;
изразът (int *) 1507 представлява абсолютен адрес в паметта с
номер 1507;
има две основни едноместни операции за указатели;
-
&операнд – определя се адресът на операнда; уточнение – всеки байт в паметта си има адрес; ако операндът съдържа повече от един байт, тогава адресът му е адресът на най-младшия байт (този който има най-малък адрес); операндът може да е променлива или елемент от масив;
-
*операнд – в общият случай операндът е израз, чиято стойност е адрес; тази операция определя стойността, която е записана на адреса, който е резултат от пресмятането на операнда; в случай, че операндът на * е адрес на променлива, тогава резултатът е променливата, записана в този адрес;
и двете операции имат приоритет 2 и са дясно-асоциативни;
Примери:
float x, y, *px, *py;
x = 3.14; // x си присвоява 3.14
px = &x; // px си присвоява адресa на x;
y = *px + 10; // y си присвоява x + 10;
py = px; // py си присвоява адресa на x;
*py = 1; //x си присвоява 1;
*px – осигурява косвен достъп към x;
x – осигурява пряк достъп към x;
float x, *px;
unsigned z;
x = 3.14; px = &x;
z = px; //компилаторът дава предупреждение;
z = (unsigned) px; //компилаторът не дава предупреждение;
и в двата случая z ще си присвои стойността на адреса на променливата x;
float y, x, *px;
x = 3.14; px = &x;
y = ++*px; //и двете операции имат приоритет 2 и са дясно-
// асоциативни;
след изпълнението: x = 4.14; y = 4.14;
float y, x, *px;
x = 3.14; px = &x;
y = (*px)--; //и двете операции имат приоритет 2 и са дясно-
// асоциативни, затова поставяме скоби;
след изпълнението: x = 2.14; y = 3.14;
float x, y;
int *px, *py;
px = &x; //предупреждение от компилатора, тъй като се разминават
// типовете на указателя и на променливата;
y = *px; //първите два байта на x, интерпретирани като int се
//присвояват на y;
Често допускани грешки:
float x, y, *py;
*py = x; // преди да се използва един указател, той трябва да сочи
// към някакъв адрес;
py = y; // на указателя се присвоява променливата, вместо адреса на
// променливата;
const int initial = 100;
int *ptr;
ptr = &initial; - това не се позволява, тъй като ако е разрешено, по косвен начин ще можем да променяме именувана константа;
можем да дефинираме указател към константа по следния начин:
const int *ptr;
ptr = &initial;
това е вярно, тъй като не се позволява да се промени *ptr;
int k = 1;
ptr = &k; - възможно е, но чрез *ptr не може да се промени стойността на k;
можем да дефинираме указател константа;
int *const ptr1 = &k;
в този случай можем да променяме *ptr, което е променливата k, но не можем да променяме адресът, към който сочи ptr;
можем да дефинираме указател, който е константа и сочи към константа;
const int *const ptr3 = &initial;
тогава ptr3 не може да се променя адреса, към който сочи ptr3, нито може да се променя *ptr, което е константата initial;
26. Адресна аритметика.
При адресната аритметика са валидни единствено операциите събиране и изваждане на цяло число от указател; при тези условия могат да се използват ++, --, +=, -=;
При добавянето (изваждането) на цяло число към (от) указател, всяка единица от числото е равна на броят на байтовете, отделени в паметта за дадения тип на елемента, към който сочи указателя;
Пример:
int x, *px, *py;
px = &x;
px++; //адресът, към който сочи px се увеличава с два байта и px
//сочи към следващите два байта;
px = px + 4; //адресът, към който сочи px се увеличава с осем байта;
py = px – 1; // py си присвоява стойността на px, намалена с два
// байта;
10 декември
*px++;//първо се извлича стойността сочена от p, а после се
//увеличава указателят p да сочи към следващия елемент;
*--px;//първо се намаля указателят p да сочи към предишния елемент и след това се извлича неговата стойност;
могат да се сравняват указатели; px < py е истина, ако px сочи към елемент, който в паметта е разположен преди елемента към който сочи py; указател може да се сравнява с нулевия указател;
в случай, че два указателя сочат към променливи, които са свързани (например елементите на масив), разликата между двата указателя е цяло число със знак равно по абсолютна стойност на броя на обектите от типа на указателя, които могат да се разположат в паметта между адресите, съхранявани от указателите;
сравняването и разликата имат смисъл единствено, когато променливите към които сочат указателите са свързани (например са елементи на един масив);
Пример:
Реализация на стек:
push – функция, която добавя число към стека;
pop – функция, която извлича число от стека;
ще използваме функции за разпределяне и освобождаване на памет;
void *malloc (size_t size);
прототипа на тази функция се намира във alloc.h, size_t е целочислен тип, дефиниран в alloc.h; функцията malloc разпределя блок динамична памет, който има size байта; функцията връща указател към първия (най-малкия по адрес) елемент от блока;
тип void e родов тип – на указател от тип void може да бъде присвоена стойност на указател от произволен тип; в ANSI C и обратното е възможно, а в C++ е необходимо явно преобразуване;
ако няма памет malloc връща нулев указател;
пример:
void *gp; int *ip;
gp = ip;
ip = gp; в ANSI C
ip = (int *) gp; в C++
void free (void *block);
прототипа се намира в alloc.h; free приема като фактически параметър указател към блок и освобождава заетата от него памет; за блока преди това трябва да е разпределена памет с функцията malloc (има и други функции);
#include
#include
void push (int);
int pop();
int *pmin, *p;
void main ()
{ pmin = (int *) malloc (20);
if (pmin==NULL)
{ printf (“Наличната памет е недостатъчна!”);
return;
}
p = pmin;
push (10);
push (20);
printf (“2-рото число е: %d\n“, pop() );
printf (“1-рото число е: %d\n“, pop() );
free (pmin);
}
void push (int i)
{ if (p==pmin+10)
{ printf (“Препълване на стека!\n”);
return;
}
*p = i;
p++;
}
int pop()
{ if (p==pmin)
{ printf (“Стекът е празен!\n”);
return 0;
}
p--;
return *p;
}
указателят p сочи към елемента, които е точно над стека, указателят pmin към дъното на стека;
в C++ има и други функции, които могат да разпределят памет;
например със средствата на обектното програмиране функциите
new за разпределяне и delete за освобождаване;
Важно: за всяка функция, която разпределя памет има точно определена функция с която се освобождава тази памет; например памет разпределена с malloc не може да бъде освободена с delete;
27. Едномерни масиви и указатели.
Указател към масив се дефинира по общоприетите правила, като се спазва изискването за еднаквост на типа на указателя и типа на масива, към който сочи указателя;
float array[20];
float *pa;
името на масива се разглежда като константа, чиято стойност е адресът на първия елемент на масива;
pa = array; pa = &array[0];
тъй като елементите на масива са разположени в последователни полета в паметта, следните изрази са еквивалентни:
&array[i] array + i pa + i
array[i] *(array + i) *(pa + i), където i е някакъв индекс (0 i < 20);
на практика компилаторът заменя ползването на индекси със събиране на указатели;
езикът C дава два начина за обръщение към елементите на масив:
-
чрез индекси
-
чрез указатели
използването на указатели е по-бързият начин;
в езика C няма операции, които да обработват низове като цяло, но за сметка на това в C има много стандартни функции, които позволяват да се извършват различни операции с низове; тези низове се задават като указатели към низ (към първия символ на низа);
char *ps;
ps = “Това е низ!”; след тази инициализация ps сочи към адреса на първия елемент на константния низ;
Сподели с приятели: |