Б. Керриган, Д. Ритчи Язык программирования C. Б. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003
Скачать 31.48 Mb.
|
40 Глава Обзор языка часто применяется для выбора одного из нескольких альтернативных пу- тей, имеющихся в программе. Условия вычисляются по порядку в направ- лении сверху вниз до тех пор, пока одно из них не будет удовлетворено; в этом случае будет выполнена соответствующая ему инструкция, и ра- бота всей конструкции завершится. из инструкций может быть груп- пой инструкций в фигурных скобках.) Если ни одно из условий не удов- летворено, выполняется последняя инструкция, расположенная сразу за else, если таковая имеется. Если же else и следующей за ней инструк- ции нет (как это было в программе подсчета слов), то никакие действия вообще не производятся. Между первым if и завершающим else может быть сколько угодно комбинаций вида else if (условие) инструкция Когда их несколько, программу разумно форматировать так, как мы здесь показали. Если же каждый следующий сдвигать вправо относи- тельно предыдущего else, то при длинном каскаде проверок текст ока- жется слишком близко прижатым к правому краю страницы. Инструкция switch, речь о которой пойдет в главе 3, обеспечивает дру- гой способ изображения многопутевого ветвления на языке Си. Он более подходит, в частности, тогда, когда условием перехода служит совпаде- ние значения некоторого выражения целочисленного с одной из кон- стант, входящих в заданный набор. Вариант нашей программы, реализо- ванной с помощью switch, приводится в параграфе 3.4. Упражнение 1.13. Напишите программу, печатающую гистограммы длин вводимых слов. Гистограмму легко рисовать горизонтальными полосами. Рисование вертикальными полосами - более трудная задача. Упражнение 1.14. Напишите программу, печатающую гистограммы частот встречаемости вводимых символов. Функции Функции в Си играют ту же что и подпрограммы и функции в Фортране или процедуры и функции в Паскале. Функция обеспечивает удобный способ отдельно оформить некоторое вычисление и пользовать- ся им далее, не заботясь о том, как оно реализовано. После того, как функ- ции написаны, можно забыть, как они сделаны, достаточно знать лишь, что они умеют делать. Механизм использования функции в Си удобен, легок и эффективен. Нередко вы будете встречать короткие функции, вы- Функции лишь единожды; они оформлены в виде функции с ственной целью - получить более ясную программу. До сих пор мы пользовались готовыми функциями вроде main, и теперь настала пора нам самим написать несколько функций. В Си нет оператора возведения в вроде * * в Фортране. Поэтому проиллюстрируем механизм определения функции на примере функции которая возводит целое в целую положительную степень п. Так, г( имеет значение 32. На самом деле для практического при- менения эта функция малопригодна, так как оперирует лишь малыми целыми степенями, однако она вполне может послужить иллюстрацией. (В стандартной библиотеке есть функция у), вычисляющая Итак, мы имеем функцию и главную функцию main, пользующу- юся ее услугами, так что вся программа выглядит следующим образом: int power(int m, int /* тест функции power */ { int i; for (i = 0; i < 10; ++i) power(2,i), power(-3, return 0; } /* base в степень; n >= 0 */ int power(int base, int n) { int i, p; P = 1; for (i = 1; i <= n; ++i) p = p * base; return p; } Определение любой функции имеет следующий вид: тип-результата имя-функции (список параметров, если он есть) объявления инструкции 42 Глава Обзор языка Определения функций могут располагаться в любом порядке в одном или в нескольких исходных файлах, но любая функция должна быть целиком расположена в каком-то одном. Если исходный текст программы распре- делен по нескольким файлам, то, чтобы ее скомпилировать и загрузить, вам придется сказать несколько чем при использовании одного файла; но это уже относится к операционной системе, а не к языку. Пока мы предполагаем, что обе функции находятся в одном файле, так что бу- дет достаточно тех знаний, которые вы уже получили относительно за- пуска программ на Си. В следующей строке из функции main к обращаются дважды. %d i, power(2,i), При каждом вызове функции передаются два аргумента, и каждый раз главная профамма main в ответ получает целое число, которое затем приводится к должному формату и печатается. Внутри выражения представляет собой целое значение точно так же, как 2 или i. (Не все функции в качестве результата выдают целые значения; подробно об этом будет сказано в главе 4.) В первой строке определения power: int base, int n) указываются типы параметров, имя функции и тип результата. Имена па- раметров локальны внутри это значит, что они скрыты для любой другой функции, что остальные могут свободно пользо- ваться теми же именами для своих целей. Последнее утверждение спра- ведливо также для переменных i и р: i в и i в main не имеют между собой ничего общего. Далее параметром мы называть переменную из списка парамет- ров, заключенного в круглые скобки и заданного в определении функ- ции, а аргументом - значение, используемое при обращении к функции. Иногда в том же смысле мы будем употреблять термины формальный аргумент и фактический аргумент. Значение, вычисляемое функцией power, возвращается main с помощью инструкции return. За словом return может следовать любое выражение: return выражение; Функция не обязательно возвращает какое-нибудь значение. Инструк- ция без выражения только передает управление в ту программу, которая ее вызвала, не передавая ей никакого результирующего значе- ния. То же самое происходит, если в процессе вычислений мы выходим на конец функции, обозначенный в тексте последней закрывающей фи- гурной скобкой. Возможна ситуация, когда вызывающая функция игно- рирует возвращаемый ей результат. Функции 43 Вы, вероятно, обратили внимание на инструкцию retu в конце main. Поскольку main есть функция, как и любая другая она может вернуть результирующее значение тому, кто ее вызвал, - фактически в ту среду, из которой была запущена программа. Обычно возвращается нулевое зна- что говорит о нормальном выполнения. Ненулевое значение сигнализирует о необычном или ошибочном завершении. До сих пор ради простоты мы опускали в main, но с этого момента будем задавать retu rn как напоминание о том, что программы должны сообщать о состоянии своего завершения в операционную систему. Объявление int int n); стоящее непосредственно main, сообщает, что функция power ожи- дает двух аргументов типа int и возвращает результат типа int. Это объявление, называемое прототипом функции, должно быть согласовано с определением и всеми вызовами Если определение функции или вызов не соответствует своему прототипу, это ошибка. Имена параметров не требуют согласования. Фактически в прототипе они могут быть произвольными или вообще отсутствовать, т. е. прототип можно было бы записать и так: int power(int, int); Однако удачно подобранные имена поясняют программу, мы будем часто этим пользоваться. Историческая справка. Самые большие отличия ANSI-Си от более ран- них версий языка как раз и заключаются в способах объявления и опре- деления функций. В первой версии Си функцию power требовалось за- давать в следующем виде: /* power: возводит base в степень; n >= 0 */ /* (версия в старом стиле языка Си) */ n) int base, n; ( int i, p; P = 1; for (i = 1; i <= n; p = p * base; return p; } Здесь имена параметров перечислены в круглых скобках, а их типы зада- ны перед первой открывающей фигурной скобкой. В случае отсутствия Глава Обзор языка указания о типе параметра, считается, что он имеет тип int. (Тело функ- ции не претерпело изменений.) Описание г в начале программы согласно первой версии Си долж- но было бы выглядеть следующим образом: Нельзя было задавать список параметров, и поэтому компилятор не имел возможности проверить правильность обращений к power. Так как при отсутствии объявления power предполагалось, что функция возвращает значение типа int, то в данном случае объявление целиком можно было бы опустить. . Новый синтаксис для прототипов функций облегчает компилятору обнаружение ошибок в количестве аргументов и их типах. Старый син- таксис объявления и определения функции все еще допускается стандар- том ANSI, по крайней мере на переходный период, но если ваш компиля- тор поддерживает новый синтаксис, мы настоятельно рекомендуем пользоваться только им. Упражнение 1.15. Перепишите программу преобразования температур, выделив само преобразование в отдельную функцию. Аргументы. Вызов по значению Одно свойство функций в Си, вероятно, будет в новинку для програм- мистов, которые уже пользовались другими языками, в частности Фор- траном. В Си все аргументы функции передаются "по значению". Это следует понимать так, что вызываемой функции посылаются значения ее аргументов во временных переменных, а не сами аргументы. Такой способ передачи аргументов несколько отличается от "вызова по ссыл- ке" в Фортране и спецификации при параметре в Паскале, которые позволяют подпрограмме доступ к самим аргументам, а не к их ло- кальным копиям, Главное отличие заключается в том, в Си вызываемая функция не мо- жет непосредственно изменить переменную вызывающей функции: она может изменить только ее частную, временную копию. Однако вызов по значению следует отнести к достоинствам языка, а не к его недостаткам. Благодаря этому свойству обычно удается написать более компактную программу, содержащую меньшее число посторонних поскольку параметры можно рассматривать как должным образом инициализированные локальные переменные вызванной под- программы. В качестве примера приведем еще одну версию функции powe г, в которой как раз использовано это свойство. Символьные массивы _ 45 /* power: возводит base в степень: п >= 0; 2 */ int power(int base, int n) { int p; for (p = 1; n > 0; p = p * base; return p; Параметр n выступает здесь в роли временной переменной, в которой циклом f o r в убывающем порядке ведется счет числа шагов до тех пор, пока ее значение не станет нулем. При этом отпадает надобность в допол- нительной переменной i для счетчика цикла. Что бы мы ни делали с n внутри power, это не окажет никакого влияния на сам аргумент, копия которого была передана функции при ее вызове. При желании можно сделать так, чтобы функция смогла изменить пе- ременную в вызывающей программе. Для этого последняя должна пере- дать адрес подлежащей изменению переменной (указатель на перемен- ную), а в вызываемой функции следует объявить соответствующий пара- метр как указатель и организовать через него косвенный доступ к этой переменной. Все, что касается указателей, мы рассмотрим в главе 5. Механизм передачи массива в качестве аргумента несколько иной. Когда аргументом является имя массива, то функции передается значе- ние, которое является адресом начала этого массива; никакие элементы массива не копируются. С помощью индексирования относительно по- лученного значения функция имеет доступ к любому элементу массива. Разговор об этом пойдет в следующем параграфе. 1.9. Символьные массивы Самый распространенный вид массива в Си - массив символов. Чтобы Проиллюстрировать использование символьных массивов и работающих с ними функций, напишем программу, которая читает набор текстовых строк и печатает самую длинную из них. Ее схема достаточно проста: while еще строка?) (данная строка длиннее самой длинной из предыдущих) ее запомнить ее длину самую длинную строку 46 Глава Обзор языка Из схемы видно, что программа естественным образом распадается на части. Одна из них получает новую другая проверяет ее, третья запоминает, а остальные управляют процессом вычислений. Поскольку процесс четко распадается на части, хорошо бы так и пере- вести его на Си. Поэтому сначала напишем отдельную функцию для получения очередной строки. Мы попытаемся сделать эту функцию полезной и для других применений. Как минимум getline должна сигна- лизировать о возможном конце файла, а еще лучше, если она будет выда- вать длину строки — или нуль в случае исчерпания файла. Нуль годится для признака конца файла, поскольку не бывает строк нулевой длины, даже строка, содержащая только один символ новой строки, имеет длину Когда мы обнаружили строку более длинную, чем самая длинная из всех предыдущих, то нам надо будет где-то ее запомнить. Здесь напрашивается вторая функция, сору, которая умеет копировать новую строку в надеж- ное место. Наконец, нам необходима главная программа, которая бы управляла функциями getline и сору. Вот как выглядит наша программа в целом: MAXLINE 1000 /* максимальный размер вводимой строки */ int getline(char int MAXLINE); void to[], char /* печать самой длинной строки */ { int /* длина текущей строки */ int max; /* длина максимальной из просмотренных строк */ char /* текущая строка */ char /* самая длинная строка */ max = while getline(line, > 0) if (len > max) { max = len; copy(longest, line); } if (max > 0) /* была ли хоть одна строка? */ longest); return 0; Символьные массивы _ _ 47 /* getline: читает строку в s, возвращает длину */ int int lim) { int c, i; (i = 0; i < && (c = != EOF && с != s[i] = c; if (c == { s[i] = c; i] = return i; /* copy: копирует т в to достаточно большой */ void copy(char to[], char { int i; i = 0; while = != Мы предполагаем, что функции getline и сору, описанные в начале про- граммы, находятся в том же что и main. Функции main и getline взаимодействуют между собой через пару ар- гументов и возвращаемое значение. В getline аргументы определяются строкой int getline(char s[], int lim) Как мы видим, ее первый аргумент s есть массив, а второй, имеет тип int. Задание размера массива в определении имеет целью резервирова- ние памяти. В самой задавать длину массива s нет необходимос- ти, так как его размер указан в main. Чтобы вернуть значение вызываю- щей программе, getline использует точно так же, как это делает функция r. В приведенной строке также сообщается, что get i ne воз- вращает значение типа int, но так как при отсутствии указания о типе подразумевается int, то перед getline слово int можно опустить. Одни функции возвращают результирующее значение, другие (такие как нужны только для того, чтобы произвести какие-то действия, 48 Глава Обзор языка не выдавая никакого значения. На месте типа результата в сору стоит void. Это явное указание на то, что никакого значения данная функция не воз- вращает. Функция getline в конец создаваемого ею массива помещает символ (null-символ, кодируемый нулевым байтом), чтобы пометить конец строки символов. То же соглашение относительно окончания нулем со- блюдается и в случае строковой константы вроде В данном случае для него формируется массив из символов этой строки с в конце. h е 1 1 0 \п \о Спецификация %s в формате предполагает, что соответствующий ей аргумент - строка символов, оформленная указанным выше образом. Функция сору в своей работе также опирается на тот факт, что читаемый ею аргумент заканчивается символом ' который она копирует наряду с остальными символами. (Все сказанное предполагает, что ' не встре- чается внутри обычного текста.) Попутно стоит заметить, что при работе даже с такой маленькой про- граммой мы сталкиваемся с некоторыми конструктивными трудностями. Например, что должна делать main, если встретится строка, превыша- ющая допустимый размер? Функция getline работает надежно: если мас- сив полон, она прекращает пересылку, даже если символа новой строки не обнаружила. Получив от длину строки и увидев, что она совпа- дает с главная программа main могла бы "отловить" этот особый случай и справиться с ним. В интересах краткости описание этого случая мы здесь опускаем. Пользователи getline не могут заранее узнать, сколь длинными бу- дут вводимые строки, поэтому getline делает проверки на переполне- ние. А вот пользователям функции сору размеры копируемых строк из- вестны (или они могут их узнать), поэтому дополнительный контроль здесь не нужен. Упражнение 1.16. Перепишите main предыдущей программы так, чтобы она могла печатать самую длинную строку без каких-либо ограничений на ее размер. переменные и область видимости 49 Упражнение 1.17. Напишите программу печати всех вводимых строк, содержащих более 80 символов. Упражнение 1.18. Напишите программу, которая будет в каждой вводимой строке заменять стоящие подряд символы пробелов и табуляций на один пробел и удалять пустые строки. Упражнение 1.19. Напишите функцию reverse(s), размещающую сим- волы в строке s в обратном порядке. Примените ее при написании про- граммы, которая каждую вводимую строку располагает в обратном по- рядке. 1.10. Внешние переменные и область видимости Переменные line, longest и прочие принадлежат только функции main, или, как говорят, локальны в ней. Поскольку они объявлены внутри main, никакие другие функции прямо к ним обращаться не могут. То же верно и применительно к переменным других функций. Например, i в line не имеет никакого отношения к i в сору. Каждая локальная переменная функции возникает только в момент обращения к этой функции и исче- зает после выхода из нее. Вот почему такие переменные, следуя термино- логии других языков, называют автоматическими. (В главе 4 обсуждает- ся класс памяти static, который позволяет локальным переменным со- хранять свои значения в промежутках между вызовами.) Так как автоматические переменные образуются и исчезают одновре- менно с входом в функцию и выходом из нее, они не сохраняют своих значений от вызова к вызову и должны устанавливаться заново при каж- дом новом обращении к функции. Если этого не делать, они будут со- держать "мусор". В качестве альтернативы автоматическим переменным можно опре- делить внешние переменные, которым разрешается обращаться по их именам из любой функции. (Этот механизм аналогичен области COMMON в Фортране и определениям переменных в самом внешнем блоке в Паска- ле.) Так как внешние переменные доступны повсеместно, их можно ис- пользовать вместо аргументов для связи между функциями по данным. Кроме того, поскольку внешние переменные существуют постоянно, а не возникают и исчезают на период выполнения функции, свои значе- ния они сохраняют и после возврата из функций, их установивших. Внешняя переменная должна быть определена, причем только один раз, вне текста любой функции; в этом случае ей будет выделена память. Она |