На головну

Хід роботи

  1. D.2. Режими роботи ліній передач
  2. I. допоміжних роботах.
  3. I. Завдання для самостійної роботи
  4. I. Створення ініціалів і визначення стажу роботи
  5. I. СТРУКТУРА КУРСОВОЇ РОБОТИ
  6. I. ЦІЛІ ВИПУСКНИЙ КВАЛІФІКАЦІЙНОЇ РОБОТИ
  7. I. Мета та завдання КУРСОВОЇ РОБОТИ

Основні відомості про покажчики в мові C ++.Коли компілятор обробляє оператор визначення змінної, наприклад, int i = 10 ;, він виділяє пам'ять відповідно до типу (int) і ініціалізує її вказаним значенням (10). Всі звернення в програмі до змінної по її імені (i) замінюються компілятором на адресу області пам'яті, в якій зберігається значення змінної. Програміст може визначити власні змінні для зберігання адрес областей пам'яті. Такі змінні називаються вказівниками.

Покажчики призначені для зберігання адрес областей пам'яті. У C ++ розрізняють три види вказівників - покажчики на об'єкт, на функцію і на void, що відрізняються властивостями і набором припустимих операцій. Покажчик не є самостійним типом, він завжди пов'язаний з будь-яким іншим конкретним типом.

