forked from ivan-igorevich/basic-c
118 lines
16 KiB
TeX
118 lines
16 KiB
TeX
\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{Существует шутка, что нельзя задавать вопрос программисту на С, является ли структура объетом. Думаю, дело в том, что это введёт С-программиста в бесконечное обдумывание такого вопроса.}
|
||
Тем не менее, у каждого объекта в мире есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда – тип, размер колёс, вес, материал, изготовитель и прочие. Для товара в магазине – артикул, название, группа, вес, цена, скидка и т.д. У объектов одного типа набор этих свойств одинаковый: все, например, собаки или коты могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
|
||
\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}}
|