Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгоритмізація та програмування.docx
Скачиваний:
84
Добавлен:
17.05.2015
Размер:
1.35 Mб
Скачать

Лекція 13. Покажчики в c

Що таке покажчики?

Покажчики — це ті ж змінні. Різниця в тому, що замість того, щоб зберігати певні дані, вони зберігають адресу (покажчик), де дані можуть бути знайдені. Концептуально це дуже важливо. Багато програм і ідей залежать від покажчиків, як від основи їх архітектури, наприклад, пов'язані списки (linked lists).

Вступ

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

int *pNumberOne;

int *pNumberTwo;

Звернули увагу на префікс "p" в обох іменах змінних? Це прийнятий спосіб позначити, що змінна є покажчиком. Так звана угорська нотація. Тепер давайте зробимо так, щоб покажчики на що-небудь вказували:

pNumberOne = &some_number;

pNumberTwo = &some_other_number;ц

Знак & (амперсанд) слід читати як "адреса змінної .". і означає адресу змінної в пам'яті, який буде повернений замість значення самій змінній. Отже, в даному прикладі pNumberOne встановлений і містить адресу змінної some_number, також pNumberOne вказує на some_number.

Таким чином, якщо ми хочемо отримати адресу змінної some_number, ми можемо використовувати pNumberOne. Якщо ми хочемо отримати значення змінної some_number через pNumberOne, треба додати зірочку (*) перед pNumberOne (*pNumberOne). Зірочка (*) розіменовує (перетворює на саму змінну) покажчик і повинна читатися як "місце в пам'яті, яке вказується через ."., окрім оголошень, як в рядку int *pNumber.

Приклад:

#include <stdio.h>

void main()

{

// оголошуємо змінні:

int nNumber;

int *pPointer;

// ініціалізували оголошені змінні:

nNumber = 15;

pPointer = &nNumber;

// виводимо значення змінної nNumber :

printf("nNumber is equal to: %d", nNumber);

// тепер змінюваний nNumber через pPointer:

*pPointer = 25;

// переконаємося що nNumber змінив своє значення в результаті попередньої дії

// вивівши значення змінної ще раз

printf("nNumber is equal to: %d", nNumber);

}

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

Пастка!

Спробуйте знайти помилку в цій програмі:

#include <stdio.h>

int *pPointer;

void SomeFunction()

{

int nNumber;

nNumber = 25;

// робимо так, щоб pPointer вказував на nNumber

pPointer = &nNumber;

}

void main()

{

SomeFunction(); // зробіть щоб pPointer вказував на що не-будь

// чому це не працює?

printf("Value of *pPointer: %d", *pPointer);

}

Програма спочатку викликає функцію SomeFunction, яка створює змінну nNumber, а потім ініціалізує pPointer, щоб він вказував на nNumber. Далі починаються проблеми. Коли функція завершується, nNumber віддаляється, оскільки це локальна змінна. Локальні змінні завжди віддаляються, коли виконання програми виходить з блоку, де ці змінні були оголошені. Це означає, що коли функція SomeFunction завершується і виконання повертається в main(), змінна зникає. Таким чином pPointer вказує на ту область пам'яті, де була змінна, але яка вже не належить програмі. Якщо ви не зовсім зрозуміли, про що йде мова, ще раз прочитайте про локальні і глобальні змінні, а також про області визначення (scope). Ця концепція також дуже важлива.

Отже, як проблема може бути розв'язана? Відповідь - використанням техніки відомої як динамічне виділення пам'яті. Зверніть увагу, що в мові Cі такої техніки немає, а приклад нижче відноситься до C++.

Передача покажчиків у функції

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

#include <stdio.h>

void AddFive(int Number)

{

Number = Number + 5;

}

void main()

{

int nMyNumber = 18;

printf("My original number is %d\n", nMyNumber);

AddFive(nMyNumber);

printf("My new number is %d\n", nMyNumber);

}

Проте тут проблема в тому, що змінна Number, до якої ми звертаємося усередині функції, - це копія змінної nMyNumber, що передається у функцію. Таким чином, рядок Number = Number + 5 додає п'ять до копії змінної, залишаючи оригінальну змінну в main() незмінній. Спробуйте запустити програму, щоб переконатися в цьому.

Щоб позбавитися від цієї проблеми, ми можемо передавати у функцію покажчик на місце в пам'яті, де зберігається число, але тоді ми повинні поправити функцію, щоб вона приймала покажчик замість числа. Для цього змінимо void AddFive(int Number) на AddFive(int* Number) додаванням зірочки. Тут знову текст програми, з внесеними змінами. Зверніть увагу, що ми повинні переконатися, що передаємо у функцію адресу nMyNumber замість самого числа. Це зроблено за допомогою оператора &, який (як ви пам'ятаєте), читається як "отримати адресу".

#include <stdio.h>

void AddFive(int *Number)

{

*Number = *Number + 5;

}

void main()