Покажчик на функцію містить адресу в сегменті коду, за яким розташовується виконуваний код функції, тобто адресу, за якою передається керування при виконанні функції. Покажчики на функції використовуються для непрямого виклику функції (не через її ім'я, а через звернення до змінної, що зберігає її адресу) а також для передачі імені функції в іншу функцію як параметр. Покажчик функції має тип «покажчик функції, що повертає значення заданого типу і має аргументи заданого типу»:

тип (* ім'я) (спісок_тіпов_аргументов);

Наприклад, оголошення:

int (* fun) (double, double);

задає покажчик з ім'ям fun на функцію, що повертає значення типу int і має два аргументи типу double.

Покажчик на об'єкт містить адресу області пам'яті, в якій зберігаються дані певного типу (основного або складеного). Найпростіше оголошення покажчика на об'єкт (в подальшому просто покажчиком) має вигляд:

тип * ім'я;

де тип може бути будь-яким, крім посилання і бітового поля, причому тип може бути до цього моменту тільки оголошений, але ще не визначений (отже, в структурі, наприклад, може бути присутнім покажчик на структуру того ж типу).

Зірочка відноситься безпосередньо до імені, тому для того, щоб оголосити кілька покажчиків, потрібно ставити її перед ім'ям кожного з них. Наприклад, в операторі

int * а, b, * с;

описуються два покажчика на ціле з іменами а й з, а також ціла змінна b.

Розмір покажчика залежить від моделі пам'яті. Можна визначити покажчик на покажчик і т. Д.

покажчик на void застосовується в тих випадках, коли конкретний тин об'єкта, адреса якого потрібно зберігати, не визначений (наприклад, якщо в одній і тій же змінної в різні моменти часу потрібно зберігати адреси об'єктів різних типів).

Вказівником на void можна привласнити значення покажчика будь-якого типу, а також порівнювати його з будь-якими покажчиками, але перед виконанням будь-яких дій з областю пам'яті, на яку він посилається, потрібно перетворити його до конкретного типу явно.

Покажчик може бути константою або змінною, а також вказувати на константу або змінну. Розглянемо приклади:

int i; // Ціла змінна

const int ci = 1; // Ціла константа

int * pi; // Покажчик на цілу змінну

const int * pci: // покажчик на цілу константу

int * const cp = & i; // Покажчик-константа на

// Цілу змінну

const int * const cpc = & ci: // покажчик

// Константа на цілу константу

Як видно з прикладів, модифікатор const, що знаходиться між ім'ям покажчика і зірочкою, відноситься до самого вказівником і забороняє його зміна, a const зліва від зірочки задає сталість значення, на яке він вказує. Для ініціалізації покажчиків використана операція отримання адреси &.

Величини типу покажчик підкоряються загальним правилам визначення області дії, видимості і часу життя.

Ініціалізація вказівників. Покажчики найчастіше використовують при роботі з динамічною пам'яттю, званої деякими естетами купою (переклад з англійської мови слова heap). Це вільна пам'ять, в якій можна під час виконання програми виділяти місце відповідно до потреб. Доступ до виділених ділянок динамічної пам'яті, званим динамічними змінними, проводиться тільки через покажчики. Час життя динамічних змінних - від точки створення до кінця програми або до явного звільнення пам'яті. У C ++ використовується два способи роботи з динамічною пам'яттю. Перший використовує сімейство функцій mallос і дістався в спадок від С, другий використовує операції new і delete.

При визначенні покажчика треба прагнути виконати його ініціалізацію, тобто привласнення початкового значення. Ненавмисне використання неініціалізованих покажчиків - поширений джерело помилок в програмах. Ініціалізатор записується після імені покажчика або в круглих дужках, або після знака рівності.

Існують наступні способи ініціалізації покажчика:

1. Привласнення вказівником адреси існуючого об'єкта:

1.1. за допомогою операції одержання адреси:

int a = 5; // Ціла змінна

int * р = & а; // В покажчик записується адреса а

int * p (& а); // Те ж саме іншим способом

1.2. за допомогою значення іншого ініціалізованих покажчика:

int * r = p;

1.3. за допомогою імені масиву або функції, які трактуються як адреса:

int b [10]; // масив

int * t = b; // Присвоювання адреси початку масиву

void f (int a) {/ * ... * /} // визначення функції

void (* pf) (int); // Покажчик на функцію

pf = f; // Присвоювання адреси функції

2. Привласнення вказівником адреси області пам'яті в явному вигляді:

char * vp = (char *) 0xB8000000;

Тут 0хВ8000000 - шістнадцяткова константа, (char *) - операція приведення типу: константа перетвориться до типу «покажчик на char».

3. Присвоєння пустого значення:

int * suxx = NULL;

int * rulez = 0;

У першому рядку використовується константа NULL, певна в деяких заголовних файлах З як покажчик, рівний нулю. Можна використовувати просто 0, так як це значення типу int буде правильно перетворено стандартними способами відповідно до контексту. Оскільки гарантується, що об'єктів з нульовим адресою немає, порожній покажчик можна використовувати для перевірки, посилається покажчик на конкретний об'єкт чи ні.

4. Виділення ділянки динамічної пам'яті і присвоювання її адреси вказівником:

4.1. за допомогою операції new:

int * n = new int; // 1

int * m = new int (10); // 2

int * q = new int [10]; // 3

4.2. за допомогою функції malloc:

int * u = (int *) malloc (sizeof (int)); // 4

В операторі 1 операція new виконує виділення достатнього для розміщення величини типу int ділянки динамічної пам'яті і записує адресу початку цієї ділянки в змінну n. Пам'ять під саму змінну n (розміру, достатнього для розміщення покажчика) виділяється на етапі компіляції.

В операторі 2, крім описаних вище дій, проводиться ініціалізація виділеної динамічної пам'яті значенням 10.

В операторі 3 операція new виконує виділення пам'яті під 10 величин типу int (масиву з 10 елементів) і записує адресу початку цієї ділянки в змінну q, яка може трактуватися як ім'я масиву. Через ім'я можна звертатися до будь-якого елементу масиву. Якщо пам'ять виділити не вдалося, за стандартом має породжуватися виняток bad_alloc. Більш ранні версії компіляторів можуть повертати 0.

В операторі 4 робиться те ж саме, що і в операторі 1, але за допомогою функції виділення пам'яті malloc, успадкованої з бібліотеки С. В функцію передається один параметр - кількість виділеної пам'яті в байтах. Конструкція (int *) використовується для приведення типу покажчика, що повертається функцією, до необхідного типу. Якщо пам'ять виділити не вдалося, функція повертає 0.

Операцію new використовувати краще, ніж функцію malloc, особливо при роботі з об'єктами.

Звільнення пам'яті, виділеної за допомогою операції new, повинно виконуватися за допомогою delete, а пам'яті, виділеної функцією mallос - за допомогою функції free. При цьому змінна-вказівник зберігається і може инициализироваться повторно. Наведені вище динамічні змінні знищуються наступним чином:

delete n;

delete m;

delete [] q;

free (u);

Якщо пам'ять виділялася за допомогою new [], щоб звільнити пам'ять телефону необхідно застосовувати delete []. Розмірність масиву при цьому не вказується. Якщо квадратних дужок немає, то ніякого повідомлення про помилку не видається, але позначений як вільний буде тільки перший елемент масиву, а решта виявляться недоступними для подальших операцій. Такі осередки пам'яті називаються сміттям.

За допомогою комбінацій зірочок, круглих і квадратних дужок можна описувати складові типи і покажчики на складові типи, наприклад, в операторі

int * (* р [10]) ();

оголошується масив з 10 покажчиків на функції без параметрів, які повертають покажчики на int.

За замовчуванням квадратні і круглі дужки мають однаковий пріоритет, більший, ніж зірочка, і розглядаються зліва направо. Для зміни порядку розгляду використовуються круглі дужки.

При інтерпретації складних описів необхідно дотримуватися правила «зсередини назовні»:

1) якщо одразу після назви є квадратні дужки, це масив, якщо дужки круглі - це функція;

