1.30.

Напишите программу, которая при введении с клавиатуры буквы печатает на терминале ключевое слово, начинающееся с данной буквы. Например, при введении буквы 'b' печатает "break".

1.31.

Напишите программу, отгадывающую задуманное вами число в пределах от 1 до 200, пользуясь подсказкой с клавиатуры "=" (равно), "<" (меньше) и ">" (больше). Для угадывания числа используйте метод деления пополам.

1.32.

Напишите программу, печатающую степени двойки
            1, 2, 4, 8, ...
Заметьте, что, начиная с некоторого n, результат становится отрицательным из-за переполнения целого.

1.33.

Напишите подпрограмму вычисления квадратного корня с использованием метода касательных (Ньютона):

       x(0)   =  a
                 1      a
       x(n+1) =  - * ( ----  + x(n))
                 2     x(n)
Итерировать, пока не будет | x(n+1) - x(n) | < 0.001

Внимание! В данной задаче массив не нужен. Достаточно хранить текущее и предыдущее значения x и обновлять их после каждой итерации.

1.34.

Напишите программу, распечатывающую простые числа до 1000.
            1, 2, 3, 5, 7, 11, 13, 17, ...
    /*#!/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;
    }
    #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 для ошибок округления при целочисленном делении */
           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.

Напишите функцию вычисления определенного интеграла по методу Симпсона.
    /*#!/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 );
    }
    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.

Где ошибка?
    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);
    }

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)? Предположим, что мы написали
    #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

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 сразу при вводе очередного числа!

    #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-а программу,
                     * визуализирующую процесс сортировки:
                     * например, изображающую эту перестановку
                     * элементов массива */
            }
    }
    /* сортировка строк */
    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;
    }

* Препроцессор Си - это программа /lib/cpp

** ANSI - American National Standards Institute, разработавший стандарт на язык Си и его окружение.

© Copyright А. Богатырев, 1992-95
Си в UNIX

Назад | Содержание | Вперед