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

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

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

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

вторник, 3 декабря 2013 г.

Занятие №20. Некоторые особенности цикла for. Оператор последовательного вычисления.

Добрый день, друзья.
Выдалась свободная минутка, решил написать небольшой пост, по мотивам уже заданных мне ранее вопросов. Быть может кому-то эти советы, тоже будут полезны. Итак, сегодня займемся циклами.
Рассмотрим цикл for().
Как вы уже знаете, синтаксис  этой конструкции имеет следующий вид:
for(инициализация счетчика; условие; изменение счетчика)
    оператор

Кроме того, мы помним, что после цикла всегда стоит один оператор, но если нам необходимо в теле цикла выполнить несколько действий, то мы можем использовать составной оператор {}. Все что заключено в фигурные скобки, будет считаться за один единственный оператор.
Если вы еще не забыли, а это не мудрено с моей-то частотой выпуска занятий, то последнем уроке мы изучали двойные массивы. Одной из типичных задач является вывод массива размерности [N][M] на экран в виде таблицы (или матрицы) в N-строк и M столбцов. Для этой задачи очень крайне удобно использовать два вложенных цикла for. Например:
Листинг 1.
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            printf("%d\t",arr[i][j]);
   
return (0);
}

Но если мы оставим этот код таким, то массив будет выводиться не табличкой, а строчкой. Нужно добавить еще переход на новую строку, после того, как мы закончили выводить все элементы текущей. Ну т.е. после каждого внутреннего цикла for нужно перенести строку. Исправим.
Листинг 2.

#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0;i<N;i++){
        for (int j=0; j<M; j++)
            printf("%d\t",arr[i][j]);
        printf("\n");
    }
return (0);
}
Вот так уже лучше. Так, наша программа будет выводить все так, как нужно. Вот, посмотрите на следующую картинку.
Рис.1. Результат работы программы.

А теперь поговорим о красоте. Этот вот дополнительный перенос строки , он как шило в заднице. Из-за него одного пришлось добавить составной оператор. Некрасиво получилось. Есть ли способ этого избежать. Да, есть!
Я вам раньше не говорил, а теперь скажу. В заголовке цикла for на месте инициализации счетчика и на месте изменения счетчика, могут стоять не одна, а сразу несколько инструкций. Чтобы их туда записать нужно использовать еще один оператор, оператор последовательного выполнения – ,.  Да-да, просто запятая. 
Как это может помочь нам. Да вот как. Смотрите, мы добавляем перенос, на каждой новой итерации внешнего цикла, вот и добавим наш printf в блок изменение счетчика.
Листинг 3.
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0;i<N;i++,printf("\n"))
        for (int j=0; j<M; j++)
            printf("%d\t",arr[i][j]);
       

return (0);
}

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

Рассмотрим один показательный пример. Используем для вывода двойного массива на экран  один цикл for. Это будет выглядеть например так:
Листинг 4:
#include <stdio.h>
#include <stdlib.h>
#define N 10
#define M 8

int main(void){
    int arr[N][M];
   
    for (int i=0;i<N;i++)
        for (int j=0; j<M; j++)
            arr[i][j]=rand();
   
    for (int i=0, j=0 ;i<N; j++){
        printf("%d\t",arr[i][j]);
        if (M-j==1){
           printf("\n");
           i++;
           j=-1; 
        }
    }  

return (0);
}
А на мониторе снова никаких изменений. ))
Рис.2. Результат работы программы.
Но такие штуки лучше не проворачивать, так как это может сильно запутать код. Я не буду подробно комментировать этот цикл, попробуйте разобраться самостоятельно, как он работает. Возьмите листочек и пошагово выполните его, будто бы вы компьютер.

UPD(от 2.02.14): Я говорил же вам, что этот код может запутать любого. Я и сам попался в эту ловушку. Записанный ранее код, был ошибочен. Стараниями внимательного читателя он исправлен, и теперь работает так, как ему и подобает. =))

Я не помню, рассказывал я о именованных константах, которые использую в своих примерах в этом уроке. На всякий случай расскажу еще раз.
Для объявления именованных констант служит директива #define. Её синтаксис таков
#define имя значение

Важно! Не нужно ставить в конце точку с запятой. А так же между именем и значением знак присвоения =.

Как работает эта инструкция, рассмотрим на нашем примере. Перед тем как компилятор начнет обрабатывать программу, он пройдется по всему коду и заменит все места, где встречается N или M и вместо них подставит в код их значения: 10 и 8 соответственно.  Причем компилятору вообще по барабану, что на что мы заменяем. С учетом этой особенности, некоторые используют при написании код «Боярский язык». Ну т.е. пишут код на обычном русском языке.  Например вот так.
Листинг 5.

#include <stdio.h>
#include <stdlib.h>

#define N 10
#define M 8
#define целое int
#define присвоить =


целое main(void){
    целое arr[N][M];
   
    for (целое i присвоить 0;i<N;i++)
        for (целое j присвоить 0; j<M; j++)
            arr[i][j] присвоить rand();
   
    for (целое i присвоить 0, j присвоить 0 ;i<M,j<N;i++){
        printf("%d\t",arr[i][j]);
        if (M-i==1){
           printf("\n");
           j++;
           i присвоить 0; 
        }
    }
return (0);
}

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

