Строковые функции сравнения, поиска символов и фрагментов

Практический курс по C/C++: https://stepik.org/course/193691

Мы продолжаем изучение основных функций работы со строками. Следующая важная операция – это сравнение на равенство двух строк между собой. При этом строки считаются равными, если равны их длины и все соответствующие символы:

Например, следующие две строки считаются равными:

char s1[12] = "Hello";
char s2[10] = "Hello";

Обратите внимание, строки равны, хотя длины массивов отличаются. Но для нас важно равенство длин именно строк, а не массивов. Очевидно, здесь длины строк совпадают и все соответствующие символы в этих строках. Поэтому приведенные строки равны.

А вот примеры неравных строк:

"Hello" != "hello";  "Hell" != "Hello";   "HELLO" != "hello"

Алгоритм сравнения на равенство можно реализовать следующим образом:

#include <stdio.h>
 
int main(void) 
{
         char s1[12] = "Hello";
         char s2[10] = "Hello";
 
         const char *str1 = s1;
         const char *str2 = s2;
 
         int i = 0;
         for(; str1[i] != '\0' && str2[i] != 0; ++i)
                   if(str1[i] != str2[i]) {
                            puts("Strings are not equal!");
                            return 0;
                   }
 
         if(str1[i] != str2[i]) {
                   puts("String lengths are not equal!");
                   return 0;
         }
 
         puts("Strings are equal!");
 
         return 0;
}

Цикл for выполняется до тех пор, пока не будет достигнут конец или первой или второй строки. В теле цикла записана проверка на равенство символов с индексом i. Если соответствующие символы не равны, то строки считаются различными и алгоритм сравнения завершается. Если же был достигнут конец хотя бы одной из двух строк, то делается проверка, что у другой строки конец не достигнут, а значит, строки имеют разные длины. А иначе, делаем вывод, что строки равны.

Конечно, это лишь один из вариантов реализации алгоритма сравнения строк. Его можно запрограммировать и по-другому. В качестве практики попробуйте придумать свою реализацию. Ну а библиотечный код, который выполняет ту же задачу, реализован с помощью следующих двух функций:

int strcmp(const char* str1, const char* str2);
int strncmp(const char* str1, const char* str2, size_t max_len);

Обе возвращают 0, если строки равны, отрицательное значение, если первая строка str1 меньше второй str2 и положительное значение, если первая строка str1 больше второй str2. Больше и меньше здесь связано не столько с длинами строк, сколько с символами, из которых они состоят. Например, если встречается символ первой строки, который имеет код меньше соответствующего символа второй строки, то функции вернут отрицательное значение. И, наоборот. Но сейчас для нас важно лишь то, что они возвращают 0 для равных строк, а иначе не 0. Кроме того, вторая функция позволяет задавать сравнение только первых max_len символов в обеих строках, а остальные игнорировать. Давайте посмотрим, как их можно использовать в программе.

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char s1[12] = "Hello";
         char s2[10] = "Hello";
 
         int res = strcmp(s1, s2);
         if(res == 0)
                   puts("Strings are equal!");
         else
                   puts("Strings are not equal!");
 
         return 0;
}

Функции strcmp() передаются указатели на начало строк, а результат их сравнения сохраняется в переменной res. Затем, мы проверяем, если значение res равно нулю, то строки равны, а иначе – не равны.

Конечно, в приведенной программе переменная res не играет какой-то особой роли, поэтому вызов функции strcmp() можно прописать непосредственно в условном операторе следующим образом:

if(strcmp(s1, s2) == 0)
         puts("Strings are equal!");
else
         puts("Strings are not equal!");

И это довольно частый вариант записи.

Давайте теперь воспользуемся второй функцией strncmp() и с ее помощью выберем все строки, которые начинаются с фрагмента «Sh»:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         const char* strings[] = {
                   "Ship", "Shopping", "Shematic", "Super", "Car", "Sherif"
         };
 
         for(int i = 0; i < sizeof(strings) / sizeof(*strings); ++i)
                   if(strncmp(strings[i], "Sh", 2) == 0)
                            puts(strings[i]);
 
         return 0;
}