2) якщо зліва є зірочка, це покажчик на проінтерпретувати раніше конструкцію;

3) якщо справа зустрічається закриває кругла дужка, необхідно застосувати наведені вище правила всередині дужок, а потім переходити назовні;

4) в останню чергу інтерпретується специфікатор типу.

Для наведеного вище опису порядок інтерпретації вказано цифрами:

int * (* р [10]) ();

5 4 2 1 3 // порядок інтерпретації опису

Операції з покажчиками.З покажчиками можна виконувати наступні операції: разадресація, або непряме звернення до об'єкта (*), присвоювання, додавання з константою, віднімання, інкремент (++), декремент (-), порівняння, приведення типів. При роботі з покажчиками часто використовується операція отримання адреси (&).

Операція разадресаціі,або разименованія, призначена для доступу до величини, адреса якої зберігається в покажчику. Цю операцію можна використовувати як для отримання, так і для зміни значення величини (якщо вона не оголошена як константа):

char a; // Змінна типу char

char * р = new char; / * Виділення пам'яті під покажчик і під динамічну змінну типу char * /

* Р = 'Ю'; а = * р; / * Присвоювання значення обом змінним * /

Як видно з прикладу, конструкцію * імя_указателя можна використовувати в лівій частині оператора присвоювання, так як вона є L-значенням, тобто визначає адресу області пам'яті. Для простоти цю конструкцію можна вважати ім'ям змінної, на яку посилається покажчик. З нею допустимі всі дії, визначені для величин відповідного типу (якщо покажчик инициализирован). На одну і ту ж область пам'яті може посилатися кілька покажчиків різного типу. Застосована до них сіерація разадресаціі дасть різні результати. Наприклад, програма

#include

int main ()

{

unsigned long int A = 0Xcc77ffaa;

unsigned short int * pint = (unsigned short int *) & A;

unsigned char * pchar = (unsigned char *) & A;

printf ( "|% x |% x |% x |", A, * pint, * pchar);

return 0;

}

на IBM PC-сумісному комп'ютері виведе па екран рядок:

| cc77ffaa | ffaa | аа |

значення покажчиків pint и pchar однакові, але разадресація pcharдає в результаті один молодший байт за цією адресою, a pint - Два молодших байти. У наведеному вище прикладі при ініціалізації покажчиків були використані операції приведення типів. Синтаксис операції явного приведення типу простий: перед ім'ям змінної в дужках вказується тип, до якого її потрібно перетворити. При цьому не гарантується збереження інформації, тому в загальному випадку явних перетворень типу слід уникати.

При змішуванні в вираженні покажчиків різних типів явне перетворення типів потрібно для всіх покажчиків, крім void *. Покажчик може неявно перетворюватися в значення типу bool (Наприклад, в вираженні умовного оператора), при цьому ненульовий покажчик перетвориться в true,а нульовий в false. Присвоєння без явного приведення типів допускається в двох випадках:

1. вказівниками типу void *;

2. якщо тип покажчиків справа і зліва від операції присвоювання один і той же.

Таким чином, неявне перетворення виконується тільки до типу void *. Значення 0 неявно перетвориться до вказівником на будь-який тип. Присвоєння покажчиків на об'єкти вказівниками на функції (і навпаки) неприпустимо. Заборонено і привласнювати значення вказівниками-констант, втім, як і констант будь-якого типу (присвоювати значення вказівниками на константу і змінним, на які посилається покажчик-константа, допускається).

арифметичні операціїз покажчиками (складання з константою, віднімання, інкремент і декремент) автоматично враховують розмір типу величин, адресованих покажчиками. Ці операції застосовні тільки до покажчиків одного типу і мають сенс в основному при роботі зі структурами даних, послідовно розміщеними в пам'яті, наприклад, з масивами.

інкремент переміщує покажчик до наступного елементу масиву, декремент - до попереднього. Фактично значення покажчика змінюється на величину sizeof (тип). Якщо покажчик на певний тип збільшується або зменшується на константу, його значення змінюється на величину цієї константи, помножену на розмір об'єкта даного типу, наприклад:

