Глиф Учебники

Урок 8. Строки

Курс «C» · урок 8 из 10 · 4 минуты чтения

Отдельного типа «строка» в C нет. Строка — это массив символов char, в конце которого стоит символ с кодом

  1. Его записывают как '\0' и называют нуль-терминатором: по нему функции понимают, где строка кончается.
C
#include <stdio.h>
#include <string.h>

int main(void) {
    char s[] = "cat";
    printf("%s\n", s);
    printf("Длина: %zu, байт в массиве: %zu\n", strlen(s), sizeof(s));
    printf("Первая буква: %c\n", s[0]);
    s[0] = 'b';
    printf("%s\n", s);
    return 0;
}
Вывод
cat
Длина: 3, байт в массиве: 4
Первая буква: c
bat

Запись char s[] = "cat"; создаёт массив из четырёх элементов: 'c', 'a', 't' и '\0'. Функция strlen считает символы до нуля (3), а sizeof — весь массив вместе с нулём (4). Подробнее о разнице — в справке Длина строки: strlen и sizeof.

Символ пишется в одинарных кавычках ('a' — один char), строка — в двойных ("a" — массив из двух: 'a' и '\0'). Это разные вещи, и путать их нельзя.

char s[] = "cat"; нуль-терминатор 'c' 'a' 't' '\0' s[0]s[1]s[2]s[3] strlen(s) = 3 sizeof(s) = 4 — вместе с нулём s[0] = 'b'; b a t \0 printf("%s\n", s); вывод: bat 'a' a один char, 1 байт "a" a \0 строка, 2 байта
Строка "cat" — четыре char: c, a, t и нуль-терминатор \0; strlen даёт 3, sizeof — 4; замена первой буквы даёт bat, а "a" в двойных кавычках занимает два байта, 'a' — один.

Ввод слова и строки

scanf с форматом %s читает одно слово — до пробела или перевода строки. Перед именем массива & не нужен: имя массива уже адрес. Число в формате ограничивает длину, чтобы длинный ввод не вышел за пределы массива:

C
#include <stdio.h>
#include <ctype.h>

int main(void) {
    char word[100];
    scanf("%99s", word);
    int digits = 0;
    for (int i = 0; word[i] != '\0'; i++) {
        if (isdigit((unsigned char)word[i])) {
            digits++;
        }
        word[i] = toupper((unsigned char)word[i]);
    }
    printf("%s, цифр: %d\n", word, digits);
    return 0;
}
Ввод
Room101
Вывод
ROOM101, цифр: 3

%99s — не больше 99 символов: ещё одно место в массиве нужно для '\0'. Цикл for (int i = 0; word[i] != '\0'; i++) — обычный способ перебрать символы строки.

Функции из ctype.h работают с отдельными символами: isdigit — цифра ли, isalpha — буква ли, isspace — пробел ли, toupper и tolower переводят букву в большую или маленькую. Приведение (unsigned char) защищает от ошибки с символами, у которых код больше 127.

Строку с пробелами (имя и фамилию, предложение) читает fgets:

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

int main(void) {
    char line[200];
    if (fgets(line, sizeof(line), stdin) != NULL) {
        line[strcspn(line, "\n")] = '\0';
        printf("[%s], длина %zu\n", line, strlen(line));
    }
    return 0;
}
Ввод
hello world
Вывод
[hello world], длина 11

fgets оставляет в конце строки символ перевода строки \n. Строка line[strcspn(line, "\n")] = '\0'; его убирает: strcspn находит позицию первого \n (или конец строки, если его нет). Подробно и про то, почему fgets после scanf «не ждёт ввода», — в справке Как прочитать строку с пробелами.

fgets(line, sizeof(line), stdin) \n остаётся в конце h e l l o w o r l d \n \0 … 0 11 strcspn(line, "\n") = 11 line[strcspn(line, "\n")] = '\0'; h e l l o w o r l d \0 \0 … вывод: [hello world], длина 11
fgets кладёт в массив line строку hello world вместе с переводом строки \n на месте 11; strcspn находит этот номер, и запись нуль-терминатора на это место убирает \n — остаётся строка длиной 11.

