Register

Static

Exetrn

Auto

Локальные переменные в каждой функции являются внутренними и не известны другой функции. Вся связь между подпрограммами осуществляется через параметры. Язык С допускает наличие переменных известных во всех функциях, они называются - глобальными.

Сделаем переменную b из предыдущего примера глобальной.

#include …

float b ; // описание глобальной переменной

void func (int *a ) // в скобках формальные параметры

{ int x, y ; // локальные переменные

x=b ; // использование глобальной переменной

}

main()

{

int x, y, z ; // y и z локальные переменные

b++ ; // использование глобальной переменной

func(&x ) ; // в скобках фактические параметры

}

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

КЛАССЫ ПАМЯТИ.

 

Каждая переменная имеет тип, кроме того каждая переменная принадлежит к некоторому классу памяти. До использования подпрограмм мы не задумывались об этом, т.к. пользовались всегда одним классом памяти – автоматическим. Вообще существует 4 класса памяти.

 

extern Внешний
auto Автоматический
static Статический
register регистровый

Классы памяти позволяют определить:

- какие функции имеют доступ к каким переменным, т.е. определяют области

видимости;

- как долго переменные находятся в памяти.

Рассмотрим каждый класс памяти.

 

 

По умолчанию все переменные, описанные внутри функции, являются автоматическими. Эти переменные локальные.

main()

{

int x, y ; одно и то же auto int x, y ;

}

Аuto иногда пишут специально, чтобы не искать описание объектов вне функции.

Переменные с теми же именами, используемые в других функциях, физически другие, нет никакой связи между ними.

Автоматическая переменная возникает при вызове функции, и исчезает при ее окончании, ее ячейка может быть использована для других целей.

int a ;

main()

{ extern int a ; // a – внешняя, глобальная переменная.

x=a*b ;

}

Будет то же самое, если вообще не описывать переменную а в main, но когда явно описано, что переменная extern, то понятно, что она глобальная и описана выше.

Внешние переменные известны всем функциям, и не исчезнут, пока не закончит работу вся программа.

 

Статические переменные локальные, т.е. видны только из одной функции, но они не исчезают при окончании работы функции до окончания работы всей программы. Компилятор хранит их значения от одного вызова до другого.

Рассмотрим простой пример иллюстрирующий работу статической переменной.

main()

{ int i ;

for ( i=1 ; i<=3 ; i++)

{ printf (“ i=%d\n”, i) ;

ff() ;

}

}

void ff(void)

{ int a=1 ;

static int b=1 ;

printf(“a=%d b=%d\n”, a++, b++) ;

}

печать результатов будет такой:

i=1

a=1 b=1

i=2

a=1 b=2

i=3

a=1 b=3

Автоматическая переменная а при каждом вызове инициализируется заново, а статическая переменная b только при первом вызове функции. Такие переменные удобно использовать, когда нужно накапливать сумму при многократном обращении к подпрограмме.

Могут быть и внешние статические переменные.

 

Регистровые переменные хранятся на регистрах центрального процессора, где доступ и работа с ними быстрее, чем в памяти. С такими переменными мы работать не будем.

 

Рекомендация: старайтесь использовать автоматические переменные и передавать информацию в

функции через параметры. Неосторожное использование глобальных переменных

порождает большие ошибки.

 

РЕКУРСИЯ.

 

Будем рассматривать рекурсию, как возможность определения объекта ссылкой на сам этот объект. В математике такие определения используются очень часто.

Например, факториал:

n!=n*(n-1)!

Определитель более высокого порядка определяется как сумма определителей более низкого порядка ( разложение по строке ).

. - матрица, полученная из А вычеркиванием 1-й строки и

к-го столбца.

В программировании рекурсия реализуется путем обращения функции к себе самой.

 

int fun( int n)

{

fun( n-1 ) ; // вызов функцией себя самой.

}

Это прямая рекурсия, а может быть еще косвенная, когда одна функция вызывает другую, а другая первую.

int fun1( int n ) int fun2 ( int k )

{ {

… …

fun2(k-1) ; fun1(n-1) ;

… …

} }

Функция, содержащая самовызов должна иметь тривиальную ветку рекурсии, т.е. то условие при котором прекращается самовызов. Рекурсия должна быть конечной. Рекурсивная функция должна содержать хотя бы один параметр, значение которого определяет прекращение самовызова.

Разберемся на примерах.

Решим задачу вычисления факториала двумя способами.

Обычный вариант ( без рекурсии).

n!=1*2*3*…*n

main()

{ int i, n, fuc ;

scanf(“%d”, &n) ;

for (i=2, fuc=1 ; i<=n ; i++) fuc*=i ;

printf(“%d”, fuc) ;

}

Рекурсивный вариант.

n!=n*(n-1)!

int fuc( int ) ;

main()

{ int n ;

scanf(“%d”, &n) ;

printf(“\n факториал=%d”, fuc(n)) ;

}

int fuc( int n)

{ if( n==1) return 1 ;

else return ( n*fuc(n-1)) ;

}

Конечно, такие простые задачи рекурсивно решают редко, но понять, как это делать можно только на них.

