В последнем случае присваивание переменной n значения 12 выполнит компилятор  еще  во
время  компиляции программы, а не сама программа при своем запуске. Точно так же про-
исходит со всеми статическими данными (описанными как static, либо расположенными вне
всех функций); причем если их начальное значение не указано явно - то подразумевается
0 ('\0', NULL, "").  Однако нулевые значения не хранятся в скомпилированном выполняе-
мом файле, а требуемая "чистая" память расписывается при старте программы.

1.16.  По поводу описания переменной с инициализацией:

    TYPE x = выражение;

является (почти) эквивалентом для

    TYPE x;         /* описание */
    x = выражение;  /* вычисление начального значения */

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

Рассмотрим пример:

    #include 
    extern double sqrt();   /* квадратный корень */
    double x   = 1.17;
    double s12 = sqrt(12.0);            /* #1 */
    double y   = x * 2.0;               /* #2 */
    FILE  *fp  = fopen("out.out", "w"); /* #3 */
    main(){
      double ss = sqrt(25.0) + x;       /* #4 */
      ...
    }

Строки с метками #1, #2 и #3 ошибочны. Почему?
     Ответ: при инициализации статических данных (а s12, y и fp таковыми и  являются,
так  как описаны вне какой-либо функции) выражение должно содержать только константы,
поскольку оно вычисляется КОМПИЛЯТОРОМ. Поэтому ни использование значений переменных,
ни вызовы функций здесь недопустимы (но можно брать адреса от переменных).
     В строке #4 мы инициализируем автоматическую переменную ss, т.е.  она  отводится
уже  во  время  выполнения программы. Поэтому выражение для инициализации вычисляется
уже не компилятором, а самой программой, что дает нам право использовать  переменные,
вызовы функций и.т.п., то есть выражения языка Си без ограничений.

1.17.  Напишите  программу,  реализующую  эхо-печать  вводимых  символов.   Программа
должна  завершать  работу  при получении признака EOF.  В UNIX при вводе с клавиатуры
признак EOF обычно обозначается одновременным нажатием клавиш CTRL  и  D  (CTRL  чуть
раньше),  что  в  дальнейшем  будет  обозначаться CTRL/D; а в MS DOS - клавиш CTRL/Z.
Используйте getchar() для ввода буквы и putchar() для вывода.

1.18.  Напишите программу, подсчитывающую число символов поступающих со  стандартного
ввода. Какие достоинства и недостатки у следующей реализации:

    #include 
    main(){ double cnt = 0.0;
            while (getchar() != EOF) ++cnt;
            printf("%.0f\n", cnt );
    }

Ответ: и достоинство и недостаток в том, что счетчик имеет тип double.  Достоинство -
можно  подсчитать очень большое число символов; недостаток - операции с double обычно
выполняются гораздо медленнее, чем с int и long  (до  десяти  раз),  программа  будет
работать  дольше.   В  повседневных  задачах  вам  вряд ли понадобится иметь счетчик,
отличный от long cnt; (печатать его надо по формату "%ld").

1.19.  Составьте программу перекодировки вводимых символов со стандартного  ввода  по
следующему правилу:

            a -> b
            b -> c
            c -> d
            ...
            z -> a
     другой символ -> *

Коды строчных латинских букв расположены подряд по возрастанию.

1.20.  Составьте программу перекодировки вводимых символов со стандартного  ввода  по
следующему правилу:

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

            B -> A
            C -> B
            ...
            Z -> Y
     другой символ -> *

Коды прописных латинских букв также расположены по возрастанию.

1.21.  Напишите программу, печатающую номер и код введенного символа в восьмеричном и
шестнадцатеричном  виде.   Заметьте,  что если вы наберете на вводе строку символов и
нажмете клавишу ENTER, то программа напечатает вам на один символ больше, чем вы наб-
рали.   Дело  в  том,  что код клавиши ENTER, завершившей ввод строки - символ '\n' -
тоже попадает в вашу программу (на экране  он  отображается  как  перевод  курсора  в
начало следующей строки!).

1.22.  Разберитесь, в чем состоит разница между символами '0'  (цифра  нуль)  и  '\0'
(нулевой байт). Напечатайте

      printf( "%d %d %c\n", '\0', '0', '0' );

Поставьте опыт: что печатает программа?

    main(){
            int c = 060;  /* код символа '0' */
            printf( "%c %d %o\n", c, c, c);
    }

Почему печатается 0 48 60?  Теперь напишите вместо
  int c = 060;
строчку
  char c = '0';

1.23.  Что напечатает программа?

    #include 
    void main(){
            printf("ab\0cd\nxyz");
            putchar('\n');
    }

Запомните, что '\0' служит признаком конца строки в памяти, а '\n' - в файле.  Что  в
строке "abcd\n" на конце неявно уже расположен нулевой байт:

    'a','b','c','d','\n','\0'

Что строка "ab\0cd\nxyz" - это

    'a','b','\0','c','d','\n','x','y',z','\0'

Что строка "abcd\0" - избыточна, поскольку будет иметь на  конце  два  нулевых  байта
(что  не  вредно,  но зачем?).  Что printf печатает строку до нулевого байта, а не до
закрывающей кавычки.
Программа эта напечатает ab и перевод строки.
Вопрос: чему равен sizeof("ab\0cd\nxyz")?  Ответ: 10.

1.24.  Напишите программу, печатающую целые числа от 0 до 100.

1.25.  Напишите программу, печатающую квадраты и кубы целых чисел.

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

1.26.  Напишите программу, печатающую сумму квадратов первых n целых чисел.

1.27.  Напишите программу, которая переводит секунды в дни, часы, минуты и секунды.

1.28.  Напишите программу, переводящую скорость из километров в час в метры в  секун-
дах.

1.29.  Напишите программу, шифрующую текст файла путем замены значения символа  (нап-
ример, значение символа C заменяется на C+1 или на ~C ).

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, ...

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

    /*#!/bin/cc primes.c -o primes -lm
     *        Простые числа.
     */
    #include 
    #include 
    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-95                  - 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 = ?

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

     */
    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();

Используемый метод получения случайных чисел таков:

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

    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 

    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();
 

RLE Banner Network