structs and appendix refactoring

This commit is contained in:
Ivan I. Ovchinnikov 2021-09-30 16:01:28 +03:00
parent 4e5e982b74
commit 4d637e228c
7 changed files with 201 additions and 103 deletions

Binary file not shown.

View File

@ -29,90 +29,8 @@
\import{sections/}{09-arrays} \import{sections/}{09-arrays}
% 10 strings % 10 strings
\import{sections/}{10-strings} \import{sections/}{10-strings}
% 11 structs
\section{Структуры} \import{sections/}{11-structs}
Несмотря на то что язык С создавался в незапамятные времена, уже тогда программисты понимали, что примитивных типов данных недостаточно для комфортного программирования. Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектовно важно помнить, что С - это процедурный язык, в нём не существует объектно-ориентированного программирования. Тем не менее, у каждого объекта в мире есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда тип, размер колёс, вес, материал, изготовитель и прочие. Для товара в магазине артикул, название, группа, вес, цена, скидка и т.д. У объектов одного типа набор этих свойств одинаковый: все, например, собаки или коты могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
% Сразу небольшое отступление, для тех кто изучал высокоуровневые языки, такие как Java или С#, в Си отсутствуют классы в том виде в котором вы привыкли их видеть. Так вот, для работы с такими объектом нам необходима конструкция, которая бы могла агрегировать различные типы данных под одним именем так появились структуры. Т.е. структура данных - это такая сущность, которая объединяет в себе несколько примитивов. Для примера, создадим такую структуру, как простая дробь. В программировании существуют дробные числа и представлены они типами float и double. Но это десятичные дроби. Мы же будем описывать обычную дробь.
% Для описания структуры используется ключевое слово struct и название структуры. Далее в фигурных скобках описываются переменные, входящие в структуру. В нашем примере это будут целая часть, числитель и знаменатель. У этих переменных не гарантируются инициализационные значения, т.е. мы ничего не присваиваем им изначально, это просто описание, которое говорит компилятору о том, что когда в коде встретится инициализация нашей структуры, для её хранения понадобится вот столько памяти, которую нужно разметить для хранения вот этих переменных.
% #include <stdio.h>
% struct fraction {
% int integer;
% int divisible;
% int divisor;
% };
% Для сокращения записи опишем новый тип данных, назовём его ДРОБЬ. Это делается при помощи ключевого слова typedef. Его синтаксис прост, пишем typedef название старого типа данных название нового типа, т.е. как мы будем называть его в дальнейшем.
% typedef struct fraction Fraction;
% Доступ к переменным внутри структуры осуществляется привычным для высокоуровневых языков способом - через точку. Создадим три переменных для хранения двух структур типа дробь с которыми будем совершать операции, и одну для хранения результата. Инициализируем переменные какими-нибудь значениями. Опишем целочисленные значения, опишем делимое для обеих дробей и опишем делитель для обеих дробей. Для простоты будем использовать простые дроби типа 1/5.
% В комментарии к каждой дроби я напишу, как бы она выглядела на бумаге.
% Внутрь функций структуры данных можно передавать как по значению, так и по ссылке.
% int main(int argc, const char* argv[]){
% Fraction f1, f2, result;
% f1.integer = -1;
% f1.divisible = 1; //-1 | 1 /5
% f1.divider = 5;
% f2.integer = 1;
% f2.divisible = 1; ; //1 | 1 /5
% f2.divider = 5;
% result.divisible = 0;
% result.divider = 0;
% }
% Опишем функцию, которая будет выводить нашу дробь на экран. В эту функцию мы можем передать нашу структуру по значению. Т.е. внутри каждой функции мы будем создавать копию структуры типа дробь, и заполнять её теми значениями, которые передадим в аргументе. Вывод каждой дроби на экран будет зависеть от ряда условий. Именно эти условия мы и опишем внутри нашей функции.
% Если делимое равно 0, то у дроби надо вывести только целую часть, если же делимое не равно 0 и целая часть равно 0 выводим только дробную часть. Пишем: если делимое не равно 0 то вступает в силу следующее условие если целая часть равна 0, то печатаем дробь следующим образом: число, значок дроби, число числа это делимое и делитель.
% printf("%d / %d", f.divisible, f.divider);
% В противном случае, если целая часть и делимое не равны 0, то выводим всю дробь целую часть, делимое и делитель
% printf("%d %d/%d",f.integer,f.divisible,f.divider);
% И еще один else для общего if если делимое равно 0 то выводим только целую чать:
% printf("%d", f.integer);
% void frPrint(Fraction f) {
% if (f.divisible != 0)
% if (f.integer == 0)
% printf("%d / %d", f.divisible, f.divider);
% else
% printf("%d %d/%d",f.integer,f.divisible,f.divider);
% else
% printf("%d", f.integer);
% }
% Проверим, насколько хорошо мы написали нашу функцию? Для этого вызовем ее и передадим туда значения наших дробей.
% Добавим пустую строчку и запустим.
% frPrint(f1);
% puts(“”);
% frPrint(f2);
% Выглядит неплохо, для полноты картины не хватает только научиться выполнять с этими дробями какие-нибудь действия. Для примера возьмём что-то простое, вроде умножения. Передадим в эту функцию значения наших двух дробей и указатель на структуру, в которую будем складывать результат вычислений.
% Назовем нашу функцию frMul, передадим туда необходимые аргументы и немного вспомним математику. Для того чтобы перемножить две дроби нам надо привести их к простому виду, т.е. лишить целой части а затем перемножить числители и знаменатели. Для перевода в простой вид опишем функцию frDesinteger, в которую будем передавать адрес первой и второй дроби.
% Чтобы не перепутать локальные структуры функции и указатели на внешние структуры, доступ к полям внутри указателей на структуры получают не при помощи точки, а при помощи вот такой стрелки. Т.е. поскольку result для функции frMul является указателем, то мы будем записывать результат не в локальную структуру, а непосредственно в ту структуру, которую мы объявили в в функции Мэйн и передали ссылку на нее нашей функции.
% Ну а далее напишем операции умножения для числителя и знаменателя:
% result->divisible = f1.divisible * f2.divisible;
% result->divider = f1.divider * f2.divider;
% void frMul(Fraction f1, Fraction f2, Fraction *result) {
% frDesinteger(&f1);
% frDesinteger(&f2);
% result->divisible = f1.divisible * f2.divisible;
% result->divider = f1.divider * f2.divider;
% }
% void frDesinteger(Fraction *f) {
% int sign = (f->integer < 0) ? -1 : 1;
% if (f->integer < 0)
% f->integer = -f->integer;
% f->divisible = f->divisible + (f->integer * f->divisor);
% f->divisible *= sign;
% f->integer = 0;
% }
% Теперь можем выводить результат умножения на экран. Для этого вызовем нашу функцию frMul в которую передадим дробь№1, дробь №2 и адрес на результирующую дробь. Затем вызовем функцию печати frPrint и передадим туда нашу результирующую дробь. Запустим нашу программу и убедимся что все работает корректно.
% puts(“”);
% frMul(f1, f2, &result);
% frPrint(result);
% Полученных знаний нам хватит практически для любых операций со структурами. До встречи на следующем уроке, коллеги.
\section{Файлы} \section{Файлы}
% Коллеги здравствуйте. % Коллеги здравствуйте.
@ -289,6 +207,19 @@
% free(area); % free(area);
\appendix
\section*{Приложения}
\addcontentsline{toc}{section}{Приложения}
\renewcommand{\thesubsection}{\Alph{subsection}}
\subsection{Полный листинг программы программы терминального калькулятора}
\label{appendix:calc}
\lstinputlisting[language=C,style=CCodeStyle]{../sources/calculator.c}
\subsection{Полный листинг программы вычисления среднего арифметического}
\label{appendix:arrayaverage}
\lstinputlisting[language=C,style=CCodeStyle]{../sources/arrayaverage.c}
\subsection{Полный листинг программы умножения дробей}
\label{appendix:fractions}
\lstinputlisting[language=C,style=CCodeStyle]{../sources/fractions.c}
\end{document} \end{document}

