Twitter VK
Заметки

Ничего не знаю: язык C, часть 2 — найти проблему на ровном месте

Язык C — проблема на ровном месте

Продолжаем пробовать язык C. Нужно придумать себе какую-нибудь простую задачу. Самую простую. Сделаем так: объявим переменную типа «строка» с некоторым заданным значением и выведем её на экран. Это же реально просто!

В языке C объявить строку можно по-разному. Считается, что строка — это массив (последовательность) символов. Массив произвольной длины, но в «хвосте» массива размещен служебный символ '\0' (кстати, его числовое значение — 0; напомню также, что служебные символы не отображаются), который и обозначает конец строки. Не будь этого символа, длину строки определить было бы невозможно. Вернее, это была бы такая же последовательность символов, но уже не строка.

За символы в языке C «отвечает» тип char (от англ. Character — «символ»). Таким образом, строкой у нас будет массив элементов типа char. Объявить строку можно так, как объявляют массивы:

char my_sting[] = "Какой-то текст...";

…или через объявление указателя:

char *my_string = "Какой-то текст...";

Что даст идентичный результат.

Здесь мы не только объявляем строку, но и инициализируем её вполне конкретным значением. Разумеется, можно было бы явно указать длину строки (и таким образом указать, сколько элементов в строке — сколько «места» она займет в оперативной памяти):

char my_sting[17] = "Какой-то текст...";

…но нам «лениво», поэтому пусть компилятор сам это посчитает из того значения, что мы прописали.

Кстати, использование указателей — одна из важнейших особенностей языка C. Обсуждать эту тему сейчас неохота, но если говорить совсем примитивно, то указатель — это переменная, содержащая адрес другой переменной (или произвольного участка памяти).

Итак, создадим нашу вторую (здесь первая) программу, теперь в файле с именем proj02.c:

#include <stdio.h>

int main() {
  // Иногда самое сложное - придумать простую задачу
  char some_string[] = "Иногда самое сложное - придумать простую задачу";
  printf("%s\n", some_string);

  // Объявить строку можно и по-другому
  char *any_string = "Странно, но это работает аналогичным образом";
  printf("%s\n", any_string);

  return 0;
}

Что мы сделали? Объявили и инициализировали значениями две переменные строкового типа — some_string и any_string (это имена переменных). Будем называть их просто «строки» (хотя на самом деле — это как раз указатели), понимая, что это символьные последовательности с '\0' на конце. Далее — мы вывели эти строки «на экран» (в терминал, консоль) с помощью функции printf(). Функция эта обеспечивает форматированный вывод, что и используется: в качестве аргументов мы передаем не только имя строковой переменной («что выводить»), но и служебную конструкцию "%s" («как выводить» — как строку). Кроме того, кажется, в данном случае используется так называемая интерполяция — строковое значение помещается в то самое место, где стоит управляющая конструкция "%s". Это пример. Если хотите, изучите printf() в подробностях сами.

Таким образом, первая строка объявляется как «массив элементов типа char», вторая строка — указатель на элемент типа char. И то и другое «работает». Думаю, что служебный символ '\0' в конце последовательности компилятор ставит сам (а как иначе — мы же этого не делаем), потому что видит инициализацию (присваивание значения) переменных строковыми данными. Еще один момент: мы объявляем строковые переменные непосредственно перед их использованием (а не в каком-то отдельном месте, скажем, в начале текста программы). Согласно последним стандартам языка C — «так можно» 🙂

Теперь, как и ранее, компилируем (пользуйтесь «терминалом»):

username ~/Папка_с_программой $ gcc -Wall -o proj02 proj02.c

…и запускаем:

username ~/Папка_с_программой $ ./proj02
Иногда самое сложное - придумать простую задачу
Странно, но это работает аналогичным образом
username ~/Папка_с_программой $

Очевидно, всё работает. Что нам еще пожелать? Конечно — посчитать количество символов в строке и вывести его на экран.

Скорее всего, есть для этого специальная функция, но, во-первых, надо вспоминать её название, во-вторых, вероятно, еще и подключать какой-нибудь модуль для работы со строками. А нам снова «лениво»… Справимся сами.

Логика наша такова: «пройдемся» в цикле по элементам строки (символам), пока не «наткнемся» на '\0'. При этом будем наращивать некий счетчик символов. При выходе из цикла значение, накопленное счетчиком, как раз и покажет нам «длину строки». Отредактируем текст нашей программы (убрав, кстати, вторую строку за ненадобностью):

#include <stdio.h>

