Compare commits

..

No commits in common. "50b4bbbdfa43d711f9c3b3751eb7118d86169ccb" and "0d00dcecb6e77ec1042612d054e5a417cb3d4fc3" have entirely different histories.

15 changed files with 412 additions and 646 deletions

View File

@ -1,29 +0,0 @@
% \usepackage[printwatermark]{xwatermark}
\usepackage{ascii}
\usepackage{textcomp}
\usepackage{eurosym}
\usepackage{cclicenses}
\input{fancy-listings-preamble}
\usetikzlibrary{positioning}
\geometry{
paperheight=24cm,
paperwidth=17cm,
lmargin=1.5cm,
rmargin=2.5cm,
tmargin=2.5cm,
bmargin=2.5cm,
}
\author{Иван Овчинников}
\date{\today}
\title{Очередное введение в\\язык программирования C}
% \newwatermark[allpages,color=red!50,angle=70,scale=7,xpos=-36,ypos=14]{DRAFT}
\addto{\captionsrussian}{\renewcommand{\figurename}{}}
\newcommand{\sectionbreak}{\clearpage}

Binary file not shown.

View File

@ -1,85 +0,0 @@
\usepackage{tikz}
\usepackage{import}
\usepackage{xcolor}
\usepackage{bookmark}
\usepackage{multicol,multirow,colortbl}
\usepackage{longtable}
\usepackage{setspace}
\usepackage{titlesec}
\usepackage{indentfirst}
\usepackage{amsmath,amsfonts,amssymb,amsthm,mathtools}
\usepackage{layout,lscape}
\usepackage{hyperref}
\usepackage{geometry}
\usepackage[russian]{babel}
\usepackage{nomencl}
\usepackage{makeidx}
\usepackage{fancyhdr}
\usepackage{tabularx,adjustbox}
\usepackage{float,makecell}
\usepackage{anyfontsize,tabto}
\usepackage{tocloft}
\makeindex
\makenomenclature
\babelfont{rm}{Times New Roman}
\babelfont{sf}{Liberation Serif}
\babelfont{tt}{Courier New}
\onehalfspacing
\hypersetup{
colorlinks=false,
linktoc=all
}
\fancypagestyle{plain}{ % для автосгенерированых
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
}
\fancypagestyle{titlepage}{
\fancyhf{}
\cfoot{\small{\textbf{Москва, \the\year{}г.}}}
}
\fancyhf{}
\renewcommand{\headrulewidth}{0pt}
\newcommand{\numerationTop}{
\fancyhead[C]{\thepage}
}
\newcommand{\numerationBottom}{
\fancyfoot[C]{\thepage}
}
\newcommand{\code}[1]{\texttt{#1}}
\renewcommand{\nomname}{ }
\newcommand*{\nom}[2]{#1\nomenclature{#1}{#2}}
\newcommand\blankpage{%
\null
\thispagestyle{empty}%
\addtocounter{page}{-1}%
\newpage
}
\newcommand{\frm}[1]{\newline%
\newline%
\indent\fbox{%
\parbox{0.9\textwidth}{%
#1}%
}%
\newline%
\newline%
}%
\makeatletter
\newcommand{\setword}[2]{%
\phantomsection
#1\def\@currentlabel{\unexpanded{#1}}\label{#2}%
}
\makeatother
\newcommand\lh[2]{\texttt{\textcolor{#1}{#2}}}
\newcommand\hrf[1]{\hyperref[#1]{\ref{#1}}}
\newcommand*\circled[1]{\tikz[baseline=(char.base)]{\node[shape=circle,draw,inner sep=2pt] (char) {#1};}}
\renewcommand{\cftsecleader}{\cftdotfill{\cftdotsep}}

View File

@ -1,104 +0,0 @@
\usepackage{listings}
\definecolor{codekeywords}{rgb}{0.1,0.4,0.4}
\definecolor{codecomments}{rgb}{0,0.6,0}
\definecolor{codenumbers}{rgb}{0.4,0.4,0.4}
\definecolor{codestring}{rgb}{0.85,0.2,0.1}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\definecolor{codefine}{rgb}{0.7,0.5,0.3}
\definecolor{dkgreen}{rgb}{0,0.6,0}
\definecolor{gray}{rgb}{0.5,0.5,0.5}
\definecolor{mauve}{rgb}{0.58,0,0.82}
\lstdefinestyle{JCodeStyle}{
frame=single,
language=Java,
aboveskip=3mm,
belowskip=3mm,
showstringspaces=false,
columns=flexible,
basicstyle=\footnotesize\ttfamily,
numbers=left,
numberstyle=\tiny\color{gray},
keywordstyle=\color{blue},
commentstyle=\color{dkgreen},
stringstyle=\color{mauve},
breaklines=true,
breakatwhitespace=true,
tabsize=4,
escapeinside={<@}{@>}
}
\lstdefinestyle{PyCodeStyle}{
frame=single,
commentstyle=\color{codecomments},
numberstyle=\tiny\color{codenumbers},
stringstyle=\color{codestring},
basicstyle=\ttfamily\footnotesize,
keywordstyle=\color{codekeywords},
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=4,
escapeinside={<@}{@>}
}
\lstdefinestyle{CCodeStyle}{
frame=single,
commentstyle=\color{codecomments},
morecomment=[l][\color{codefine}]{\#},
numberstyle=\tiny\color{codenumbers},
stringstyle=\color{codestring},
basicstyle=\ttfamily\footnotesize,
keywordstyle=\color{codekeywords},
emph={int,char,double,float,unsigned,void,bool},
emphstyle={\color{blue}},
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=4,
escapeinside={<@}{@>}
}
\lstdefinestyle{ASMStyle}{
frame=single,
numberstyle=\tiny\color{codenumbers},
commentstyle=\color{codecomments},
keywordstyle=\color{codekeywords},
morecomment=[l]{//}, % l is for line comment
morecomment=[s]{/*}{*/}, % s is for start and end delimiter
basicstyle={\ttfamily\footnotesize},
morekeywords={
add,addi,and,andi,
bge,beq,bne,br,
cmpeqi,cmpgei,cmplti,cmpnei,
ldhu,ldw,ldwio,
mov,movi,movhi,muli,
nop,nor,
ret,
slli,srai,srli,stw,stwio},
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showtabs=false,
tabsize=4,
escapeinside={<@}{@>}
}
\lstset{escapeinside={<@}{@>}}

75
formatting.tex Normal file
View File

@ -0,0 +1,75 @@
% \usepackage[printwatermark]{xwatermark}
\usepackage{import}
\usepackage{listings}
\usepackage{xcolor}
\usepackage{bookmark}
\usepackage{multicol}
\usepackage{setspace}
\usepackage{titlesec}
\usepackage{indentfirst}
\usepackage{amsmath,amsfonts,amssymb,amsthm,mathtools}
\usepackage{icomma}
\usepackage{layout}
\usepackage{titlesec}
\usepackage{hyperref}
\usepackage{tikz}
\usepackage{ascii}
\usepackage{textcomp}
\usepackage{eurosym}
\usepackage{cclicenses}
\usetikzlibrary{positioning}
\usepackage[lmargin=1.5cm,rmargin=2.5cm,tmargin=2.5cm,bmargin=2.5cm,paperheight=240mm,paperwidth=170mm]{geometry}
\definecolor{codekeywords}{rgb}{0.1,0.3,0.3}
\definecolor{codecomments}{rgb}{0,0.4,0}
\definecolor{codenumbers}{rgb}{0.4,0.4,0.4}
\definecolor{codestring}{rgb}{0.85,0.2,0.1}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\definecolor{codefine}{rgb}{0.7,0.5,0.3}
\lstdefinestyle{CCodeStyle}{
commentstyle=\color{codecomments},
morecomment=[l][\color{codefine}]{\#},
numberstyle=\tiny\color{codenumbers},
stringstyle=\color{codestring},
basicstyle=\ttfamily\footnotesize,
keywordstyle=\color{codekeywords},
emph={int,char,double,float,unsigned,void,bool},
emphstyle={\color{blue}},
breakatwhitespace=false,
breaklines=true,
captionpos=b,
keepspaces=true,
numbers=left,
numbersep=5pt,
showspaces=false,
showstringspaces=false,
showtabs=false,
tabsize=4
}
\author{Иван Овчинников}
\date{\today}
\title{Очередное введение в\\язык программирования C}
\babelfont{rm}{Times New Roman}
\babelfont{sf}{Microsoft Sans Serif}
\babelfont{tt}{Courier New}
% \newwatermark[allpages,color=red!50,angle=70,scale=7,xpos=-36,ypos=14]{DRAFT}
\onehalfspacing
\addto{\captionsrussian}{\renewcommand{\figurename}{}}
\hypersetup{
colorlinks=false,
linktoc=all
}
\newcommand{\code}[1]{\texttt{#1}}
\newcommand{\sectionbreak}{\clearpage}
\newcommand{\frm}[1]{\newline%
\newline%
\indent\fbox{%
\parbox{0.9\textwidth}{%
#1}%
}%
\newline%
\newline%
}%

326
main.tex
View File

@ -1,6 +1,6 @@
\documentclass[fontsize=14bp]{article} \documentclass[a4paper]{article}
\input{common-preamble} \usepackage[russian]{babel}
\input{book-preamble} \include{formatting}
\begin{document} \begin{document}
\maketitle \maketitle
@ -29,108 +29,272 @@
\import{sections/}{09-arrays} \import{sections/}{09-arrays}
% 10 strings % 10 strings
\import{sections/}{10-strings} \import{sections/}{10-strings}
% 11 structs
\import{sections/}{11-structs} \section{Структуры}
% 12 files % Коллеги, здравствуйте. Рад вас приветствовать на 12м уроке Основы Языка С. На этом занятии мы поговорим о структурах данных в языке С.
\import{sections/}{12-files}
% Несмотря на то что язык Си создавался в незапамятные времена, уже тогда программисты понимали, что примитивных типов данных недостаточно для комфортного программирования. Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектов.
% У каждого объекта есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда тип, размер колёс, вес, материал, изготовитель и пр. Для товара в магазине идентификационный номер, название, группа, вес, цена, скидка и т.д. У объектов одного типа набор этих свойств одинаковый: все собаки могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
% СЛАЙД ПРО ТО ЧТО СТРУКТУРЫ МОГУТ БЫТЬ С ОДИНАКОВЫМИ ЗНАЧЕНИЯМИ
% Сразу небольшое отступление, для тех кто изучал высокоуровневые языки, такие как 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{Файлы}
% Коллеги здравствуйте.
% За предыдущие занятия мы с вами познакомились почти со всеми существующими в языке С типами данных, как примитивными, так и ссылочными. Довольно подробно рассмотрели работу почти всех операторов языка. Пришло время поговорить о взаимодействии программы с операционной системой, а именно - о чтении и записи в файловую систему компьютера.
% Файловая система любого компьютера - это структура. Для языка С файл - это тоже структура. Структура, хранящая данные о положении курсора в файле, его название, буферы, флажки и прочие свойства. Файлы делятся на два основных типа - текстовые и бинарные. Мы рассмотрим работу с текстовыми.
% СЛАЙД О ФАЙЛОВОЙ СИСТЕМЕ
% Опишем переменную, хранящую указатель на нашу структуру. Вся основная работа будет проходить через неё. Для того, чтобы присвоить этой переменной указатель на какой-то реальный файл воспользуемся функцией fopen, которая возвращает указатель на адрес в памяти.
% FILE *f;
% Функция принимает в качестве аргументов имя файла в двойных кавычках и режим его открытия.
% Основных используемых режимов шесть - чтение, запись, добавление, двоичное
% чтение, двоичную запись и двоичное добавление. Функции записи и добавления создают файл в случае его отсутствия. А функция записи стирает файл, если он существует и не пустой.
% СЛАЙД О ВОЗМОЖНОСТЯХ И РЕЖИМАХ FOPEN
% Итак создадим текстовый файл с каким-то неожиданным названием вроде filename.txt, и скажем нашей программе, что нужно будет его создать, если его не существует, перезаписать, если существует, а дальше мы будем в него записывать данные.
% Имя файла в аргументе может быть как полным, вроде C:\FILE.TXT тогда файл будет создан в корне диска C, так и относительным, каким мы его указали сейчас. Это значит, что файл будет создан в той папке, в которой запускается наша программа.
% f = fopen(“filename.txt”, “w”);
% В случае, если файл не найден или по какой-то причине не создался, в переменную file запишется нулевой указатель, поэтому перед тем, как начать работу с файлом, нужно проверить, смогла-ли программа его открыть, для этого запишем условие если в наш указатель записался нулевой указатель, то дальнейшее выполнение функции Мэйн не имеет смысла.
% if(file == NULL) return 1;
% Если всё хорошо, можем записывать в файл данные. Для записи в файл есть несколько функций, мы воспользуемся самой простой и очевидной
% fprintf(); . В неё в качестве первого аргумента обязательно нужно передать указатель на файл, в который мы собираемся писать, а дальше можно использовать как знакомый нам printf() со всеми его удобствами, заполнителями, экранированными последовательностями и дополнительными аргументами. После того как мы закончили запись в файл его необходимо
% закрыть, вызвав функцию fclose();
% fprintf(f, “Hello, files! %s”, “we did it! \n”);
% fclose(f);
% Запустим наш проект и посмотрим что у нас получилось. Перейдем в проводник и увидим что в папке проекта появился файл filename.txt, в котором написано наше содержимое, откроем его с помощью блокнота.
% Теперь давайте рассмотрим не менее важную тему, а именно - чтение из файла. Для этого нужно его открыть в режиме чтения. Далее мы можем воспользоваться неожиданно похожей функцией - fscanf() чтобы прочитать форматированные значения из файла. Создадим массив из переменных типа char, назовем его word и, при помощи функции fscanf() считаем из файла некоторую строку, которую положим в этот массив. Далее выведем в консоль строку которую прочитали, для этого воспользуемся привычной нам функцией printf, а затем выведем пустую строку. Запустим нашу программу и увидим, что в консоль вывелось слово Hello, - т.е. до пробела, функция fscanf отлично отработала.
% char word[256];
% f = fopen(“filename.txt”, “r”);
% fscanf(f, “%s”, &word);
% printf(“%s”, word);
% puts(“”);
% Но сколько данных читать? Как узнать, что достигнут конец файла? Для этого придумали функцию feof() (FILE END OF FILE) возвращающую ноль, если конец файла не достигнут, и единицу если достигнут.
% Опишем цикл, который выведет в консоль все полученные сканом строки из нашего файла. Для этого мы циклически пройдемся по всему файлу пока не будет достигнут конец и будем выводить считанные строки в консоль
% Запустим наш проект и убедимся, что вывод в консоль полностью соответствует содержимому файла, и это было не так уж сложно.
% Не забудем в конце закрыть файл.
% char word[256];
% f = fopen(“filename.txt”, “r”);
% while(!feof(file)){
% fscanf(f, “%s”, &word);
% printf(“%s”, word);
% }
% fclose(f);
% puts(“”);
% На следующем уроке поговорим о распределении памяти. До скорой встречи!
\section{Распределение памяти} \section{Распределение памяти}
Этот раздел находится в конце документа, но не по важности. Сильная сторона языка С (и, как следствие, С++) не только в возможности работать с указателями, но и в возможности самостоятельно управлять выделяемой памятью внутри программы. В языках высокого уровня данная возможность зачастую скрыта от программиста, чтобы по случайности программа не привела к зависанию среды виртуализации, по неосторожности не попыталась воспользоваться всей возможной оперативной памятью или не сломала операционную систему. Итак, как мы уже знаем, все переменные всех типов как-то хранятся в памяти, и до этого момента нас устраивало, как операционная система нам эту память выделяет. Но, пришло время взять бразды правления в свои руки. Процесс \textbf{выделения памяти} для программы называется \textbf{memory allocation}, отсюда и название функции, которая выделяет память и пишет в предложенный идентификатор указатель на начало этой области.
\subsection{\code{void* malloc(size);}} % Коллеги, здравствуйте.
Функция \code{void* malloc(size);} принимает в качестве аргумента размер выделяемой памяти. Как видим, функция возвращает довольно необычный на первый взгляд тип: \code{void*}, то есть область памяти будет зафиксирована, но не размечена. То есть это будет просто некоторая пустая область из \code{size} байт. % Это занятие находится в конце курса, но не по важности. Сильная сторона языка С не только в возможности работать с указателями, но и в возможности самостоятельно управлять выделяемой памятью внутри программы. В языках высокого уровня данная возможность зачастую скрыта от программиста, чтобы по случайности не подвесить среду виртуализации или не сломать операционную систему.
Чтобы иметь возможность в этой области хранить значения нам нужно её подготовить для хранения этих значений разметить. Например, мы уверены, что будем складывать в нашу область памяти какие-то целые числа типа \code{int}. Для этого при вызове функции нам надо использовать синтаксис приведения типа полученной области. Мы знаем, что каждая переменная типа \code{int} хранится в памяти в четырёх байтах. Например мы создаем указатель на некоторую область состоящую из 123 байт таким образом будет выделена память для 123 байт, но она никак не будет размечена. А при помощи оператора приведения типа мы скажем компилятору, что нам необходимо не только выделить некоторую область памяти, но и поделить её на ячейки размера \code{int}, и каждой такой ячейке дать свой адрес.
\begin{lstlisting}[language=C,style=CCodeStyle]
void* area = malloc(123);
int *array = (int*) malloc(123);
\end{lstlisting}
Здесь важно отметить, что именно такое выделение памяти, как на второй строке, не имеет особого смысла, поскольку 123 не делится на четыре нацело, поэтому у такой области памяти будет не совсем хорошо определённый хвост (последние три байта). Современные компиляторы действуют немного умнее, чем пишет программист, поэтому, скорее всего, будет выделено чуть больше памяти (обычно, это объём в байтах, равный степеням двойки), то есть для нашего случая, 128. Но расчитывать на это и строить на этом логику программы не стоит, во избежание досадных ошибок. Итак, как узнать, сколько нужно выделить памяти и при этом не запутаться. Для этого не обязательно знать размеры всех типов данных языка, для этого придумали оператор \code{sizeof()}. % Итак, как мы уже знаем, все переменные всех типов как-то хранятся в памяти, и до этого момента нас устраивало, как операционная система нам эту память выделяет. Но, пришло время взять бразды правления в свои руки. Процесс выделения памяти для программы называется memory allocation отсюда и название функции, которая выделяет память и пишет в предложенный идентификатор указатель на начало этой области. malloc(size); - она принимает в качестве аргумента размер выделяемой памяти. Как видим, функция возвращает пустоту, то есть область памяти будет зафиксирована, но не размечена. То есть это будет просто некоторая пустая область из n байт.
\frm{Интересно, что \code{sizeоf()} - это именно оператор, хоть и выглядит как функция.} % СЛАЙД ПРО MALLOC() И АРГУМЕНТЫ
Он возвращает размер переменной (типа данных) в байтах. Мы напишем \code{sizeof(int)} и умножим его на десять, таким образом мы выделим память в размере 40 байт, или под 10 целочисленных переменных. Приведением типа мы разметим выделенную область под хранение переменных типа \code{int}. Фактически, мы сделали то же самое что и описание массива типа \code{int} при помощи записи объявления массива с использованием квадратных скобок. % Чтобы иметь возможность в этой области хранить значения нам нужно её подготовить для хранения этих значений разметить.
% Например, мы уверены, что будем складывать в нашу область памяти какие-то целые числа типа integer. Для этого при вызове функции нам надо использовать синтаксис приведения типа полученной области. Мы знаем, что каждая переменная типа integer хрпнится в памяти в 4 байтах. Например мы создаем указатель на некоторую область состоящую из 123 байт таким образом будет выделена память для 123 байт, но она никак не будет размечена.
% А при помощи оператора приведения типа мы скажем компилятору, что нам необходимо выделить некоторую область памяти, поделить её на ячейки размера int, и каждой такой ячейке дать свой адрес.
\begin{lstlisting}[language=C,style=CCodeStyle]
int *area = (int*) malloc(sizeof(int) * 10);
int array[10];
\end{lstlisting}
Помните, мы говорили про арифметику указателей? Вот это - то самое место, где она нам поможет понять, что вообще происходит. Давайте реализуем два массива: привычным нам способом и при помощи динамического выделения памяти. Для реализации массива нам понадобится его размер, определим его как константу \code{SIZE}. Заменим в объявленном массиве \code{10} на \code{SIZE} и заполним этот массив какими-нибудь значениями. И напишем второй цикл для вывода этого массива в консоль.
\begin{lstlisting}[language=C,style=CCodeStyle]
const int SIZE = 10;
int *area = (int*) malloc(sizeof(int) * SIZE);
int array [SIZE];
int i;
for(i = 0; i < SIZE; i++)
array[i] = i * 10;
for(i = 0; i < SIZE; i++)
printf("%d ", array[i]);
\end{lstlisting}
Добавим пустую строку и проделаем тоже самое со вторым массивом, который мы инициализировали как область памяти. В этом виде данный код явно демонстрирует что мы можем одинаково работать и с массивами, объявленными привычными способами и с динамически выделенной областью памяти. Для более наглядной разницы, при заполнении и выводе в консоль второго массива, воспользуемся арифметикой указателей. % int *area = (int*) malloc(123);
% Итак, как узнать сколько нужно выделить памяти и при этом не запутаться. Для этого не обязательно знать размеры всех типов данных языка Си, для этого придумали оператор sizeof. Оператор sizeоf возвращает размер переменной (типа данных) в байтах. Мы напишем sizeof (int), умножим его на 10. Таким образом мы выделим память в размере 40 байт и разметим их под хранение переменных типа int. Фактически мы сделали то же самое что и описание массива типа int при помощи записи объявления массива с использованием квадратных скобок.
\begin{lstlisting}[language=C,style=CCodeStyle]
puts("");
for(i = 0; i < SIZE; i++) area[i] = i*10;
for(i = 0; i < SIZE; i++) printf("%d ", area[i]);
for(i = 0; i < SIZE; i++) *(area + i) = i * 10; % int *area = (int*) malloc(sizeof (int) * 10);
for(i = 0; i < SIZE; i++) printf("%d ", *(area + i));
\end{lstlisting}
Напомню, мы реализовали массив area вручную. то есть выполняем почти те же операции, которые выполняет компилятор при реализации массива \code{array}. Практически, разложили синтаксис на базовые операции. Очевидно, что это знание открывает нам возможность распределять память в нашем приложении для любых типов данных, в том числе и сложных, таких как структуры.
\frm{Если применить оператор \code{sizeof()} к указателю на локальный массив (объявленный через квадратные скобки), вернётся размер массива, а если применить этот оператор к динамически выделенному массиву (объявленному через оператор \code{malloc();}) вернётся размер указателя (8 байт для 64-хразрядных операционных систем).}
Для того, чтобы каждый раз не пересчитывать размеры переменных вручную, особенно это актуально для строк и структур, используют оператор \code{sizeof()}, который возвращает целочисленное значение в байтах, которое займёт в памяти та или иная переменная.
\subsection{\code{void* calloc(n, size);}} % int array[10];
Функция \code{malloc();} резервирует память для нашей программы, но делает это весьма просто, вместе со всеми теми случайными переменными, которые могут там храниться. Если в коде из предыдущего подраздела убрать заполнение массива \code{area}, с большой долей вероятности, увидим в консоли непонятные ненулевые значения. % Помните, мы говорили про арифметику указателей? Вот это то место, где она нам поможет понять, что вообще происходит. Давайте реализуем два массива привычным нам способом и при помощи динамического выделения памяти. Для реализации массива нам понадобится его размер, определим его как константу SIZE. Заменим в объявленном массиве 10 на SIZE и заполним этот массив какими-нибудь значениями.
\frm{Важно, что выделенная память «не гарантирует нулевых значений» в ячейках, то есть значения вполне могут оказаться нулевыми, но это не гарантируется.} % И напишем второй цикл для вывода этого массива в консоль.
Для того чтобы гарантированно очистить вновь выделенную область памяти используют функцию \code{calloc();} - clear allocate, которая не только выделит нам память, но и очистит содержимое. Поскольку функция не только выделяет память но и очищает её, считается, что она работает медленнее, чем \code{malloc()}. Синтаксис её весьма похож, только размеры необходимой области памяти передаются двумя аргументами - первый - сколько элементов, второй - какого размера будут элементы.
\begin{lstlisting}[language=C,style=CCodeStyle]
int *area = (int*) calloc(SIZE, sizeof (int));
\end{lstlisting}
В остальном же выделенная область памяти ничем не будет отличаться от выделенной с помощью \code{malloc();} % Добавим пустую строку
% И проделаем то же самое со вторым массивом, который мы инициализировали как область памяти. В этом виде данный код демонстрирует что мы можем одинаков работать и с массивами, объявленными привычными способами и с динамически выделенной областью памяти. Для более наглядной разницы, при заполнении и выводе в консоль второго массива, воспользуемся арифметикой указателей.
% Запустим наш проект, все работает.
\subsection{\code{void free(ptr); void* realloc(ptr, size);}}
По окончании работы с областью памяти надо её освободить, чтобы операционная система могла её использовать по своему усмотрению. Делается это при помощи функции \code{free()}. Если не освобождать память после использования - велика вероятность того, что мы, например, в каком-то цикле будем выделять себе место под какую-то структуру, и рано или поздно съедим всю память. Неприятно может получиться. Такие ситуации называются «утечками памяти» и могут возникать по огромному количеству причин, особенно во встраиваемых и многопоточных системах.
\begin{lstlisting}[language=C,style=CCodeStyle] % const int SIZE = 10;
free(area); % int *area = (int*) malloc(sizeof (int) * SIZE);
\end{lstlisting} % int array [SIZE];
% for(i = 0; i < SIZE; i++)
% array [i] = i * 10;
% for(i = 0; i < SIZE; i++)
% printf("%d ", array [i]);
% puts("");
% for(i = 0; i < SIZE; i++) area[i] = i*10;
% for(i = 0; i < SIZE; i++) printf("%d ", area[i]);
Одной из основных задач программиста является недопущение таких утечек.
И напоследок: довольно часто возникают ситуации, когда нам нужно придать нашей программе какой-то динамики, в этом случае мы можем изменить размеры уже выделенного блока памяти. Например, расширить наш массив, добавив туда пару элементов. Это делается при помощи функции \code{realloc();} в которую мы должны передать указатель на область памяти, которую хотим изменить, и размеры новой области памяти в байтах. При помощи этой функции области памяти можно как увеличивать, так и уменьшать, но в этом процессе есть довольно много особенностей, которые выходят далеко за пределы основ языка. % for(i = 0; i < SIZE; i++) *(area + i) = i * 10;
% for(i = 0; i < SIZE; i++) printf("%d ", *(area + i));
\begin{lstlisting}[language=C,style=CCodeStyle]
int newsize = SIZE + 10;
area = realloc(area, newsize * sizeof(int));
for(i = 0; i < newsize; i++) *(area + i) = i * 10;
for(i = 0; i < newsize; i++) printf("%d ", *(area + i));
\end{lstlisting}
Большинство компиляторов выделяют память блоками, размеры которых обычно равны какой-то из степеней двойки, поэтому при объявлении или изменении области памяти в 20 байт, скорее всего (но это не гарантировано, поскольку не регламентируется стандартом) будет выделена область в 32 байта, или если мы объявим 70 байт, то скорее всего будет выделено 128. То есть при работе с областями памяти не стоит ожидать, что они будут даваться нашей программе подряд. Это приводит нас к беседе о фрагментации памяти и других интересных явлениях, также не являющихся основами языка. % Напомню, мы реализовали массив area вручную. то есть выполняем ровно те операции, которые выполняет компилятор при реализации массива array. Разложили синтаксис на базовые операции. Очевидно, что это знание открывает нам возможность распределять память в нашем приложении для любых типов данных, в том числе и сложных, таких как структуры. Для того, чтобы каждый раз не пересчитывать размеры переменных вручную, особенно это актуально для строк и структур, используют оператор sizeof(), который возвращает целочисленное значение в байтах, которое займёт в памяти та или иная переменная.
% Функция malloc резервирует память для нашей программы, но делает это весьма просто, вместе со всеми теми случайными переменными, которые могут там храниться.
% Давайте я продемонстрирую это. Закомментируем заполнение массива area, и увидим в консоли непонятные ненулевые значения.
% Для того чтобы гарантированно очистить вновь выделенную область памяти используют функцию calloc() clear allocate которая не только выделит нам память, но и очистит содержимое. Поскольку функция не только выделяет память но и очищает её, считается, что она работает медленнее, чем malloc. Синтаксис её весьма похож, только размеры необходимой области памяти передаются двумя аргументами - первый - сколько элементов, второй - какого размера будут элементы.
% int *area = (int*) calloc(SIZE, sizeof (int));
% По окончании работы с областью памяти надо её освободить, чтобы ОС могла её использовать по своему усмотрению. Делается это при помощи функции free(). Если не освобождать память после использования - велика вероятность того, что мы, например, в каком-то цикле будем выделять себе место под какую-то структуру, и рано или поздно съедим всю память. Неприятно может получиться.
% free(area);
% И напоследок: довольно часто возникают ситуации, когда нам нужно придать нашей программе какой-то динамики, в этом случае мы можем изменить размеры уже выделенного блока памяти. Например, расширить наш массив, добавив туда пару элементов. Это делается при помощи функции realloc() в которую мы должны передать указатель на область памяти, которую хотим изменить, и размеры новой области памяти. При помощи этой функции области памяти можно как увеличивать, так и уменьшать.
% area = realloc(area, sizeof (int));
% Большинство компиляторов выделяют память блоками, размеры которых обычно равны какой-то из степеней двойки, поэтому при объявлении или изменении области памяти в 20 байт, скорее всего будет выделена область в 32 байта, или если мы объявим 70 байт, то скорее всего будет выделено 128. То есть при работе с областями памяти не стоит ожидать, что они будут даваться нашей программе подряд. Организация памяти это отдельный долгий разговор, явно выходящий за рамки Основ.
% Давайте запустим нашу программу с вновь выделенным измененным блоком памяти и увидим что все прекрасно перевыделилось.
% puts("");
% int newsize = SIZE + 10;
% for(i = 0; i < newsize; i++) *(area + i) = i * 10;
% for(i = 0; i < newsize; i++) printf("%d ", *(area + i));
% Спасибо, за внимание и интерес, проявленный к курсу. За прошедшие 14 уроков мы узнали как устроена практически любая программа изнутри, научились работать с памятью и указателями, узнали основные принципы и механизмы работы программ на уровне операционной системы. Заглянули внутрь привычных синтаксических конструкций, узнали, что делают и что скрывают от программистов среды виртуализации и фреймворки. Я и команда Гикбрейнс желает всем успехов в освоении Ваших профессий.
% (артефакт из видеокурса основ си)
% Удачи и до новых встреч, а ну да, и не забывайте освобождать память!
% СЛАЙД С ИТОГАМИ
% free(area);
\section{Итоги}
Спасибо, за внимание и интерес, проявленный к этой книге. В тринадцати коротких главах мы узнали как устроена практически любая программа изнутри, научились работать с памятью и указателями, узнали основные принципы и механизмы работы программ на уровне операционной системы. Заглянули внутрь привычных синтаксических конструкций, немного заглянули, что делают и что скрывают от программистов среды виртуализации и фреймворки. Продолжайте изучать технологии и не теряйте веру в себя, когда что-то не получается с первого раза.
\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

@ -4,19 +4,19 @@
Переменные делятся на целочисленные, символьные, указатели и числа с плавающей точкой (англ. floating point, дробное число). Все, кроме указателей и символьных переменных бывают как знаковыми так и беззнаковыми. То есть в знаковых самый старший бит в двоичной записи этих переменных отводится под определение, является ли число отрицательным, или положительным, в беззнаковых все биты используются для записи числа, что увеличивает его диапазон возможных значений, но позволяет записать только положительные числа. В классическом С нет булевого типа, вместо него используется целое число и значения нуля для \textbf{лжи} и \textit{любое} другое число для \textbf{истины}, обычно это единица. Об указателях и булевой алгебре мы будем подробно говорить в одном из последующих разделов. Переменные делятся на целочисленные, символьные, указатели и числа с плавающей точкой (англ. floating point, дробное число). Все, кроме указателей и символьных переменных бывают как знаковыми так и беззнаковыми. То есть в знаковых самый старший бит в двоичной записи этих переменных отводится под определение, является ли число отрицательным, или положительным, в беззнаковых все биты используются для записи числа, что увеличивает его диапазон возможных значений, но позволяет записать только положительные числа. В классическом С нет булевого типа, вместо него используется целое число и значения нуля для \textbf{лжи} и \textit{любое} другое число для \textbf{истины}, обычно это единица. Об указателях и булевой алгебре мы будем подробно говорить в одном из последующих разделов.
\begin{figure}[h!] \begin{figure}[h!]
\centering \centering
\begin{tabular}{|p{15mm}|p{65mm}|p{25mm}|} \begin{tabular}{|p{1.5cm}|p{6.6cm}|p{2.4cm}|}
\hline \hline
Тип & Пояснение & Спецификатор формата \\ Тип & Пояснение & Спецификатор формата \\
\hline \hline
char & Целочисленный, самый маленький из адресуемых типов, диапазон: [\textminus128, +127] & \%c \\ char & Целочисленный, самый маленький из адресуемых типов, диапазон: [\textminus128, +127] & \%c \\
\hline \hline
short\newline short int & Тип короткого целого числа со знаком, диапазон:\newline[\textminus32 768, +32 767] & \%hi \\ short\newline short int & Тип короткого целого числа со знаком, диапазон: [\textminus32 768, +32 767] & \%hi \\
\hline \hline
int & Основной тип целого числа со знаком, диапазон:\newline[\textminus2 147 483 648, +2 147 483 647] & \%i или \%d \\ int & Основной тип целого числа со знаком, диапазон: [\textminus2 147 483 648, +2 147 483 647] & \%i или \%d \\
\hline \hline
long\newline long int & Тип длинного целого числа со знаком, диапазон:\newline[\textminus2 147 483 648, +2 147 483 647] & \%li или \%ld \\ long\newline long int & Тип длинного целого числа со знаком, диапазон: [\textminus2 147 483 648, +2 147 483 647] & \%li или \%ld \\
\hline \hline
long long\newline long long int & Тип двойного длинного целого числа со знаком, диапазон:\newline[\textminus9 223 372 036 854 775 808, +9 223 372 036 854 775 807] & \%lli \\ long long\newline long long int & Тип двойного длинного целого числа со знаком, диапазон: [\textminus9 223 372 036 854 775 808, +9 223 372 036 854 775 807] & \%lli \\
\hline \hline
float & Тип вещественного числа с плавающей запятой (одинарной точности) & \%f (автоматически преобразуется в double для printf()) \\ float & Тип вещественного числа с плавающей запятой (одинарной точности) & \%f (автоматически преобразуется в double для printf()) \\
\hline \hline
@ -363,7 +363,7 @@ a = a ^ b; //00001111
\end{figure} \end{figure}
Применять бинарную алгебру можно и в больших проектах, работающих со сложными высокоуровневыми абстракциями. Помимо этого важно помнить, что поддержка бинарных операций есть в подавляющем числе языков программирования. Используя бинарную алгебру можно создавать оптимальные протоколы передачи данных и/или алгоритмы хранения и обработки. Применять бинарную алгебру можно и в больших проектах, работающих со сложными высокоуровневыми абстракциями. Помимо этого важно помнить, что поддержка бинарных операций есть в подавляющем числе языков программирования. Используя бинарную алгебру можно создавать оптимальные протоколы передачи данных и/или алгоритмы хранения и обработки.
\begin{figure}[H] \begin{figure}[h!]
\begin{verbatim} \begin{verbatim}
$ ./program $ ./program
a = 15 a = 15

View File

@ -341,5 +341,10 @@ 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

@ -84,13 +84,10 @@ $
Теперь мы без проблем можем оформить уже существующие у нас программы в виде функций. Например, оформим в виде функции программу проверки простоты числа. Для этого опишем функцию которая возвращает целое число, назовем ее \code{isPrime()}, в качестве параметра она будет принимать целое число, назовем его \code{number}. Найдем в предыдущих разделах (стр. \hyperref[code:isPrime]{\pageref{code:isPrime}}) программу определения простоты числа и скопируем в тело функции. Внесем небольшие правки, уберем вывод так как это будет, можно сказать, классическая проверяющая функция, вывод оставим для функции \code{int main (int argc, char *argv[])}, пусть о наличии у нас терминала <<знает>> только она. Теперь мы без проблем можем оформить уже существующие у нас программы в виде функций. Например, оформим в виде функции программу проверки простоты числа. Для этого опишем функцию которая возвращает целое число, назовем ее \code{isPrime()}, в качестве параметра она будет принимать целое число, назовем его \code{number}. Найдем в предыдущих разделах (стр. \hyperref[code:isPrime]{\pageref{code:isPrime}}) программу определения простоты числа и скопируем в тело функции. Внесем небольшие правки, уберем вывод так как это будет, можно сказать, классическая проверяющая функция, вывод оставим для функции \code{int main (int argc, char *argv[])}, пусть о наличии у нас терминала <<знает>> только она.
\frm{Такой процесс, перенос участков кода между функциями, выделение участков кода в функции, синтаксические, стилистические и другие улучшения, называетя \textbf{рефакторингом}. Обычно, рефакторингом занимаются сами разработчики в свободное от основной деятельности времени, в периоды код ревью или по необходимости улучшить читаемость/повторяемость собственного кода.} \frm{Такой процесс, перенос участков кода между функциями, выделение участков кода в функции, синтаксические, стилистические и другие улучшения, называетя \textbf{рефакторингом}. Обычно, рефакторингом занимаются сами разработчики в свободное от основной деятельности времени, в периоды код ревью или по необходимости улучшить читаемость/повторяемость собственного кода.}
Следовательно, допишем условия: если делителей два, то число простое, возвращаем \code{ИСТИНУ}, то есть любое ненулевое значение, в нашем примере - единицу. Если же делителей больше число не простое, возвращаем \code{ЛОЖЬ}, в нашем случае, это ноль. Такой вывод можно записать и другим способом, \code{return (dividers == 2)} это выражение в случае истины вернет единицу в случае лжи ноль. Или можно воспользоваться тернарным оператором, то есть, написать \code{return (dividers == 2) ? 1 : 0}: если условие в скобках истинно вернется единица, ложно ноль. Также важно, что выйти из функции мы можем на любом этапе ее выполнения, например если делителей уже три, то нам нужно не завершать цикл, а вернуть \code{ЛОЖЬ} из функции. Следовательно, допишем условия: если делителей два, то число простое, возвращаем \code{ИСТИНУ}, то есть любое ненулевое значение, в нашем примере - единицу. Если же делителей больше число не простое, возвращаем \code{ЛОЖЬ}, в нашем случае, это ноль. Такой вывод можно записать и другим способом, \code{return (dividers == 2)} это выражение в случае истины вернет единицу в случае лжи ноль. Или можно воспользоваться тернарным оператором, то есть, написать \code{return (dividers == 2) ? 1 : 0}: если условие в скобках истинно вернется единица, ложно ноль. Также важно, что выйти из функции мы можем на любом этапе ее выполнения, например если делителей уже три, то нам нужно не завершать цикл, а вернуть \code{ЛОЖЬ} из функции.
\begin{multicols}{2}
\begin{lstlisting}[language=C,style=CCodeStyle]
\begin{figure}[H]
\setlength{\columnsep}{22pt}
\begin{multicols}{2}
\begin{lstlisting}[language=C,style=CCodeStyle]
int isPrime(int number){ int isPrime(int number){
int dividers = 0, i = 1; int dividers = 0, i = 1;
@ -105,9 +102,9 @@ int isPrime(int number){
} }
return (dividers == 2) return (dividers == 2)
} }
\end{lstlisting} \end{lstlisting}
\columnbreak \columnbreak
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
int number; int number;
int dividers = 0, i = 1; int dividers = 0, i = 1;
@ -127,10 +124,8 @@ int main(int argc, char *argv[]) {
(dividers == 2) ? "" : " not" (dividers == 2) ? "" : " not"
); );
} }
\end{lstlisting} \end{lstlisting}
\end{multicols} \end{multicols}
\end{figure}
%\setlength{\columnsep}{10pt}
Немного подправив вывод, внесем в него вызов функции \code{isPrime()} и объявим переменную \code{int num}, которую будем передавать в качестве аргумента в функцию \code{isPrime()}. Запустим нашу программу и убедимся что все работает число 71 действительно является простым. Немного подправив вывод, внесем в него вызов функции \code{isPrime()} и объявим переменную \code{int num}, которую будем передавать в качестве аргумента в функцию \code{isPrime()}. Запустим нашу программу и убедимся что все работает число 71 действительно является простым.
\begin{figure}[h!] \begin{figure}[h!]

View File

@ -52,13 +52,9 @@ value of 'pointer' is 000000000061FE1C
\frm{Для языка С также справедливо выражение <<передача по ссылке>>, поскольку в языке С нет отдельной операции передачи по ссылке. Так, например, в языке С++ передача по ссылке и передача по указателю - это разные операции.} \frm{Для языка С также справедливо выражение <<передача по ссылке>>, поскольку в языке С нет отдельной операции передачи по ссылке. Так, например, в языке С++ передача по ссылке и передача по указателю - это разные операции.}
То есть функция будет ссылаться на переменные, на которые мы укажем и оперировать их значениями. Давайте немного модифицируем нашу программу обмена значениями внутри двух переменных (\hyperref[code:programswap]{\ref{code:programswap}}): опишем её в виде функции, принимающей в качестве параметров два указателя на целые числа типа \code{char}, и передадим адреса созданных в \code{int main (int argc, char *argv[])} переменных. Внутри функции, при её вызове, у нас будут создаваться не переменные, а указатели на переменные, то есть мы будем ссылаться на те самые переменные, созданные вне функции, и будем менять именно их (тех переменных) значения. Таким образом, нам не нужно ничего возвращать, потому что в функции ничего не создавалось, и типом возвращаемого значения функции должен быть \code{void}. То есть функция будет ссылаться на переменные, на которые мы укажем и оперировать их значениями. Давайте немного модифицируем нашу программу обмена значениями внутри двух переменных (\hyperref[code:programswap]{\ref{code:programswap}}): опишем её в виде функции, принимающей в качестве параметров два указателя на целые числа типа \code{char}, и передадим адреса созданных в \code{int main (int argc, char *argv[])} переменных. Внутри функции, при её вызове, у нас будут создаваться не переменные, а указатели на переменные, то есть мы будем ссылаться на те самые переменные, созданные вне функции, и будем менять именно их (тех переменных) значения. Таким образом, нам не нужно ничего возвращать, потому что в функции ничего не создавалось, и типом возвращаемого значения функции должен быть \code{void}.
\begin{multicols}{2}
\begin{figure}[H]
\setlength{\columnsep}{30pt}
\begin{multicols}{2}
\lstinputlisting[language=C,style=CCodeStyle]{../sources/swapfunc.c} \lstinputlisting[language=C,style=CCodeStyle]{../sources/swapfunc.c}
\columnbreak \columnbreak
\lstinputlisting[language=C,style=CCodeStyle]{../sources/swapprog.c} \lstinputlisting[language=C,style=CCodeStyle]{../sources/swapprog.c}
\end{multicols} \end{multicols}
\end{figure}
Применение такого подхода открывает перед нами широкие возможности. Важно, на схеме со стр. \pageref{fig:dereference}, что указатель - это тоже переменная, поэтому мы можем создавать указатели на указатели, и так далее, указатели любой сложности, тем самым увеличивая уровень абстракции программы. Применение такого подхода открывает перед нами широкие возможности. Важно, на схеме со стр. \pageref{fig:dereference}, что указатель - это тоже переменная, поэтому мы можем создавать указатели на указатели, и так далее, указатели любой сложности, тем самым увеличивая уровень абстракции программы.

View File

@ -1,18 +1,14 @@
\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{Обратите внимание, что директива пишется немного не так, как обычный оператор языка, хоть и может находиться в любом месте кода. В конце директивы не ставится точка с запятой. Это важно именно потому что директивы работают с текстом программы, то есть если точка с запятой всё же будет поставлена, текст программы будет всегда содержать вместо макроконстанты число и точку с запятой, что может в корне изменить смысл программы.}
Весьма удобно, но этим можно не ограничиваться, мы можем попросить компилятор заменить вызовы функций и операторы на короткие, удобные нам слова. Важно помнить, что директивы препроцессора работают с текстом программы, поэтому не осуществляют никаких дополнительных проверок. Это сложный и мощный инструмент, который чаще всего используется для решения нетривиальных задач, например, выбор кода, который попадёт в компиляцию в зависимости от операционной системы. Иногда в программах можно встретить описание недостающего, но такого привычного булева типа при помощи директив препроцессора: Весьма удобно, но этим можно не ограничиваться, мы можем попросить компилятор заменить вызовы функций и операторы на короткие, удобные нам слова. Важно помнить, что директивы препроцессора работают с текстом программы, поэтому не осуществляют никаких дополнительных проверок. Это сложный и мощный инструмент, который чаще всего используется для решения нетривиальных задач, например, выбор кода, который попадёт в компиляцию в зависимости от операционной системы. Иногда в программах можно встретить описание недостающего, но такого привычного булева типа при помощи директив препроцессора:
\begin{lstlisting}[language=C,style=CCodeStyle]
\begin{figure}[H]
\begin{lstlisting}[language=C,style=CCodeStyle]
#define bool int #define bool int
#define true 1 #define true 1
#define false 0 #define false 0
\end{lstlisting} \end{lstlisting}
\end{figure}
Но нам пока что достаточно умения создать глобальную именованную константу. Код ниже демонстрирует, что директивы не обязательно группировать именно в начале файла, а можно использовать там, где это удобно и уместно, так мы можем объявить константу с длиной массива в начале файла, а можем прямо внутри функции \code{int main (int argc, char *argv[])}. Но нам пока что достаточно умения создать глобальную именованную константу. Код ниже демонстрирует, что директивы не обязательно группировать именно в начале файла, а можно использовать там, где это удобно и уместно, так мы можем объявить константу с длиной массива в начале файла, а можем прямо внутри функции \code{int main (int argc, char *argv[])}.
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@ -255,8 +251,11 @@ float average(int* array, int length) {
\end{lstlisting} \end{lstlisting}
\end{figure} \end{figure}
Так, полный листинг этого примера в приложении \hyperref[appendix:arrayaverage]{\ref{appendix:arrayaverage}} Так, полный листинг этого примера на стр. \hyperref[code:arrayaverage]{\pageref{code:arrayaverage}}.
\begin{figure}[h!]
\lstinputlisting[language=C,style=CCodeStyle]{../sources/arrayaverage.c}
\label{code:arrayaverage}
\end{figure}
\newpage \newpage
\subsection{Многомерные массивы} \subsection{Многомерные массивы}
@ -264,7 +263,7 @@ float average(int* array, int length) {
Попробуем визуализировать двумерный массив. Создадим двумерный массив в коде, например, 5х5 элементов. Массив 5х5 это 5 столбцов и 5 строчек. Соответственно, \textit{каждая строчка это будет у нас младший индекс, а каждый столбец старший индекс}. Трехмерный массив может быть, например, 3х3х3 его можно визулизировать как всем известный кубик Рубика то есть, это три стоящих друг за другом таблицы 3х3. Также опишем его в коде ниже. Получается, что мы к таблице (ширине и высоте) добавили третье \textbf{измерение}, поэтому и массив получается \textbf{многомерным}, в данном случае, \textbf{трёхмерным}. Массивы б\'{о}льших размерностей тоже можно встретить в программах, но значительно реже, только лишь потому, что их действительно немного сложнее представить себе. Попробуем визуализировать двумерный массив. Создадим двумерный массив в коде, например, 5х5 элементов. Массив 5х5 это 5 столбцов и 5 строчек. Соответственно, \textit{каждая строчка это будет у нас младший индекс, а каждый столбец старший индекс}. Трехмерный массив может быть, например, 3х3х3 его можно визулизировать как всем известный кубик Рубика то есть, это три стоящих друг за другом таблицы 3х3. Также опишем его в коде ниже. Получается, что мы к таблице (ширине и высоте) добавили третье \textbf{измерение}, поэтому и массив получается \textbf{многомерным}, в данном случае, \textbf{трёхмерным}. Массивы б\'{о}льших размерностей тоже можно встретить в программах, но значительно реже, только лишь потому, что их действительно немного сложнее представить себе.
\begin{figure}[h!] \begin{figure}[h!]
\setlength{\columnsep}{30pt}
\begin{multicols}{2} \begin{multicols}{2}
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int twoDimensional[5][5]; int twoDimensional[5][5];
@ -291,7 +290,7 @@ int twoDimensional[5][5];
\begin{lstlisting}[language=C,style=CCodeStyle] \begin{lstlisting}[language=C,style=CCodeStyle]
int threeDimensional[3][3][3]; int threeDimensional[3][3][3];
\end{lstlisting} \end{lstlisting}
\centering
\begin{tikzpicture}[every node/.style={minimum size=1cm},on grid] \begin{tikzpicture}[every node/.style={minimum size=1cm},on grid]
\begin{scope}[every node/.append style={yslant=-0.5},yslant=-0.5] \begin{scope}[every node/.append style={yslant=-0.5},yslant=-0.5]
\node at (0.5,2.5) {2,0,0}; \node at (0.5,2.5) {2,0,0};
@ -324,9 +323,9 @@ int threeDimensional[3][3][3];
\node at (4.5,2.5) {2,0,1}; \node at (4.5,2.5) {2,0,1};
\node at (4.5,1.5) {1,0,1}; \node at (4.5,1.5) {1,0,1};
\node at (4.5,0.5) {0,0,1}; \node at (4.5,0.5) {0,0,1};
\node at (5.5,2.5) {2,0,2}; \node at (5.5,2.5) {2,1,2};
\node at (5.5,1.5) {1,0,2}; \node at (5.5,1.5) {1,1,2};
\node at (5.5,0.5) {0,0,2}; \node at (5.5,0.5) {0,1,2};
\draw (3,0) grid (6,3); \draw (3,0) grid (6,3);
\end{scope} \end{scope}
\end{tikzpicture} \end{tikzpicture}
@ -370,13 +369,10 @@ for (r = 0; r < rows; r++) {
Запустив нашу программу с формированием таблицы умножения увидим, что все отлично работает. Полный листинг программы приводить не целесообразно, поскольку это цикл с заполнением и цикл с выводом, полностью привед§нные выше. К тому же, такой код носит исключительно академический характер, и в случае действительной необходимости формирования таблицы Пифагора на экране промежуточное заполнение массива будет излишним, результат умножения целесообразнее сразу выводить на экран. Запустив нашу программу с формированием таблицы умножения увидим, что все отлично работает. Полный листинг программы приводить не целесообразно, поскольку это цикл с заполнением и цикл с выводом, полностью привед§нные выше. К тому же, такой код носит исключительно академический характер, и в случае действительной необходимости формирования таблицы Пифагора на экране промежуточное заполнение массива будет излишним, результат умножения целесообразнее сразу выводить на экран.
Как уже говорилось, все массивы могут содержать данные любых типов, в том числе и указатели. Именно это позволяет массиву хранить другие массивы, строки и прочие ссылочные типы данных. Используя массивы указателей, мы можем создать, например, массив строк. Как уже говорилось, все массивы могут содержать данные любых типов, в том числе и указатели. Именно это позволяет массиву хранить другие массивы, строки и прочие ссылочные типы данных. Используя массивы указателей, мы можем создать, например, массив строк.
\begin{lstlisting}[language=C,style=CCodeStyle]
\begin{figure}[H] char* stringArray[3] = {"Hello", "C", "World"};
\begin{lstlisting}[language=C,style=CCodeStyle] int r;
char* stringArray[3] = {"Hello", "C", "World"}; for (r = 0; r < 3; r++)
int r;
for (r = 0; r < 3; r++)
printf("%s ", stringArray[r]); printf("%s ", stringArray[r]);
\end{lstlisting} \end{lstlisting}
\end{figure}
Это указатели на строки, а точнее, на строковые литералы. Такой тип данных (строковый литерал) является указателем. И мы можем создать из этих указателей массив. Используя массивы указателей, мы можем создать, например, двумерный массив, где каждый элемент не обязан быть того же размера, что и остальные (но обязан быть того же типа, как мы помним). Но строки и сложно составленные указатели - это темы, которые очень сильно выходят за рамки Основ языка, хотя, конечно, это не помешает нам немного подробнее разобраться со строками в следующем разделе. Это указатели на строки, а точнее, на строковые литералы. Такой тип данных (строковый литерал) является указателем. И мы можем создать из этих указателей массив. Используя массивы указателей, мы можем создать, например, двумерный массив, где каждый элемент не обязан быть того же размера, что и остальные (но обязан быть того же типа, как мы помним). Но строки и сложно составленные указатели - это темы, которые очень сильно выходят за рамки Основ языка, хотя, конечно, это не помешает нам немного подробнее разобраться со строками в следующем разделе.

View File

@ -1,5 +1,4 @@
\section{Строки} \section{Строки}
\subsection{Основные понятия}
Получив в предыдущих разделах представление об указателях и массивах, и вскользь несколько раз упомянув строки, пришла пора изучить их подробнее. Получив в предыдущих разделах представление об указателях и массивах, и вскользь несколько раз упомянув строки, пришла пора изучить их подробнее.
Итак, что же такое \textbf{строка}. В повседневной жизни строка \textit{это набор или последовательность символов}. Так вот в языке С строка - это тоже последовательность символов. А последовательности значений, в том числе символьных, в языке С представлены, как вы, уже знаете, массивами и указателями. Никакого примитива \code{string} в языке С нет. Как бы нам этого не хотелось - его нет. Но есть и хорошая новость, примитива \code{string} не существует и в других языках (здесь имеются ввиду, конечно, си-подобные языки, то есть примерно треть вообще всех языков программирования, известных сегодня). Раз строка - это массив или указатель, это всегда ссылочный тип данных. Например, строку можно объявить двумя способами: Итак, что же такое \textbf{строка}. В повседневной жизни строка \textit{это набор или последовательность символов}. Так вот в языке С строка - это тоже последовательность символов. А последовательности значений, в том числе символьных, в языке С представлены, как вы, уже знаете, массивами и указателями. Никакого примитива \code{string} в языке С нет. Как бы нам этого не хотелось - его нет. Но есть и хорошая новость, примитива \code{string} не существует и в других языках (здесь имеются ввиду, конечно, си-подобные языки, то есть примерно треть вообще всех языков программирования, известных сегодня). Раз строка - это массив или указатель, это всегда ссылочный тип данных. Например, строку можно объявить двумя способами:
@ -68,7 +67,6 @@ 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!} это тоже строка. Выведем наши строки в консоль и убедимся, что автор не ошибся и их действительно можно так объявлять.
@ -104,7 +102,6 @@ zsh: bus error ./program
$ $
\end{verbatim} \end{verbatim}
\subsection{Строки и функции}
Но довольно об ограничениях. Указатели на строки не такие уж бесполезные, мы можем, например, возвращать их из функций. То есть, мы можем объявить тип возвращаемого из функции значения как указатель \code{char*}, вернуть из нее строку и, например, вывести в консоль. Это открывает перед нами широчайшие возможности по работе с текстами. Но довольно об ограничениях. Указатели на строки не такие уж бесполезные, мы можем, например, возвращать их из функций. То есть, мы можем объявить тип возвращаемого из функции значения как указатель \code{char*}, вернуть из нее строку и, например, вывести в консоль. Это открывает перед нами широчайшие возможности по работе с текстами.
\begin{figure}[h!] \begin{figure}[h!]
@ -153,16 +150,14 @@ int main(int argc, const char* argv[]) {
\frm{В некоторых случаях может показаться, что никакой проблемы нет, поскольку написанная таким образом программа благополучно поприветствует пользователя, но такое поведение не гарантируется ни одним компилятором и ни одной операционной системой, поскольку возвращаемый таким образом указатель может быть переписан абсолютно любой следующей инструкцией кода. Такое \textit{исчезающее} значение называется \textbf{xvalue}.} \frm{В некоторых случаях может показаться, что никакой проблемы нет, поскольку написанная таким образом программа благополучно поприветствует пользователя, но такое поведение не гарантируется ни одним компилятором и ни одной операционной системой, поскольку возвращаемый таким образом указатель может быть переписан абсолютно любой следующей инструкцией кода. Такое \textit{исчезающее} значение называется \textbf{xvalue}.}
Выход очень простой: раз указатель не идёт в \code{int main (int argc, char *argv[])}, надо чтобы \code{int main (int argc, char *argv[])} дал нам указатель. Добавим в параметры функции указатель на выходную строку, и напишем что для начала сложить строки и положить в локальный массив \code{strcat(welcome, name)}. Добавим в основную функцию массив \code{char result[]}, который будет хранить результат и передадим в функцию \code{helloFunction} аргументы \code{name} и \code{result}. А раз функция больше ничего не возвращает, вполне легально сделать её \code{void}. Выход очень простой: раз указатель не идёт в \code{int main (int argc, char *argv[])}, надо чтобы \code{int main (int argc, char *argv[])} дал нам указатель. Добавим в параметры функции указатель на выходную строку, и напишем что для начала сложить строки и положить в локальный массив \code{strcat(welcome, name)}. Добавим в основную функцию массив \code{char result[]}, который будет хранить результат и передадим в функцию \code{helloFunction} аргументы \code{name} и \code{result}. А раз функция больше ничего не возвращает, вполне легально сделать её \code{void}.
\begin{lstlisting}[language=C,style=CCodeStyle]
\begin{figure}[H] void helloFunction(char* name, char* out) {
\begin{lstlisting}[language=C,style=CCodeStyle]
void helloFunction(char* name, char* out) {
char welcome[255] = "Hello, "; char welcome[255] = "Hello, ";
strcat(welcome, name); strcat(welcome, name);
out = welcome; out = welcome;
} }
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
char name[256]; char name[256];
char result[256]; char result[256];
gets(name); gets(name);
@ -170,9 +165,8 @@ int main(int argc, const char* argv[]) {
helloFunction(name, result); helloFunction(name, result);
puts(result); puts(result);
return 0; return 0;
} }
\end{lstlisting} \end{lstlisting}
\end{figure}
Запускаем, и \textbf{снова не работает}, да ещё и как интересно, смотрите! Предупреждение, ладно, понятно, мы о нём говорили, но дальше, когда мы вводим имя на выходе получается какая-то совсем уж непонятная строчка, совсем не похожая на приветствие. Запускаем, и \textbf{снова не работает}, да ещё и как интересно, смотрите! Предупреждение, ладно, понятно, мы о нём говорили, но дальше, когда мы вводим имя на выходе получается какая-то совсем уж непонятная строчка, совсем не похожая на приветствие.
\begin{verbatim} \begin{verbatim}
@ -204,8 +198,9 @@ 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, ”;
@ -215,11 +210,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{atoi();} и \code{atof();} переводящие написанные в строке цифры в численные переменные внутри программы. \code{atoi();} переводит в \code{int}, а \code{atof();} во \code{float}, соответственно. Для примера объявим переменную \code{num}, предложим пользователю ввести цифру, естественно в виде строки. Будем принимать ее при помощи небезопасной функции \code{gets();}, хотя как мы помним, могли бы и \code{scanf();} который сразу бы преобразовал строку согласно использованного заполнителя. Заведем переменную \code{int number} для хранения результата работы функции преобразования. Затем, давайте умножим результат сам на себя, чтобы убедиться, что это и правда число, причём именно то, которое мы ввели, и выведем окончательное число в консоль.
\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: ");
@ -228,10 +223,9 @@ 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();} возвращает истину, если символ в аргументе является символом из алфавита;
@ -242,6 +236,7 @@ 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, ”;
@ -253,3 +248,4 @@ void helloFunction (char* name, char* out) {
strcpy(out, welcome); strcpy(out, welcome);
} }
\end{lstlisting} \end{lstlisting}
\end{figure}

View File

@ -1,120 +0,0 @@
\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{figure}[H]
\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}
\end{figure}
Обычно доступ к переменным внутри структуры осуществляется привычным для высокоуровневых языков способом - через точку. Есть одно исключение, но об этом чуть позже. Создадим три переменных для хранения двух дробей, с которыми будем совершать операции, и одну для хранения результата. Инициализируем переменные какими-нибудь значениями. Опишем целочисленные значения, опишем делимое для обеих дробей и опишем делитель для обеих дробей. Для простоты будем использовать простые дроби: \( 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}}

View File

@ -1,65 +0,0 @@
\section{Файлы}
В предыдущих разделах мы познакомились почти со всеми существующими в языке С типами данных, как примитивными, так и ссылочными. Довольно подробно рассмотрели работу почти всех операторов языка. Пришло время выйти за пределы программы (хотя бы в части хранения данных) и поговорить о взаимодействии программы с операционной системой, а именно - о чтении и записи в файловую систему компьютера. Файловая система \textit{любого} компьютера - \textbf{это структура}. Для языка С файл - это тоже структура. Структура, хранящая данные о положении курсора в файле, его название, буферы, флажки и прочие свойства. Файлы делятся на два основных типа - текстовые и бинарные. Мы рассмотрим работу с текстовыми, поскольку работа с бинарными практически ничем не отличается, а текст всё-таки проще воспринимается.
С места в карьер, опишем переменную, хранящую указатель на структуру <<файл>>. Вся основная работа будет проходить через неё. Для того, чтобы присвоить этой переменной указатель на какой-то реальный файл будем пользоваться функцией \code{fopen();}, которая возвращает указатель на адрес в памяти. Функция принимает в качестве аргументов имя файла в двойных кавычках и режим его открытия.
\begin{lstlisting}[language=C,style=CCodeStyle]
FILE *f;
\end{lstlisting}
Основных используемых режимов шесть:
\begin{itemize}
\item чтение,
\item запись,
\item добавление,
\item двоичное чтение,
\item двоичная запись,
\item двоичное добавление.
\end{itemize}
\subsection{Запись}
Функции \textbf{записи и добавления} \textit{создают} файл в случае его отсутствия. А функция \textbf{записи} \textit{стирает} файл, если он существует и не пустой. Итак создадим текстовый файл с каким-то неожиданным названием, вроде \code{filename.txt}, и скажем нашей программе, что нужно будет его создать, если его не существует, перезаписать, если существует, а дальше мы будем в него записывать данные, то есть режим открытия будет \code{"w"}. Имя файла в аргументе может быть как полным (абсолютным), вроде \code{C:\\FILE.TXT} тогда файл будет создан в корне диска C, или \code{/home/user/file.txt}. Также имя файла может быть и относительным, каким мы его указали сейчас, это значит, что файл будет создан в той папке, в которой запускается наша программа.
\begin{lstlisting}[language=C,style=CCodeStyle]
f = fopen("filename.txt", "w");
\end{lstlisting}
В случае, если файл не найден или по какой-то причине не создался, в переменную \code{f} запишется нулевой указатель, поэтому перед тем, как начать работу с файлом, нужно проверить, смогла-ли программа его открыть, для этого запишем условие, что если в наш указатель записался нулевой указатель, то дальнейшее выполнение функции \code{int main (int argc, char *argv[])} не имеет смысла.
\begin{lstlisting}[language=C,style=CCodeStyle]
if (file == NULL) return 1;
\end{lstlisting}
Если всё хорошо, можем записывать в файл данные. Для записи в файл есть несколько функций, мы воспользуемся самой простой и очевидной:\code{fprintf();}, в неё в качестве первого аргумента обязательно нужно передать указатель на файл, в который мы собираемся писать, а дальше можно использовать как знакомый нам \code{printf();} со всеми его удобствами, заполнителями, экранированными последовательностями и дополнительными аргументами. После того как мы закончили запись в файл его необходимо
закрыть, вызвав функцию \code{fclose();}
\begin{lstlisting}[language=C,style=CCodeStyle]
fprintf(f, "Hello, files! %s", "we did it!\n");
fclose(f);
\end{lstlisting}
Запустив проект, посмотрим что у нас получилось. В терминале не будет никакого вывода, но если перейти в проводник, то можно увидеть, что в папке проекта появился файл \code{filename.txt}, в котором написано наше содержимое. Открывается такой текстовый файл любым обычным блокнотом
\subsection{Чтение}
Отлично, программа теперь умеет сохранять результаты своей работы в файлы, то есть у нас появилось ещё одно средство вывода информации, помимо терминала. Теперь давайте рассмотрим не менее важную тему, а именно - чтение из файла. Ведь тогда мы сможем не просто иначе выводить информацию, но и сохранять её на жёстком диске от запуска к запуску программы, а значит хранить какие-то настройки или промежуточные значения. Для этого нам нужно совершить несколько несложных действий с применением неожиданно знакомой функции \code{fscanf();} чтобы прочитать форматированные значения из файла:
\begin{itemize}
\item создать массив \code{char[]}, для примера, назовем его \code{word};
\item нужный файл открыть в режиме чтения;
\item при помощи функции \code{fscanf();} считать из файла некоторую строку, которую положим в этот массив.
\end{itemize}
Далее выведем в консоль строку которую прочитали, для этого воспользуемся привычной нам функцией \code{printf();} а затем выведем пустую строку. Запустим нашу программу и увидим, что в консоль вывелось слово \code{Hello}, функция \code{fscanf();} отлично отработала, прочитав все символы из файла до пробела.
\begin{lstlisting}[language=C,style=CCodeStyle]
char word[256];
f = fopen("filename.txt", "r");
fscanf(f, "%s", &word);
printf("%s\n", word);
\end{lstlisting}
Но сколько данных читать? Как узнать, что достигнут конец файла? Для этого придумали функцию \code{feof();} (англ. file: end of file) возвращающую ноль, если конец файла не достигнут, и единицу если достигнут. Опишем цикл, который выведет в консоль все полученные \code{fscanf();} строки из нашего файла. То есть, мы циклически пройдемся по всему файлу, пока не будет достигнут его конец и будем выводить считанные строки в консоль:
\begin{lstlisting}[language=C,style=CCodeStyle]
char word[256];
f = fopen("filename.txt", "r");
while (!feof(file)) {
fscanf(f, "%s", &word);
printf("%s ", word);
}
printf("\n");
fclose(f);
\end{lstlisting}

View File

@ -1,58 +0,0 @@
#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");
}