- •1 Основні відомості про вказівники
- •2. Вказівники та посилання
- •Void function_a (char*, int, short*);
- •Int *pi; /* вказівник-змінна на дані типу int */
- •3. Ініціалізація вказівника
- •4. Розіменування та присвоєння
- •5. Void-вказівник
- •6. Операція пересування вказівника
- •7. Використання вказівників з модифікатором const
- •8. Деактивації дії модифікатора
- •9. Подвійний вказівник
- •10. Вказівники і масиви
- •11. Доступ до ділянок динамічної пам'яті
- •12. Звільнення пам'яті
- •8. Операції з вказівниками
- •Int I, *pi; /* pi –змінна-вказівник */
- •13. Динамічні масиви
- •11. Посилання
- •Лабораторный практикум
- •Вопросы без ответов
- •3 Основні операції над вказівниками
- •4 Багаторівнева непряма адресація
- •5 Операції над вказівниками
- •Void main ()
- •Void main()
- •6 Проблеми, пов'язані з вказівниками
- •Int *х; /* змінній-покажчику 'х' виділена оп, але 'х' не містить значення адреси оп для змінної */
- •Int *х; /* х - ім'я покажчика, він одержав оп*/
- •Void main ()
- •9 Масиви
- •1.9.1 Основні поняття
- •Int а[5]; /* оголошення зовнішнього масиву */ main ()
- •9.2 Оголошення та звертання в одновимірних масивах
- •9.3 Оголошення та звертання до багатовимірних масивів
- •Int а[3][4]; /* а - вказівник-константа */
- •10 Масиви покажчиків
- •10.1 Робота з великими масивами
- •Void main()
- •Int *p[200], I, j; clrscr() ;
- •10.2 Вільні масиви та покажчики
- •11 Символьні рядки
- •11.1 Основні відомості про представлення рядків
- •11.2 Функції роботи з рядками
- •1. Функції введення рядків.
- •2. Функції виведення рядків.
- •14 Лекція №14
- •14.1 Вказівники Лекція № 2 - Вказівники та посилання
- •1.2.1. Вказівники
- •6. Вказівники і масиви
- •6.1. Вказівники і адреси
- •6.2. Вказівники і аргументи функцій
- •6.3. Вказівники і масиви
- •6.4. Адресна арифметика
- •6.5. Вказівники символів і функції
- •6.6. Вказівники – не цілі значення
- •6.7. Багатовимірні масиви
- •6.8. Масиви вказівників (вказівники на вказівники)
- •6.9. Ініціалізація масивів вказівників
- •6.10. Вказівники і багатовимірні масиви
- •6.11. Командний рядок аргументів
- •6.12. Вказівники на функції
- •Посібник для початківця про вказівники
- •6. Вказівники і структуровані програмні змінні
- •9. Вказівники типу far
- •10. Вказівники і динамічні змінні (керування пам'яттю)
- •9. Вказівники типу far
- •10. Вказівники і динамічні змінні (керування пам'яттю)
- •8. Вказівники і динамічні змінні (керування пам'яттю)
- •Void* operator new(size_t t)
- •Void operator delete(void* p)
- •Void main()
6.3. Вказівники і масиви
Будь-яку операцію, яку можна виконати за допомогою індексів масиву, можна виконати і за допомогою вказівників. Варіант із вказівниками більш швидкий, але й більш складний для розуміння. Опис
int a[10];
визначає масив розміром 10, тобто набір з 10 послідовних об’єктів a[0], a[1], ..., a[9]. Запис a[i] відповідає елементу масиву через i позицій від початку. Якщо ра – вказівник цілого, описаний як int *pa, то присвоєння pa = &a[0] означає, що pa вказує на нульовий елемент масиву a, тобто ра містить адресу елемента a[0]. Присвоєння x = *pa буде копіювати вміст a[0] у x. Якщо pa вказує на деякий певний елемент масиву a, то pa + 1 вказує на наступний елемент. Взагалі pa-і вказує на елемент, який розміщено на i позицій до елемента, що вказується pa, а pa + i на елемент, розміщений на i позицій після елемента. Отже, якщо ра вказує на а[0], то *(ра + 1) посилається на вміст а[1], ра + і – адреса а[і], а *(ра + і) – вміст а[і]. Ці зауваження справедливі незалежно від типу змінних у масиві a.
Очевидно існує дуже тісна відповідність між індексацією і арифметикою вказівників. Насправді компілятор перетворить посилання на масив у вказівник на початок масиву. В результаті цього ім’я масиву є вказівником виразу, тобто ім’я масиву є синонімом місця розташування його нульового елемента. Тому pa = &a[0] можна записати як pa = a.
Посилання на a[i] можна записати у вигляді *(a+i), тобто ці дві форми еквівалентні. Якщо застосувати операцію & до обох частин такого співвідношення еквівалентності, то &a[i] і a+i теж будуть ідентичними: a+i – адреса i-го елемент від початку a. Проте, якщо pa є вказівником, то у виразах його можна використати з індексом pa[i], що ідентично *(pa+i). Отже, будь-який вираз, який містить масиви й індекси, можна записати через вказівники та зсуви і навпаки.
Слід мати на увазі, що вказівник є змінною, тому операції pa = a і pa++ мають сенс. Але ім’я масиву є константою, а не змінною, тому конструкції типу a = pa або a++, або p = &a будуть помилковими.
Коли ім’я масиву передається функції, то насправді їй передається місце розташування початку цього масиву. Отже ім’я масиву як аргумент дійсно є вказівником, тобто змінною, яка містить адресу. Розглянемо функцію StrLen (див. ПП6.4 на СD), яка обчислює довжину рядка, де описи формальних параметрів
char s[];
char *s;
еквівалентні, і який вид опису варто використати, визначається тим, які вирази будуть використані для написання функції.
Можна передати функції частину масиву, якщо задати як аргумент вказівник початку підмасиву. Наприклад, якщо a – масив, то як F(&a[2]), так і F(a+2) передають функції F адресу елемента a[2], оскільки і &a[2], і a+2 є вказівними виразами, що посилаються на третій елемент a.
6.4. Адресна арифметика
Якщо p є вказівником, то який би не був тип об’єкта, на який він вказує, операція p++ збільшує p так, що він вказує на наступний елемент набору цих об’єктів, а операція p += i збільшує p так, щоб він вказував на елемент, віддалений на i елементів від поточного елемента. Ці та аналогічні конструкції формами арифметики вказівників або адресної арифметики мови C++.
Проілюструємо можливості мови на прикладі програми розподілу пам’яті. Є дві функції: функція Alloc(n) повертає як своє значення вказівник p, що вказує на першу з n послідовних символьних позицій, які можуть бути використані програмою для зберігання символів; функція Free(p) звільняє зайняту в такий спосіб пам’ять. Отже, керована функціями Alloc і Free пам’ять є стеком або списком, у якому елемент, що вводиться останнім, вилучається першим.
Реалізація полягає в тому, щоб функція виділяла відрізки великого символьного масиву (надамо ім’я allocbuf), який є власністю функцій Alloc і Free. Вони працюють із вказівниками, а не з індексами масиву, тому ніякій іншій функції не потрібно знати імен цього масиву. Його можна описати як зовнішній статичний (локальний стосовно вихідного файлу, що містить Alloc і Free) і невидимий за його межами.
Іншою потрібною інформацією є, інформація про, яка частина масиву allocbuf уже використана. Для цього використовують вказівник першого вільного елемента – allocp. Коли потрібно функції Alloc виділити місце під n символів, вона перевіряє, чи досить залишилося для цього місця в allocbuf. Якщо так, то Alloc повертає поточне значення allocp (тобто початок вільного блоку), потім збільшує його на n для того, щоб він вказував на наступну вільну область. Функція Free(p) визначає allocp рівним p за умови, що p вказує на позицію всередині allocbuf (див. ПП6.5 на СD).
Зазначимо, що вказівник може бути ініційований так само, як і будь-яка інша змінна. Об’єкт null – вираз, який містить адреси визначених раніше даних відповідного типу. Опис
static char* allocp = allocbuf;
визначає allocp як вказівник на символи і ініціює його так, щоб він вказував на allocbuf, тобто на першу вільну позицію на початку роботи програми. Ім’я масиву є адресою його нульового елемента, що можна записати так:
static char* allocp = &allocbuf[0];
За допомогою перевірки
if (allocp+n <= allocbuf + allocsize)
з’ясовується, чи залишилося досить місця, щоб задовольнити запит на n символів. Якщо достатньо, то нове значення allocp не буде вказувати далі, ніж на останню позицію allocbuf. Якщо запит може бути вдоволено, то Alloc повертає звичайний вказівник. Якщо ж ні, то Alloc повинна повернути деяку ознаку, яка свідчить про те, що більше місця не залишилося. У мові C++ гарантується, що жоден правильний вказівник даних не може мати значення нуль, тому повернення нуля може служити сигналом про ненормальну подію, у цьому випадку про відсутність місця. Однак замість нуля пишемо null (спеціальне значення вказівника). Взагалі цілі числа не можуть присвоюватись вказівникам, а нуль – це особливий випадок.
Перевірки if (allocp + n <= allocbuf + aloocsize) і if (p >= allocbuf && p < allocbuf + allocsize) показують кілька важливих аспектів арифметики вказівників. По-перше, за певних умов вказівники можна порівнювати. Якщо p і q вказують на елементи того самого масиву, то такі відношення, як <, >=, > і т. д., працюють належним чином. Наприклад, p < q справедливо, якщо p вказує на більш ранній елемент масиву, ніж q. Відношення == та != теж працюють. Довільний вказівник можна порівняти на рівність або нерівність із null. Але ні в чому не можна бути впевненим, якщо використати порівняння під час роботи з вказівниками, які вказують на різні масиви.
По-друге, вказівник і ціле число можна додавати і віднімати. Конструкція p + n має на увазі n-й об’єкт за тим, на який p указує в цей момент. Це справедливо, незалежно від того, на який вид об’єктів p повинен вказувати; компілятор сам масштабує n відповідно до опису p розміру об’єктів, які вказують за допомогою p. Наприклад, масштабний множник дорівнює 1 для char, 2 для int і short, 4 для long і float і 8 для double.
Віднімання вказівників теж можливе: якщо p і q вказують на елементи того самого масиву, то p-q – кількість елементів між p і q. Цей факт був використаний для написання варіанта функції StrLen (див. ПП6.6. на СD).
Під час опису вказівник p у цій функції ініційований за допомогою рядка s, у результаті чого він вказує на перший символ рядка. У циклі while по черзі перевіряється кожен символ до появи символа кінця рядка ’\0’ (значення 0). Явну перевірку можна опустити , і цикли записати так:
while (*p)
p++;
p вказує на символи, тому оператор p++ пересуває p щоразу так, щоб він вказував на наступний символ. У результаті вираз p-s обчислює кількість переглянутих символів, тобто довжину рядка. Арифметика вказівників враховує тип змінної. Якби оброблялися змінні типу float, які займають більше пам’яті, ніж змінні типу char, і якби p був вказівником на float, то оператор p++ пересунув би p на наступне значення типу float. Отже, можна написати інший варіант функції Alloc, яка розподіляє пам’ять для float. Для цього потрібно всюди в Alloc і Free дескриптор char змінити на float. Усі дії з вказівниками автоматично враховують розмір об’єктів.
За винятком операцій додавання, віднімання вказівника, цілого числа, віднімання і порівняння двох вказівників уся інша арифметика вказівників є хибною. Заборонено складати два вказівники, множити, ділити, об’єднувати або маскувати, а також додавати до них змінні типу float або double.
