HTML Diff
1 added 1 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#статьи</a></p>
1 <p><a>#статьи</a></p>
2 <ul><li>11 янв 2024</li>
2 <ul><li>11 янв 2024</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Дэвид Малан показывает, как писать на C программы с циклами, условиями и пользовательскими функциями, а также рассказывает про ошибки переполнения.</p>
4 </ul><p>Дэвид Малан показывает, как писать на C программы с циклами, условиями и пользовательскими функциями, а также рассказывает про ошибки переполнения.</p>
5 <p>Фото: LordHenriVoton / Getty Images</p>
5 <p>Фото: LordHenriVoton / Getty Images</p>
6 <p>Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.</p>
6 <p>Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.</p>
7 <p>CS50 (Computer Science 50) - легендарный курс по информатике от Гарвардского университета. Когда заходит разговор о вкатывании в программирование, опытные разработчики чаще всего советуют именно его в качестве источника базовых знаний. В нём последовательно разбираются логика работы компьютера, простые алгоритмы и основы программирования в визуальной среде Scratch, массивы, устройство и работа памяти, структуры данных, основы языка C (об этом сегодня), Python, SQL, HTML, CSS, JavaScript, Flask и много другое.</p>
7 <p>CS50 (Computer Science 50) - легендарный курс по информатике от Гарвардского университета. Когда заходит разговор о вкатывании в программирование, опытные разработчики чаще всего советуют именно его в качестве источника базовых знаний. В нём последовательно разбираются логика работы компьютера, простые алгоритмы и основы программирования в визуальной среде Scratch, массивы, устройство и работа памяти, структуры данных, основы языка C (об этом сегодня), Python, SQL, HTML, CSS, JavaScript, Flask и много другое.</p>
8 <p><strong>Зачем смотреть/читать CS50</strong>: по окончании курса вы будете знать, как работает компьютер на уровне процессора и ОЗУ, освоите универсальные принципы программирования (то есть без привязки к конкретному языку), научитесь понимать и читать код, написанный на разных языках.</p>
8 <p><strong>Зачем смотреть/читать CS50</strong>: по окончании курса вы будете знать, как работает компьютер на уровне процессора и ОЗУ, освоите универсальные принципы программирования (то есть без привязки к конкретному языку), научитесь понимать и читать код, написанный на разных языках.</p>
9 <p>У нас уже вышло несколько статей на основе уроков CS50:</p>
9 <p>У нас уже вышло несколько статей на основе уроков CS50:</p>
10 <ul><li><a>Основные понятия информатики. Лекция 0.1</a></li>
10 <ul><li><a>Основные понятия информатики. Лекция 0.1</a></li>
11 <li><a>Программирование на Scratch. Лекция 0.2</a></li>
11 <li><a>Программирование на Scratch. Лекция 0.2</a></li>
12 <li><a>Начинаем изучать язык программирования С. Лекция 1.1</a></li>
12 <li><a>Начинаем изучать язык программирования С. Лекция 1.1</a></li>
13 </ul><p><strong>Пишем первые программы на C. Лекция 1.2</strong><strong>←</strong>вы находитесь здесь.</p>
13 </ul><p><strong>Пишем первые программы на C. Лекция 1.2</strong><strong>←</strong>вы находитесь здесь.</p>
14 <p>CS50 - это самый популярный курс в Гарвардском университете и самый посещаемый массовый открытый онлайн-курс на edX. Все материалы курса<a>доступны бесплатно</a>(в том числе и практические задания), но, если заплатить, можно получить сертификат и <a>дополнительные плюшки</a>.</p>
14 <p>CS50 - это самый популярный курс в Гарвардском университете и самый посещаемый массовый открытый онлайн-курс на edX. Все материалы курса<a>доступны бесплатно</a>(в том числе и практические задания), но, если заплатить, можно получить сертификат и <a>дополнительные плюшки</a>.</p>
15 <p>Мы перевели видеолекции в текстовый формат, снабдили их иллюстрациями, кое-где дополнили объяснения и выкладываем в открытый доступ. Оригинальный курс доступен по лицензии<a>Attribution-NonCommercial-ShareAlike 4.0 International</a>(CC BY-NC-SA 4.0) - его можно дорабатывать и распространять бесплатно, но только под исходной лицензией. Так что этот цикл материалов вы также сможете использовать в своей работе или общественной деятельности совершенно свободно и бесплатно в рамках той же лицензии.</p>
15 <p>Мы перевели видеолекции в текстовый формат, снабдили их иллюстрациями, кое-где дополнили объяснения и выкладываем в открытый доступ. Оригинальный курс доступен по лицензии<a>Attribution-NonCommercial-ShareAlike 4.0 International</a>(CC BY-NC-SA 4.0) - его можно дорабатывать и распространять бесплатно, но только под исходной лицензией. Так что этот цикл материалов вы также сможете использовать в своей работе или общественной деятельности совершенно свободно и бесплатно в рамках той же лицензии.</p>
16 <p>Каждая статья из цикла CS50 состоит из следующих материалов:</p>
16 <p>Каждая статья из цикла CS50 состоит из следующих материалов:</p>
17 <ul><li>текстовый перевод видео (иногда - половины видео, если тема обширная);</li>
17 <ul><li>текстовый перевод видео (иногда - половины видео, если тема обширная);</li>
18 <li>ссылка на оригинальное видео на английском языке;</li>
18 <li>ссылка на оригинальное видео на английском языке;</li>
19 <li>схемы и пояснения;</li>
19 <li>схемы и пояснения;</li>
20 <li>ссылки на более подробные материалы по теме статьи;</li>
20 <li>ссылки на более подробные материалы по теме статьи;</li>
21 <li>практические задания.</li>
21 <li>практические задания.</li>
22 </ul><p><strong>Содержание этого занятия</strong></p>
22 </ul><p><strong>Содержание этого занятия</strong></p>
23 <ul><li><a>Пишем код на C и создаём калькулятор</a></li>
23 <ul><li><a>Пишем код на C и создаём калькулятор</a></li>
24 <li><a>Стиль программного кода</a></li>
24 <li><a>Стиль программного кода</a></li>
25 <li><a>Целочисленное переполнение</a></li>
25 <li><a>Целочисленное переполнение</a></li>
26 <li><a>Условные выражения</a></li>
26 <li><a>Условные выражения</a></li>
27 <li><a>Циклы while</a></li>
27 <li><a>Циклы while</a></li>
28 <li><a>Циклы for</a></li>
28 <li><a>Циклы for</a></li>
29 <li><a>Создание пользовательских функций</a></li>
29 <li><a>Создание пользовательских функций</a></li>
30 <li><a>Создаём игру на C</a></li>
30 <li><a>Создаём игру на C</a></li>
31 <li><a>Переполнение чисел с плавающей точкой</a></li>
31 <li><a>Переполнение чисел с плавающей точкой</a></li>
32 <li><a>Преобразование типов данных</a></li>
32 <li><a>Преобразование типов данных</a></li>
33 <li><a>Возвращаемся к проблеме переполнения в реальной жизни</a></li>
33 <li><a>Возвращаемся к проблеме переполнения в реальной жизни</a></li>
34 </ul><p>На прошлом уроке мы познакомились с синтаксисом и основными концепциями языка C. Сейчас приступим к практике и посмотрим, как на нём писать программы. Откроем VS Code и с помощью командной строки создадим файл calculator.c:</p>
34 </ul><p>На прошлом уроке мы познакомились с синтаксисом и основными концепциями языка C. Сейчас приступим к практике и посмотрим, как на нём писать программы. Откроем VS Code и с помощью командной строки создадим файл calculator.c:</p>
35 code calculator.c<p>Он автоматически откроется в среде разработки. Начнём с подключения библиотек cs50.h и stdio.h:</p>
35 code calculator.c<p>Он автоматически откроется в среде разработки. Начнём с подключения библиотек cs50.h и stdio.h:</p>
36 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { }<p>Теперь создадим простой калькулятор на основе тех операторов, которые мы изучали на <a>прошлом занятии</a>.</p>
36 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { }<p>Теперь создадим простой калькулятор на основе тех операторов, которые мы изучали на <a>прошлом занятии</a>.</p>
37 <p>Создадим переменные x и y и выведем их сумму x + y:</p>
37 <p>Создадим переменные x и y и выведем их сумму x + y:</p>
38 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int x = get_int("x: "); int y = get_int("y: "); printf("%i\n", x + y); }<p>Функция printf() в качестве первого аргумента принимает форматную строку "%i\n", определяющую формат вывода, а в качестве второго - выражение, которое выведется на экран.</p>
38 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int x = get_int("x: "); int y = get_int("y: "); printf("%i\n", x + y); }<p>Функция printf() в качестве первого аргумента принимает форматную строку "%i\n", определяющую формат вывода, а в качестве второго - выражение, которое выведется на экран.</p>
39 <p>Мы получили простой калькулятор, который умеет складывать два числа. Скомпилируем его - никаких сообщений об ошибках не поступает. Запустим калькулятор и сложим 1 + 1.</p>
39 <p>Мы получили простой калькулятор, который умеет складывать два числа. Скомпилируем его - никаких сообщений об ошибках не поступает. Запустим калькулятор и сложим 1 + 1.</p>
40 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Как видим, всё работает.</p>
40 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Как видим, всё работает.</p>
41 <p>Немного изменим код калькулятора - добавим переменную z:</p>
41 <p>Немного изменим код калькулятора - добавим переменную z:</p>
42 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int x = get_int("x: "); int y = get_int("y: "); int z = x + y; printf("%i\n", z); }<p>Если мы запустим калькулятор и сложим 1 + 1, то получим тот же результат. Он будет работать и для других значений x и y. Код с переменной z подходит в тех случаях, когда мы планируем дополнять его и будем использовать результат сложения повторно.</p>
42 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int x = get_int("x: "); int y = get_int("y: "); int z = x + y; printf("%i\n", z); }<p>Если мы запустим калькулятор и сложим 1 + 1, то получим тот же результат. Он будет работать и для других значений x и y. Код с переменной z подходит в тех случаях, когда мы планируем дополнять его и будем использовать результат сложения повторно.</p>
43 <p>Теперь поговорим о стиле нашего кода. Имена x и y в нашем примере отлично подходят для переменных, потому что часто используются в математике. Однако если хочется добавить ясности, то можно изменить названия на first_number и second_number:</p>
43 <p>Теперь поговорим о стиле нашего кода. Имена x и y в нашем примере отлично подходят для переменных, потому что часто используются в математике. Однако если хочется добавить ясности, то можно изменить названия на first_number и second_number:</p>
44 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int first_number = get_int("x: "); int second_number = get_int("y: "); printf("%i\n", first_number + second_number); }<p>Теперь добавим в программу комментарии. Они помогут нам вспомнить, что делает код, когда мы вернёмся к нему и что-нибудь забудем. Строки комментариев начинаются с двух косых черт:</p>
44 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int first_number = get_int("x: "); int second_number = get_int("y: "); printf("%i\n", first_number + second_number); }<p>Теперь добавим в программу комментарии. Они помогут нам вспомнить, что делает код, когда мы вернёмся к нему и что-нибудь забудем. Строки комментариев начинаются с двух косых черт:</p>
45 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_int("x: "); // Запрашиваем у пользователя y int second_number = get_int("y: "); // Выполняем сложение printf("%i\n", first_number + second_number); }<p>Комментарии не влияют на работу программы - это просто заметки для вас и других разработчиков. В такой небольшой программе от них мало пользы. Но, по мере того как количество кода будет увеличиваться, заметки не дадут нам забыть, что он делает.</p>
45 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_int("x: "); // Запрашиваем у пользователя y int second_number = get_int("y: "); // Выполняем сложение printf("%i\n", first_number + second_number); }<p>Комментарии не влияют на работу программы - это просто заметки для вас и других разработчиков. В такой небольшой программе от них мало пользы. Но, по мере того как количество кода будет увеличиваться, заметки не дадут нам забыть, что он делает.</p>
46 <p><strong>Совет</strong>: большинство операционных систем (как минимум ОС семейства Linux, Windows и macOS), в которых ведётся разработка, поддерживают автодополнение кода. Вы можете нажать стрелку вверх, чтобы увидеть всю историю команд и выбрать нужную, вместо того чтобы набирать несколько раз одно и то же. Это заметно ускоряет работу.</p>
46 <p><strong>Совет</strong>: большинство операционных систем (как минимум ОС семейства Linux, Windows и macOS), в которых ведётся разработка, поддерживают автодополнение кода. Вы можете нажать стрелку вверх, чтобы увидеть всю историю команд и выбрать нужную, вместо того чтобы набирать несколько раз одно и то же. Это заметно ускоряет работу.</p>
47 <p>Пришло время поработать с большими числами. Например, пусть x и y равны 1 000 000 000. Введём эти числа в командную строку и посмотрим результат:</p>
47 <p>Пришло время поработать с большими числами. Например, пусть x и y равны 1 000 000 000. Введём эти числа в командную строку и посмотрим результат:</p>
48 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Как видим, всё получилось.</p>
48 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Как видим, всё получилось.</p>
49 <p>А теперь пусть x и y будут равны 2 000 000 000:</p>
49 <p>А теперь пусть x и y будут равны 2 000 000 000:</p>
50 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>А здесь, кажется, что-то пошло не так… Произошло переполнение!</p>
50 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>А здесь, кажется, что-то пошло не так… Произошло переполнение!</p>
51 <p>Теперь мы понимаем, что тестирование со сложением двух единиц было ненадёжным решением. В последнем примере в компьютере закончилось место для хранения битов. Это случается с типами данных<strong>string</strong>,<strong>int</strong>,<strong>float</strong>,<strong>char</strong>- все они используют конечное число битов для представления чисел и символов.</p>
51 <p>Теперь мы понимаем, что тестирование со сложением двух единиц было ненадёжным решением. В последнем примере в компьютере закончилось место для хранения битов. Это случается с типами данных<strong>string</strong>,<strong>int</strong>,<strong>float</strong>,<strong>char</strong>- все они используют конечное число битов для представления чисел и символов.</p>
52 <p>Для хранения целого числа используется 32 бита. С их помощью можно представить число 2³², что примерно равно 4 000 000 000. Результат должен умещаться в 32-битном целом числе. Кажется, что нам этого хватит:</p>
52 <p>Для хранения целого числа используется 32 бита. С их помощью можно представить число 2³², что примерно равно 4 000 000 000. Результат должен умещаться в 32-битном целом числе. Кажется, что нам этого хватит:</p>
53 <p>2 000 000 000 + 2 000 000 000 = 4 000 000 000</p>
53 <p>2 000 000 000 + 2 000 000 000 = 4 000 000 000</p>
54 <p>Но дело в том, что компьютеры, кроме положительных, поддерживают отрицательные числа, то есть хранят числа в диапазоне от -2 000 000 000 до 2 000 000 000. Поэтому мы получаем странный вывод в сложении.</p>
54 <p>Но дело в том, что компьютеры, кроме положительных, поддерживают отрицательные числа, то есть хранят числа в диапазоне от -2 000 000 000 до 2 000 000 000. Поэтому мы получаем странный вывод в сложении.</p>
55 <p>Чтобы решить проблему переполнения, будем использовать тип данных<em>long</em>. Поменяем в программе тип переменных x и y с int на long.</p>
55 <p>Чтобы решить проблему переполнения, будем использовать тип данных<em>long</em>. Поменяем в программе тип переменных x и y с int на long.</p>
56 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_long("x: "); // Запрашиваем у пользователя y int second_number = get_long("y: "); // Выполняем сложение printf("%i\n", first_number + second_number); }<p>Запустим калькулятор:</p>
56 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_long("x: "); // Запрашиваем у пользователя y int second_number = get_long("y: "); // Выполняем сложение printf("%i\n", first_number + second_number); }<p>Запустим калькулятор:</p>
57 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Теперь всё получилось!</p>
57 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Теперь всё получилось!</p>
58 <p>Но, если мы будем использовать тип long, числа в нём всё равно будут конечными. И это может стать проблемой, если мы хотим сделать калькулятор работоспособным для любых входных данных. Мы ещё вернёмся к этому вопросу позже.</p>
58 <p>Но, если мы будем использовать тип long, числа в нём всё равно будут конечными. И это может стать проблемой, если мы хотим сделать калькулятор работоспособным для любых входных данных. Мы ещё вернёмся к этому вопросу позже.</p>
59 <p>А сейчас рассмотрим условные выражения. В C они записываются так:</p>
59 <p>А сейчас рассмотрим условные выражения. В C они записываются так:</p>
60 if (x &lt; y) { printf("x is less than y\n") }<p>Добавим блок<strong>else</strong>, чтобы описать действия при несоблюдении условия:</p>
60 if (x &lt; y) { printf("x is less than y\n") }<p>Добавим блок<strong>else</strong>, чтобы описать действия при несоблюдении условия:</p>
61 if (x &lt; y) { printf("x is less than y\n") } else { printf("x is not less than y\n") }<p>Одна из особенностей синтаксиса языка C - круглые скобки используются как для записи функции, так и для записи логических выражений. А ещё, в нём нет необходимости использовать фигурные скобки, если в них заключена всего одна строка с отступом. Но я рекомендую использовать их всегда - так код будет понятнее.</p>
61 if (x &lt; y) { printf("x is less than y\n") } else { printf("x is not less than y\n") }<p>Одна из особенностей синтаксиса языка C - круглые скобки используются как для записи функции, так и для записи логических выражений. А ещё, в нём нет необходимости использовать фигурные скобки, если в них заключена всего одна строка с отступом. Но я рекомендую использовать их всегда - так код будет понятнее.</p>
62 <p>Если мы хотим написать, что будет при условии x == y, то код будет выглядеть так:</p>
62 <p>Если мы хотим написать, что будет при условии x == y, то код будет выглядеть так:</p>
63 if (x &lt; y) { printf("x is less than y\n") } else { printf("x is not less than y\n") } else if (x == y) { printf("x is equal to y\n") }<p>Последнее условие мы можем убрать, ведь если не выполняются первые два, то x может быть равен только y.</p>
63 if (x &lt; y) { printf("x is less than y\n") } else { printf("x is not less than y\n") } else if (x == y) { printf("x is equal to y\n") }<p>Последнее условие мы можем убрать, ведь если не выполняются первые два, то x может быть равен только y.</p>
64 <p>Оптимизируем код:</p>
64 <p>Оптимизируем код:</p>
65 if (x &lt; y) { printf("x is less than y\n") } else if (x &gt; y) { printf("x is not less than y\n") } else { printf("x is equal to y\n") }<p>Убрав третье условие, мы уменьшим время работы программы, так как ей не придётся проверять условие. Не забывайте о таких вещах, когда будете писать код: он должен быть не только правильным, но и быстрым.</p>
65 if (x &lt; y) { printf("x is less than y\n") } else if (x &gt; y) { printf("x is not less than y\n") } else { printf("x is equal to y\n") }<p>Убрав третье условие, мы уменьшим время работы программы, так как ей не придётся проверять условие. Не забывайте о таких вещах, когда будете писать код: он должен быть не только правильным, но и быстрым.</p>
66 <p>Перейдём к решению реальной проблемы: спросим пользователя, сколько баллов он потерял при решении первого набора задач<strong>CS50</strong>. Я сам потерял там пару баллов в 1996 году. Сравним потери пользователя с моими:</p>
66 <p>Перейдём к решению реальной проблемы: спросим пользователя, сколько баллов он потерял при решении первого набора задач<strong>CS50</strong>. Я сам потерял там пару баллов в 1996 году. Сравним потери пользователя с моими:</p>
67 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя баллы int points = get_int("Сколько баллов вы потеряли?"); if (points &lt; 2) { printf("Вы потеряли меньше баллов, чем я.\n"); } else if (points &gt; 2) { printf("Вы потеряли больше баллов, чем я.\n"); } else { printf("Вы потеряли столько же баллов, сколько я.\n"); } }<p>Теперь мы знаем, как использовать условные выражения, однако наш код всё ещё избыточен. Я жёстко запрограммировал число 2 - потерянное мной количество очков. Если мне нужно будет заменить 2 на 3, то я легко это сделаю. Но предположим, что количество баллов сравнивается не в одном, а в двух, трёх или пяти местах кода. Тогда при замене числа мы наверняка где-нибудь ошибёмся.</p>
67 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя баллы int points = get_int("Сколько баллов вы потеряли?"); if (points &lt; 2) { printf("Вы потеряли меньше баллов, чем я.\n"); } else if (points &gt; 2) { printf("Вы потеряли больше баллов, чем я.\n"); } else { printf("Вы потеряли столько же баллов, сколько я.\n"); } }<p>Теперь мы знаем, как использовать условные выражения, однако наш код всё ещё избыточен. Я жёстко запрограммировал число 2 - потерянное мной количество очков. Если мне нужно будет заменить 2 на 3, то я легко это сделаю. Но предположим, что количество баллов сравнивается не в одном, а в двух, трёх или пяти местах кода. Тогда при замене числа мы наверняка где-нибудь ошибёмся.</p>
68 <p>Вместо того чтобы переписывать число в разных местах программы, в самом начале создадим переменную, которая будет хранить это значение. А чтобы случайно не присвоить что-то по ошибке, сделаем её константой. Константа говорит компилятору, что ниже в коде её значение изменить нельзя:</p>
68 <p>Вместо того чтобы переписывать число в разных местах программы, в самом начале создадим переменную, которая будет хранить это значение. А чтобы случайно не присвоить что-то по ошибке, сделаем её константой. Константа говорит компилятору, что ниже в коде её значение изменить нельзя:</p>
69 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { const int MINE = 2; // Запрашиваем у пользователя баллы int points = get_int("Сколько баллов вы потеряли?"); if (points &lt; MINE) { printf("Вы потеряли меньше баллов, чем я.\n"); } else if (points &gt; MINE) { printf("Вы потеряли больше баллов, чем я.\n"); } else { printf("Вы потеряли столько же баллов, сколько я.\n"); } }<p>В C и других языках существует правило: имя константы всегда пишется заглавными буквами. Это упрощает чтение кода.</p>
69 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { const int MINE = 2; // Запрашиваем у пользователя баллы int points = get_int("Сколько баллов вы потеряли?"); if (points &lt; MINE) { printf("Вы потеряли меньше баллов, чем я.\n"); } else if (points &gt; MINE) { printf("Вы потеряли больше баллов, чем я.\n"); } else { printf("Вы потеряли столько же баллов, сколько я.\n"); } }<p>В C и других языках существует правило: имя константы всегда пишется заглавными буквами. Это упрощает чтение кода.</p>
70 <p>А теперь напишем программу, которая будет проверять, чётное или нечётное число ввёл пользователь. Используем для этого синтаксис и приёмы, которые мы уже изучили.</p>
70 <p>А теперь напишем программу, которая будет проверять, чётное или нечётное число ввёл пользователь. Используем для этого синтаксис и приёмы, которые мы уже изучили.</p>
71 <p>Из математики мы знаем, что число считается чётным, если его деление на два даёт остаток 0, а нечётным - если остаток равен 1. Воспользуемся оператором %, который при делении числителя на знаменатель возвращает не частное, а остаток от деления.</p>
71 <p>Из математики мы знаем, что число считается чётным, если его деление на два даёт остаток 0, а нечётным - если остаток равен 1. Воспользуемся оператором %, который при делении числителя на знаменатель возвращает не частное, а остаток от деления.</p>
72 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int n = get_int("n: "); // Если n чётное if (n % 2 == 0) { printf("чётное\n"); } // Если n нечётное else { printf("нечётное\n"); } }<p>Обратите внимание на ==. В языке C это знак равенства, а = - знак присваивания. Возможно, вам это решение покажется странным, но оно было принято давно и нам приходится с ним жить. В некоторых языках, таких как JavaScript, используется ===.</p>
72 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int n = get_int("n: "); // Если n чётное if (n % 2 == 0) { printf("чётное\n"); } // Если n нечётное else { printf("нечётное\n"); } }<p>Обратите внимание на ==. В языке C это знак равенства, а = - знак присваивания. Возможно, вам это решение покажется странным, но оно было принято давно и нам приходится с ним жить. В некоторых языках, таких как JavaScript, используется ===.</p>
73 <p>Теперь давайте напишем программу, которая спрашивает у пользователя, согласен ли он с каким-нибудь выражением. В качестве ответа будет приниматься один символ, например y или n. Другие символы игнорируются.</p>
73 <p>Теперь давайте напишем программу, которая спрашивает у пользователя, согласен ли он с каким-нибудь выражением. В качестве ответа будет приниматься один символ, например y или n. Другие символы игнорируются.</p>
74 <p>Программа выглядит так:</p>
74 <p>Программа выглядит так:</p>
75 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем согласие пользователя char c = get_char("Вы согласны?"); // Проверяем, согласен ли он if (c == 'y') { printf("Согласен\n"); } if (c == 'n') { printf("Не согласен\n"); } }<p>А что делать, если пользователь решит ответить Y или N? Учтём это в коде и добавим в наши условия логическое ИЛИ. В языке C это две вертикальные полосы <strong>||</strong>.</p>
75 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем согласие пользователя char c = get_char("Вы согласны?"); // Проверяем, согласен ли он if (c == 'y') { printf("Согласен\n"); } if (c == 'n') { printf("Не согласен\n"); } }<p>А что делать, если пользователь решит ответить Y или N? Учтём это в коде и добавим в наши условия логическое ИЛИ. В языке C это две вертикальные полосы <strong>||</strong>.</p>
76 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем согласие пользователя char c = get_char("Вы согласны?"); // Проверяем, согласен ли он if (c == 'y' || c == 'Y') { printf("Согласен\n"); } if (c == 'n' || c =='N') { printf("Не согласен\n"); } }<p>Есть альтернатива этому решению: можно преобразовывать вводимые символы в строчные буквы, чтобы не использовать ||. Но об этом мы поговорим на других лекциях.</p>
76 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем согласие пользователя char c = get_char("Вы согласны?"); // Проверяем, согласен ли он if (c == 'y' || c == 'Y') { printf("Согласен\n"); } if (c == 'n' || c =='N') { printf("Не согласен\n"); } }<p>Есть альтернатива этому решению: можно преобразовывать вводимые символы в строчные буквы, чтобы не использовать ||. Но об этом мы поговорим на других лекциях.</p>
77 <p>Вы видите, что в условии я использую одинарные кавычки, хотя в других частях программы мы заключали в двойные всё, что выглядит как текст. Но обратите внимание, что мы используем тип данных char, а не string. В отличие от строк, в переменных такого типа используются одинарные кавычки, а не двойные.</p>
77 <p>Вы видите, что в условии я использую одинарные кавычки, хотя в других частях программы мы заключали в двойные всё, что выглядит как текст. Но обратите внимание, что мы используем тип данных char, а не string. В отличие от строк, в переменных такого типа используются одинарные кавычки, а не двойные.</p>
78 <p>Теперь посмотрим, как в языке C организовать циклы. Вспомним<a>позапрошлый урок</a>, где мы учили кошку мяукать, и напишем программу:</p>
78 <p>Теперь посмотрим, как в языке C организовать циклы. Вспомним<a>позапрошлый урок</a>, где мы учили кошку мяукать, и напишем программу:</p>
79 #include &lt;stdio.h&gt; int main(void) { printf("мяу\n"); printf("мяу\n"); printf("мяу\n"); }<p>Сейчас она написана не совсем корректно - в ней есть повторения. Чтобы их убрать, используем циклы. Вот один из самых простых:</p>
79 #include &lt;stdio.h&gt; int main(void) { printf("мяу\n"); printf("мяу\n"); printf("мяу\n"); }<p>Сейчас она написана не совсем корректно - в ней есть повторения. Чтобы их убрать, используем циклы. Вот один из самых простых:</p>
80 while (true) { printf("мяу\n"); }<p>В скобках стоит логическое выражение - условие выполнения цикла. Он будет работать до тех пор, пока условие истинно, а когда станет ложным - прервётся. Если бы мы хотели, чтобы цикл выполнялся вечно, то могли бы поставить заведомо истинное условие (1 == 1), (2 &gt; 1) и так далее. В языке C в таких случаях используют логические значения true и false.</p>
80 while (true) { printf("мяу\n"); }<p>В скобках стоит логическое выражение - условие выполнения цикла. Он будет работать до тех пор, пока условие истинно, а когда станет ложным - прервётся. Если бы мы хотели, чтобы цикл выполнялся вечно, то могли бы поставить заведомо истинное условие (1 == 1), (2 &gt; 1) и так далее. В языке C в таких случаях используют логические значения true и false.</p>
81 <p>Чтобы организовать корректную работу цикла, добавим в него счётчик. В C и многих других языках существует соглашение: для счётчика используют переменную i c первоначальным значением 0.</p>
81 <p>Чтобы организовать корректную работу цикла, добавим в него счётчик. В C и многих других языках существует соглашение: для счётчика используют переменную i c первоначальным значением 0.</p>
82 int i = 0; while (i &lt; 3) { printf("мяу\n"); i = i + 1; }<p>У нас есть возможность усовершенствовать эту программу. Применим то, что называется синтаксическим сахаром:</p>
82 int i = 0; while (i &lt; 3) { printf("мяу\n"); i = i + 1; }<p>У нас есть возможность усовершенствовать эту программу. Применим то, что называется синтаксическим сахаром:</p>
83 int i = 0; while (i &lt; 3) { printf("мяу\n"); i = i++; }<p>Выражение i = i + 1 мы поменяли на i = i++. Такие изменения не влияют на работу программы, но делают код короче.</p>
83 int i = 0; while (i &lt; 3) { printf("мяу\n"); i = i++; }<p>Выражение i = i + 1 мы поменяли на i = i++. Такие изменения не влияют на работу программы, но делают код короче.</p>
84 <p>Разберём алгоритм программы:</p>
84 <p>Разберём алгоритм программы:</p>
85 <ul><li>Мы начинаем с инициализации переменной i.</li>
85 <ul><li>Мы начинаем с инициализации переменной i.</li>
86 <li>Затем компьютер проверяет условие i &lt; 3. Если оно верно, то выполняется всё, что заключено в фигурные скобки, а именно - программа печатает мяу.</li>
86 <li>Затем компьютер проверяет условие i &lt; 3. Если оно верно, то выполняется всё, что заключено в фигурные скобки, а именно - программа печатает мяу.</li>
87 <li>Значение i увеличивается на единицу.</li>
87 <li>Значение i увеличивается на единицу.</li>
88 <li>Теперь компьютер перепроверяет условие, чтобы убедиться, что i не стала больше 3. Если это не так, он выполняет всё, что находится в блоке.</li>
88 <li>Теперь компьютер перепроверяет условие, чтобы убедиться, что i не стала больше 3. Если это не так, он выполняет всё, что находится в блоке.</li>
89 <li>После трёх повторений условие станет ложным и выполнение цикла заканчивается.</li>
89 <li>После трёх повторений условие станет ложным и выполнение цикла заканчивается.</li>
90 <li>Компьютер переходит к командам, следующим за циклом.</li>
90 <li>Компьютер переходит к командам, следующим за циклом.</li>
91 </ul><p>Конечно, вы можете начать цикл не с 0, а, например, с 1. Тогда, чтобы тело цикла выполнилось три раза, придётся изменить условие:</p>
91 </ul><p>Конечно, вы можете начать цикл не с 0, а, например, с 1. Тогда, чтобы тело цикла выполнилось три раза, придётся изменить условие:</p>
92 int i = 0; while (i &lt;= 3) { printf("мяу\n"); i = i++; }<p>Обратите внимание, как записывается знак "меньше или равно": сначала знак "меньше", а потом знак равенства без пробелов между ними: &lt;=.</p>
92 int i = 0; while (i &lt;= 3) { printf("мяу\n"); i = i++; }<p>Обратите внимание, как записывается знак "меньше или равно": сначала знак "меньше", а потом знак равенства без пробелов между ними: &lt;=.</p>
93 <p>Мы могли бы установить i равным 2, 10 или другому числу и соответствующим образом изменить условие. Но лучше придерживаться основ - начинать отсчёт с нуля и увеличивать счётчик до нужного значения.</p>
93 <p>Мы могли бы установить i равным 2, 10 или другому числу и соответствующим образом изменить условие. Но лучше придерживаться основ - начинать отсчёт с нуля и увеличивать счётчик до нужного значения.</p>
94 <p>Возможно, вы захотите вести обратный отсчёт. Установите i = 3 и уменьшайте счётчик до тех пор, пока он не станет равен 0, например:</p>
94 <p>Возможно, вы захотите вести обратный отсчёт. Установите i = 3 и уменьшайте счётчик до тех пор, пока он не станет равен 0, например:</p>
95 int i = 3; while (i &gt; 0) { printf("мяу\n"); i = i--; }<p>Эту задачу можно решить с помощью цикла<strong>for</strong>.</p>
95 int i = 3; while (i &gt; 0) { printf("мяу\n"); i = i--; }<p>Эту задачу можно решить с помощью цикла<strong>for</strong>.</p>
96 <p><strong>for</strong>часто используется в C и других языках программирования. С ним наш мяукающий код будет выглядеть так:</p>
96 <p><strong>for</strong>часто используется в C и других языках программирования. С ним наш мяукающий код будет выглядеть так:</p>
97 for (int i = 0; i &lt; 3; i++) { printf("мяу\n"); }<p>Рассмотрим выражение в круглых скобках:</p>
97 for (int i = 0; i &lt; 3; i++) { printf("мяу\n"); }<p>Рассмотрим выражение в круглых скобках:</p>
98 <ul><li>Сначала мы инициализируем счётчик: i = 0.</li>
98 <ul><li>Сначала мы инициализируем счётчик: i = 0.</li>
99 <li>Затем инициализируется условие, которое будет проверяться каждый раз при прохождении цикла. Мы проверяем, i меньше 3 или нет.</li>
99 <li>Затем инициализируется условие, которое будет проверяться каждый раз при прохождении цикла. Мы проверяем, i меньше 3 или нет.</li>
100 <li>И последнее - это увеличение счётчика на 1.</li>
100 <li>И последнее - это увеличение счётчика на 1.</li>
101 </ul><p>В сущности, оба способа одинаковы - циклы<strong>for</strong>и <strong>while</strong>используются для одного и того же действия. Но между ними есть различия. Обратите внимание на переменную i в цикле for - она находится в круглых скобках. Это означает, что i будет существовать только в этих четырёх строках кода. В цикле while переменная i находится вне скобок, то есть существует за пределами условий цикла.</p>
101 </ul><p>В сущности, оба способа одинаковы - циклы<strong>for</strong>и <strong>while</strong>используются для одного и того же действия. Но между ними есть различия. Обратите внимание на переменную i в цикле for - она находится в круглых скобках. Это означает, что i будет существовать только в этих четырёх строках кода. В цикле while переменная i находится вне скобок, то есть существует за пределами условий цикла.</p>
102 <p>Теперь создадим нашу собственную функцию на языке C. Дадим ей название meow() ("мяу"). Теперь программа будет выглядеть так:</p>
102 <p>Теперь создадим нашу собственную функцию на языке C. Дадим ей название meow() ("мяу"). Теперь программа будет выглядеть так:</p>
103 #include &lt;stdio.h&gt; void meow(void) int main(void) { for (int i = 0; i &lt; 3; i++) { meow(); } } void meow(void) { printf("мяу\n"); }<p>Команда void meow(void) означает, что у функции нет входных данных и она ничего не возвращает. Её цель - выводить числа и строки на экран.</p>
103 #include &lt;stdio.h&gt; void meow(void) int main(void) { for (int i = 0; i &lt; 3; i++) { meow(); } } void meow(void) { printf("мяу\n"); }<p>Команда void meow(void) означает, что у функции нет входных данных и она ничего не возвращает. Её цель - выводить числа и строки на экран.</p>
104 <p>Обратите внимание, что в C объявление функции всегда ставится в начале программы. В некоторых других языках программирования, например в Python, функции можно располагать в любом месте кода.</p>
104 <p>Обратите внимание, что в C объявление функции всегда ставится в начале программы. В некоторых других языках программирования, например в Python, функции можно располагать в любом месте кода.</p>
105 <p>Мы поставили в теле цикла for вызов функции meow(). Теперь немного исправим её - пусть она мяукает несколько раз. Для этого перенесём в неё цикл for, а количество мяуканий будем передавать в качестве аргумента n. Это будет целое число, поэтому присвоим переменной n тип int.</p>
105 <p>Мы поставили в теле цикла for вызов функции meow(). Теперь немного исправим её - пусть она мяукает несколько раз. Для этого перенесём в неё цикл for, а количество мяуканий будем передавать в качестве аргумента n. Это будет целое число, поэтому присвоим переменной n тип int.</p>
106 #include &lt;stdio.h&gt; void meow(int n) int main(void) { meow(3); } // Настраиваем многократное мяукание void meow(int n) { for (int i = 0; i &lt; n; i++) { printf("мяу\n"); } }<p>В объявление функции meow() вместо пустого значения мы добавили аргумент n.</p>
106 #include &lt;stdio.h&gt; void meow(int n) int main(void) { meow(3); } // Настраиваем многократное мяукание void meow(int n) { for (int i = 0; i &lt; n; i++) { printf("мяу\n"); } }<p>В объявление функции meow() вместо пустого значения мы добавили аргумент n.</p>
107 <p>Теперь давайте создадим функцию, которая бы не только принимала аргументы, но и возвращала какое-нибудь значение. На языке C мы легко можем это сделать.</p>
107 <p>Теперь давайте создадим функцию, которая бы не только принимала аргументы, но и возвращала какое-нибудь значение. На языке C мы легко можем это сделать.</p>
108 <p>Напишем программу, рассчитывающую цену товара со скидкой. Пусть она выглядит так:</p>
108 <p>Напишем программу, рассчитывающую цену товара со скидкой. Пусть она выглядит так:</p>
109 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { float regular = get_float("Обычная цена: "); float sale = regular * .85; printf("Цена со скидкой: %.2f\n", sale); }<p>Здесь regular - первоначальная цена, а sale - цена со скидкой.</p>
109 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { float regular = get_float("Обычная цена: "); float sale = regular * .85; printf("Цена со скидкой: %.2f\n", sale); }<p>Здесь regular - первоначальная цена, а sale - цена со скидкой.</p>
110 <p>Создадим пользовательскую функцию, которая в качестве аргумента принимала бы первоначальную цену, а возвращала бы значение цены со скидкой:</p>
110 <p>Создадим пользовательскую функцию, которая в качестве аргумента принимала бы первоначальную цену, а возвращала бы значение цены со скидкой:</p>
111 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; float discount(float price) int main(void) { float regular = get_float("Обычная цена: "); float sale = discount(regular); printf("Цена со скидкой: %.2f\n", sale); } float discount(float price) { return price * .85; }<p>Все переменные мы сделали числами с плавающей точкой.</p>
111 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; float discount(float price) int main(void) { float regular = get_float("Обычная цена: "); float sale = discount(regular); printf("Цена со скидкой: %.2f\n", sale); } float discount(float price) { return price * .85; }<p>Все переменные мы сделали числами с плавающей точкой.</p>
112 - <p>Обратите внимание, что функция discount() не печатает рассчитанное значение, а возвращает его в вызывающую функцию. Для этого используется ключевое слово return.</p>
112 + <p>Обратите внимание, что функция discount() не печатает рассчитанное значние, а возвращает его в вызывающую функцию. Для этого используется ключевое слово return.</p>
113 <p>Функции могут принимать не один аргумент, а два, три и более. Введём в качестве аргумента функции discount() величину скидки. Это позволит задать любую скидку - не только 15%.</p>
113 <p>Функции могут принимать не один аргумент, а два, три и более. Введём в качестве аргумента функции discount() величину скидки. Это позволит задать любую скидку - не только 15%.</p>
114 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; float discount(float price, int percentage) int main(void) { float regular = get_float("Обычная цена: "); int percent_off = get_ing("Размер скидки: "); float sale = discount(regular, percent_off); printf("Цена со скидкой: %.2f\n", sale); } float discount(float price, int percentage) { return price * (100 - percentage) / 100; }<p>Всё получилось. Наша функция вычисляет стоимость товара со скидкой, значение которой мы можем ввести сами.</p>
114 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; float discount(float price, int percentage) int main(void) { float regular = get_float("Обычная цена: "); int percent_off = get_ing("Размер скидки: "); float sale = discount(regular, percent_off); printf("Цена со скидкой: %.2f\n", sale); } float discount(float price, int percentage) { return price * (100 - percentage) / 100; }<p>Всё получилось. Наша функция вычисляет стоимость товара со скидкой, значение которой мы можем ввести сами.</p>
115 <p>А теперь используем полученные нами знания для разработки небольшой игры. Вспомните Super Mario Bros.: там в небе за вопросительными знаками были спрятаны монеты.</p>
115 <p>А теперь используем полученные нами знания для разработки небольшой игры. Вспомните Super Mario Bros.: там в небе за вопросительными знаками были спрятаны монеты.</p>
116 Дэвид на фоне своих слайдов во время чтения курса. Здесь он объясняет правила игры Mario<em>Кадр:<a>CS50</a>/ YouTube</em><p>Мы пока не сможем создать красочный мир игры. Давайте просто выведем несколько вопросительных знаков:</p>
116 Дэвид на фоне своих слайдов во время чтения курса. Здесь он объясняет правила игры Mario<em>Кадр:<a>CS50</a>/ YouTube</em><p>Мы пока не сможем создать красочный мир игры. Давайте просто выведем несколько вопросительных знаков:</p>
117 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { printf("????\n"); }<p>Усовершенствуем программу - используем циклы<strong>for</strong>:</p>
117 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { printf("????\n"); }<p>Усовершенствуем программу - используем циклы<strong>for</strong>:</p>
118 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { for (int i = 0; i &lt; 4; i++) { printf("?"); } printf("\n"); }<p>Как видите, после цикла for мы вывели на печать знак перевода строки \n. В цикле мы его поставить не можем, так как каждый вопросительный знак будет печататься на новой строке.</p>
118 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { for (int i = 0; i &lt; 4; i++) { printf("?"); } printf("\n"); }<p>Как видите, после цикла for мы вывели на печать знак перевода строки \n. В цикле мы его поставить не можем, так как каждый вопросительный знак будет печататься на новой строке.</p>
119 <p>Усложним нашу программу - пусть она спрашивает у пользователя, сколько вопросительных знаков нужно вывести на экран. Для этого познакомимся с ещё одним видом циклов - do while. Он похож на цикл while, но проверяет условие не перед, а после своего тела.</p>
119 <p>Усложним нашу программу - пусть она спрашивает у пользователя, сколько вопросительных знаков нужно вывести на экран. Для этого познакомимся с ещё одним видом циклов - do while. Он похож на цикл while, но проверяет условие не перед, а после своего тела.</p>
120 <p>Цикл do while полезен, когда вы хотите сделать что-то независимое от условия, а само условие проверить в конце:</p>
120 <p>Цикл do while полезен, когда вы хотите сделать что-то независимое от условия, а само условие проверить в конце:</p>
121 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { for (int i = 0; i &lt; n/; i++) { printf("?"); } printf("\n"); }<p>Здесь пользователь вряд ли введёт n = 0 или n = -100, так как это не имеет смысла. А когда n будет больше или равно 1, программа выйдет из цикла и управление перейдёт к следующей по порядку команде.</p>
121 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { for (int i = 0; i &lt; n/; i++) { printf("?"); } printf("\n"); }<p>Здесь пользователь вряд ли введёт n = 0 или n = -100, так как это не имеет смысла. А когда n будет больше или равно 1, программа выйдет из цикла и управление перейдёт к следующей по порядку команде.</p>
122 <p>Теперь рассмотрим ту часть игры, где Марио спускается в подземелье и перед ним появляется стена из кирпичей.</p>
122 <p>Теперь рассмотрим ту часть игры, где Марио спускается в подземелье и перед ним появляется стена из кирпичей.</p>
123 Дэвид на фоне своих слайдов во время чтения курса. Мы видим стену из кирпичей - препятствие для Марио<em>Кадр:<a>CS50</a>/ YouTube</em><p>Выведем на печать что-то похожее на квадрат, как на изображении. Так как у нас на нём кирпичи, используем для вывода символ #.</p>
123 Дэвид на фоне своих слайдов во время чтения курса. Мы видим стену из кирпичей - препятствие для Марио<em>Кадр:<a>CS50</a>/ YouTube</em><p>Выведем на печать что-то похожее на квадрат, как на изображении. Так как у нас на нём кирпичи, используем для вывода символ #.</p>
124 <p>Чтобы кирпичи были расположены в несколько строк, будем использовать цикл в цикле. Программа выглядит так:</p>
124 <p>Чтобы кирпичи были расположены в несколько строк, будем использовать цикл в цикле. Программа выглядит так:</p>
125 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int n; do { n = get_int("Ширина: "); } while (n &lt;1 ); // Для каждой строки for (int i = 0; i &lt; n; i++) { // Для каждого столбца for (int j = 0; i &lt; j; i++) { // Печатаем кирпичек printf("#"); } } // Переходим к следующей строке printf("\n"); }<p>Первый цикл используется для подсчёта строк сверху вниз, а второй цикл в каждой строке посимвольно выводит знаки на экран, наподобие старой пишущей машинки.</p>
125 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { int n; do { n = get_int("Ширина: "); } while (n &lt;1 ); // Для каждой строки for (int i = 0; i &lt; n; i++) { // Для каждого столбца for (int j = 0; i &lt; j; i++) { // Печатаем кирпичек printf("#"); } } // Переходим к следующей строке printf("\n"); }<p>Первый цикл используется для подсчёта строк сверху вниз, а второй цикл в каждой строке посимвольно выводит знаки на экран, наподобие старой пишущей машинки.</p>
126 <p>Запустим программу. Вот что у нас получилось:</p>
126 <p>Запустим программу. Вот что у нас получилось:</p>
127 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Квадрат получился не идеальным, так как символ # в длину меньше, чем в ширину, но это уже особенности шрифта. Будем считать, что задача решена.</p>
127 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Квадрат получился не идеальным, так как символ # в длину меньше, чем в ширину, но это уже особенности шрифта. Будем считать, что задача решена.</p>
128 <p>А теперь вернёмся к нашему калькулятору. Снова рассмотрим проблему переполнения при сложении больших чисел, но на этот раз будем работать с числами с плавающей точкой. Изменим тип переменных с int на float и вместо сложения проведём деление.</p>
128 <p>А теперь вернёмся к нашему калькулятору. Снова рассмотрим проблему переполнения при сложении больших чисел, но на этот раз будем работать с числами с плавающей точкой. Изменим тип переменных с int на float и вместо сложения проведём деление.</p>
129 <p>Добавим в код ещё одну переменную float z = x / y и выведем её на экран:</p>
129 <p>Добавим в код ещё одну переменную float z = x / y и выведем её на экран:</p>
130 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_float("x: "); // Запрашиваем у пользователя y int second_number = get_float("y: "); // Выполняем деление float z = x / y; printf("%i\n", z; }<p>Запустим калькулятор и зададим x = 2, y = 3:</p>
130 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_float("x: "); // Запрашиваем у пользователя y int second_number = get_float("y: "); // Выполняем деление float z = x / y; printf("%i\n", z; }<p>Запустим калькулятор и зададим x = 2, y = 3:</p>
131 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Мы получили ответ - число с шестью знаками после точки. С такой точностью компьютер возвращается результат по умолчанию.</p>
131 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Мы получили ответ - число с шестью знаками после точки. С такой точностью компьютер возвращается результат по умолчанию.</p>
132 <p>Предположим, что мы хотим уменьшить число знаков после точки до двух. Тогда нужно будет изменить формат вывода в последней команде:</p>
132 <p>Предположим, что мы хотим уменьшить число знаков после точки до двух. Тогда нужно будет изменить формат вывода в последней команде:</p>
133 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_float("x: "); // Запрашиваем у пользователя y int second_number = get_float("y: "); // Выполняем деление float z = x / y; printf("%.2f\n", z; }<p>Запустим программу:</p>
133 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_float("x: "); // Запрашиваем у пользователя y int second_number = get_float("y: "); // Выполняем деление float z = x / y; printf("%.2f\n", z; }<p>Запустим программу:</p>
134 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>А теперь попробуем вывести 50 знаков после точки:</p>
134 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>А теперь попробуем вывести 50 знаков после точки:</p>
135 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_float("x: "); // Запрашиваем у пользователя y int second_number = get_float("y: "); // Выполняем деление float z = x / y; printf("%.50f\n", z; }<p>Запустим калькулятор:</p>
135 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_float("x: "); // Запрашиваем у пользователя y int second_number = get_float("y: "); // Выполняем деление float z = x / y; printf("%.50f\n", z; }<p>Запустим калькулятор:</p>
136 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Оказывается, мы не только не можем сложить миллиарды, но даже получить точный ответ при делении двух чисел.</p>
136 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Оказывается, мы не только не можем сложить миллиарды, но даже получить точный ответ при делении двух чисел.</p>
137 <p>Происходит нечто похожее на переполнение при сложении - программа выдаёт неправильный результат, если вы пытаетесь использовать больше битов, чем предусмотрено для хранения целого числа. В этом нет ничего удивительного, ведь число целых чисел бесконечно, а их длина не ограничена.</p>
137 <p>Происходит нечто похожее на переполнение при сложении - программа выдаёт неправильный результат, если вы пытаетесь использовать больше битов, чем предусмотрено для хранения целого числа. В этом нет ничего удивительного, ведь число целых чисел бесконечно, а их длина не ограничена.</p>
138 <p>Здесь происходит то же самое, но в контексте чисел с плавающей точкой: множество действительных чисел несчётно, а длина числа может быть бесконечной. Компьютер с его ограниченной памятью не способен работать с ними. К счастью, в научном мире есть решения, увеличивающие точность вычисления.</p>
138 <p>Здесь происходит то же самое, но в контексте чисел с плавающей точкой: множество действительных чисел несчётно, а длина числа может быть бесконечной. Компьютер с его ограниченной памятью не способен работать с ними. К счастью, в научном мире есть решения, увеличивающие точность вычисления.</p>
139 <p>В процессе работы программы компьютер может менять тип данных, которые он обрабатывает. Сделаем x и y целыми числами, а z оставим числом с плавающей точкой:</p>
139 <p>В процессе работы программы компьютер может менять тип данных, которые он обрабатывает. Сделаем x и y целыми числами, а z оставим числом с плавающей точкой:</p>
140 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_int("x: "); // Запрашиваем у пользователя y int second_number = get_int("y: "); // Выполняем деление float z = x / y; printf("%.50f\n", z; }<p>Запустим программу. Пусть x = 2, а y = 3:</p>
140 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_int("x: "); // Запрашиваем у пользователя y int second_number = get_int("y: "); // Выполняем деление float z = x / y; printf("%.50f\n", z; }<p>Запустим программу. Пусть x = 2, а y = 3:</p>
141 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Результат неожиданный. На самом деле должно получиться бесконечное число 0,6666666666 и так далее. Дело в том, что в языке С при делении целого числа на целое число получается целое число, поэтому компьютер просто отбрасывает ту часть числа, которая меньше 0. Эта функция в языке C называется усечением. Результат был меньше 1, и компьютер выдал 0.</p>
141 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Результат неожиданный. На самом деле должно получиться бесконечное число 0,6666666666 и так далее. Дело в том, что в языке С при делении целого числа на целое число получается целое число, поэтому компьютер просто отбрасывает ту часть числа, которая меньше 0. Эта функция в языке C называется усечением. Результат был меньше 1, и компьютер выдал 0.</p>
142 <p>А теперь рассмотрим ещё один пример. Пусть x = 4, а y = 3. При делении 4 на 3 должна получиться бесконечная десятичная дробь 1,333333 и так далее. Запустим калькулятор:</p>
142 <p>А теперь рассмотрим ещё один пример. Пусть x = 4, а y = 3. При делении 4 на 3 должна получиться бесконечная десятичная дробь 1,333333 и так далее. Запустим калькулятор:</p>
143 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Компьютер разделил 4 на 3 как целые числа, и в результате выдал 1.0000. Здесь также произошло усечение дробной части числа.</p>
143 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Компьютер разделил 4 на 3 как целые числа, и в результате выдал 1.0000. Здесь также произошло усечение дробной части числа.</p>
144 <p>Чтобы исправить ситуацию, используем то, что в C и других языках называется преобразованием типов:</p>
144 <p>Чтобы исправить ситуацию, используем то, что в C и других языках называется преобразованием типов:</p>
145 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_int("x: "); // Запрашиваем у пользователя y int second_number = get_int("y: "); // Выполняем деление float z = (float)x / (float)y; printf("%.50f\n", z; }<p>Здесь мы сообщаем компьютеру, что хотим обрабатывать переменные int как числа с плавающей точкой. Запустим наш калькулятор и опять разделим 2 на 3:</p>
145 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { // Запрашиваем у пользователя x int first_number = get_int("x: "); // Запрашиваем у пользователя y int second_number = get_int("y: "); // Выполняем деление float z = (float)x / (float)y; printf("%.50f\n", z; }<p>Здесь мы сообщаем компьютеру, что хотим обрабатывать переменные int как числа с плавающей точкой. Запустим наш калькулятор и опять разделим 2 на 3:</p>
146 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Результат уже ближе к правильному, хотя погрешность всё равно остаётся.</p>
146 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Результат уже ближе к правильному, хотя погрешность всё равно остаётся.</p>
147 <p>Вспомним<a>первую лекцию</a>. У нас было три бита, и мы помещали в них числа от 0 до 7, то есть от 000 до 111. Я тогда задал вопрос: а как мы будем считать до 8? Кто-то сказал, что нужен четвёртый бит. Действительно, в этом случае число 8 можно было бы представить как 1000.</p>
147 <p>Вспомним<a>первую лекцию</a>. У нас было три бита, и мы помещали в них числа от 0 до 7, то есть от 000 до 111. Я тогда задал вопрос: а как мы будем считать до 8? Кто-то сказал, что нужен четвёртый бит. Действительно, в этом случае число 8 можно было бы представить как 1000.</p>
148 <p>Но допустим, что у вас нет места для четвёртого бита. Тогда, прибавив 1 к числу 111, вы опять получите 000. Происходит то, что называется переполнением, - число, равное 8, не помещается в три бита, и они заполняются нулями.</p>
148 <p>Но допустим, что у вас нет места для четвёртого бита. Тогда, прибавив 1 к числу 111, вы опять получите 000. Происходит то, что называется переполнением, - число, равное 8, не помещается в три бита, и они заполняются нулями.</p>
149 <p>Какой бы странной ни казалась эта проблема, люди с нею уже сталкивались. Возможно, вы помните или читали о проблеме Y2K. 1 января 2000 года компьютеры должны были обновить часы. Но многие системы, особенно написанные давно, в датах хранили только две последние цифры года, чтобы сэкономить место в памяти. Для 2000 года это было просто 00. И если программа добавляла к такой дате префикс 19, то оказывалось, что из 1999 года мы вернулись в 1900-й.</p>
149 <p>Какой бы странной ни казалась эта проблема, люди с нею уже сталкивались. Возможно, вы помните или читали о проблеме Y2K. 1 января 2000 года компьютеры должны были обновить часы. Но многие системы, особенно написанные давно, в датах хранили только две последние цифры года, чтобы сэкономить место в памяти. Для 2000 года это было просто 00. И если программа добавляла к такой дате префикс 19, то оказывалось, что из 1999 года мы вернулись в 1900-й.</p>
150 <p>К счастью, к тому времени удалось поправить много кода, и эту проблему в основном решили. Однако в следующий раз она может возникнуть 19 января 2038 года. В некоторых программах используется время, представляющее собой количество секунд, прошедшее с полуночи 1 января 1970 года. Но в старых системах используется хранение секунд в виде 32-битного целого со знаком. Самая поздняя точка во времени, которая может быть представлена таким форматом, - это 03:14:07 19 января 2038 года.</p>
150 <p>К счастью, к тому времени удалось поправить много кода, и эту проблему в основном решили. Однако в следующий раз она может возникнуть 19 января 2038 года. В некоторых программах используется время, представляющее собой количество секунд, прошедшее с полуночи 1 января 1970 года. Но в старых системах используется хранение секунд в виде 32-битного целого со знаком. Самая поздняя точка во времени, которая может быть представлена таким форматом, - это 03:14:07 19 января 2038 года.</p>
151 <p>Время позже этой даты заставит поле данных стать отрицательным, а отрицательное число может быть воспринято программами как время в 1901 году. В результате любые расчёты, использующие дату позже 19 января 2038 года, могут привести к ошибочным вычислениям.</p>
151 <p>Время позже этой даты заставит поле данных стать отрицательным, а отрицательное число может быть воспринято программами как время в 1901 году. В результате любые расчёты, использующие дату позже 19 января 2038 года, могут привести к ошибочным вычислениям.</p>
152 <p>Но сегодня аппаратное обеспечение стало намного дешевле, чем несколько десятилетий назад, а компьютеры работают намного быстрее, так что это уже не так важно.</p>
152 <p>Но сегодня аппаратное обеспечение стало намного дешевле, чем несколько десятилетий назад, а компьютеры работают намного быстрее, так что это уже не так важно.</p>
153 <p>Подобная ошибка может возникать и в распространённых программах. Напишем небольшую программу, которая конвертирует доллары в пенни:</p>
153 <p>Подобная ошибка может возникать и в распространённых программах. Напишем небольшую программу, которая конвертирует доллары в пенни:</p>
154 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { float amount = get_float("Количество долларов: "); int pennies = amount * 100; printf("Пенни: %i\n", pennies); }<p>Запустим программу и введём несколько значений переменной amount:</p>
154 #include &lt;cs50.h&gt; #include &lt;stdio.h&gt; int main(void) { float amount = get_float("Количество долларов: "); int pennies = amount * 100; printf("Пенни: %i\n", pennies); }<p>Запустим программу и введём несколько значений переменной amount:</p>
155 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Как видите, при вводе amount равном 4.2 программа выдаёт неправильный результат. Конечно, ничего страшного, если кассир в супермаркете обсчитает вас на одно пенни, но представьте себе последствия подобной ошибки в финансовых операциях или научных измерениях.</p>
155 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Как видите, при вводе amount равном 4.2 программа выдаёт неправильный результат. Конечно, ничего страшного, если кассир в супермаркете обсчитает вас на одно пенни, но представьте себе последствия подобной ошибки в финансовых операциях или научных измерениях.</p>
156 <p>Попробуем исправить эту ошибку. Представим, что компьютер хранит 4 доллара и 19,99999 центов или около того. Избавимся от неточности путём округления результата. Подключим библиотеку математических функций math.h и используем функцию round(), которая в ней содержится:</p>
156 <p>Попробуем исправить эту ошибку. Представим, что компьютер хранит 4 доллара и 19,99999 центов или около того. Избавимся от неточности путём округления результата. Подключим библиотеку математических функций math.h и используем функцию round(), которая в ней содержится:</p>
157 #include &lt;cs50.h&gt; #include &lt;math.h&gt; #include &lt;stdio.h&gt; int main(void) { float amount = get_float("Количество долларов: "); int pennies = round(amount * 100); printf("Пенни: %i\n", pennies); }<p>Снова запустим программу:</p>
157 #include &lt;cs50.h&gt; #include &lt;math.h&gt; #include &lt;stdio.h&gt; int main(void) { float amount = get_float("Количество долларов: "); int pennies = round(amount * 100); printf("Пенни: %i\n", pennies); }<p>Снова запустим программу:</p>
158 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Теперь всё правильно.</p>
158 <em>Кадр:</em><a><em>CS50</em></a><em>/ YouTube</em><p>Теперь всё правильно.</p>
159 <p>О таких вещах забывать нельзя. К сожалению, даже профессиональные программисты не всегда уделяют должное внимание подобным мелочам. И цель наших занятий не просто научить вас программировать, а ещё и научить понимать то, что происходит, так сказать, "под капотом" программного кода.</p>
159 <p>О таких вещах забывать нельзя. К сожалению, даже профессиональные программисты не всегда уделяют должное внимание подобным мелочам. И цель наших занятий не просто научить вас программировать, а ещё и научить понимать то, что происходит, так сказать, "под капотом" программного кода.</p>
160 <p>Иногда обществу приходится дорого платить за такие ошибки. Например, несколько лет назад стало известно о баге в системе управления самолёта Boeing. Система должна была перегружаться каждые 248 дней, иначе рейс прямо в ходе полёта мог перейти в отказоустойчивый режим и обесточить свои генераторы, что привело бы к катастрофе.</p>
160 <p>Иногда обществу приходится дорого платить за такие ошибки. Например, несколько лет назад стало известно о баге в системе управления самолёта Boeing. Система должна была перегружаться каждые 248 дней, иначе рейс прямо в ходе полёта мог перейти в отказоустойчивый режим и обесточить свои генераторы, что привело бы к катастрофе.</p>
161 <p>Это произошло потому, что программное обеспечение использовало 32-битное число, отсчитывающее десятые доли секунды для отслеживания параметров электрической мощности генераторов. Через 248 дней происходило переполнение, и в качестве побочного эффекта система отключала бы электроснабжение. Решение компании Boeing было связано с использованием 32-битной операционной системы. В настоящее время она выпустила патч с исправлением.</p>
161 <p>Это произошло потому, что программное обеспечение использовало 32-битное число, отсчитывающее десятые доли секунды для отслеживания параметров электрической мощности генераторов. Через 248 дней происходило переполнение, и в качестве побочного эффекта система отключала бы электроснабжение. Решение компании Boeing было связано с использованием 32-битной операционной системы. В настоящее время она выпустила патч с исправлением.</p>
162 <p>Чем больше аппаратного обеспечения мы носим с собой и чем больше используем подобных устройств, тем с большим количеством проблем нам придётся столкнуться.</p>
162 <p>Чем больше аппаратного обеспечения мы носим с собой и чем больше используем подобных устройств, тем с большим количеством проблем нам придётся столкнуться.</p>
163 <p>Подведём итоги сегодняшней лекции:</p>
163 <p>Подведём итоги сегодняшней лекции:</p>
164 <ul><li>Если вы планируете использовать результат какого-либо вычисления в коде повторно, то сохраните его в отдельную переменную.</li>
164 <ul><li>Если вы планируете использовать результат какого-либо вычисления в коде повторно, то сохраните его в отдельную переменную.</li>
165 <li>Вы можете нажать<strong>стрелку вверх</strong>в редакторе кода, чтобы увидеть всю историю команд и выбрать нужную, вместо того чтобы набирать несколько раз одно и то же. Это ускорит работу.</li>
165 <li>Вы можете нажать<strong>стрелку вверх</strong>в редакторе кода, чтобы увидеть всю историю команд и выбрать нужную, вместо того чтобы набирать несколько раз одно и то же. Это ускорит работу.</li>
166 <li>Помните о проблеме переполнения при работе с типами данных int и float - все они используют конечное число битов для представления. Используйте тип данных long, чтобы этой проблемы избежать.</li>
166 <li>Помните о проблеме переполнения при работе с типами данных int и float - все они используют конечное число битов для представления. Используйте тип данных long, чтобы этой проблемы избежать.</li>
167 <li>В C объявление функции всегда ставится в начале программы. В некоторых других языках программирования, например в Python, функции можно указывать в любом месте кода.</li>
167 <li>В C объявление функции всегда ставится в начале программы. В некоторых других языках программирования, например в Python, функции можно указывать в любом месте кода.</li>
168 <li>Для правильного округления чисел используйте функцию round() из библиотеки математических функций math.h.</li>
168 <li>Для правильного округления чисел используйте функцию round() из библиотеки математических функций math.h.</li>
169 </ul><a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
169 </ul><a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>