Теоретичні відомості#

Масиви типу char, або символьні масиви#

Мова С не містить стандартного типу даних рядок. Замість цього вона підтримує масиви символів, які завершуються нуль-символом '\0' (символ з ASCII-кодом 0). Функції для роботи з такими масивами описано в заголовковому файлі string.h. Вони дають змогу досягти високої ефективності, але небезпечні у використанні, оскільки вихід за межі рядка не перевіряється.

Розташування нуль-символа визначає фактичну довжину рядка. Наприклад, довжина рядка char text[] = "Моя програма!" дорівнює 25 байтів.

Тут необхідно згадати, що кирилічні символи кодуються багатобайтовими послідовностями. У випадку UTF-8 це 2 байти на символ, звідси \(2 * 11 + 3 = 25\), де три однобайтових символи це пробіл, знак оклику та прикінцевий нуль-символ.

Стандартні функції роботи з рядками зі string.h очікують, що символи закодовані у ASCII-коді. Для маніпулювання UTF-8 текстом необхідно використовувати сторонні бібліотеки, наприклад utf8.h.

Часто, навіть якщо ви правильно маніпулюєте багатобайтовими кодуваннями, вони можуть відображатися у консолі некоректно — як ? чи — через настройки кодової стрінки самого терміналу.

На Windows це можна виправити виконавши команду chcp 65001. Інструкції для MacOS. Інструкції для різних дистрибутивів та емуляторів терміналів у Linux можуть відрізнятися, наприклад, для Ububtu.

Символьна константа — це символ (один), оточений одинарними лапками, наприклад, 'Х'. Значенням символьної константи є чисельне значення цього символу в машинному поданні набору символів. Усі символи впорядковано відповідно до кодів ASCII. При цьому порядковий номер символу називають кодом (наприклад, код латинського символу 'А' дорівнює 65; код символу '3' дорівнює 51). Для символьних даних не визначено арифметичних операцій, але їх можна порівнювати за своїми кодами, використовувати в читанні, друку, операторах присвоювання.

У програмі рядки можна визначати таким чином:

  • як рядкові константи;

  • як масиви символів;

  • через указівник на символьний тип;

  • як масиви рядків.

Для зберігання рядка обов’зково має бути передбачено виділення пам’яті.

Будь-яка послідовність символів, оточена в подвійні лапки "", розглядається як рядкова константа. Для коректного виведення будь-який рядок повинен закінчуватися нуль-символом '\0'. Під час оголошення рядкової константи нуль-символ додається до неї автоматично. Під зберігання рядка виділяють елементи оперативної пам’яті, що йдуть послідовно. Таким чином, рядок є масивом символів. Для зберігання коду кожного символу рядка відводиться 1 байт.

Для розміщення в рядковій константі деяких службових символів використовують символьні комбінації. Зокрема, якщо потрібно включити в рядок символ подвійної лапки, йому повинен передувати символ '\' (зворотний слеш). Рядкові константи розміщують у статичній пам’яті. Початкову адресу послідовності символів у подвійних лапках трактують як адресу рядка.

Під час визначення масиву символів потрібно повідомити компілятору розмір пам’яті, який треба відвести під нього, наприклад:

char m[82];

Компілятор також може самостійно визначити розмір масиву символів, якщо ініціалізацію масиву задано під час оголошення за допомогою рядкової константи.

Приклад 3.1#

Розгляньмо декларацію та ініціалізацію рядка:

#include <stdio.h>

char cString1[] = "A new string is:";
char cString2[] = { 'A', ' ', 'n', 'e', 'w', ' ', 's', 't', 'r', 
                    'i', 'n', 'g', ' ', 'i', 's', ':', '\0' };

int main(void)
{
    printf("%s\n", cString1);
    scanf("%s", cString1);
    printf("\n%s\n%s", cString1, cString2);
}

Варто звернути увагу на виклики функції printf(). Параметр форматування %s змінює її синтаксис: тепер аргументом має бути вказівник на рядок. Функція scanf() завершує уведення після натискання пробілу або клавіші Enter.

Рядки cString1 і cString2 абсолютно ідентичні під час ініціалізації. Символьний масив cString1 складається з 17 елементів, хоча друкованих символів у ньому 16. Ця невідповідність пояснюється керуючим символом, що позначає кінець рядка. У випадку формування символьного масиву в процесі виконання програми потрібно поклопотатися про коректне закінчення рядка, оскільки бібліотекові функції використовують цю ознаку.

Для задавання рядка можна використовувати вказівник на символьний тип. Іноді в програмах виникає потреба опису масиву символьних рядків. У цьому випадку можна використовувати індекс рядка для доступу до декількох різних рядків.

Більшість операцій мови С, що мають справу з рядками, працює зі вказівниками. Для розміщення в оперативній пам’яті рядка символів потрібно:

  • виділити під масив блок оперативної пам’яті;

  • ініціалізувати рядок.

Для виділення пам’яті під зберігання рядка можна використовуватифункції динамічного виділення пам’яті. При цьому потрібно враховувати розмір рядка:

char* name;
name = (char*) malloc(10);
scanf("%9s", name);

У вищенаведеному фрагменті для уведення рядка використано функцію scanf(), при цьому уведений рядок не може перевищувати 9 символів. Останній символ рядка буде '\0'.

Функції уведення та виведення рядків#

