Главная · Поиск книг · Поступления книг · 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 ... 3 4 5 6 7 8 9  10 11 12 13 14 15 16 ... 87
запятую, но это совсем другая синтаксическая конструкция. Вот еще пример:

    int y = 2, x;
    x = (y+4, y, y*2); printf("%d\n", x);      /*  4 */
    x =  y+4, y, y*2 ; printf("%d\n", x);      /*  6 */
    x = (x=y+4, ++y, x*y); printf("%d\n", x);  /* 18 */

Сначала обратим внимание на первую строку. Это - объявление переменных x и y  (причем
y  -  с  инициализацией),  поэтому  запятая здесь - не ОПЕРАТОР, а просто разделитель
объявляемых переменных!  Далее следуют три строки выполняемых операторов.   В  первом
случае выполнилось x=y*2; во втором x=y+4 (т.к.  приоритет у присваивания выше, чем у

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

запятой). Обратите внимание, что выражение без присваивания (которое может вообще  не
иметь эффекта или иметь только побочный эффект) вполне законно:

    x+y;   или   z++;   или   x == y+1;   или   x;

В частности, все вызовы функций-процедур именно таковы (это выражения  без  оператора
присваивания, имеющие побочный эффект):

    f(12,x);        putchar('Ы');

в отличие, скажем, от x=cos(0.5)/3.0; или c=getchar();
     Оператор "запятая" разделяет выражения, а не просто операторы, поэтому если хоть
один из перечисленных операторов не выдает значения, то это является ошибкой:

    main(){ int i, x = 0;
      for(i=1; i < 4; i++)
          x++, if(x > 2) x = 2; /* используй { ; } */
    }

оператор if не выдает значения. Также логически ошибочно использование  функции  типа
void (не возвращающей значения):

    void f(){}
      ...
      for(i=1; i < 4; i++)
          x++, f();

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

    if( условие ) { x = 0; y = 0; }
    if( условие )   x = 0, y = 0;
    if( условие )   x = y = 0;

1.109.  Найдите опечатку:

            switch(c){
            case 1:
                    x++; break;
            case 2:
                    y++; break;
            defalt:
                    z++; break;
            }

Если c=3, то z++ не происходит. Почему?  (Потому, что defalt: - это метка, а не  клю-
чевое слово default).

