Начинаем изучать язык программирования C. CS50 на русском. Лекция 1.1
Дэвид Малан рассказывает об основах языка С, о хорошем и плохом коде, о работе с Visual Studio Code и разбирает простую программу на C.
Кадр: фильм «Переводчики»
CS50 (Computer Science 50) — легендарный курс о компьютерных технологиях Гарвардского и Йельского университетов. Практически в любом разговоре о том, как вкатиться в программирование, опытные ребята упоминают этот курс как необходимую базу. В нём последовательно разбираются логика работы компьютера, работа с визуальным программированием в Scratch, основы языка C, массивы, основные алгоритмы, работа памяти, структуры данных, основы языка Python, основы SQL, HTML, CSS, JavaScript, Python-фреймворк Flask и даже эмодзи :)
Изучив материалы курса, вы разберётесь в том, как работает компьютер, узнаете универсальные принципы программирования и сможете понимать и читать код, написанный на разных языках.
В этой статье мы разберём основы программирования на примере визуальной среды Scratch — хардкор, C и прочие штуки начнутся со следующей статьи цикла.
Содержание
- Главные условия создания хорошего кода
- Использование IDE
- Компилирование программы
- Функции и аргументы
- Зачем нужна функция main
- Заголовочные файлы (header files)
- Команды Linux
Почему мы перевели CS50 и как устроена каждая статья по курсу
CS50 — это самый популярный курс в Гарвардском университете и самый посещаемый массовый открытый онлайн-курс на edX. Все материалы курса доступны бесплатно (в том числе и практические задания), но, если заплатить, можно получить сертификат и разные дополнительные плюшки.
Мы перевели видеолекции в текстовый формат, снабдили их иллюстрациями, кое-где дополнили объяснения и выкладываем в открытый доступ. Оригинальный курс доступен по лицензии Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) — его можно дорабатывать и распространять бесплатно, но только под исходной лицензией. Так что этот цикл материалов вы также сможете использовать в своей работе или общественной деятельности совершенно свободно и бесплатно в рамках той же лицензии.
Каждая статья из цикла CS50 состоит из следующих материалов:
- текстовый перевод видео (иногда — половины видео, если тема обширная);
- ссылка на оригинальное видео на английском языке;
- схемы и пояснения;
- ссылки на более подробные материалы по теме статьи;
- практические задания.
Дэвид Дж. Малан
Американский учёный, профессор Гарвардского университета.
Оригинальное видео
Главные условия создания хорошего кода
На этой лекции мы начнём изучать новый язык — C. В отличие от графического Scratch, это традиционный текстовый язык, лежащий в основе многих современных языков, например того же Python.
Хочу напомнить, что наша цель не в том, чтобы выучили Scratch, C или даже Python, а в том, чтобы научиться программировать. В языке С много деталей, которые могут отвлекать от процесса программирования: синтаксис, странная пунктуация — все эти решётки, угловые, круглые и фигурные скобки, обратная косая черта и многое другое. Но я обещаю, что скоро вы со всем этим разберётесь. Для этого нужно вспомнить, что мы изучали.
Итак, выполняя задачи на Scratch, мы рассмотрели функции, у которых есть аргументы (входные данные), а также возвращаемые значения (выходные данные) — хотя последние есть не у всех функций.
Затем мы немного поговорили об условных выражениях — так сказать, дорожных развилках, — а также о логических выражениях, представляющих собой вопросы с двумя вариантами ответа: да/нет или истина/ложь.
Мы рассмотрели циклы, которые позволяют повторять какое-то действие снова и снова, переменные, временно сохраняющие значения и тому подобное. Всё это нам пригодится.
Теперь поговорим о том, как переводить эти идеи на языки программирования. Синтаксис языков программирования проще, чем синтаксис естественного, человеческого языка, ведь и слов в языках программирования гораздо меньше. Однако с языками программирования нужно быть более аккуратными — компьютер не прощает даже малейших ошибок.
Итак, вы написали код и спрашиваете себя: хорош ли он? Как же это понять? Самый первый критерий — код должен правильно решать поставленную задачу. Но, даже если вы правильно решили задачу, а при этом ваш код длинный и беспорядочный, он будет считаться плохим. Другому программисту будет сложно понять, что ваш код делает и действительно ли он правильный. Да и вы сами, когда посмотрите на этот код в следующем году, а то и на следующее утро, возможно, не поймёте, что написали.
В итоге программисту необходимо сосредоточиться на разработке хорошего кода, на повышении эффективности алгоритмов, на том, чтобы код чистым и даже выглядел красиво. Но это уже вопрос стиля.
Итак, у хорошего кода есть ряд характеристик: корректность, дизайн (архитектура, структура), стиль. А теперь разберём программу на С, которая печатает в терминале hello, world.
Честно говоря, это довольно жестоко — столько раз нажимать на клавиши, чтобы получить такой незначительный результат. Через несколько лекций, когда мы представим другие, более современные языки, такие как Python, вы сможете преобразовать эту логику буквально в одну строку кода. Но сейчас будет полезно понять, что здесь происходит.
Использование IDE
Итак, как напечатать hello, world на экране? Я мог бы открыть Word, Google Docs или Pages, набрать текст символ за символом, сохранить его — и вот у меня уже есть готовая программа. Но проблема в том, что компьютеры понимают только двоичный код, нули и единицы. Ни один из тех инструментов, которые я перечислил, не подходит для программирования — у них нет возможности преобразовать этот текст в двоичный код.
Но есть инструменты, которые дают такую возможность. Они называются интегрированными средами разработки, или IDE. Вот, например, как выглядит очень популярная Visual Studio Code (VS Code).
Здесь вверху вы видите, как я создал пустой файл под названием hello.c, где .c указывает, что это файл с кодом на языке программирования C. Цифра 1 слева — это автоматически проставляется нумерация строк. Так удобнее ориентироваться в коде программы.
Теперь я добавлю кое-что под названием stdio.h (подробнее об этом поговорим позже), а затем наберу int main(void) (к этому мы тоже вернёмся далее), затем я введу фигурные скобки и нажму клавишу Tab, чтобы сделать отступ на несколько пробелов. В завершение я наберу printf(), "hello, world/n" и поставлю точку с запятой.
Теперь нужно нажать Ctrl + S — это сохранит файл. И вуаля, я уже программист.
Компилирование программы
Моя программа находится в файле под названием hello.c. Теперь мне нужно преобразовать исходный код — то есть текст программы на языке программирования, как его принято называть, в нули и единицы, которые мы будем называть машинным кодом. Его как раз и выполняет компьютер.
Конечно, я не буду делать это вручную. Есть алгоритм, реализованный специальной программой, которая делает такое преобразование — эта программа называется компилятором. Не все языки его используют, но C — язык, использующий компилятор.
Итак, в верхней части экрана находится мой текстовый редактор, в котором я могу запросто создавать файлы и писать код. Посмотрите на нижнюю часть экрана — там есть область, которая называется окном терминала. Там я могу вводить и запускать команды, например скомпилировать свой исходный код в машинный код.
Итак, как же мне превратить свой файл hello.c в программу? Для этого я ввожу в командной строке терминала команду make hello и нажимаю Enter, а затем ввожу ./hello и опять нажимаю Enter.
make hello осуществляет сборку и компиляцию программы, а ./hello означает запуск программы под названием hello в текущей папке.
Теперь я открою боковую панель IDE.
То, что я открыл, называется Explorer. Здесь можно увидеть все файлы своей учётной записи. Один из них называется hello.c — он выделен, потому что открыт прямо здесь, в нашей IDE. Другой называется hello — это новый файл, созданный при компиляции. Файл hello — исполняемый, то есть его можно запускать.
Теперь, если мы хотим изменить программу, например, чтобы она печатала hello, CS50, нам нужно не только исправить код в файле hello.c, но и скомпилировать его заново, чтобы получить обновленный файл hello. Только тогда результат вывода программы изменится.
Функции и аргументы
А сейчас вспомним, что такое функции и аргументы. Функции — это действия, такие как «говорить», «спрашивать» и тому подобное. Аргументы функций в Scratch вводились, как правило, в маленьких белых овалах.
Давайте рассмотрим нашу программу на С в контексте Scratch. Для вывода текста на экран здесь используется функция printf. Круглые скобки заменяют белый овал. Ещё есть двойные кавычки, которых не было в Scratch — они нужны, если в качестве аргумента функции, то есть данных, которые функция обрабатывает, используется строка. Ещё в коде есть точка с запятой — в программировании на языке C она используется, чтобы «закончить свою мысль».
У функций могут быть разные типы выходных данных — то есть результат. Чаще всего это какие-то строки, символы, числа и тому подобное. Но выходными данными может быть и некий «побочный эффект» — обычно это что-то, что вы видите на экране или что можете услышать: изображения, звук, какой-то появляющийся текст.
В Scratch у нас были функции, которые, помимо побочных эффектов, возвращали какое-то значение. Например, блок Спросить, который сохранял ответ на вопрос «Как вас зовут?» в специальной переменной Ответ. Такой результат вы могли использовать повторно — в отличие от побочного эффекта, который появляется на экране и исчезает.
Самое близкое совпадение, которое я могу предложить для блока Спросить — это функция get_string. Она также возвращает полученный результат в виде переменной, и его можно использовать повторно.
В отличие от математики, знак = в программировании на C не означает равенство — это оператор присваивания. Переменной answer присваивается результат выполнения функции. А сама функция предлагает пользователю ввести какое-то значение.
Кроме того, в C вы не просто даёте имя переменной, как в Scratch. Вам нужно заранее сообщить компьютеру, какой тип данных она будет хранить. Тип string используется для строк, а int — для целых чисел. Есть и другие типы данных — мы изучим их позже.
Если в программе указано, что переменная имеет тип string, то компьютер будет интерпретировать хранящиеся в ней нули и единицы как слова или последовательность букв (даже пустую последовательность букв), а если это int — как целое число. Если я попытаюсь поместить строку в число или число в строку, команда make выдаст сообщение об ошибке (но тут важно понимать, что все данные, которые вы вводите сами в терминал, — будь то число или текст, программа будет понимать именно как текст, строку, тип string).
Типы данных в C:
- bool — булевы значения (истина/ложь);
- char — единичный символ;
- float — числа с плавающей точкой/запятой (дробные числа);
- double — более длинные числа с плавающей точкой/запятой (дробные числа);
- int — целые числа;
- long — большие целые числа;
- string — строковые данные.
Эти типы помогают «понимать» и обрабатывать последовательности единичек и нулей как осмысленные данные конкретного формата.
Теперь разберёмся с \n. Перейдём к VS Code и вернёмся к программе "hello, world". Уберём \n и запустим код на компиляцию и выполнение.
Как видим, знак $ остался на той же строке, что и "hello, world". Мы можем сделать вывод, что \n — это обозначение для перемещения курсора на следующую строку. Без него выводимый текст выглядит неаккуратно.
А теперь сделаем программу более интерактивной. Пусть она сначала спрашивает: «Как вас зовут?», а потом здоровается с человеком.
Обновлённая программа на С будет выглядеть так:
Разберёмся в тексте (листинге) этой программы. Я включил сюда ещё одну строку кода, точнее, библиотеку, которая называется cs50.h. Дело в том, что некоторые функции C поставляются в комплекте с языком. Но чаще всего, если вы хотите использовать функцию, вы должны загрузить библиотеку, которая её содержит. get_string — это функция, которую некоторое время назад написали для нашего курса CS50. Она позволяет легко получать информацию от пользователя из терминала — если бы не она, нам бы пришлось писать очень мудрёный код. Чтобы использовать эту функцию, нужно сказать программе, чтобы она загрузила и использовала библиотеку cs50.h, в состав которой и входит функция get_string.
Библиотека — это что-то вроде заранее написанных другими программистами полезных кусочков кода, к которым можно обращаться как к функциям с помощью коротких имён — в нашем случае get_string. stdio.h — это так называемая «стандартная библиотека», которая идёт в комплекте с языком программирования. Да, верно — даже за базовыми функциями языка программирования есть чей-то заранее написанный код.
Итак, в нашей программе есть две библиотеки:
- stdio.h — библиотека, которая даёт вам доступ к printf и другим функциям, связанным с вводом и выводом.
- cs50.h — библиотека, предоставляющая доступ к функциям, которых нет в стандартной библиотеке C, и включающая в себя get_string.
Какие функции включены в состав библиотеки CS50:
- get_char()
- get_double()
- get_float()
- get_int()
- get_long()
- get_string()
Эти функции помогают ввести в программу данные того или иного типа из командной строки. Если вам нужна строка, используйте функцию get_string() — она попросит ввести в терминал какой-то текст и присвоит его определённой переменной (в этом случае и число будет считаться текстом, строковым параметром).
А что же означает знак %s? В Scratch, если нам нужно было объединить две строки, мы просто писали «яблоко» и «банан». В языке С другой синтаксис. Вы помещаете в строке внутри двойных кавычек последовательность символов %s, которая говорит компьютеру, что на это место надо поставить переменную, название которой будет указано после кавычек, но внутри круглых скобок. В нашем случае это переменная answer.
Кроме %s есть и другие специальные последователи символов:
- %s — string;
- %i — integer;
- %f — float и double;
- %li — long integer;
- %c — char.
Синтаксис вы помните: даём строку в кавычках, внутри неё используем %s, а после строки пишем имя переменной, из которой надо взять значение и подставить вместо %s.
Итак, сколько аргументов (то есть входных данных) принимает функция printf? Правильный ответ — два аргумента. Запятая после кавычек отделяет первый аргумент — строку в кавычках от второго — переменной answer. Запятая внутри кавычек является частью аргумента типа string, то есть частью строки.
Я хочу указать на красивые цвета, которые подсвечивают текст в окне редактирования. Так, например, переменная string_answer здесь выделена чёрным цветом, функция get_string — коричнево-жёлтым, %s — синим. Это редактор VS Code выделяет для вас синтаксис — такая функция называется «подсветка синтаксиса». Благодаря этому программист может различать разные сущности по цветам и более просто ориентироваться в тексте программы. Видите чёрное — ага, это точно переменная. Видите коричнево-жёлтое — это явно функция. Подсветка синтаксиса — это функциональность, которую предоставляют практически все редакторы кода и IDE.
Обратите внимание на отступ в строках 6 и 7. Я настроил VS Code на отступ в четыре пробела, появляющиеся каждый раз, когда я нажимаю клавишу Tab, — это распространённое соглашение. Отступы помогают подчеркнуть иерархию элементов, их вложенность. Если элементы на одном уровне — они начинаются с одинакового отступа (например, открывающая фигурная скобка и закрывающая фигурная скобка). А если один элемент вложен в другой, он начинается с отступа относительно родительского элемента. Например, объявление переменной string_answer вложено в функцию main.
Операторы, инкременты, декременты и объявление переменных
В языке C существует много операторов для математических вычислений, а сами эти вычисления происходят на каждом шагу. Вот все операторы:
- + — плюс;
- - — минус;
- * — умножение;
- / — деление;
- % — даёт остаток от деления одного числа на другое.
Также в C есть переменные. Мы уже видели их. А ещё есть синтаксический сахар — это такие выражения, которые позволяют очень просто выразить сложную мысль в коде.
Когда мы создаём переменные, нам надо их объявить — указать, какого они типа, и в конце поставить точку с запятой. Для примера создадим переменную counter, то есть простой счётчик. Помните, такой счётчик у нас был в Scratch? Только он выглядел гораздо проще.
- int — указатель типа переменной;
- counter — имя переменной;
- = — оператор присваивания какого-либо значения (это не «равно», а именно оператор присваивания, он не просто говорит, что какие-то значения равны, он именно присваивает левой части выражения значение из правой части);
- 0 — значение, которое мы присваиваем переменной;
- ; — окончание практически для любой смысловой единицы в C.
В Scratch есть удобный визуальный блок, который позволяет создать счётчик, значение которого бы обновлялось при совершении какого-то действия — увеличивалось на единицу. В C, чтобы увеличить значение счётчика, можно написать следующий код:
Заметим снова: знак «равно» здесь — не знак «равно», а оператор присваивания. Если бы это был знак «равно», само выражение не имело бы смысла. Вообще, операция увеличения счётчика на один — очень распространённая в программировании (это ещё называется «инкремент»). И сейчас мы покажем, что такое синтаксический сахар в действии. Следите за руками:
Оно означает, что counter равен самому себе плюс единице. А вот ещё один способ сделать это гораздо проще:
Те же самые операции можно проводить и с оператором — (такая операция будет называться «декремент»).
Важное примечание: когда мы меняем значение уже созданной переменной, нам не надо снова указывать её тип, то есть мы не пишем так:
Зачем нужна функция main
Давайте вернёмся к тому, с чего мы начали. Зачем нужна строка int main(void)?
Это стандартное начало программ на С. Строка int main(void) сообщает компилятору, что есть функция с именем main (главная функция), и она возвращает целое число типа int. Слово void показывает, что у нашей функции нет аргументов (void переводится как «пустота»).
Фигурные скобки показывают начало { и конец } тела функции. Они используются и в других структурных блоках кода, но обозначают всегда одно — начало и конец какой-то сущности внутри программы.
Когда будет достигнут конец главной функции (закрывающая фигурная скобка), программа автоматически вернёт значение 0 (именно поэтому тип нашей функции — int, то есть целое число). Это важное значение — проанализировав его, операционная система может понять, успешно завершилась наша программа или нет. Возвращаемое значение 0 означает успешный успех.
Заголовочные файлы (header files)
Нырнём немного глубже и разберёмся, что такое заголовочные файлы. Когда мы изучали Scratch, я их не упоминал: мы просто выставляли зелёный флаг на клик и потом выводили "hello, world" — всё очень просто и наглядно.
Однако в языке C вы не можете просто написать функцию main() со вложенной функцией printf(). Вам придётся написать в первой строке программы #include <stdio.h>, чтобы сказать компилятору: «Загрузи библиотеку кода, которую написал кто-то до меня» — только так компилятор узнает, что вообще такое printf().
Например, вам надо будет загрузить библиотеку CS50, чтобы использовать функции get_string(), get_int() и тому подобные (далее мы поговорим и о них). В общем, без такой строки сам компилятор никогда не узнает, что такое get_string(). А файлы типа stdio.h или cs50.h среди программистов на C и C++ принято называть заголовочными файлами. Чуть позже мы посмотрим, что находится внутри этих файлов, но если кратко, то это как ресторанное меню, где вместо блюд — все доступные функции. И такое «меню» помогает компилятору узнать, как правильно использовать и понимать все эти функции.
Важное различие: библиотека предоставляет все те функции, о которых мы говорили выше, а заголовочный файл — это лишь специальный механизм, благодаря которому вы можете включить нужные функции в свой код. То есть библиотека будет называться CS50 или Standard I/O, а соответствующие им заголовочные файлы будут называться stdio.h, cs50.h.
Команды Linux
Теперь перейдём на вкладку Explorer IDE и посмотрим, как устроен наш программный проект. Как видно на скриншоте, там у нас лежат два файла: hello и hello.c.
А теперь представьте, что нам надо как-то организовать свой код — например, для занятий студентов разных групп или с разными учебными программами на C. В принципе, в графическом интерфейсе мы можем сделать всё, что нам нужно, используя знакомые и привычные иконки macOS, Windows или Linux. С их помощью можно нажать на иконку добавления каталога и назвать новый каталог так, как нам надо.
Подробная справка по командной строке Linux — в нашей статье.
Однако то же самое можно сделать с помощью командной строки. Например, введя в командном интерфейсе ls, я могу просмотреть содержимое проекта (названия со слешем / на конце — это как раз папки, а не обычные файлы). С помощью команды mkdir (MaKe DIRectory) можно создать и назвать новую директорию.
clean в командном режиме позволяет стереть историю — то есть все команды, которые вы набирали в командной строке ранее. На практике это делается редко, и в рамках курса я ввожу clean, чтобы держать в фокусе только свои последние набранные команды.
mkdir создаст новую директорию. После этой команды надо написать название новой директории. Например, mkdir pset1 создаст каталог для кода первой недели, а mkdir pset2 — для второй.
Теперь я хочу перейти в одну из вновь созданных директорий. Для этого в командной строке я введу команду cd pset1. cd означает Change Directory. После ввода этой команды я нажимаю клавишу Enter и тут же происходят две примечательные вещи: строка приглашения к набору команд меняется на имя той директории, в которую я только что перешёл — всегда напоминая мне, где я в данный момент работаю.
После этого я могу просмотреть содержимое каталога с помощью команды ls. Если ввести её в текущем каталоге, она выведет список файлов (в нашем случае ничего — потому что в папке пока пусто).
Теперь попробуем создать в папке новый файл — командой code my_first_program.c. Этот файл тут же появится на новой вкладке вашего редактора. Если же мы хотим перейти на один каталог выше, надо ввести cd .. — эта команда автоматически поднимет вас в родительский каталог. А cd ../.. — переведёт вас на два каталога выше. ./ означает «текущий каталог». В общем, командная строка может делать всё, что умеет GUI, но не наоборот. Напомним, это команды терминала, они не имеют отношения к языку C.
Итоги
Ну что ж, на сегодня хватит — а в следующем уроке мы ещё сильнее погрузимся в программирование на C, напишем простенькие программки и попрактикуемся. Подведём небольшие итоги:
- C — это компилируемый язык программирования. Прежде чем программа на C начнёт выполняться, её необходимо полностью перевести в единички и нули, то есть сделать понятной для компьютера.
- Программировать удобно в средах разработки (IDE) или редакторах для программирования. мы использовали VS Code.
- Вместо того чтобы создавать и просматривать папки обычным способом — с помощью иконок, можно использовать командную строку.
- Переменные — это такие области памяти компьютера, в котором хранятся какие-то данные. У этих данных есть тип — строка, число и тому подобные.
- В языке C существуют операторы, функции, заголовочные файлы и библиотеки.
- main() — самая главная функция, с которой начинается программа.
Больше интересного про код — в нашем телеграм-канале. Подписывайтесь!