Н.В. Вдовикина, И.В. Машечкин, А.Н. Терехин, В.В. Тюляева - Программирование в ОС UNIX на языке Си (1114934), страница 4
Текст из файла (страница 4)
Другой способ – передавать через фиксированный параметр количество неименованных параметров, если их типы предполагаются известными.Задача 2. Написать функцию, возвращающую максимальный из полученных ею фактических параметров (типа unsigned int).#include <stdio.h>21#include <stdarg.h>unsigned int unsgn_max(unsigned int count, ...);/* Число неименованных параметров передается черезпараметр count, который не учитывается при поискемаксимума.
*/main(){printf(“max = %d\n”,unsgn_max (3, 10, 20, 30));}unsigned int unsgn_max(unsigned int count, ...){va_list ap;unsigned int res = 0, cur;int i;va_start(ap, count);for(i=1; i<=count; i++)if(res < (cur = va_arg(ap,unsigned int)))res = cur;va_end(ap);return res;}Общая структура программыПрограмма может размещаться как в одном, так и в нескольких файлах, содержать одну или несколько функций, одна из которых считается головной (main) - с нее начинается выполнение программы.
Определение каждой функции должно полностью размещаться в одном файле, при этом файл может содержать несколькоопределений различных функций.В соответствии со структурой программы переменные обладают следующими качествами:• видимость переменной (область видимости);• существование переменной (область существования).Область видимости переменныхОбласть видимости переменной определяет часть исходноготекста программы, из любой точки которой доступна данная переменная.С точки зрения видимости можно выделить следующие группы переменных:22• видимые в пределах блоков (локальные)4,• видимые в пределах файла,• видимые в пределах программы.Для переменных, определенных в начале любого блока, областью видимости является весь этот блок. В случае вложенных блоков переменные, определенные внутри вложенного блока, “перекрывают” переменные с такими же именами, определенные в объемлющем блоке (и так для любой степени вложенности).
Например:main(){ int x = 1;if(x>0){ int x = 2;printf(“x = %d\n”, ++x); /*выводит: x = 3 */}printf(“x = %d\n”, x); /*выводит: x = 1 */}Переменные, определенные внутри функции, “перекрывают” формальные параметры с теми же именами 5:int f(int f){int f = 1;…}Переменные, определенные вне блоков (т.е., фактически, внетела какой-либо функции), доступны с точки определения до концафайла.
Если на такую переменную нужно сослаться до того, как онаопределена, необходимо ее описание со спецификатором extern,например:int x;main(){extern int y;x = y = 10;printf(“x=%d, y=%d\n”,x,y); /* x=10, y=10 */}…int y;блоками являются: составной оператор, определение (тело) функции, условный оператор и переключатель switch, оператор цикла (вложенным блоком для него является также телоцикла)5Следует заметить, что в приведенном ниже примере коллизии имени функции f с именем формального параметра или с переменной f, определенной внутри функции, не будет из-заразличия областей видимости.423В файле вне функций не может встречаться несколько определений переменных (возможно, разных типов) с одним и тем же именем:int x;main(){...}float x;/* ошибка: повторное определение x */Указание static, примененное к нелокальной переменной илифункции, ограничивает область их видимости концом файла 6.
Прочие же функции и нелокальные переменные могут быть доступны издругого программного файла.Если используемая переменная определена в другом программном файле, она также должна быть описана со спецификатором extern. При таком описании переменной память под нее не отводится, а только декларируется тип переменной, что позволяеткомпилятору осуществлять проверки типизации.Примечание. Переменные, определенные внутри блока, как иформальные параметры функций, "перекрывают" переменные с темиже именами, видимые в пределах файла и в пределах программы.Область существования переменныхОбласть существования переменной – это множество всех точек программы, при приходе управления на которые переменнаясуществует, т.е. для нее выделена память.
В отличие от области видимости, про область существования можно сказать, что это понятие«времени выполнения» С этой точки зрения можно выделить двегруппы переменных:1. Статические переменныеПеременные, являющиеся статическими, существуют на всемпротяжении работы программы. Память под эти переменные выделяется на этапе редактирования внешних связей и загрузки программы, тогда же происходит и инициализация статических переменных (следует отметить, что инициализатором для статическойпеременной может служить только константное выражение, а приотсутствии инициализатора статические переменные по умолчаниюинициализируются нулем). Правила определения статических переменных различаются в зависимости от конкретного места программы, в котором это определение встретилось. Для определения статиЗдесь возникает некая терминологическая путаница, поскольку, как будет рассказанониже, ключевое слово static еще используется для определения статических (с точки зренияобласти существования) переменных, а в данном контексте с помощью static задаются границы области видимости.624ческой переменной, локализованной в блоке, используется ключевоеслово static, например:int max; /*статическая переменная вне блока*/int f(int param){static int min; /* статическая переменная,определенная внутри блока */…}Основным свойством статических переменных, определенныхвнутри блока, является сохранение их значений при выходе из блока.
Например:#include <stdio.h>void print_a();main(){ int i;for(i=0; i<5; i++)print_a();}в результате напечатается:aaaaa=====12345void print_a(){static int a = 1;printf(“a = %d\n”, a++);}Все переменные, определенные вне функций, являются статическими.2. Автоматические переменныеАвтоматическими переменными являются все переменные определенные внутри блока (функции) и не являющиеся статическими.Автоматические переменные существуют на протяжении работыблока, в котором они определены, включая блоки, вложенные в данный. Выделение памяти под автоматические переменные и их инициализация осуществляется каждый раз при входе в блок.
Инициализация для автоматических переменных, фактически, эквивалентнаприсваиванию, т.е. в качестве инициализирующего выражения может выступать любое выражение, которое может стоять в правойчасти оператора присваивания. При отсутствии инициализатора начальное значение по умолчанию для автоматических переменных неопределено.25#include <stdio.h>void print_a();main(){ int i;for(i=0; i<5; i++)print_a();}в результате напечатается:aaaaa=====11111void print_a(){int a = 1;printf(“a = %d\n”, a++);}При выходе из блока память, выделенная под автоматическиепеременные, освобождается.3. Регистровые переменныеВ качестве одного из доступных программисту средств оптимизации язык Си предлагает возможность использования так называемых регистровых переменных.
Это достигается за счет использования квалификатора register в определении переменных, чтоуказывает компилятору, что данную переменную в целях ускоренияпрограммы имеет смысл разместить на регистрах, однако компилятор может проигнорировать это указание. Квалификатор registerможет применяться только к автоматическим переменным и формальным параметрам функций.
Независимо от того, была ли переменная, описанная с квалификатором register, действительно размещена на регистрах или нет (что программисту неизвестно), длянее не определено понятие адреса (т.е. не определена операция &).26int f(int a){int i;register int r;...r = a--;if(r){return f(r);}return r;}…a=2адрес возвратасохраненный fp(*)сохраненные регистрыauto переменная iauto переменная rmain(){...int b = f(2);...}(если ее не удалосьразместить на регистрах)a=1адрес возвратасохраненный fpсохраненные регистрыauto переменная iauto переменная r(если ее не удалосьразместить на регистрах)(*) – стековый кадр –непрерывная область памятистека, используемая функциейдля своей работыfp (frame pointer) – указательстекового кадраРис.1Стековый кадрОбычно размещение в памяти автоматических переменных,как и передача параметров и возврат значений из функций, реализуются с использованием стека.
Параметрам функции отводится память в стеке, в которую копируются фактические значения. Поокончании работы функции эта память освобождается.Рассмотрим состояние стека на примере рекурсивной функции(cм. Рис.1). В примере каждый раз при вызове функции f в стекесохраняется адрес возврата и значения регистров, после чего там жеотводится место для параметра a и автоматической переменной i, атакже для автоматической переменной r в случае, если ее не удалосьразместить на регистрах. Вся вместе эта область стека называетсястековым кадром.
Заметим, что при рекурсивном вызове функцииf в стеке еще раз выделяется память под новый стековый кадр, и вней размещаются новые копии автоматических переменных и пара-27метры нового вызова. При выходе из функции стековый кадр освобождается.Упражнения1. Написать функцию getletter(), читающую одну литеру из стандартного ввода с помощью getchar() и возвращающую введеннуюлитеру, если это большая или малая латинская буква, и –1 в противном случае.2. Написать функцию, которая считывает со стандартного ввода nчисел (n – параметр функции) и возвращает их среднее арифметическое.3.
Написать рекурсивную функцию вычисления факториала неотрицательного числа.4. Написать функцию, которая для заданных натурального n и вещественного x вычисляет значение выражения:s = sin x + sin sin x + …+sin sin …sin xn раз5. Написать функцию, вычисляющую n-ое число Фибоначчи (n>=0)по правилу:1, при n=0 или n=1F(n) =F(n-1) + F(n-2), n>=228ТЕМА 4.