View File

@ -341,10 +341,5 @@ Unknown operator
$ $
\end{verbatim} \end{verbatim}
\end{figure} \end{figure}
Также приведём полный листинг получившейся программы, пока он ещё помещается на одну страницу. Далее некоторые примеры будет невозможно привести полностью, поэтому собирать их в единый работающий код читатель будет вынужден самостоятельно. Также приведём полный листинг получившейся программы в приложении \hyperref[appendix:calc]{\ref{appendix:calc}}. Далее некоторые примеры будет невозможно привести полностью, поэтому собирать их в единый работающий код читатель будет вынужден самостоятельно.
\begin{figure}[h!]
\lstinputlisting[language=C,style=CCodeStyle]{../sources/calculator.c}
\label{code:calculator}
\end{figure}

View File

@ -1,6 +1,7 @@
\section{Массивы} \section{Массивы}
В этом разделе нас с вами ждут массивы. Много массивов. И ещё пара слов о директивах компилятору, иногда также называемых директивами препроцессора. С них и начнём. В этом разделе нас с вами ждут массивы. Много массивов. И ещё пара слов о директивах компилятору, иногда также называемых директивами препроцессора. С них и начнём.
\subsection{Директива \code{\#define}} \subsection{Директива \code{\#define}}
\label{text:define}
Помимо уже хорошо знакомой вам директивы \code{\#include}, частично описанной в разделе \hyperref[text:directive]{\ref{text:directive}}, естественно, существуют и другие. Некоторые из них ограничивают импорт описанных в заголовочном файле функций, некоторые <<\textbf{описывают}>> какие-то константы и даже действия. Вот, директиву \textbf{описать} мы и рассмотрим подробнее. Она не зря называется директивой препроцессора, поскольку даёт указание не процессору во время выполнения программы выделить память, присвоить значения, а непосредственно компилятору: заменить в тексте программы одни слова на другие. Таким образом можно задавать константы проекта, и даже делать сокращённые записи целых действий. Например, написав \code{\#define ARRAY\_LENGTH 50} мы предпишем компилятору, перед запуском трансляции нашего кода заменить все слова \code{ARRAY\_LENGTH} на цифру 50. В такой записи, слово \code{ARRAY\_LENGTH} будет называться \textit{макроконстантой}. Помимо уже хорошо знакомой вам директивы \code{\#include}, частично описанной в разделе \hyperref[text:directive]{\ref{text:directive}}, естественно, существуют и другие. Некоторые из них ограничивают импорт описанных в заголовочном файле функций, некоторые <<\textbf{описывают}>> какие-то константы и даже действия. Вот, директиву \textbf{описать} мы и рассмотрим подробнее. Она не зря называется директивой препроцессора, поскольку даёт указание не процессору во время выполнения программы выделить память, присвоить значения, а непосредственно компилятору: заменить в тексте программы одни слова на другие. Таким образом можно задавать константы проекта, и даже делать сокращённые записи целых действий. Например, написав \code{\#define ARRAY\_LENGTH 50} мы предпишем компилятору, перед запуском трансляции нашего кода заменить все слова \code{ARRAY\_LENGTH} на цифру 50. В такой записи, слово \code{ARRAY\_LENGTH} будет называться \textit{макроконстантой}.
\frm{Обратите внимание, что директива пишется немного не так, как обычный оператор языка, хоть и может находиться в любом месте кода. В конце директивы не ставится точка с запятой. Это важно именно потому что директивы работают с текстом программы, то есть если точка с запятой всё же будет поставлена, текст программы будет всегда содержать вместо макроконстанты число и точку с запятой, что может в корне изменить смысл программы.} \frm{Обратите внимание, что директива пишется немного не так, как обычный оператор языка, хоть и может находиться в любом месте кода. В конце директивы не ставится точка с запятой. Это важно именно потому что директивы работают с текстом программы, то есть если точка с запятой всё же будет поставлена, текст программы будет всегда содержать вместо макроконстанты число и точку с запятой, что может в корне изменить смысл программы.}
Весьма удобно, но этим можно не ограничиваться, мы можем попросить компилятор заменить вызовы функций и операторы на короткие, удобные нам слова. Важно помнить, что директивы препроцессора работают с текстом программы, поэтому не осуществляют никаких дополнительных проверок. Это сложный и мощный инструмент, который чаще всего используется для решения нетривиальных задач, например, выбор кода, который попадёт в компиляцию в зависимости от операционной системы. Иногда в программах можно встретить описание недостающего, но такого привычного булева типа при помощи директив препроцессора: Весьма удобно, но этим можно не ограничиваться, мы можем попросить компилятор заменить вызовы функций и операторы на короткие, удобные нам слова. Важно помнить, что директивы препроцессора работают с текстом программы, поэтому не осуществляют никаких дополнительных проверок. Это сложный и мощный инструмент, который чаще всего используется для решения нетривиальных задач, например, выбор кода, который попадёт в компиляцию в зависимости от операционной системы. Иногда в программах можно встретить описание недостающего, но такого привычного булева типа при помощи директив препроцессора:
@ -251,11 +252,8 @@ float average(int* array, int length) {
\end{lstlisting} \end{lstlisting}
\end{figure} \end{figure}
Так, полный листинг этого примера на стр. \hyperref[code:arrayaverage]{\pageref{code:arrayaverage}}. Так, полный листинг этого примера в приложении \hyperref[appendix:arrayaverage]{\ref{appendix:arrayaverage}}
\begin{figure}[h!]
\lstinputlisting[language=C,style=CCodeStyle]{../sources/arrayaverage.c}
\label{code:arrayaverage}
\end{figure}
\newpage \newpage
\subsection{Многомерные массивы} \subsection{Многомерные массивы}

View File

@ -1,4 +1,5 @@
\section{Строки} \section{Строки}
\subsection{Основные понятия}
Получив в предыдущих разделах представление об указателях и массивах, и вскользь несколько раз упомянув строки, пришла пора изучить их подробнее. Получив в предыдущих разделах представление об указателях и массивах, и вскользь несколько раз упомянув строки, пришла пора изучить их подробнее.
Итак, что же такое \textbf{строка}. В повседневной жизни строка \textit{это набор или последовательность символов}. Так вот в языке С строка - это тоже последовательность символов. А последовательности значений, в том числе символьных, в языке С представлены, как вы, уже знаете, массивами и указателями. Никакого примитива \code{string} в языке С нет. Как бы нам этого не хотелось - его нет. Но есть и хорошая новость, примитива \code{string} не существует и в других языках (здесь имеются ввиду, конечно, си-подобные языки, то есть примерно треть вообще всех языков программирования, известных сегодня). Раз строка - это массив или указатель, это всегда ссылочный тип данных. Например, строку можно объявить двумя способами: Итак, что же такое \textbf{строка}. В повседневной жизни строка \textit{это набор или последовательность символов}. Так вот в языке С строка - это тоже последовательность символов. А последовательности значений, в том числе символьных, в языке С представлены, как вы, уже знаете, массивами и указателями. Никакого примитива \code{string} в языке С нет. Как бы нам этого не хотелось - его нет. Но есть и хорошая новость, примитива \code{string} не существует и в других языках (здесь имеются ввиду, конечно, си-подобные языки, то есть примерно треть вообще всех языков программирования, известных сегодня). Раз строка - это массив или указатель, это всегда ссылочный тип данных. Например, строку можно объявить двумя способами:
@ -67,6 +68,7 @@ char str[50] =
Таких таблиц кодировок несколько, а может, даже и несколько десятков. Разные операционные системы и разные приложения используют разные кодировки, например, в русскоязычной версии ОС Windows по-умолчанию используется cp1251, в то время как в командной строке этой же ОС используется cp866. Файлы можно сохранить в Unicode или ANSI. UNIX-подобные ОС, такие как Linux и Mac OS X обычно используют UTF-8 или UTF-16, а более ранние операционные системы и интернет пространства в русскоязычном сегменте использовали KOI8-R. Таких таблиц кодировок несколько, а может, даже и несколько десятков. Разные операционные системы и разные приложения используют разные кодировки, например, в русскоязычной версии ОС Windows по-умолчанию используется cp1251, в то время как в командной строке этой же ОС используется cp866. Файлы можно сохранить в Unicode или ANSI. UNIX-подобные ОС, такие как Linux и Mac OS X обычно используют UTF-8 или UTF-16, а более ранние операционные системы и интернет пространства в русскоязычном сегменте использовали KOI8-R.
\subsection{Особенности}
Немного вспомнив, что такое символы, переходим к строкам. Строками мы пользуемся с самых первых страниц этого документа: написав в двойных кавычках <<Привет, Мир>>, мы использовали строку, а если точнее, строковый литерал. Строки иногда называют типом данных, но в языке С строка это указатель на последовательно записанный набор символов, поэтому работать с ним можно, как с массивами. Строки в языке С можно описать двумя способами: как указатель и как массив из переменных типа char. Немного вспомнив, что такое символы, переходим к строкам. Строками мы пользуемся с самых первых страниц этого документа: написав в двойных кавычках <<Привет, Мир>>, мы использовали строку, а если точнее, строковый литерал. Строки иногда называют типом данных, но в языке С строка это указатель на последовательно записанный набор символов, поэтому работать с ним можно, как с массивами. Строки в языке С можно описать двумя способами: как указатель и как массив из переменных типа char.
\frm{Объявление строки как указателя на символы в языке С++ полностью заменили на указатель на константный набор символов, чтобы подчеркнуть неизменяемость литерала. То есть, если в языке С считается нормальной запись \code{char* s = "Hello";} то в С++ это можно записать \textbf{только} как \code{const char* s = "Hello";} при этом в обоих языках поведение такого указателя будет обинаковым.} \frm{Объявление строки как указателя на символы в языке С++ полностью заменили на указатель на константный набор символов, чтобы подчеркнуть неизменяемость литерала. То есть, если в языке С считается нормальной запись \code{char* s = "Hello";} то в С++ это можно записать \textbf{только} как \code{const char* s = "Hello";} при этом в обоих языках поведение такого указателя будет обинаковым.}
Давайте создадим строку в виде массива из \code{char} назовем ее \code{string1} и запишем внутрь литерал \code{This is a string!} - это строка. Также создадим указатель, назовем его \code{string2} и запишем в него литерал \code{This is also a string!} это тоже строка. Выведем наши строки в консоль и убедимся, что автор не ошибся и их действительно можно так объявлять. Давайте создадим строку в виде массива из \code{char} назовем ее \code{string1} и запишем внутрь литерал \code{This is a string!} - это строка. Также создадим указатель, назовем его \code{string2} и запишем в него литерал \code{This is also a string!} это тоже строка. Выведем наши строки в консоль и убедимся, что автор не ошибся и их действительно можно так объявлять.
@ -102,6 +104,7 @@ zsh: bus error ./program
$ $
\end{verbatim} \end{verbatim}
\subsection{Строки и функции}
Но довольно об ограничениях. Указатели на строки не такие уж бесполезные, мы можем, например, возвращать их из функций. То есть, мы можем объявить тип возвращаемого из функции значения как указатель \code{char*}, вернуть из нее строку и, например, вывести в консоль. Это открывает перед нами широчайшие возможности по работе с текстами. Но довольно об ограничениях. Указатели на строки не такие уж бесполезные, мы можем, например, возвращать их из функций. То есть, мы можем объявить тип возвращаемого из функции значения как указатель \code{char*}, вернуть из нее строку и, например, вывести в консоль. Это открывает перед нами широчайшие возможности по работе с текстами.
\begin{figure}[h!] \begin{figure}[h!]
@ -198,9 +201,8 @@ int main(int argc, const char* argv[]) {
\end{figure} \end{figure}
Если присмотреться, то можно заметить, что все функции работающие со строками, именно так и делают - запрашивают источник данных и конечную точку, куда данные нужно положить. А кто мы такие, чтобы спорить со стандартными библиотечными функциями? Обратите внимание на то, что функции \code{strcat();} и \code{strcpy();} возвращают указатель на получившуюся строку. Мы перестали возвращать указатель на получившуюся строку, поскольку никто не гарантирует, что он просуществует достаточно долго, и тут встаёт вопрос о вызывающем функцию контексте, нужен ли этот указатель вызывающему. В случае необходимости, конечно, его можно вернуть. Работа со строками в С до сих пор является очень и очень актуальной темой на программистских форумах, можете удостовериться в этом самостоятельно. Если присмотреться, то можно заметить, что все функции работающие со строками, именно так и делают - запрашивают источник данных и конечную точку, куда данные нужно положить. А кто мы такие, чтобы спорить со стандартными библиотечными функциями? Обратите внимание на то, что функции \code{strcat();} и \code{strcpy();} возвращают указатель на получившуюся строку. Мы перестали возвращать указатель на получившуюся строку, поскольку никто не гарантирует, что он просуществует достаточно долго, и тут встаёт вопрос о вызывающем функцию контексте, нужен ли этот указатель вызывающему. В случае необходимости, конечно, его можно вернуть. Работа со строками в С до сих пор является очень и очень актуальной темой на программистских форумах, можете удостовериться в этом самостоятельно.
Раз уж заговорили о стандартной библиотеке, рассмотрим ещё пару-тройку функций. Например, сравнение строк: функция \code{strcmp();} допустим, я хочу, чтобы именно меня программа приветствовала как-то иначе. Функция возвращает отрицательные значения, если первая строка меньше второй, положительные, если первая больше второй, и ноль, если строки равны. Это функция, которую удобно применять в условиях. Если строки будут действительно равны, мы скопируем в строку с именем слово \code{Creator}. Раз уж заговорили о стандартной библиотеке, рассмотрим ещё пару-тройку интересных функций. Например, сравнение строк: функция \code{strcmp();} допустим, я хочу, чтобы именно меня программа приветствовала как-то иначе. Функция возвращает отрицательные значения, если первая строка меньше второй, положительные, если первая больше второй, и ноль, если строки равны. Это функция, которую удобно применять в условиях. Если строки будут действительно равны, мы скопируем в строку с именем слово \code{Creator}.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
void helloFunction (char* name, char* out) { void helloFunction (char* name, char* out) {
char welcome[255] = “Hello, ”; char welcome[255] = “Hello, ”;
@ -210,11 +212,11 @@ void helloFunction (char* name, char* out) {
strcpy(out, welcome); strcpy(out, welcome);
} }
\end{lstlisting} \end{lstlisting}
\end{figure}
Из всех функций для работы со строками чрезвычайно часто используются \code{atoi();} и \code{atof();} переводящие написанные в строке цифры в численные переменные внутри программы. \code{atoi();} переводит в \code{int}, а \code{atof();} во \code{float}, соответственно. Для примера объявим переменную \code{num}, предложим пользователю ввести цифру, естественно в виде строки. Будем принимать ее при помощи небезопасной функции \code{gets();}, хотя как мы помним, могли бы и \code{scanf();} который сразу бы преобразовал строку согласно использованного заполнителя. Заведем переменную \code{int number} для хранения результата работы функции преобразования. Затем, давайте умножим результат сам на себя, чтобы убедиться, что это и правда число, причём именно то, которое мы ввели, и выведем окончательное число в консоль. Из всех функций для работы со строками чрезвычайно часто используются \code{atoi();} и \code{atof();} переводящие написанные в строке цифры в численные переменные внутри программы. \code{atoi();} переводит в \code{int}, а \code{atof();} во \code{float}, соответственно.
\frm{Существует несколько десятков функций, разнообразно преобразующих одни типы данных в другие и обратно, например, функции \code{atoi();} и \code{atof();} имеют ответные \code{itoa();} и \code{ftoa();}. Такие функции пишутся и для высокоуровневых библиотек, например, преобразование строковой записи в понятный компьютеру для соединения IP-адрес.}
Для примера объявим переменную \code{num}, предложим пользователю ввести цифру, естественно в виде строки. Будем принимать ее при помощи небезопасной функции \code{gets();}, хотя как мы помним, могли бы и \code{scanf();} который сразу бы преобразовал строку согласно использованного заполнителя. Заведем переменную \code{int number} для хранения результата работы функции преобразования. Затем, давайте умножим результат сам на себя, чтобы убедиться, что это и правда число, причём именно то, которое мы ввели, и выведем окончательное число в консоль.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
char num[64]; char num[64];
puts("Enter a number: "); puts("Enter a number: ");
@ -223,9 +225,10 @@ int number = atoi(num);
number *= number; number *= number;
printf("We powered your number to %d", number); printf("We powered your number to %d", number);
\end{lstlisting} \end{lstlisting}
\end{figure}
Полный список функций для работы со строками можно посмотреть в заголовочном файле \code{string.h}. Описание и механика их работы легко гуглится, документации по языку очень много. Полный список функций для работы со строками можно посмотреть в заголовочном файле \code{string.h}. Описание и механика их работы легко гуглится, документации по языку очень много.
\subsection{Работа с символами}
В завершение беседы о строках и манипуляциях с ними, скажем ещё пару слов об обработке символов. Функции для работы с символами содержатся в заголовочном файле \code{stdlib.h}. Естественно, наша программа может получить от пользователя какие-то значения в виде строк. Не всегда же есть возможность использовать \code{scanf();} например, считывание из графических полей ввода или потоковый ввод данных из сети даёт нашей программе значения в виде строк. Стандартная библиотека языка С предоставляет нам функции для работы с каждым символом строки, например: В завершение беседы о строках и манипуляциях с ними, скажем ещё пару слов об обработке символов. Функции для работы с символами содержатся в заголовочном файле \code{stdlib.h}. Естественно, наша программа может получить от пользователя какие-то значения в виде строк. Не всегда же есть возможность использовать \code{scanf();} например, считывание из графических полей ввода или потоковый ввод данных из сети даёт нашей программе значения в виде строк. Стандартная библиотека языка С предоставляет нам функции для работы с каждым символом строки, например:
\begin{itemize} \begin{itemize}
\item \code{isalpha();} возвращает истину, если символ в аргументе является символом из алфавита; \item \code{isalpha();} возвращает истину, если символ в аргументе является символом из алфавита;
@ -236,7 +239,6 @@ printf("We powered your number to %d", number);
\end{itemize} \end{itemize}
Можем использовать одну из них соответственно нашей задаче, допустим, пользователь может вводить своё имя как с заглавной буквы, так и всеми строчными. Уравняем оба варианта для нашей проверки одной строкой \code{name[0] = tolower(name[0]);} а после проверки вернём заглавную букву на место \code{name[0] = toupper(name[0]);} и удостоверимся что даже если мы напишем своё имя с маленькой буквы - программа напишет его с большой. Можем использовать одну из них соответственно нашей задаче, допустим, пользователь может вводить своё имя как с заглавной буквы, так и всеми строчными. Уравняем оба варианта для нашей проверки одной строкой \code{name[0] = tolower(name[0]);} а после проверки вернём заглавную букву на место \code{name[0] = toupper(name[0]);} и удостоверимся что даже если мы напишем своё имя с маленькой буквы - программа напишет его с большой.
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
void helloFunction (char* name, char* out) { void helloFunction (char* name, char* out) {
char welcome[255] = “Hello, ”; char welcome[255] = “Hello, ”;
@ -248,4 +250,3 @@ void helloFunction (char* name, char* out) {
strcpy(out, welcome); strcpy(out, welcome);
} }
\end{lstlisting} \end{lstlisting}
\end{figure}

115
sections/11-structs.tex Normal file
View File

@ -0,0 +1,115 @@
\section{Структуры}
\subsection{Оператор \code{typedef}}
Иногда бывает удобно создавать для работы понятные или сокращённые названия типов данных. Мы уже рассматривали один из способов создания описаний (\hyperref[text:define]{\ref{text:define}}), но это были описания, работающие с текстом программы, позволяющие заменять целые фрагменты кода короткими понятными сочетаниями. Оператор \code{typedef} работает не с текстом программы, а с механизмами компилятора, позволяя создавать так называемые псевдонимы (англ. alias) для типов данных. Так, например, можно создать простейший псевдоним для булева типа, который фактически является целым числом. Это делается при помощи ключевого слова \code{typedef}. Его синтаксис прост, пишем \code{typedef} название старого типа данных название нового типа, т.е. как мы будем называть его в дальнейшем:
\begin{lstlisting}[language=C,style=CCodeStyle]
typedef int boolean;
\end{lstlisting}
Обратите внимание, что в отличие от директивы \code{\#define} это оператор языка С, а не препроцессора, поэтому в конце обязательно ставится точка с запятой. Написав такой псевдоним мы в любом месте программы можем писать \code{boolean} вместо \code{int}, что должно повысить понятность кода для людей, которые будут с ним работать.
\subsection{Структуры данных}
Несмотря на то что язык С создавался в незапамятные времена, уже тогда программисты понимали, что примитивных типов данных недостаточно для комфортного программирования. Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектовно важно помнить, что С - это процедурный язык, в нём не существует объектно-ориентированного программирования. Тем не менее, у каждого объекта в мире есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда тип, размер колёс, вес, материал, изготовитель и прочие. Для товара в магазине артикул, название, группа, вес, цена, скидка и т.д. У объектов одного типа набор этих свойств одинаковый: все, например, собаки или коты могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
\frm{
Сразу небольшое отступление, для тех кто изучал высокоуровневые языки, такие как Java или С\#, в С отсутствуют классы в том виде в котором вы привыкли их видеть. Для объектно-ориентированного программирования был разработан язык С++, который изначально называли Си-с-классами.
}
Так, для работы с объектом нам необходима конструкция, которая бы могла агрегировать и инкапсулировать различные типы данных под одним именем так появились \textbf{структуры}. Т.е. структура данных - это такая сущность, которая объединяет в себе несколько примитивов или других структур. Для примера, создадим структуру <<простая дробь>>. В программировании существуют дробные числа и представлены они типами float и double. Но это десятичные дроби. Мы же будем описывать обыкновенную, простую, смешанную дробь (ту, у которой кроме числителя и знаменателя есть ещё целая часть).
Для \textit{описания структуры} используется ключевое слово \code{struct} и название структуры. Далее в фигурных скобках описываются переменные, входящие в структуру. В нашем примере это будут целая часть, числитель и знаменатель. У этих переменных не гарантируются инициализационные значения, т.е. мы ничего не присваиваем им изначально, \textit{это просто описание}, которое говорит компилятору о том, что когда в коде встретится инициализация нашей структуры, для её хранения понадобится вот столько памяти, которую нужно разметить для хранения вот этих переменных.
\begin{lstlisting}[language=C,style=CCodeStyle]
struct fraction {
int integer;
int divisible;
int divisor;
};
\end{lstlisting}
Для сокращения записи опишем новый тип данных, назовём его \textbf{дробь}. Далее будут приведены два равнозначных способа сокращения записи структур: первый способ создаёт псевдоним для уже существующей структуры, а второй создаёт псевдоним для структуры прямо в момент её описания, поэтому и саму структуру можно не называть, а обращаться к ней только через псевдоним:
\begin{lstlisting}[language=C,style=CCodeStyle]
// alias for existing struct
typedef struct fraction Fraction;
// alias-defined structure
typedef struct {
int nat; // natural (integer)
int num; // numerator
int den; // denominator
} Fraction;
\end{lstlisting}
Обычно доступ к переменным внутри структуры осуществляется привычным для высокоуровневых языков способом - через точку. Есть одно исключение, но об этом чуть позже. Создадим три переменных для хранения двух дробей, с которыми будем совершать операции, и одну для хранения результата. Инициализируем переменные какими-нибудь значениями. Опишем целочисленные значения, опишем делимое для обеих дробей и опишем делитель для обеих дробей. Для простоты будем использовать простые дроби: \( 1\frac{1}{5} \) и \( -1\frac{1}{5} \).
\begin{figure}[h!]
\begin{lstlisting}[language=C,style=CCodeStyle]
int main(int argc, const char* argv[]){
Fraction f1, f2, result;
// f1 = -1 | 1 /5
f1.nat = -1;
f1.num = 1;
f1.den = 5;
// f2 = 1 | 1 /5
f2.nat = 1;
f2.num = 1;
f2.den = 5;
// result = 0
result.nat = 0;
result.num = 0;
result.den = 0;
}
\end{lstlisting}
\end{figure}
\subsection{Работа со структурами}
Внутрь функций структуры данных можно передавать как по значению, так и по ссылке. Опишем функцию, которая будет выводить нашу дробь на экран. В эту функцию мы можем передать нашу структуру по значению. Т.е. внутри каждого вызова функции мы будем создавать копию структуры типа дробь, и заполнять её теми значениями, которые передадим в аргументе. Вывод каждой дроби на экран будет зависеть от ряда условий. Именно эти условия мы и опишем внутри функции вывода на экран. Если делимое равно нулю, то у дроби надо вывести только целую часть, если же делимое не равно нулю и целая часть равна нулю выводим только дробную часть.
Пишем: если делимое не равно 0 то вступает в силу следующее условие если целая часть равна нулю, то печатаем дробь следующим образом: число, значок дроби, число, где числа это делимое и делитель, соответственно.
\begin{lstlisting}[language=C,style=CCodeStyle]
void frPrint(Fraction f) {
if (f.num != 0)
if (f.nat == 0)
printf("'%d / %d'", f.num, f.den);
else
printf("'%d | %d / %d'", f.nat, f.num, f.den);
else
printf("%d", f.nat);
}
\end{lstlisting}
Проверим, насколько хорошо мы написали нашу функцию, для этого вызовем ее и передадим туда значения наших дробей. Обратите внимание, что в строках оператора \code{printf();} в конце нет ни пробелов ни переходов на новую строку, это сделано на случай, если у нас будет необходимость вставить информацию о дроби в какой-то текст или отобразить в составе уравнения, например.
\begin{verbatim}
$ ./program
'-1 | 1 / 5'
'1 | 1 / 5'
0
\end{verbatim}
Выглядит неплохо, для полноты картины не хватает только научиться выполнять с этими дробями какие-нибудь действия. Для примера возьмём что-то простое, вроде умножения. Передадим во вновь созданную функцию значения наших двух дробей и указатель на структуру, в которую будем складывать результат вычислений. Назовем нашу функцию \code{frMultiply();} передадим туда необходимые аргументы и немного вспомним математику. Для того чтобы перемножить две дроби нам надо привести их к простому виду, т.е. лишить целой части, а затем перемножить числители и знаменатели. Для перевода в простой вид опишем функцию \code{frDesinteger();} в которую будем передавать адреса первой и второй дроби, чтобы изменять не копии, а сами созданные в \code{int main (int argc, char *argv[])} дроби. То есть параметрами этой функции должны стать указатели на структуры.
\frm{Аналогичным образом, кстати, можно написать функцию инициализации дроби значениями, чтобы не было необходимости для каждой дроби кроме строки объявления писать ещё три строки с инициализацией.}
Чтобы не перепутать локальные структуры в функции и указатели на внешние структуры, доступ к полям внутри указателей на структуры получают не при помощи точки, а при помощи специального оператора доступа к членам указателя, который выглядит как стрелка (\code{->}).
\begin{lstlisting}[language=C,style=CCodeStyle]
void frDesinteger(Fraction *f) {
if (f->nat == 0) return;
int sign = (f->nat < 0) ? -1 : 1;
if (f->nat < 0)
f->num = -f->num;
f->num = f->num + (f->nat * f->den);
f->nat = 0;
}
\end{lstlisting}
Аналогично и \code{result} для функции \code{frMultiply();} является указателем, а значит мы будем записывать результат не в локальную структуру, а непосредственно в ту структуру, которую мы объявили в \code{int main (int argc, char *argv[])} и передали её адрес нашей функции.
\begin{lstlisting}[language=C,style=CCodeStyle]
void frMultiply(Fraction f1, Fraction f2, Fraction *result) {
frDesinteger(&f1);
frDesinteger(&f2);
result->num = f1.num * f2.num;
result->den = f1.den * f2.den;
}
\end{lstlisting}
Теперь можем выводить результат умножения на экран. Для этого вызовем нашу функцию \code{frMultiply();}, в которую передадим две начальные дроби и адрес результирующей дроби. Затем вызовем функцию печати \code{frPrint();} и передадим туда в качестве аргумента нашу результирующую дробь.
\begin{verbatim}
./program
Before:
f1: -1 1/5
f2: 1 1/5
result: 0
After:
result: -36 / 25
\end{verbatim}
Запустив программу, убедимся что всё работает корректно. Полученных знаний нам хватит практически для любых операций со структурами. Полный листинг программы умножения дробей в приложении \hyperref[appendix:fractions]{\ref{appendix:fractions}}

58
sources/fractions.c Normal file
View File

@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int nat; // natural (integer)
int num; // numerator
int den; // denominator
} Fraction;
void frPrint(Fraction f) {
if (f.num != 0)
if (f.nat == 0)
printf("%d / %d", f.num, f.den);
else
printf("%d %d/%d",f.nat,f.num,f.den);
else
printf("%d", f.nat);
}
void frDesinteger(Fraction *f) {
if (f->nat == 0) return;
int sign = (f->nat < 0) ? -1 : 1;
if (f->nat < 0)
f->num = -f->num;
f->num = f->num + (f->nat * f->den);
f->nat = 0;
}
void frMultiply(Fraction f1, Fraction f2, Fraction *result) {
frDesinteger(&f1);
frDesinteger(&f2);
result->num = f1.num * f2.num;
result->den = f1.den * f2.den;
}
int main(int argc, const char* argv[]){
Fraction f1, f2, result;
// f1 = -1 | 1 /5
f1.nat = -1;
f1.num = 1;
f1.den = 5;
// f2 = 1 | 1 /5
f2.nat = 1;
f2.num = 1;
f2.den = 5;
// result = 0
result.nat = 0;
result.num = 0;
result.den = 0;
printf("Before:\n");
printf(" f1: "); frPrint(f1); printf("\n");
printf(" f2: "); frPrint(f2); printf("\n");
printf("result: "); frPrint(result); printf("\n");
puts("After:");
frMultiply(f1, f2, &result);
printf("result: "); frPrint(result); printf("\n");
}