Область видимости переменных

Область действия переменной – это правила, которые устанавливают, какие данные доступны из данного места программы.

Область действия локальной переменной – это блок, внутри которого она объявлена, такая переменная существует, пока выполняется этот блок. Каждая локальная переменная функции возникает только в момент обращения к этой функции и исчезает после выхода из нее. Глобальные переменные областью действия имеют всю программу. Такие переменные доступны во всей программе, их используют для связи между функциями.

 

Пример 1 Пример 2
#include <stdio.h> #include <conio.h>   void fnkt(void);     int main(void) { int i=1; clrscr(); fnkt(); printf(“main(): i = %d &i = %p ”, i, &i); getch(); return 0; } void fnkt(void) { int i=10; printf(“fnkt(): i = %d &i = %p ”, i, &i); } #include <stdio.h> #include <conio.h>   void fnkt(void); int i = 4;   int main(void) {   clrscr(); fnkt(); printf(“main(): i = %d &i = %p ”, i, &i); getch(); return 0; } void fnkt(void) { int i=10; printf(“fnkt(): i = %d &i = %p ”, i, &i); }
Результат 1 Результат 2
fnkt(): i = 10 &i = FFF0 main(): i = 1 &i = FFF4 fnkt(): i = 10 &i = FFF0 main(): i = 4 &i = 00AA

 

 

Лекция № 12

 