Обратите внимание, как в программе объявлен массив из нескольких строк. В действительности, это обычный массив из указателей и каждый из них содержит адрес начала соответствующей строки. Причем, строки в памяти занимают ровно столько места, сколько требуется для представления корректной строки:

Не нужно путать эту конструкцию с двумерным массивом. Это обычный одномерный массив, но состоящий из указателей. Так удобно делать, когда нужно определить несколько строковых литералов и работать с ними, как с единым целым. Причем, все указатели объявлены с ключевым словом const, так как строковые литералы, как мы с вами уже знаем, сохраняются в неизменяемой области памяти. Данные из них можно только читать, но не менять.

После определения строк, идет цикл for, который перебирает все указатели массива strings. В теле цикла на каждой итерации происходит проверка, содержит ли текущая строка (с индексом i) первые два символа «Sh». И если это так, то на экран выводится строка.

После запуска этой программы на экране увидим следующий результат:

Ship
Shopping
Shematic
Sherif

Как видите, функция strncmp() полезна, когда нам нужно сравнивать не строки целиком, а лишь определенные фрагменты.

Функции поиска символов и подстрок

Во второй части этого занятия познакомимся с несколькими функциями, которые позволяет находить заданный символ и заданный фрагмент в строке:

  • char* strchr(const char* str, int val);
    выполняет поиск символа слева-направо с кодом val в строке str;

  • char* strrchr(const char* str, int val);
    выполняет поиск символа справа-налево с кодом val в строке str;

  • char* strstr(const char* str, const char* find);
    выполняет поиск слева-направо подстроки find в строке str;

  • char* strpbrk(const char* str, const char* find);
    выполняет поиск слева-направо любого символа из подстроки find в строке str.

Все эти функции возвращаю указатель на ячейку памяти, где был найден символ или фрагмент подстроки, либо значение NULL, если поиск не увенчался успехом.

Пользоваться всеми этими функциями достаточно просто и очевидно. Например:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char str[] = "Abrakadabra";
         char *ptr = strchr(str, 'a');
         
         printf("str = %p\nptr = %p\n", str, ptr);
         if(ptr != NULL)
                   printf("*ptr = %c\n", *ptr);
 
         return 0;
}

Здесь функция strchr() возвращает указатель на 4-й символ буквы ‘a’ в строке "Abrakadabra". После запуска программы увидим следующий результат:

str = 0062ff10
ptr = 0062ff13
*ptr = a

Если же вместо strchr() записать функцию strrchr(), то поиск символа будет осуществляться с конца строки:

char *ptr = strrchr(str, 'a');

с результатом работы:

str = 0062ff10
ptr = 0062ff1a
*ptr = a

Если указать не существующий символ, например, буквы s:

char *ptr = strrchr(str, 's');

то функция вернет нулевой указатель:

str = 0062ff10
ptr = 00000000

Следующие две функции работают похожим образом. Если нам нужно определить, входит ли фрагмент «ra» в строку "Abrakadabra", то сделать это можно так:

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char str[] = "Abrakadabra";
         char *ptr = strstr(str, "ra");
         
         printf("str = %p\nptr = %p\n", str, ptr);
         if(ptr != NULL)
                   printf("ptr: %s\n", ptr);
 
         return 0;
}

После запуска программы увидим результат:

str = 0062ff10
ptr = 0062ff12
ptr: rakadabra

Наконец, последняя функция strpbrk() позволяет проверить, содержится ли в строке хотя бы один из символов подстроки. Это бывает полезно, когда, например, мы хотим проверить, есть ли во введенном пароле хотя бы один из символов "@!#$^&?":

#include <stdio.h>
#include <string.h>
 
int main(void) 
{
         char pass[] = "dfdfg90!#$$A";
         char *ptr = strpbrk(pass, "@!#$^&?");
         
         printf("pass = %p\nptr = %p\n", pass, ptr);
         if(ptr != NULL)
                   printf("ptr: %s\n", ptr);
 
         return 0;
}

После запуска программы увидим результат:

pass = 0062ff0f
ptr = 0062ff16
ptr: !#$$A

На этом мы завершим текущее занятие по строкам. На следующем продолжим эту тему и поговорим о функциях преобразования произвольной числовой информации в строки и из строк в числа.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме