Лекции по увод в програмирането



страница14/16
Дата25.07.2016
Размер0.96 Mb.
#5224
ТипЛекции
1   ...   8   9   10   11   12   13   14   15   16

5 декември

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 сочи към адреса на първия елемент на константния низ;





Сподели с приятели:
1   ...   8   9   10   11   12   13   14   15   16




©obuch.info 2024
отнасят до администрацията

    Начална страница