(x) стоит пробел, который изменяет весь смысл макроопределения: вместо макроса с
параметром inc(x) мы получаем, что слово inc будет заменяться на (x)((x)+1). Заметим
однако, что пробелы после # перед именем директивы вполне допустимы. В четвертом
случае показана характерная опечатка - символ ; после определения. В результате напи-
санный printf() заменится на
printf( "n=%d\n", 12; );
где лишняя ; даст синтаксическую ошибку.
В пятом случае ошибки нет, но нас ожидает неприятность в строке p=4-X; которая
расширится в строку p=4--2; являющуюся синтаксически неверной. Чтобы избежать подоб-
ной ситуации, следовало бы написать
p = 4 - X; /* через пробелы */
но еще проще (и лучше) взять макроопределение в скобки:
#define X (-2)
1.45. Напишите функцию max(x, y), возвращающую большее из двух значений. Напишите
аналогичное макроопределение. Напишите макроопределения min(x, y) и abs(x) (abs -
модуль числа). Ответ:
#define abs(x) ((x) < 0 ? -(x) : (x))
#define min(x,y) (((x) < (y)) ? (x) : (y))
Зачем x взят в круглые скобки (x)? Предположим, что мы написали
#define abs(x) (x < 0 ? -x : x )
вызываем
abs(-z) abs(a|b)
получаем
(-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b )
У нас появилась "дикая" операция --z; а выражение a|b<0 соответствует a|(b<0), с сов-
сем другим порядком операций! Поэтому заключение всех аргументов макроса в его теле
в круглые скобки позволяет избежать многих неожиданных проблем. Придерживайтесь этого
правила!
Вот пример, показывающий зачем полезно брать в скобки все определение:
#define div(x, y) (x)/(y)
При вызове
А. Богатырев, 1992-95 - 19 - Си в UNIX
z = sizeof div(1, 2);
превратится в
z = sizeof(1) / (2);
что равно sizeof(int)/2, а не sizeof(int). Вариант
#define div(x, y) ((x) / (y))
будет работать правильно.
1.46. Макросы, в отличие от функций, могут порождать непредвиденные побочные
эффекты:
int sqr(int x){ return x * x; }
#define SQR(x) ((x) * (x))
main(){ int y=2, z;
z = sqr(y++); printf("y=%d z=%d\n", y, z);
y = 2;
z = SQR(y++); printf("y=%d z=%d\n", y, z);
}
Вызов функции sqr печатает "y=3 z=4", как мы и ожидали. Макрос же SQR расширяется в
z = ((y++) * (y++));
и результатом будет "y=4 z=6", где z совсем не похоже на квадрат числа 2.
1.47. ANSI препроцессор[*] языка Си имеет оператор ## - "склейка лексем":
#define VAR(a, b) a ## b
#define CV(x) command_ ## x
main(){
int VAR(x, 31) = 1;
/* превратится в int x31 = 1; */
int CV(a) = 2; /* даст int command_a = 2; */
...
}
Старые версии препроцессора не обрабатывают такой оператор, поэтому раньше использо-
вался такой трюк:
#define VAR(a, b) a/**/b
в котором предполагается, что препроцессор удаляет комментарии из текста, не заменяя
их на пробелы. Это не всегда так, поэтому такая конструкция не мобильна и пользо-
ваться ею не рекомендуется.
1.48. Напишите программу, распечатывающую максимальное и минимальное из ряда чисел,
вводимых с клавиатуры. Не храните вводимые числа в массиве, вычисляйте max и min
сразу при вводе очередного числа!
____________________
[*] ANSI - American National Standards Institute, разработавший стандарт на язык Си
и его окружение.
А. Богатырев, 1992-95 - 20 - Си в UNIX
#include
main(){
int max, min, x, n;
for( n=0; scanf("%d", &x) != EOF; n++)
if( n == 0 ) min = max = x;
else{
if( x > max ) max = x;
if( x < min ) min = x;
}
printf( "Ввели %d чисел: min=%d max=%d\n",
n, min, max);
}
Напишите аналогичную программу для поиска максимума и минимума среди элементов мас-
сива, изначально min=max=array[0];
1.49. Напишите программу, которая сортирует массив заданных чисел по возрастанию
(убыванию) методом пузырьковой сортировки. Когда вы станете более опытны в Си, напи-
шите сортировку методом Шелла.
/*
* Сортировка по методу Шелла.
* Сортировке подвергается массив указателей на данные типа obj.
* v------.-------.------.-------.------0
* ! ! ! !
* * * * *
* элементы типа obj
* Программа взята из книги Кернигана и Ритчи.
*/
#include
#include
#include
#define obj char
static shsort (v,n,compare)
int n; /* длина массива */
obj *v[]; /* массив указателей */
int (*compare)(); /* функция сравнения соседних элементов */
{
int g, /* расстояние, на котором происходит сравнение */
i,j; /* индексы сравниваемых элементов */
obj *temp;
for( g = n/2 ; g > 0 ; g /= 2 )
for( i = g ; i < n ; i++ )
for( j = i-g ; j >= 0 ; j -= g )
{
if((*compare)(v[j],v[j+g]) <= 0)
break; /* уже в правильном порядке */
/* обменять указатели */
temp = v[j]; v[j] = v[j+g]; v[j+g] = temp;
/* В качестве упражнения можете написать
* при помощи curses-а программу,
* визуализирующую процесс сортировки:
* например, изображающую эту перестановку
* элементов массива */
}
}
А. Богатырев, 1992-95 - 21 - Си в UNIX
/* сортировка строк */
ssort(v) obj **v;
{
extern less(); /* функция сравнения строк */
int len;
/* подсчет числа строк */
len=0;
while(v[len]) len++;
shsort(v,len,less);
}
/* Функция сравнения строк.
* Вернуть целое меньше нуля, если a < b
* ноль, если a == b
* больше нуля, если a > b
*/
less(a,b) obj *a,*b;
{
return strcoll(a,b);
/* strcoll - аналог strcmp,
* но с учетом алфавитного порядка букв.
*/
}
char *strings[] = {
"Яша", "Федя", "Коля",
"Гриша", "Сережа", "Миша",
"Андрей Иванович", "Васька",
NULL
};
int main(){
char **next;
setlocale(LC_ALL, "");
ssort( strings );
/* распечатка */
for( next = strings ; *next ; next++ )
printf( "%s\n", *next );
return 0;
}
1.50. Реализуйте алгоритм быстрой сортировки.
А. Богатырев, 1992-95 - 22 - Си в UNIX
/* Алгоритм быстрой сортировки. Работа алгоритма "анимируется"
* (animate-оживлять) при помощи библиотеки curses.
* cc -o qsort qsort.c -lcurses -ltermcap
*/
#include "curses.h"
#define N 10 /* длина массива */
/* массив, подлежащий сортировке */
int target [N] = {
7, 6, 10, 4, 2,
9, 3, 8, 5, 1
};
int maxim; /* максимальный элемент массива */
/* quick sort */
qsort (a, from, to)
int a[]; /* сортируемый массив */
int from; /* левый начальный индекс */
int to; /* правый конечный индекс */
{
register i, j, x, tmp;
if( from >= to ) return;
/* число элементов <= 1 */
i = from; j = to;
x = a[ (i+j) / 2 ]; /* значение из середины */
do{
/* сужение вправо */
while( a[i] < x ) i++ ;
/* сужение влево */
while( x < a[j] ) j--;
if( i <= j ){ /* обменять */
tmp = a[i]; a[i] = a[j] ; a[j] = tmp;
i++; j--;
demochanges(); /* визуализация */
}
} while( i <= j );
/* Теперь обе части сошлись в одной точке.
* Длина левой части = j - from + 1
* правой = to - i + 1
* Все числа в левой части меньше всех чисел в правой.
* Теперь надо просто отсортировать каждую часть в отдельности.
* Сначала сортируем более короткую (для экономии памяти
* в стеке ). Рекурсия:
*/
if( (j - from) < (to - i) ){
qsort( a, from, j );
qsort( a, i, to );
} else {
qsort( a, i, to );
qsort( a, from, j );
}
}
А. Богатырев, 1992-95 - 23 - Си в UNIX
int main (){
register i;
initscr(); /* запуск curses-а */
/* поиск максимального числа в массиве */
for( maxim = target[0], i = 1 ; i < N ; i++ )
if( target[i] > maxim )
maxim = target[i];
demochanges();
qsort( target, 0, N-1 );
demochanges();
mvcur( -1, -1, LINES-1, 0);
/* курсор в левый нижний угол */
endwin(); /* завершить работу с curses-ом */
return 0;
}
#define GAPY 2
#define GAPX 20
/* нарисовать картинку */
demochanges(){
register i, j;
int h = LINES - 3 * GAPY - N;
int height;
erase(); /* зачистить окно */
attron( A_REVERSE );
/* рисуем матрицу упорядоченности */
for( i=0 ; i < N ; i++ )
for( j = 0; j < N ; j++ ){
move( GAPY + i , GAPX + j * 2 );
addch( target[i] >= target[j] ? '*' : '.' );
addch( ' ' );
/* Рисовать '*' если элементы
* идут в неправильном порядке.
* Возможен вариант проверки target[i] > target[j]
*/
}
attroff( A_REVERSE );
/* массив */
for( i = 0 ; i < N ; i++ ){
move( GAPY + i , 5 );
printw( "%4d", target[i] );
height = (long) h * target[i] / maxim ;
for( j = 2 * GAPY + N + (h - height) ;
j < LINES - GAPY; j++ ){
move( j, GAPX + i * 2 );
addch( '|' );
}
}
refresh(); /* проявить картинку */
sleep(1);
}
А. Богатырев, 1992-95 - 24 - Си в UNIX
1.51. Реализуйте приведенный фрагмент программы без использования оператора goto и
без меток.
if ( i > 10 ) goto M1;
goto M2;
M1: j = j + i; flag = 2; goto M3;
M2: j = j - i; flag = 1;
M3: ;
Заметьте, что помечать можно только оператор (может быть пустой); поэтому не может
встретиться фрагмент
{ ..... Label: } а только { ..... Label: ; }
1.52. В каком случае оправдано использование оператора goto?
Ответ: при выходе из вложенных циклов, т.к. оператор break позволяет выйти