Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Операції з вказівниками
Мова Сі надає можливість використання адрес змінних програми за допомогою основних операцій - & та *: За допомогою основних операцій можна отримати значення адреси змінної а використовуючи непряму адресацію - одержати значення змінної за її адресою. Призначення цих операцій: & ім'я змінної - одержання адреси; визначає адресу розміщення значення змінної визначеного типу; * ім'я-вказівника - отримання значення визначеного типу за вказаною адресою; визначає вміст змінної, розміщеної за адресою, що міститься у даному вказівнику; це - непряма адресація (інші назви -" зняття значення за вказівником" або " розіменування"). Оператор присвоювання значення адреси вказівнику має вигляд: Ім'я_змінної_вказівника = & ім'я змінної; Наприклад: int i, *pi; /* pi –змінна-вказівник */ pi = & i; /* pi одержує значення адреси 'i' */ Операція & - визначення адреси змінної повертає адресу ОП свого операнда. Операндом операції & повинне бути ім'я змінної того ж типу, для якого визначений вказівник лівої частини оператора присвоювання, що одержує значення цієї адреси. У вищенаведеному прикладі це тип int. Операції * і & можна писати впритул до імені операнда або через пробіл. Наприклад: & і, * pi. Непряма адресація змінної за допомогою операції * здійснює доступ до змінної за вказівником, тобто повернення значення змінної, розташованої за адресою, що міститься у вказівнику. Операнд операції * обов'язково повинен бути типу вказівник. Результат операції * - це значення, на яке вказує (адресує, посилається) операнд. Тип результату - це тип, визначений при оголошенні вказівника. У загальному вигляді оператор присвоювання, що використовує ім'я вказівника та операцію непрямої адресації, можна представити у вигляді: ім'я змінної * ім'я-вказівник; де ім'я-вказівника - це змінна або константа, що містить адресу розміщення значення, необхідного для змінної лівої частини оператора присвоювання. Наприклад: i= *pi; /* 'i' одержує значення, розташоване за адресою, що міститься в вказівник 'pi' */ Як і будь-які змінні, змінна pi типу вказівник має адресу і значення. Операція & над змінною типу вказівник: & pi - дає адресу місця розташування самого вказівника, pi - ім'я вказівника визначає його значення, a *pi - значення змінної, що адресує вказівник. Звичайно, усі ці значення можна надрукувати. Наприклад, за допомогою наступної програми: Приклад 20 #include < windows.h> #include < clocale> #include < stdio.h> void main() { setlocale (LC_CTYPE, " rus"); char c = 'A'; int i = 7776; int *pi = & i; char *pc = & c; printf (" pi=%u, *pi=%d, & pi=%u\n", pi, *pi, & pi); printf (" pc=%u, *pc=%c, & pc=%u\n", pc, *pc, & pc); system(" pause"); } У результаті виконання буде виведено:
Одне з основних співвідношень при роботі з вказівниками - це симетричність операцій адресації та непрямої адресації. Вона полягає в тому, що: & х == х, тобто вміст за адресою змінної х є значення х. Наприклад, оголошення вказівника pi і змінних i та j: int *pi, i = 123, j; pi = & i; /*-присвоювання вказівнику значення адреси i */ j = *pi; /* - присвоювання j вмісту за адресою pi */ Тут змінна j отримує вміст, розташований за адресою змінної i, тобто значення змінної, що адресує вказівник pi: j = * pi = * & i = i;. Два останніх вищенаведених оператора виконують те саме, що один оператор: j = i. Для повного остаточного розуміння процесів, що відбувається у пам'яті при маніпуляції з вказівниками, розглянемо ще такий фрагмент: Приклад 21 #include < windows.h> #include < clocale> #include < stdio.h> void main() { setlocale (LC_CTYPE, " rus"); int x=5; int *px; /* px - вказівник на змінну типу int*/ px= & x; /* адреса змінної x заноситься в px*/ *px=77; /* число зберігається за адресою, на яку вказує px */ printf (" Адреса комiрки х=%u\n", px); printf (" Значення комiрки х=%d\n", x); system(" pause"); } У результаті виконання буде виведено:
Розглянемо цей приклад на конкретному малюнку: функція займає область пам'яті, починаючи з адреси 0x100, х знаходиться за адресою 0x102, а рх - 0x106. Тоді перша операція присвоювання, коли значення & х(0х102) зберігається в рх, матиме вигляд, зображений на рис. 11 зліва: Наступну операцію, коли число 77 записується за адресою, яка знаходиться в рх та дорівнює 0х102 (адреса х), відображає рис. 1 справа. Запис *рх надає доступ до вмісту комірки, на яку вказує рх. Рис. 1.Схематичне представлення значень в ОП Далі наведений приклад програми виводу значень вказівника вмісту, розташованого за адресою, що він зберігає. Приклад 22 #include < windows.h> #include < clocale> #include < stdio.h> void main() { setlocale (LC_CTYPE, " rus"); int і = 123, *pi=& і; // pi-вказівник на значення типу int printf(" розмiр змiнної i = %d\n", sizeof(і)); printf(" розмiр вказiвника pi = %d\n", sizeof(pi)); printf(" адреса розмiщення вказiвника pi=%u\n", & pi); printf(" адреса змiнної i = %u\n", & і); printf(" значення вказiвника pi = %u\n", pi); printf(" значення за адресою pi = %d\n", *pi); printf(" значення змiнної i = %d\n", і); system(" pause"); } Результати виконання програми: Вказівники можна використовувати: 1. у виразах, наприклад, для одержання значень, розташованих за адресою, що зберігається у вказівнику; 2. у лівій частині операторів присвоювання, наприклад: a. для одержання значення адреси, за якою розташоване значення змінної; b. для одержання значення змінної. Наприклад, якщо pi - вказівник цілого значення (змінної і), то *pi можна використовувати в будь-якому місці програми, де можна використовувати значення цілого типу. Наприклад: int i = 123, j, *pi; pi = & i; /*pi у лівій частині оператора присвоювання */ j = *pi + 1; /*-це еквівалентно: j = i + 1; pi-у виразі правої частини оператора присвоювання*/ Виклик значення за вказівником можна використовувати також як фактичні параметри при звертанні до функцій. Наприклад: d = sqrt ((double) *pi); /* *pi - фактичний параметр */ fscant (f, " %d", pi); /* pi - фактичний параметр */ printf (" %d\n", *pi); /* *pi - фактичний параметр */ У виразах унарні операції & і *, пов'язані з вказівниками, мають більший пріоритет, ніж арифметичні. Наприклад: *рх = & х; у = 1 + *рх; /*-спочатку виконується '*', потім '+' */ Останній оператор еквівалентний: у = 1 + х; Для звертання до значення за допомогою вказівника-змінної його можна використовувати в операторі присвоювання скрізь, де може бути ім'я змінної. Наприклад, після виконання оператора: рх = & х; цілком еквівалентними є такі описи:
Наступна програма демонструє найпростіше практичне використання покажчиків, виводячи звичайну послідовність літер алфавіту: Приклад 23 #include < windows.h> #include < clocale> #include < stdio.h> int main() { setlocale (LC_CTYPE, " rus"); char c; /* змінна символьного типу*/ char *pc; /* вказівник на змінну символьного типу*/ pc=& c; for(c='A'; c< ='Z'; c++) printf(" %c", *pc); printf(" \n"); system(" pause"); return 0; } Результати виконання програми:
У операторі printf(" %c", *pc) має місце розіменування вказівника (*рс) - передача у функцію значення, що зберігається за адресою, яка міститься у змінній рс. З вказівниками допускається виконувати такі операції: 1) розіменування * 2) присвоювання; 3) арифметичні операції (додавання з цілочисельною константою, вирахування, інкремент, декремент); 4) порівняння; 5) приведення типів 6) одержання адреси & Операція розіменування (розадресація) призначена для доступу до величини, адреса якої зберігається у вказівнику. Приклад int *x= new int; *x=12; Приклад 24 #include < windows.h> #include < clocale> #include < stdio.h> void main() { setlocale (LC_CTYPE, " rus"); // оголошення змінних: int *x= new int; int *po; // задаємо значення: *x=12; // вивід значення *x: printf(" *x дорiвнює: %d\n", *x); po=x; // змінюємо значення через po: *po = 25; // на доказ того, що значення змінилось виведемо вміст пам’яті знову: printf(" *x дорiвнює: %d\n", *x); system(" pause"); } Скомпілюйте наведений приклад. Арифметичні операції з вказівниками застосовні тільки до вказівників одного типу і використовуються при роботі з даними, послідовно розміщеними в оперативній пам'яті (масивами). Ці операції автоматично враховують розмір типу величин, що адресуються вказівниками. Якщо до вказівника додають або віднімають константу, то його значення змінюється на величину константи помножену на розмір типу що адресується (sizeof(тип)). Різниця двох вказівників – це різниця їхніх значень, ділена на розмір типу в байтах. Підсумовування вказівників не допускається. Інкремент або декремент вказівника змінює його на величину sizeof(тип). При роботі з масивами це означає переміщення на наступний або попередній елемент. Приклад 25 #include < windows.h> #include < clocale> #include < iostream> #include < conio.h> void main() { setlocale (LC_CTYPE, " rus"); int const n=8; //Число елементів у масиві float x[n]={6, -8, 3, 2.4, -7.1, -5, -9, 71}; float *px; px=x; //установка вказівника на початок масиву x std:: cout< < " Масив X" < < std:: endl; for (int і=0; і< n; і++) { std:: cout< < *px< < " "; px++; //інкремент вказівника } std:: cout< < std:: endl; system(" pause"); } Скомпілюйте наведений приклад. Використовуючи оператор new можна провести відразу ініціалізацію заданим значенням int* i=new int (0); //ініціалізація зміної 0 int* i=new int [10] (17); // ініціалізація масиву значенням 17 Приклад 26 #include < windows.h> #include < clocale> #include < iostream> #include < conio.h> void main() { setlocale (LC_CTYPE, " rus"); int* x=new int [10] (17); // ініціалізація масиву значенням 17 std:: cout< < " Масив X" < < std:: endl; for (int і=0; і< 10; і++) { std:: cout< < *x< < " "; x++; //інкремент вказівника } std:: cout< < std:: endl; delete x; system(" pause"); } Звільняється пам'ять в купі оператором delete (або delete[] для масивів). Зауважимо, що використання оператора delete[] для знищення динамічних масивів за явним вказуванням розміру в дужках було необхідним для ранніх версій С++-компіляторів. Сучасні компілятори позбавлені цього недоліку. Проте у випадку складних динамічних багатомірних структур, коли елементами масиву є конструкції, які у свою чергу мають власні конструктори та деструктори, лише оператор delete[] правильно звільняє пам’ять. В цьому випадку стає необхідним правильний виклк деструкторів елементів масиву, а оператор delete цього може не зробити. Операції по виділенню динамічноі пам'яті можуть призводити до ситуації, яка називається кровотечею пам'яті (memory bleeding) і визначається наявністю недоступних динамічних об'єктів. Ці ситуації породжуються переприсвоєнням динамічним вказівникам адрес нових динамічних змінних, при тому, що не були знищені попередні. Тому треба стого відслідковувати усі динамічні змінні, а у випадку їх невикористання - одразу знищувати. Операція виділення пам'яті за допомогою оператора new повертає вказівнику адресу блоку і встановлює вказівник на початок блоку int* j; j=new int; // або j=new int[10]; // або int* j=new int[10];
|