Главная страница
Навигация по странице:

  • Урок 9. Форматированный ввод данных

  • Ввод чисел, символов и строк

  • Использование обычных символов

  • Использование "шаблонов"

  • Некоторые особенности и ограничения функции scanf()

  • Урок 10. Псевдослучайные числа Функции rand() и srand()

  • Получение целых случайных чисел в заданных диапазонах

  • Получение вещественных случайных чисел

  • Программирование в Linux. Учебное пособие С. В. Шапошникова, Лаборатория юного линуксоида, май 2012 1 Пояснительная записка


    Скачать 0.88 Mb.
    НазваниеУчебное пособие С. В. Шапошникова, Лаборатория юного линуксоида, май 2012 1 Пояснительная записка
    АнкорПрограммирование в Linux
    Дата02.12.2022
    Размер0.88 Mb.
    Формат файлаpdf
    Имя файлаProgramming Linux C.pdf
    ТипУчебное пособие
    #824650
    страница5 из 10
    1   2   3   4   5   6   7   8   9   10
    Передача аргументов по ссылке
    В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров
    (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные
    37
    в функцию при вызове, не оказывают. В примере выше даже если бы в функции median() менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и
    num2 не оказал.
    Однако можно организовать изменение локальной переменной одной функции, с помощью другой функции. Сделать это можно, передав в функцию адрес переменной или указатель на нее. На самом деле в этом случае также передается копия значения. Но какого значения?!
    Это адрес на область памяти. На один и тот же участок памяти может существовать множество ссылок, и с помощью каждой из них можно поменять находящееся там значение.
    Рассмотрим пример:
    #include
    void multi (int *px, int y);
    main () {
    int x = 34, y = 6;
    multi(&x, 367);
    multi(&y, 91);
    printf("%d %d\n", x, y);
    }
    void multi (int *base, int pow) {
    while (pow >= 10) {
    *base = *base * 10;
    pow = pow / 10;
    }
    }
    Функция multi()
    ничего не возвращает, что подчеркнуто с помощью ключевого слова void
    Принимает эта функция адрес, который присваивается локальной переменной-указателю, и целое число. В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Но по сути это адрес переменной x из фукнции main()
    , а значит меняется и ее значение.
    Когда multi()
    вызывается в main()
    , то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов multi(x, 786)
    привел бы к ошибке, а вызов multi(&x, 786)
    — правильный, т.к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main()
    указатель и передавать именно его
    (в данном случае сама переменная p содержит адрес):
    int x = 34, y = 6;
    int *p;
    p = &x;
    multi(p, 367);
    p = &y;
    multi(p, 367);
    printf("%d %d\n", x, y);
    Кроме того, следует знать, что функция может возвращать адрес.
    Важно понять механизм так называемой передачи аргументов по ссылке, т.к. это понимание пригодится при изучении массивов и строк. Использовать указатели при работе с простыми типами данных не стоит. Лучше возвращать из функции значение, чем менять локальные переменные одной функции с помощью кода другой функции. Функции должны быть достаточно автономными.
    38

    Решение задач
    Задания
    1. Перепишите код первого примера этого урока так, чтобы в нем использовался указатель; а код примера с функцией multi(), наоборот, избавьте от указателей.
    2. Напишите программу, в которой помимо функции main() были бы еще две функции: в одной вычислялся факториал переданного числа, в другой — находился n-ый элемент ряда
    Фибоначчи (n — параметр функции). Вызовите эти функции с разными аргументами.
    3. Придумайте и напишите программу, в которой из функции main() вызывается другая функция, а из последней вызывается еще одна функция.
    Урок 9. Форматированный ввод данных
    В то время как функция printf()
    осуществляет форматированный вывод данных, функция scanf()
    осуществляет их форматированный ввод. Это значит, что поступающие на ввод данные преобразуются соответственно указанному формату(ам) и записываются по адресу(ам) указанной(ых) переменной(ых):
    scanf(строка_формата, адреса_переменных);
    Причина, по которой в scanf()
    передаются адреса, а не значения переменных, очевидна.
    Функция scanf()
    должна изменять значения переменных тех функций, из которых вызывается. Единственный способ — это получить адреса областей памяти.
    Спецификации формата данных, допустимые в строке формата, для scanf()
    почти идентичны тем, что были описаны для функции printf()
    . На этом уроке мы подробно не рассмотрим все возможности форматированного ввода с помощью scanf()
    , зато разберем ряд конкретных примеров.
    Ввод чисел, символов и строк
    Пример ввода-вывода целого и вещественного чисел, символа и строки:
    int a;
    float b;
    char ch, str[30];
    scanf("%d%f%c%s", &a, &b, &ch, str);
    printf("%d %.3f %c %s\n", a, b, ch, str);
    Результат:
    45 34.3456y hello
    45 34.346 y hello
    Здесь при выполнении программы все данные были введены в одну строку. Разделителем между числами и строками является пробел, а также любой другой символ пустого пространства (например, '\n'). Однако при считывании символа, пробел учитывается как символ; чтобы этого не произошло, в примере букву записали сразу после числа. Данные можно было бы ввести, разделяя их переходом на новую строку (опять же при этом надо иметь ввиду, как считывается символ).
    В строке формата функции scanf()
    между спецификациями вполне допустимо поставить пробелы:
    %d %f %c %s
    . Они никакой роли не сыграют. Понятно, что данные можно было получить и так:
    scanf("%d", &a);
    scanf("%f", &b);
    scanf("%c", &ch);
    scanf("%s", str);
    Обратите внимание, перед переменной str отсутствует знак амперсанда. Это не опечатка. В
    39
    последующих уроках вы узнаете, что имя массива уже само по себе является ссылкой на массив (другими словами, str содержит адрес начала массива).
    В функции scanf()
    в спецификации формата вещественных чисел не указывается точность представления числа. Запись типа %.3f или %.10lf приведет к невозможности получить вещественное число. Чтобы получить число типа double используют формат %lf, для long double - %Lf.
    Для целых чисел: длинное целое - %ld, короткое целое - %hd. Кроме того, существуют спецификации для ввода восьмеричных и шестнадцатеричных чисел.
    Функция scanf()
    возвращает количество удачно считанных данных; т.е. значение, возвращаемое функцией, можно проанализировать и таким образом узнать, корректно ли были введены данные. Например:
    int a;
    double b;
    char ch, str[30]; ch = scanf("%d %lf %s", &a, &b, str); if (ch == 3)
    printf("%d %.3lf %s\n", a, b, str);
    else printf("Error input\n");
    Использование обычных символов
    В строке формата scanf()
    допустимо использование обычных символов. В этом случае при вводе данных также должны вводится и эти символы:
    int a, b, c;
    scanf("%d + %d = %d", &a, &b, &c);
    printf("Your answer is %d\nThe correct answer is %d\n", c, a+b);
    В данном случае, когда программа выполняется, ввод должен выглядеть примерно так:
    342+1024 = 1366. Знаки "+" и "=" обязательно должны присутствовать между числами, наличие пробелов или их отсутствие абсолютно никакой роли не играет:
    45 + 839=875
    Your answer is 875
    The correct answer is 884
    Запрет присваивания
    Если какие-либо данные, вводимые пользователем, следует проигнорировать, то используют запрет присваивания, ставя после знака %, но перед буквой формата звездочку *. В таком случае данные считываются, но никакой переменной не присваиваются. Это можно использовать, например, когда нет определенной уверенности в том, что поступит на ввод, с одной стороны, и нужды сохранять эти данные, с другой:
    float arr[3];
    int i;
    for(i = 0; i < 3; i++)
    scanf("%*s %f", &arr[i]);
    printf("Sum: %.2f\n", arr[0]+arr[1]+arr[2]);
    Здесь предполагается, что перед каждым числом будет вводится строка, которую следует проигнорировать, например:
    40

    First: 23.356
    Second: 17.285
    Third: 32.457
    Sum: 73.098
    Использование "шаблонов"
    Для функции scanf()
    есть пара спецификаций формата, отдаленно напоминающих шаблоны командной оболочки и др. Формат […] позволяет получить строку, содержащую любые символы, указанные в квадратных скобках. Как только на ввод поступает символ, не входящий в указанный набор, считывание данных прекращается. Формат [^…], наоборот, помещает в строку символы, не входящие в указанный набор, до тех пор пока не встретит любой из указанных.
    В примере ниже как только поступает не цифра, считывание ввода завершается. При этом если первый символ — не цифра, то в str вообще ничего не записывается:
    char str[30]="";
    scanf("%[0-9]", str);
    printf("%s\n", str);
    А в этом случае строке будет присвоена последовательность символов до любого из указанных знаков препинания:
    scanf("%[^;:,!?]", str);
    printf("%s\n", str);
    Результат:
    Hello, World!
    Hello
    Некоторые особенности и ограничения функции scanf()
    Как только поступают некорректные данные, функция scanf()
    завершает свою работу. В примере:
    scanf("%d%f", &a, &b);
    если переменной a попытаться присвоить символ или строку, что невозможно, то переменная b уже обрабатываться не будет. Можно предположить, что так будет надежнее:
    scanf("%d", &a);
    scanf("%f", &b);
    Вроде бы неудачное считывание a не должно оказывать никакого влияния на b, т.к. это уже иной вызов scanf()
    . Но не все так просто: при некорректном вводе данные остаются в буфере и пытаются "навязать" себя последующим вызовам scanf()
    . Поэтому при использовании scanf()
    надо думать о том, как в случае некорректного ввода очистить буфер. Например, это можно сделать так, как показано ниже, или путем использования специальных функций (здесь не рассматриваются):
    if (scanf("%d", &a) != 1) // если данные не удалось присвоить переменной,
    scanf("%*s"); // то выбросить их в виде строки.
    scanf("%f", &b);
    Разделителем данных для scanf()
    являются символы пустого пространства. Это означает отсутствие возможности записать строку, содержащую неизвестное количество пробелов, в одну переменную, используя только scanf(). Придется использовать либо другую функцию
    (например, getchar()
    ), либо создать циклическую конструкцию, считывающую по одному слову и добавляющую его к общей строке.
    Таким образом, не смотря на достаточно большие возможности scanf()
    , эта функция хранит в себе ряд неудобств и опасностей.
    41

    Решение задач
    Задание
    1. На прошлом занятии вы написали программу, содержащую функции, вычисляющие факториал числа и заданный элемент ряда Фибоначчи. Измените эту программу таким образом, чтобы она запрашивала у пользователя, что он хочет вычислить: факториал или число Фибоначчи. Затем программа запрашивала бы у пользователя либо число для вычисления факториала, либо номер элемента ряда Фибоначчи.
    2. Напишите программу, которая запрашивает у пользователя две даты в формате дд.мм.гггг.
    Дни, месяцы и года следует присвоить целочисленным переменным. Программа должна выводить на экран информацию о том, какая дата более ранняя, а какая более поздняя.
    3. Используя цикл, напишите код, в котором пользователю предлагается вводить данные до тех пор, пока он не сделает это корректно, т.е. пока все указанные в scanf() переменные не получат свои значения. Протестируйте программу. Что вы наблюдаете и почему? Как можно решить проблему?
    Урок 10. Псевдослучайные числа
    Функции rand() и srand()
    В языках программирования обычно предусмотрены функции, позволяющие генерировать случайные числа в определенном по умолчанию диапазоне. На самом деле генерируются не случайные, а так называемые псевдослучайные числа; они выглядят случайно, но вычисляются по вполне конкретной формуле. Но для простоты далее мы все равно будем называть их случайными.
    В языке программирования C получить случайное число можно с помощью функции rand()
    , которая входит в стандартную библиотеку языка. Эта функция не принимает никакие параметры.
    Задание
    Напишите программу, в которой целочисленной переменной присваивается результат выполнения функции rand(). Выведите значение переменной на экран.
    Функция rand()
    возвращает целое число от 0 до значения присвоенного константе
    RAND_MAX
    . Значение
    RAND_MAX
    зависит от системы и определено в заголовочном файле stdlib.h. Так, например, оно может быть равно 32767 (двухбайтовое целое) или 2147483647
    (четырехбайтовое целое).
    Задание
    Определите значение
    RAND_MAX в вашей системе. Для этого не забудьте подключить к файлу исходного кода заголовочный файл stdlib.h.
    Код ниже выводит на экран 50 случайных чисел:
    #include
    #include
    main () {
    char i;
    for (i = 1; i <= 50; i++) {
    printf("%15d", rand());
    if (i % 5 == 0) printf("\n");
    }
    }
    42

    В теле цикла осуществляется переход на новую строку после каждых выведенных на экран пяти чисел. Для этого используется выражение, в котором находится остаток от деления i на
    5, результат сравнивается с 0. Чтобы после первого числа не происходил переход на новую строку, i сначала присваивается единица, а не ноль (т.к. 0 делится на 5 без остатка).
    Задание
    Спишите код, приведенный выше. Выполните программу несколько раз, при этом обратите внимание, разные ли результаты вы получаете от выполнения к выполнению.
    Вы должны были заметить, что при каждом запуске программы числа остаются одинаковыми. Даже если вы перекомпилируете программу, результат не изменится. Данный эффект связан с тем, что начальное (инициализирующее) число, которое подставляется в формулу вычисления первого и последующих псевдослучайных чисел, для каждой системы всегда одно и то же. Однако это начальное число можно изменить с помощью функции srand()
    , которой в качестве параметра передается любое целое число. Понятно, что если вы зададите конкретный аргумент для функции, например, srand(1000)
    , то от вызова к вызову программы числа будут также одни и те же. Хотя и не те, что были бы без srand()
    . Поэтому появляется проблема, как сделать так, чтобы аргумент для srand()
    был тоже случайным?
    Получается замкнутый круг.
    Задание
    Переделайте программу, выводящую на экран 50 случайных чисел так, чтобы сначала у пользователя запрашивалось любое целое число с помощью scanf(), которое передавалось бы в функцию srand().
    Пользователь программы сам может задавать инициализирующее значение. Но чаще всего это не является полноценным выходом из ситуации. Поэтому инициализирующее значение привязывают к какому-либо процессу, протекающему в операционной системе, например, к часам. Время (учитывая не только время суток, но и дату) никогда не бывает одинаковым.
    Значит значение для srand()
    , преобразованное в целое из системного времени, будет различным.
    Текущее время можно узнать с помощью функции time()
    , прототип которой описан в файле time.h. Передав time()
    в качестве параметра NULL, мы получим целое число, которое можно передать в srand()
    :
    srand(time(NULL));
    Задание
    Переделайте вашу программу так, чтобы инициализирующее значение зависело от системного времени.
    Получение целых случайных чисел в заданных диапазонах
    Функция rand()
    выдает случайное число от 0 до значения
    RAND_MAX
    . Что делать, если требуется получать случайные числа в иных диапазонах, например, от 100 до 999?
    Сначала рассмотрим более простую ситуацию: получить случайные числа от 0 до 5. Если любое целое число попытаться разделить на 5 нацело, то можно получить как 0 (когда число делится на 5 без остатка), так и 1, 2, 3, 4. Например, rand()
    вернула число 283. Применяя к этому числу операцию нахождения остатка от деления на 5, получим 3. Т.е. выражение rand() % 5
    дает любое число в диапазоне [0, 5).
    Однако, что если надо, чтобы число 5 так же входило в диапазон, т.е. диапазон имеет вид [0,
    5]? Логично предположить, что следует найти остаток от деления на 6. При этом более грамотным будет следующее рассуждение: надо находить остаток от деления на размер диапазона. В данном случае он равен шести значениям: 0, 1, 2, 3, 4, 5. Чтобы найти размер
    43
    диапазона надо из допустимого максимума вычесть допустимый минимум и прибавить единицу: max - min + 1. Будьте внимательны: если, например, требуется, чтобы указанный в задаче максимум не входил в диапазон, то единицу прибавлять не надо или надо вычитать единицу из максимума.
    Задание
    Напишите программу, выдающую 50 случайных чисел от 0 до 99 включительно.
    Итак, мы знаем формулу получения длины диапазона: max - min + 1. Если требуется получить число от 6 до 10 включительно, то длина диапазона будет равна 10 - 6 + 1 = 5.
    Выражение rand() % 5
    даст любое число от 0 до 4 включительно. Но нам надо от 6 до 10. В таком случае достаточно к полученному случайному остатку прибавить 6, т.е. минимум.
    Другими словами, надо выполнить сдвиг. Действительно для приведенного примера:

    если остаток был равен 0, то добавляя 6, получаем 6;

    остаток 1, добавляем 6, получаем 7;



    остаток 4, прибавляем 6, получаем 10;

    остатка больше 4 не может быть.
    В таком случае формула для получения случайного числа в диапазоне [a, b] выглядит так:
    rand() % длина_диапазона + сдвиг,
    где длина_диапазона вычисляется как b - a + 1, сдвиг является значением a.
    В эту формулу также вписываются случаи, когда необходимо получить случайное число от 0 до N, т.е. они являются ее частными случаями.
    Задание
    Выведите на экран ряд случайных чисел, принадлежащих диапазону от 100 до 299 включительно.
    С таким же успехом можно получать случайные отрицательные числа. Действительно, если диапазон задан как [-35, -1], то его длина будет равна -1 - (-35) + 1 = 35, что соответствует действительности; выражение получения случайного числа будет выглядеть так: rand() % 35 - 35
    Так, если остаток от деления составил 0, то мы получим -35, а если 34, то -1. Остальные остатки дадут значения в промежутке от -35 до -1.
    Задание
    Выведите на экран ряд случайных чисел, принадлежащих диапазону от -128 до 127 включительно.
    Получение вещественных случайных чисел
    Ситуация с вещественными числами выглядит несколько по-иному. Во-первых, мы не можем получить остаток от деления, если делимое или делитель дробное число. Во вторых при вычислении длины диапазона нельзя прибавлять единицу.
    Поясним вторую причину. Допустим диапазон задан как [2.50, 5.30]. Он состоит не из определенного количества чисел (как в случае целых), а из неопределенного (можно сказать, бесконечного) числа значений, т.к. вещественные числа можно представлять с различной степенью точности. Позже выполняя округление все равно будет шанс получить максимальную границу диапазона, поэтому для вычисления длины диапазона достаточно из
    44
    максимума вычесть минимум.
    Если разделить случайное число, преобразованное к вещественному типу, которое выдала функция rand()
    , на значение константы
    RAND_MAX
    , то получится вещественное случайное число от 0 до 1. Теперь, если это число умножить на длину диапазона, то получится число, лежащее в диапазоне от 0 до значения длины диапазона. Далее если прибавить к нему смещение к минимальной границе, то число благополучно впишется в требуемый диапазон.
    Таким образом формула для получения случайного вещественного числа выглядит так:
    (float) rand() / RAND_MAX * (max - min) + min
    Задание
    Заполните массив случайными числами в диапазоне от 0.51 до 1.00. Выведите значение элементов массива на экран.
    1   2   3   4   5   6   7   8   9   10


    написать администратору сайта