1.120.

Что напечатает программа? (Пример посвящен указателям на функции и массивам функций):


    int f(n){ return n*2; }

    int g(n){ return n+4; }

    int h(n){ return n-1; }

    int (*arr[3])() = { f, g, h };

    main(){

     int i;

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

         printf( "%d\n", (*arr[i])(i+7) );

    }

1.121. Что напечатает программа?


    extern double sin(), cos();

    main(){ double x; /* cc -lm */

      for(x=0.0; x < 1.0; x += 0.2)

        printf("%6.4g %6.4g %6.4g\n",

            (x > 0.5 ? sin : cos)(x), sin(x), cos(x));

    }

то же в варианте

    extern double sin(), cos();

    main(){ double x; double (*f)();

      for(x=0.0; x < 1.0; x += 0.2){

            f = (x > 0.5 ? sin : cos);

            printf("%g\n", (*f)(x));

      }

    }

1.122. Рассмотрите четыре реализации функции факториал:


         n! = 1 * 2 * ... * n

     или n! = n * (n-1)!   где 0! = 1

Все они иллюстрируют определенные подходы в программировании:

    /* ЦИКЛ (ИТЕРАЦИЯ) */

    int factorial1(n){ int res = 1;

        while(n > 0){ res *= n--; }

        return res;

    }

    /* ПРОСТАЯ РЕКУРСИЯ */

    int factorial2(n){

        return (n==0 ? 1 : n * factorial2(n-1));

    }

    /* Рекурсия, в которой функция вызывается рекурсивно

     * единственный раз - в операторе return, называется

     * "хвостовой рекурсией" (tail recursion) и

     * легко преобразуется в цикл */

    /* АВТОАППЛИКАЦИЯ */

    int fi(f, n) int (*f)(), n;

    {   if(n == 0) return 1;

        else       return n * (*f)(f, n-1);

    }

    int factorial3(n){ return fi(fi, n); }

    /* РЕКУРСИЯ С НЕЛОКАЛЬНЫМ ПЕРЕХОДОМ */

    #include <setjmp.h>

    jmp_buf checkpoint;

    void fact(n, res) register int n, res;

    {   if(n) fact(n - 1, res * n);

        else  longjmp(checkpoint, res+1);

    }

    int factorial4(n){ int res;

        if(res = setjmp(checkpoint)) return (res - 1);

        else fact(n, 1);

    }

1.123.

Напишите функцию, печатающую целое число в системе счисления с основанием base. Ответ:


         printi( n, base ){

            register int i;

            if( n < 0 ){  putchar( '-' ); n = -n;   }

            if( i = n / base )

                    printi( i, base );

            i = n % base ;

            putchar( i >= 10 ? 'A' + i - 10 : '0' + i );

         }

Попробуйте написать нерекурсивный вариант с накоплением ответа в строке. Приведем рекурсивный вариант, накапливающий ответ в строке s и пользующийся аналогом функции printi: функция prints - такая же, как printi, но вместо вызовов putchar(нечто); в ней написаны операторы


            *res++ = нечто;

