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++. Такой комментарий простирается от символа // до конца строки. [Назад][Содержание][Вперед] |