Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
knrc (1).pdf
Скачиваний:
12
Добавлен:
12.02.2016
Размер:
925.69 Кб
Скачать

Роздiл 5

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

Покажчиком є змiнна, яка мiстить адресу iншої змiнної. Покажчики часто використовуються в C, частково тому, що це iнодi єдиний спосiб виразити обчислення, та частково тому, що вони, як правило, призводять до компактнiшого i ефективнiшого коду, нiж той, що можна написати iншим способом. Покажчики та масиви тiсно по- в’язанi мiж собою; цей роздiл також дослiджує їхнiй взаємозв’язок i демонструє як скористатися з нього.

Покажчики iнодi поєднують з твердженнями goto, як чудовий спосiб створення програм, якi неможливо зрозумiти. Це правда, якщо користуватися ними недбало. Так само досить легко створити покажчики якi вказують кудись непередбачувано. Однак, за певної дисциплiни, завдяки покажчикам можна досягти ясностi та простоти. Саме цю їхню рису ми намагатимемось проiлюструвати.

Основною змiною ANSI C було внесення ясностi щодо того, як можна манiпулювати покажчиками, фактично роблячи обов’язковим те, що хорошi програмiсти практикують, i до чого хорошi компiлятори примушують. На додаток, тип void * (порожнiй покажчик) замiнює char *, як належний тип загального покажчика.

5.1Покажчики й адреси

Почнiмо зi спрощеного зображення того як органiзовано пам’ять. Типова машина має масив послiдовно нумерованих або адресованих комiрок (секцiй) пам’ятi, якими можна орудувати окремо або прилеглими групами. Поширеним випадком є, коли один байт може складати char, тодi як пара однобайтових комiрок розглядається як коротке цiле (short int), а чотири сумiжних байти утворюють довге цiле. Покажчик — це група комiрок (часто двi або чотири), що можуть утримати адресу. Тож, якщо c — це char, а p — це покажчик, що вказує на адресу c, то ми можемо графiчно зобразити цю ситуацiю як наступне:

Унарний оператор & добуває адресу об’єкта, тож твердження

p = &c;

105

106

РОЗДIЛ 5. ПОКАЖЧИКИ ТА МАСИВИ

присвоює адресу c змiннiй p, i p, як говорять, вказує на c. Оператор & застосовується лише з об’єктами з пам’ятi — змiнними й елементами масивiв. Його неможливо використати з виразами, константами або регiстровими змiнними.

Унарний * є оператором непрямого звертання або розiменування . Коли його застосовано до покажчика, вiн дає доступ до об’єкта, на який вказує покажчик. Припустiмо, що x та y є цiлими, а ip — це покажчик на int. Наступний штучний приклад демонструє як оголосити покажчик, i як користуватися & та *:

int x = 1, y = 2, z[10];

 

int *ip;

/* ip є покажчиком на int */

ip = &x;

/* ip тепер вказує на x */

y = *ip;

/* y дорiвнює тепер 1

*/

*ip = 0;

/* x дорiвнює тепер 0

*/

ip = &z[0];

/* ip тепер вказує на z[0] */

Оголошення змiнних x, y та z нам зрозумiле. Оголошення ж покажчика ip

int *ip;

задумане як мнемонiка (як символiчне); воно вказує на те, що вираз *ip є типу int. Синтаксис оголошення змiнної iмiтує синтаксис виразiв, в яких змiнна може з’явитися. Ця сама система застосовується також при оголошеннi функцiй. Наприклад,

double *dp, atof(char *);

вказує на те, що вирази *dp i atof(s) повертають значення типу double i, що аргументом atof є покажчик на char.

Ви також повиннi звернути увагу на те, що покажчик обмежений вказувати тiльки на окремий рiд об’єктiв: кожний покажчик вказує на певний тип даних. (Iснує один виняток — покажчик на void (порожнiй покажчик), використовуваний для утримування будь-якого типу покажчикiв, але до якого неможливо непрямо звернутися. Ми повернемося до нього у Роздiлi 5.11.)

Якщо ip вказує на цiле x, тодi *ip може з’являтися в будь-якому контекстi, в якому може x, тож

*ip = *ip + 10;

