Руководство полного чайника по программированию на языке Си

КАК ПРОИСХОДИТ ВЫЗОВ ФУНКЦИИ


КАК ПРОИСХОДИТ ВЫЗОВ ФУНКЦИИ ============================

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

/* ОПРЕДЕЛЕНИЕ ФУНКЦИИ func(). */ /* Где func - ее имя. Назвать мы ее можем как нам угодно. */

int func(int a, int b, int c){ int x, y;

... x = a + 7; ... b = b + 4; ...

return(некое_значение); }

Здесь a, b, c - аргументы функции (параметры) x, y - локальные переменные

Точка вызова - находится внутри какой-то другой функции, например функции main()

main(){

int zz, var; ... var = 17; zz = func(33, 77, var + 3) + 44; ... }

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

zz = func(33, 77, var + 3) + 44;

1) Происходит ВЫЗОВ ФУНКЦИИ func()



(a) Этот пункт мы увидим ниже.

(b) Создаются переменные с именами a, b, c, x, y;

(c) Переменным-аргументам присваиваются начальные значения, которые берутся из точки вызова. В точке вызова перечислен список (через запятую) выражений (формул):

func(выражение1, выражение2, выражение3)

Вычисленные значения этих выражений соответственно будут присвоены 1-ому, 2-ому и 3-ему аргументам (параметрам) из определения функции:

int func(a, b, c){ /* a = номер 1, b = 2, c = 3 */

Первый параметр:

a = 33;

Второй параметр:

b = 77;

Третий параметр:

c = var + 3;

то есть, вычисляя,

c = 20;

Локальные переменные x и y содержат неопределенные значения, то есть мусор (мы не можем предсказать их значения, пока не присвоим им явным образом какое-либо значение сами).

2) Выполняется ТЕЛО функции, то есть вычисления, записанные внутри { ... } в определении функции. Например:

x = a + 7;

И параметры, и локальные переменные - это ПЕРЕМЕННЫЕ, то есть их можно изменять.

b = b + 4;

При этом никакие переменные ВНЕ этой функции не изменяются. (Об этом еще раз позже).

3) Производится ВОЗВРАТ из функции.

... return(некое_значение); }

Например, это может быть

... return(a + 2 * x); }

Рассмотрим, что при этом происходит в точке вызова:

zz = func(33, 77, var + 3) + 44;

(1) Вычеркиваем func(.....)

zz = XXXXXXX + 44;

(2) Вычисляем значение "некое_значение" в операторе return, и берем КОПИЮ этого значения. Пусть при вычислении там получилось 128.


(3) Подставляем это значение на место вычеркнутого func(.....) У нас получается

zz = 128 + 44;

(4) АВТОМАТИЧЕСКИ УНИЧТОЖАЮТСЯ локальные переменные и аргументы функции:

a - убито b - убито c - убито x - убито y - убито

Таких переменных (и их значений) больше нет в природе.

(5) Пункт, который мы обсудим позже.

(6) Продолжаем вычисление:

zz = 128 + 44;

Вычисляется в

zz = 172; /* оператор присваивания */

int func1(int x){ printf("func1: x=%d\n", x); /* 1 */ x = 77; printf("func1: x=%d\n", x); /* 2 */ return x; }

void main(){ int var, y;

var = 111; y = func1(var); /* @ */

printf("main: var=%d\n", var); /* 3 */ }

В данном случае в точке @ мы передаем в функцию func1() ЗНАЧЕНИЕ переменной var, равное 111. Это значит, что при вызове функции будет создана переменная x и ей будет присвоено начальное значение 111

x = 111;

Поэтому первый оператор printf() напечатает 111.

Затем мы изменяем значение переменной x на 77. Мы меняем переменную x, но не переменную var !!! Использовав ЗНАЧЕНИЕ (его копию) из переменной var для x, мы о переменной var забыли - она нас не касается (а мы - ее).

Поэтому второй оператор printf() напечатает 77. В переменной же var осталось значение 111, что и подтвердит нам третий оператор printf, который напечатает 111.

ВРЕМЕННОЕ СОКРЫТИЕ ПЕРЕМЕННЫХ =============================