Функция gets удалена из языка в стандарте C11: она не знает размера массива, и длинный ввод портит память. Не используйте её, даже если она встречается в старых учебниках.

Функции string.h

ФункцияЧто делает
strlen(s)длина строки без '\0'
strcpy(d, s)копирует строку s в массив d
strcat(d, s)дописывает s в конец строки d
strcmp(a, b)сравнивает: 0 — строки равны, меньше 0 — a раньше по алфавиту, больше 0 — позже
strchr(s, c)ищет символ, возвращает указатель на него или NULL
strstr(s, t)ищет подстроку, возвращает указатель на её начало или NULL
C
#include <stdio.h>
#include <string.h>

int main(void) {
    char first[] = "Anna";
    char last[] = "Smith";
    char full[50];
    strcpy(full, first);
    strcat(full, " ");
    strcat(full, last);
    printf("%s (%zu символов)\n", full, strlen(full));

    if (strcmp(first, "Anna") == 0) {
        printf("Имя совпало\n");
    }
    if (strstr(full, "mit") != NULL) {
        printf("Подстрока найдена\n");
    }
    return 0;
}
Вывод
Anna Smith (10 символов)
Имя совпало
Подстрока найдена

Строки нельзя сравнивать через == (сравнятся адреса, а не буквы) и нельзя присваивать через = (full = first не собирается) — для этого и нужны strcmp и strcpy. Подробно — в справке Как сравнить строки.

strcpy и strcat не проверяют размер массива-получателя. Если строка не помещается, запись идёт за границу массива и портит соседнюю память. Следите, чтобы места хватало с учётом '\0'. Безопасно собрать строку помогает snprintf(full, sizeof(full), "%s %s", first, last): она никогда не пишет больше указанного размера.

Пример: перевернуть строку

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

int main(void) {
    char s[] = "programming";
    int n = strlen(s);
    for (int i = 0; i < n / 2; i++) {
        char t = s[i];
        s[i] = s[n - 1 - i];
        s[n - 1 - i] = t;
    }
    printf("%s\n", s);
    return 0;
}
Вывод
gnimmargorp

Символы меняются местами попарно с краёв к середине: первый с последним, второй с предпоследним.

Массив или указатель на строку

C
char a[] = "text";      // массив: его можно менять
char *p = "text";       // указатель на строку-литерал: менять нельзя

Строка в кавычках, записанная прямо в программе, называется литералом и обычно хранится в памяти только для чтения. Запись p[0] = 'T' собирается без ошибок, но результат не определён: как правило, программа падает с segmentation fault. Если строку нужно менять, объявляйте массив.

char a[] = "text"; a t e x t \0 своя копия строки в массиве a[0] = 'T'; можно: в a станет "Text" char *p = "text"; p t e x t \0 литерал: обычно только чтение p[0] = 'T'; собирается, но поведение не определено, обычно — segmentation fault
Массив a получает свою копию строки "text", и её можно менять, а p смотрит на литерал, который обычно лежит в памяти только для чтения: запись по p собирается, но обычно роняет программу.

Русские буквы

Файлы с программами обычно сохраняются в кодировке UTF-8, а в ней русская буква занимает два байта:

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

int main(void) {
    char s[] = "привет";
    printf("Байт: %zu\n", strlen(s));
    return 0;
}
Вывод
Байт: 12

Поэтому strlen даёт удвоенное число букв, s[0] — только половину буквы, а toupper русские буквы в UTF-8 не меняет. Для учебных задач на обработку символов берите латиницу. О том, почему русский текст в консоли Windows бывает непонятными символами, — в справке Русские буквы в консоли Windows.

Коротко

Проверьте себя

1. Сколько байт занимает массив char s[] = "hello";?
2. Как правильно проверить, что строка s равна "yes"?
3. Почему в scanf("%s", name) перед name нет &?
4. Что выведет программа?
C
#include <stdio.h>
#include <string.h>

int main(void) {
    char s[20] = "abc";
    strcat(s, "de");
    printf("%zu\n", strlen(s));
    return 0;
}