Для уведення рядка можна використовувати функцію scanf(). Проте, функцію scanf() призначено радше для отримання не рядка, а окремого слова. Якщо застосовувати формат "%s" для уведення, рядок уводиться до (але не включаючи) наступного порожнього символу, яким може бути пробіл, табуляція або символ нового рядка.

Для уведення рядка, включаючи пробіли, використовують таку функцію:

char* gets(char*);

Указівник на рядок, у який здійснюється уведення, передають як аргумент функції. Функція просить користувача увести рядок, який вона поміщає в масив. Уведення завершується, коли користувач натисне клавішу Enter.

Зауваження 1. Функція gets читає все, що набирає користувач, доти, доки він не натисне клавішу Enter. Код клавіші Enter не розміщується в рядку. У кінець рядка додається символ '\0'.

Зауваження 2. Якщо під час уведення рядка кількість знаків перевищить його розмір, установлений під час його опису, то це швидше за все призведе до збою роботи програми, оскільки зайві знаки буде записано в пам’ять «чужої» області, не зарезервованої під рядок.

Для виведення рядків можна використати розглянуту раніше функцію printf("%s", str), або її скорочений формат: printf(str), де str — указівник на рядок.

Для виведення рядків також можна використовувати таку функцію:

int puts(char* s);

Функція друкує рядок s і переводить курсор на новий рядок (на відміну від printf()). Функцію puts() також можна використовувати для виведення рядкових констант, оточених лапками.

Функції уведення та виведення символів#

Для уведення символів можна використовувати таку функцію:

char getchar();

Функція повертає значення символу, уведеного з клавіатури. Зазначену функцію використовують для затримки вікна консолі після виконання програми до натискання клавіші.

Для виведення символів можна використовувати таку функцію:

char putchar(char c);

Функція повертає значення символу, що виводиться, і виводить на екран символ, переданий у якості аргумента.

Приклад 3.2#

У наступній програмі реалізується підрахунок кількості символів в уведеному рядку.

#include <stdio.h>
int main()
{
    char str[80], sym;
    int count;

    printf("Уведіть рядок: ");
    gets(str);
    printf("Уведіть символ: ");
    sym = getchar();

    count = 0;
    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] ==  sym)
            count++;
    }
    printf("У рядку\n");
    puts(str);    /* виведення рядка */
    printf("символ ");
    putchar(sym); /* виведення символу */
    printf(" зустрічається %d разів\n", count);
    getchar();

    return 0;
}

Основні функції бібліотеки string.h наведено в таблиці 3.1.

Таблиця 3.1 – Функції бібліотеки string.h

Функція

Опис

char* strcat(char* s1, char* s2)

приєднує s2 до s1, повертає s1

char* strncat(char* s1, char* s2, int n)

приєднує не більше за n символів s2 до s1, завершує рядок символом '\0', повертає s1

char* strсpy(char* s1, char* s2)

копіює s2 в s1, включаючи '\0', повертає s1

char* strncpy(char* s1, char* s2, int n)

копіює не більше за n символів s2 в s1, повертає s1

intstrcmp(char* s1, char* s2)

порівнює s1 і s2, повертає значення 0, якщо рядки еквівалентні

intstrncmp(char* s1, char* s2, int n)

порівнює початкові n символів s1 і s2, повертає значення 0, якщо початкові n символів рядків еквівалентні

intstrlen(char* s)

повертає кількість символів в s

char* strset(char* s, char c)

заповнює s символами, код яких дорівнює c, повертає вказівник на s

char* strnset(char* s, char c, int n)

замінює перші n символів s символами, код яких дорівнює c, повертає вказівник на s

Функції для перевірки символів (стандартна бібліотека ctype.h)#

Для перевірки символів використовують функції, які повертають значення «істина» або «хиба». Прототипи цих функцій описано у файлі ctype.h бібліотеки стандартних функцій.

Функції з бібліотеки повертають значення «істина», якщо:

  • isalpha(c): c — символ алфавіту;

  • isupper(c): c — символ верхнього регістру;

  • islower(c): c — символ нижнього регістра;

  • isdigit(c): c — цифра від 0 до 9;

  • isxdigit(c): c — шістнадцяткова цифра;

  • isalnum(c): c — буква чи цифра;

  • isspace(c): c — символ пробілу, табуляції, переведення рядка чи формату.

Приклад 3.4#

Наведена нижче програма демонструє використання функцій із бібліотеки string.h.

#include <stdio.h>
#include <string.h>

int main()
{
    char m1[80] = "Перший рядок";
    char m2[80] = "другий рядок";
    char m3[80];

    strncpy(m3, m1, 6); /* не додає '\0' у кінці рядка */
    printf("Результат strncpy(m3, m1, 6):\n%s\n\n", m3);

    strcpy(m3, m1);
    printf("Результат strcpy(m3, m1):\n%s\n\n", m3);

    printf("Результат strcmp(m3, m1):\n%d\n\n", strcmp(m3, m1));
  
    strncat(m3, m2, 4);
    printf("Результат strncat(m3, m2, 4):\n%s\n\n", m3);

    strcat(m3, m2);
    printf("Результат strcat(m3, m2):\n%s\n\n", m3);

    printf("Кількість символів у рядку m1 дорівнює strlen(m1):\n%lu\n\n", strlen(m1));

    memset(m3, 'f', 6);
    printf("Результат strnset(m3, 'f', 6):\n%s\n\n", m3);
}