1.110.  Почему программа зацикливается и печатает совсем не то, что нажато на клавиа-
туре, а только 0 и 1?

            while ( c = getchar() != 'e')
                    printf("%d %c\n, c, c);

Ответ: данный фрагмент должен был выглядеть так:

            while ((c = getchar()) != 'e')
                    printf("%d %c\n, c, c);

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

Сравнение в Си имеет высший приоритет, нежели присваивание!  Мораль: надо быть внима-
тельнее к приоритетам операций.  Еще один пример на похожую тему:

           вместо
    if(  x & 01  == 0 ) ...     if( c&0377  > 0300)...;
           надо:
    if( (x & 01) == 0 ) ...     if((c&0377) > 0300)...;

И еще пример с аналогичной ошибкой:

    FILE *fp;
    if( fp = fopen( "файл", "w" ) == NULL ){
        fprintf( stderr, "не могу писать в файл\n");
        exit(1);
    }
    fprintf(fp,"Good bye, %s world\n","cruel"); fclose(fp);

В этом примере файл открывается, но fp  равно  0  (логическое  значение!)  и  функция
fprintf() не срабатывает (программа падает по защите памяти[*]).
     Исправьте аналогичную ошибку (на приоритет операций) в следующей функции:

    /* копирование строки from в to */
    char *strcpy( to, from ) register char *from, *to;
    {
         char *p = to;
         while( *to++ = *from++ != '\0' );
         return p;
    }

1.111.  Сравнения с нулем (0, NULL, '\0') в Си принято опускать (хотя это  не  всегда
способствует ясности).

        if( i == 0 ) ...;    -->    if( !i ) ... ;
        if( i != 0 ) ...;    -->    if(  i ) ... ;

например, вместо

    char s[20], *p ;
    for(p=s; *p != '\0'; p++ ) ... ;
         будет
    for(p=s; *p; p++ ) ... ;

и вместо

    char s[81], *gets();
    while( gets(s) != NULL ) ... ;
         будет
    while( gets(s)) ... ;

Перепишите strcpy в этом более лаконичном стиле.

____________________
   [*] "Падать" - программистский жаргон. Означает "аварийно завершаться".  "Защита па-
мяти"  - обращение по некорректному адресу.  В UNIX такая ошибка ловится аппаратно, и
программа будет убита одним из сигналов: SIGBUS, SIGSEGV,  SIGILL.   Система  сообщит
нечто  вроде  "ошибка  шины".  Знайте, что это не ошибка аппаратуры и не сбой, а ВАША
ошибка!

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

1.112.  Истинно ли выражение

            if( 2 < 5 < 4 )

Ответ: да!  Дело в том, что Си не имеет логического типа, а вместо "истина" и  "ложь"
использует  целые  значения  "не 0" и "0" (логические операции выдают 1 и 0).  Данное
выражение в условии if эквивалентно следующему:

            ((2 < 5) < 4)

Значением (2 < 5) будет 1. Значением (1 < 4) будет тоже 1 (истина).  Таким образом мы
получаем совсем не то, что ожидалось. Поэтому вместо

            if( a < x < b )

надо писать

            if( a < x && x < b )

1.113.  Данная программа должна печатать коды вводимых символов.   Найдите  опечатку;
почему цикл сразу завершается?

      int c;
      for(;;) {
          printf("Введите очередной символ:");
          c = getchar();
          if(c = 'e') {
             printf("нажато e, конец\n"); break;
          }
          printf( "Код %03o\n", c & 0377 );
      }

Ответ: в if имеется опечатка: использовано `=' вместо `=='.
     Присваивание в Си (а также операции +=, -=, *=, и.т.п.)  выдает  новое  значение
левой части, поэтому синтаксической ошибки здесь нет!  Написанный оператор равносилен

          c = 'e'; if( c ) ... ;

и, поскольку 'e'!= 0, то условие оказывается истинным!  Это еще и следствие того, что
в Си нет специального логического типа (истина/ложь).  Будьте внимательны: компилятор
не считает ошибкой использование оператора = вместо == внутри условий  if  и  условий
циклов (хотя некоторые компиляторы выдают предупреждение).
     Еще аналогичная ошибка:

    for( i=0; !(i = 15) ; i++ ) ... ;

(цикл не выполняется); или

    static char s[20] = "   abc"; int i=0;
    while(s[i] = ' ') i++;
    printf("%s\n", &s[i]); /* должно напечататься abc */

(строка заполняется пробелами и цикл не кончается).
     То, что оператор присваивания имеет значение, весьма удобно:

    int x, y, z;           это на самом деле
    x = y = z = 1;         x = (y = (z = 1));

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

или[*]

    y=f( x += 2 );       // вместо x+=2; y=f(x);
    if((y /= 2) > 0)...; // вместо y/=2; if(y>0)...;

Вот пример упрощенной игры в "очко" (упрощенной - т.к. не учитывается  ограниченность
числа карт каждого типа в колоде (по 4 штуки)):

    #include 
    main(){
      int sum = 0, card; char answer[36];
      srand( getpid());  /* рандомизация */
      do{  printf( "У вас %d очков. Еще? ", sum);
           if( *gets(answer) == 'n' ) break;
           /* иначе маловато будет */
           printf( "  %d очков\n",
                   card = 6 + rand() % (11 - 6 + 1));
      } while((sum += card) < 21);      /* SIC ! */
      printf ( sum == 21 ? "очко\n"   :
               sum >  21 ? "перебор\n":
                           "%d очков\n", sum);
    }

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

    #include 
    int width = 20; /* начальное значение ширины поля */
    int len; char str[512];
    main(){
      while(gets(str)){
        if((len = strlen(str)) > width){
    fprintf(stderr,"width увеличить до %d\n", width=len);
        }
        printf("|%*.*s|\n", -width, width, str);
      }
    }

Вызывай эту программу как
  a.out < входнойФайл > /dev/null

1.114.  Почему программа "зависает" (на самом деле - зацикливается) ?

            int x = 0;
            while( x < 100 );
                    printf( "%d\n", x++ );
            printf( "ВСЕ\n" );

Указание: где кончается цикл while?
     Мораль: не надо ставить ; где попало. Еще  мораль:  даже  отступы  в  оформлении
программы не являются гарантией отсутствия ошибок в группировке операторов.

1.115.  Вообще, приоритеты операций в Си  часто  не  соответствуют  ожиданиям  нашего
здравого смысла. Например, значением выражения:

            x = 1 << 2 + 1 ;
____________________
   [*] Конструкция //текст, которая будет изредка попадаться в дальнейшем - это коммен-
тарий  в  стиле  языка  C++.  Такой  комментарий  простирается от символа // до конца
строки.

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

будет 8, а не 5, поскольку сложение выполнится первым.  Мораль: в  затруднительных  и
неочевидных случаях лучше явно указывать приоритеты при помощи круглых скобок:

            x = (1 << 2) + 1 ;

Еще пример: увеличивать x на 40, если установлен флаг, иначе на 1:

            int bigFlag = 1, x = 2;
            x = x + bigFlag ? 40 : 1;
            printf( "%d\n", x );

ответом будет 40, а не 42, поскольку это

            x = (x + bigFlag) ? 40 : 1;

а не

            x = x + (bigFlag ? 40 : 1);

которое мы имели в виду. Поэтому вокруг условного выражения ?: обычно  пишут  круглые
скобки.
     Заметим, что () указывают только приоритет, но не порядок вычислений. Так,  ком-
пилятор имеет полное право вычислить

    long a = 50, x; int b = 4;
    x = (a * 100) / b;
      /* деление целочисленное с остатком ! */
    и как   x = (a * 100)/b = 5000/4 = 1250
    и как   x = (a/b) * 100 = 12*100 = 1200

невзирая на наши скобки, поскольку и * и  /  имеют  одинаковый  приоритет  (хотя  это
"право"  еще  не  означает, что он обязательно так поступит).  Такие операторы прихо-
дится разбивать на два, т.е. вводить промежуточную переменную:

    { long a100 = a * 100; x = a100 / b; }

1.116.  Составьте программу вычисления тригонометрической функции.  Название  функции
и  значение  аргумента  передаются в качестве параметров функции main (см. про argv и
argc в главе "Взаимодействие с UNIX"):

            $ a.out sin 0.5
            sin(0.5)=0.479426

(здесь и далее значок $ обозначает  приглашение,  выданное  интерпретатором  команд).
Для  преобразования строки в значение типа double воспользуйтесь стандартной функцией
atof().

      char *str1, *str2, *str3; ...
      extern double atof();    double x = atof(str1);
      extern long   atol();    long   y = atol(str2);
      extern int    atoi();    int    i = atoi(str3);

либо

    sscanf(str1, "%f",  &x);
    sscanf(str2, "%ld", &y); sscanf(str3,"%d", &i);

К слову заметим, что обратное преобразование - числа в текст - удобнее всего делается
при  помощи  функции  sprintf(),  которая  аналогична  printf(), но сформированная ею
строка-сообщение не выдается на экран, а заносится в массив:

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

            char represent[ 40 ];
            int i = ... ;
            sprintf( represent, "%d", i );

1.117.  Составьте программу вычисления полинома n-ой степени:

               n          n-1
     Y = A  * X + A    * X    + ... + A0
          n        n-1

         схема (Горнера):

     Y = A0 + X * ( A1 + X * ( A2 + ... + X * An )))...)

Оформите алгоритм как функцию с переменным числом параметров:

    poly( x, n, an, an-1, ... a0 );

О том, как это сделать - читайте раздел руководства по UNIX man varargs.  Ответ:

    #include 
    double poly(x, n, va_alist)
           double x; int n; va_dcl
Предыдущая страница Следующая страница
1 ... 3 4 5 6 7 8 9  10 11 12 13 14 15 16 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама