Последние новости

YoungCoder теперь и на Stepikе. Записывайтесь: https://vk.cc/75rISy

Чтобы записаться на курс, необходимо зарегистрироваться на Степике: https://vk.cc/75rIC4

Это моя личная ссылка-приглашение на Stepik для вас. Регистрируясь по этой ссылке, записываясь на курсы и решая задачи, Вы помогаете автору данного сайта принять участие в конкурсе платформы Stepik! Подробности конкурса здесь: https://vk.cc/75rKuS

воскресенье, 28 июля 2013 г.

Занятие 13. Азы работы с файлами и потоками. Случайные числа.


Добрый день друзья.
Не знаю как вас, а меня жутко раздражает одна штука. Вот вам еще не надоело, каждый раз, когда проверяете свою программу вводить заново исходные данные с клавиатуры? Меня этот факт всегда печалил. Хочется быстрее посмотреть, как работает программа, а тут тебе бац, такая засада – снова данные надо вводить. Я думаю пора с этим покончить, тем более для этого у нас уже достаточно знаний и средств.  Так что, сегодняшний мини-урок будет посвящен именно решению этой проблемы.
Не далее, как полтора года назад, я рассказывал вам о стандартных потоках ввода и вывода. Для тех, кто подзабыл, рекомендую повторить материалы занятия про ввод и вывод данных. Теперь еще потренируем свою память. Что вы помните об авгиевых конюшнях?  Ну вспоминайте-вспоминайте. Если никак я напомню.  Был такой персонаж – Геракл. Большой силач, как утверждают некоторые. Так вот, встала перед ним однажды задачка, почистить конюшни, которые 30 лет никто не чистил. История умалчивает, но возможно Геракл, был первым, кто реально мог на себе почувствовать, что значит быть «по уши в дерьме». Говорят здоровяки обычно тупые. Это было явно не про нашего героя. Хитрый Геракл, перегородил   рядом протекающую речку,  направив поток воды в эти самые конюшни. Водичка за день вымыла весь навоз. 
Много воды утекло с тех пор. Сейчас данная история известна как шестой подвиг Геракла.
Кто еще не понял, чего я тут упражняюсь в остроумии, говорю напрямую. Сегодняшнее занятие будет посвящено  перенаправлению стандартных потоков ввода и вывода. Хотя нас, в данной ситуации, больше интересует поток ввода. Перенаправлять потоки мы будем в текстовый файл и из текстового файла соответственно.
Мы еще посвятим файлам отдельное занятие, сейчас же коснемся лишь некоторых основных моментов.
С текстовым файлом в Си можно работать в нескольких режимах:
r– режим, при котором мы можем читать информацию из файла. Понятное дело, что файл должен существовать, иначе откуда мы будем читать-то? 
w – режим,  который создает новый файл для записи в него информации. Если файл уже существовал, то его содержимое удаляется.
a – режим, при котором создается новый файл для записи в него, либо если файл уже существует, то в отличие от режима “w” вся прежняя информации будет сохранена. А новые данные будут записаны в конец этого файла.
r+” – при данном режиме, файл открывается сразу и для чтения и для записи. То есть мы можем как читать из файла, так и записывать в него информацию. Файл должен существовать.
w+” создается новый файл для чтения и записи. Если файл уже существовал, то его содержимое будет удалено.
 a+” – файл открывается или создается для чтения и записи информации в него. Вся информация будет записана в конец файла.

Каждый режим немного отличается от другого.  В различных ситуациях удобно использовать тот или иной режим работы с файлом.
Теперь о потоках.
Когда наша программа запускается, у нас уже открыты два стандартных потока:
stdin – стандартный поток ввода, изначально связан с клавиатурой.
stdout – стандартный поток вывода, изначально связан с монитором.

Как отмечалось выше, мы хотим перенаправить наши потоки в текстовые файлы. Для этого предусмотрена функция freopen.
На следующем рисунке показан пример её использования.

Разберем, что означают её аргументы. Всего их три.
Первый аргумент (красный)  - указывает путь к файлу. Двойные кавычки обязательны.  Про двойной обратный слеш отмечу отдельно. Обратный слеш, это начало управляющей последовательности как известно. Но сейчас нам никакие управляющие последовательности не нужны, а  нужен  просто обратный слеш, как символ. В таких случаях используют двойной обратный слеш.
Второй аргумент (зеленый) – указывает режим работы с файлом. В нашем случае обычный режим чтения. Двойные кавычки обязательны.
Третий аргумент (синий) – имя потока.
Что делает функция freopen.  Данная функция, открывает файл (указанный в первом аргументе) с заданным режимом (второй аргумент) и связывает его с потоком, который указан в третьем аргументе.
Получается в нашем случае, мы открываем для чтения файл 1.txt , хранящийся на диске D, и связываем его со стандартным потоком ввода.
Естественно я не привожу сейчас полный синтаксис данной функции, кому интересно может посмотреть его в любом справочнике. Для новичков этих данных более чем достаточно.
Вы уже осознали масштабы, которые открывает нам эта функция?  Допустим мне нужно ввести в программу 100 целых чисел, сохранить их в массив. Для дальнейшей работы с ними. Если я буду каждый раз запускать программу и вводить заново 100 чисел, у меня на это уйдет больше времени, чем на исправление ошибок. Теперь же мы можем, просто записать их один раз в файл и пользоваться им в дальнейшем. Ниже пример.
Листинг 13.1
#include<stdio.h> 
int main(){ 
      freopen("D:\\input.txt","r",stdin); 
      int arr[30]; 
      for (int i=0;i<30;i++) 
            scanf("%d",&arr[i]); 
      for (int i=1;i<30;i++) 
            arr[0]+=arr[i]; 
      printf("summa elementov massiva %d\n",arr[0]); 
      return 0;
}
На следующем рисунке  показан результат работы программы и содержимое файла input.txt.



