{
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);
Список аргументов просматривается слева направо в одном направлении, возврат к
А. Богатырев, 1992-95 - 59 - Си в UNIX
предыдущему аргументу невозможен.
Нельзя указывать в качестве типов 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");
А. Богатырев, 1992-95 - 60 - Си в UNIX
}
Что печатает программа?
char *f(){
return "Hello, user!";
}
g(func)
char * (*func)();
{
puts((*func)());
}
main(){
g(f);
}
Почему было бы неверно написать
main(){
g(f());
}
Еще аналогичная ошибка (посмотрите про функцию signal в главе "Взаимодействие с
UNIX"):
#include
f(){ printf( "Good bye.\n" ); exit(0); }
main(){
signal ( SIGINT, f() );
...
}
Запомните, что f() - это ЗНАЧЕНИЕ функции f (т.е. она вызывается и нечто возвращает
return-ом; это-то значение мы и используем), а f - это АДРЕС функции f (раньше это
так и писалось &f), то есть метка начала ее машинных кодов ("точка входа").
1.120. Что напечатает программа? (Пример посвящен указателям на функции и массивам
функций):
int f(n){ return n*2; }
int g(n){ return n+4; }
int h(n){ return n-1; }
int (*arr[3])() = { f, g, h };
main(){
int i;
for(i=0; i < 3; i++ )
printf( "%d\n", (*arr[i])(i+7) );
}
1.121. Что напечатает программа?
extern double sin(), cos();
main(){ double x; /* cc -lm */
for(x=0.0; x < 1.0; x += 0.2)
printf("%6.4g %6.4g %6.4g\n",
(x > 0.5 ? sin : cos)(x), sin(x), cos(x));
}
то же в варианте
А. Богатырев, 1992-95 - 61 - Си в UNIX
extern double sin(), cos();
main(){ double x; double (*f)();
for(x=0.0; x < 1.0; x += 0.2){
f = (x > 0.5 ? sin : cos);
printf("%g\n", (*f)(x));
}
}
1.122. Рассмотрите четыре реализации функции факториал:
n! = 1 * 2 * ... * n
или n! = n * (n-1)! где 0! = 1
Все они иллюстрируют определенные подходы в программировании:
/* ЦИКЛ (ИТЕРАЦИЯ) */
int factorial1(n){ int res = 1;
while(n > 0){ res *= n--; }
return res;
}
/* ПРОСТАЯ РЕКУРСИЯ */
int factorial2(n){
return (n==0 ? 1 : n * factorial2(n-1));
}
/* Рекурсия, в которой функция вызывается рекурсивно
* единственный раз - в операторе return, называется
* "хвостовой рекурсией" (tail recursion) и
* легко преобразуется в цикл */
/* АВТОАППЛИКАЦИЯ */
int fi(f, n) int (*f)(), n;
{ if(n == 0) return 1;
else return n * (*f)(f, n-1);
}
int factorial3(n){ return fi(fi, n); }
/* РЕКУРСИЯ С НЕЛОКАЛЬНЫМ ПЕРЕХОДОМ */
#include
jmp_buf checkpoint;
void fact(n, res) register int n, res;
{ if(n) fact(n - 1, res * n);
else longjmp(checkpoint, res+1);
}
int factorial4(n){ int res;
if(res = setjmp(checkpoint)) return (res - 1);
else fact(n, 1);
}
1.123. Напишите функцию, печатающую целое число в системе счисления с основанием
base. Ответ:
А. Богатырев, 1992-95 - 62 - Си в UNIX
printi( n, base ){
register int i;
if( n < 0 ){ putchar( '-' ); n = -n; }
if( i = n / base )
printi( i, base );
i = n % base ;
putchar( i >= 10 ? 'A' + i - 10 : '0' + i );
}
Попробуйте написать нерекурсивный вариант с накоплением ответа в строке. Приве-
дем рекурсивный вариант, накапливающий ответ в строке s и пользующийся аналогом функ-
ции printi: функция prints - такая же, как printi, но вместо вызовов putchar(нечто);
в ней написаны операторы
*res++ = нечто;
и рекурсивно вызывается конечно же prints. Итак:
static char *res;
... текст функции prints ...
char *itos( n, base, s )
char *s; /* указывает на char[] массив для ответа */
{
res = s; prints(n, base); *res = '\0';
return s;
}
main(){ char buf[20]; printf( "%s\n", itos(19,2,buf); }
1.124. Напишите функцию для побитной распечатки целого числа. Имейте в виду, что
число содержит 8 * sizeof(int) бит. Указание: используйте операции битового сдвига и
&. Ответ:
printb(n){
register i;
for(i = 8 * sizeof(int) - 1; i >= 0; --i)
putchar(n & (1 << i) ? '1':'0');
}
1.125. Напишите функцию, склоняющую существительные русского языка в зависимости от
их числа. Например:
printf( "%d кирпич%s", n, grammar( n, "ей", "", "а" ));
Ответ:
char *grammar( i, s1, s2, s3 )
char *s1, /* прочее */
*s2, /* один */
*s3; /* два, три, четыре */
{
i = i % 100;
if( i > 10 && i <= 20 ) return s1;
i = i % 10;
if( i == 1 ) return s2;
if( i == 2 || i == 3 || i == 4 )
return s3;
return s1;
}
А. Богатырев, 1992-95 - 63 - Си в UNIX
1.126. Напишите оператор printf, печатающий числа из интервала 0..99 с добавлением
нуля перед числом, если оно меньше 10 :
00 01 ... 09 10 11 ...
Используйте условное выражение, формат.
Ответ:
printf ("%s%d", n < 10 ? "0" : "", n);
либо
printf ("%02d", n );
либо
printf ("%c%c", '0' + n/10, '0' + n%10 );
1.127. Предостережем от одной ошибки, часто допускаемой начинающими.
putchar( "c" ); является ошибкой.
putchar( 'c' ); верно.
Дело в том, что putchar требует аргумент - символ, тогда как "c" - СТРОКА из одного
символа. Большинство компиляторов (те, которые не проверяют прототипы вызова стан-
дартных функций) НЕ обнаружит здесь никакой синтаксической ошибки (кстати, ошибка эта
- семантическая).
Также ошибочны операторы
printf ( '\n' ); /* нужна строка */
putchar( "\n" ); /* нужен символ */
putchar( "ab" ); /* нужен символ */
putchar( 'ab' ); /* ошибка в буквенной константе */
char c; if((c = getchar()) == "q" ) ... ;
/* нужно писать 'q' */
Отличайте строку из одного символа и символ - это разные вещи! (Подробнее об этом -
в следующей главе).
1.128. Весьма частой является ошибка "промах на единицу", которая встречается в
очень многих и разнообразных случаях. Вот одна из возможных ситуаций:
int m[20]; int i = 0;
while( scanf( "%d", & m[i++] ) != EOF );
printf( "Ввели %d чисел\n", i );
В итоге i окажется на 1 больше, чем ожидалось. Разберитесь в чем дело.
Ответ: аргументы функции вычисляются до ее вызова, поэтому когда мы достигаем
конца файла и scanf возвращает EOF, i++ в вызове scanf все равно делается. Надо напи-
сать
while( scanf( "%d", & m[i] ) != EOF ) i++;
1.129. Замечание по стилистике: при выводе сообщения на экран
printf( "Hello \n" );
пробелы перед \n достаточно бессмысленны, поскольку на экране никак не отобразятся.
Надо писать (экономя память)
printf( "Hello\n" );
А. Богатырев, 1992-95 - 64 - Си в UNIX
Единственный случай, когда такие пробелы значимы - это когда вы выводите текст инвер-
сией. Тогда пробелы отображаются как светлый фон.
Еще неприятнее будет
printf( "Hello\n " );
поскольку концевые пробелы окажутся в начале следующей строки.
1.130. printf - интерпретирующая функция, т.е. работает она довольно медленно. Поэ-
тому вместо
char s[20]; int i;
...
printf( "%c", s[i] ); и printf( "\n" );
надо всегда писать
putchar( s[i] ); и putchar( '\n' );
поскольку printf в конце-концов (сделав все преобразования по формату) внутри себя
вызывает putchar. Так сделаем же это сразу!
1.131. То, что параметр "формат" в функции printf может быть выражением, позволяет
делать некоторые удобные вещи. Например:
int x; ...
printf( x ? "значение x=%d\n" : "x равен нулю\n\n", x);