Следующая задача в рекурсивном варианте более сложна. Найти 10 чисел Фибоначчи. Без рекурсии эта задача решалась в разделе ВЕКТОРЫ.

#include<stdio.h>

int fib( int) ;

const len=10 ;

int a[len] ;

main()

{

(void) fib( len) ; // первый вызов функции вычисляющей числа Фибоначчи

printf(“\n”) ;

for( int k=0 ; k<len ; k++) printf(“%d”, a[k]) ; // печать чисел Фибоначчи, размещенных в векторе

}

int fib( int k)

{

extern int a[len] ;

int i=k-1 ;

if(i==1||i==0) { a[i]=1 ; // тривиальная ветка рекурссии

return 1 ;

}

else { a[i]= fib(k-1) + fib(k-2) ; // двойной самовызов функции

return a[i] ;

}

}

Существует множество задач, которые без рекурсии решить очень трудно и не наглядно.

 

1. вычислить для заданного n.

2. вычисление определителя, формулу смотри выше.

3. работа с деревьями.

 

ФАЙЛЫ.

 

Файлом будем называть – именованную часть внешней памяти последовательного доступа.

Расширение файла определяет его природу, характеристика аналогичная типу переменной, но природа файла может быть гораздо более сложная.

Например: .c, .cpp, .pas – языковые;

.txt, .doc - текстовые файлы;

.exe – загрузочный.

Работа в любой операционной системе производится именно с файлами. Windows и Norton производят создание, просмотр, копирование, удаление файлов.

Для чего же будут использоваться файлы при решении наших задач?

Представьте ситуацию – вам нужно отладить программу, но исходные данные для ее запуска довольно большие и набирать их каждый раз довольно трудоемко, случайно допущенная ошибка приводит к тому, что нужно начинать все сначала. Другой случай, когда результаты вычислений очень большие, они быстро пробегают по экрану и вы не успеваете их остановить и проанализировать. В обоих случаях нужно осуществлять ввод из файла и вывод в файл.

Чтение из файла:

file *in ;

int age ;

in=fopen(“test1.dat”, “r”) ;

fscanf(in, “%d”, &age) ;

fclose(in) ;

Читать можно только из ранее созданного и заполненного файла.

Запись в файл:

file *out ;

int age ;

out=fopen(“test2.dat”, “w”) ;

fprintf(out, “%d”, age) ;

fclose(out) ;

При записи создается новый файл с именем test2.dat, если такой файл уже существовал, то он обновляется, т.е. исчезает вся находившаяся в нем информация и записывается новая.

 

Чем отличается работа с файлом от работы с массивом?

В оперативную память не загружается весь файл, а только одна текущая его компонента. По файлу, как по массиву, нельзя ходить туда-сюда, извлекая любые элементы. Двигаться можно только в одном направлении и осуществлять только одну операцию: чтение или запись. Очень редко когда система позволяет и то и другое одновременно. Но за то, файл может быть, как угодно длинным и не нужно знать, сколько в нем компонент, обрабатываем по одной, столько сколько лежит.

Ввод строк из файла и вывод их в другой.

file *in, out ;

char str[81] ;

in=fopen(“test1.dat”, “r”) ;

out=fopen(“test2.dat”, “w”) ;

while ( fgets(str, 81,in)!=NULL)

fputs(str,out) ;

fclose(in) ;

fclose(out) ;

При работе с файлами можно использовать все известные нам функции ввода-вывода, указывая только, из какого файла читаем.

Рассмотрим практические задачи работы с файлами.

1. Найти максимум в файле.

file *in;

int max, a ;

in=fopen(“test.dat”, “r”) ;

fscanf(in, “%d”, &max) ;

 

while ( fscanf(in, “%d”, &a)!=NULL)

if( a> max) max=a ;

printf( “максимум=%d”, max) ;

 

fclose(in) ;

2. найти максимум в файле и заменить его нулем.

После того как проработала предыдущая программа и значение максимума найдено, нужно выполнить следующее:

file *out ;

in=fopen(“test.dat”, “r”) ;

out=fopen(“test1.dat”, “w”) ;

while ( fscanf(in, “%d”, &a)!=NULL)

{

if( a==max) a =0 ;

fprintf( out,“%d”, a) ; // производим копирование всех компонент файла

} // без изменения, кроме одной, где находится max, его

fclose(in) ; // заменяем на ноль.

fclose(out) ;

 

Работа с файлами огромный раздел программирования. С использованием файлов организованы все БД ( базы данных) и БЗ (базы знаний), Правовые и бухгалтерские системы. Поиск информации производится или по ее реквизитам (характеристикам) или по словам. Пользователь даже не задумывается, что работает с файлами.

 

ПОДГОТОВКА К ЗАЧЕТУ.

 

Зачет в первом семестре представляет собой решение задачи одной из следующих групп.

1. Вычислительные.

Уметь определять делимость чисел, простые числа, цифры любого числа, перевод из любой системы счисления в любую, вычислять члены последовательности суммы и произведения.

Задачи для проверки:

1.1. Определить количество «счастливых билетов» в катушке от 000 000 до 999 999.

1.2. Вычислить , где , . С точностью до 0.001.