short * р = new short [S3;

р ++; // Значення р збільшується на 2

long * q = new long [5];

q ++; // Значення q збільшується на 4

різниця двох покажчиків - це різниця їх значень, поділена на розмір типу в байтах (в застосуванні до масивів різниця покажчиків, наприклад, на третій і шостий елементи дорівнює 3). Підсумовування двох покажчиків не допускається.

При запису виразів з покажчиками слід звертати увагу на пріоритети операцій. Як приклад розглянемо послідовність дій, задану в операторі

* Р ++ = 10;

Операції разадресаціі і инкремента мають однаковий пріоритет і виконуються справа наліво, але, оскільки інкремент постфіксний, він виконується після виконання операції привласнення. Таким чином, спочатку за адресою, записаному в покажчику р, буде записано значення 10, а потім покажчик буде збільшений на кількість байт, що відповідає його типу. Те ж саме можна записати докладніше:

* Р = 10; р ++;

вираз (* Р) ++, Навпаки, інкрементує значення, на яке посилається покажчик.

Унарна операція отримання адреси & застосовна до величин, які мають ім'я і розміщеним в оперативній пам'яті. Таким чином, не можна отримати адресу скалярного вираження, неіменованого константи або реєстрової змінної. Приклади операції наводилися вище.

Динамічні масиви. Якщо до початку роботи програми невідомо, скільки в масиві елементів, в програмі слід використовувати динамічні масиви. Пам'ять під них виділяється за допомогою операції new або функції mallос в динамічній області пам'яті під час виконання програми. Адреса початку масиву зберігається в змінної-покажчику.

Звернення до елементу динамічного масиву здійснюється так само, як і до елементу звичайного - наприклад а [3]. Можна звернутися до елементу масиву і іншим способом - * (а + 3). В цьому випадку ми явно задаємо ті ж дії, що виконуються при зверненні до елементу масиву звичайним чином. Розглянемо їх детальніше. В змінної-покажчику а зберігається адреса початку масиву. Для отримання адреси третього елемента до цією адресою додається зсув 3. Операція складання з константою для покажчиків враховує розмір адресованих елементів, тобто насправді індекс множиться на довжину елементу масиву: а + 3 * sizeof (int). Потім за допомогою операції * (разадресаціі) виконується вибірка значення з зазначеній галузі пам'яті.

Якщо динамічний масив в якийсь момент роботи програми перестає бути потрібним і ми збираємося згодом використовувати цю пам'ять повторно, необхідно звільнити її за допомогою операції delete [], наприклад:

delete [] a;

Розмірність масиву при цьому не вказується.

Таким чином, час життя динамічного масиву, як і будь-який динамічної змінної, - з моменту виділення пам'яті до моменту її звільнення. Область дії залежить від місця опису покажчика, через який проводиться робота з масивом. Область дії і час життя покажчиків підкоряються загальним правилам, розглянутим на першому семінарі. Як ви пам'ятаєте, локальна змінна при виході з блоку, в якому вона описана, «губиться». Якщо ця змінна є покажчиком і в ній зберігається адреса виділеної динамічної пам'яті, при виході з блоку ця пам'ять перестає бути доступною, проте не позначається як вільна, тому не може бути використана в подальшому. Це називається витоком пам'яті і є поширеною помилкою:

{// Приклад витоку пам'яті

int n;

cin >> n;

int * pmas = new int [n];

...

}

// Після виходу з блоку покажчик pmas недоступний

Приклад 5.1. Написати програму, яка для речового масиву з n елементів визначає суму його елементів, розташованих правіше останнього негативного елементу.

У цьому завданні розмірність масиву задана змінною величиною. Передбачається, що вона буде нам відома на етапі виконання програми до того, як ми будемо вводити самі елементи. В цьому випадку ми зможемо виділити в динамічної пам'яті безперервний ділянку потрібного розміру, а потім заповнювати його вводяться значеннями. Якщо ж стоїть завдання вводити заздалегідь невідома кількість чисел до тих пір, поки не буде введений будь-якої ознака закінчення введення, то заздалегідь виділити достатню кількість пам'яті не вдасться і доведеться скористатися так званими динамічними структурами даних, наприклад списком.

Переглядаючи масив з початку до кінця, знайти номер останнього негативного елементу, а потім організувати цикл підсумовування всіх елементів, розташованих правіше нього. Ось як виглядає побудована за цим алгоритмом програма:

 



лістинг 4.3 | лістинг 5.1

лістинг 3.1 | лістинг 3.2 | лістинг 3.3 | лістинг 3.4 | лістинг 3.5 | лістинг 3.6 | лістинг 3.7 | Хід роботи | лістинг 4.1 | лістинг 4.2 |

© um.co.ua - учбові матеріали та реферати