Еще об аргументах функций
В лекции №1 мы уже обсуждали тот факт , что аргументы функций передаются по значению, т.е. вызванная функция получает свою временную копию каждого аргумента, а не его адрес. Это означает, что вызванная функция не может воздействовать на исходный аргумент в вызывающей функции. Внутри функции каждый аргумент по существу является локальной переменной, которая инициализируется тем значением, с которым к этой функции обратились.
Если в качестве аргумента функции выступает имя массива, то передается адрес начала этого массива; сами элементы не копируются. функция может изменять элементы массива, используя индексацию и адрес начала. Таким образом, массив передается по ссылке. В лекции №5 мы обсудим, как использование указателей позволяет функциям воздействовать на отличные от массивов переменные в вызывающих функциях.
Между прочим, несуществует полностью удовлетворительного способа написания переносимой функции с переменным числом аргументов. Дело в том, что нет переносимого способа, с помощью которого вызванная функция могла бы определить, сколько аргументов было фактически передано ей в данном обращении. Таким образом, вы, например, не можете написать действительно переносимую функцию, которая будет вычислять максимум от произвольного числа аргументов, как делают встроенные функции max в фортране и PL/1.
Обычно со случаем переменного числа аргументов безопасно иметь дело, если вызванная функция не использует аргументов, которые ей на самом деле не были переданы, и если типы согласуются. Самая распространенная в языке "C" функция с переменным числом - printf. Она получает из первого аргумента информацию, позволяющую определить количество остальных аргументов и их типы. функция printf работает совершенно неправильно, если вызывающая функция передает ей недостаточное количество аргументов, или если их типы не согласуются с типами, указанными в первом аргументе. Эта функция не является переносимой и должна модифицироваться при использовании в различных условиях.
Если же типы аргументов известны, то конец списка аргументов можно отметить, используя какое-то соглашение; например, считая, что некоторое специальное значение аргумента часто нуль) является признаком конца аргументов.
#define maxop 20 /* max size of operand, operАtor * #define number '0' /* signal that number found */ #define toobig '9' /* signal that string is too big *
main() /* reverse polish desk calculator */ /( int tupe; char s[maxop]; double op2,atof(),pop(),push();
while ((tupe=getop(s,maxop)) !=EOF); switch(tupe) /( 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("zero divisor popped\n"); break; case '=': printf("\t%f\n",push(pop())); break; case 'c': clear(); break; case toobig: printf("%.20s ... is too long\n",s) break; /) /) #define maxval 100 /* maximum depth of val stack */
int sp = 0; /* stack pointer */ double val[maxval]; /*value stack */ double push(f) /* push f onto value stack */ double f; /( if (sp < maxval) return(val[sp++] =f); else /( printf("error: stack full\n"); clear(); return(0); /) /)
double pop() /* pop top value from steack */ /( if (sp > 0) return(val[--sp]); else /( printf("error: stack empty\n"); clear(); return(0); /) /)
clear() /* clear stack */ /( sp=0; /)
Команда C очищает стек с помощью функции clear, которая также используется в случае ошибки функциями push и pop. К функции getop мы очень скоро вернемся.
Как уже говорилось в лекции №1, переменная является внешней, если она определена вне тела какой бы то ни было функции. Поэтому стек и указатель стека, которые должны использоваться функциями push, pop и clear, определены вне этих трех функций. Но сама функция main не ссылается ни к стеку, ни к указателю стека - их участие тщательно замаскировано. В силу этого часть программы, соответствующая операции =, использует конструкцию
push(pop());
для того, чтобы проанализировать верхний элемент стека, не изменяя его.
Отметим также, что так как операции + и * коммутативны, порядок, в котором объединяются извлеченные операнды, несущественен, но в случае операций - и / необходимо различать левый и правый операнды.
Упражнение 4-3
Приведенная основная схема допускает непосредственное расширение возможностей калькулятора. Включите операцию деления по модулю /%/ и унарный минус. Включите команду "стереть", которая удаляет верхний элемент стека. Введите команды для работы с переменными. /Это просто, если имена переменных будут состоять из одной буквы из имеющихся двадцати шести букв/.