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++. Такой комментарий простирается от символа // до конца
строки.
© Copyright А. Богатырев, 1992-95
Си в UNIX
Назад | Содержание | Вперед