Написал Lekka
07.11.2008
Параметры функции main() В соответствии с синтаксисом языка Си основная функция каждой программы может иметь такой заголовок: int main(int argc, char* argv[], char* envp[]) где: параметр argv – массив указателей на строки; argc – параметр типа int, значение которого определяет размер массива argv, то есть количество его элементов, envp – параметр массив указателей на символьные строки, каждая из которых содержит описание одной из переменных среды(окружения). Под средой понимается та программа (обычно это операционная система), которая «запустила» на выполнение функцию main(). Назначение параметров функции main() – обеспечить связь выполняемой программы с операционной системой, точнее, с командной строкой, из которой запускается программа и в которую можно вносить данные и тем самым передавать исполняемой программе любую информацию. Если внутри функции main() нет необходимости обращаться к информации из командной строки, то параметры обычно опускаются. Если программист «запускает» программу на языке Си из интегрированной среды разработки, то он редко использует командную строку и должен предпринимать специальные меры, чтобы увидеть эту строку или внести в нее нужную информацию (пункт меню – RUN, подпункт – ARGUMENTS). При запуске программы командная строка явно доступна, и именно в ней записывается имя выполняемой программы. Вслед за именем программы можно разместить нужное количество слов, разделяя их пробелами. Каждое слово из командной строки становится строкой-значением , на которую указывает очередной элемент параметра argv[i], где 0<i<argc. Как и в каждом массиве, в массиве argv[] индексация элементов начинается с нуля, то есть всегда имеется элементargv[0]. Этот элемент является указателем на полное название запускаемой программы. Например, если из командной строки выполняется обращение к программе PR1 из каталога Programs, размещенного на диске С, то вызов MS DOS выглядит так: С:ProgramsPR1.exe В главной программе разрешено использовать и третий параметр envp[]. Его назначение – передать в программу всю информацию об окружении, в котором выполняется программа. TMP=C:WINDOWSTEMP и так далее В приведенном результате имя переменной связано с именем каталога для временных данных. Изменив значение переменной TMP, можно заставить программу использовать для хранения временных данных другой каталог. Пример [программа pr_27, pr_28]: #include<stdio.h> #include<conio.h>   int main(int argc, char *argv[], char *envp[]) { int i; clrscr(); for(i=0; i<argc; i++) printf("argc = %d argv[%d] -> %s ", argc, i, argv[i]); getch(); clrscr(); for(i=0; envp[i]; i++) printf("%s ", envp[i]); getch(); return 0; }     Указатели на функции До сих пор мы рассматривали функцию как минимальный модуль программы, обмен данными с которым происходит через набор параметров функции и с помощью значений, возвращаемых функцией в точку вызова. Теперь перейдем к вопросу о том, почему в языке Си функция введена как один из производных типов. Необходимость в таком типе связана с задачами, в которых функция или ее адрес должна выступать в качестве параметра другой функции или в качестве значения, возвращаемого другой функцией. В соответствии с синтаксисом указатель на функцию – это выражение или переменная, используемые для представления адреса функции. По определению, указатель на функцию содержит адрес первого байта или первого слова выполняемого кода функции. Важно: арифметические операции над указателями на функции запрещены. Указатель на функцию как переменная вводится отдельно от определения и прототипа какой-либо функции. Для этих целей используется конструкция: тип (*имя_указателя) (спецификация_параметров); где тип – определяет тип возвращаемого функцией значения, имя_указателя – идентификатор, произвольно выбранный программистом, спецификация_параметров – определяет состав и типы параметров функции. Важнейшим элементом в определении указателя на функцию являются круглые скобки - int (*point) (void). Если записать int *point (void), то это будет не определением указателя, а прототипом функции без параметров с именем point, возвращающей значение типа int*, то есть адрес некоторой переменной типа int. В отличие от имени функции указатель point является переменной, то есть ему можно присваивать значения других указателей, определяющих адреса функций программы. Принципиальное требование – тип указателя-переменной должен полностью соответствовать типу функции, адрес которой ему присваивается. Имя функции в ее определении и прототипе – указатель-константа. Он всегда связан с определяемой функцией и не может быть настроен на что-либо иное, чем ее адрес. Неконстантный указатель на функцию, настроенный на адрес конкретной функции, может быть использован для вызова этой функции. Общий вид вызова функции с помощью неконстантных указателей: (*имя_указателя) (список_фактических_параметров); или имя_указателя (список_фактических_параметров); в обоих случаях тип указателя должен соответствовать типу вызываемой функции, между формальными и фактическими параметрами также должно быть полное соответствие. При использовании явного разыменования обязательны круглые скобки. Запись *point(); будет ошибочной, так как операция круглые скобки ‘()’имеет более высокий приоритет, чем операция разыменования ‘*’. В этом ошибочном вызове вначале выполниться вызов функции point(), а уже к результату будет применена операция разыменования. При определении указателя на функции он может быть инициализирован. В качестве инициализирующего выражения должен использоваться адрес функции того же типа, что и тип определяемого указателя:   Пример: int fnkt(float); // прототип функции int (*pfnkt1)(float) = fnkt; // pfnkt1 – указатель на функцию fnkt int (*pfnkt2)(float) = NULL; // pfnkt2 – указатель на функцию, обнуленный   Пример [программа pr_29]: #include<stdio.h> #include<conio.h>   void f1(void); void f2(void);   int main(void) { clrscr(); // определяем указатель-переменную с именем point на // функции без параметров, не возвращающие значение. void (*point) (void); // явный вызов функции f1() f1(); // настройка указателя на функцию f1() point = f1; // вызов функции f1() по ее адресу с разыменованием указателя (*point)(); // настройка указателя на функцию f2() point = f2; // вызов функции f2() по ее адресу с разыменованием указателя (*point)(); // вызов функции f2() по ее адресу без разыменования указателя point(); getch(); return 0; }   void f1(void) { printf("Function f1() "); }   void f2(void) { printf("Function f2() "); }   Результат: Function f1() Function f1() Function f2() Function f2() Указатели на функции как параметры позволяют создавать функции, реализующие тот или иной метод обработки другой функции, которая заранее не определена. Например, можно определить функцию для вычисления интеграла. Подынтегральная функция может быть передана в функцию вычисления интеграла с помощью параметра-указателя. Для этого введем формулы численного интегрирования. Пусть на отрезке [a, b] задана непрерывная функция y=f(x). Требуется вычислить определенный интеграл. Разделим отрезок [a, b] точками a=x0, x1, x2, …, xn=b, на n равных частей длины Dx: Dx=(b-a)/n. Обозначим далее через y0, y1, y2, …, yn значения функции f(x) в точках x0, x1, x2, …, xn, т. е. y0=f(x0), y1=f(x1), y2=f(x2), …,yn=f(xn). Значение интеграла определяется по одной из формул: Пример [программа pr_30]: #include<stdio.h> #include<conio.h> #include<math.h>   // прототипы функций, используемых в программе float f1(float); float f2(float); float rect(float (*)(float), float, float);   int main(void) { clrscr(); // вызов функции rect() для вычисления первого интеграла методом прямоугольников printf("1) First integral: %f ", rect(f1, -1, 2)); // вызов функции rect() для вычисления второго интеграла методом прямоугольников printf("2) Second integral: %f ", rect(f2, 0, 0.5)); getch(); return 0; }   // параметры функции rect() – указатель на функцию, с параметром типа float, возвращающий // значение типа float; и два параметра типа float для задания пределов интегрирования. float rect(float (*f)(float), float a, float b) { int i; float s=0, h=(b-a)/N; for(i=0;i<20;i++) s+=f(a+i*h); return h*s; }   float f1(float x) { return x/((x*x+1)*(x*x+1)); }   float f2(float x) { return 4*cos(x)*cos(x); }   При организации меню часто используется указатель на функцию как возвращаемое функцией значение. int (*menu_items[])(void) = {f1, f2}; - массив указателей на функции, в качестве инициализирующих значений в списке использованы имена функций f1() и f2(). t = (*menu_items[0])(); t = (*menu_items[1])(); Если функция f1() возвращает значение равное 1, то t = 1. Если функция f2() возвращает значение равное 2, то t = 2.

 

