7. неделя

Strings

Стандартный ввод и вывод, массивы, строки, работа с библиотекой string.h и ctype.h, sizeof(), отладка программ

Тема лабораторной

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

Цели

  1. Понять, как работают массивы и строки в языке C
  2. Изучить функции библиотек string.h и ctype.h .
  3. Научиться работать со строками и реализовывать свои функции
  4. Познакомиться с оператором sizeof() .

Инструкции

Шаг 1: Стандартный ввод и вывод

На этом шаге мы проверим, как работает стандартный ввод и вывод в отношении строки. Попробуем различные распространенные способы их реализации.

Задача 1.1

Создайте каталог ~/labs/lab6, в котором будет храниться код вашей программы на языке Си.

Задача 1.2

В своем любимом текстовом редакторе создайте новый файл с именем my_name.c.

Задача 1.3

Создайте программу, которая получает ввод пользователя с клавиатуры с помощью функции scanf() (с одним %s) и печатает: "Hello, {ваше_имя}". В данном случае наше имя будет Janko.

Решением может быть следующий код:

#include <stdio.h>

#define BUFFER_SIZE 20

int main()
{
    char buffer[BUFFER_SIZE];
    printf("Enter name: ");
    scanf("%s", buffer);
    printf("Hello, %s ", buffer);
    return 0;
}

Если вы правильно реализовали программу, то увидите сообщение "Hello, Janko".

Задача 1.4

Снова запустите скомпилированную программу, но на этот раз введите свое новое имя: "Janko Hraško".

ОШИБКА: Даже если мы написали "Janko Hraško ", вывод будет одинаковым. Почему?

Задача 1.5

Измените программу таким образом, чтобы она могла принимать пробелы.

Решением проблемы может стать следующий код:

#include <stdio.h>

#define BUFFER_SIZE 20

int main()
{
    char buffer[BUFFER_SIZE];
    printf("Enter name: ");
    fgets(buffer, sizeof(buffer), stdin);  // read string
    printf("Hello, ");
    puts(buffer);    // display string    
    return 0;
}

Мы использовали функцию puts(), которая выводит строку, а также функцию fgets(), которая получает строку от пользователя. Результат size(buffer) в нашем случае равен 20. Это означает, что максимальная длина входной строки составляет всего 20.

Но ведь наша строка была короче. Как функция узнает, на каком индексе закончилось имя?

Шаг 2: Окончание цепи

Строк как таковых в языке Си не существует. По сути, они представляют собой массивы символов.

В этом шаге мы покажем, как и когда можно завершить строку в языке C. Конец строки определяется специальным символом Null, который в народе называют терминатором '\0'.

Задача 2.1

Снова в любимом текстовом редакторе создайте новый файл с именем terminator.c.

Задача 2.2

Скопируйте следующий код в свой файл.

#include <stdio.h>

int main()
{
    char hello_world[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\0'};
    char hello_world2[] = "Hello World!"; 

    return 0;
}

Задача 2.3

С помощью оператора sizeof() найдите размер обоих строк в байтах и выведите его на стандартный вывод.

Если вы правильно выполнили процедуру, то размер обоих полей составляет всего 13 байт. Почему же тогда строка hello_world также имеет на один символ больше?

Шаг 3: Обнаружение анаграмм

В этом шаге мы создадим инструмент, который мог бы немного помочь Тому Хэнксу в фильме "Код да Винчи" в определении анаграммы. Для реализации инструмента мы будем использовать функции из библиотек string.h и ctype.h, которые в основном предназначены для работы со строками.

Как должен работать инструмент:

  • Если набрать "hello_world и drOllWhole ", то инструмент напишет "The two strings are anagrams!"
  • Если мы напечатаем "Margit и TheFellOmen ", то инструмент напишет "The two strings are not anagrams!"

Задача 3.1

Снова в любимом текстовом редакторе создайте новый файл с именем anagram.c.

Задача 3.2

Реализуйте получение строки, заданной пользователем. Аналогично предыдущему шагу.

Задача 3.3

В стандартный ввод введите 2 слова, разделенные пробелом.

Решением может стать следующий фрагмент кода:

#include <stdio.h>

#define BUFFER_SIZE 50

int main()
{
    char buffer[BUFFER_SIZE];
    fgets(buffer, sizeof(buffer), stdin);  
    
    
    //....
    return 0;
}

Для обнаружения анаграмм рассмотрим, как сравнивать строки. Одним из возможных способов является их одинаковый символьный порядок. Поскольку символы в языке Си представляются числами, нам необходимо упорядочить строку. Например, символ 'C' имеет в таблице ASCII другое значение, чем символ 'c', поэтому необходимо изменить все символы alpha на upper или lower.

Задача 3.4

Реализовать функцию prepare_string() для подготовки строки к дальнейшей обработке.

Используйте функцию isalpha() из библиотеки ctype.h для определения всех символов alpha и преобразования их в lower с помощью функции tolower().

Решением может служить следующий фрагмент.

void prepare_string(char* input) {
    if (input == NULL)
    {
        return;
    }

    int len = strlen(input);

    for (int i = 0; i < len; i++)
    {
        if (isalpha(input[i]))
        {
            input[i] = tolower(input[i]);
        }
        
    }
    
}

Задача 3.5

Разделите модифицированную строку на две разных в соответствии с пробелом и сохраните их в отдельных переменных. Для этого можно использовать функцию string(), которая создает так называемый токен на основе некоторого разделителя - в нашем случае пробела. То есть, по сути, она разделяет строку на основе знака.

Решением будет следующий фрагмент кода.

// ...
prepare_string(buffer);

char delim[] = " "; // space as delimiter

char *token = strtok(buffer, delim); // first word
char first_part[strlen(token) + 1];
strcpy(first_part, token); // terminator is added by strcpy

token = strtok(NULL, delim); // second word
char second_part[strlen(token) + 1];
strcpy(second_part, token);
// ...

Задача 3.6

Однако в одном слове остался пробел. Используйте функцию remove_spaces() для удаления всех пробелов.

Следующий фрагмент может стать решением :

void remove_spaces(char *str) {
    int count = 0;
    for (int i = 0; str[i]; i++) {
        if (str[i] != ' ' && str[i] != '\t' && str[i] != '\n') 
        {
            str[count++] = str[i];
        }
    }
    str[count] = '\0';
}

Задача 3.7

Теперь, когда у нас есть обе модифицированные строки, мы можем их упорядочить. Создадим функцию are_anagrams(), которая упорядочивает символы в обеих строках.

Поскольку символы также имеют вид чисел, то их сортировку можно решить с помощью циклов и ветвлений. Сортировка такой строки представлена следующим фрагментом кода.

    for (int i = 0; i < len1 - 1; i++)
    {
        for (int j = i + 1; j < len1; j++)
        {
            if (sorted_str1[i] > sorted_str1[j])
            {
                char temp = sorted_str1[i];
                sorted_str1[i] = sorted_str1[j];
                sorted_str1[j] = temp;
            }
        }
    }

Задача 3.8

Добавьте функцию are_anagrams(), чтобы она могла сравнивать две строки в конце и оценивать результат. Для сравнения строк можно использовать функцию strcmp() из библиотеки string.h.

Задача 3.9

Одтестируйте свое решение.

Anagram
Рис. 1: Anagram

Дополнительные источники

  1. Оператор sizeof(): c-reference - Queries size of the object or type. Used when actual size of the object must be known.
  2. Как определить размер поля

Видео