{

int nMyNumber = 18;

printf("My original number is %d", nMyNumber);

AddFive(&nMyNumber);

printf("My new number is %d", nMyNumber);

}

Спробуйте придумати свій власний приклад для демонстрації цих можливостей покажчиків. Звернули увагу на важливість зірочки (*) перед Number у функції AddFive? Це необхідно для того, щоб вказати компілятору що ми хочемо додати 5 до числа на яке вказує змінна Number, а не додати 5 до самого покажчика.

Наступна програма міняє зміст двох змінних :

#include <stdio.h>

void Swap (int *pOne, int *pTwo){

int temp;

temp = *pOne;

*pOne = *pTwo;

*pTwo = temp;

}

void main()

{

int a = 1, b = 20;

printf("Original numbers is %d %d", a, b);

Swap(&a,&b);

printf("New numbers is %d %d", a, b);

}

Останнє зауваження з приводу функцій це те що ви можете повертати з них покажчики, це робиться так:

int *MyFunction();

В даному прикладі, MyFunction повертає покажчик на ціле число (int).

Покажчики на масиви

Ви також можете створювати покажчики які вказують на масиви. Це робиться так:

int *pArray;

int MyArray[6];

pArray = &MyArray[0];

Зверніть увагу, що замість написання &MyArray[0], ви можете просто написати MyArray. Це, звичайно ж, застосовано тільки до масивів унаслідок способу їх реалізації в мовах C/C++. За загальними правилами необхідно було б написати pArray = &MyArray, але це неправильно. Якщо ви так напишіть те отримаєте покажчик на покажчик на масив (не друкарська помилка), що ясно не те що вам потрібне.

Використання покажчиків на масиви

Якщо у вас є покажчик на масив, як його використовувати? Наприклад у вас є покажчик на масив цілих чисел (int[]). Покажчик спочатку вказуватиме на перше значення в масиві як показує наступний.

Приклад:

#include <stdio.h>

void main()

{

int Array[3];

Array[0] = 10;

Array[1] = 20;

Array[2] = 30;

int *pArray;

pArray = &Array[0];

printf("pArray points to the value %d\n", *pArray);

}

Для того, щоб перемістити покажчик до наступного елементу масиву, ми можемо зробити pArray++. Ми також можемо, як деякі з вас могли вже догадатися, зробити pArray+2, що пересуне покажчик відразу на 2 елементи. З чим треба бути обережним так це з верхньою межею масиву (в даному випадку це 3 елементи), тому що компілятор не може перевірити чи вийшли ви за межу масиву використовуючи покажчики. Ви легко можете отримати повний збій системи якщо будете не обережні. Ось ще один приклад, що цього разу показує три значення які ми встановили :

#include <stdio.h>

void main()

{

int Array[3];

Array[0] = 10;

Array[1] = 20;

Array[2] = 30;

int *pArray;

pArray = &Array[0];

printf("pArray points to the value %d\n", *pArray);

pArray++;

printf("pArray points to the value %d", *pArray);

pArray++;

printf("pArray points to the value %d\n", *pArray);

}

Ми також можемо рухати покажчик у будь-яку сторону, так pArray - 2 це 2 елементи від того місця куди вказує покажчик. Переконаєтеся що ви додаєте і віднімає значення покажчика, а не у значення на яке він вказує. Цей метод використання покажчиків і масивів понад усе корисний при використанні циклів, таких як for або while.

Помітимо так само, що якщо ми маємо покажчик на значення, наприклад int* pNumberSet, ми можемо звертатися до нього як до масиву. Ось приклад, pNumberSet[0] рівне *pNumberSet; а pNumberSet[1] рівно *(pNumberSet + 1).

Це не абсолютно повне керівництво по роботі з покажчиками. Є ще багато речей, які я міг би розкрити детальніше, таких як покажчики на покажчики, і тим, яких я не торкнувся взагалі, наприклад, функціональних покажчиків, які занадто складні для цієї статті. Так само є речі, які використовуються занадто рідко, щоб збивати початківців з пантелику великою кількістю деталей. От і все! Спробуйте запустити код представленый в цій статті і придумати ще які нибудь свої варіації і приклади. (За мотивами (с) Andrew Peace)

Практика

Описати процедуру AddRightDigit(D, K), що додає до цілого позитивного числа K справа цифру D (D — вхідний параметр цілого типу, що лежить в діапазоні 0-9, K — параметр цілого типу, що являється одночасно вхідним і вихідним). За допомогою цієї процедури послідовно додати до цього числа K справа ці цифри D1 і D2, виводячи результат кожного додавання.

Описати процедуру SortArray(A, N), виконуюче сортування за збільшенням речового масиву A розміру N. Масив A є вхідним і вихідним параметром. За допомогою цієї процедури відсортувати масиви A, B, C розміру NA, NB, NC відповідно.

Описати рекурсивну функцію Fact(N) речового типу, що обчислює значення факторіалу

N! = 1*2*...*N

(N > 0 — параметр цілого типу). За допомогою цієї функції вичислити факторіали п'яти цих чисел.