и рекурсивно вызывается конечно же prints. Итак:

    static char *res;

     ... текст функции prints ...

    char *itos( n, base, s )

         char *s; /* указывает на char[] массив для ответа */

    {

            res = s; prints(n, base); *res = '\0';

            return s;

    }

    main(){ char buf[20]; printf( "%s\n", itos(19,2,buf); }

1.124.

Напишите функцию для побитной распечатки целого числа. Имейте в виду, что число содержит 8 * sizeof(int) бит. Указание: используйте операции битового сдвига и &. Ответ:


    printb(n){

      register i;

      for(i = 8 * sizeof(int) - 1; i >= 0; --i)

         putchar(n & (1 << i) ? '1':'0');

    }

1.125.

Напишите функцию, склоняющую существительные русского языка в зависимости от их числа. Например:


     printf( "%d кирпич%s", n, grammar( n, "ей", "", "а" ));

Ответ:

     char *grammar( i, s1, s2, s3 )

     char *s1, /* прочее */

          *s2, /* один */

          *s3; /* два, три, четыре */

     {

            i = i % 100;

            if( i > 10 && i <= 20 ) return s1;

            i = i % 10;

            if( i == 1 ) return s2;

            if( i == 2 || i == 3 || i == 4 )

                   return s3;

            return s1;

     }

1.126.

Напишите оператор printf, печатающий числа из интервала 0..99 с добавлением нуля перед числом, если оно меньше 10 :


            00 01 ... 09 10 11 ...

Используйте условное выражение, формат.

Ответ:


       printf ("%s%d", n < 10 ? "0" : "", n);

            либо

       printf ("%02d", n );

            либо

       printf ("%c%c", '0' + n/10, '0' + n%10 );

1.127. Предостережем от одной ошибки, часто допускаемой начинающими.


            putchar( "c" );   является ошибкой.

            putchar( 'c' );   верно.

Дело в том, что putchar требует аргумент - символ, тогда как "c" - СТРОКА из одного символа. Большинство компиляторов (те, которые не проверяют прототипы вызова стандартных функций) НЕ обнаружит здесь никакой синтаксической ошибки (кстати, ошибка эта - семантическая). Также ошибочны операторы


            printf ( '\n' ); /* нужна строка */

            putchar( "\n" ); /* нужен символ */

            putchar( "ab" ); /* нужен символ */

            putchar( 'ab' ); /* ошибка в буквенной константе */

            char c; if((c = getchar()) == "q" ) ... ;

            /* нужно писать 'q' */

Отличайте строку из одного символа и символ - это разные вещи! (Подробнее об этом в следующей главе).

1.128.

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


            int m[20]; int i = 0;

            while( scanf( "%d", & m[i++] ) != EOF );

            printf( "Ввели %d чисел\n", i );

В итоге i окажется на 1 больше, чем ожидалось. Разберитесь в чем дело.

Ответ: аргументы функции вычисляются до ее вызова, поэтому когда мы достигаем конца файла и scanf возвращает EOF, i++ в вызове scanf все равно делается. Надо написать


            while( scanf( "%d", & m[i] ) != EOF ) i++;

1.129. Замечание по стилистике: при выводе сообщения на экран


            printf( "Hello    \n" );

пробелы перед \n достаточно бессмысленны, поскольку на экране никак не отобразятся. Надо писать (экономя память)

            printf( "Hello\n" );

Единственный случай, когда такие пробелы значимы - это когда вы выводите текст инверсией. Тогда пробелы отображаются как светлый фон.

Еще неприятнее будет


            printf( "Hello\n     " );

поскольку концевые пробелы окажутся в начале следующей строки.

1.130. printf - интерпретирующая функция, т.е. работает она довольно медленно. Поэтому вместо


    char s[20]; int i;

      ...

    printf( "%c", s[i] );   и    printf( "\n" );

надо всегда писать

    putchar( s[i] );        и    putchar( '\n' );

поскольку printf в конце-концов (сделав все преобразования по формату) внутри себя вызывает putchar. Так сделаем же это сразу!

1.131.

То, что параметр "формат" в функции printf может быть выражением, позволяет делать некоторые удобные вещи. Например:


    int x; ...

    printf( x ? "значение x=%d\n" : "x равен нулю\n\n", x);

Формат здесь - условное выражение. Если x!=0, то будет напечатано значение x по формату %d. Если же x==0, то будет напечатана строка, не содержащая ни одного %-та. В результате аргумент x в списке аргументов будет просто проигнорирован. Однако, например


    int x = ... ;

    printf( x > 30000 ? "%f\n" : "%d\n", x);

(чтобы большие x печатались в виде 31000.000000) незаконно, поскольку целое число нельзя печатать по формату %f ни в каких случаях. Единственным способом сделать это является явное приведение x к типу double:


    printf("%f\n", (double) x);

Будет ли законен оператор?

    printf( x > 30000 ? "%f\n" : "%d\n",

            x > 30000 ? (double) x : x );

Ответ: нет. Условное выражение для аргумента будет иметь "старший" тип - double. А значение типа double нельзя печатать по формату %d. Мы должны использовать здесь оператор if:


    if( x > 30000 ) printf("%f\n", (double)x);

    else            printf("%d\n", x);

1.132.

Напишите функцию, печатающую размер файла в удобном виде: если файл меньше одного килобайта - печатать его размер в байтах, если же больше - в килобайтах (и мегабайтах).


    #define KBYTE    1024L  /* килобайт */

    #define THOUSAND 1024L  /* кб. в мегабайте */

    void tellsize(unsigned long sz){

      if(sz < KBYTE) printf("%lu байт", sz);

      else{

        unsigned long Kb  = sz/KBYTE;

        unsigned long Mb  = Kb/THOUSAND;

        unsigned long Dec = ((sz % KBYTE) * 10) / KBYTE;

          if( Mb ){

              Kb %= THOUSAND;

    printf( Dec ? "%lu.%03lu.%01lu Мб." : "%lu.%lu Мб.",

                    Mb,   Kb,   Dec );

            } else

    printf( Dec ? "%lu.%01lu Кб.":"%lu Кб.", Kb, Dec);

      }

      putchar('\n');

    }

1.133. Для печати строк используйте


            printf("%s", string);   /* A */

    но не   printf(string);         /* B */

Если мы используем вариант B, а в строке встретится символ '%'

    char string[] = "abc%defg";

то %d будет воспринято как формат для вывода целого числа. Во-первых, сама строка %d не будет напечатана; во-вторых - что же будет печататься по этому формату, когда у нас есть лишь единственный аргумент - string?! Напечатается какой-то мусор!

1.134. Почему оператор


    char s[20];

    scanf("%s", s); printf("%s\n", s);

в ответ на ввод строки

      Пушкин  А.С.

печатает только "Пушкин"?

Ответ: потому, что концом текста при вводе по формату %s считается либо \n, либо пробел, либо табуляция, а не только \n; то есть формат %s читает слово из текста.

Чтение всех символов до конца строки, (включая пробелы) должно выглядеть так:


    scanf("%[^\n]\n", s);

      %[^\n] - читать любые символы, кроме \n (до \n)

      \n     - пропустить \n на конце строки

      %[abcdef] - читать слово,

                  состоящее из перечисленных букв.

      %[^abcde] - читать слово из любых букв,

        кроме перечисленных (прерваться по букве из списка).

Пусть теперь строки входной информации имеют формат:

       Фрейд Зигмунд 1856 1939

Пусть мы хотим считывать в строку s фамилию, в целое y - год рождения, а прочие поля - игнорировать. Как это сделать? Нам поможет формат "подавление присваивания" %*:


     scanf("%s%*s%d%*[^\n]\n",

            s,   &y );

%* пропускает поле по формату, указанному после *, не занося его значение ни в какую переменную, а просто "забывая" его. Так формат

            "%*[^\n]\n"

игнорирует "хвост" строки, включая символ перевода строки.

Символы " ", "\t", "\n" в формате вызывают пропуск всех пробелов, табуляций, переводов строк во входном потоке, что можно описать как


    int c;

    while((c = getc(stdin))== ' ' || c == '\t' || c == '\n' );

либо как формат

      %*[ \t\n]

Перед числовыми форматами (%d, %o, %u, %ld, %x, %e, %f), а также %s, пропуск пробелов делается автоматически. Поэтому

            scanf("%d%d",  &x, &y);

                    и

            scanf("%d %d", &x, &y);

равноправны (пробел перед вторым %d просто не нужен). Неявный пропуск пробелов не делается перед %c и %[... , поэтому в ответ на ввод строки "12 5 x" пример


    main(){ int n, m; char c;

       scanf("%d%d%c", &n, &m, &c);

       printf("n=%d m=%d c='%c'\n", n, m, c);

    }

напечатает "n=12 m=5 c=' '", то есть в c будет прочитан пробел (предшествовавший x), а не x.

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


     scanf("%[^\n]%*1[\n]", s);

в котором модификатор длины 1 заставляет игнорировать только один символ \n, а не ВСЕ пробелы и переводы строк, как "\n". К сожалению (как показал эксперимент) этот формат не в состоянии прочесть пустую строку (состоящую только из \n). Поэтому можно сделать глобальный вывод: строки надо считывать при помощи функций gets() и fgets()!

1.135.

Еще пара слов про scanf: scanf возвращает число успешно прочитанных им данных (обработанных %-ов) или EOF в конце файла. Неудача может наступить, если данное во входном потоке не соответствует формату, например строка


            12 quack

для

            int d1; double f; scanf("%d%lf", &d1, &f);

В этом случае scanf прочтет 12 по формату %d в переменную d1, но слово quack не отвечает формату %lf, поэтому scanf прервет свою работу и выдаст значение 1 (успешно прочел один формат). Строка quack останется невостребованной - ее прочитают последующие вызовы функций чтения; а сейчас f останется неизмененной.

1.136.

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


    const int x = 22;

    x = 33; /* ошибка: константу нельзя менять */

Использование const с указателем: Указуемый объект - константа


         const char *pc = "abc";

         pc[1]  = 'x';    /* ошибка */

         pc     = "123";  /* OK */

Сам указатель - константа

         char *const cp = "abc";

         cp[1]  = 'x';    /* OK */

         cp     = "123";  /* ошибка */

Указуемый объект и сам указатель - константы

         const char *const cpc = "abc";

         cpc[1] = 'x';    /* ошибка */

         cpc    = "123";  /* ошибка */

Указатель на константу необходимо объявлять как const TYPE*

               int a = 1;

         const int b = 2;

         const int *pca  = &a;   /* OK, просто рассматриваем a как константу */

         const int *pcb  = &b;   /* OK */

               int *pb   = &b;   /* ошибка, так как тогда возможно было бы написать */

                   *pb   = 3;    /* изменить константу b */

1.137.

Стандартная функция быстрой сортировки qsort (алгоритм quick sort) имеет такой формат: чтобы отсортировать массив элементов типа TYPE


    TYPE arr[N];

          надо вызывать

    qsort(arr,/* Что сортировать? Не с начала: arr+m    */

          N,  /* Сколько первых элементов массива?      */

              /* можно сортировать только часть: n < N  */

          sizeof(TYPE),/* Или sizeof arr[0]             */

                       /* размер одного элемента массива*/

          cmp);

где

    int cmp(TYPE *a1, TYPE *a2);

функция сравнения элементов *a1 и *a2. Ее аргументы - АДРЕСА двух каких-то элементов сортируемого массива. Функцию cmp мы должны написать сами - это функция, задающая упорядочение элементов массива. Для сортировки по возрастанию функция cmp() должна возвращать целое


       < 0, если  *a1 должно идти раньше *a2    <

       = 0, если  *a1 совпадает с        *a2   ==

       > 0, если  *a1 должно идти после  *a2    >

Для массива строк элементы массива имеют тип (char *), поэтому аргументы функции имеют тип (char **). Требуемому условию удовлетворяет такая функция:

            char *arr[N]; ...

            cmps(s1, s2) char **s1, **s2;

            { return strcmp(*s1, *s2); }

(Про strcmp смотри раздел "Массивы и строки"). Заметим, что в некоторых системах программирования (например в TurboC++*) вы должны использовать функцию сравнения с прототипом


    int cmp (const void *a1, const void *a2);

и внутри нее явно делать приведение типа:

    cmps (const void *s1, const void *s2)

    { return strcmp(*(char **)s1, *(char **)s2); }

или можно поступить следующим образом:

    int cmps(char **s1, char **s2){

        return strcmp(*s1, *s2);

    }

    typedef int (*CMPS)(const void *, const void *);

    qsort((void *) array, ..., ..., (CMPS) cmps);

Наконец, возможно и просто объявить

    int cmps(const void *A, const void *B){

        return strcmp(A, B);

    }

Для массива целых годится такая функция сравнения:

            int arr[N]; ...

            cmpi(i1, i2) int *i1, *i2;

            { return *i1 - *i2; }

Для массива структур, которые мы сортируем по целому полю key, годится

            struct XXX{ int key; ... } arr[N];

            cmpXXX(st1, st2) struct XXX *st1, *st2;

            { return( st1->key  -  st2->key ); }

Пусть у нас есть массив long. Можно ли использовать

            long arr[N]; ...

            cmpl(L1, L2) long *L1, *L2;

            { return *L1 - *L2; }

Ответ: оказывается, что нет. Функция cmpl должна возвращать целое, а разность двух long-ов имеет тип long. Поэтому компилятор приводит эту разность к типу int (как правило обрубанием старших битов). При этом (если long-числа были велики) результат может изменить знак! Например:


    main(){

      int n; long a = 1L; long b = 777777777L;

      n = a - b;  /* должно бы быть отрицательным... */

      printf( "%ld %ld %d\n", a, b, n );

    }

печатает 1 777777777 3472. Функция сравнения должна выглядеть так:

            cmpl(L1, L2) long *L1, *L2; {

                    if( *L1 == *L2 ) return   0;

                    if( *L1 <  *L2 ) return (-1);

                                     return   1;

            }

или

            cmpl(L1, L2) long *L1, *L2; {

                return( *L1 == *L2 ?  0 :

                        *L1 <  *L2 ? -1 : 1 );

            }

поскольку важна не величина возвращенного значения, а только ее знак.

Учтите, что для использования функции сравнения вы должны либо определить функцию сравнения до ее использования в qsort():


            int cmp(...){ ... } /* реализация */

                    ...

            qsort(..... , cmp);

либо предварительно объявить имя функции сравнения, чтобы компилятор понимал, что это именно функция:

            int cmp();

            qsort(..... , cmp);

                    ...

            int cmp(...){ ... } /* реализация */

1.138.

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


     a.c                          b.c

    --------------------------   -----------------------------

    #include <fcntl.h>           #include <fcntl.h>

    struct W{ int x,y; }a;       struct W{ int x,y; }b;

    main(){  int fd;             main(){  int fd;

      a.x = 12; a.y = 77;          fd = open("f", O_RDONLY);

      fd = creat("f", 0644);       read(fd, &b, sizeof b);

      write(fd, &a, sizeof a);     close(fd);

      close(fd);                   printf("%d %d\n", b.x, b.y);

    }                            }

Что будет, если мы изменим структуру на

            struct W { long x,y; };

                    или

            struct W { char c; int x,y; };

в файле a.c и забудем сделать это в b.c? Будут ли правильно работать эти программы?

Из наблюдаемого можно сделать вывод, что если две или несколько программ (или частей одной программы), размещенные в разных файлах, используют общие

  • типы данных (typedef);
  • структуры и объединения;
  • константы (определения #define);
  • прототипы функций;
то их определения лучше выносить в общий include-файл (header-файл), дабы все программы придерживались одних и тех же общих соглашений. Даже если эти соглашения со временем изменятся, то они изменятся во всех файлах синхронно и как бы сами собой. В нашем случае исправлять определение структуры придется только в include-файле, а не выискивать все места, где оно написано, ведь при этом немудрено какое-нибудь место и пропустить!

             W.h

            ----------------------

           struct W{ long x, y; };

     a.c                          b.c

    --------------------------   -----------------

    #include <fcntl.h>           #include <fcntl.h>

    #include "W.h"               #include "W.h"

    struct W a;                  struct W b;

    main(){  ...                 main(){  ...

                                    printf("%ld...

Кроме того, вынесение общих фрагментов текста программы (определений структур, констант, и.т.п.) в отдельный файл экономит наши силы и время - вместо того, чтобы набивать один и тот же текст много раз в разных файлах, мы теперь пишем в каждом файле единственную строку - директиву #include. Кроме того, экономится и место на диске, ведь программа стала короче! Файлы включения имеют суффикс .h, что означает "header-file" (файл-заголовок).

Синхронную перекомпиляцию всех программ в случае изменения include-файла можно задать в файле Makefile - программе для координатора make** -:


    all: a b

            echo Запуск a и b

            a ; b

    a: a.c W.h

            cc a.c -o a

    b: b.c W.h

            cc b.c -o b

Правила make имеют вид

    цель: список_целей_от_которых_зависит

            команда

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

Команда выполняется только если файл цель еще не существует, либо хоть один из файлов справа от двоеточия является более "молодым" (свежим), чем целевой файл (смотри поле st_mtime и сисвызов stat в главе про UNIX).

1.139.

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

  • Объявление переменной внутри функции как static делает переменную статической (т.е. она будет сохранять свое значение при выходе из функции) и ограничивает ее видимость пределами данной функции.
  • Переменные, описанные вне функций, и так являются статическими (по классу памяти). Однако слово static и в этом случае позволяет управлять видимостью этих переменных - они будут видимы только в пределах данного файла.
  • Функции, объявленные как static, также видимы только в пределах данного файла.
  • Аргументы функции и локальные (автоматические) переменные функции и так существуют только на время вызова данной функции (память для них выделяется в стеке при входе в функцию и уничтожается при выходе) и видимы только внутри ее тела.

Аргументы функции нельзя объявлять static:


         f(x) static x; { x++; }

незаконно.

Таким образом все переменные и функции в данном файле делятся на две группы:

  • Видимые только внутри данного файла (локальные для модуля). Такие имена объявляются с использованием ключевого слова static. В частности есть еще "более локальные" переменные - автоматические локалы функций и их формальные аргументы, которые видимы только в пределах данной функции. Также видимы лишь в пределах одной функции статические локальные переменные, объявленные в теле функции со словом static.
  • Видимые во всех файлах (глобальные имена).
Глобальные имена образуют интерфейс модуля и могут быть использованы в других модулях. Локальные имена извне модуля недоступны.

Если мы используем в файле-модуле функции и переменные, входящие в интерфейс другого файла-модуля, мы должны объявить их как extern ("внешние"). Для функций описатели extern и int можно опускать:


    // файл A.c

    int x, y, z;              // глобальные

    char ss[200];             // глоб.

    static int v, w;          // локальные

    static char *s, p[20];    // лок.

    int f(){ ... }            // глоб.

    char *g(){ ... }          // глоб.

    static int h(){ ... }     // лок.

    static char *sf(){ ... }  // лок.

    int fi(){ ... }           // глоб.

    // файл B.c

    extern int x, y;

    extern z;           // int можно опустить

    extern char ss[];   // размер можно опустить

    extern int f();

    char *g();          // extern можно опустить

    extern fi();        // int можно опустить

Хорошим тоном является написание комментария - из какого модуля или библиотеки импортируется переменная или функция:

    extern int x, y;  /* import from A.c     */

    char *tgetstr();  /* import from termlib */

Следующая программа собирается из файлов A.c и B.c командой***

    CFLAGS = -O

    AB:     A.o     B.o

            cc A.o B.o -o AB

    A.o:    A.c

            cc -c $(CFLAGS) A.c

    B.o:    B.c

            cc -c $(CFLAGS) B.c

и собирать программу просто вызывая команду make.

       cc A.c B.c -o AB

Почему компилятор сообщает "x дважды определено"?

     файл A.c                файл B.c

    ----------------------------------------

    int x=12;               int x=25;

    main(){                 f(y) int *y;

      f(&x);                {

      printf("%d\n", x);       *y += x;

    }                       }

Ответ: потому, что в каждом файле описана глобальная переменная x. Надо в одном из них (или в обоих сразу) сделать x локальным именем (исключить его из интерфейса модуля):


   static int x=...;

Почему в следующем примере компилятор сообщает "_f дважды определено"?

     файл A.c               файл B.c

    ---------------------------------------------------

    int x;                  extern int x;

    main(){ f(5); g(77); }  g(n){ f(x+n); }

    f(n)  { x=n;         }  f(m){ printf("%d\n", m); }

Ответ: надо сделать в файле B.c функцию f локальной: static f(m)...

Хоть в одном файле должна быть определена функция main, вызываемая системой при запуске программы. Если такой функции нигде нет - компилятор выдает сообщение "_main неопределено". Функция main должна быть определена один раз! В файле она может находиться в любом месте - не требуется, чтобы она была самой первой (или последней) функцией файла****.

* TurboC - компилятор Си в MS DOS, разработанный фирмой Borland International.

** Подробное описание make смотри в документации по системе UNIX.

*** Можно задать Makefile вида

**** Если вы пользуетесь "новым" стилем объявления функций, но не используете прототипы, то следует определять каждую функцию до первого места ее использования, чтобы компилятору в точке вызова был известен ее заголовок. Это приведет к тому, что main() окажется последней функцией в файле - ее не вызывает никто, зато она вызывает кого-то еще.

[Назад][Содержание][Вперед]

Используются технологии uCoz