}
int f2(){
printf("x=%d\n", x); /* 5 */
return 0;
}
void main(){
int x, y; /* main::x */
x = 111; /* 1 */
printf("x=%d\n", x); /* 2 */
printf("glob=%d\n", globvar); /* 3 */
y = f1();
y = f2();
}
В данном примере мы видим:
- во-первых мы видим ФУНКЦИИ БЕЗ ПАРАМЕТРОВ. Это нормальная ситуация.
- во-вторых тут используются ТРИ переменные с именем "x".
Как выполняется программа?
/* 1 */ main::x = 111;
Это локальный x, а не глобальный.
Глобальный x попрежнему содержит 12.
/* 2 */ Напечатает значение переменной main::x, то есть 111.
Внутри функции main глобальная переменная ::x
заслонена своей собственной переменной x.
В данном случае НЕТ СПОСОБА добраться из main к глобальной
переменной x, это возможно только в языке Си++ по имени ::x
К переменной же globvar у нас доступ есть.
/* 3 */ Печатает ::globvar. Мы обнаруживаем, что ее значение 0.
В отличие от глобальных переменных,
которые изначально содержат МУСОР,
глобальные переменные изначально содержат значение 0.
В рамочку, подчеркнуть.
/* 4 */ При вызове f1()
переменная f1::x
заслоняет собой как
main::x
так и
::x
В данном случае напечатается 77,
но ни ::x ни main::x не будут изменены оператором x = 77.
Это изменялась f1::x
/* 5 */ При вызове f2() история интереснее.
Тут нет своей собственной переменной x.
Но какая переменная печатается тут -
::x или
main::x ?
Ответ: ::x
то есть 12.
Переменные названы локальными еще и потому,
что они НЕВИДИМЫ В ВЫЗЫВАЕМЫХ ФУНКЦИЯХ.
Это ОПРЕДЕЛЕНИЕ локальных переменных.
(Поэтому не спрашивайте "почему?" По определению)
То есть, если мы имеем
funca(){
int vara;
...
...funcb();... /* вызов */
...
}
то из функции funcb() мы НЕ ИМЕЕМ ДОСТУПА К ПЕРЕМЕННОЙ vara.
funcb(){
int z;
z = vara + 1; /* ошибка,
vara неизвестна внутри funcb() */
}
Если, в свою очередь, funcb() вызывает funcc(),
то и из funcc() переменная vara невидима.
Остановитесь и осознайте.
Это правило служит все той же цели - разные функции
могут быть написаны разными программистами, которые могут
использовать одни и те же имена для РАЗНЫХ переменных,
не боясь их взаимопересечения.
Множества имен, использованных в разных функциях, независимы
друг от друга. Имена из одной функции НИКАК не относятся
к переменным с теми же именами ИЗ ДРУГОЙ функции.
Вернемся к параграфу КАК ПРОИСХОДИТ ВЫЗОВ ФУНКЦИИ
и рассмотрим пункт (a). Теперь он может быть описан как
(a) Локальные переменные и аргументы вызывающей функции делаются невидимыми.
~~~~~~~~~~
А при возврате из функции:
(5) Локальные переменные и аргументы вызывающей функции снова делаются видимыми.
ОДНАКО глобальные переменные видимы из ЛЮБОЙ функции,
исключая случай, когда глобальная переменная заслонена
одноименной локальной переменной данной функции.
---------------------------------------------------------------------------
ПРОЦЕДУРЫ
=========
Бывают функции, которые не возвращают никакого значения.
Такие функции обозначаются void ("пустышка").
Такие функции называют еще ПРОЦЕДУРАМИ.
void func(){
printf("Приветик!\n");
return; /* вернуться в вызывающую функцию */
}
Такие функции вызываются ради "побочных эффектов",
например печати строчки на экран или изменения глобальных (и только)
переменных.
int glob;
void func(int a){
glob += a;
}
Оператор return тут необязателен, он автоматически выполняется
перед последней скобкой }
Вызов таких функций не может быть использован
в операторе присваивания:
main(){
int z;
z = func(7); /* ошибка, а что мы присваиваем ??? */
}
Корректный вызов таков:
main(){
func(7);
}
Просто вызов и все.
ЗАЧЕМ ФУНКЦИИ?
Чтобы вызывать их с разными аргументами!
int res1, res2;
...
res1 = func(12 * x * x + 177, 865, 'x');
res2 = func(432 * y + x, 123 * y - 12, 'z');
Кстати, вы заметили, что список фактических параметров
следует через запятую;
и выражений ровно столько, сколько параметров у функции?
Функция описывает ПОСЛЕДОВАТЕЬНОСТЬ ДЕЙСТВИЙ,
которую можно выполнить много раз,
но с разными исходными данными (аргументами).
В зависимости от данных она будет выдавать разные результаты,
но выполняя одни и те же действия.
В том то и состоит ее прелесть:
мы не дублируем один кусок программы много раз,
а просто "вызываем" его.
Функция - абстракция АЛГОРИТМА, то есть последовательности действий.
Ее конкретизация - вызов функции с уже определенными параметрами.
---------------------------------------------------------------------------
Оператор return может находиться не только в конце функции,
но и в ее середине.
Он вызывает немедленное прекращение тела функции и возврат значения
в точку вызова.
int f(int x){
int y;
y = x + 4;
if(y > 10) return (x - 1);
y *= 2;
return (x + y);
}
РЕКУРСИВНЫЕ ФУНКЦИИ. СТЕК
Рекурсивной называется функция, вызывающая сама себя.
int factorial(int arg){
if(arg == 1)
return 1; /* a */
else
return arg * factorial(arg - 1); /* b */
}
Эта функция при вызове factorial(n) вычислит произведение
n * (n-1) * ... * 3 * 2 * 1
называемое "факториал числа n".
В данной функции переменная arg будет отведена (а после и уничтожена) МНОГО РАЗ.
Так что переменная factorial::arg должна получить еще и НОМЕР вызова функции:
factorial::arg[уровень_вызова]
И на каждом новом уровне новая переменная скрывает все предыдущие.
Так для factorial(4) будет
+----------------------------------------+
| | factorial::arg[ 0_ой_раз ] есть 4 |
| | factorial::arg[ 1_ый_раз ] есть 3 |
| | factorial::arg[ 2_ой_раз ] есть 2 |
| | factorial::arg[ 3_ий_раз ] есть 1 |
V --------+ +---------
Затем пойдут возвраты из функций:
+----------------------------------------+
A | /* b */ return 4 * 6 = 24 |
| | /* b */ return 3 * 2 = 6 |
| | /* b */ return 2 * 1 = 2 |
| | /* a */ return 1; |
| --------+ +---------
Такая конструкция называется СТЕК (stack).
--------+ +------------
| | пустой стек
+---------------+
Положим в стек значение a
|
--------+ | +------------
| V |
+---------------+
| a | <--- вершина стека
+---------------+
Положим в стек значение b
--------+ +------------
| |
+---------------+
| b | <--- вершина стека
+---------------+
| a |
+---------------+
Положим в стек значение c
--------+ +------------
| |
+---------------+
| c | <--- вершина стека
+---------------+
| b |
+---------------+
| a |
+---------------+
Аналогично, значения "снимаются со стека" в обратном порядке: c, b, a.
В каждый момент времени у стека доступно для чтения (копирования) или
выбрасывания только данное, находящееся на ВЕРШИНЕ стека.
Так и в нашей рекурсивной функции переменная factorial::arg
ведет себя именно как стек (этот ящик-стек имеет имя arg) -
она имеет ОДНО имя, но разные значения в разных случаях.
Переменные, которые АВТОМАТИЧЕСКИ ведут себя как стек,
встречаются только в (рекурсивных) функциях.
Стек - это часто встречающаяся в программировании конструкция.
Если подобное поведение нужно программисту, он должен промоделировать
стек при помощи массива:
int stack[10];
int in_stack = 0; /* сколько элементов в стеке */
/* Занесение значения в стек */
void push(int x){
stack[in_stack] = x;
in_stack++;
}
/* Снять значение со стека */
int pop(){
if(in_stack == 0){
printf("Стек пуст, ошибка.\n");
return (-1);
}
in_stack--;
return stack[in_stack];
}
Обратите в нимание, что нет нужды СТИРАТЬ (например обнулять)
значения элементов массива, выкинутых с вершины стека.
Они просто больше не используются, либо затираются новым значением при
помещении на стек нового значения.
void main(){
push(1);
push(2);
push(3);
while(in_stack > 0){
printf("top=%d\n", pop());
}
}
СТЕК И ФУНКЦИИ
Будем рассматривать каждый ВЫЗОВ функции как помещение в специальный стек
большого "блока информации", включающего в частности
АРГУМЕНТЫ И ЛОКАЛЬНЫЕ ПЕРЕМЕННЫЕ вызываемой функции.
Оператор return из вызванной функции выталкивает со стека ВЕСЬ такой блок.
В качестве примера рассмотрим такую программу:
int x = 7; /* глобальная */
int v = 333; /* глобальная */
int factorial(int n){
int w; /* лишняя переменная, только для демонстрационных целей */
w = n;
if(n == 1) return 1; /* #a */
else return n * factorial(n-1); /* #b */
}
void func(){
int x; /* func::x */
x = 777; /* #c */
printf("Вызвана функция func()\n");
}
void main(){
int y = 12; /* main::y */
int z;
/* A */
z = factorial(3); /* B */
printf("z=%d\n", z);
func(); /* C */
}
---------------------------------------------------------------------------
Выполнение программы начнется с вызова функции main().
В точке /* A */
| в з г л я д |
V V
--------+ +--------
|=======================|
| z = мусор |
| y = 12 | +------+---------+