Покажчики — це змінні, котрі містять адресу пам’яті, розподіленої для об’єкта відповідного типу. Усі змінні, розглянуті до цього, зберігали якісь значення (дані). Ці дані могли бути різних типів: символьного, цілого, дійсного тощо. При оголошенні змінної-покажчика слід вказати тип даних, адресу яких буде містити змінна, та ім’я покажчика з символом «*».
Загальний формат опису покажчика має вигляд:
тип *ім’я;
де тип — тип значень, на який вказує покажчик;
ім’я — ім’я змінної-покажчика;
«*» — операція над типом, що читається «покажчик на тип».
Наприклад:
int *рn – покажчик на ціле значення;
float *pf1, *pf2; — два покажчики на дійсні значення.
Покажчики не прив’язують дані до якого-небудь визначеного імені змінної і можуть містити адреси будь-якого неіменованого значення. Існує адресна константа NULL, що означає порожню адресу.
Слід нагадати, що мова C++ налічує лише дві операції, які стосуються адрес змінних, а саме:
«&» — операція взяття адреси («адреса значення»);
«*» — операція розіменування («значення за адресою»).
Операція взяття адреси «&» застосовується разом зі змінною і повертає адресу цієї змінної. Операція розіменування «*» використовується разом з покажчиками і вилучає значення, на яке вказує змінна-покажчик, розташована безпосередньо після символа «*».
Оголошення покажчиків можна здійснити одним з таких способів:
<тип> *ptr;
<тип> *ptr = <змінна-покажчик>;
<тип> *ptr = &<ім’я змінної>;.
Наприклад:
int *ptx, b; float у; — оголошені змінна-покажчик ptx та змінні b і у;
float *sp = &у; — покажчику sp присвоюється адреса змінної у;
float *р = sp; — покажчику р присвоюється значення (адреса значення), яке міститься в змінній sp, тобто адреса змінної у.
При оголошенні покажчиків символ «*» може знаходитися перед ім’ям покажчика або відразу після оголошення типу покажчика і поширювати свою дію тільки на одну змінну-покажчик, перед якою він записаний:
long *pt; long*Uk; int *ki, x, h; — оголошення описів.
За потреби для опису покажчика на комірку довільного типу замість ідентифікатора типу записується слово void, а саме:
void *р, *pt; — опис двох покажчиків на довільний тип даних.
Перед використанням покажчика у програмі його обов’язково необхідно ініціювати, іншими словами, необхідно присвоїти адресу якого-небудь даного, інакше можуть бути непередбачені результати.
Для одержання доступу до значення змінної, адреса якої зберігається в покажчику, досить у відповідному операторі програми записати ім’я покажчика з символом «*» — здійснити операцію розіменування.
Розглянемо фрагмент програми з поясненнями:
int *р, *р1; — оголошені два покажчики на комірку пам’яті типу int;
int х = 12, у = 5, m[7]; — оголошені змінні х, у і масив m, змінні ініційовані;
р = &у; // р (&у); — покажчику р присвоєна адреса змінної у.
Якщо для цього фрагмента програми записати оператор виведення у вигляді
cout << “Адрес р ” << р << “Значение по этому адресу = ” << *р;,
то виведеться адреса комірки пам’яті, де записана змінна у і значення цієї змінної (тобто 5).
Використовуючи запис х = *р;, одержимо х = 5, тому щo *р = у = 5;.
Змінити величину параметра у можна так:
у = 10; // *Р= 10;
*р = *р+5; //у +=5;.
Остання операція означає збільшення значення змінної у цiлого типу на 5, тобто у= 15.
При ініціюванні покажчиків їм можна присвоювати або адресу об’єкта (змінної), або адресу конкретного місця пам’яті (масиву), або число 0 (нуль), а саме:
int *pt = (char *) 0x00147; — присвоюється адреса комірки;
int *arrpt = new int [10]; — визначається початкова адреса розміщення динамічного масиву;
char *р = 0; — здійснюється ініціювання нулем.
Оскільки покажчики — це спеціальні змінні, то в операціях з іншими покажчиками вони можуть використовуватися без символа «*», тобто без розкриття посилання, наприклад:
float *pt1, *pt2, х=15, m[5];
pt1 = &x;
pt2 = pt1;
pt1 = m; //pt1 = &m[0];
де m — ім’я масиву, що розглядається як спеціальний покажчик-константа.
Приклад 6.6. Написати ілюстраційну програму з використанням покажчиків.
// P6_6.CPP — применение указателей #include <iostream.h> #include <conio.h> int main ( ) { int x = 10; int *px (&x); // int *px = &x; cout << "x =" << x << endl; cout << "*px =" << *px << endl; x *= 2; //x=x*2; cout << "Новое значение *px = " << *px << endl;* px += 2; // *px=*px + 2; cout << "Результат *px, т. e. x = " << x << endl; getch(); //задержка экрана }
Результат виконання програми:
х = 10
*рх = 10
Новое значение *рх = 20
Результат *рх, т. е. х = 22
Для змінної-покажчика існує своя адреса і тому будуть до цільними записи:
int *pt1, *pt2;
pt1 = (int*) &pt2; — покажчику pt1 присвоюється адреса пам’ятi де розташована змінна pt2.
Це має сенс у випадку, коли
int у, *pt1, *pt2 = &у;
pt1 = (int*) & pt2;.
Обмеження на застосування операції взяття адреси:
- не можна визначати адресу літеральної константи (оскільки для неї не виділяється комірка пам’яті), тобто такий запис, як vp = &345; — неприпустимий;
- не можна визначати адресу результату арифметичного виразу, тобто запис vp = &(x + y); теж неприпустимий.
Дозволені операції для змінних-покажчиків:
- операція розіменування «*»;
- операція взяття адреси «&»;
- операція присвоювання «=»;
- операції інкремент «++» і декремент « –»;
- операції додавання «+» і віднімання «-»;
- операції відношення (порівняння) покажчиків однакового типу: «==», «!=», «<», «<=», «>», «>=».
У мові C++ масиви і покажчики зв’язані між собою: ім’я масиву визначається як покажчик-константа на початковий (нульовий)елемент масиву. Так, наприклад, при оголошенні одновимірного масиву у вигляді int mas [20]; його ім’я mas – покажчик на адресу початкового елемента масиву &mas[0].
Існує два способи доступу до елементів масиву:
- з використанням індексу елемента масиву, наприклад, mas[2] або mas[i];
- з використанням адресного виразу, тобто виразу з покажчиком на масив, наприклад, *(mas + 2) або *(mas + і).
Ім’я покажчика на масив можна записати так:
int mas [20];
int *ptr1;
ptr1 = mas; //ptr1 = &mas[0];,
тут вирази &mas[0] і mas — еквівалентні.
Оскільки в комп’ютері для масивів завжди є суцільний блок комірок пам’яті, в яких розташовуються їх елементи, то адресу наступного елемента mas[1] можна вказати шляхом збільшення покажчика на 1, а саме:
р = &mas[0];
р++; //р=р + 1;
Таким чином, адреса і-го елемента визначається як р + і. При цьому з урахуванням типу масиву і відведеної кількості байтів для кожного його елемента автоматично виконується операція збiльшення адреси, тобто:
адреса х[і] = адреса х[0] + i*sizeof (тип); .
Слід зауважити, що для покажчиків, які посилаються на елементи масивів різних типів, результат арифметичних операцій і операцій відношення невизначений.
До двох покажчиків р1 і р2, що вказують на елементи одного масиву, застосовують операції відношення: «==», «!=», «<», «<=», «>», «>=». При цьому значення покажчиків розглядаються як цілі числа, а результат порівняння дорівнює 0 ( »неправда») або 1 («істина»). Так, відношення вигляду р1<р2 є «істина», якщо р1 указує на більш ранній елемент, ніж р2. Будь-який покажчик можна порівняти з нулем.
В арифметиці з покажчиками можна використовувати адресу неіснуючого «наступного за масивом» елемента. До покажчиків можна додавати або віднімати від них цілу величину.
В обох випадках результатом операції буде покажчик на вихідний тип, значення якого на вказане число елементів більше або менше вихідного. Тобто, якщо до покажчика р можна додати деяку цілу величину n, а саме: р + n, то цей вираз визначає ділянку об’єкта, що займає n-не місце після об’єкта, на який вказує р, при цьому n автоматично збільшується на коефіцієнт, що дорівнює відповідній довжині об’єкта. Наприклад, якщо int займає 4 байти, то цей коефіцієнт дорівнює чотирьом.
Допускається також операція віднімання покажчиків, що вказують на елементи одного масиву. Так, якщо р1 < р2, то р2 – р1 + 1 — це число елементів масиву від р1 до р2 включно.
Наведемо приклади програм роботи з покажчиками.
Приклад 6.7. Обчислити середнє значення додатних елементів одновимірного масиву.
Розглянемо перший варіант програмної реалізації цієї задачі (див. Р6_7_1.СРР).
/* Р671.СРР — вычисление среднего значения положительных элементов массива */ //---------------- программа без использования указателей #include <iostream.h> #include <conio.h> main( ) { const int n = 10; float mas[n], s = 0; int i, kol = 0; cout << "Ввод массива " << endl; for(i =0; і < n; i++) cin >> mas[i]; for(i = 0; і < n; i++) if (mas[i] > 0) { s += mas[i]; //накопление суммы kol++; //подсчет положительных елементов } cout.precision(3 ) ; cout << "Средн. арифм. полож. элементов = " << s/kol << endl; getch(); //задержка экрана вывода резулътата }
Результати виконання програми:
Ввод массива
1.56 -4.78 6.5 7.89 -3.6 9.45 7.4 -8.43 9.3 -10.2
Средн. арифм. полож. элементов = 7.02
Використовуючи ім’я масиву як покажчик на початок масиву (перший елемент), можна навести другий варіант програми (див. Р6_7_2.СРР):
// Р6_7_2.СРР - использование имени массива как указателя #indude <iostream.h> #include <conio.h> main ( ) { const int n = 10; float mas[n], s; int i, kol = 0; for (і = 0, s = 0; і < n; i++) { сіn >> *(mas+i); if (*(mas+i) > 0) { s += *(mas+i); kol++; } } cout.precision(3); cout <<"\n Среднее арифм. полож. элементов = " << s/kol << endl; getch(); }
Якщо описати покажчик і зв’язати його з масивом (адресувати на початок масиву), то з використанням арифметики покажчиків можна написати третій варіант (див. Р6__7_З.СРР) цiєї програми.
// Р6_7_3. СРР — использование арифметики указателей #include <iostream.h> #include <conio.h> main ( ) { const int n = 10; int і, kol(0); float mas[n], s(0); float *pm = mas; //pm= &mas[0]; for (і = 0; і < n; i++) { cout << "Введите" << і << "элемент mas" << endl; cin >> *pm++; cout << mas[i] << endl; if (mas[i] >0) { s += mas[i]; kol++; } } cout.precision(3); cout << "\n Среднее арифм. полож. элементов = " << s/kol << endl; getch(); }
У цій програмі для введення масиву застосований покажчик *рm, а для роботи з масивом — ім’я масиву з індексом.
В останньому випадку використання покажчика *рm призвело б до помилкового результату, оскільки цей покажчик в операціях введення збільшує свою адресу (рm++) після введення чергового елемента масиву і надалі вказує на ще не введений елемент.
Наведемо четвертий варіант (Р6_7_4.СРР) програмної реалiзації прикладу:
/* Р6_7_4.СРР — использование указателей #include <iostream.h> #include <conio.h> main ( ) { const int n = 10; float mas[n], s = 0; float *pm = &mas[0]; //pm *= &mas[0]; int i, kol = 0; for (і = 0; і < n; i++) { cout << "Введите" << і << "элемент mas" << endl; cin >> *pm; if (*pm >0) { s += *pm; kol++; pm++; } } cout.precision(3); cout << ”\n Среднее арифм. полож. элементов = " << s/kol << endl; getch(); }