В программе достаточно один раз связать файл с некоторым потоком, и он будет связан с ним, до конца выполнения программы.
Аналогично, мы можем связать стандартный поток вывода данных с некоторым файлом. Следует, конечно же, изменить режим работы с файлом. Я обычно использую режим w.

Ну и это еще не всё. Файлы это все конечно классно, но вот вводить 100 чисел руками, даже один раз затруднительно. А точнее лениво. Было бы классно, чтобы программа сама как-нибудь заполняла такой файл разными числами.  Решение давно существует. Функция rand(). Данная функция возвращает случайное целое число в диапазоне от нуля до RAND_MAX.  RAND_MAX это зарезервированная константа языка Си, в ней содержится максимальное целое число, которое может быть возвращено функцией rand().
Для работы с этой функций, не обходимо подключить заголовочный файл stdlib.h Кстати, этом же файле описана константа RAND_MAX. Вы можете найти этот файл у себя на компьютере, и посмотреть её значение.
Рассмотрим простой пример. Необходимо заполнить массив случайными числами.
Листинг 13.2
#include <stdio.h> 
#include <stdlib.h> 
int main(void){ 
      int arr[10]; 
      for(int i=0; i<10; i++) 
            arr[i]=rand(); 
      for(int i=0; i<10; i++) 
            printf("%d\t",arr[i]);
}

Результат работы это программы:

Теперь рассмотрим, как наложить некоторые ограничения на эту функцию.

1. Пусть нам нужны числа, которые больше 100.  Функция rand() выдает нам числа, начиная с нуля. Т.е. самое маленькое число, которое может получиться, будет нуль. Если мы прибавим к возвращаемому значению rand() число 100, то  получим левую границу нужного нам множества.
В нашей программе мы должны были бы написать
arr[i]=rand() + 100;

2. Зайдем с другой стороны. Пусть мы хотим получить числа от нуля до  100. На помощь нам придет математика. Как известно остаток от деления на некоторое число A,всегда меньше этого числа А.
Например, при делении на 4, могут получиться остатки 0,1, 2 и 3.
Таким образом, мы можем взять от числа возвращаемого функцией rand(0) остаток от деления на 100 и получим числа от 0 до 100, причем  число 100 не включено в промежуток.
arr[i]=rand()%100;

3. Теперь пусть нужно получить число из некоторого промежутка [A,B]. Для определенности пусть это будет промежуток [100;200].
Логично было бы объединить эти условия.
arr[i]=100+rand()%200;

Но в таком виде у нас может получиться число и большее 200. Например, если бы rand()вернула бы число 395. Остаток от деления на 200 395%200=195, да еще + 100 получили бы 295. Не совсем то, что нам нужно. Такая строка даст нам числа из интервала [100;299] или если записать это иначе [A;A+B-1].  Как видим правая граница сдвинута на A-1 вправо. Нам её нужно сдвинуть влево. За правую границу у нас отвечает остаток от деления. Значит вычтем её на A-1.
Получим  rand()%(B-A+1).
Ну а в наших условиях:
arr[i]=100+rand()%101;

С учетом изменений получим следующую программу

Листинг 13.3
#include <stdio.h>

#include <stdlib.h>



int main(void){ 
      int arr[10]; 
     
      for(int i=0; i<10; i++) 
            arr[i]=100+rand()%101; 
      for(int i=0; i<10; i++) 
            printf("%d\t",arr[i]);
}

Результат её работы на следующем рисунке


С целыми числами разобрались. А как быть с вещественными? Как вариант можно поделить возвращаемое значение на RAND_MAX, при этом не забывайте о коварном свойствеоперации деления описанной в четвертом занятии.
float b;

b=(float) rand()/RAND_MAX;

Но при таком подходе мы получим случайные числа от нуля до единицы [0;1).
Если же нам надо получить число из интервала [A;B), то можно воспользоваться следующей формулой:
A+rand()*(B-A)/RAND_MAX;

Ну вот на этом все на сегодня. Практического задания к этому уроку не будет. Попрактикуйтесь сами, используя задания из прошлых двух занятий.

Если Вам понравился этот урок, расскажите о нем вашим друзьям. В этом  Вам могут помочь кнопки основных социальных сетей, расположенные ниже. Вам остается всего лишь кликнуть по любой из них. 

