29 мая 2010 г.

Изучаю C++. Проблемы с функцией fgets(). Выкидываем символ новой строки из строковой переменной.

Я люблю хорошую музыку. Вы тоже? Отлично. А ещё я люблю переделывать простые и скучные программы из учебников по программированию, добавляя в них дополнительную функциональность и улучшая их. В одной из таких программ (Герберт Шильдт, "C++. Руководство для начинающих, 2-е изд.", стр.182-183) предлагалось написать простой телефонный справочник, используя массив строк. Стоп, телефонный справочник?.. гм... звучит не очень интересно. Я попробовал создать небольшую и простую музыкальную базу данных. Конечно, то, что получилось, было мало похоже на Musicbrainz или Discogs... Ну конечно, ведь это - просто улучшенная версия телефонного справочника из учебника по C++.

Итак, что мы имеем изначально? Программа, приведённая в книге, очень проста. Создаётся массив строк numbers[10][80], этот массив сразу заполняется данными: имя, телефон, имя, телефон ... и т. д. Вы вводите имя, программа последовательно сравнивает введённую вами строку с элементами строкового массива. Если есть совпадение, программа отображает телефонный номер. Конец программы.

cin >> str; // Считываем строку в символьную (char) переменную str[80]
for(i = 0; i < 10; i += 2)
   if(!strcmp(str, numbers[i])) { // Проверяем на совпадение с элементом массива
     cout << "Телефонный номер: " // и выводим на экран
          << numbers[i+1] << "\n";// телефонный номер 

     break;
   }

Теперь пора превратить этот телефонный справочник в музыкальную БД. Во-первых, я поменял название массива и сделал его "безразмерным", вот так: bands[][200]. Как видите, компилятор сам подсчитывает количество строк массива. Кроме того, я увеличил количество столбцов массива  (т. е. максимальную длину записи) до 200, чтобы можно было перечислить несколько альбомов для каждого музыканта:

char bands[][200] = {
   "группа1", "альбом1, альбом2 ...",
   "группа2", "альбом1, альбом2, альбом3 ...",
   // ...
   "группаN", "
альбом1, альбом2 ...",
   "", ""
};


Здесь я добавил в конце массива две пустые строки, содержащие нулевой символ "\0" (компилятор самостоятельно добавляет этот символ в конце каждой строковой переменной). Это пригодится для огранизации цикла последовательного просмотра элементов строкового массива.

Поместил считывание строки, поиск совпадений, и вывод результата в бесконечный цикл, из которого два выхода (а как же иначе?..) - или клавиша <q>, или... <Ctrl>+<c>.

Теперь проблема с cin >> str; - нам не удастся ввести название группы, состоящее из нескольких слов, разделённых пробелами - знак пробела будет воспринят как конец ввода и в переменную str попадёт только первое слово из названия группы. Чтобы исправить это, потребуется использовать... ну, скажем, fgets(str, 100, stdin) для считывания строки. Однако именно эта функция и явилась причиной очень занятного бага, из-за которого программа не могла корректно работать. Дело в том, что функция fgets() прежде, чем завершить свою работу после нажатия <Enter>, считывает в строковую переменную str символ новой строки "\n"! Из-за этой "особенности" функция strcmp(str, bands[i]) работала некорректно - как я не бился, она "в упор" не видела очевидные совпадения введённой строки с элементом строкового массива... И вот теперь я знаю, в чём дело. Но как это исправить? В Интернете нашлось несколько различных howto на тему "как выкинуть ненужный символ новой строки из переменной". Однако все эти способы показались мне чресчур сложными для подобной задачи. После размышлений, я написал следующее:

// с функцией fgets проблема...
fgets(str, 100, stdin);
// ...она передаёт в символьную переменную str, кроме "обычных" символов,
// символ новой строки (\n). Из-за этого операции со str выполняются некорректно.
// Поэтому, с помощью простого цикла, мы находим символ конца строки
// и заменяем его на нулевой символ.
for(i = 0; i < strlen(str); i++) 

  if(str[i] == '\n') str[i] = 0;

После этого программа стала работать корректно.

В процессе изучения языка C++ я часто сталкиваюсь с различными проблемами, которые пытаюсь решить. Иногда это бывает очень просто, а иногда приходится потратить немало времени. В большинстве случаев, больше всего времени уходит на то, чтобы понять, где же я ошибся.

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

2 комментария:

  1. Почему именно C++ изучаешь? Сложноватая тема, плюс память надо в ручную чистить..

    ОтветитьУдалить
  2. @.NET Trading
    Во-первых, интерес. К тому же, в Linux много чего на C++ написано, так что знание этого языка не помешает.
    Во-вторых, программисты на C++ более востребованы, насколько я знаю.
    В-третьих, на мой взгляд, этот язык более красивый и удобный, чем Pascal (который я со школы изучал).

    ОтветитьУдалить