На сегодня всё. Удачи вам и красивого кода. ))

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

  1. Ответы
    1. /* Вниманию автора!!!
      В Листинге 4 небольшая ошибка.
      Он должен выглядеть примерно так:
      */
      #include
      #define N 3
      #define M 4

      int main(void)
      {
      int arr[N][M] ={{1,2,3,4}, {5,6,7,8}, {9,0}};

      for (int i=0, j=0 ; j < M, i < N; j++)
      {
      printf("%d\t",arr[i][j]);

      if (M-j==1)
      {
      printf("\n");
      i++;
      j=-1;
      }//if
      } //for

      return (0);
      }//main

      /*
      Причём, после нескольких экспериментов, оказалось, что во втором блоке внутри заголовка цикла (на месте условия) цикл сверяется только с последним условием и не важно какая ерунда написана перед ним:
      for (int i = 0, j = 0; j < M, i > N, i == 0, i < N; j++). Отразите это в уроке, а если я где-то не прав укажите мне на ошибку.
      Уроки просто супер!!! Спасибо.
      */

      Удалить
    2. Дмитрий, огромное вам спасибо, за указанный недочет. Сейчас же всё исправлю.=)))
      И да, с условием вы всё верно подметили. )

      Удалить
    3. И небольшая просьба. Можете посоветовать какие-то конкретные учебники по С, С++, в которых всё также доходчиво и подробно расписано как у Вас, желательно чтобы там были примеры и задачки.
      Просто идти в книжно-компьютерный магазин и выбирать НЕТ ВОЗМОЖНОСТИ, могу только заказать через интернет, но тогда покупаешь по сути кота в мешке т.к. сайты не дают возможность полистать учебники.
      А если ждать Ваши новые уроки, то программистом я стану лет так через 20 :)

      Удалить
    4. Если не через 30. =)
      Я не знаю таких книг. И не ищите их, надо же развиваться и переходить к более серьезной литературе. И кстати, может вам попрактиковаться в решении олимпиадных задачек?
      У вас уже достаточно знаний, попробуйте-ка посмотреть следующий сайт http://acmp.ru/

      Удалить
  2. Спасибо, KaDeaT. Не подскажешь ли ты почему у меня программы на си работают, а вот стоит даже самую элементарную отправить на acmp.ru (их задачу самую первую,например) так ругает и все, никак не принимает

    ОтветитьУдалить
    Ответы
    1. Скорее всего, это несоблюдение формата входных и выходных данных. Вы все время пишите "Vvedite N" и прочее. Там всего этого не должно быть. Только то, что требуется. Ну либо вы допускаете какие-то ошибки. Покажите ваш код, который вы отправляете и что вам отвечает система. )

      Удалить
    2. =) сегодня принял ту же задачу, которую не принял вечером

      Удалить
  3. Хорошо, что еще есть уроки. Я всегда хотел начать писать, но не знал, с чего начать. За месяца два по твоим урокам(раньше я никакие уроки не понимал, писал код, но не осмыслял его) я научился делать более-менее интересные вещи. Написал игру "угадай число", прикрутил туда счетчики очков и режимы сложности. Теперь пытаюсь сделать крестики нолики, продумываю AI, все благодаря тебе, ты дал мне нужный толчок для развития, а дальше я сам уже начну разбираться. Спасибо.

    ОтветитьУдалить
    Ответы
    1. Спасибо, за приятный отзыв. )

      Удалить
    2. Прошло 3 года. Хочу сказать, что я уже имею в своем портфолио программу для администрирования групп через VK Api (C#), эмулятор старенькой восьмибитной консоли (C + OpenGL), пару утилит для Linux (C), программы автоматизации для развертывания веб-серверов (Python), парочку веб-приложений на RubyOnRails, расширения для ВК (JS) и несколько игр (Unity + C#). Вроде это не все, есть много мелких программ и скриптов.

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

      Удалить
    3. И вам спасибо большое, что не забываете!)

      Удалить
  4. Анонимный31 июля 2014 г., 10:46

    Очень благодарен вам за ваши уроки. Действительно прям с нуля можно. Всю неделю бился над создание программы по решению квадратных уравнений
    #include
    #include

    int main()
    {
    int a,b,c;
    double f,g,d,s;
    scanf ("%d", &a);
    scanf ("%d", &b);
    scanf ("%d", &c);
    d=pow(b,2)-4*a*c;
    s=sqrt(d);
    f=(-b+s)/(2*a);
    g=(-b-s)/(2*a);
    if (f==g) { printf ("odin coren %f\n", f);}
    else {
    printf ("%f\n",f);
    printf ("%f\n",g);}
    return 0;
    }
    всё благодаря вам. Только у меня был codeblocks

    ОтветитьУдалить
  5. Здравствуйте!
    Огромное спасибо за уроки, без них было бы непросто начать изучение такого непривычно низкоуровневого языка как С.
    Пользуясь случаем, сообщаю, что на площадке pm-pu.ru вижу только вопросительные знаки, перебор кодировок ничего не дал. С другими вашими доменами такой проблемы нет. openSUSE 13.1-13.2 Linux x64.

    ОтветитьУдалить
    Ответы
    1. Добрый день. Я вроде поправлял кодировку. Проверьте, пожалуйста, сейчас всё ещё вопросы?

      Удалить
    2. Теперь всё читаемо, спасибо!

      Удалить

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