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


            while ( c = getchar() != 'e')

                    printf("%d %c\n, c, c);

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

            while ((c = getchar()) != 'e')

                    printf("%d %c\n, c, c);

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

           вместо

    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 в этом более лаконичном стиле.

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

или**

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

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

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

    #include <stdio.h>

    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 <stdio.h>

    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 ;

будет 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(), но сформированная ею строка-сообщение не выдается на экран, а заносится в массив:


            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 <varargs.h>

    double poly(x, n, va_alist)

           double x; int n; va_dcl

    {

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

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

Нельзя указывать в качестве типов 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");

    }

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

            char *f(){

                    return "Hello, user!";

            }

            g(func)

                char * (*func)();

            {

                    puts((*func)());

            }

            main(){

                    g(f);

            }

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

            main(){

                    g(f());

            }

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

            #include <signal.h>

            f(){ printf( "Good bye.\n" ); exit(0); }

            main(){

                 signal ( SIGINT, f() );

                 ...

            }

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

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

** Конструкция //текст, которая будет изредка попадаться в дальнейшем - это комментарий в стиле языка C++. Такой комментарий простирается от символа // до конца строки.

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

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