
Масиви покажчиків
У С/С++ елементи масивів можуть мати будь-який тип, зокрема, можуть бути покажчиками на будь-який тип (один і той же). Масив, який містить адреси однотипних даних, називають масивом покажчиків.
Синтаксис оголошення масиву покажчиків:
тип *ім’я [розмір].
Наприклад,
int *m_ptr[3]; //масив з 3-х покажчиків на тип int
typedef float *array[5]; //оголошення типу користувача
array a_ptr; //масив з 5-ти покажчиків на тип float
Оголошені вище змінні m_ptr і a_ptr є масивами покажчиків як тип даних, але не є такими як структура даних. Щоб вони перетворилися на структури даних, їх треба ініціалізувати (зв'язати із відповідними змінними через покажчики-зв'язки). Наприклад,
int a=5, b=10, с=15;
int *m_ptr []={&a,&b,&c}.
Масив покажчиків має такі самі властивості, як і масив будь-якого базового типу: його вимірність задається під час оголошення і не змінюється протягом роботи з ним; доступ до кожної адреси з масиву покажчиків здійснюється за індексом, тощо.
Алгоритми роботи з масивом покажчиків і звичайним масивом зовні дуже схожі. Різниця ж полягає в тому, що розміщення даних в звичайному масиві відповідає їх фізичному порядку слідування в оперативній пам'яті, а масив покажчиків дозволяє сформувати логічний порядок слідування елементів у відповідності з розміщенням покажчиків на них. Тоді зміна порядку слідування (включення, виключення, упорядкування, перестановка), яка в звичайному масиві полягає в переміщенні самих елементів, у масиві покажчиків повинна супроводжуватися операціями над покажчиками на них.
При роботі з масивом покажчиків використовуються два контекста:
р[i] - i-й покажчик в масиві;
*р[i] - значення змінної, на яку посилається i-ий покажчик в масиві.
Наприклад,
int s=0, a=5, b=10, с=15;
int *p[]={&a,&b,&c}; // ініціалізація масива покажчиків
for (int i=0; i<3; i++)
s=s+*p[i]; // знаходження суми значень змінних a, b, с
При цьому слід розрізняти оголошення масиву (int mas[5]), масиву покажчиків (int *ptr[5]), покажчика на масив (int (*) mas[5]).
При роботі з масивами покажчиків часто використовується ще один похідний тип даних - покажчик на покажчик. Синтаксис його задання:
тип **покажчик.
Розіменуванням такого покажчика є звертання до значення, на яке вказує покажчик, що міститься за адресою, на яку вказує поточний покажчик.
Покажчик на покажчик допускає чотири інтерпретації:
Покажчик на покажчик на окрему змінну (рис. 9). Наприклад,
int a=5, b=10;
int *p=&a;
int **pp=&p;
(**pp)++; // а=6
*pp=&b;
**pp=0; // b=0
Рис.9. Реалізація покажчика на покажчик на окрему змінну
Покажчик на покажчик на лінійний масив змінних (рис. 10). Наприклад,
int a[10]={5}, b[10]={10};
int *p=a;
int **pp=&p;
for (int i=0; i<10; i++) // ініціалізація елементів масиву а
(*pp)[i]=4; // p[i]=4;
*pp= b;
for (int i=0; i<10; i++) // ініціалізація елементів масиву b, починаючи з другого
{ (*pp)++; // p++;
**pp= i; // *p= i;
}
Рис.10. Реалізація покажчика на покажчик на лінійний масив змінних
У виразах, які використовуються в такій структурі даних, присутні пріоритетні дужки, наприклад, (*pp), оскільки операція непрямого звернення на першому рівні передує операції індексації на другому.
Інші два варіанти стосуються роботи з масивами вказівників.
Покажчик на масив покажчиків на окремі об'єкти (рис. 11). Наприклад,
int s=0, a=5, b=10, с=15;
int *p[]={&a,&b,&c,NULL}; // ініціалізація масива покажчиків
int **pp=p;
for (int i=0; i<3; i++) // знаходження суми значень змінних a, b, с
s=s+*pp[i];
Рис.11. Реалізація покажчика на масив покажчиків на окремі об'єкти
Для доступу до об'єктів, що адресуються покажчиками масива покажчиків, використовується природний порядок операцій *pp[i].
Об'єкти, що адресуються покажчиками масива покажчиків, можуть бути логічно пов'язаними між собою (утворювати масив якогось базового типу).
Нехай має місце наступне оголошення змінних:
int a[] = {10,11,12,13};
int *p[] = {a, a +1, a +2, a +3};
int **pp = p;
В результаті породжуються наступні програмні об'єкти:
Посилання на них може мати достатньо складний вид. Так при виконанні операції pp-p отримаємо нульове значення, так як посилання pp і p вказують на початковий елемент масиву покажчиків, пов'язаного з покажчиком p (на елемент p[0]); при зверненні за допомогою посилання **pp отримаємо 10 - це значення першого елемента масиву a; посилання *pp++ дасть значення другого елемента масиву p, тобто адресу другого елемента масиву a. Якщо вважати, що pp = p, то звернення **++pp - це значення другого елемента масиву a (тобто значення 11), операція ++*pp змінить вміст покажчика p[0], таким чином, що він стане рівним значенню адреси елемента a[1].
Щоб правильно інтерпретувати складні посилання, треба керуватися правилом: такі звернення розкриваються зсередини. Наприклад звернення *(++(*pp)) можна розбити на наступні дії: *pp дає значення початкового елемента масиву p[0], далі це значення інкременується ++(*p) у результаті чого покажчик p[0] стане дорівнювати значенню адреси елемента a[1], і остання дія - це вибірка значення за отриманою адресою, тобто значення 11.
Наприклад,
int a[] = {10,11,12,13};
int *p[] = {a,a+1,a+2,a+3};
int **pp = p;
printf("pp=%p *pp=%p p[]=%p %p %p %p \n",pp,*pp,p[0],p[1],p[2],p[3]);
printf("**pp=%d *p[]= %d %d %d %d \n",**pp,*p[0],*p[1],*p[2],*p[3]);
printf("\n**++pp=%d \n",**++pp);
printf("pp=%p *pp=%p p[]=%p %p %p %p \n",pp,*pp,p[0],p[1],p[2],p[3]);
printf("**pp=%d *p[]= %d %d %d %d \n",**pp,*p[0],*p[1],*p[2],*p[3]);
printf("\n++*pp=%p \n",++*pp);
printf("pp=%p *pp=%p p[]=%p %p %p %p \n",pp,*pp,p[0],p[1],p[2],p[3]);
printf("**pp=%d *p[]= %d %d %d %d \n",**pp,*p[0],*p[1],*p[2],*p[3]);
printf("\n*(++(*pp))=%d \n",*(++(*pp)));
printf("pp=%p *pp=%p p[]=%p %p %p %p \n",pp,*pp,p[0],p[1],p[2],p[3]);
printf("**pp=%d *p[]= %d %d %d %d \n",**pp,*p[0],*p[1],*p[2],*p[3]);
Хоча складні оголошення рідко зустрічаються на практиці, важливо знати як їх зрозуміти і, коли треба, як створити їх.
Покажчик на масив покажчиків на лінійні масиви змінних (рис. 12). Наприклад,
int s=0,a[]={5,6,7,8}, b[]={1,2,3,4}, с[]={5,2,4,8};
int *p[]={a,b,c};
int **pp=p;
for (int i=0; i<3; i++) // знаходження суми елементів масивів a, b, с
for (int j=0; j<4; j++)
s=s+pp[i][j];
Рис.12. Реалізація покажчика на масив покажчиків на лінійні масиви змінних
Масив покажчиків на лінійні масиви змінних є двовимірної структурою даних і використовує подвійну індексацію. Функціонально вона є еквівалентом двовимірного масиву. Наприклад, наступні оголошення змінних
int a[3][3] = {{11,12,13},
{21,22,23},
{31,32,33}};
int * pa[3] = {a, a [1], a [2]};
int * p = a[0];
породжують у програмі об'єкти, представлені на схемі на рис. 13:
Рис. 13 Схема розміщення покажчиків на двовимірний масив
Відповідно до цієї схеми, доступ до елемента двовимірного масиву a[0][0] можна отримати за покажчиками a, p, pa за допомогою наступних посилань: a[0][0], *a, **a[0], *p, **pa , *p[0].
Слід зазначити, що за відповідність кожної, створеної в результаті використання покажчика на покажчик, структури даних і операціями над нею повинен стежити програміст (компілятор цього не робить).
Застосування масивів покажчиків є ефективним при реалізації багатьох задач, зокрема, алгоритмів сортування (переставлення елементів місцями можна замінити переадресацією покажчиків). Очевидні переваги такої реалізації виникають, коли об'єкти, які адресуються масивом покажчиків є достатньо великими, або їх переміщення з якихось причин неможливе (наприклад, на них посилаються інші частини програми).
Приклад. Сортування цілочисельного масиву.
#include <iostream>
#include <string.h>
using namespace std;
const int n=10;
int *masptr[n], //масив покажчиків на int
mas[n]; //числовий масив
void input(); //введення масиву
void output(); //виведення масиву
void sort (); //сортування масиву
//----------------------- головна функцiя ----------------------------------------------
void main()
{ input(); //введення масиву
output(); //виведення масиву
sort(); //сортування масиву
output(); //виведення відсортованого масиву
system("pause");
}
//----------------------- введення масиву -------------------------------------------
void input()
{ cout<<"input "<<n<<" integer elements: ";
for (int i=0; i<n; i++)
{ cin>>mas[i];
masptr[i]=&mas[i]; // masptr[i]=&mas[i];cin>>*masptr[i];
}
}
//----------------------- виведення масиву ---------------------------------------------
void output()
{ for (int i=0; i<n; i++)
cout<<*masptr[i]<<" ";
cout<<endl;
}
//----------------------- сортування масиву -------------------------------------------
void sort()
{ int i, k;
do
{ for (k=0, i=0; masptr[i+1]!=NULL; i++)
if (*masptr [i] > * masptr [i+1])
{ double *c;
c = masptr [i];
masptr [i] = masptr [i+1];
masptr [i+1] = c;
k = 1;
}
} while (k);
}
1 Розмір одновимірного параметра-масива вказувати не обов’язково