int func1(int x){ /* f.1 */ printf("func1: x=%d\n", x); /* f.2 */ x = 77; /* f.3 */ printf("func1: x=%d\n", x); /* f.4 */ return x; /* f.5 */ }

void main(){ int x, y; /* 1 */

x = 111; /* 2 */ y = func1(x); /* 3 */

printf("main: x=%d y=%d\n", x, y); /* 4 */ }

А теперь мы и переменную внутри main(), и аргумент функции func1() назвали одним и тем же именем. Что будет?

Будет то же самое, что в предыдущем примере.

В момент вызова функции func1() будет создана НОВАЯ переменная с именем x, а старая (прежняя) переменная и ее значение будут ВРЕМЕННО СПРЯТАНЫ (скрыты).

Можно было бы уточнить эти переменные именами функций, в которых они определены:



main::x

и

func1::x

( но это уже конструкции из языка Си++, а не Си).

Выполним программу по операторам:

|/* 1 */ Отводятся переменные main::x и main::y для целых чисел; |/* 2 */ main::x = 111; |/* 3 */ Вызывается func1(111); | +-------+ . |/* f.1 */ Отводится переменная func1::x со значением 111; . |/* f.2 */ Печатается 111 из переменной func1::x; . | . |/* f.3 */ func1::x = 77; (это не main::x, а другая переменная, . | ЛОКАЛЬНАЯ для функции func1. . | Переменную main::x мы сейчас не видим - . | она "заслонена" именем нашей локальной . | переменной. . | Поэтому мы не можем ее изменить). . | . |/* f.4 */ Печатает 77 из func1::x; . |/* f.5 */ Возвращает значение func1::x , то есть 77. . | Переменная func1::x уничтожается. . | . | Теперь мы снова возвращаемся в функцию main(), . | где имя x обозначает переменную main::x . | а не func1::x +-------+ | |/* 3 */ y = 77; |/* 4 */ Печатает значения main::x и main::y, то есть | 111 и 77.

Этот механизм сокрытия имен позволяет писать функции main() и func1() разным программистам, позволяя им НЕ ЗАБОТИТЬСЯ о том, чтобы имена локальных переменных в функциях НЕ СОВПАДАЛИ. Пусть совпадают - хуже не будет, механизм упрятывания имен разрешит конфликт. Зато программист может использовать любое понравившееся ему имя в любой функции - хотя бы и x, или i.

То же самое происходит с локальными переменными, а не с аргументами функции.

int func1(int arg){ /* локальная переменная-параметр func1::arg */ int x; /* локальная переменная func1::x */

x = arg; printf("func1: x=%d\n", x); x = 77; printf("func1: x=%d\n", x); return x; }

void main(){ int x, y; /* переменные main::x и main::y */

x = 111; y = func1(x);

printf("main: x=%d y=%d\n", x, y); }

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



имя_функции(..., ..., ....) арг1 арг2 арг3

в месте вызова функции.

То есть

ОПИСАНИЕ ФУНКЦИИ:

int f( int арг1, int арг2, int арг3){ int перем1, перем2; ... /* продолжение */ }

ВЫЗОВ:

.... f(выражение1, выражение2, выражение3) ...

ТО В ТЕЛЕ ФУНКЦИИ ВЫПОЛНИТСЯ (в момент ее вызова):

арг1 = выражение1; арг2 = выражение2; арг3 = выражение3;

перем1 = МУСОР; перем2 = МУСОР;

... /* продолжение */

ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ =====================

Наконец, существуют переменные, которые объявляются ВНЕ ВСЕХ ФУНКЦИЙ, и существующие все время выполнения программы (а не только то время, когда активна функция, в которой они созданы).

Локальные переменные и аргументы УНИЧТОЖАЮТСЯ при выходе из функции. Глобальные переменные - нет.

int x = 12; /* ::x - ей можно заранее присвоить константу */ int globvar; /* ::globvar */

int f1(){ int x; /* f1::x */

x = 77; printf("x=%d\n", x); /* 4 */ return x; }

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); }

Просто вызов и все.


Содержание раздела