14 комментариев :

  1. Объявив функцию freopen() дальнейшее использование scanf() приводит к тому, что элементы читаются из этого файла.
    Как сделать так что бы в n-й раз данные считывались с файла, а в i-й с клавиатуры?

    Частный случай: 1й раз считали с файла, во второй с клавиатуры, в третий с другого файла.

    ОтветитьУдалить
    Ответы
    1. ну чтобы читать с другого файла, можно снова перенаправить поток, и делать так каждый раз. Как сбросить обратно к клавиатуре я что-то не знаю, быть может так и нельзя. Я еще поищу по этому поводу информацию.
      Но для того, чтобы решить поставленную задачу есть другой способ. Здесь мы сразу весь поток перенаправили в файл, но мы могли действовать иначе. Мы могли использовать функции работы с файлами, и тогда могли бы выбирать откуда нам читать данные сейчас из файла или из стандартного потока.
      В этом уроке лишь азы. =))) Разберем позже и работу с файлами

      Удалить
  2. А почему при использовании функции rand() с такими же условиями выводит один и тот же набор чисел? Точно такие же, как у тебя в уроке. Я думал это генератор случайных чисел и он должен каждый раз новый выдавать, разве нет?

    ОтветитьУдалить
    Ответы
    1. На самом деле это генератор псевдо случайных чисел, компьютеры пока что не обладают интеллектом, чтобы создавать что либо не заданное заранее в программе. Сдесь случайные числа задаются по определённому алгоритму от изначально заданного числа более подробно можно узнать в интернете.

      Удалить
    2. Чуть ниже есть правильный ответ, на ваш вопрос. Думаю, об этом стоит упомянуть и в самом уроке. =)

      Удалить
  3. Задание 3

    #include
    #include
    int main(void){
    int arr[40],a,summ=0;
    for(int i=0; i<40; i++)
    arr[i]=rand();
    printf("Vvedite chislo:\n");
    scanf("%d", &a);
    for(int i=0; i<40; i++)
    {
    if (arr[i]<0)
    {
    arr[i]=arr[i]*(-1);
    }
    if (arr[i]<a)
    summ++;
    }

    printf("%d\n",summ);

    return (0);
    }

    ОтветитьУдалить
  4. ри повторном запуске программы, печатаются те же самые числа. Суть в том, что функция rand() один раз генерирует случайные числа, а при последующих запусках программы всего лишь отображает сгенерированные первый раз числа. Такая особенность функции rand() нужна для того, чтобы можно было правильно отладить разрабатываемую программу. При отладке программы, внеся какие-то изменения, необходимо удостовериться, что программа срабатывает правильно, а это возможно, если входные данные остались те же, то есть сгенерированные числа. Когда программа успешно отлажена, нужно, чтобы при каждом выполнении программы генерировались случайные числа. Для этого нужно воспользоваться функцией srand() из стандартной библиотеки С++. Функция srand() получив целый положительный аргумент типа unsigned или unsigned int (без знаковое целое) выполняет рандомизацию, таким образом, чтобы при каждом запуске программы функция srand() генерировала случайные числа.

    Первоисточник: http://cppstudio.com/post/339/

    ОтветитьУдалить
  5. добрый день...

    Пишу программу(даже просто копировал со страницы) листинг 13.1...и в любом случае я не могу ничего ввести и когда программа открывается, то сразу стоит сумма -24 и все программа заканчивается...что не так?

    заранее спасибо!

    ОтветитьУдалить
    Ответы
    1. Извиняюсь за спам...перечитал урок, все сам понял)))

      Удалить
  6. #include
    #include
    #include
    #include

    int main()
    {
    freopen("D:\\fail\\fail.txt","w",stdout);
    int mass[5],n;
    for(int i=0;i<5;i++)
    {
    printf("vvedite 4islo:");
    scanf_s("%d",&n);
    mass[i]=n;
    }

    printf("vvedenie 4isla:");

    for(int i=0;i<5;i++)
    {
    printf("%d",mass[i]);
    }
    _getch();
    }

    ОтветитьУдалить
    Ответы
    1. как сделать так, чтобы в созданном файле не сохранялось "printf("vvedite 4islo:");",
      а только:
      printf("vvedenie 4isla:");

      for(int i=0;i<5;i++)
      {
      printf("%d",mass[i]);

      как бы отбирать, что будет сохраняться в файле, а что нет?

      заранее спасибо

      Удалить
  7. "time.h"
    "stdlib.h"
    srand(time(NULL));- инициализация рандома(параметром выступает текущее время) иначе рандом будет постоянно одинаковым выпадать. Это я так добавлю от себя)
    Функцию желательно добавлять в начале, а
    после уже rand() будет рандомным рандомом, а не предопределённым

    ОтветитьУдалить
    Ответы
    1. хотя да, параметр хоть и разный в разное время, но при включении программы функция определиться один раз и для всех последующих рандомов значения будут предопределены.... нужно вставлять в цикл)

      Удалить

Примечание. Отправлять комментарии могут только участники этого блога.