Выбрать главу

/* trim: удаляет завершающие пробелы, табуляции и новые строки */

int trim(char s[])

{

 int n;

 for (n = strlen(s)-1; n ›= 0, n--)

  if (s[n]!= ' '&& s[n]!= '\t'&& s[n]!= '\n')

   break;

 s[n+1] = '\0';

 return n;

}

С помощью функции strlen можно получить длину строки. Цикл for просматривает его в обратном порядке, начиная с конца, до тех пор, пока не встретится символ, отличный от пробела, табуляции и новой строки. Цикл прерывается, как только такой символ обнаружится или n станет отрицательным (т. е. вся строка будет просмотрена). Убедитесь, что функция ведет себя правильно и в случаях, когда строка пуста или состоит только из символов-разделителей.

Инструкция continue в чем-то похожа на break, но применяется гораздо реже. Она вынуждает ближайший объемлющий ее цикл (for, while или do-while) начать следующий шаг итерации. Для while и do-while это означает немедленный переход к проверке условия, а для for - к приращению шага. Инструкцию continue можно применять только к циклам, но не к switch. Внутри переключателя switch, расположенного в цикле, она вызовет переход к следующей итерации этого цикла.

Вот фрагмент программы, обрабатывающий только неотрицательные элементы массива a (отрицательные пропускаются).

for (i = 0; i ‹ n; i++) {

 if (a[i] ‹ 0) /* пропуск отрицательных элементов */

  continue;

 … /* обработка положительных элементов */

}

К инструкции continue часто прибегают тогда, когда оставшаяся часть цикла сложна, а замена условия в нем на противоположное и введение еще одного уровня приводят к слишком большому числу уровней вложенности.

3.8 Инструкция goto и метки

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

Однако существуют случаи, в которых goto может пригодиться. Наиболее типична ситуация, когда нужно прервать обработку в некоторой глубоко вложенной структуре и выйти сразу из двух или большего числа вложенных циклов. Инструкция break здесь не поможет, так как она обеспечит выход только из самого внутреннего цикла. В качестве примера рассмотрим следующую конструкцию:

for (…)

 for (…) {

  …

 if (disaster) /* если бедствие */

  goto error; /* уйти на ошибку */

error: /* обработка ошибки */

 ликвидировать беспорядок

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

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

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

for (i = 0; i ‹ n; i++)

 for (j = 0; j ‹ m; j++)

  if (a[i] == b[i])

   goto found;

/* нет одинаковых элементов */

 …

found:

/* обнаружено совпадение: a[i] - b[i] */

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

found = 0;

for (i = 0; i ‹ n && !found; i++)

 for (j = 0; j ‹ m && !found; j++)

  if (a[i] == b[j])

   found = 1;

if (found) /* обнаружено совпадение: a[i-1] - b[j-1] */

 …

else

 /* нет одинаковых элементов */

 …

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

Глава 4. Функции и структура программы

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

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

Объявление и определение функции - это та область, где стандартом ANSI в язык внесены самые существенные изменения. Как мы видели в главе 1, в описании функции теперь разрешено задавать типы аргументов. Синтаксис определения функции также изменен, так что теперь объявления и определения функций соответствуют друг другу. Это позволяет компилятору обнаруживать намного больше ошибок, чем раньше. Кроме того. если типы аргументов соответствующим образом объявлены, то необходимые преобразования аргументов выполняются автоматически.

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

4.1 Основные сведения о функциях

Начнем с того, что сконструируем программу, печатающую те строки вводимого текста, в которых содержится некоторый "образец", заданный в виде строки символов. (Эта программа представляет собой частный случай функции grep системы UNIX.) Рассмотрим пример: в результате поиска образца "ould" в строках текста

Ah Love! could you and I with Fate conspire

To grasp this sorry Scheme of Things entire,

Would not we shatter it to bits - and then

Re-mould it nearer to The Heart's Desire!

мы получим

Ah Love! could you and I with Fate conspire

Would not we shatter it to bits - and then

Re-mould it nearer to the Heart's Desire!