forked from ivan-igorevich/basic-c
pointers+minor refactoring of previous sections
This commit is contained in:
parent
85d24e586d
commit
d2bb51931c
BIN
build/main.pdf
BIN
build/main.pdf
Binary file not shown.
179
main.tex
179
main.tex
|
@ -25,132 +25,59 @@
|
|||
\import{sections/}{07-functions}
|
||||
|
||||
\section{Указатели}
|
||||
% Коллеги, здравствуйте. Вот и пришла пора поговорить о серьёзном низкоуровневом программировании. О том, от чего стараются оградить программистов языки высокого уровня и современные фреймворки. Об указателях.
|
||||
% На этом уроке мы поговорим о том, что такое указатели и как они соотносятся с остальными переменными, что такое передача аргумента по значению и по ссылке.
|
||||
|
||||
|
||||
|
||||
% Как мы наверняка помним, все переменные и константы, используемые в программе, хранятся в оперативной памяти. У каждой переменной и константы в памяти есть свой собственный адрес. Адреса принято показывать на экране в виде шестнадцатиричных чисел. Этот адрес выдаётся нашей программе операционной системой, а язык Си позволяет использовать его на усмотрение программиста. Иными словами в языке С есть возможность получить доступ к переменной не только по имени, но и по адресу. Получение доступа к переменной по адресу называется разыменовыванием. Давайте выведем в консоль всю имеющуюся информацию о переменной «а». Мы знаем, что это целочисленная переменная значением 50, которая хранится по какому-то адресу.
|
||||
% Адрес переменной может храниться в специальной переменной, которая называется указатель.
|
||||
% Для объявления указателя пишут тип переменной, адрес которой будет храниться в указателе, знак звёздочки и имя указателя. Такому указателю можно присвоить значение адреса существующей переменной, также как мы делали это раньше с другими типами данных
|
||||
% Для наглядности снова выведем всю имеющуюся у нас на данный момент информацию на экран. Напомню, для вывода адреса используется заполнитель %p. Выведем в консоль десятичное значение переменной pointer и адрес переменной pointer
|
||||
% Увидим, что значение переменной pointer является совершенно случайным числом.
|
||||
% Если мы представим это в виде адреса то всё встанет на свои места. Адрес «а» - это значение переменной pointer.
|
||||
|
||||
|
||||
% СЛАЙД ГДЕ БУКВЫ И ЦИФРЫ ТОРЧАТ ИЗ КОРОБОК С «АДРЕСАМИ»
|
||||
|
||||
% СЛАЙД С ПРИМЕРОМ АДРЕСА
|
||||
|
||||
|
||||
|
||||
|
||||
% РАЗЫМЕНОВЫВАНИЕ (ВОЗМОЖНО КАРТИНКА)
|
||||
|
||||
|
||||
% int main (int argc, const char** argv) {
|
||||
% int a = 50;
|
||||
% printf("value of a is %d \n", a);
|
||||
% printf("address of a is %p \n", &a);
|
||||
|
||||
|
||||
|
||||
|
||||
% int * pointer;
|
||||
|
||||
|
||||
% pointer = &a;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
% printf("value of pointer is %d \n", pointer);
|
||||
% printf("address of pointer is %p \n", &pointer);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
% printf("value of pointer is %d %p \n", pointer);
|
||||
% Пока что ничего необычного, все эти операции мы выполняли на предыдущих уроках. Но поскольку pointer это необычная переменная, а указатель, то мы можем получить не только её значение, но и значение переменной, на которую она указывает. Давайте запишем, вывести в консоль «переменная pointer указывает на такое-то значение» и разыменуем pointer. Т.е. получим доступ к переменной, на которую ссылается указатель pointer.
|
||||
% Таким образом, получается, что в указателе хранится ссылка на значение некоторой переменной. То есть указатель - это простейший ссылочный тип данных. Без указателей невозможно себе представить создание классов, и всеми любимого объектно-ориентированного программирования, даже массивов.
|
||||
|
||||
|
||||
% СЛАЙД СО СТРЕЛОЧКАМИ ЗНАЧЕНИЕ-ПЕРЕМЕННАЯ-АДРЕС-УКАЗАТЕЛЬ-И-ОБРАТНО
|
||||
|
||||
|
||||
% printf(“variable ‘pointer’ points at: %d”, * pointer);
|
||||
|
||||
|
||||
% Давайте изменим значение переменной «а», не на прямую, а с использованием указателя. Как видим, значение переменной изменилось.
|
||||
% *pointer = 70;
|
||||
% printf("value of a is %d \n", a);
|
||||
|
||||
|
||||
% Теперь, когда мы знаем об указателях, и умеем получать значения переменных, на которые они указывают, а также изменять их, перед нами открываются невообразимые ранее перспективы. Мы можем писать функции не создавая в них копии переменных, а передавать в них ссылки на уже существующие переменные, тем самым экономя память, и ускоряя выполнение программы.
|
||||
|
||||
|
||||
% Например, не составит труда написать ПРОГРАММУ, которая бы меняла местами значения двух переменных. Но написать ФУНКЦИЮ, которая бы проделывала тоже самое невозможно без применения указателей. Почему? очень просто - внутри функции создаются свои собственные переменные, значения которых меняются местами, и даже если мы вернём одну из них – как быть со второй? А получить доступ ко второй переменной мы не можем, поскольку, помним, она находится в области видимости функции. Такая передача аргументов называется передачей по значению (мы берём значение некоторой переменной и передаём внутрь функции). Т.е. мы берем значения некоторых переменных в функции Мэйн и передаем их в функцию, где создаём новые переменные с этими, переданными, значениями.
|
||||
% Как решить эту проблему? Передавать не значения переменных, а их адрес, тем самым сообщив функции, что нужно не создавать новые копии переменных, а сделать что-то с уже существующими, и, естественно указать адрес, с какими именно. Передача в качестве аргумента адреса, и создание в теле функции нового указателя называется передачей по
|
||||
% ссылке. То есть функция будет ссылаться на переменные, на которые мы укажем и оперировать их значениями.
|
||||
% Давайте немного модифицируем нашу функцию – передадим ей адрес переменных. Внутри функции у нас будут создаваться не переменные, а указатели на переменные, т.е. мы будем ссылаться на те же самые значения – т.е. мы будем изменять переменные, которые создали в функции Мэйн. Теперь нам не нужно ничего возвращать, потому что в функции ничего не создавалось, поэтому заменим int на void.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
% int swap_variables(int x, int y) {
|
||||
% return x;
|
||||
% }
|
||||
|
||||
|
||||
% int first = 50;
|
||||
% int second = 40;
|
||||
% swap_variables(40, 50);
|
||||
|
||||
% СЛАЙД ПЕРЕДАЧА ПО ЗНАЧЕНИЮ И ПО ССЫЛКЕ
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
% swap_variables(&first, &second);
|
||||
|
||||
|
||||
|
||||
|
||||
% void swap_variables(int *x, int *y) {
|
||||
% int temp z = *x;
|
||||
% *x = *y;
|
||||
% *y = *z;
|
||||
% }
|
||||
% Удалим из функции все лишнее и выведем в консоль переменные first и second до их смены и после. Передадим в функцию swap переменные first и second и убедимся, что все работает корректно.
|
||||
% int first = 50;
|
||||
% int second = 40;
|
||||
% printf("first = %d, second = %d \n", first, second);
|
||||
% swap_variables(&first, &second);
|
||||
% printf("first = %d, second = %d \n", first, second);
|
||||
|
||||
|
||||
% Применение такого подхода открывает перед нами широкие возможности, которые мы рассмотрим в следующих видео.
|
||||
|
||||
|
||||
|
||||
|
||||
% В этом видео мы познакомились с таким и понятиями, как указатели, передача по ссылке и передача по значению. До новых встреч.
|
||||
% СЛАЙД С ИТОГАМИ
|
||||
|
||||
Вот и пришла пора поговорить о серьёзном низкоуровневом программировании. О том, от чего стараются оградить программистов языки высокого уровня и современные фреймворки. Об указателях, что такое указатели и как они соотносятся с остальными переменными, что такое передача аргумента по значению и по указателю.. Этого разговора боятся все начинающие программисты и не без причин: работа с указателями на память может не только навредить программе, но и, например, оказать влияние на операционную систему (автор знает, что этот тезис не всегда справедлив, также, как тезис со стр. \hyperref[text:simplify]{\pageref{text:simplify}}, но мы снова идём на такое упрощение ради того, чтобы было понятно, насколько это мощный инструмент). Также сразу отметим, что указателям достался свой собственный раздел в этом документе, хотя формально это просто ещё один тип данных.
|
||||
|
||||
Как мы, наверняка, помним, все переменные и константы, используемые в программе, хранятся в оперативной памяти. Оперативная память разделена на несколько участков, но не это для нас сейчас важно. Важно то, что у каждой переменной и константы в памяти есть свой собственный адрес. Адреса принято показывать на экране в виде шестнадцатиричных чисел. Этот адрес выдаётся нашей программе операционной системой, а язык С позволяет использовать его на усмотрение программиста. Иными словами в языке С есть возможность получить доступ к переменной не только по имени, но и по адресу. Получение доступа к значению переменной по адресу называется \textbf{разыменованием}. Давайте выведем в консоль всю имеющуюся информацию о переменной \code{а}. Мы знаем, что это целочисленная переменная значением 50, которая хранится по какому-то адресу.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
int a = 50;
|
||||
printf("value of 'a' is %d \n", a);
|
||||
printf("address of 'a' is %p \n", &a);
|
||||
\end{lstlisting}
|
||||
\end{figure}
|
||||
Адрес переменной может храниться в специальной переменной, которая называется указатель. Для объявления указателя пишут тип переменной, адрес которой будет храниться в указателе, знак звёздочки и имя указателя. Такому указателю можно присвоить значение адреса существующей переменной, также как мы делали это раньше с другими типами данных. Для наглядности снова выведем всю имеющуюся у нас на данный момент информацию на экран. Напомню, для вывода адреса используется заполнитель \code{\%p}. Выведем в консоль десятичное значение переменной \code{pointer} и адрес переменной \code{pointer}. Увидим, что значение переменной \code{pointer} является как будто бы совершенно случайным числом, но ниже мы представим это значение не в виде обычного целого числа в десятичной системе счисления, а в виде адреса (заполнитель \code{\%p}) то всё встанет на свои места.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
int * pointer;
|
||||
pointer = &a;
|
||||
|
||||
printf("value of 'pointer' is %d \n", pointer);
|
||||
printf("address of 'pointer' is %p \n", &pointer);
|
||||
printf("value of 'pointer' is %p \n", pointer);
|
||||
|
||||
\end{lstlisting}
|
||||
\end{figure}
|
||||
Так, объединённый вывод двух предыдущих листингов будет примерно такой, и можно явно увидеть, что адрес \code{а} - это значение переменной \code{pointer}:
|
||||
\begin{verbatim}
|
||||
value of 'a' is 50
|
||||
address of 'a' is 000000000061FE1C
|
||||
value of 'pointer' is 6422044
|
||||
address of 'pointer' is 000000000061FE10
|
||||
value of 'pointer' is 000000000061FE1C
|
||||
\end{verbatim}
|
||||
В общем-то, пока что ничего необычного, все эти операции мы выполняли на предыдущих уроках. Но поскольку \code{pointer} это немного необычная переменная, а указатель, то мы можем получить не только её значение, но и \textit{значение переменной, на которую она указывает}, именно этот процесс называется разыменованием указателя. Давайте запишем, вывести в консоль <<переменная pointer указывает на такое-то значение>> и разыменуем \code{pointer}. То есть получим доступ к значению переменной, на которую ссылается указатель \code{pointer}.
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
printf("variable 'pointer' points at: %d", *pointer);
|
||||
\end{lstlisting}
|
||||
Таким образом, получается, что в указателе хранится ссылка на значение некоторой переменной, и мы можем это значение изменить. Давайте изменим значение переменной \code{а}, не на прямую, а с использованием указателя. Как видим, значение переменной изменилось.
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
*pointer = 70;
|
||||
printf("value of a is %d \n", a);
|
||||
\end{lstlisting}
|
||||
То есть указатель - это простейший \textbf{ссылочный тип данных}. Без указателей невозможно себе представить создание классов, и всеми любимого объектно-ориентированного программирования, даже массивов или строк. Теперь, когда мы знаем об указателях, и умеем получать значения переменных, на которые они указывают, а также изменять их, перед нами открываются невообразимые ранее перспективы. Мы можем писать функции не создавая в них копии переменных, а передавать в них указатели на уже существующие переменные, тем самым экономя память, и ускоряя выполнение программы. Например, не составит труда написать \textit{программу}, которая бы меняла местами значения двух переменных. Но написать \textit{функцию}, которая бы проделывала тоже самое невозможно без применения указателей. Почему? Очень просто - в параметре функции создаются свои собственные переменные, значения которых задаются копированием аргументов вызова, и меняются местами именно эти, скопированные значения в локальных переменных. И даже если мы вернём одно из этих значений – как быть со вторым? А получить доступ к значению второй переменной мы не можем, поскольку, помним, она находится в области видимости функции и недоступна извне. Такая передача аргументов называется \textit{передачей по значению} (мы берём значение некоторой переменной и копируем внутрь функции, иногда такую передачу значений ещё называют \textit{передачей копированием}). Т.е. мы берем значения некоторых переменных в функции \code{int main (int argc, char *argv[])} и передаем их в функцию, где создаём новые переменные с этими, переданными, значениями.
|
||||
|
||||
Как решить эту проблему? Передавать не значения переменных, а их адрес, тем самым сообщив функции, что нужно не создавать новые копии переменных, а сделать что-то с уже существующими, и, естественно указать адрес, с какими именно. Передача в качестве аргумента адреса, и создание в теле функции нового указателя называется \textit{передачей по указателю}.
|
||||
\frm{Для языка С также справедливо выражение <<передача по ссылке>>, поскольку в языке С нет отдельной операции передачи по ссылке. Так, например, в языке С++ передача по ссылке и передача по указателю - это разные операции.}
|
||||
То есть функция будет ссылаться на переменные, на которые мы укажем и оперировать их значениями. Давайте немного модифицируем нашу программу обмена значениями внутри двух переменных (\hyperref[code:programswap]{\ref{code:programswap}}): опишем её в виде функции, принимающей в качестве параметров два указателя на целые числа типа \code{char}, и передадим адреса созданных в \code{int main (int argc, char *argv[])} переменных. Внутри функции, при её вызове, у нас будут создаваться не переменные, а указатели на переменные, то есть мы будем ссылаться на те самые переменные, созданные вне функции, и будем менять именно их (тех переменных) значения. Таким образом, нам не нужно ничего возвращать, потому что в функции ничего не создавалось, и типом возвращаемого значения функции должен быть \code{void}.
|
||||
|
||||
\begin{multicols}{2}
|
||||
\lstinputlisting[language=C,style=CCodeStyle]{../sources/swapfunc.c}
|
||||
\columnbreak
|
||||
\lstinputlisting[language=C,style=CCodeStyle]{../sources/swapprog.c}
|
||||
\end{multicols}
|
||||
Применение такого подхода открывает перед нами широкие возможности, некоторые из них мы рассмотрим в следующих разделах.
|
||||
|
||||
\section{Массивы}
|
||||
% Здравствуйте, коллеги, рад всех приветствовать на очередном занятии по Основам языка С. В этом видео нас с вами ждут массивы и ещё пара слов о директивах компилятору, иногда также называемых директивами препроцессора. С них и начнём.
|
||||
|
|
|
@ -46,5 +46,6 @@ non-windows $ ./program
|
|||
\frm{\centering\code{int main(int argc, char** args)}}
|
||||
более подробно о функциях, их синтаксисе и аргументах мы поговорим позднее, на данном этапе можно просто запомнить такое (или же упрощённое \code{int main()} описание главной функции любой программы на языке С. Далее в фигурных скобках пишется так называемое <<тело>> программы, то есть именно те операторы, функции и алгоритмы, которые являются программой. По сути, всё наше программирование будет происходить либо в этой функции, либо будет довольно тесно с ней связано.
|
||||
\paragraph{Возврат из функции \code{return;}} это оператор явно завершающий выполнение функции \code{main} и, соответственно, программы. Все операторы, кроме директив препроцессора, комментариев и описаний тел функций должны заканчиваться точкой с запятой.
|
||||
\label{text:simplify}
|
||||
\frm{\textbf{Внимание!} Далее Вы прочитаете тезис, который является значительным упрощением реальной ситуации. Автор пошёл на такое упрощение по двум причинам: во-первых, поскольку в классическом С дела обстояли именно так, а в современных компьютерах ситуация меняется настолько быстро, что никакой текст не сможет оставаться актуальным, и во-вторых, поскольку целью данного документа не является детальное описание архитектур современных операционных систем.}
|
||||
Поскольку программа написанная на языке С работает на одном уровне с операционной системой, а не в средах виртуализации, как это происходит в Java, например, она должна сообщить операционной системе, что она отработала нормально. Это делается посредством возврата в качестве результата работы программы кода ноль. В нашем случае, оператор \code{return} сообщает код \code{0}, говорящий об успешности завершения работы программы. Такой возвратный код - исторически сложившаяся договорённость между программистами: ненулевой код означает аварийное завершение программы и сообщает системе, что программа завершена некорректно и необходимо дополнительно и явно освобождать занятые ею ресурсы.
|
||||
|
|
|
@ -91,12 +91,9 @@ $
|
|||
\end{figure}
|
||||
|
||||
Функция \code{scanf();} – это функция форматированного ввода. Принцип её работы очень похож на принцип работы функции \code{printf();} В двойных кавычках мы указываем в виде заполнителя тип переменной, которую ожидаем от пользователя, а в качестве дополнительного аргумента указываем адрес той переменной, в которую хотим записать введённые пользователем данные. Получается процесс прямо противоположный выводу. В этой функции можно использовать все те же заполнители, что и при выводе, поэтому пользователь может ввести как целые числа, так и символы, строки и числа с плавающей точкой.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
printf("You entered %d, let's double it: %d\n", input, input * 2);
|
||||
\end{lstlisting}
|
||||
\end{figure}
|
||||
Выведем в консоль изменённое число, введённое пользователем, чтобы удостовериться, что всё работает. В результате запуска программы, консоль застынет в ожидании пользовательского ввода. Пользователь сообщает консоли (терминалу операционной системы) об окончании ввода нажатием клавиши \code{Enter}:
|
||||
|
||||
\begin{figure}[h!]
|
||||
|
|
|
@ -275,6 +275,7 @@ $
|
|||
// here will be the swapping algorithm
|
||||
printf("a = %d, b = %d\n", a, b);
|
||||
\end{lstlisting}
|
||||
\label{code:programswap}
|
||||
\end{figure}
|
||||
Далее, напишем некую конструкцию, которая при детальном изучении не представляет из себя никакой магии. В переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в переменную \code{b} нужно будет записать результат вычисления \code{b \^{} a} и наконец в переменную \code{а} нужно будет записать результат вычисления \code{a \^{} b}, в коде ниже будет приведена сразу сокращённая запись:
|
||||
|
||||
|
|
|
@ -82,11 +82,9 @@ $
|
|||
\end{figure}
|
||||
которая будет обозначать, что в случае если \code{a > b}, в переменную \code{c} запишется значение {b}, и наоборот если \code{b > a}, то в переменную \code{c} запишется значение \code{а}. Также тернарный оператор можно использовать для удобного форматированного вывода, например опишем функцию \code{printf();} которая будет печатать нам строку, и в зависимости от условия, это будет \code{"true"} либо \code{"false"}:
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
printf("%s", (1 > 0) ? "true" : "false");
|
||||
\end{lstlisting}
|
||||
\end{figure}
|
||||
Проверим как это работает, и в результате видим true, потому что единица действительно больше нуля.
|
||||
|
||||
\begin{figure}[h!]
|
||||
|
@ -148,12 +146,10 @@ $
|
|||
\paragraph{Логические операторы}
|
||||
Их три: это \code{И (\&\&)}, \code{ИЛИ (||)}, \code{НЕ (!)}. В отличие от арифметических двоичных операторов - логические возвращают истину или ложь т.е. в случае языка С - \code{1} либо \code{0} и работают с операндами слева и справа целиком, а не поразрядно. В этом легко убедиться, попытавшись вывести на экран результат сравнения с заведомо неверным форматированием и получив ошибку, говорящую о том, что компилятор ожидал от нас число, а мы хотим его отформатировать в строку.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
printf("%s\n", 1 == 1);
|
||||
\end{lstlisting}
|
||||
\end{figure}
|
||||
Некоторые компиляторы выдают ошибку на этапе компиляции, некоторые компилируют такой код, но программа не сможет выполниться и выдаст ошибку \textbf{Segmentation fault}. Это зависит от большого количества факторов, таких как тип операционной системы, тип и версия компилятора, версия используемого стандарта языка.
|
||||
Некоторые компиляторы выдают ошибку на этапе компиляции, некоторые компилируют такой код, но программа не сможет выполниться и выдаст ошибку \textbf{Segmentation fault}, то есть ошибку доступа к памяти (попытка обратиться к недоступной памяти или попытка обратиться к памяти неподобающим образом). В этой конкретной ситуации, мы попытаемся интерпретировать как строку часть памяти, которая находится по адресу 1, что находится далеко за пределами доступа программы. Это поведение (ошибка компиляции или ошибка времени выполнения) зависит от большого количества факторов, таких как тип операционной системы, тип и версия компилятора, версия используемого стандарта языка.
|
||||
|
||||
Отдельного внимания заслуживает применение оператора поразрядного (арифметического) \code{ИСКЛЮЧАЮЩЕГО ИЛИ} в качестве логического. В случае такого применения оператор \code{ИСКЛЮЧАЮЩЕГО ИЛИ}, фактически, дублирует сравнение на неравенство, это легко объяснить, проведя анализ происходящего с числами при таком сравнении:
|
||||
|
||||
|
|
|
@ -147,9 +147,7 @@ int main(int argc, char *argv[]) {
|
|||
\frm{С определением функции тесно связано понятие \textit{сигнатуры} функции. Сигнатура функции для разных языков программирования представляется немного разным составом сведений, так, например, в языке С сигнатура - это тип возвращаемого значения, название функции и порядок типов параметров, например, для функции суммирования чисел, описанной выше, это будет \code{int sum(int, int)}.}
|
||||
Опишем прототип функции \code{isPrime()}, описав сигнатуру этой функции. Обратите внимание, что допустимо в определении функции также писать названия параметров, а не только их типы, но это необязательно.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\begin{lstlisting}[language=C,style=CCodeStyle]
|
||||
int isPrime(int number);
|
||||
\end{lstlisting}
|
||||
\end{figure}
|
||||
Из таких определений часто составляют так называемые \textit{заголовочные файлы}. Заголовочные файлы это мощный инструмент модульной разработки. Мы уже неоднократно видели подключение заголовочного файла \code{stdio.h}, Обнаружив данный файл на диске компьютера, мы увидим, что в нём содержатся другие подключения библиотек, директивы препроцессора (о которых более подробно мы будем говорить на следующих занятиях) и прототипы функций (например, так часто используемой нами \code{printf()}). Заголовочным этот файл называется, потому что его обычно пишут в коде программы в самом верху, и фактически, компилятор просто вставляет его содержимое в текст программы. Расширение файла (\code{.h}) является сокращением от английского слова header, заголовок. Обратите внимание, что подключая заголовочный файл \code{stdio.h} мы получаем вообще всю функциональность стандартного ввода-вывода, то есть, например, работу с файлами, которую можем и не использовать. В стандарте С++20 было принято решение о переходе для поддержки повторяемости кода от заголовочных файлов к целостным модулям, импортируемым отдельно. Это позволяет интегрировать в программу только нужный функционал, игнорируя всю остальную библиотеку.
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdio.h>
|
||||
|
||||
void swap(char* a, char* b) {
|
||||
*a ^= *b;
|
||||
*b ^= *a;
|
||||
*a ^= *b;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
char a = 11;
|
||||
char b = 15;
|
||||
printf("a = %d, b = %d", a, b);
|
||||
swap(&a, &b);
|
||||
printf("a = %d, b = %d", a, b);
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#include <stdio.h>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
char a = 11;
|
||||
char b = 15;
|
||||
printf("a = %d, b = %d", a, b);
|
||||
*a ^= *b;
|
||||
*b ^= *a;
|
||||
*a ^= *b;
|
||||
printf("a = %d, b = %d", a, b);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue