Формат здесь - условное выражение. Если x!=0, то будет напечатано значение x по фор-
мату %d. Если же x==0, то будет напечатана строка, не содержащая ни одного %-та. В
результате аргумент x в списке аргументов будет просто проигнорирован. Однако, нап-
ример
int x = ... ;
printf( x > 30000 ? "%f\n" : "%d\n", x);
(чтобы большие x печатались в виде 31000.000000) незаконно, поскольку целое число
нельзя печатать по формату %f ни в каких случаях. Единственным способом сделать это
является явное приведение x к типу double:
printf("%f\n", (double) x);
Будет ли законен оператор?
printf( x > 30000 ? "%f\n" : "%d\n",
x > 30000 ? (double) x : x );
Ответ: нет. Условное выражение для аргумента будет иметь "старший" тип - double. А
значение типа double нельзя печатать по формату %d. Мы должны использовать здесь
оператор if:
if( x > 30000 ) printf("%f\n", (double)x);
else printf("%d\n", x);
1.132. Напишите функцию, печатающую размер файла в удобном виде: если файл меньше
одного килобайта - печатать его размер в байтах, если же больше - в килобайтах (и
мегабайтах).
#define KBYTE 1024L /* килобайт */
#define THOUSAND 1024L /* кб. в мегабайте */
А. Богатырев, 1992-95 - 65 - Си в UNIX
void tellsize(unsigned long sz){
if(sz < KBYTE) printf("%lu байт", sz);
else{
unsigned long Kb = sz/KBYTE;
unsigned long Mb = Kb/THOUSAND;
unsigned long Dec = ((sz % KBYTE) * 10) / KBYTE;
if( Mb ){
Kb %= THOUSAND;
printf( Dec ? "%lu.%03lu.%01lu Мб." : "%lu.%lu Мб.",
Mb, Kb, Dec );
} else
printf( Dec ? "%lu.%01lu Кб.":"%lu Кб.", Kb, Dec);
}
putchar('\n');
}
1.133. Для печати строк используйте
printf("%s", string); /* A */
но не printf(string); /* B */
Если мы используем вариант B, а в строке встретится символ '%'
char string[] = "abc%defg";
то %d будет воспринято как формат для вывода целого числа. Во-первых, сама строка %d
не будет напечатана; во-вторых - что же будет печататься по этому формату, когда у
нас есть лишь единственный аргумент - string?! Напечатается какой-то мусор!
1.134. Почему оператор
char s[20];
scanf("%s", s); printf("%s\n", s);
в ответ на ввод строки
Пушкин А.С.
печатает только "Пушкин"?
Ответ: потому, что концом текста при вводе по формату %s считается либо \n, либо
пробел, либо табуляция, а не только \n; то есть формат %s читает слово из текста.
Чтение всех символов до конца строки, (включая пробелы) должно выглядеть так:
scanf("%[^\n]\n", s);
%[^\n] - читать любые символы, кроме \n (до \n)
\n - пропустить \n на конце строки
%[abcdef] - читать слово,
состоящее из перечисленных букв.
%[^abcde] - читать слово из любых букв,
кроме перечисленных (прерваться по букве из списка).
Пусть теперь строки входной информации имеют формат:
Фрейд Зигмунд 1856 1939
Пусть мы хотим считывать в строку s фамилию, в целое y - год рождения, а прочие поля
- игнорировать. Как это сделать? Нам поможет формат "подавление присваивания" %*:
scanf("%s%*s%d%*[^\n]\n",
s, &y );
А. Богатырев, 1992-95 - 66 - Си в UNIX
%* пропускает поле по формату, указанному после *, не занося его значение ни в какую
переменную, а просто "забывая" его. Так формат
"%*[^\n]\n"
игнорирует "хвост" строки, включая символ перевода строки.
Символы " ", "\t", "\n" в формате вызывают пропуск всех пробелов, табуляций,
переводов строк во входном потоке, что можно описать как
int c;
while((c = getc(stdin))== ' ' || c == '\t' || c == '\n' );
либо как формат
%*[ \t\n]
Перед числовыми форматами (%d, %o, %u, %ld, %x, %e, %f), а также %s, пропуск
пробелов делается автоматически. Поэтому
scanf("%d%d", &x, &y);
и
scanf("%d %d", &x, &y);
равноправны (пробел перед вторым %d просто не нужен). Неявный пропуск пробелов не
делается перед %c и %[... , поэтому в ответ на ввод строки "12 5 x" пример
main(){ int n, m; char c;
scanf("%d%d%c", &n, &m, &c);
printf("n=%d m=%d c='%c'\n", n, m, c);
}
напечатает "n=12 m=5 c=' '", то есть в c будет прочитан пробел (предшествовавший x),
а не x.
Автоматический пропуск пробелов перед %s не позволяет считывать по %s строки,
лидирующие пробелы которых должны сохраняться. Чтобы лидирующие пробелы также считы-
вались, следует использовать формат
scanf("%[^\n]%*1[\n]", s);
в котором модификатор длины 1 заставляет игнорировать только один символ \n, а не
ВСЕ пробелы и переводы строк, как "\n". К сожалению (как показал эксперимент) этот
формат не в состоянии прочесть пустую строку (состоящую только из \n). Поэтому можно
сделать глобальный вывод: строки надо считывать при помощи функций gets() и fgets()!
1.135. Еще пара слов про scanf: scanf возвращает число успешно прочитанных им данных
(обработанных %-ов) или EOF в конце файла. Неудача может наступить, если данное во
входном потоке не соответствует формату, например строка
12 quack
для
int d1; double f; scanf("%d%lf", &d1, &f);
В этом случае scanf прочтет 12 по формату %d в переменную d1, но слово quack не отве-
чает формату %lf, поэтому scanf прервет свою работу и выдаст значение 1 (успешно про-
чел один формат). Строка quack останется невостребованной - ее прочитают последующие
вызовы функций чтения; а сейчас f останется неизмененной.
1.136. Си имеет квалификатор const, указывающий, что значение является не перемен-
ной, а константой, и попытка изменить величину по этому имени является ошибкой. Во
многих случаях const может заменить #define, при этом еще явно указан тип константы,
что полезно для проверок компилятором.
А. Богатырев, 1992-95 - 67 - Си в UNIX
const int x = 22;
x = 33; /* ошибка: константу нельзя менять */
Использование const с указателем:
Указуемый объект - константа
const char *pc = "abc";
pc[1] = 'x'; /* ошибка */
pc = "123"; /* OK */
Сам указатель - константа
char *const cp = "abc";
cp[1] = 'x'; /* OK */
cp = "123"; /* ошибка */
Указуемый объект и сам указатель - константы
const char *const cpc = "abc";
cpc[1] = 'x'; /* ошибка */
cpc = "123"; /* ошибка */
Указатель на константу необходимо объявлять как const TYPE*
int a = 1;
const int b = 2;
const int *pca = &a; /* OK, просто рассматриваем a как константу */
const int *pcb = &b; /* OK */
int *pb = &b; /* ошибка, так как тогда возможно было бы написать */
*pb = 3; /* изменить константу b */
1.137. Стандартная функция быстрой сортировки qsort (алгоритм quick sort) имеет
такой формат: чтобы отсортировать массив элементов типа TYPE
TYPE arr[N];
надо вызывать
qsort(arr,/* Что сортировать? Не с начала: arr+m */
N, /* Сколько первых элементов массива? */
/* можно сортировать только часть: n < N */
sizeof(TYPE),/* Или sizeof arr[0] */
/* размер одного элемента массива*/
cmp);
где
int cmp(TYPE *a1, TYPE *a2);
функция сравнения элементов *a1 и *a2. Ее аргументы - АДРЕСА двух каких-то элементов
сортируемого массива. Функцию cmp мы должны написать сами - это функция, задающая
упорядочение элементов массива. Для сортировки по возрастанию функция cmp() должна
возвращать целое
< 0, если *a1 должно идти раньше *a2 <
= 0, если *a1 совпадает с *a2 ==
> 0, если *a1 должно идти после *a2 >
Для массива строк элементы массива имеют тип (char *), поэтому аргументы функции
имеют тип (char **). Требуемому условию удовлетворяет такая функция:
А. Богатырев, 1992-95 - 68 - Си в UNIX
char *arr[N]; ...
cmps(s1, s2) char **s1, **s2;
{ return strcmp(*s1, *s2); }
(Про strcmp смотри раздел "Массивы и строки"). Заметим, что в некоторых системах
программирования (например в TurboC++ [*]) вы должны использовать функцию сравнения с
прототипом
int cmp (const void *a1, const void *a2);
и внутри нее явно делать приведение типа:
cmps (const void *s1, const void *s2)
{ return strcmp(*(char **)s1, *(char **)s2); }
или можно поступить следующим образом:
int cmps(char **s1, char **s2){
return strcmp(*s1, *s2);
}
typedef int (*CMPS)(const void *, const void *);
qsort((void *) array, ..., ..., (CMPS) cmps);
Наконец, возможно и просто объявить
int cmps(const void *A, const void *B){
return strcmp(A, B);
}
Для массива целых годится такая функция сравнения:
int arr[N]; ...
cmpi(i1, i2) int *i1, *i2;
{ return *i1 - *i2; }
Для массива структур, которые мы сортируем по целому полю key, годится
struct XXX{ int key; ... } arr[N];
cmpXXX(st1, st2) struct XXX *st1, *st2;
{ return( st1->key - st2->key ); }
Пусть у нас есть массив long. Можно ли использовать
long arr[N]; ...
cmpl(L1, L2) long *L1, *L2;
{ return *L1 - *L2; }
Ответ: оказывается, что нет. Функция cmpl должна возвращать целое, а разность двух
long-ов имеет тип long. Поэтому компилятор приводит эту разность к типу int (как
правило обрубанием старших битов). При этом (если long-числа были велики) результат
может изменить знак! Например:
main(){
int n; long a = 1L; long b = 777777777L;
n = a - b; /* должно бы быть отрицательным... */
printf( "%ld %ld %d\n", a, b, n );
}
____________________
[*] TurboC - компилятор Си в MS DOS, разработанный фирмой Borland International.
А. Богатырев, 1992-95 - 69 - Си в UNIX
печатает 1 777777777 3472. Функция сравнения должна выглядеть так:
cmpl(L1, L2) long *L1, *L2; {
if( *L1 == *L2 ) return 0;
if( *L1 < *L2 ) return (-1);
return 1;
}
или
cmpl(L1, L2) long *L1, *L2; {
return( *L1 == *L2 ? 0 :
*L1 < *L2 ? -1 : 1 );
}
поскольку важна не величина возвращенного значения, а только ее знак.
Учтите, что для использования функции сравнения вы должны либо определить функ-
цию сравнения до ее использования в qsort():
int cmp(...){ ... } /* реализация */
...
qsort(..... , cmp);
либо предварительно объявить имя функции сравнения, чтобы компилятор понимал, что это
именно функция:
int cmp();
qsort(..... , cmp);
...
int cmp(...){ ... } /* реализация */
1.138. Пусть у нас есть две программы, пользующиеся одной и той же структурой данных
W:
a.c b.c
-------------------------- ------------------------------
#include #include
struct W{ int x,y; }a; struct W{ int x,y; }b;
main(){ int fd; main(){ int fd;
a.x = 12; a.y = 77; fd = open("f", O_RDONLY);
fd = creat("f", 0644); read(fd, &b, sizeof b);