Урок 8. Строки
Отдельного типа «строка» в C нет. Строка — это массив символов char, в конце которого стоит символ с кодом
- Его записывают как
'\0'и называют нуль-терминатором: по нему функции понимают, где строка кончается.
#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'). Это разные вещи, и путать их нельзя.
Ввод слова и строки
scanf с форматом %s читает одно слово — до пробела или перевода строки. Перед именем массива & не нужен: имя массива уже адрес. Число в формате ограничивает длину, чтобы длинный ввод не вышел за пределы массива:
#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;
}Room101ROOM101, цифр: 3%99s — не больше 99 символов: ещё одно место в массиве нужно для '\0'. Цикл for (int i = 0; word[i] != '\0'; i++) — обычный способ перебрать символы строки.
Функции из ctype.h работают с отдельными символами: isdigit — цифра ли, isalpha — буква ли, isspace — пробел ли, toupper и tolower переводят букву в большую или маленькую. Приведение (unsigned char) защищает от ошибки с символами, у которых код больше 127.
Строку с пробелами (имя и фамилию, предложение) читает fgets:
#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], длина 11fgets оставляет в конце строки символ перевода строки \n. Строка line[strcspn(line, "\n")] = '\0'; его убирает: strcspn находит позицию первого \n (или конец строки, если его нет). Подробно и про то, почему fgets после scanf «не ждёт ввода», — в справке Как прочитать строку с пробелами.
Функция 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 |
#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): она никогда не пишет больше указанного размера.
Пример: перевернуть строку
#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Символы меняются местами попарно с краёв к середине: первый с последним, второй с предпоследним.
Массив или указатель на строку
char a[] = "text"; // массив: его можно менять
char *p = "text"; // указатель на строку-литерал: менять нельзяСтрока в кавычках, записанная прямо в программе, называется литералом и обычно хранится в памяти только для чтения. Запись p[0] = 'T' собирается без ошибок, но результат не определён: как правило, программа падает с segmentation fault. Если строку нужно менять, объявляйте массив.
Русские буквы
Файлы с программами обычно сохраняются в кодировке UTF-8, а в ней русская буква занимает два байта:
#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.
Коротко
- Строка — массив
charс'\0'в конце; места нужно на один символ больше длины. - Слово читает
scanf("%99s", s)(без&), строку с пробелами —fgets;\nпослеfgetsубирают черезstrcspn. strlen— длина,strcpy— копия,strcat— склейка,strcmp— сравнение (0 — равны).- Строки не сравнивают через
==и не присваивают через=. - Литерал
char *p = "…"менять нельзя, массивchar a[] = "…"— можно.getsне используют.