Функции и структура программ☛Язык программирования C ✎ |
Функцию atof должно быть объявлено и определено без противоречий. Если сама atof и ее вызов внутри main иметь несогласованные типы в том же исходном тексте, компилятор заметит ошибку. Но (что бывает чаще), если atof было собран отдельно, расхождение обнаружено не будет, atof вернула бы double, которое main рассмотрела бы int, выдавая бессодержательное значение.
Учитывая сказанное нами, что объявления должны совпадать с определениями, это может показаться странным. Причиной несоответствия может быть отсутствие прототипа, функцию косвенно объявлено во время первой ее появления в выражении, как вот
sum + = atof (line);
Если название, которого не было предварительно объявлено, появится в каком выражении, и за ней следует левая скобка, она будет рассмотрена в контексте как объявление названия функции, а функции, как известно, по умолчанию возвращают int без предположений нялась ее аргументов. Более того, если объявление функции не содержит аргументов, например
double atof ();
это также означает, что ничего не допускается в отношении аргументов atof, и проверку параметров будет выключен. Этот особый смысл пустого списка аргументов предусмотрено для того, чтобы позволить старшим C-программам компилироваться с новыми компиляторами. Но использовать пустой список в новых программах - это плохая идея. Если функция принимает аргументы, объявите их, если нет - воспользуйтесь void.
Имея правильно объявленную atof, мы могли бы написать atoi (функцию преобразования цепочки на int), исходя из нее:
/ * Atoi: с помощью atof превращает цепочку s на целое число * /
int atoi (char s [])
{
double atof (char s []);
return (int) atof (s);
}
Обратите внимание на структуру объявлений и на утверждение return. Значение выражения
return выражение;
перетворетворюеться к типу функции, до того, как состоится поверннення. Таким образом, значение atof, число двойной точности, автоматически преобразуется в int при появлении в return, поскольку функция atoi должен вернуть int. Эта операция, однако, потенциально, отвергает часть информации, поэтому некоторые компиляторы могут предупредить об этом. Оператор возведения типов заявляет открыто, что именно это имелось в виду, и подавляет любые предупреждения.
Упражнение 4-2. Продолжите atof, чтобы она могла оперировать экспоненциальное представлением в форме
123.45e-6
где за числом с плавающей точкой может следовать e или E и необязательна знаковая экспонента.
Внешние переменные
Программа на C состоит также из набора внешних объектов - переменных или функций. Прилагательное "внешний" используется как противопоставление "внутреннем", который описывает аргументы и переменные, определенные внутри функций. Внешние переменные - обозначенные вне любой функции и, таким образом, потенциально доступны многим функциям. Сами функции всегда остаются внешними, так C не позволяет означать функции внутри других функций. Стандартно, внешние переменные и функции имеют одно свойство, которое заключается в том, что все обращения к ним за тем же именем - даже из функций, компилируемых отдельно - ссылаются на ту же вещь. (Стандарт называет это внешней совместимость) В этом смысле, внешние переменные похожи блоков COMMON языка Fortran или переменных из самого блока в Pascal. Мы узнаем позже, как означуваты внешние переменные и функции, видимые только с того же исходного файла.
Поскольку внешние переменные доступны глобально, они служат альтернативой аргументам функций, и могут быть использованы для обмена данными между функциями. Любая функция может обратиться к внешней переменной, ссылаясь на ее имя, если это имя каким образом объявлено.
Если функции вынуждены разделять большое число переменных между ними, внешние сменные удобнее и эффективнее, чем длинные списки аргументов. Но, как уже было указано в главе 1, эти соображения следует применять с осторожностью, поскольку они могут плохо сказаться на структуре программы, и привести к программам по великоватой количеством сочетаний данных между функциями.
Внешние переменные также полезные через большую область действия и продолжительность жизни. Автоматические переменные являются внутренними для функций, они появляются при входе в функцию и исчезают при выходе из нее. Противовес, внешние переменные - постоянные, а значит удерживают значение от одного вызова функции до другого. Таким образом, если две функции должны совместно использовать какие-то данные, но ни одна из них не вызывает другой, часто удобнее, чтобы общие данные хранились во внешних переменных, вместо передачи их туда-сюда в виде аргументов.
Исследуем этот вопрос глубже на большем примере. Задача будет состоять в написании программы-калькулятора, которая бы обеспечивала операторами +, -, * и /. Поскольку это легче воплотить - калькулятор использовать обратную польскую запись вместо инфиксного (обычного). (Обратная польская нотация используется в некоторых карманных калькуляторах а также в таких языках как Forth или Postscript.)
В обратном польском записи, каждый оператор следует за операндами; инфиксний выражение
(1 - 2) * (4 + 5)
будет введено как
1 февраля - 4 5 + *
Скобки лишние; нотация будет недвусмысленным, пока мы знаем сколько операндов каждый оператор ожидает.
Воплощение - простое. Каждый операнд проталкивается в стек; когда поступает оператор, надлежащее число операндов (два - для бинарных операторов) выталкиваются из стека, и применен оператор; результат проталкивается обратно в стек. В примере выше, скажем, 1 и 2 проталкиваются, а затем заменяются следствием их вычитания: -1. Впоследствии, 4 и 5 проталкиваются, и заменяются 9. Результат умножения -1 на 9, равна -9, заменит и их в стеке. Наконец, когда наступает конец строки ввода значения с верхушки стека выталкивается и выводится на экран.
Таким образом, структура программы будет состоять из цикла, который будет выполнять соответствующее действие, в зависимости от оператора или операнда, поступил:
while (следующий оператор или операнд не является концом файла)
if (число)
проштовхуты его
else if (оператор)
вытолкнуть операнды
выполнить действие
проштовнуты результат
else if (красная строка)
вытолкнуть и вывести верхушку стека
else
ошибка
Операции проталкивания и выталкивания из стека простые, но, как только добавляется проверка ошибок и восстановление, код становится слишком длинным, поэтому лучше поместить каждую из них в отдельную функцию, чтобы можно было повторить тот же код течение выполнения программы. Также нужна отдельная функция для извлечения следующего введенного операнда или оператора.
Основным вопросом разработки, к которому мы еще не дошли, является местонахождение стеке, то есть - какие функции имеют прямой доступ к нему? Одно из возможных решений - сохранить его в main, и передавать стек и текущее положение в нем функциям, которые проталкивать и выталкивать его данные. Но main не должна знать о переменных, управляющих стеком, он должен осуществлять только операции проталкивания и выталкивания. Поэтому мы решили сохранить стек и соответствующую информацию во внешних переменных, доступных функциям проталкивания и выталкивания, а не в main.
Перевести эту схему в код достаточно легко. Если, мы будем считать пока, что наша программа находится в одном файле, она будет иметь примерно следующий вид:
# Include (включаемые файлы)
# Define (определение)
объявления функций для main
main () {... }
внешние переменные для проталкивания (push) и выталкивание (pop)
void push (double f) {... }
double pop (void) {... }
int getop (char s []) {... }
функции, вызванные getop
Позже мы рассмотрим, как это можно разбить на два или более выходных файла.
Функция main состоять из цикла, содержать большой переключатель (switch) типа оператора или операнда; это - типичнее использования switch чем то, что мы видели ранее.
# Include
# Include
# Define MAXOP 100 / * максимальный размер операнда или оператора * /
# Define NUMBER '0 '/ * сигнализировать, что номер найдено * /
int getop (char []);
void push (double);
double pop (void);
/ * Калькулятор с обратной польской нотацией * /
main ()
{
int type;
double op2;
char s [MAXOP];
while ((type = getop (s))! = EOF) {
switch (type) {
case NUMBER:
push (atof (s));
break;
case '+':
push (pop () + pop ());
break;
case '*':
push (pop () * pop ());
break;
case '-':
op2 = pop ();
push (pop () - op2);
break;
case '/':
op2 = pop ();
if (op2! = 0.0)
push (pop () / op2);
else
printf ("error: zero divisor \ n");
break;
case '\ n':
printf ("\ t% .8 g \ n", pop ());
break;
default:
printf ("error: unknown command% s \ n", s);
break;
}
}
return 0;
}
Поскольку + и * это переставные (коммутативные) операторы, последовательность в которой вытолкнуты операнды сочетаются не имеет значения, но в случае - и /, правый и левый операнд должны различаться. Например, в
push (pop () - pop ()); / * НЕПРАВИЛЬНО * /
последовательность, в которой два вызова pop вычислено, не определено. Чтобы обеспечить правильную последовательность, нужно вытолкнуть первое значение во временную переменную, как мы это сделали в main:
# Define MAXVAL 100 / * максимальная глубина стека * /
int sp = 0; / * следующая свободная позиция в стеке * /
double val [MAXVAL]; / * стек значений * /
/ * Push: проталкивания в стек значений * /
void push (double f)
{
if (sp
else
printf ("error: stack full, can't push% g \ n", f);
}
/ * Pop: вытолкнуть и вернуть верхне значение из стека * /
double pop (void)
{
if (sp> 0)
return val [- sp];
else {
printf ("error: stack empty \ n");
return 0.0;
}
}
Переменная считается внешней, если ее обозначили вне любой функции. Таким образом, стек и индекс стека, совместно использоваться push и pop, определены снаружи этих функций. Но сама main не обращается в стеке или положение в стеке, поэтому представление может быть скрытым.
Другие материалы по теме:
- Типы, операторы и выражения- Язык программирования c в игровой индустрии: движки и производительность
- Символьные массивы
- Внешние переменные и область действия
- Функции и структура программ
