Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование Cи / Богатырев_Язык Си в системе Unix

.pdf
Скачиваний:
80
Добавлен:
25.04.2015
Размер:
1.19 Mб
Скачать

А. Богатырёв, 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. Реализуйте алгоритм быстрой сортировки.