5.2. ПОКАЖЧИКИ Й АРГУМЕНТИ ФУНКЦIЙ

107

збiльшує *ip на 10.

Унарнi оператори * та & прив’язанi тiснiше нiж арифметичнi оператори, тож присвоєння

y = *ip + 1;

вiзьме те, на що вказує ip, додасть 1 i присвоїть отриманий результат y, тодi як

*ip += 1;

здiйснює прирiст того, на що вказує ip; те саме стосується

++*ip;

та

(*ip)++;

В останньому прикладi, дужки обов’язковi; без них, вираз збiльшив би саму ip (тобто адресу) замiсть того, на що вона вказує, оскiльки унарнi оператори такi як * й ++ асоцiюються (спрягаються) справа налiво.

I, нарештi, оскiльки покажчики, це також змiннi, їх можна виживати без непрямого звертання. Наприклад, якщо iq — це iнший покажчик на int, то

iq = ip;

копiює вмiст ip до iq, таким чином примушуючи iq вказувати на той самий об’єкт, на який вказує ip.

5.2Покажчики й аргументи функцiй

Оскiльки C передає тiльки значення аргументам функцiй, не iснує прямого способу для викликаної функцiї змiнити значення змiнної викликової. Для прикладу, певна функцiя сортування може обмiнюватися двома невпорядкованими аргументами з функцiєю пiд назвою swap. Недостатньо написати

swap(a, b);

там де функцiю swap визначено як

108

РОЗДIЛ 5. ПОКАЖЧИКИ ТА МАСИВИ

void swap(int x, int y)

/* НЕПРАВИЛЬНО */

{

 

int temp;

 

temp = x;

 

x = y;

 

y = temp;

 

}

 

Отримуючи тiльки значення, swap не може вплинути на самi змiннi а i b у функцiї, яка викликала swap. Рутина, наведена вище, мiняє мiсцями тiльки копiї a та b.

Щоб досягти бажаного ефекту, потрiбно, щоб викликова програма передала покажчики на значення, якi потрiбно помiняти:

swap(&a, &b);

Оскiльки оператор & добуває адресу змiнної, &a буде покажчиком на a. А в самiй функцiї swap, параметри необхiдно оголосити як покажчики, i через їхнє посередництво дiстатися самих операндiв.

void swap(int *px, int *py) /* мiняє мiсцями *px i *py */

{

int temp;

temp = *px; *px = *py; *py = temp;

}

Графiчно:

5.2. ПОКАЖЧИКИ Й АРГУМЕНТИ ФУНКЦIЙ

109

Аргументи-покажчики забезпечують функцiю доступом до об’єктiв викликової функцiї, i можливiстю їх змiнювати. Як приклад, уявiть собi функцiю getint, яка здiйснює перетворення вводу вiльного формату, розбиваючи потiк знакiв на цiлi величини, по одному цiлому числу на один виклик функцiї. getint повинна повернути обчислене значення а також сигналiзувати кiнець файла, коли ввiд закiнчився. Цi значення потрiбно передати назад рiзними шляхами, оскiльки незалежно вiд того, яке значення використовується для EOF, це також могло би бути чинним значенням введеного цiлого.

Одне з рiшень, це щоб getint повертала вказiвник кiнця файла як кiнцеве значення самої функцiї, одночасно використовуючи аргументи-покажчики для збереження перетворених цiлих у викликовiй функцiї. Саме ця схема використовується також у scanf (дивiться Роздiл 7.4).

Наступний цикл заповнює масив цiлими викликаючи getint:

int n, array[SIZE], getint(int *);

for (n = 0; n < SIZE && getint(&array[n]) != EOF; n++)

;

Кожний виклик присвоює array[n] значення наступного цiлого, знайденого у вводi, i нарощує n. Звернiть увагу, що суттєво вказати саме адресу array[n] функцiї getint, iнакше getint не має способу передати перетворене цiле назад викликовiй функцiї.

Наша версiя getint повертає EOF у випадку кiнця файла, нуль — якщо наступний знак вводу не є цiлим, i додатнє значення — якщо ввiд мiстить чинне цiле число.

#include <ctype.h>

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]