Ланцюжкова константа, записана як
"I am a string"
являється масивом знаків. У внутрішньому представленні, такий масив закінчується нульовим символом '\0', щоб програми могли знайти кінець символьного масиву. Розмір для збереження, таким чином, буде на одиницю більшим за кількість знаків в лапках. Напевне, найчастіше ланцюжкові константи можна зустріти в якості аргументів функцій, як от
printf("hello, world\n");
Коли ланцюжок знаків як цей з'являється в програмі, доступ до нього відбувається через покажчик на символ; printf отримує покажчик на початок символьного масиву. Іншими словами, доступ до ланцюжкової константи відбувається через покажчик на її перший елемент.
Ланцюжкові константи не обов'язково являються аргументами функцій. Якщо pmessageоголошено як
char *pmessage;
тоді твердження
pmessage = "now is the time";
присвоює pmessage покажчик на символьний масив. Тут не відбувається жодного копіювання ланцюжків; задіяні лишень покажчики. C не передбачає жодних операторів для обробки цілого ланцюжка символів як єдиного цілого.
Існує одна важлива різниця між цими двома визначеннями:
char amessage[] = "now is the time"; /* масив */
char *pmessage = "now is the time"; /* покажчик */
Тут, amessage — це масив, досить великий, щоб втримати послідовність знаків і кінцевий'\0', які йому присвоєно. Можна змінювати окремі знаки всередині масиву, але amessageзавжди вказуватиме на те саме місце збереження в пам'яті. Напротивагу, pmessage — це покажчик, ініційований для того, щоб вказувати на ланцюжкову константу; покажчик згодом можна змінити так, щоб він вказував на інше місце, але ви отримаєте невизначений результат, якщо спробуєте змінити зміст самого ланцюжка.
+---+ +-------------------+
pmessage: | *-|---->| now is the time\0 |
+---+ +-------------------+
+-------------------+
amessage: | now is the time\0 |
+-------------------+
Ми проілюструємо ще деякі риси покажчиків і масивів шляхом вивчення версій двох корисних функцій, адаптованих зі стандартної бібліотеки. Першою функцією є strcpy(s,t), яка копіює ланцюжок t до ланцюжка s. Було би простіше, якби можна було написати просто s=t, але це копіює покажчик, а не символи. Для копіювання знаків нам потрібний цикл. Спершу, версія з масивом:
/* strcpy: копіює t до s; версія з індексованим масивом */
void strcpy(char *s, char *t)
{
int i;
i = 0;
while ((s[i] = t[i]) != '\0')
i++;
}
Для порівняння, тут таки подаємо версію з покажчиками:
/* strcpy: копіює t до s; версія з покажчиками */
void strcpy(char *s, char *t)
{
int i;
i = 0;
while ((*s = *t) != '\0') {
s++;
t++;
}
}
Так як аргументи передаються за значенням, strcpy може використовувати параметри s із t як їй заманеться. Тут це зручно ініційовані покажчики, якими проходяться по масиву, кожен символ по-черзі доти, доки '\0', який завершує t, не копійовано до s. На практиці, strcpy не було би написано так як ми от це показали. Досвідчені C-програмісти надали би перевагу наступному
/* strcpy: копіює t до s; друга версія з покажчиками */
void strcpy(char *s, char *t)
{
while ((*s++ = *t++) != '\0')
;
}
У цьому варіанті, приріст s із t перенесено в тестову частину циклу. Значенням *t++ являється символ, на який вказував t до того як відбувся приріст; постфіксний ++ не змінює t доти, доки не здобуто символ. Аналогічно, символ буде збережоно до старого положення s до того як збільшити s. Цей символ, одночасно, являється значенням, яке прирівнюється до '\0' для контролю над циклом. В кінцевому результаті, символи копійовано з t до s аж до завершального '\0', включно.
Як остаточне скорочення, зверніть увагу, що порівнювання з '\0' насправді — зайве, оскільки питання тільки в тому, чи вираз являється нульовим. Тож функцію, скоріш за все, було би написано як
/* strcpy: копіює t до s; третя версія з покажчиками */
void strcpy(char *s, char *t)
{
while (*s++ = *t++)
;
}
Хоч це може видатись зашифрованим, з першого погляду, важливою є зручність нотації і цю ідіому варто засвоїти, так як ви часто побачите її в C-програмах. Функція strcpy зі стандартної бібліотеки (<string.h>) повертає кінцевий ланцюжок як значення функції.
Другою функцією, яку ми розглянемо, є strcmp(s,t), яка порівнює символьний ланцюжок sіз t і повертає від'ємне, нуль або додатнє значення, якщо лексикографічно s менший, рівний або більший за t. Результат буде отримано шляхом віднімання знаків у першому ж положенні, де sіз t не узгоджуються.
/* strcmp: повертає <0, якщо s<t; 0, якщо s==t; >0, якщо s>t */
int strcmp(char *s, char *t)
{
int i;
for (i = 0; s[i] == t[i]; i++)
if (s[i] == '\0')
return 0;
return s[i] - t[i];
}
Версія strcmp із використанням покажчиків:
/* strcmp: повертає <0, якщо s<t; 0, якщо s==t; >0, якщо s>t */
int strcmp(char *s, char *t)
{
for ( ; *s == *t; s++, t++)
if (*s == '\0')
return 0;
return *s - *t;
}
Оскільки ++ та -- це або префіксні або постфіксні оператори, інші комбінації з * або ++ або --теж можуть мати місце, хоч і не так часто, Наприклад,
*--p
зменшує p до того як отримати символ, на який вказує p. Насправді, набір виразів
*p++ = val; /* проштовхнути val у стек */
val = *--p; /* виштовхнути верхівку стеку і присвоїти *
* це значення val */
являються стандартними ідіомами проштовхування і виштовхування зі стеку; подивіться Розділ 4.3.
Файл заголовка <string.h> містить оголошення функцій, згаданих у цьому розділі, так само різноманітних інших стандартної бібліотеки по обробці ланцюжків.
Вправа 5-3. Напишіть версію з покажчиками функції strcat, яку ми пройшли в Розділі 2;strcat(s,t) копіює ланцюжок t в кінець s.
Вправа 5-4. Напишіть функцію strend(s,t), яка повертає 1, якщо ланцюжок t знайдено в кінці ланцюжка s, і нуль — якщо ні.
Вправа 5-5. Напишіть версії бібліотечних функцій strncpy, strncat і strncmp, які би діяли на щонайбільше n символів власних аргументів-ланцюжків. Наприклад, strncpy(s,t,n) копіює максимум n знаків t до s. Повні описи знаходяться в Додатку Б.
Вправа 5-6. Перепишіть наново відповідні програми та вправи з попередніх розділів, використовуючи покажчики замість індексації масивів. Хороші можливості складають getline(з розділів 1 і 4), atoi, itoa та їхні варіанти (Розділ 2, 3 та 4), reverse (Розділ 3) таstrindex і getop (Розділ 4).