Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#15| Dragon God
Demon's Souls |#14| Flamelurker
Demon's Souls |#13| Storm King
Demon's Souls |#12| Old Monk & Old Hero

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Богатырев А. Весь текст 120.46 Kb

Руководство полного идиота по программированию на C

Предыдущая страница Следующая страница
1 2 3 4 5  6 7 8 9 10 11
}

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          |           +------+---------+
Предыдущая страница Следующая страница
1 2 3 4 5  6 7 8 9 10 11
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (6)

Реклама