запятую, но это совсем другая синтаксическая конструкция. Вот еще пример:
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