Лекция № 13

 

Написал Lekka
07.11.2008
Функции с переменным количеством параметров В языке Си допустимы функции, количество параметров у которых при компиляции функции не фиксировано. Кроме того, могут неизвестными и типы параметров. Количество и типы параметров становятся известными только в момент вызова функции, когда явно задан список фактических параметров. При определении и описании таких функций, имеющих списки параметров неопределенной длины, спецификация формальных параметров заканчивается запятой и многоточием. Формат прототипа функции с переменным списком параметров: тип имя_функции(спецификация_явных_параметров, …); где тип – тип возвращаемого функцией значения; имя_функции – имя функции;спецификация_явных_параметров – список спецификаций параметров, количество и типы которых фиксированы и известны в момент компиляции. Эти параметры можно назвать обязательными. Каждая функция с переменным количеством параметров должна иметь хотя бы один обязательный параметр. После списка явных обязательных параметров ставится запятая, а затем многоточие, извещающее компилятор, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции проводить не нужно. Сложность в том, что у переменного списка параметров нет даже имени, поэтому не понятно, как найти его начало и конец. Каждая функция с переменным списком параметров должна иметь механизм определения их количества и их типов. Принципиально различных подходов к созданию этого механизма два. Первый подход предполагает добавление в конец списка реально использованных необязательных фактических параметров специального параметра-индикатора с уникальным значением, которое будет сигнализировать об окончании списка. При таком подходе в теле функции параметры последовательно перебираются, и их значения сравниваются с заранее известным концевым признаком. Второй подход предусматривает передачу в функцию сведений о реальном количестве фактических параметров. Эти сведения можно передавать с помощью одного из явно задаваемых обязательных параметров. В обоих подходах – при задании концевого признака и при указании числа реально используемых фактических параметров – переход от одного фактического параметра к другому выполняется с помощью указателей, то есть с использованием адресной арифметики. Недостаток функции – возможность ошибочного задания неверного количества реально используемых параметров.   Пример [программа pr_31]: #include<stdio.h> #include<conio.h>   int summa(int, ...);   int main(void) { clrscr(); printf("summa = %d ", summa(2, 5, 7)); printf("summa = %d ", summa(5, 1, 2, 3, 4, 5)); getch(); return 0; }   int summa(int k, ...) { int *p = &k, s=0; // k – число суммируемых параметров, настроили указатель на параметр k for(; k; k--) s+=*(++p); return s; } Результат: summa(2, 5, 7) = 12 summa(5, 1, 2, 3, 4, 5) = 15 Рекурсивные функции Рекурсивной называют функцию, которая прямо или косвенно сама вызывает себя. Именно возможность прямого или косвенного вызова позволяет различать прямую или косвенную рекурсию. При каждом обращении к рекурсивной функции создается новый набор объектов автоматической памяти, локализованных в теле функции. Функция называется косвенно рекурсивной в том случае, если она содержит обращение к другой функции, содержащей прямой или косвенный вызов определяемой (первой функции). Если в теле функции явно используется вызов этой же функции, то имеет место прямая рекурсия. Из рекурсивной функции надо предусмотреть выход, иначе это вызовет «зависание» системы. Рекурсивные алгоритмы эффективны, например, в тех задачах, где рекурсия использована в определении обрабатываемых данных.   Пример [программа pr_32]: #include<stdio.h> #include<conio.h>   float factorial(long int); float stepen(float, int);   int main(void) { long int f; int y; float x; clrscr(); printf("Enter x^y "); scanf("%f%d",&x, &y); printf("Enter f "); scanf("%ld", &f); printf(" 1) x^y = %.2f ", stepen(x, y)); printf("2) f! = %.0f ", factorial(f)); getch(); return 0; }   float factorial(long int k) { if(k<0) return 0; if(k==0 || k==1) return 1; return k*factorial(k-1); }   float stepen(float a, int b) { if(a==0) return 0; if(b==0) return 1; if(b>0) return a*stepen(a, b-1); return stepen(a, b+1)/a; }   Результат: k*(k-1)*(k-2)*…*3*2*1*1   Сравнивая рекурсию с итерационными методами, рекурсивные алгоритмы наиболее пригодны в случаях, когда поставленная задача или используемые данные определены рекурсивно. В тех случаях, когда вычисляемые значения определяются с помощью простых рекуррентных соотношений, гораздо эффективнее применять итеративные методы.

 

Лекция № 14

 

Написал Lekka
07.11.2008

5rik.ru - Материалы для учебы и научной работы