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

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


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

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


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

Хрестоматия по программированию на Си в Unix

Предыдущая страница Следующая страница
1 ... 4 5 6 7 8 9 10  11 12 13 14 15 16 17 ... 87
    {
      va_list args;
      double sum = 0.0;
      va_start(args); /* инициализировать список арг-тов */
      while( n-- >= 0 ){
         sum *= x;
         sum += va_arg(args, double);
         /* извлечь след. аргумент типа double */
      }
      va_end(args);   /* уничтожить список аргументов */
      return sum;
    }

    main(){
                            /* y = 12*x*x + 3*x + 7 */
      printf( "%g\n", poly(2.0, 2, 12.0,    3.0,  7.0));
    }

Прототип этой функции:

    double poly(double x, int n, ... );

В этом примере использованы макросы va_нечто.   Часть  аргументов,  которая  является
списком переменной длины, обозначается в списке параметров как va_alist, при этом она
объявляется как va_dcl в списке типов параметров. Заметьте, что точка-с-запятой после
va_dcl  не нужна!  Описание va_list args; объявляет специальную "связную" переменную;
смысл ее машинно зависим.  va_start(args) инициализирует эту переменную списком  фак-
тических  аргументов,  соответствующих va_alist-у.  va_end(args) деинициализирует эту
переменную (это надо делать обязательно, поскольку инициализация могла быть связана с
конструированием  списка  аргументов при помощи выделения динамической памяти; теперь
мы должны уничтожить этот список и освободить память).  Очередной аргумент типа  TYPE
извлекается из списка при помощи

    TYPE x = va_arg(args, TYPE);

Список аргументов просматривается  слева  направо  в  одном  направлении,  возврат  к

А. Богатырев, 1992-95                  - 59 -                               Си в UNIX

предыдущему аргументу невозможен.
Нельзя указывать в качестве типов char, short, float:

    char ch = va_arg(args, char);

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

    int ch = va_arg(args, int);

1.118.  Еще об одной ловушке в языке Си на PDP-11 (и в компиляторах бывают ошибки!):

            unsigned x = 2;
            printf( "%ld %ld",
                    - (long) x,
                    (long)  -x
            );

Этот фрагмент напечатает числа -2 и 65534.  Во втором случае при  приведении  к  типу
long  был  расширен  знаковый  бит.   Встроенная операция sizeof выдает значение типа
unsigned.  Подумайте, каков будет эффект в следующем фрагменте программы?

            static struct point{ int  x,  y    ;}
                          p =  {     33, 13   };
            FILE *fp = fopen( "00", "w" );

            /* вперед на длину одной структуры */
            fseek( fp, (long)  sizeof( struct point ), 0 );

            /* назад на длину одной структуры */
     /*!*/  fseek( fp, (long) -sizeof( struct point ), 1 );

            /* записываем в начало файла одну структуру */
            fwrite( &p, sizeof p, 1, fp );

            /* закрываем файл */
            fclose( fp );

Где должен находиться минус во втором вызове fseek для получения  ожидаемого  резуль-
тата?  (Данный пример может вести себя по-разному на разных машинах, вопросы касаются
PDP-11).

1.119.  Обратимся к указателям на функции:

    void g(x){ printf("%d: here\n", x); }
    main(){
      void (*f)() = g;  /* Указатель смотрит на функцию g() */
      (*f)(1); /* Старая форма вызова функции по указателю */
     f (2); /* Новая  форма вызова */

      /* В обоих случаях вызывается g(x); */
    }

Что печатает программа?

    typedef void (*(*FUN))(); /* Попытка изобразить
            рекурсивный тип typedef FUN (*FUN)(); */
    FUN  g(FUN f){ return f; }
    void main(){
         FUN y = g(g(g(g(g))));
         if(y == g) printf("OK\n");

А. Богатырев, 1992-95                  - 60 -                               Си в UNIX

    }

Что печатает программа?

            char *f(){
                    return "Hello, user!";
            }
            g(func)
                char * (*func)();
            {
                    puts((*func)());
            }
            main(){
                    g(f);
            }

Почему было бы неверно написать

            main(){
                    g(f());
            }

Еще аналогичная ошибка (посмотрите про  функцию  signal  в  главе  "Взаимодействие  с
UNIX"):

            #include 
            f(){ printf( "Good bye.\n" ); exit(0); }
            main(){
                 signal ( SIGINT, f() );
                 ...
            }

Запомните, что f() - это ЗНАЧЕНИЕ функции f (т.е. она вызывается и  нечто  возвращает
return-ом;  это-то  значение  мы и используем), а f - это АДРЕС функции f (раньше это
так и писалось &f), то есть метка начала ее машинных кодов ("точка входа").

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

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

А. Богатырев, 1992-95                  - 61 -                               Си в UNIX

    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 
    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.  Ответ:

А. Богатырев, 1992-95                  - 62 -                               Си в UNIX

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

А. Богатырев, 1992-95                  - 63 -                               Си в UNIX

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

А. Богатырев, 1992-95                  - 64 -                               Си в UNIX

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

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

Реклама