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; }
Сравнения с нулем (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 в этом более лаконичном стиле.
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 )
Данная программа должна печатать коды вводимых символов. Найдите опечатку; почему цикл сразу завершается?
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
int x = 0; while( x < 100 ); printf( "%d\n", x++ ); printf( "ВСЕ\n" );Указание: где кончается цикл while?
Мораль: не надо ставить ; где попало. Еще мораль: даже отступы в оформлении программы не являются гарантией отсутствия ошибок в группировке операторов.
Вообще, приоритеты операций в Си часто не соответствуют ожиданиям нашего здравого смысла. Например, значением выражения:
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; }
Составьте программу вычисления тригонометрической функции. Название функции и значение аргумента передаются в качестве параметров функции 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 );
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);
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).
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++. Такой комментарий простирается от символа // до конца строки.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед