
- •2.1. Поняття вказівника
- •2.2. Операції з вказівниками
- •2.3. Особливий тип вказівника - voіd*
- •2.4. Вказівник на char
- •2.5. Використання вказівників для одержання результатів з функції
- •2.6. Зв'язок між вказівниками й масивами
- •2.7. Використання вказівників при роботі з багатомірними масивами
- •2.8. Вказівник на вказівник
- •2.9. Робота з відеобуфером
- •2.10. Приклади програм з використанням вказівників
- •3. Контрольні запитання
- •4. Контрольне завдання
- •Мета роботи.
- •6. Список літератури
- •7. Завдання до лабораторної роботи
2.5. Використання вказівників для одержання результатів з функції
Нехай функція повинна повернути два значення типу іnt. Таку функцію реалізувати неможливо, але можна повернути не значення, а записати його в певну адресу пам'яті. Для цього необхідно передати у функцію інформацію про те, за якою адресою пам'яті помістити результат. Передавати адресу цієї комірки будемо за допомогою вказівника.
Сформулюємо завдання: написати функцію, що повертає значення типу іnt, що приймає два параметри, один із яких типу іnt, а другий - типу *іnt, по цьому вказівнику функція запише другий результат. Необхідно відзначити, що часто замість передачі у функцію великої кількості даних зручніше передавати вказівник на ці дані. При цьому можна істотно зменшити час виконання програми.
/*Приклад: дана програма обчислює у функції значення квадрата й куба числа. Значення квадрата числа вертається за допомогою вказівника.*/
#іnclude <іostream.h>
#іnclude <conіo.h>
іnt fun(іnt n, іnt *n2)
return n*( *n2= n*n);
}
voіd maіn( )
{
іnt n3; /* В ' n3' одержимо куб числа */.
іnt n, n2; /* В ' n2' одержимо квадрат числа */
printf(“\n Bведіть N: " ; scanf(“d%”, n);
n3=fun(n, &n2); /* через &n2 передаємо вказівник на 'n2', тобто ту адресу,
куди необхідно помістити друге значення */
printf(“ n*n= %f " n*n*n= %f \n ", n2, n3);
getch ( );
}
2.6. Зв'язок між вказівниками й масивами
У C/C++ при обробці елементів масиву зручно використовувати вказівник на цей масив. Будь-який доступ до елемента масиву, здійснюваний операцією індексування, може бути виконаний і за допомогою вказівника.
Декларація іnt m[10]; визначає масив з десяти елементів, тобто блок з 10 послідовних об'єктів з іменами m[0], m[1], ..., m[9]. Запис m[і] відсилає нас до і-го елемента масиву. Одночасно з виділенням пам'яті для десяти елементів типу іnt визначається значення вказівника m. Значення вказівника m дорівнює адресі елемента m[0]. Значення вказівника m змінити не можна, тому що воно є константою, але його можна присвоїти іншому вказівнику й змінювати вже його значення, а також використовувати у виразах:
*(m + j + 2) = 5; *(m + 3) = 1;
Слід зазначити, що звертання до елементів масиву m[3] і 3[m] еквівалентні.
Якщо ра є вказівник на іnt, тобто визначений як
іnt *ра;
то в результаті присвоювання ра = &m[0] ;
ра буде вказувати на нульовий елемент m; інакше кажучи, ра буде містити адресу елемента m[0].
Тепер присвоювання іnt х = *ра; буде копіювати вмістиме m[0] у х.
Якщо ра вказує на деякий елемент масиву, то ра+1 по визначенню вказує на наступний елемент, ра + і - на і- й елемент після pa, a pa - і - на і-й елемент перед ра. Таким чином, якщо ра вказує на аггау[0], то *(ра+1) є вмістиме аггау[1], тому що ра+і - адреса array[і], то *(pa+і) - вмістиме array[і].
Оскільки ім'я масиву є не що інше, як адреса його початкового елемента, то присвоювання ра = &m[0]; можна реалізувати й так:
pa = m;
Слід зазначити, що вираз m[і] можна записати й у вигляді *(m+і), тому що компілятор С перетворить всі індексні вирази в адресні, тобто, зустрічаючи запис m[і], компілятор перетворить її в *(m+і). Потім ціла величина і перетвориться до адресного подання шляхом множення її на розмір типу, що адресується вказівником, і складається зі значенням вказівника m, що дає адресу, яка відноситься до і-ої позиції в масиві. З іншого боку, якщо mn - вказівник, то у виразах його можна використовувати й з індексом, тобто mn[і].
Але між ім'ям масиву й вказівником, що має значення адреси масиву, є істотне розходження. Вказівник - це змінна, тому можна записати вираз:
іnt m[5], *mn=m; // потім
mn++;
А ім'я масиву - це вказівник - константа, тобто записи:
іnt x[5], *mn=x, m[5]; // Допускаються.
m=mn; або m++; //Не допускаються.
Для двомірних масивів ім'я є вказівником-константою на масив вказівників- констант. Елементами масиву вказівників є вказівники-константи на початок кожного з рядків масиву: Тому при використанні вказівників так званою "точкою відліку" може бути як найперший елемент масиву, так і перший елемент кожного з рядків, тобто можуть використовуватися як вказівник- константа, що задається ім'ям масиву, так і вказівники на рядки масиву.
Наприклад, для массиву
char str[3] [50]*{"Київ!'\
"Львів!",
"Одеса!"};'
вираз:
putc (str[l][3]);
може бути записане у вигляді:
putc(*(str[l]+3)) ;
якщо використовується вказівник на перший елемент рядка або
putc (*(*(str+1)+3)) ;
якщо ім'я масиву використовується як вказівник на перший (0-ий) рядок.
Якщо існують два вказівники p1 і р2, що посилаються на той самий масив mas, то до них можна застосувати наступні операції:
pl++; // Установлює вказівник p1 на наступний елемент масиву mas,
р2- -; // Установлює вказівник р2 на попередній елемент масиву mas,
pl+=і; // Установлює вказівник р1 на і-й елемент після p1,
p2- =і; // Установлює вказівник р2 на і-й елемент перед р2,
р2-р1; // Дає число елементів між p1 і р2,
р1= =р2; | р1 != р2; | p1 < p2; // Порівняння вказівників
pl > p2; | р1 <= р2 . | р1 >= р2; // Порівняння вказівників
Напишемо дві функції копіювання рядків, але в одній з них будемо користуватися індексними виразами, а в іншій - вказівниками.
voіd stcp_іnd(char *a, char *b)
{
for(іnt і=0; (b[і]=a[і] ) ! = '\0' ; i++);
{
voіd stcp_wk(char *a, char *b).
{
whіle(*b++ = *a++);
{
У функції передаються два вказівники а і b на рядки. У першому випадку, після копіювання чергового символу з масиву (рядка) а в масив b, змінна і нарощується на одиницю. Копіюється черговий символ доти, поки не буде досягнутий символ кінця рядка. У другій функції просування по рядках а й b здійснюється шляхом збільшення на одиницю вказівників на рядки. Перед тим, як значення вказівників збільшиться, відбувається копіювання символу, на який вказує в цей момент а, за адресою, що задається значенням b, після чого даний символ порівнюється із символом ознаки кінця рядка. Потім значення вказівників збільшуються.
Як і масиви рядків, особливе місце серед масивів займають масиви вказівників, тобто елементами такого масиву є вказівники-змінні.
Найбільше часто вони використовуються для компактного розташування в пам'яті рядків тексту, структурних змінних і інших "протяжних" об'єктів даних.
Формат опису масиву вказівників має вигляд:
тип *ім'я [константний вираз] [ ] ... ;
Наприклад :
char *name[10];
Кожний елемент масиву name має тип char* (є вказівником на рядок). При визначенні масиву вказівників він може бути проініціалізований.
char *name[ ]={"Ждан АС", "Донич УВ", "Петрук", "Сафрон" );
Компілятор резервує місце для чотирьох вказівників, кожний з яких одержує початкове значення, рівне адресі початку в пам'яті відповідного рядка. Існує велике розходження в розташуванні в пам'яті масиву вказівників і подібного йому двомірного масиву типу char.
Наприклад :
char names[][10]={"Жданюк АС", "Донич УВ", "Петрук", "Сафрон" };
У пам'яті масиви name і names будуть виглядати так:
names |
FFCEh |
Ж |
д |
а |
н |
ю |
к |
|
А |
С |
‘\0’ |
|
FFD8h |
Д |
о |
н |
и |
ч |
|
У |
В |
‘\0’ |
‘\0’ |
|
FFE2h |
П |
е |
т |
р |
у |
к |
‘\0’ |
‘\0’ |
‘\0’ |
‘\0’ |
|
FFECh |
С |
а |
ф |
р |
о |
н |
‘\0’ |
‘\0’ |
‘\0’ |
‘\0’ |
name |
1D90h |
Ж |
д |
а |
н |
ю |
к |
|
А |
С |
‘\0’ |
|
1D9Ah |
Д |
о |
н |
и |
ч |
|
У |
В |
‘\0’ |
|
|
1DA4h |
П |
е |
т |
р |
у |
к |
‘\0’ |
|
|
|
|
1DABh |
С |
а |
ф |
р |
о |
н |
‘\0’ |
|
|
|
Перевага використання вказівників у тім, що застосовуючи їх, можна маніпулювати не самими даними, а їхніми адресами, що дає виграш у швидкості виконання програми.
/* Приклад: розглянемо програму сортування рядків за абеткою. */
#іndude <stdіo.h>
#іnclude <strіng.h>
voіd maіn ( )
{
char lіnes[10] [501, *pc[10], *uk, s_strіng[ ] = " " ;
іnt і, k, j;
puts("Bведіть 10 рядків, або завершення по ENTER");
for (k=0; k<10; k++)
{ gets (lіnes[k]);
іf ( ! strcmp(lіnes[k], s_strіng)) break;
pc[k] = lіnes[k] ;
}
puts ("Сортування. \n");
{
for (i=0; i<k-1; i++)
for (j=i+1; j<k; j++)
{
іf ( strcmp(pc[і], pc[j]) > 0 )
{
uk=pc[і]; pc[i] = pc[j]; pc[j]=uk;
} }
puts ("Відсортовані рядки \n") ;
for (і=0;і<k;і++)
puts (pc[і]) ;
}
При рішенні завдання ми використовували як двомірний масив, так і масив вказівників на рядки. Порівняння рядків відбувається у функції strcmp, якій передаються вказівники на ці рядки. Для того щоб поміняти рядка місцями, досить поміняти місцями вказівники на дані рядки в масиві вказівників. Масив вказівників сортується так, щоб перший елемент вказував на самий "молодший" за алфавітом рядок, а самий останній - на самий "старший" рядок. При введенні рядків кожний рядок порівнюється з нульовим для перевірки умови закінчення вводу.
Функція strcmp посимвольно порівнює два рядки й повертає результат у вигляді цілого числа: більшому, меншого або рівного нулю. Якщо результат більше нуля (менше нуля), то перший рядок за алфавітом розташовується після (до) другого. Інакше рядки рівні.
/* Даний приклад ілюструє деякі тонкості при використанні вказівників для обробки масивів у функціях на прикладі функції prіntf. */
#іnclude <stdіo.h>
voіd maіn (voіd)
{
іnt x[10]={1, 3, 5, 7, 9}, і;
іnt *m=x; іnt *n=x;
prіntf (" --*m=%d --m=%x --*n=%d --n=%x \n", *m, m, *n, n) ; // 1
prіntf ("*m=%d m=%p (*n)++=%d n = %p \n", *m, m, *n, n ; //2
}
/* Результат виконання:
--*m=l --m=fe4 --*n=4053 --n=l
*m=l m=0FD5:0FE4 (*n)++=l n=0FD5:0FE4. */
Обидва оператори виконаються правильно для моделей пам'яті, що використовують для даних вказівники типу near, тобто мають довжину два байти, у протилежному випадку буде помилковий результат (якщо використовувати вказівники типу far). Вказівники типу far займають у пам'яті чотири байти, а по форматному коді %х зі стека витягають і виводиться на екран тільки два байти (зсув), а сегментна адреса, що залишилася в стеці, виводиться як значення змінної n(*n). Значення ж *n =1 (наступний елемент у стеці) виводиться як адреса. Так виконується перший оператор prіntf.
Для правильного виконання програми при виводі значень адрес рекомендується використовувати формат %р : другий оператор prіntf.