int main() {
  // Иногда самое сложное - придумать простую задачу
  char some_string[] = "Иногда самое сложное - придумать простую задачу";
  printf("%s\n", some_string);

  // Не помню название функции, которая вычисляет длину строки
  // Поэтому делаем "вручную"

  int char_count = 0; // Объявляем "счетчик" и инициализируем его нулем

  // Далее - цикл "с предусловием": "пока" ("условие") {"код"}
  while (some_string[char_count] != '\0') {
    printf("%c", some_string[char_count]); // Выводим очередной символ на экран
    char_count++; // "Наращиваем" счетчик
  }

  // Выводим значение счетчика
  printf("\nДлина строки - %d символов\n", char_count);

  return 0;
}

Итак, что мы делаем: объявляем переменную-счетчик char_count типа int («знаковое целое»), присваиваем ей значение 0. Далее — цикл while с предусловием (пока «условие истинно» выполнять некий код). В выражении условия цикла обращаемся к символам строки как к элементам массива, который «индексируется» значением char_count (индексация в языке C, как известно, начинается с нуля). Если очередной символ не равен '\0' (операция сравнения !=), выполняем итерацию цикла. И в каждой итерации наращиваем значение счетчика (унарная постфиксная операция «++»). А еще — для самопроверки выводим каждый символ (учитывая формат — "%c") на экран, причем без «перевода строки» (без "\n"), чтобы символы «легли» в одну строку. Ну и после выполнения цикла — выводим на экран значение счетчика, учитывая формат (обратите внимание на символ форматирования "%d").

Компилируем и запускаем:

username ~/Папка_с_программой $ ./proj02
Иногда самое сложное - придумать простую задачу
Иногда самое сложное - придумать простую задачу
Длина строки - 87 символов
username ~/Папка_с_программой $

Что видим в результате: строка напечатана два раза: один раз до выполнения цикла (целиком, строкой), второй — в результате выполнения цикла (посимвольно). И результат подсчета длины строки — 87 символов.

То есть как 87?!. Ну-ка, посчитаю сам. Должно быть 47 — шесть пробелов, один дефис и ровно сорок буковок. Так почему же 87? Приехали…

Видимо, придется таки вспомнить, как называется функция подсчета длины строки — strlen(). А для её использования нужно подключить string.h — заголовочный файл библиотеки (модуля) работы со строками. Итак, вносим необходимые правки:

#include <stdio.h>
#include <string.h> // Модуль для работы со строками

int main() {
  // Иногда самое сложное - придумать простую задачу
  char some_string[] = "Иногда самое сложное - придумать простую задачу";
  printf("%s\n", some_string);

  // Не помню название функции, которая вычисляет длину строки
  // Поэтому делаем "вручную"

  int char_count = 0; // Объявляем "счетчик" и инициализируем его нулем

  // Далее - цикл "с предусловием": "пока" ("условие") {"код"}
  while (some_string[char_count] != '\0') {
    printf("%c", some_string[char_count]); // Выводим очередной символ на экран
    char_count++; // "Наращиваем" счетчик
  }

  // Выводим значение счетчика
  printf("\nДлина строки, что сами посчитали - %d символов\n", char_count);

  // А тут длину строки узнаем с помощью функции strlen()
  printf("Длина строки, что функция strlen() вернула - %d символов\n", (int)strlen(some_string));

  return 0;
}

Мы подключили библиотеку для работы со строками — #include <string.h>. А ближе к концу текста программы мы передаем на печать результат вызова функции strlen() с именем строки в качестве аргумента — strlen(some_string). Поскольку strlen() возвращает значение типа size_t, а функции printf() нужно передать значение типа int, выполняем «приведение типа» — (int)strlen(some_string).

И снова — компилируем и выполняем:

username ~/Папка_с_программой $ ./proj02
Иногда самое сложное - придумать простую задачу
Иногда самое сложное - придумать простую задачу
Длина строки, что сами посчитали - 87 символов
Длина строки, что функция strlen() вернула - 87 символов
username ~/Папка_с_программой $

И снова 87! Ну что тут скажешь — по крайней мере, мы в цикле подсчитали длину строки «не хуже», чем функция strlen(). Хотя всё равно неправильно. Кажется, я догадываюсь в чем дело (подсказка — unicode). И даже предполагаю, как можно решить эту проблему (одним из возможных способов). Но это — в следующий раз.


P.S. Заметка была почти готова, когда я наткнулся в Википедии на на такой момент: приведение результата функции strlen() к типу int для функции printf() можно не делать, если символ форматирования %d заменить на %zu. Таким образом, можно заменить строку

printf("Длина строки, что функция strlen() вернула - %d символов\n", (int)strlen(some_string));

на более простой вариант

printf("Длина строки, что функция strlen() вернула - %zu символов\n", strlen(some_string));

и всё будет прекрасно работать. Думаю, для тех, кто в отличие от меня язык C знает, это не новость 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *