commit 71813ad6dffb336bfb00ec92a40bb74765487d6f Author: Ivan I. Ovchinnikov Date: Fri Aug 20 15:57:50 2021 +0300 initial commit diff --git a/formatting.tex b/formatting.tex new file mode 100644 index 0000000..3262d1b --- /dev/null +++ b/formatting.tex @@ -0,0 +1,55 @@ +%%% Работа с русским языком +\usepackage{cmap} % поиск в PDF +\usepackage[T2A]{fontenc} % кодировка +\usepackage[utf8]{inputenc} % кодировка исходного текста +\usepackage[english,russian]{babel} % локализация и переносы +\usepackage[printwatermark]{xwatermark} +\usepackage{xcolor} +\usepackage{multicol} + +\usepackage{setspace} +% XeLaTeX +%\usepackage{fontspec} +%\setmainfont{Times New Roman} +\onehalfspacing +\usepackage{titlesec} +\usepackage{indentfirst} + +\newcommand{\frm}[1]{ +\newline +\newline +\indent +\fbox{% + \parbox{0.9\textwidth}{% + #1 + }% +} +\newline +\newline +} +\newcommand{\office}{(LibreOffice, OpenOffice, Microsoft Office, iWork и другие)} + +\hbadness=99999 + +\def\code#1{\texttt{#1}} + +\author{Иван Овчинников} +\date{\today} + +\usepackage{amsmath,amsfonts,amssymb,amsthm,mathtools} % AMS +\usepackage{icomma} % "Умная" запятая: $0,2$ --- число, $0, 2$ --- перечисление +%% Перенос знаков в формулах (по Львовскому) +\newcommand*{\hm}[1]{#1\nobreak\discretionary{} +{\hbox{$\mathsurround=0pt #1$}}{}} +\usepackage{layout} +\usepackage{titlesec} +\newcommand{\sectionbreak}{\clearpage} + +\addto{\captionsrussian}{\renewcommand{\figurename}{}} +\usepackage{minted} + +\usepackage[lmargin=1.5cm,rmargin=2.5cm,tmargin=2.5cm,bmargin=2.5cm,paperheight=240mm,paperwidth=170mm]{geometry} +%\setlength{\marginparwidth}{0pt} +% \begin{multicols}{2} +% \columnbreak +% \end{multicols} diff --git a/main.tex b/main.tex new file mode 100644 index 0000000..1eb9d7d --- /dev/null +++ b/main.tex @@ -0,0 +1,1209 @@ +\documentclass[a5paper,twoside,openright]{article} +\include{formatting} +\title{Очередное введение в\\язык программирования C} + +\newwatermark[allpages,color=red!50,angle=70,scale=7,xpos=-36,ypos=14]{DRAFT} + +\begin{document} +\maketitle +\thispagestyle{empty} +\newpage +\tableofcontents +\newpage +\section{Введение} %раздел +\subsection{От автора} %подраздел +Приветствую, коллеги! Начиная работу над этим документом я не ставил целью написать очередной учебник по языку С или поспорить с трудами Кернигана, Ритчи и Страуструпа. Я думаю, что понял, почему программирование кажется таким сложным: не нашлось ни одного материала, написанного понятным и простым языком. В этом документе я постараюсь не повторить этой досадной ошибки именитых авторов. +\frm{Брайан Керниган: <<\textbf{Язык C} — инструмент, острый, как бритва: с его помощью можно создать и элегантную программу, и кровавое месиво.>>} +Да, спасибо классикам за предупреждения, постараемся, также, этой работой не создать кровавое месиво. +\subsection{Инструментарий} +Я сознательно пропускаю этот подраздел, потому что не хочу начинать очередной раунд борьбы за право какого бы то ни было компилятора и/или среды разработки называться единственно верным. Обозначу лишь популярные варианты для основной тройки ОС (Windows, Linux, Mac OS X). Компиляторы: +\begin{itemize} + \item clang; + \item GCC/MinGW; + \item MSVC. +\end{itemize} +Остальные, такие как, например, Borland C++ можно считать экзотическими и не пытаться их устанавливать без экономического обоснования. Интегрированные среды разработки и программы для редактирования кода: +\begin{itemize} + \item CLion; + \item Visual Studio; + \item VSCode; + \item Qt Creator; + \item CodeBlocks; + \item Notepad++; + \item Sublime Text. +\end{itemize} +Этот список можно продолжать почти бесконечно, поскольку редактировать код можно в абсолютно любом редакторе, позволяющем сохранять простой текст в файлы. +%https://docs.google.com/document/d/15b226AsztngEhyzP6ZPAYnSzSlGkLHVj +%https://docs.google.com/document/d/11PmQYyQIPHl84Fh9dpJ3fwKP_h5f2veu +\subsection{Немного истории} +Первая версия языка С была разработана в 1972м году Деннисом Ритчи для программирования в недавно созданной на тот момент среде UNIX. Язык разрабатывался не государством, а обычными практикующими программистами. В нём сразу были учтены и исправлены все неудобства существовавших на тот момент FORTRAN и PASCAL. Поскольку интерес к языку со временем не пропадал, а технологии развивались, появились редакции языка, такие как С99 (1999 год) С11(2011 год). В языке С есть возможность работать с указателями на физические ячейки оперативной памяти компьютера. Конечно, это небезопасно, но при должной квалификации программиста позволяет получить максимально эффективный код, близкий к языку ассемблера и даже машинным кодам конкретного процессора. С является компилируемым процедурным языком со строгой статической типизацией, что позволяет писать максимально безопасный код, и отсеять бОльшую часть ошибок ещё на этапе компиляции проекта. На языке С написано огромное количество программ, библиотек, и даже операционных систем. Какая-бы у Вас ни была установлена операционная система, очень вероятно, что она написана на С. На языке С пишут драйверы для периферийного оборудования, программируют контроллеры для космической аппаратуры, пишут высокоскоростные приложения. Помимо этого, сейчас стремительно набирает популярность такое направление, как <<умная техника>> и <<интернет вещей>>. Именно из-за этих трендов язык С за последние пару лет снова поднялся в рейтинге TIOBE на лидирующие места. Какой бы язык программирования вы ни изучали, знание языка С нужно потому что языки высокого уровня делают много вещей одной командой, а если Вы хотите не просто стать программистом, а быть хорошим программистом - вы должны понимать, что там, внутри, происходит на самом деле. Знание языка С можно сравнить с умением ездить на автомобиле с механической коробкой передач: коробка-автомат резко снижает порог вхождения в участники дорожного движения, но, зачастую настолько ухудшает качество управления автомобилем, что это приводит к самым печальным последствиям. +\section{Основные понятия} +\subsection{Компиляция} +\label{text:naming} +Прежде, чем говорить о языках программирования и о том, что такое компиляция, как она работает и прочих интересных вещах, нам необходимо познакомиться с понятием, которое будет сопровождать весь курс и в целом всю программистскую жизнь - это понятие имени. Имя - это некий символьный идентификатор (переменная, контейнер) для некоторого числа (числом в свою очередь является адрес ячейки памяти, куда записывается значение). Именовать можно как переменные, так и функции. Простейший пример - запись равенства: \code{name = 123456}. Различие между именем и числом задает признак числа, в программах для компьютеров признаком числа является первый символ, имя (идентификатор) не должно начинаться с цифры. Таким образом компиляторы однозначно могут определить, что является именем, а что числом. Это отличие накладывает на программиста очевидное ограничение: невозможность создать идентификаторы, начинающиеся с цифр. Также, чтобы не создавать неоднозначности в поведении программы нельзя, чтобы имена в рамках одной программы повторялись. + +Общий алгоритм работы со всеми компилируемыми языками, в том числе С++ выглядит следующим образом: +\begin{enumerate} + \item программист пишет текст программы на каком-либо языке программирования, в нашем случае это С; + \item при помощи программы-транслятора и, зачастую, ассемблера этот текст преобразуется в исполняемые машинные коды, этот процесс, обычно, состоит из нескольких этапов и называется общим словом <<компиляция>>; + \item исполняемые коды запускаются на целевом компьютере (чаще всего, это сервер или персональный компьютер пользователя). +\end{enumerate} +Применим этот общий алгоритм для написания первой программы. Предполагается, что на данный момент у Вас установлена либо среда разработки, либо текстовый редактор и компилятор по отдельности, чтобы иметь возможность повторить этот и все последующие примеры самостоятельно. +\paragraph{Первая программа, файл \code{program.c}} +\begin{minted}{C} +/* + * Project: yet another basic C guide + * Author: Иван Игоревич Овчинников + * program.c + */ +#include +int main(int argc, char** args) { + printf("Hello, World!\n"); + return 0; +} +\end{minted} +\label{code:firstprogram} +\paragraph{Запуск компиляции и исполнение программы} +В зависимости от выбранного инструментария и ОС процесс компиляции (трансляции) и запуска программы на исполнение может незначительно отличаться, далее будут приведены несколько вариантов. Естественно, что не нужно выполнять их все, а следует выбрать один, который сработает именно с Вашим набором инструментов. Трансляция (компиляция): +\begin{verbatim} + clang -o program program.c +(или) gcc -o program program.c +\end{verbatim} +Запуск будет отличаться только для Windows (символами доллара и угловой скобкой обозначены приглашения unix-терминала и командной строки windows, соответственно): +\begin{verbatim} + non-windows $ ./program + windows > .\program.exe +\end{verbatim} +Далее в тексте в целях демонстрации будет использоваться запуск в стиле non-windows, а также, будет опускаться демонстрация этапа компиляции (кроме случаев, когда сам процесс компиляции отличается). +\subsection{Шаблон программы} +Как и программы на любом другом языке, программы на языке С имеют ряд обязательных элементов, также называемых шаблоном программы. Рассмотрим эти элементы подробнее на примере только что написанной первой программы (\hyperref[code:firstprogram]{\ref{code:firstprogram}}). Некоторые, незначительные аспекты, будут рассмотрены сразу и полностью, комментарии, например, но некоторые будут рассмотрены поверхностно, поскольку являются масштабными, сложными для понимания фундаментальными механизмами языка, которым будут посвящены целые специальные разделы. +\paragraph{Комментарии} Некоторые среды разработки оставляют в шапке файла комментарии об авторе и дате создания файла. Некоторые команды разработки регламентируют такие комментарии и рекомендуют их написание каждым участником. Комментарий это любой текст, написанный для удобства программиста и игнорируемый компилятором. Комментарии бывают как однострочные, так и многострочные. В редких случаях можно встретить внутристрочные комментарии, но их лучше стараться не использовать, они считаются дурным тоном, поскольку резко снижают читаемость кода. +\frm{\textbf{Комментарий} - это фрагмент текста программы, который будет проигнорирован компилятором языка.} +Очень старые компиляторы допускали только комментарии в стиле \code{/* xxx */}, сейчас допустим также стиль \code{// xxx}, завершается такой комментарий концом строки (то есть вся оставшаяся строка, после символов \code{//} будет проигнорирована компилятором). Комментарии в коде важны, особенно для описания и пояснения неочевидных моментов, но важно соблюсти баланс и не превратить программу в один сплошной комментарий, иногда прерывающийся на работающий код. +\paragraph{Директивы препроцессора} это такие команды, которые будут выполняться не просто до запуска программы, но даже до компиляции. +\frm{Есть мнение, что С/С++ программисты - это не программисты на языке С/С++, а программисты на языке препроцессора используемых ими компиляторов.} +В директивах препроцессора подключаются внешние заголовочные файлы, и определяются некоторые абсолютные значения проекта. Обратите внимание, что директивы препроцессора это достаточно сложный инструмент, и использовать его, например, только для определения константных значений - не лучшее архитектурное решение. Для нашего проекта нам понадобится директива \code{\#include } - эта директива подключит библиотеку стандартного ввода вывода в наш проект, что позволит нам "общаться" с пользователем нашей программы, используя терминал операционной системы (командную строку в терминах Windows) +\label{text:main} +\paragraph{Функция \code{main()\{\}}} это точка входа в программу. Программа может состоять из огромного числа файлов и функций, но операционная система как-то должна понять, откуда ей начинать исполнение программы. Такой точкой начала исполнения является функция \code{main} которая должна быть написана именно так: +\frm{\centering\code{int main(int argc, char** args)}} +более подробно о функциях, их синтаксисе и аргументах мы поговорим позднее, на данном этапе можно просто запомнить такое (или же упрощённое \code{int main()} описание главной функции любой программы на языке С. Далее в фигурных скобках пишется так называемое <<тело>> программы, то есть именно те операторы, функции и алгоритмы, которые являются программой. По сути, всё наше программирование будет происходить либо в этой функции, либо будет довольно тесно с ней связано. +\paragraph{Возврат из функции \code{return;}} это оператор явно завершающий выполнение функции \code{main} и, соответственно, программы. Все операторы, кроме директив препроцессора, комментариев и описаний тел функций должны заканчиваться точкой с запятой. +\frm{\textbf{Внимание!} Далее Вы прочитаете тезис, который является значительным упрощением реальной ситуации. Автор пошёл на такое упрощение по двум причинам: во-первых, поскольку в классическом С дела обстояли именно так, а в современных компьютерах ситуация меняется настолько быстро, что никакой текст не сможет оставаться актуальным, и во-вторых, поскольку целью данного документа не является детальное описание архитектур современных операционных систем.} +Поскольку программа написанная на языке С работает на одном уровне с операционной системой, а не в средах виртуализации, как это происходит в Java, например, она должна сообщить операционной системе, что она отработала нормально. Это делается посредством возврата в качестве результата работы программы кода ноль. В нашем случае, оператор \code{return} сообщает код \code{0}, говорящий об успешности завершения работы программы. Такой возвратный код - исторически сложившаяся договорённость между программистами: ненулевой код означает аварийное завершение программы и сообщает системе, что программа завершена некорректно и необходимо дополнительно и явно освобождать занятые ею ресурсы. +\section{Базовый ввод-вывод} +\subsection{Форматированный вывод} +Общение с пользователем на чистом С происходит через консоль. Для того, чтобы выводить какую-либо информацию для чтения пользователем - используется функция \code{printf();} предназначенная для форматированного вывода некоторого текста в консоль. Функция описана в заголовке \code{stdio.h}, поэтому мы и включили данный заголовок в нашу программу. Какого рода форматирование применяется при выводе строк в консоль? Существуют два основных инструмента придания выводу необходимого вида: экранированные последовательности (escape sequences) и заполнители (placeholders). +\paragraph{Экранированная последовательность} это буква или символ, написанные после знака обратного слэша (\code{\char`\\}), которые при выполнении программы будут на что-то заменены. Самые часто используемые это: +\begin{itemize} + \item \code{\char`\\'} - одинарная кавычка; + \item \code{\char`\\"} - двойная кавычка; + \item \code{\char`\\?} - вопросительный знак; + \item \code{\char`\\\char`\\} - обратный слэш; + \item \code{\char`\\0} - нулевой символ; + \item \code{\char`\\b} - забой (backspace); + \item \code{\char`\\f} - перевод страницы - новая страница; + \item \code{\char`\\n} - перевод строки - новая строка; + \item \code{\char`\\r} - возврат каретки; + \item \code{\char`\\t} - горизонтальная табуляция; + \item \code{\char`\\v} - вертикальная табуляция; + \item \code{\char`\\nnn} - произвольное восьмеричное значение; + \item \code{\char`\\xnn} - произвольное шестнадцатеричное значение; + \item \code{\char`\\unnnn} - произвольное Юникод-значение. +\end{itemize} +Чтобы убедится что это правильно работает выведем еще одну строку (в дополнение к коду со страницы \hyperref[code:firstprogram]{\pageref{code:firstprogram}}) с надписью <<Это новая строка>> на следующую строку нашей консоли. Также добавим к ещё одной строке символ табуляции чтобы увидеть как он работает. И сразу рассмотрим экранированную последовательность \code{\char`\\\char`\\} она делает ни что иное как добавляет символ обратного слэша в наш текст. Аналогичным образом работают и другие символьные экранирования. Это нужно, чтобы компилятор мог отличить, например, символ двойных кавычек в строке, написанной программистом, от символа двойных кавычек, завершающего строку в коде программы. Обратите внимание что не поставив в конец строки последовательность \code{\char`\\n} мы заставим компилятор постоянно писать текст на одной строке, не переходя на новую. И наконец \code{\char`\\0} сообщает компилятору что строка закончилась. Даже если у нас есть еще какие-то символы до закрывающих кавычек компилятор их просто проигнорирует. Добавим в код программы ещё пару строк, таким образом тело функции \code{main} должно принять следующий вид: +\begin{minted}{C} +printf("Hello, world!\n"); +printf("This is a new row"); +printf("This is \"\t\"tab\n"); +printf("This is \\ symbol\n"); +printf("This is a terminant \0 it ends a string"); +\end{minted} +Запустив программу мы можем убедиться, что всё работает так, как мы описали в тексте: сначала идёт приветствие миру, на новой строке сообщение о том, что это новая строка, далее на той же строке (ведь мы не переходили на новую) демонстрация пробела и символа табуляции. На последних двух строках происходит демонстрация обратного слэша и терминанта, видно, что остаток строки не был выведен на экран. +\begin{verbatim} +$ ./program +Hello, World! +This is a new rowThis is " "tab +This is \ symbol +This is terminant +$ +\end{verbatim} +\paragraph{Заполнитель} это также специальная последовательность, но она говорит компилятору, что на место этой последовательности необходимо вставить некий аргумент, который будет передан после строки, через запятую, при вызове данной функции \code{printf();}. Заполнитель начинается со знака процента и обозначает тип вставляемой переменной. +\begin{itemize} + \item \code{\%\%} - символ процента; + \item \code{\%i (\%d)} - целые числа (integer, decimal); + \item \code{\%ld (\%li)} – целые числа (long int); + \item \code{\%s} - для вывода строк; + \item \code{\%c} – для вывода символов; + \item \code{\%p} - для вывода указателей; + \item \code{\%f} – для вывода чисел с плавающей точкой; + \item \code{\%lf} – для вывода чисел с плавающей точкой удвоенной точности; + \item \code{\%x (\%X)} - беззнаковое целое в шестнадцатеричном виде. +\end{itemize} +Как видно, существуют заполнители для всех основных типов данных и для экранирования самого символа начала заполнителя. Заполнители можно и нужно комбинировать в строках как между собой, так и с экранированными последовательностями. Умение работать с заполнителями пригодится не только в консоли, но и при формировании локализованных сообщений в более сложных приложениях, в том числе на других языках. +\begin{minted}{C} +int a = 50; +printf("%d\n", a); +printf("%5d\n", a); +printf("%05d\n", a); +printf("%.2f\n", 5.12345); +printf("Заполнители это \"%5d%%\" форматирования\n", a); +\end{minted} +Так первый оператор выведет просто число. Второй это же число, но оставив для его отображения пять пробельных символов (два окажутся заняты разрядами числа 50, и ещё три останутся свободными, слева от числа). Третий оператор форматированного вывода впишет число в пять отображаемых символов, но заполнит пустоту нулями (запись с лидирующими нулями, leading zeroes). Четвёртый осуществит вывод числа с плавающей точкой, ограничив дробную часть двумя отображаемыми символами, при этом важно, что не произойдёт математического округления, символы просто не отобразятся, такое отображение часто используется для демонстрации денежных значений в долларах и центах, рублях и копейках, и пр. Последний же оператор выведет на экран надпись, информирующую о том, что заполнители - это 50\% форматирования: +\begin{verbatim} +$ ./program +50 + 50 +00050 +5.12 +Заполнители это " 50%" форматирования +$ +\end{verbatim} +Для заполнителей \code{\%d}, \code{\%i}, \code{\%f} часто используются дополнительные параметры, такие как количество знаков после запятой, например, \code{\%.2f} или минимальное количество знаков для отображения целого числа \code{\%5d}. +\subsection{Форматированный ввод} +Поговорив о выводе в консоль нельзя не сказать о пользовательском вводе данных. Один из способов пользовательского ввода данных в программу - это использование функции \code{scanf();}. Предложим пользователю ввести некоторое число: +\begin{minted}{C} +int input; +printf("Введите, пожалуйста, число: "); +scanf("%d", &input); +\end{minted} +Функция \code{scanf();} – это функция форматированного ввода. Принцип её работы очень похож на принцип работы функции \code{printf();} В двойных кавычках мы указываем в виде заполнителя тип переменной, которую ожидаем от пользователя, а в качестве дополнительного аргумента указываем адрес той переменной, в которую хотим записать введённые пользователем данные. Получается процесс прямо противоположный выводу. В этой функции можно использовать все те же заполнители, что и при выводе, поэтому пользователь может ввести как целые числа, так и символы, строки и числа с плавающей точкой. +\begin{minted}{C} +printf("Вы ввели %d, удвоим его: %d\n", input, input * 2); +\end{minted} +Выведем в консоль изменённое число, введённое пользователем, чтобы удостовериться, что всё работает. В результате запуска программы, консоль застынет в ожидании пользовательского ввода. Пользователь сообщает консоли (терминалу операционной системы) об окончании ввода нажатием клавиши \code{Enter}: +\begin{verbatim} +$ ./program +Введите, пожалуйста, число: 50 +Вы ввели 50, удвоим его: 100 +$ +\end{verbatim} +\section{Переменные и типы данных. Базовые манипуляции с данными} +\subsection{Переменные в программе на языке С} Это некие \textit{именованные контейнеры}, содержащие значение, тип которых строго описан при их создании. Названия или имена переменных не могут начинаться с цифр и спецсимволов, а также не должны повторяться (\hyperref[text:naming]{\ref{text:naming}}). +\frm{\textbf{Идентификатор} переменной - это её имя, которое для программы не существует без привязки к типизации, то есть для объявления переменной мы пишем её тип и название, что вместе составляет идентификатор. По идентификатору переменной мы можем записать в неё значение, прочитать текущее значение, узнать адрес хранения этого значения, и т.д. \textbf{Литерал} - это число или строка, которые мы пишем в тексте явно. Литералу нельзя присвоить значение, литерал это и есть значение. Литерал (ни строковый, ни числовой) нельзя изменить. Если изменить какой-то литерал, то это будет уже другой литерал, явно изменённое в коде значение. Также есть термины \code{lvalue} и \code{rvalue}. Если очень сильно упрощать, то их можно отождествить с идентификатором и литералом: \code{lvalue} - это то, \textit{куда} присваивается, \code{rvalue} - это то, \textit{что} присваивается} +Переменные делятся на целочисленные, символьные, указатели и числа с плавающей точкой (англ. floating point, дробное число). Все, кроме указателей и символьных переменных бывают как знаковыми так и беззнаковыми. То есть в знаковых самый старший бит в двоичной записи этих переменных отводится под определение, является ли число отрицательным, или положительным, в беззнаковых все биты используются для записи числа, что увеличивает его диапазон возможных значений, но позволяет записать только положительные числа. В классическом С нет булевого типа, вместо него используется целое число и значения нуля для \textbf{лжи} и \textit{любое} другое число для \textbf{истины}, обычно это единица. Об указателях и булевой алгебре мы будем подробно говорить в одном из последующих разделов. +\begin{table}[ht] + \centering + \begin{tabular}{|p{1.5cm}|p{6.6cm}|p{2.4cm}|} + \hline + Тип & Пояснение & Спецификатор формата \\ + \hline + char & Целочисленный, самый маленький из адресуемых типов, диапазон: [\textminus128, +127] & \%c \\ + \hline + short\newline short int & Тип короткого целого числа со знаком, диапазон: [\textminus32 768, +32 767] & \%hi \\ + \hline + int & Основной тип целого числа со знаком, диапазон: [\textminus2 147 483 648, +2 147 483 647] & \%i или \%d \\ + \hline + long\newline long int & Тип длинного целого числа со знаком, диапазон: [\textminus2 147 483 648, +2 147 483 647] & \%li или \%ld \\ + \hline + long long\newline long long int & Тип двойного длинного целого числа со знаком, диапазон: [\textminus9 223 372 036 854 775 808, +9 223 372 036 854 775 807] & \%lli \\ + \hline + float & Тип вещественного числа с плавающей запятой (одинарной точности) & \%f (автоматически преобразуется в double для printf()) \\ + \hline + double & Тип вещественного числа с плавающей запятой (двойной точности) & \%f(\%F) (\%lf(\%lF) для scanf())\newline\%g \%G \%e \%E \\ + \hline + long double & Тип вещественного числа с плавающей запятой, ставящийся в соответствие формату повышенной точности с плавающей запятой & \%Lf \%LF \%Lg\newline\%LG \%Le \%LE \\ + \hline + \end{tabular} + \caption{Основные типоы данных в языке С} + \label{tab:types} +\end{table} +\paragraph{Символьный тип} не такой простой, как может показаться на первый взгляд. Если вкратце, то в переменной типа \code{char} хранится число, которое можно интерпретировать как символ. По умолчанию тип знаковый, то есть может содержать значения от \textminus128 до +127, но символы в таблице ASCII\footnote{American standard code for interaction interchange}, что совершенно логично, имеют только положительные индексы, поэтому в читаемый текст в стандартном С можно превратить только латинский алфавит и некоторый набор знаков и символов, находящиеся на первых 128-ми местах в этой таблице. Также можно явно указать компилятору, что мы хотим использовать эту переменную как беззнаковую, для этого используется ключевое слово \code{unsigned}, что позволит нам хранить только положительные числа гораздо больших значений. Например для переменной типа \code{unsigned char} это будут значения от 0 до 255, а для переменной типа \code{unsigned int} можно можно хранить значения от 0 до +4.294 миллиардов с какими-то копейками. В более поздних редакциях языка были утверждены типы \code{long long} и другие, для хранения 64-х разрядных целых чисел. +\begin{minted}{C} +unsigned char symbol = 75; +printf("75 stands for: %c\n", symbol); +\end{minted} +Соответственно, программа выше выведет в терминал такую информацию: +\begin{verbatim} +$ ./program +75 stands for: K +$ +\end{verbatim} +\paragraph{Числа с плавающей точкой (дробные)} представлены двумя типами: четырёхбайтный \code{float} и восьмибайтный \code{double} (также называемый long float). Хранятся в памяти в неявном виде, а разделённые на мантиссу экспоненту и знак, что делает их одними из самых сложных в работе\footnote{Здесь имеется ввиду внутренняя работа самого компьютера, а не работа с такими типами с точки зрения программиста. Именно из-за сложности, многие старые процессорные архитектуры не имели возможности работать с переменными такого типа}. При работе с числами с плавающей точкой нужно обращать особенное внимание на тип переменной, поскольку сравнение внешне одинаковых чисел разных типов с вероятностью 99\% даст ложный результат. +\begin{minted}{C} +float real = 5,345f; // 4 bytes +double realdouble = 5,345; // 8 bytes +printf("float and double: %d\n", real == realdouble); + +int a = 10; +int b = 10; +printf("integers: %d\n", a == b); +\end{minted} +Запустим код и убедимся в этом: +\begin{verbatim} +$ ./program +float and double: 0 +integers: 1 +$ +\end{verbatim} +\paragraph{Тип данных - указатель.} Как было сказано - переменная это именованный контейнер. У каждого такого контейнера есть свой собственный адрес в оперативной памяти. Язык С позволяет узнать этот адрес и работать с ним. Оператор взятия адреса это знак амперсанд (\&), написанный перед именем переменной. То есть у любой переменной всегда есть значение и адрес где это значение хранится. Для вывода в консоль адреса используется специальный заполнитель - \code{\%p}. +\begin{minted}{C} +printf("Переменная а имеет значение: %d \n", a); +printf("Переменная а хранится по адресу: %p \n", &a); +\end{minted} +При неоднократном запуске кода можно обратить внимание, что первые цифры в адресе всегда остаются неизменными, а последние меняются редко, это связано с тем, что в современных операционных системах пользователю для его работы чаще всего выделяется некоторое адресное пространство, которое потом просто переиспользуется и перезаписывается. При запуске точно такого же кода на Вашем компьютере, Вы увидите, что адрес хранения переменной наверняка будет отличаться, это нормально. +\begin{verbatim} +$ ./program +Переменная а имеет значение: 10 +Переменная а хранится по адресу: 0x7ffe6aa136cf +$ +\end{verbatim} +\subsection{Базовая арифметика} +Раз уж в предыдущем разделе мы коснулись арифметических выражений, поговорим немного об арифметике. +\paragraph{Простые арифметические операции.} В языке С поддерживаются все базовые арифметические операции, такие как сложение, вычитание, умножение, деление. Операции бинарной арифметики (булевой алгебры), такие как \code{И}, \code{ИЛИ}, \code{НЕ}, \code{ИСКЛЮЧАЮЩЕЕ ИЛИ}, \code{СДВИГИ}. А также все вышеперечисленные операции с последующим присваиванием в первую переменную. Для начала, инициализируем переменную типа \code{int} значением, например, \code{70}, и выведем ее в консоль. Мы можем производить с этой переменной все привычные базовые арифметические манипуляции, ровно также, как мы можем производить эти манипуляции с литералом, который ей присваивается: +\begin{minted}{C} +int variable = 70; +printf("Переменная variable = %d\n", variable); +printf("Переменная variable = %d\n", variable + 10); +variable = variable + 50; +printf("Переменная variable = %d\n", variable); +variable = 123 + 50 * 12; +printf("Переменная variable = %d\n", variable); +\end{minted} +То есть, нам доступны операции сложения, умножения, вычитания и деления. Как видно в результате работы программы, есть прямая зависимость между действительным значением переменной и порядком присваивания в неё значения, так в третьем выводе значение равно 120, то есть $70 + 50$, а значит во втором выводе присваивания нового значения $70 + 10$ в переменную \code{variable} не произошло. +\begin{verbatim} +$ ./program +Переменная variable = 70 +Переменная variable = 80 +Переменная variable = 120 +Переменная variable = 723 +$ +\end{verbatim} +\paragraph{Оператор деления} заслуживает особенного внимания, поскольку у него есть два режима работы, отличающие этот оператор от привычной нам арифметики: если мы производим операции с целыми числами такими как \code{int}, \code{short} или \code{char} оператор деления всегда будет возвращать только целые числа, \textbf{отбросив дробную часть}. Это происходит из-за оптимизаций компилятора, то есть если операнды - это целые числа, то и результатом по мнению компьютера может быть только целое число, а из-за строгости типизации мы не можем положить в целочисленную переменную значение с плавающей точкой. + +Таким образом, отслеживание точности вычислений полностью возлагается на плечи программиста. Важно, что компилятор нам в этом помогает, хоть и не делает всю работу за нас. Компилятор, при преобразовании операций автоматически приводит типы операндов к наиболее подходящему и широкому. То есть, если мы, например, складываем два числа \code{int} и \code{char}, то \code{char} будет автоматически расширен до \code{int}, потому что максимальное значение \code{char} точно поместится в переменную типа \code{int}, а максимальное значение \code{int} точно никак не сможет поместиться в переменную с типом \code{char}. Точно также если умножать \code{int} и \code{float}, то \code{int} будет преобразован во \code{float} по той же причине - \code{int} совсем никак не умеет работать с плавающей точкой, а \code{float} вполне может содержать число без дробной части. Из-за этого языки С/С++ считаются слабо типизированными. +\begin{minted}{C} +variable = 10; +variable = variable / 3; +printf("Переменная variable = %d\n", variable); + +float var = 10; +var = var / 3; +printf("Переменная var = %f\n", var); +\end{minted} +При использовании оператора деления с целочисленными операндами теряется точность вычислений, что недопустимо. +\frm{Чтобы оператор деления отработал в не целочисленном режиме, нужно, чтобы хотя бы один операнд был не целочисленным.} +Чаще всего целочисленные переменные используют в качестве счётчиков, индексов и других вспомогательных переменных, поэтому математические операции с ними весьма распространены. +\begin{verbatim} +$ ./program +Переменная variable = 3 +Переменная var = 3.333333 +$ +\end{verbatim} +\paragraph{Деление по модулю.} Также особенного внимания заслуживает оператор получения остатка от деления, иногда называемый оператором взятия по модулю. Записывается как символ \code{\%} и возвращает остаток от целочисленного деления первого числа на второе: +\begin{minted}{C} +int remain = variable % 5; +printf("Остаток от деления %d на %d: %d\n", variable, 5, remain); +variable = variable + 50; +printf("Переменная variable = %d\n", variable); +variable += 50; +printf("Переменная variable = %d\n", variable); +\end{minted} +Любые арифметические операции можно выполнить с последующим присваиванием в первую переменную. То есть это означает, что запись вида \code{variable = variable + 50;} можно сократить до \code{variable += 50;} и запустив следующий код мы можем убедиться, что начальное значение переменной увеличилось сначала на 50, а затем ещё на 50. +\begin{verbatim} +$ ./program +Остаток от деления 3 на 5: 3 +Переменная variable = 53 +Переменная variable = 103 +$ +\end{verbatim} +\paragraph{Инкремент и декремент.} Так, бегло рассмотрев арифметику в языке С нельзя не упомянуть об операторах увеличения и уменьшения значения переменной на единицу с последующим присваиванием. Они называются операторами инкремента (\code{++}) и декремента (\code{\textminus\textminus}). Это унарный оператор, поэтому записывается со своим операндом строго без пробелов: \code{variable++;} и редко используется как самостоятельный оператор на отдельной строке. У операторов инкремента и декремента есть два вида записи: префиксный и постфиксный. Их отличает время применения текущего значения и его изменения. При постфиксной записи, сначала происходит применение текущего результата, а затем его изменение, а при префиксной записи - сначала изменение, а затем применение. Например: +\begin{minted}{C} +variable = 50; +printf("1. Пост-инкремент: %d\n", variable++); +printf("1. Следующая строка: %d\n", variable); +variable = 50; +printf("2. Пре-инкремент: %d\n", ++variable); +printf("2. Следующая строка: %d\n", variable); +\end{minted} +В результате выполнения этого кода мы можем видеть на экране следующий результат: +\begin{verbatim} +$ ./program +1. Пост-инкремент: 50 +1. Следующая строка: 51 +2. Пре-инкремент: 51 +2. Следующая строка: 51 +$ +\end{verbatim} + +\subsection{Булева алгебра и двоичные вычисления} +\paragraph{Двоичная система счисления} представляет особенный интерес для области информационных технологий, поскольку вся электроника работает по принципу «есть напряжение или нет напряжения», единица или ноль. Все числа из любых систем счисления в результате преобразуются в двоичную и представляются в виде набора единиц и нулей. Так для записи десятичного числа \code{116} используется двоичная запись \code{1110100}. Преобразование из системы счисления с бОльшим основанием в систему счисления с меньшим основанием производится последовательным делением исходного числа на основание системы счисления и записи остатков такого деления в младшие разряды. Например: +\[ +\frac{116}{2} = \frac{58 (0)}{2} = \frac{29 (0)}{2} = \frac{14 (1)}{2} = \frac{7 (0)}{2} = \frac{3 (1)}{2} = \frac{1 (1)}{2} = 1 < 2 +\] + +В этом примере полученные остатки от деления записаны в скобках и можно обратить внимание на то, что они полностью повторяют запись числа \code{116} показанную ранее в зеркальном отражении. Обратное преобразование - это последовательное умножение разрядов числа на величину каждого разряда с их аккумулированием к общему результату: +\begin{equation*} +\begin{gathered} +1110100 = 0*2^0 + 0*2^1 + 1*2^2 + 0*2^3 + 1*2^4 + 1*2^5 + 1*2^6 = \\ 0*1 + 0*2 + 1*4 + 0*8 + 1*16 + 1*32 + 1*64 = \\ 4 + 16 + 32 + 64 = 116 +\end{gathered} +\end{equation*} +Поскольку двоичная система счисления является основной для компьютерной техники, помнить, например, значения степеней двойки - обычно, хорошее подспорье в работе. +\paragraph{Булева алгебра} это один из базовых, но вместе с тем один из самых мощных инструментов в программировании. Двоичные вычисления выполняются быстрее десятичных, поскольку являются естественными для цифровой техники. В бинарной алгебре используются операторы \code{И (\&)}, \code{ИЛИ (|)}, \code{НЕ (\~{})}, \code{ИСКЛЮЧАЮЩЕЕ ИЛИ (\^{})} и операции \code{СДВИГА} влево (\code{$<<$}) и вправо(\code{$>>$}). Работают эти операторы относительно разрядов двоичного представления чисел, где истина – это единица, а ложь - это ноль. +\frm{Разница между логическими и арифметическими бинарными операторами в представлении операндов: логические оперируют числовыми литералами и переменными целиком, а арифметические числами поразрядно. Работу логических операторов мы рассмотрим в следующем разделе.} +Условия истинности двоичных арифметических операторов следующие: +\begin{itemize} + \item оператор \code{И} возвращает единицу только когда оба операнда единицы; + + \begin{tabular}{|c|c|c|} + \hline + операнд & операнд & результат \\ + \hline + 0 & 0 & 0 \\ + \hline + 0 & 1 & 0 \\ + \hline + 1 & 0 & 0 \\ + \hline + 1 & 1 & 1 \\ + \hline + \end{tabular} + \item оператор \code{ИЛИ} возвращает единицу когда хотя бы один из операндов единица; + + \begin{tabular}{|c|c|c|} + \hline + операнд & операнд & результат \\ + \hline + 0 & 0 & 0 \\ + \hline + 0 & 1 & 1 \\ + \hline + 1 & 0 & 1 \\ + \hline + 1 & 1 & 1 \\ + \hline + \end{tabular} + \item оператор \code{НЕ} возвращает единицу когда операнд равен нулю; + + \begin{tabular}{|c|c|} + \hline + операнд & результат \\ + \hline + 0 & 1 \\ + \hline + 1 & 0 \\ + \hline + \end{tabular} + \item оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ} возвращает единицу когда операнды различаются. + + \begin{tabular}{|c|c|c|} + \hline + операнд & операнд & результат \\ + \hline + 0 & 0 & 0 \\ + \hline + 0 & 1 & 1 \\ + \hline + 1 & 0 & 1 \\ + \hline + 1 & 1 & 0 \\ + \hline + \end{tabular} +\end{itemize} + +На основе этих знаний мы можем для примера написать программу, меняющую местами значения переменных без использования третьей, вспомогательной и быть уверенными, что переполнения переменных не произойдёт, как это могло бы произойти, например, при использовании сложения и обратного вычитания. Объявим две переменных \code{a} и \code{b}, присвоим им значения и выведем их в консоль. Также подготовим вывод измененных значений \code{a} и \code{b} в консоль: +\begin{minted}{C} +char a = 11; +char b = 15; +printf("a = %d, b = %d\n", a, b); +// here will be the swapping algorithm +printf("a = %d, b = %d\n", a, b); +\end{minted} +Далее, напишем некую конструкцию, которая при детальном изучении не представляет из себя никакой магии. В переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в переменную \code{b} нужно будет записать результат вычисления \code{b \^{} a} и наконец в переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в коде ниже будет приведена сразу сокращённая запись: +\begin{minted}{C} +a ^= b; +b ^= a; +a ^= b; +\end{minted} +Нужно сразу оговориться, что этот алгоритм может некорректно работать с одинаковыми и отрицательными числами, это будет зависеть от компилятора, поэтому, если включать этот алгоритм в состав более сложных, лучше осуществлять дополнительные проверки. Вывод этой конкретной программы будет следующим: +\begin{verbatim} +$ ./program +a = 11, b = 15 +a = 15, b = 11 +$ +\end{verbatim} +Дополнительно, для написания этого документа был проведён ряд тестов: +\begin{verbatim} +Test project ~/Documents/c-basic/build +1/7 Test #1: Swap.TwoPosNumbers ..... Passed 0.00 sec +2/7 Test #2: Swap.SamePosNumbers .... Passed 0.00 sec +3/7 Test #3: Swap.OneNegNumber ...... Passed 0.00 sec +4/7 Test #4: Swap.TwoNegNumbers ..... Passed 0.00 sec +5/7 Test #5: Swap.SameNegNumbers .... Passed 0.00 sec +6/7 Test #6: Swap.BareOverflow ...... Passed 0.00 sec +7/7 Test #7: Swap.OverflowNumbers ...***Failed 0.00 sec + +86% tests passed, 1 tests failed out of 7 +Total Test time (real) = 0.04 sec +\end{verbatim} +Ожидаемо, не прошёл тест, в котором присутствовало переполнение переменной, этот случай также был отмечен предупреждением компилятора о том, что программист пытается присвоить переменной значение, большее, чем переменная способна вместить. +\frm{Здесь был преднамеренно использован тест с провальным результатом, для более явной демонстрации происходящего внутри алгоритма, и потому что нам не нужна дальнейшая компиляция продакшн кода. Обычно, тест-кейсы с ожидаемым провалом пишутся с инверсией проверки, то есть, если мы ожидаем, что некоторые значения не будут равны эталонным, необходимо проверять значения на неравенство, таким образом все тест-кейсы пройдут успешно.} +Рассмотрим происходящее для приведённого примера кода пошагово: оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ} выполняется следующим образом: результат будет равен \code{1} если операнды (в данном случае, разряды двоичного представления числа) различаются и \code{0} если они совпадают. Изначально имеем две переменных, \code{a} и \code{b} - число \code{11} типа \code{char} (в двоичном представлении это \code{00001011}), и число \code{15} (это \code{00001111}). В коде ниже можно наглядно рассмотреть работу оператора \code{ИСКЛЮЧАЮЩЕГО ИЛИ}: +\begin{verbatim} +// a = 11 (00001011) +// b = 15 (00001111) +a = a ^ b; //00000100 +\end{verbatim} +После выполнения первого оператора в переменную \code{a} будет положено промежуточное число \code{00000100} – это цифра \code{4}, а в переменной \code{b} останется число \code{15 (00001111)}. Ко второму действию мы приходим с начальными значениями: \code{a = 4 (00000100)}, \code{b = 15 (00001111)}, производим операцию \code{ИСКЛЮЧАЮЩЕГО ИЛИ} с последующим присваиванием в переменную \code{b} и получаем \code{00001011} – т.е. \code{b = 11 (00001011)}. +\begin{verbatim} +// b = 15 (00001111) +// a = 4 (00000100) +b = b ^ a; //00001011 +\end{verbatim} +И после выполнения третьего оператора \code{ИСКЛЮЧАЮЩЕГО ИЛИ} в переменную \code{a} будет положено значение \code{00001111} – это цифра \code{15}. +\begin{verbatim} +// a = 4 (00000100) +// b = 11 (00001011) +a = a ^ b; //00001111 +\end{verbatim} +\paragraph{Операции сдвига} бывают логические, арифметические и циклические. В языке С реализован логический сдвиг, то есть недостающие разряды при сдвиге заполняются нулями. Итак, допустим, что нам нужно переменную \code{a} сдвинуть влево на \code{3} бита, на самом деле это означает что мы переменную \code{a} умножим на $2^3$. А переменную \code{b} мы сдвинем вправо на \code{2} бита, это означает, что мы переменную \code{b} разделим на $2^2$, при этом важно упомянуть, что будет произведено целочисленное деление. +\frm{\textbf{Сдвиг влево} числа k на n - это \textit{умножение} $k*2^n$. \textbf{Сдвиг вправо} числа k на n - это \textit{целочисленное деление} $\frac{k}{2^n}$.} +Это тоже самое что записать $a * 8$ и $b / 4$. Просто на маломощных компьютерах выполнится это гораздо быстрее. Бинарная алгебра это большая и сложная тема. +\begin{minted}{C} +a = 15; //00001111 +b = 11; //00001011 +printf("a = %d", a); +a = a << 3; // 15 * 8 +printf("a = %d", a); //a = 120; //01111000 +printf("b = %d", b); +b = b >> 2; // 11 / 4 +printf("b = %d", b); //b = 2; //00000010 +\end{minted} +Применять бинарную алгебру можно и в больших проектах, работающих со сложными высокоуровневыми абстракциями. Поддержка бинарных операций есть в подавляющем числе языков программирования. +\begin{verbatim} +$ ./program +a = 15 +a = 120 +b = 11 +b = 2 +$ +\end{verbatim} +\section{Условия, блоки кода, видимость} +\subsection{Условный оператор} +\paragraph{\code{if()}} пожалуй, самый часто используемый в любом языке программирования, в том числе и в языке С оператор. Оператор \code{if()} позволяет программе принять решение о выполнении или невыполнении того или иного действия в зависимости от текущего состояния. В случае, если условие в круглых скобках выполнится, выполнится и последующий код, который чаще всего пишется в фигурных скобках. Если условие в круглых скобках не выполнится, то все операторы внутри идущих следом фигурных скобок будут проигнорированы. + +Например, зададим пользователю вопрос, хочет ли он, чтобы его поприветствовали, для этого опишем переменную \code{char answer}, которая будет хранить ответ пользователя в виде символа и спросим у пользователя в терминале, хочет ли он, чтобы мы его поприветствовали, выведем на экран строку с приглашением. Далее при помощи уже знакомой нам функции \code{scanf();} считаем ответ пользователя в переменную, и, в зависимости от пользовательского ввода, программа либо поприветствует пользователя, либо нет, это решение будет принято с помощью оператора \code{if()}. +\begin{minted}{C} +char answer; +printf("do you want me to salute you (y/n)? "); + +scanf ("%s", &answer); +if (answer == 'y') { + printf("Hello, user"); +} +\end{minted} +\paragraph{\code{else}} Зачастую складываются ситуации, когда нужно выполнить разные наборы действий, в зависимости от результата проверки условия. Для таких случаев используется дополнение к оператору \code{if()} - оператор \code{else}, в котором описывается последовательность действий выполняемая в случае если условие в круглых скобках дало ложный результат. Код немного изменится, не будем приводить повторяющиеся части, взаимодействия с пользователем, сконцентрируемся на условном операторе: +\begin{minted}{C} +if (answer == 'y') { + printf("Hello, user"); +} else { + printf("I didn't want to salute you anyway"); +} +\end{minted} +Как вы видите, в зависимости от того что ввел пользователь мы реализуем ту или иную ветку оператора \code{if}-\code{else}. Конструкция \code{if}-\code{else} является единым оператором выбора, то есть выполнив код в фигурных скобках после \code{if} программа не станет выполнять код в \code{else}, и наоборот. + +\paragraph{\code{else if()}} Множественный выбор при помощи оператора \code{if} можно осуществить используя конструкцию \code{if}-\code{else if}-\code{else}. Данное усложнение также будет являться единым оператором выбора. Добавим в нашу конструкцию еще одно условие и опишем поведение для ответа <<да>> и ответа <<нет>>. В этом примере оператором \code{else} будет непонимание программы того что ввел пользователь. Выведем в консоль надпись <<Я не могу понять Ваш ввод>>. +\begin{minted}{C} +if (answer == 'y') { + printf("Hello, user"); +} else if (answer == 'n') { + printf("I didn't want to salute you anyway"); +} else { + printf("I can't understand your input"); +} +\end{minted} +Операторов \code{else if} в одном операторе выбора может быть сколько угодно, в отличие от оператора \code{if} и оператора \code{else} которых не может быть больше одного. +\begin{verbatim} +$ ./program +do you want me to salute you (y/n)? y +Hello, user +$ ./program +do you want me to salute you (y/n)? n +I didn't want to salute you anyway +$ ./program +do you want me to salute you (y/n)? x +I can't understand your input +$ +\end{verbatim} +\paragraph{Тернарный оператор.} Для короткой или внутристрочной записи условного оператора, а также для присваивания переменных по условию можно использовать \textbf{тернарный оператор}, также называемый оператором условного перехода и записываемый с помощью следующего синтаксиса: \code{(условие) ? истина : ложь}. Например, создадим три целочисленные переменные \code{а}, \code{b}, \code{c} и зададим двум из них какие-нибудь начальные значения, допустим \code{а = 10} и \code{b = 15}. Поставим себе задачу: присвоить переменной \code{c} наименьшее из значений \code{а} или \code{b}. Если мы будем использовать только что изученный нами оператор \code{if}-\code{else} у нас должен получиться такой код: +\begin{minted}{C} +int a = 10; +int b = 15; +int c; +if (a > b) { + c = b; +} else { + c = a; +} +\end{minted} +Запись условного оператора можно значительно сократить, поскольку в теле оператора происходит выбор: какое значение присвоить в \code{c} по условию в круглых скобках. Условие оставляем, а \code{а} и \code{b} переносим в секции истины и лжи, соответственно. Получим запись вида: +\begin{minted}{C} +int a = 10; +int b = 15; +int c = (a > b) ? b : a; +\end{minted} +которая будет обозначать, что в случае если \code{a > b}, в переменную \code{c} запишется значение {b}, и наоборот если \code{b > a}, то в переменную \code{c} запишется значение \code{а}. + +Также тернарный оператор можно использовать для удобного форматированного вывода, например опишем функцию \code{printf();} которая будет печатать нам строку, и в зависимости от условия, это будет \code{"true"} либо \code{"false"}: +\begin{minted}{C} +printf("%s", (1 > 0) ? "true" : "false"); +\end{minted} +Проверим как это работает, и в результате видим true, потому что единица действительно больше нуля. +\begin{verbatim} +$ ./program +true +$ +\end{verbatim} +\paragraph{Вложенные условия и сокращения} +Внутри фигурных скобок конструкций \code{if()} находится код программы, поэтому там могут находиться и другие условные операторы. Условия, расположенные таким образом называются вложенными. Никаких ограничений на использование вложенных условий в языке С нет. В примере ниже показано, что условия всегда выполняются (единица в круглых скобках будет означать, что условие всегда истинно), а комментариями с многоточием показано, где может располагаться код программы. +\begin{minted}{C} +if (1) { + // операторы языка + if (1) { + //операторы языка + } + // операторы языка +} +\end{minted} +Также важно отметить, что если после условных операторов следует только один оператор языка, как в примере ниже, то использование фигурных скобок не обязательно, хотя и считается хорошим тоном писать их всегда: +\begin{minted}{C} +// условие с одним оператором +if (1) { + // единственный оператор +} + +// также условие с одним оператором +if (1) + // единственный оператор + +// и ещё одно условие с одним оператором +if (1) + // единственный оператор +else + // единственный оператор + +\end{minted} +При использовании такой записи можно легко допустить ошибку: забыть о необходимости объединения кода фигурными скобками, и предполагать, что несколько операторов могут выполниться по условию без фигурных скобок. +\frm{Часто это заблуждение приводит к трудноуловимым ошибкам в коде, когда программа компилируется, запускается, но работает не так, как следует.} +\subsection{Операции сравнения} +\paragraph{Арифметическое сравнение} это знакомые и привычные нам со школы операторы <<больше>> (\code{>}), <<меньше>> (\code{<}), <<больше или равно>> (\code{>=}), <<меньше или равно>> (\code{<=}), а также в языке С присутствуют в виде отдельных операторов <<проверка на равенство>>, которая записывается в виде двух знаков равенства (\code{==}), и <<проверка на неравенство>>, которая записывается в виде восклицательного знака и символа равенства (\code{!=}). Возвращают истину, когда выполняются соответствующие названиям условия и ложь, когда условия не выполняются, что очевидно. +\paragraph{Логических операторов} три: это \code{И (\&\&)}, \code{ИЛИ (||)}, \code{НЕ (!)}. В отличие от арифметических двоичных операторов - логические возвращают истину или ложь т.е. в случае языка С - \code{1} либо \code{0} и работают с операндами слева и справа целиком, а не поразрядно. В этом легко убедиться, попытавшись вывести на экран результат сравнения с заведомо неверным форматированием и получив ошибку, говорящую о том, что компилятор ожидал от нас число, а мы хотим его отформатировать в строку. +\begin{minted}{C} +printf("%s\n", 1 == 1); +\end{minted} +Некоторые компиляторы выдают ошибку на этапе компиляции, некоторые компилируют такой код, но программа не сможет выполниться и выдаст ошибку \textbf{Segmentation fault}. Это зависит от большого количества факторов, таких как тип операционной системы, тип и версия компилятора, версия используемого стандарта языка. + +Отдельного внимания заслуживает применение оператора поразрядного (арифметического) \code{ИСКЛЮЧАЮЩЕГО ИЛИ} в качестве логического. В случае такого применения оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ}, фактически, дублирует сравнение на неравенство, это легко объяснить, проведя анализ происходящего с числами при таком сравнении: +\begin{minted}{C} +int a = 11; //00001011 +int b = 11; //00001011 + // ^ 00000000 +if (a ^ b) { + printf("numbers are not equal\n"); +} +\end{minted} +Данный код внутри фигурных скобок оператора \code{if()} никогда не выполнится, поскольку в С оператор сравнения работает с числами, интерпретируя ноль как ложь, а любое ненулевое значение как истину, мы наблюдаем, что побитовое \code{ИСКЛЮЧАЮЩЕЕ ИЛИ} - это тоже самое, что проверка на неравенство. +\begin{itemize} + \item оператор \code{И (\&\&)} возвращает истину только когда оба операнда истинны; + + \begin{tabular}{|c|c|c|} + \hline + операнд & операнд & результат \\ + \hline + 0 & 0 & 0 \\ + \hline + 0 & 1 & 0 \\ + \hline + 1 & 0 & 0 \\ + \hline + 1 & 1 & 1 \\ + \hline + \end{tabular} + \item оператор \code{ИЛИ (||)} возвращает истину когда хотя бы один из операндов истинный; + + \begin{tabular}{|c|c|c|} + \hline + операнд & операнд & результат \\ + \hline + 0 & 0 & 0 \\ + \hline + 0 & 1 & 1 \\ + \hline + 1 & 0 & 1 \\ + \hline + 1 & 1 & 1 \\ + \hline + \end{tabular} + \item оператор \code{НЕ (!)} возвращает истину когда операнд ложный; + + \begin{tabular}{|c|c|} + \hline + операнд & результат \\ + \hline + 0 & 1 \\ + \hline + 1 & 0 \\ + \hline + \end{tabular} +\end{itemize} + +Используя логические операторы в программе мы можем написать логику практически любой сложности. В языке С нет ограничений на использование сложных условий. Сложные условия это такие, где в круглых скобках выполняется более одного сравнения. Сравнения производятся в порядке заранее оговоренного приоритета. В списке ниже указаны операторы в порядке уменьшения их приоритета: +\begin{enumerate} + \item $!$ + \item $<, <=, >, >=$ + \item $==, !=$ + \item $\&\&$ + \item $||$ +\end{enumerate} +Приведём короткий пример: дана некоторая целочисленная переменная и нужно выяснить, не выходит ли эта переменная за рамки заданных значений, например от нуля до десяти. Условие можно будет скомбинировать так: \code{x >= 0 \&\& x <= 10}. В случае его истинности - выдадим сообщение о том, что \code{х} подходит. +\begin{minted}{C} +int x = 7; +if ((x >= 0) && (x <= 10)) { + printf("X Fits!\n"); +} +\end{minted} +В данной записи мы видим, что сначала \code{х} сравнивается с нулём, затем с десятью, и в конце результаты будут сравнены между собой. +\begin{verbatim} +$ ./program +X Fits! +$ +\end{verbatim} +Самый не приоритетный оператор тернарный, внимательный читатель мог заметить, что он даже не вошёл в список выше, это сделано, поскольку использование тернарного оператора внутри условий нежелательно. Тернарный оператор внутри условий резко снижает читаемость кода и усложняет его интерпретацию. Если Вы сомневаетесь в приоритете сравнений или Вам необходимо описать какое-то очень сложное условие, всегда можно воспользоваться простыми математическими круглыми скобками, задав приоритет операций явно. В таком случае в первую очередь будут выполнены операции в скобках. +\subsection{Блоки кода и область видимости} +Говоря об операторах языка С и управляющих конструкциях нельзя не сказать о <<блоках кода>> и <<областях видимости>>. Как видно, условные операторы содержат записи в фигурных скобках. В такие же скобки заключён код функции \code{main}. Эти скобки называются <<операторными>>, а то что в них содержится, называется <<блоком кода>> или <<телом>> оператора или функции. Все переменные, которые инициализируются внутри блока кода, существуют и <<видны>> только внутри кодового блока. Поэтому пространство между операторными скобками также называют <<областью видимости>>. +\begin{minted}{C} +int x = 7; +if ((x >= 0) && (x <= 10)) { + int var = 0; + printf("X Fits!\n"); +} +printf("%d", var); +\end{minted} +На этом примере можно увидеть, что мы не можем напечатать значение переменной \code{var}, поскольку она была создана внутри блока кода оператора \code{if()} и перестала существовать для нашей программы как только мы вышли за его пределы. Такой код даже не скомпилируется: +\begin{verbatim} +$ gcc -o program main.c +error: 'var' undeclared (first use in this function); + did you mean 'char'? +printf("%d", var); + ^~~ + char +$ +\end{verbatim} +\section{Циклы} +Теперь, когда мы узнали, что можно делать в программе с использованием условий, пришло время познакомиться с таким базовым понятием программирования, как цикл. +\frm{Цикл - это одно или несколько действий повторяющихся до тех пор пока не наступит условие, прекращающее эти действия.} +С помощью циклов в программировании выполняются все рутинные задачи, такие как поиск значений в больших наборах данных, создание разного рода прогрессий, построение графиков, сортировки, ожидание ответов на запросы, чтение потоков данных и многие другие. +\subsection{Операторы циклов} +\subsubsection{\code{while()}} +Базовый цикл в языке С записывается при помощи ключевого слова \code{while()} после которого в круглых скобках пишется условие. При истинности данного условия будет выполняться тело цикла, которое в свою очередь пишется в фигурных скобках. Общий внешний вид очень похож на условный оператор, с той лишь разницей, что по окончании выполнения операторов внутри фигурных скобок мы переходим не на следующую строку, а обратно в проверку условия. + +Для примера, выведем на экран все числа в заданном промежутке, границы которого мы обозначим как \code{а} и \code{b}. Для этого нам необходимо их инициализировать, то есть объявить и задать начальные значения, и пока \code{а} меньше \code{b} заходить в тело цикла где мы будем выводить на экран и инкрементировать меньшее число, пока оно не станет равным второму. Как только числа сравняются условие входа в тело цикла перестанет быть истинным, и мы не зайдём в него, оказавшись на следующей после тела цикла строке. +\begin{minted}{C} +int a = 10; +int b = 20; +while (a < b) { + printf("%d ", a++); +} +\end{minted} +Запустим программу и убедимся что все работает: +\begin{verbatim} +$ ./program +10 11 12 13 14 15 16 17 18 19 +$ +\end{verbatim} +В данном коде мы использовали оператор инкремента в постфиксной записи, которая означает, что значение переменной \code{а} сначала будет передано функции вывода на экран, а уже потом увеличено на единицу. +\frm{Один проход тела цикла и возврат в управляющую конструкцию называется \textbf{итерацией}, этот термин можно очень часто услышать в беседах программистов, а зачастую и не только в беседах о программировании.} +Также допустимо использовать префиксную запись оператора инкремента, при которой, как не сложно догадаться, значение сначала будет увеличено, а уже потом передано функции вывода на экран. +\begin{minted}{C} +int a = 10; +int b = 20; +while (a < b) { + printf("%d ", ++a); +} +\end{minted} +Запустим снова, чтобы увидеть разницу: после запуска первого варианта мы увидели в терминале значения от 10 до 19, а после запуска второго варианта значения от 11 до 20. +\begin{verbatim} +$ ./program +11 12 13 14 15 16 17 18 19 20 +$ +\end{verbatim} +Давайте напишем ещё один пример, в котором подсчитаем сколько чётных чисел на промежутке от \code{а} до \code{b}. для этого нам понадобится циклически пройтись по всем числам от \code{а} до \code{b} и в каждой итерации цикла, то есть для каждого числа, сделать проверку, является ли число чётным. Если является - увеличить счётчик чётных чисел для заданного промежутка. После того, как перестанет выполняться условие в скобках и наш цикл будет завершён, выведем в консоль количество чётных чисел. +\begin{minted}{C} +int a = 10; +int b = 20; +int evens = 0; +while (a < b) { + if (a % 2 == 0) + evens++; + a++; +} +printf("There are %d even numbers in sequence", evens); +\end{minted} +Запустим нашу программу и убедимся в правильности ее работы. +\begin{verbatim} +$ ./program +There are 5 even numbers in sequence +$ +\end{verbatim} +Программа выдала результат: пять чётных чисел, давайте пересчитаем вручную: 10, 12, 14, 16, 18, значение 20 не войдёт в результат работы программы, поскольку на последней итерации, когда \code{а = 20} условие в круглых скобках окажется ложным: двадцать не меньше двадцати. А раз условие не выполнено мы не попадаем в тело цикла. + +Цикл \code{while()} используется когда мы не можем достоверно сказать сколько итераций нам понадобится выполнить. На этом примере мы видим что тело цикла может быть любой сложности, оно может содержать как условные операторы так и другие циклы. +\subsubsection{\code{do \{\} while();}} +Простой цикл \code{while()}, по очевидным причинам, попадает в категорию циклов с предусловием. Сначала программа проверяет условие, затем, по результатам этой проверки, либо выполняет либо не выполняет тело цикла. А раз есть циклы с предусловием, то логично предположить наличие циклов с постусловием. Как ни удивительно, в языке С есть и такой. Это разновидность цикла \code{while()}, который записывается как ключевое слово \code{do \{тело цикла\} while (условие);}, в этой конструкции сначала гарантированно один раз выполнится тело цикла, и только потом будет принято решение, нужно ли выполнять его снова. Такие циклы широко применяются для проверки пользовательского ввода до получения приемлемого результата и для ожидания ответов на запросы, что логично, поскольку глупо было бы ожидать ответ, не послав запрос. + +Например, мы пишем калькулятор, и знаем, что в арифметике числа нельзя делить на ноль. Предположим, что наш пользователь этого не знает. Что ж, будем предлагать пользователю ввести делитель, пока он не введёт что-то отличное от нуля. Если пользовательский ввод равен нулю, значит нам нужно спросить его снова. Это и будет условием для очередной итерации цикла. Когда пользователь введёт удовлетворяющее нашему условию число произведём необходимые подсчёты и выведем их результаты в консоль. +\begin{minted}{C} +int input; +do { + printf("Enter a divider for 100, "); + printf("remember, you can’t divide by zero: "); + scanf("%d", &input); +} while (input == 0); + +printf("100 / %d = %d", input, 100 / input); +\end{minted} +Запустим программу. Каждый раз когда мы вводим ноль, программа будет повторно задавать нам вопрос. +\begin{verbatim} +$ ./program +Enter a divider for 100, remember, you can’t divide by zero: 0 +Enter a divider for 100, remember, you can’t divide by zero: 0 +Enter a divider for 100, remember, you can’t divide by zero: 5 +100 / 5 = 20 +$ +\end{verbatim} +Когда введем любое другое число получим результат нашего деления. +\subsubsection{\code{for(;;)}} +Зачастую, складываются ситуации, когда мы точно знаем, сколько итераций цикла нам понадобится, например, когда мы последовательно проверяем содержимое созданного нами числового ряда, или заполняем значениями таблицы, границы которых заранее известны. В конце концов, для подсчёта среднего арифметического некоторого конечного числового ряда. В этих ситуациях принято использовать. Это цикл с предусловием, где заранее отведено место для инициализации переменной-счётчика, условия захода в следующую итерацию цикла и изменения переменной счетчика. В более поздних версиях С (C99) появилась возможность объявлять переменную счетчик прямо в управляющей конструкции. +\frm{Для применения более позднего стандарта в проекте, необходимо установить специальный ключ компиляции \code{-std=c99}, то есть полная команда компиляции исходного кода для транслятора GCC будет выглядеть так: + +\centering\code{gcc program.c -std=c99 -o program}} +В классическом С необходимо объявить переменную счетчик заранее, а в управляющей конструкции можно только задать ей начальное значение. +Условие захода в следующую итерацию цикла это логическое выражение, которое может принимать два значения: истина и ложь. Если условие истинно - идём на следующую итерацию (исполняем тело цикла), если ложно – выходим из цикла и перемещаемся на следующий после цикла оператор. +\begin{minted}{C} +// классический стиль +int i; +for (i = 0; /*условие*/; /*приращение*/) { + // тело цикла +} + +// С99 и позднее +for(int i = 0; /*условие*/; /*приращение*/) { + // тело цикла +} + +// Например: +int i; +for (i = 0; i < 5; i++) { + printf("%d ", i); +} +\end{minted} +Цикл из примера выше выведет на экран числа от нуля до четырёх. На каждой итерации мы будем инкрементировать значение \code{i}, соответственно, пока логическое условие \code{i < 5} верно, мы будем заходить в тело цикла, а как только \code{i} станет равным пяти, логическое условие вернет ложь (\code{0}) и мы из него выйдем. +\begin{verbatim} +$ ./program +0 1 2 3 4 +$ +\end{verbatim} +\subsection{Управление циклами} +Операторы, которые осуществляют управление циклами называют операторами безусловного перехода, поскольку они просто перемещают выполнение программы на другую строку, невзирая ни на что. Программист должен сам описать логику такого перемещения, используя условные операторы. +\paragraph{Оператор \code{continue;}} +нужен для того, чтобы программа проигнорировала оставшиеся действия на текущей итерации цикла, часто используется для отбрасывания неподходящих значений при переборе больших объёмов данных. Оператор \code{continue} просто напросто передаёт управление логической конструкции цикла. +\paragraph{Оператор \code{break;}} +используется для того, чтобы выйти за пределы цикла, мы сразу попадаем к следующему после цикла оператору, без передачи управления логической конструкции. +\subsection{Практические задачи} +\paragraph{Возведение в степень} +Решим немного более сложную, чем выведение в консоль числовых рядов задачу возведения числа в степень. Язык С не предоставляет оператора возведения в степень по умолчанию, как это делают некоторые другие языки высокого уровня, такие как Python, поэтому для этой математической операции нам нужно подключать специальную математическую библиотеку. Но иногда это может оказаться излишним, ведь не так сложно написать собственную функцию, которая бы делала это. +\frm{Как известно, возведение в степень - это последовательное умножение основания на само себя указанное количество раз.} +А раз заранее известно, сколько раз мы будем умножать основание само на себя, это работа для цикла \code{for(;;)}. Объявим переменную-итератор \code{i}, переменную-основание, переменную-показатель и переменную, в которую будем сохранять промежуточные и конечный результаты. +\begin{minted}{C} +int i; +int base; +int significative; +int result = 1; +\end{minted} +Логика работы следующая: результатом работы будет совокупность результатов предыдущих итераций умноженных на основание. +\begin{minted}{C} +for (i = 0; i < significative; i++) { + result = result * base; +} +\end{minted} +Запись вида \code{result = result * base;} можно сократить до \code{result *= base;}, и выведем результаты работы цикла в консоль. +\begin{minted}{C} +for (i = 0; i < significative; i++) { + result *= base; +} +printf("%d powered by %d is %d \n", base, significative, result); +\end{minted} +Конечно, мы можем спросить у пользователя какое число, и в какую степень он хочет возвести, для этого применим уже привычные нам конструкции. Так, весь код программы будет иметь следующий вид: +\begin{minted}{C} +#include + +int main(int argc, char *argv[]) { + int i; + int base; + int significative; + int result = 1; + printf("Enter base: "); + scanf("%d", &base); + printf("Enter significative: "); + scanf("%d", &significative); + for (i = 0; i < significative; i++) { + result *= base; + } + printf("%d powered by %d is %d \n", base, significative, result); +} +\end{minted} +Запустим нашу программу, введем для базы значение два, для показателя десять. Убедимся, что наша программа работает корректно, $2^{10}=1024$. +\begin{verbatim} +$ ./program +Enter base: 2 +Enter significative: 10 +2 powered by 10 is 1024 +$ +\end{verbatim} +Вообще, степени двойки, пожалуй, самые популярные в программировании числа. +\paragraph{Простое число} +Решим ещё одну несложную задачу. Напишем программу, которая будет определять, является ли введённое пользователем число простым. Мы напишем не самый быстрый и оптимальный алгоритм, но постараемся использовать все доступные нам конструкции. То есть, эта задача призвана продемонстрировать возможности языка, а не улучшить или заменить существующие, более оптимальные алгоритмы проверки. +\frm{Что такое простое число? Это такое число, которое имеет ровно два делителя с целочисленным результатом - единицу и само себя.} +Наша программа будет запрашивать у пользователя число и определять, простое оно или нет. Для этого заведём переменную, и привычными нам функциями, попросим пользователя ввести число, которое положим в неё. Для подсчетов нам понадобятся дополнительные переменные, например, переменная которая будет хранить количество делителей, назовем ее \code{dividers} и переменная итератор \code{i} значение которой будет увеличиваться от единицы до введенного пользователем числа. +\begin{minted}{C} +int number; +int dividers = 0, i = 1; +printf("Введите число: "); +scanf("%d", &number); +\end{minted} +Поскольку мы не знаем, сколько итераций понадобится, напишем цикл \code{while}, и пройдемся от единицы, которую мы записали в переменную \code{i} до введённого пользователем числа. После того как мы переберем все возможные варианты от единицы до введённого пользователем числа выведем в консоль получившийся результат. Напишем введённое число, а дальше предоставим программе выбор ставить ли частицу <<не>> при помощи заполнителя \code{\%s} и тернарного оператора. В случае истинности условия \code{dividers == 2} тернарный оператор вернет пустую строку, в случае ложности, вернет частицу <<не>>. Обратите внимание на то, как оператор вывода на экран написан в несколько срок. Такие разделения на несколько строк можно часто увидеть, если оператор длинный и может, например, выйти за пределы экрана. +\begin{minted}{C} +while (i <= number) { + // здесь будет алгоритм проверки +} +printf("Число %d%s является простым", + number, + (dividers == 2) ? "" : " не" + ); +\end{minted} +Итак, что будет происходить на каждой итерации цикла? Мы будем проверять делится ли введённое пользователем число на текущее значение итератора. Если остаток от деления исходного числа на текущий делитель равен нулю, то мы увеличим счетчик количества целочисленных делителей для данного числа. Тело цикла примет следующий вид: +\begin{minted}{C} +if (number++ % i == 0) { + dividers++; +} else { + continue; +} +if (dividers == 3) + break; +\end{minted} +Если количество целочисленных делителей не изменилось, то мы прекратим текущую итерацию цикла при помощи ключевого слова \code{continue}. Как мы знаем, оператор \code{continue} передаст управление в логическую конструкцию цикла, заставив программу проигнорировать все дальнейшие инструкции в рамках текущей итерации. Если количество целочисленных делителей достигнет трёх, что будет означать нецелесообразность дальнейших вычислений, мы разорвем цикл при помощи ключевого слова \code{break}. И полный получившийся код приложения будет такой: +\begin{minted}{C} +#include + +int main(int argc, char *argv[]) { + int number; + int dividers = 0, i = 1; + printf("Введите число: "); + scanf("%d", &number); + while (i <= number) { + if (number++ % i == 0) { + dividers++; + } else { + continue; + } + if (dividers == 3) + break; + } + printf("Число %d%s является простым", + number, + (dividers == 2) ? "" : " не" + ); +} +\end{minted} +Естественно, повторимся, этот код можно оптимизировать по множеству направлений, как минимум, сократив как количество проверок, так и границы проверок (нет смысла проверять числа больше, чем $\sqrt{number}$). Дополнительно можно не проверять чётные числа, например. +\begin{verbatim} +$ ./program +Введите число: 2 +Число 2 не является простым +$ ./program +Введите число: 7 +Число 7 является простым +$ ./program +Введите число: 457 +Число 457 является простым +$ ./program +Введите число: 1457 +Число 1457 не является простым +$ +\end{verbatim} +\subsection{Множественный выбор \code{switch()\{\}}} +Пришло время поговорить об операторе множественного выбора \code{switch()\{\}}. Тем более, что теперь мы обладаем всеми необходимыми для этого знаниями. Оператор множественного выбора используется когда мы хотим описать действия для какого-то ограниченного количества условий. В отличие от оператора \code{if()}, который может использоваться также и для проверки диапазонов значений. +\frm{Это не совсем точно, потому что \code{switch()\{\}} в языке C тоже может использоваться для проверки диапазонов значений, но это довольно редко применяется, а в С++ и других языках может вовсе не работать. Не стоит пугаться, увидев \code{case 5 ... 50:} это как раз проверка диапазона целочисленных значений от 5 до 50 включительно.} +Удобство применения того или иного оператора, естественно, зависит от задачи. Довольно важным ограничением оператора \code{switch(){}} в языке С является то, что он умеет работать только с целыми числами. Для примера, напишем свой собственный маленький калькулятор. Сразу предположим, что калькулятором будет пользоваться внимательный человек, который понимает, что арифметические действия можно совершать только с числами, и умножать строки или символы - не получится. + +Заведем в нашей программе переменные типа \code{float}, которые будут хранить операнды – числа над которыми будут производиться действия. И переменную типа \code{char} для хранения операции. Спросим у пользователя, какие числа он хочет посчитать, для этого используем уже привычную нам связку \code{printf();}/\code{scanf();}. Далее, таким же образом предложим пользователю ввести действие, действие мы закодируем в виде чисел: +\begin{itemize} + \item 1 – сложение; + \item 2 – вычитание; + \item 3 – умножение + \item 4 – деление. +\end{itemize} +Здесь вступает в силу тот факт, что на ноль делить нельзя, поэтому нам нужно не дать пользователю ввести в качестве второго операнда \code{"0"}, если в качестве оператора он выбрал деление. Конечно, это можно оставить на усмотрение пользователя, но мы, как сознательные программисты, не дадим пользователю в арифметическом порыве сломать нашу программу. Совершенно очевидно, что просто скопировать ввод будет неправильным действием. Для того чтобы не дать пользователю сделать неправильный ввод введем в прорамму условие: если пользователь выбрал деление, используя цикл \code{do \{\} while();} будем просить его ввести второй операнд отличный от нуля, а если выбранный оператор не является делением, то просто попросим пользователя ввести второе число, без проверок и повторений. +\begin{minted}{C} +if (operator == 4) { + do { + printf("/nEnter second operand: "); + scanf("%f", &second); + } while (second == 0); +} else { + printf("/nEnter second operand: "); + scanf("%f", &second); +} +\end{minted} +Если мы воспользуемся нашими знаниями на текущий момент, то мы напишем примерно следующее: если оператор равен единице, то делать это, в противном случае, если оператор равен двум, то делать вот это, и так далее, описывали бы все возможные действия условными операторами. Получился бы весьма громоздкий код. +\begin{minted}{C} +if (operator == 1) { + //... +} else if (operator == 2) { + //... +} +//... +\end{minted} +Но хорошая новость в том, что существует гораздо более удобный оператор \code{switch()\{\}}. Воспользуемся им относительно переменной \code{operator}. Разделим действия оператора \code{switch()\{\}} на несколько так называемых \textit{кейсов}. +\begin{minted}{C} +switch (operator) { +case 1: + result = first + second; +case 2: + result = first - second; +case 3: + result = first * second; +case 4: + result = first / second; +default: + printf("Unknown operator\n"); +} +\end{minted} +Оператор \code{switch()\{\}} последовательно проверит входящую переменную на соответствие описанным в кейсах значениях. В случае, если значение совпадёт, будет выполнен блок кода до оператора \code{break;}, если же значение переменной не совпадёт ни с одним из описанный в кейсах, выполнится блок по умолчанию \code{default}. +\frm{Важно помнить, что в случае отсутствия внутри \code{case} оператора \code{break;}, программа будет выполнять последующие кейсы, пока не найдёт \code{break;} или пока не закончится конструкция \code{switch()\{\}}, то есть пока не встретится её закрывающая фигурная скобка.} +В кейсах мы опишем присваивание результата в переменную \code{result}, а после выхода из \code{switch()\{\}} - вывод результата в консоль. Кейсом по умолчанию будет вывод пользователю сообщения о невозможности распознать оператор. Так получается, что даже если мы ввели неизвестный оператор, программа попытается вывести в консоль результат, что неприемлемо. Поэтому кейс по умолчанию должен содержать также и оператор \code{return 1;} вынуждающий программу экстренно завершиться с кодом ошибки \code{1}. Получится такой код: +\begin{minted}{C} +#include + +int main (int argc, char *argv[]) { + float first; + float second; + float result; + int operator; + + printf("Enter first operand: "); + scanf("%f", &first); + printf("/nEnter 1 for (+), 2 for (-), 3 for (*), 4 for (/): "); + scanf("%d", &operator); + if (operator == 4) { + do { + printf("/nEnter second operand: "); + scanf("%f", &second); + } while (second == 0); + } else { + printf("/nEnter second operand: "); + scanf("%f", &second); + } + switch (operator) { + case 1: + result = first + second; + break; + case 2: + result = first - second; + break; + case 3: + result = first * second; + break; + case 4: + result = first / second; + break; + default: + printf("Unknown operator\n"); + return 1; + } + + printf("Result is: %f \n", result); + return 0; +} +\end{minted} +Запустив описанный нами калькулятор, убедимся что все работает. Сымитируем нерадивого пользователя и несколько раз попробуем ввести при использовании четвёртого оператора цифру ноль, программа естественно не даст нам этого сделать. +\begin{verbatim} +$ ./program +Enter first operand: 10 +Enter 1 for (+), 2 for (-), 3 for (*), 4 for (/): 4 +Enter second operand: 0 +Enter second operand: 0 +Enter second operand: 0 +Enter second operand: 3 +Result is: 3.333333 +$ ./program +Enter first operand: 10 +Enter 1 for (+), 2 for (-), 3 for (*), 4 for (/): 5 +Unknown operator +$ +\end{verbatim} +На основе этого кода можно описывать любые виды меню, описать поведение программ, которые должны опираться на получаемые извне команды или описывать конечные автоматы. +\section{Функции} +Функция - это такая обособленная часть кода, которую можно выполнять любое количество раз. У функций обязательно в таком порядке должны быть описаны: тип возвращаемого значения, название, аргументы и так называемое тело, то есть собственно исполняемый код. Рассмотрим более детально функцию \code{int main (int argc, char *argv[])}: \code{int} - это \textit{тип возвращаемого значения}, то есть на том месте, откуда будет вызвана эта функция, в результате её работы по выполнении оператора \code{return;}, появится некое целое число. Возвращаемые значения могут быть любых типов. В случае же когда функция не должна возвращать результат своей работы, или никакого возвращаемого результата не предполагается, указывается ключевое слово \code{void} (англ. - пустота). То есть на месте вызова функции в результате её выполнения ничего не появится. Оператор \code{return;} обязателен для не-void функций, а в \code{void} функциях может присутствовать или нет, но никогда не содержит возвращаемого значения. \code{main} - это \textit{название функции}. Функция именно с таким названием, написанным с маленькой буквы, всегда является точкой входа в программу (\hyperref[text:main]{\ref{text:main}}). Операционная система ищет именно эту функцию, когда получает команду на выполнение программы. +\frm{Названия функций в рамках одной программы не должны повторяться и не должны начинаться с цифр или спецсимволов, также, как и названия переменных (\hyperref[text:main]{\ref{text:naming}}) никаких других ограничений на название функций не накладывается.} +Конструкция в круглых скобках \code{(int argc, char *argv[])} - это \textit{аргументы функции}. Аргументы функции - это такие переменные, которые создаются при вызове функции и существуют только внутри неё. С их помощью можно передать в функцию какие-то параметры и исходные данные для работы. Аргументы пишутся в круглых скобках сразу после названия функции. В случае если функция не принимает аргументов необходимо поставить после названия пустые круглые скобки. Весь код, содержащийся в фигурных скобках после аргументов функции называется \textit{телом функции}. Это те операторы и команды, которые будут последовательно выполнены при вызове функции. В теле функции мы можем \textbf{вызывать} другие функции, но никогда не можем создавать в теле функции другие функции. Никаких других ограничений на написание тела функции язык не накладывает. +\begin{verbatim} +ТипВозвращаемогоЗначения Имя (СписокАргументов) +{ + ТелоФункции + return ВозвращаемоеЗначение; +} +\end{verbatim} + + +\begin{minted}{C} +void somefunction() { // <-- это функция + // полезные действия +} + +int main (int argc, const char* argv[]) { + // больше полезных действий + somefunction(); // <-- это вызов функции + return 0; +} + +\end{minted} + + +Функции принято разделять на проверяющие, считающие и выводящие, и каждая из вышеописанных функций не должна нести дополнительной нагрузки. То есть, функция не должна знать откуда в программе появились её аргументы, и где будет использован результат её работы. Для примера опишем функцию суммирующую два числа. в качестве аргументов она будет принимать целые числа и возвращать целочисленный результат. Обратите внимание что функция не знает откуда взялись эти числа, мы можем их считать с консоли, можем задать в виде констант или получить в результате работы какой то другой функции. +Внутри функции main мы вызываем нашу функцию sum суммирующую два числа и передаем в качестве аргументов эти числа. + + + + + + + + +int sum(int x, int y) { + int result = x + y; + return result; +} + + + + + +int main (int argc, const char* argv[]) { + sum(50, 60); +return 0; +} +Обратите внимание, что в качестве аргументов мы можем передавать константные значения, а также переменные. Значения переменных мы можем получить например из консоли, либо в результате выполнения какой-нибудь другой функции. +int main (int argc, const char* argv[]) { +int a; +scanf(“%d, &a”); +sum(50, a); +return 0; +} +Как уже было сказано - аргументы - это переменные, которые хранят в себе некоторые параметры вызова функции. Аргументы позволяют использовать одни и те же функции с разными исходными данными. Приглядимся повнимательнее к хорошо знакомой нам функции printf(). Строка, которую мы пишем в круглых скобках в двойных кавычках это и есть аргумент функции. То есть мы знаем, что функция умеет выводить на экран строки. Как именно - нам нет дела, а какие именно строки - мы указываем в качестве аргумента. +Функция printf() примечательна еще и тем, что она может принимать в себя нефиксированное количество аргументов. Описание работы таких функций, а также их написание выходит далеко за пределы основ языка, нам важно помнить что мы можем это использовать. В аргументе функции printf() мы можем написать заполнитель соответствующего типа и вызвать нашу функцию sum. + + + + + + +printf(“HelloWorld!”); + + + + + + + + + + + + + +printf(“%d”, sum(50, a)); + + + +Теперь мы без проблем можем оформить уже существующие у нас программы в виде функций. Например, оформим в виде функций программу проверки простоты числа. +Для этого опишем функцию которая возвращает целое число, назовем ее isPrime, в качестве аргумента она принимает целое число, назовем его number. Найдем из предыдущих занятий программу определения простоты числа и скопируем в тело функции. +Внесем небольшие правки, уберем вывод т.к. это проверяющая функция, вывод оставим для основной части. И допишем если делителей 2 то число простое, возвращаем 1. Если же делителей больше – число не простое, возвращаем 0. +Такой вывод можно записать и другим способом, return (d == 2) – это выражение в случае истины вернет 1 в случае лжи 0. +Можно воспользоваться тернарным оператором, т.е. написать +return (d == 2) ? 1 : 0 – если условие в скобках истинно вернется 1, ложно – 0.; +Выйти из функции мы можем на любом этапе ее выполнения, например если делителей уже 3, то мы можем вернуть 0. + + + + +int isPrime(int number){ +int d = 0, i = 1; + while(i <= number){ + if(number % i++ ==0) + d++; + else + continue; + if (d == 3) break; + } + //if (d == 2) return 1; + //else return 0; + + return (d == 2) + + +//return (d == 2) ? 1 : 0; + +} + + + if (d == 3) return 0; + + +Немного подправим вывод, внесем в него вызов функции isPrime и объявим переменную int num, которую будем передавать в качестве аргумента в функцию isPrime. +Запустим нашу программу и убедимся что все работает – число 71 действительно является простым. + +Теперь мы можем написать программы любой сложности, содержащие функции isPrime. sum. О том, что мы работаем с консолью, в нашем случае должна знать только функция main, поэтому ввод значений и вывод на экран мы оставим в ней, а подсчёты значений положим в функции. +int main (int argc, const char* argv[]) { +int a; +scanf(“%d, &a”); +printf(“%d”, sum(50, a)); + +int num = 71; +printf(“Введенное число %d %s является простым \n”, number, isPrime(num) ? “” : “не”); +return 0; +} + + +Пришло время поговорить про прототипы. +Зачастую возникают ситуации, когда функция не описана до точки входа в программу, или вовсе лежит в другом файле. В этом случае мы должны сообщить компилятору, что такую функцию придётся дополнительно поискать. Для этого необходимо указать всю информацию о функции, кроме её тела. Такой оператор называется прототип функции. +Опишем прототип функции isPrime, описав сигнатуру этой функции. + + + + + + + + + + + +int isPrime(int number); +И пара слов о заголовочных файлах. Заголовочные файлы это мощный инструмент модульной разработки. Мы уже неоднократно видели подключение заголовочного файла stdio.h, давайте посмотрим, что же скрывает и как именно работает эта строка. Обнаружив данный файл на диске мы видим, что в нём содержатся другие подключения библиотек, директивы препроцессора (о которых более подробно мы будем говорить на следующих занятиях) и прототипы функций (например, так часто используемой нами +printf()). (Где-то вот здесь… на 259 строке) + +// Здесь показ содержимого stdio.h + + + + + + + + + + + + + +\end{document}