Программирование Cи / Богатырев_Язык Си в системе Unix
.pdfА. Богатырёв, 1992-96 |
- 11 - |
Си в UNIX™ |
/*#!/bin/cc primes.c -o primes -lm * Простые числа.
*/
#include <stdio.h> #include <math.h> int debug = 0;
/* Корень квадратный из числа по методу Ньютона */ #define eps 0.0001
double sqrt (x) double x;
{
double |
sq, sqold, |
EPS; |
|
|
|
if (x < 0.0) |
|
|
|
|
|
return -1.0; |
|
|
|
|
|
if (x == 0.0) |
|
|
|
|
|
return 0.0; |
/* может привести к делению на 0 */ |
||||
EPS = x * eps; |
|
|
|
|
|
sq = x; |
|
|
|
|
|
sqold = x + 30.0; |
/* != sq */ |
||||
while (fabs (sq * sq - x) >= EPS) { |
|
||||
/* |
fabs( sq - sqold )>= EPS |
*/ |
|||
sqold = sq; |
|
|
|
|
|
sq = 0.5 * (sq + x / sq); |
|
||||
} |
|
|
|
|
|
return sq; |
|
|
|
|
|
} |
|
|
|
|
|
/* таблица прoстых чисел */ |
|
|
|||
int is_prime (t) register int |
t; { |
|
|||
register int |
i, |
up; |
|
|
|
int |
|
not_div; |
|
|
|
if (t == 2 || t == 3 || t == 5 || t == 7) |
|||||
return 1; |
|
|
/* prime */ |
||
if (t % 2 == 0 || t == 1) |
|
|
|||
return 0; |
|
|
/* composite */ |
||
up = ceil (sqrt ((double) t)) + 1; |
|
||||
i = 3; |
|
|
|
|
|
not_div = 1; |
|
|
|
|
|
while (i <= up && not_div) { |
|
|
|||
if (t % i == 0) { |
|
|
|||
|
if (debug) |
|
|
||
|
fprintf (stderr, |
"%d поделилось на %d\n", |
|||
|
|
|
|
t, |
i); |
not_div = 0; break;
}
i += 2; /*
*Нет смысла проверять четные,
*потому что если делится на 2*n,
*то делится и на 2,
*а этот случай уже обработан выше.
*/
}
return not_div;
}
А. Богатырёв, 1992-96 |
- 12 - |
Си в UNIX™ |
|
#define COL 6 |
|
||
int |
n; |
|
|
main (argc, argv) char **argv; |
|
||
{ |
|
|
|
|
int |
i, |
|
|
|
j; |
|
|
int |
n; |
|
if( argc < 2 ){
fprintf( stderr, "Вызов: %s число [-]\n", argv[0] ); exit(1);
}
i = atoi (argv[1]); /* строка -> целое, ею изображаемое */ if( argc > 2 ) debug = 1;
printf ("\t*** Таблица простых чисел от 2 до %d ***\n", i); n = 0;
for (j = 1; j <= i; j++) if (is_prime (j)){
/* распечатка в COL колонок */
printf ("%3d%s", j, n == COL-1 ? "\n" : "\t"); if( n == COL-1 ) n = 0;
else |
n++; |
} |
|
printf( "\n---\n" ); |
|
exit (0); |
|
}
1.35. Составьте программу ввода двух комплексных чисел в виде A + B * I (каждое на отдельной строке) и печати их произведения в том же виде. Используйте scanf и printf. Перед тем, как использовать scanf, проверьте себя: что неверно в нижеприведенном операторе?
int x;
scanf( "%d", x );
Ответ: должно быть написано "АДРЕС от x", то есть scanf( "%d", &x );
1.36. Напишите подпрограмму вычисления корня уравнения f(x)=0 методом деления отрезка пополам. Приведем реализацию этого алгоритма для поиска целочисленного квадратного корня из целого числа (этот алгоритм может использоваться, например, в машинной графике при рисовании дуг):
/* Максимальное unsigned long число */
#define MAXINT (~0L) |
|
|
/* |
Определим имя-синоним для типа unsigned long */ |
|
typedef unsigned long ulong; |
|
|
/* |
Функция, корень которой мы ищем: */ |
|
#define FUNC(x, arg) ((x) * (x) - (arg)) |
||
/* |
тогда x*x - arg = 0 означает |
x*x = arg, то есть |
* |
x = корень_квадратный(arg) |
*/ |
/* |
Начальный интервал. Должен выбираться исходя из |
|
* |
особенностей функции FUNC */ |
|
#define LEFT_X(arg) 0
#define RIGHT_X(arg) (arg > MAXINT)? MAXINT : (arg/2)+1;
/* |
КОРЕНЬ КВАДРАТНЫЙ, округленный вниз до целого. |
|
* |
Решается по методу деления отрезка пополам: |
|
* |
FUNC(x, arg) = 0; |
x = ? |
*/ |
|
|
ulong i_sqrt( ulong arg ) { |
|
|
register ulong mid, /* середина интервала |
*/ |
|
rgt, /* правый край интервала */ |
||
lft; |
/* левый край интервала |
*/ |
lft = LEFT_X(arg); rgt = |
RIGHT_X(arg); |
|
do{ mid = (lft + rgt + 1 |
)/2; |
|
/* +1 для ошибок округления |
при целочисленном делении */ |
А. Богатырёв, 1992-96 |
|
|
- 13 - |
Си в UNIX™ |
if( FUNC(mid, arg) > 0 ){ |
|
|||
if( rgt == mid ) mid--; |
|
|||
rgt = |
mid |
; |
/* приблизить правый край */ |
|
} else lft = |
mid |
; |
/* приблизить левый край |
*/ |
} while( lft < rgt ); return mid;
}
void main(){ ulong i; for(i=0; i <= 100; i++)
printf("%ld -> %lu\n", i, i_sqrt(i));
}
Использованное нами при объявлении переменных ключевое слово register означает, что переменная является ЧАСТО ИСПОЛЬЗУЕМОЙ, и компилятор должен попытаться разместить ее на регистре процессора, а не в стеке (за счет чего увеличится скорость обращения к этой переменной). Это слово используется как
register тип переменная;
register переменная; /* подразумевается тип int */
От регистровых переменных нельзя брать адрес: &переменная ошибочно.
1.37. Напишите программу, вычисляющую числа треугольника Паскаля и печатающую их в виде треугольника.
C(0,n) |
= |
C(n,n) |
= |
1 |
n = 0... |
C(k,n+1) |
= |
C(k-1,n) + |
C(k,n) |
k = 1..n |
n - номер строки
В разных вариантах используйте циклы, рекурсию.
1.38. Напишите функцию вычисления определенного интеграла методом Монте-Карло. Для этого вам придется написать генератор случайных чисел. Си предоставляет стандартный датчик ЦЕЛЫХ равномерно распределенных псевдослучайных чисел: если вы хотите получить целое число из интервала [A..B], используйте
int x = A + rand() % (B+1-A);
Чтобы получать разные последовательности следует задавать некий начальный параметр последовательности (это называется "рандомизация") при помощи
srand( число ); /* лучше нечетное */
Чтобы повторить одну и ту же последовательность случайных чисел несколько раз, вы должны поступать так:
srand(NBEG); x=rand(); ... ; x=rand(); /* и повторить все сначала */ srand(NBEG); x=rand(); ... ; x=rand();
Используемый метод получения случайных чисел таков:
static unsigned long int next = 1L; int rand(){
next = next * 1103515245 + 12345;
return ((unsigned int)(next/65536) % 32768);
}
void srand(seed) unsigned int seed; { next = seed; }
Для рандомизации часто пользуются таким приемом:
char t[sizeof(long)];
time(t); srand(t[0] + t[1] + t[2] + t[3] + getpid());
1.39. Напишите функцию вычисления определенного интеграла по методу Симпсона.
А. Богатырёв, 1992-96 |
- 14 - |
Си в UNIX™ |
/*#!/bin/cc $* -lm
* Вычисление интеграла по методу Симпсона */
#include <math.h>
extern double integral(), sin(), fabs(); #define PI 3.141593
double |
myf(x) |
double x; |
|
{ |
return |
sin(x / 2.0); |
} |
int niter; /* номер итерации */
void main(){
double integral();
printf("%g\n", integral(0.0, PI, myf, 0.000000001)); /* Заметьте, что myf, а не myf().
* Точное значение интеграла равно 2.0 */
printf("%d итераций\n", niter );
}
А. Богатырёв, 1992-96 |
- 15 - |
Си в UNIX™ |
double integral(a, b, f, eps)
double a, b; |
/* концы отрезка */ |
double eps; |
/* требуемая точность */ |
double (*f)(); |
/* подынтегральная функция */ |
{ |
|
register long i; |
|
double fab = (*f)(a) + (*f)(b); /* сумма на краях */ |
|
double h, h2; |
/* шаг и удвоенный шаг */ |
long n, n2; |
/* число точек разбиения и оно же удвоенное */ |
double Sodd, Seven; /* сумма значений f в нечетных и в |
|
|
четных точках */ |
double S, Sprev;/* значение интеграла на данной |
|
|
и на предыдущей итерациях */ |
double x; |
/* текущая абсцисса */ |
niter = 0; |
|
n = 10L; |
/* четное число */ |
n2 = n * 2; |
|
h = fabs(b - a) / n2; h2 = h * 2.0;
/* Вычисляем первое приближение */ /* Сумма по нечетным точкам: */ for( Sodd = 0.0, x = a+h, i = 0;
i < n;
i++, x += h2 )
Sodd += (*f)(x);
/* Сумма по четным точкам: */ for( Seven = 0.0, x = a+h2, i = 0;
i < n-1;
i++, x += h2 )
Seven += f(x); |
|
|
/* Предварительное значение интеграла: */ |
||
S = h / 3.0 * (fab + |
4.0 * Sodd + 2.0 * Seven ); |
|
do{ |
|
|
niter++; |
|
|
Sprev = S; |
|
|
/* Вычисляем |
интеграл с половинным шагом */ |
|
h2 = h; |
h /= 2.0; |
|
if( h == 0.0 ) break; |
/* потеря значимости */ |
|
n = n2; |
n2 *= 2; |
|
Seven = Seven + Sodd; |
|
|
/* Вычисляем сумму по новым точкам: */ |
||
for( Sodd = 0.0, x = a+h, |
i = 0; |
|
|
|
i < n; |
|
|
i++, x += h2 ) |
Sodd += (*f)(x); |
|
|
/* Значение интеграла */ |
|
|
S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); |
} while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps ); /* Используем условие Рунге для окончания итераций */
return ( 16.0 * S - Sprev ) / 15.0 ;
/* Возвращаем уточненное по Ричардсону значение */
}
1.40. Где ошибка?
А. Богатырёв, 1992-96 |
- 16 - |
Си в UNIX™ |
struct time_now{ |
|
|
int hour, min, |
sec; |
|
} X = { 13, 08, 00 }; /* 13 часов 08 минут 00 сек.*/ |
||
Ответ: 08 - восьмеричное число (так как начинается с нуля)! |
А в восьмеричных числах цифры 8 и 9 не |
|
бывают. |
|
|
1.41. Дан текст:
int i = -2; i <<= 2;
printf("%d\n", i); /* печать сдвинутого i : -8 */ i >>= 2;
printf("%d\n", i); /* печатается -2 */
Закомментируем две строки (исключая их из программы):
int i = -2; i <<= 2;
/*
printf("%d\n", i); /* печать сдвинутого i : -8 */ i >>= 2;
*/
printf("%d\n", i); /* печатается -2 */
Почему теперь возникает ошибка? Указание: где кончается комментарий?
Ответ: Си не допускает вложенных комментариев. Вместо этого часто используются конструкции вроде:
#ifdef COMMENT
... закомментированный текст ...
#endif /*COMMENT*/
и вроде
/**/ |
printf("here");/* отладочная выдача включена */ |
|
/* |
printf("here");/* отладочная выдача выключена */ |
|
или |
|
|
/* выключено(); |
/**/ |
|
включено(); |
/**/ |
А вот дешевый способ быстро исключить оператор (с возможностью восстановления) - конец комментария занимает отдельную строку, что позволяет отредактировать такой текст редактором почти не сдвигая курсор:
/*printf("here");
*/
1.42. Почему программа печатает неверное значение для i2 ?
int main(int argc, char *argv[]){ int i1, i2;
i1 = 1; /* Инициализируем i1 /
i2 = 2; /* Инициализируем i2 */ printf("Numbers %d %d\n", i1, i2); return(0);
}
Ответ: в первом операторе присваивания не закрыт комментарий - весь второй оператор присваивания полностью проигнорировался! Правильный вариант:
int main(int argc, char *argv[]){ int i1, i2;
i1 = 1; /* Инициализируем i1 */
i2 = 2; /* Инициализируем i2 */ printf("Numbers %d %d\n", i1, i2); return(0);
}
А. Богатырёв, 1992-96 |
|
|
- 17 - |
Си в UNIX™ |
1.43. А вот "шальной" комментарий. |
|
|
|
|
void main(){ |
|
|
|
|
int n |
= 10; |
|
|
|
int *ptr = &n; |
|
|
|
|
int x, y = 40; |
|
|
|
|
x = y/*ptr |
/* должно быть 4 */ |
+ 1; |
||
printf( "%d\n", |
x ); |
/* пять */ |
|
exit(0);
}
/* или такой пример из жизни - взят из переписки в Relcom */
...
cost = nRecords/*pFactor |
/* divided by Factor, and |
*/ |
+ fixMargin; |
/* plus the precalculated |
*/ |
... |
|
|
Результат непредсказуем. Дело в том, что y/*ptr превратилось в начало комментария! Поэтому бинарные операции принято окружать пробелами.
x = y / *ptr /* должно быть 4 */ + 1;
1.44. Найдите ошибки в директивах препроцессора Си † (вертикальная черта обозначает левый край файла).
|
| #include <stdio.h> |#include < sys/types.h >
|# |
define inc (x) ((x) + 1) |
|#define N 12; |
|
|#define X -2 |
|
| |
|
|... |
printf( "n=%d\n", N ); |
|... |
p = 4-X; |
Ответ: в первой директиве стоит пробел перед #. Диез должен находиться в первой позиции строки. Во второй директиве в <> находятся лишние пробелы, не относящиеся к имени файла - препроцессор не найдет такого файла! В данном случае "красота" пошла во вред делу. В третьей - между именем макро inc и его аргументом в круглых скобках (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)? Предположим, что мы написали
† Препроцессор Си - это программа /lib/cpp
А. Богатырёв, 1992-96 |
|
- 18 - |
Си в UNIX™ |
|
#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) |
При вызове
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-96 |
|
- 19 - |
Си в UNIX™ |
#include <stdio.h> |
|
|
|
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 <stdio.h> #include <string.h> #include <locale.h> #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-96 |
- 20 - |
Си в 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. Реализуйте алгоритм быстрой сортировки.