Логические побитовые операторы

Операции сдвига

Операции сдвига осуществляют смещение операнда влево (<<) или вправо (>>) на число битов, задаваемое вторым операндом. Оба операнда должны быть целыми величинами. Выполняются обычные арифметические преобразования. При сдвиге влево правые освобождающиеся биты устанавливаются в нуль. При сдвиге вправо метод заполнения освобождающихся левых битов зависит от типа первого операнда. Если тип unsigned, то свободные левые биты устанавливаются в нуль. В противном случае они заполняются копией знакового бита. Результат операции сдвига не определен, если второй операнд отрицательный.

Преобразования, выполненные операциями сдвига, не обеспечивают обработку ситуаций переполнения и потери значимости. Информация теряется, если результат операции сдвига не может быть представлен типом первого операнда, после преобразования.

Отметим, что сдвиг влево соответствует умножению первого операнда на степень числа 2, равную второму операнду, а сдвиг вправо соответствует делению первого операнда на 2 в степени, равной второму операнду.

Логические операторы

&& и

\!\! или

!не

== равно

!= не равно

Динамическое размещение массивов

При динамическом распределении памяти для массивов следует описать соответствующий указатель, которому будет присвоено значение адреса начала области выделенной памяти. Адрес формируется как возвращаемое значение при обращении к функции calloc или malloc. Одномерный массив a[10] из элементов типа float можно создать следующим образом :

float *a;

a=(float*) calloc(10, sizeof(float);

Для создания двухмерного массива вначале нужно распределить память для массива указателей на одномерные массивы, а затем распределять память для одномерных массивов. Пусть, например, требуется создать массив a[n][m]. Это можно сделать следующим образом:

#include <malloc.h>

void main ()

{

double **a;

int n ,m, i;

scanf("%d" ,& n);

scanf("%d" ,& m);

a= (double **) calloc (m,sizeof(double *));

for (i=0; i<=m; i++)

a[i]=(double *) calloc(n,sizeof(double));

. . . . . . . . . . . .

}

Аналогичным образом можно распределить память и для трехмерного массива размером n, m, k. Следует только помнить, что в дальнейшем ненужную для выполнения программы память следует освобождать при помощи функции free.

Пример 47:

#include <malloc.h>

void main ()

{

long ***a;

int n, m, k, i, j;

scanf("%d", & n);

scanf("%d", & m);

scanf("%d", & k);

/* распределение памяти */

a=(long ***) calloc(m,sizeof(long **));

for (i=0; i<=m; i++)

{

a+i=(long **) calloc(n,sizeof(long *));

for (j=0; i<=k; j++)

a+i+j=(long *) calloc(l,sizeof(long));

}

. . . . . . . . . . . .

/* освобождение памяти */

for (i=0; i<=m; i++)

{

for (j=0; j<=k; j++)

free (a + i + j);

free (a + i);

}

free (a);

}

Динамическая память – это оперативная память компьютера, предоставляемая программе при ее работе. Динамическое размещение данных означает использование динамической памяти при работе программы. В отличие от этого статическое размещение (например, явное определение массива данных заданного типа) осуществляется компилятором в процессе компиляции (запуска) программы.

10. Указатели и массивы в языке С. Многомерные массивы. Связь многомерных массивов и указателей.

Директивы препроцессора

Директивы препроцессора представляют собой инструкции, записанные в тексте программы на СИ, и выполняемые до трансляции программы. Директивы препроцессора позволяют изменить текст программы, например, заменить некоторые лексемы в тексте, вставить текст из другого файла, запретить трансляцию части текста и т.п. Все директивы препроцессора начинаются со знака #. После директивы препроцессора точка с запятой не ставится.

Директива #include

Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы:

#include " имя_файла "

#include <имя_файла>

Имя файла должно соответствовать соглашениям с операционной системой и может состоять либо только из имени файла, либо из имени файла с указанием маршрута до него. Если имя файла указано в кавычках, то поиск файла осуществляется в соответствии с заданным маршрутом, а при его отсутствии - в текущем каталоге. Если имя файла задано в угловых скобках, то поиск файла производится в стандартных директориях операционной системы, задаваемых в файле AUTOEXEC командой PATH.

Директива #include может быть вложенной, т.е. во включаемом файле тоже может содержаться директива #include, которая замещается после включения файла, содержащего эту директиву.

Директива #include широко используется для включения в программу так называемых заголовочных файлов, содержащих прототипы библиотечных функций, и поэтому большинство программ на СИ начинаются с этой директивы.

Директива #define

Директива #define служит для замены часто использующихся констант, ключевых слов, операторов или выражений некоторыми идентификаторами. Идентификаторы, заменяющие текстовые или числовые константы, называют именованными константами. Идентификаторы, заменяющие фрагменты программ, называют макроопределениями, причем макроопределения могут иметь аргументы.

Директива #define имеет две синтаксические формы:

#define идентификатор текст

#define идентификатор(список параметров) текст

Эта директива заменяет все последующие вхождения идентификатора на текст. Такой процесс называется макроподстановкой. Текст может представлять собой любой фрагмент программы на СИ, а также может и отсутствовать. В последнем случае все экземпляры идентификатора удаляются из программы.

Пример 48:

#define WIDTH 80

#define LENGTH (WIDTH+10)

Эти директивы изменят в тексте программы каждое слово WIDTH на число

80, а каждое слово LENGTH на выражение (80+10) вместе с окружающими его скобками.

Скобки, содержащиеся в макроопределении, позволяют избежать недоразумений, связанных с порядком вычисления операций. Например, при отсутствии скобок выражение t=LENGTH*7 будет преобразовано в выражение t=80+10*7, а не в выражение t=(80+10)*7, как это получается при наличии скобок, и в результате получится 780, а не 630.

Во второй синтаксической форме в директиве #define имеется список формальных параметров, который может содержать один или несколько идентификаторов, разделенных запятыми. Формальные параметры в тексте макроопределения отмечают позиции, на которые должны быть подставлены фактические аргументы макровызова. Каждый формальный параметр может появиться в тексте макроопределения несколько раз.

При макровызове вслед за идентификатором записывается список фактических аргументов, количество которых должно совпадать с количеством формальных параметров.

Пример 49:

#define MAX(x,y) ((x)>(y))?(x):(y)

Директива, описанная в примере 49, заменит фрагмент t=MAX(i,s[i]); на фрагмент t=((i)>(s[i])?(i):(s[i]);

Как и в примере 48, круглые скобки, в которые заключены формальные параметры макроопределения, позволяют избежать ошибок, связанных с неправильным порядком выполнения операций, если фактические аргументы являются выражениями.

Например, при наличии скобок фрагмент t=MAX(i&j,s[i]||j); будет заменен на фрагмент t=((i&j) > (s[i] || j) ? (i&j) : (s[i] || j); а при отсутствии скобок - на фрагмент t=(i & j > s[i] || j) ? i&j : s[i] || j; в котором условное выражение вычисляется в совершенно другом порядке.

Директива #undef

Директива #undef используется для отмены действия директивы #define. Синтаксис этой директивы следующий:

#undef идентификатор

Директива отменяет действие текущего определения #define для указанного идентификатора. Не является ошибкой использование директивы #undef для идентификатора, который не был определен директивой #define.

Пример 50:

#undef WIDTH

#undef MAX

Еще

Директива #undef отменяет самое последнее определение поименованного макроопределения.

#define BIG 3#define HUGE 5#undef BIG /* BIG теперь не определен */#define HUGE 10 /* HUGE переопределен как 10 */#undef HUGE /* HUGE снова равен 5*/#undef HUGE /* HUGE теперь не определен */

Эти директивы отменяют определение именованной константы WIDTH и макроопределения MAX.

При использовании макросов со списками аргументов следует обратить внимание на следующие моменты:

1. Вложенные круглые скобки и запятые: Список-фактических-аргументов может содержать вложенные круглые скобки, при условии соответствия числа открывающих числу закрывающих скобок; кроме того, запятые, заключенные во внутренние круглые скобки или кавычки, не рассматриваются в качестве разделителей аргументов:

#define ERRMSG(x, str) showerr("Error",x,str)#define SUM(x,y) ((x) + (y))...ERRMSG(2, "Press Enter, then Esc");/* расширится в: showerr("Error",2,"Press Enter, then Esc"); */return SUM(f(i,j), g(k.l));/* расширится в: return ((f(i,j)) + (g(k,l))); */

2. Склеивание лексем при помощи ##: можно выполнить склеивание (слияние) двух лексем, разделив их символами ## (и плюс опциональными пробельными символами с каждой стороны). Препроцессор удаляет пробельные символы и ##, объединяя две отдельные лексемы в одну новую лексему. Это средство можно использовать для конструирования идентификаторов; например, выполнив определение

#define VAR(i,j) (i##j)

и затем вызвав VAR(x,6), можно получить расширение (x6). Этот метод заменяет старый (не обеспечивающий мобильность кода) метод использования (i/**/j).

3. Преобразование к строкам при помощи #: символ # можно поместить перед формальным аргументом макроса с тем, чтобы после подстановки фактический аргумент был преобразован в строку. Поэтому, с учетом следующего определения макроса:

#define TRACE(flag) printf(#flag "=%d\n",flag)

фрагмент кода

int highval = 1024;TRACE(highval);

станет равным

int highval = 1024;printf("highval" "= %d\n", highval);

что в свою очередь будет рассматриваться как

int highval = 1024;printf("highval=%d\n", highval);

4. Символ обратной наклонной черты для продолжения строки: длинная последовательность лексем может продлить строку при помощи обратной наклонной черты (\). Символы обратной наклонной черты и новой строки оба вырезаются, и в результате образуется фактическая последовательность лексем, используемая в расширении:

#define WARN "фактически это одно\строчное сообщение"...puts(WARN);/* на экране будет: фактически это однострочное сообщение */

5. Побочные эффекты и прочие опасности: схожесть между вызовами макросов и функциями иногда скрывает различия между ними. При вызове макроса отсутствует встроенный контроль типов данных, поэтому различие в типах данных формального и фактического аргументов может вызвать непредсказуемые результаты, причину которых нелегко установить, причем относительно такой ошибки не будет выдано никаких предупреждений. При вызовах макросов могут также возникнуть нежелательные побочные эффекты, особенно когда фактический аргумент вычисляется более одного раза. Сравните CUBE и cube в следующем примере:

int cube(int x) {return x*x*x;}#define CUBE(x) ((x)*(x)*(x))...int b =0, a = 3;b = cube(a++);/* cube() передается фактический аргумент = 3; поэтому b = 27,и теперь a = 4 */a = 3;b = CUBE(a++);/* расширяется в: ((a++)*(a++)*(a++)); и теперь a = 6 */

Итоговое значение b зависит от того, что компилятор делает с расширенным выражением.

17. Макрофункции. Отличие от функций. Проблемы использования.

Макроопределения должны использоваться скорее как хитрости, а не как обычные функции: они могут иметь нежелательные побочные эффекты, если вы будете неосторожны. Некоторые компиляторы ограничивают макроопределения одной строкой, и, по-видимому, лучше соблюдать такое ограничение, даже если ваш компилятор этого не делает.

Выбор макроопределения приводит к увеличению объема памяти, а выбор функции - к увеличению времени работы программы. Так что думайте, что выбрать! Макроопределение создает "строчный" код, т. е. вы получаете оператор в программе. Если макроопределение применить 20 раз, то в вашу программу вставится 20 строк кода. Если вы используете функцию 20 раз, у вас будет только одна копия операторов функции, поэтому получается меньший объем памяти. Однако управление программой следует передать туда, где находится функция, а затем вернуться в вызывающую программу, а на это потребуется больше времени, чем при работе со "строчными" кодами.

Преимущество макроопределений заключается в том, что при их использовании вам не нужно беспокоиться о типах переменных (макроопределения имеют дело с символьными строками, а не с фактическими значениями). Так наше макроопределение SQUARE(x) можно использовать одинаково хорошо с переменными типа int или float.

18.Условная компиляция в языке С. Директивы условной компиляции.

#ifdef MAVIS #include " horse.h" /* выполнится, если MAVIS определен */#define STABLES 5#else#include "cow.h" /*выполнится, если MAVIS не определен */#define STABLES 15#endif

Директива #ifdef сообщает, что если последующий идентификатор (MAVIS) определяется препроцессором, то выполняются все последующие директивы вплоть до первого появления #else или #endif. Когда в программе есть #else, то программа от #else до #endif будет выполняться, если идентификатор не определен.

Такая структура очень напоминает конструкцию if-else языка Си. Основная разница заключается в том, что препроцессор не распознает фигурные скобки {}, отмечающие блок, потому что он использует директивы #else (если есть) и #endif (которая должна быть) для пометки блоков директив.

Эти условные конструкции могут быть вложенными.

Директивы #ifdef и #if можно использовать с #else и #endif таким же образом. Директива #ifndef опрашивает, является ли последующий идентификаторнеопределенным; эта директива противоположна #ifdef. Директива #if больше похожа на обычный оператор if языка Си. За ней следует константное выражение, которое считается истинным, если оно не равно нулю:

#if SYS == "IBM"#include "ibm.h"#endif

Одна из целей использования "условной компиляции" - сделать программу более мобильной. Изменяя несколько ключевых определений в начале файла, вы можете устанавливать различные значения и включать различные файлы для разных систем.

19.Функции в языке С. Определение и вызов функции. Формальные и фактические параметры. Прототипы функций.

Определение и вызов функций

Порядок и типы формальных параметров должны быть одинаковыми в определении функции и во всех ее объявлениях. Типы фактических параметров при вызове функции должны быть совместимы с типами соответствующих формальных параметров. Тип формального параметра может быть любым основным типом, структурой, объединением, перечислением, указателем или массивом. Если тип формального параметра не указан, то этому параметру присваивается тип int.

Для формального параметра можно задавать класс памяти register, при этом для величин типа int спецификатор типа можно опустить.

Идентификаторы формальных параметров используются в теле функции в качестве ссылок на переданные значения. Эти идентификаторы не могут быть переопределены в блоке, образующем тело функции, но могут быть переопределены во внутреннем блоке внутри тела функции.

При передаче параметров в функцию, если необходимо, выполняются обычные арифметические преобразования для каждого формального параметра и каждого фактического параметра независимо. После преобразования формальный параметр не может быть короче чем int, т.е. объявление формального параметра с типом char равносильно его объявлению с типом int. А параметры, представляющие собой действительные числа, имеют тип double.

Преобразованный тип каждого формального параметра определяет, как интерпретируются аргументы, помещаемые при вызове функции в стек. Несоответствие типов фактических аргументов и формальных параметров может быть причиной неверной интерпретации.

Тело функции - это составной оператор, содержащий операторы, определяющие действие функции.

Все переменные, объявленные в теле функции без указания класса памяти, имеют класс памяти auto, т.е. они являются локальными. При вызове функции локальным переменным отводится память в стеке и производится их инициализация. Управление передается первому оператору тела функции и начинается выполнение функции, которое продолжается до тех пор, пока не встретится оператор return или последний оператор тела функции. Управление при этом возвращается в точку, следующую за точкой вызова, а локальные переменные становятся недоступными. При новом вызове функции для локальных переменных память распределяется вновь, и поэтому старые значения локальных переменных теряются.

Параметры функции передаются по значению и могут рассматриваться как локальные переменные, для которых выделяется память при вызове функции и производится инициализация значениями фактических параметров. При выходе из функции значения этих переменных теряются. Поскольку передача параметров происходит по значению, в теле функции нельзя изменить значения переменных в вызывающей функции, являющихся фактическими параметрами. Однако, если в качестве параметра передать указатель на некоторую переменную, то используя операцию разадресации можно изменить значение этой переменной.

Пример:

Неправильное использование параметров */

void change (int x, int y)

{ int k=x;

x=y;

y=k;

}

В данной функции значения переменных x и y, являющихся формальными параметрами, меняются местами, но поскольку эти переменные существуют только внутри функции change, значения фактических параметров, используемых при вызове функции, останутся неизменными. Для того чтобы менялись местами значения фактических аргументов можно использовать функцию приведенную в следующем примере.

Пример:

/* Правильное использование параметров */

void change (int *x, int *y)

{ int k=*x;

*x=*y;

*y=k;

}

При вызове такой функции в качестве фактических параметров должны быть использованы не значения переменных, а их адреса

change (&a,&b);

Если требуется вызвать функцию до ее определения в рассматриваемом файле, или определение функции находится в другом исходном файле, то вызов функции следует предварять объявлением этой функции. Объявление (прототип) функции имеет следующий формат:

[спецификатор-класса-памяти] [спецификатор-типа] имя-функции ([список-формальных- параметров]) [,список-имен-функций];

В отличие от определения функции, в прототипе за заголовком сразу же следует точка с запятой, а тело функции отсутствует. Если несколько разных функций возвращают значения одинакового типа и имеют одинаковые списки формальных параметров, то эти функции можно объявить в одном прототипе, указав имя одной из функций в качестве имени-функции, а все другие поместить в список-имен-функций, причем каждая функция должна сопровождаться списком формальных параметров. Правила использования остальных элементов формата такие же, как при определении функции. Имена формальных параметров при объявлении функции можно не указывать, а если они указаны, то их область действия распространяется только до конца объявления.

Прототип - это явное объявление функции, которое предшествует определению функции. Тип возвращаемого значения при объявлении функции должен соответствовать типу возвращаемого значения в определении функции.

Если прототип функции не задан, а встретился вызов функции, то строится неявный прототип из анализа формы вызова функции. Тип возвращаемого значения создаваемого прототипа int, а список типов и числа параметров функции формируется на основании типов и числа фактических параметров используемых при данном вызове.

Таким образом, прототип функции необходимо задавать в следующих случаях:

1. Функция возвращает значение типа, отличного от int.

2. Требуется проинициализировать некоторый указатель на функцию до того, как эта функция будет определена.

Наличие в прототипе полного списка типов аргументов параметров позволяет выполнить проверку соответствия типов фактических параметров при вызове функции типам формальных параметров, и, если необходимо, выполнить соответствующие преобразования.

В прототипе можно указать, что число параметров функции переменно, или что функция не имеет параметров.

Если прототип задан с классом памяти static, то и определение функции должно иметь класс памяти static. Если спецификатор класса памяти не указан, то подразумевается класс памяти extern.

Вызов функции имеет следующий формат:

адресное-выражение ([список-выражений])

Поскольку синтаксически имя функции является адресом начала тела функции, в качестве обращения к функции может быть использовано адресное-выражение (в том числе и имя функции или разадресация указателя на функцию), имеющее значение адреса функции.

Список-выражений представляет собой список фактических параметров, передаваемых в функцию. Этот список может быть и пустым, но наличие круглых скобок обязательно.

Фактический параметр может быть величиной любого основного типа, структурой, объединением, перечислением или указателем на объект любого типа. Массив и функция не могут быть использованы в качестве фактических параметров, но можно использовать указатели на эти объекты.

Выполнение вызова функции происходит следующим образом:

1. Вычисляются выражения в списке выражений и подвергаются обычным арифметическим преобразованиям. Затем, если известен прототип функции, тип полученного фактического аргумента сравнивается с типом соответствующего формального параметра. Если они не совпадают, то либо производится преобразование типов, либо формируется сообщение об ошибке. Число выражений в списке выражений должно совпадать с числом формальных параметров, если только функция не имеет переменного числа параметров. В последнем случае проверке подлежат только обязательные параметры. Если в прототипе функции указано, что ей не требуются параметры, а при вызове они указаны, формируется сообщение об ошибке.

2. Происходит присваивание значений фактических параметров соответствующим формальным параметрам.

3. Управление передается на первый оператор функции.

4. Выполнение оператора return в теле функции возвращает управление и возможно, значение в вызывающую функцию. При отсутствии оператора return управление возвращается после выполнения последнего оператора тела функции, а возвращаемое значение не определено.

Адресное выражение, стоящее перед скобками определяет адрес вызываемой функции. Это значит что функция может быть вызвана через указатель на функцию.

Пример:

int (*fun)(int x, int *y);

Здесь объявлена переменная fun как указатель на функцию с двумя параметрами: типа int и указателем на int. Сама функция должна возвращать значение типа int. Круглые скобки, содержащие имя указателя fun и признак указателя *, обязательны, иначе запись

int *fun (intx,int *y);

будет интерпретироваться как объявление функции fun возвращающей указатель на int.

Вызов функции возможен только после инициализации значения указателя fun и имеет вид:

(*fun)(i,&j);

В этом выражении для получения адреса функции, на которую ссылается указатель fun используется операция разадресации *.

Указатель на функцию может быть передан в качестве параметра функции. При этом разадресация происходит во время вызова функции, на которую ссылается указатель на функцию. Присвоить значение указателю на функцию можно в операторе присваивания, употребив имя функции без списка параметров.

Пример:

double (*fun1)(int x, int y);

double fun2(int k, int l);

fun1=fun2; /* инициализация указателя на функцию*/

(*fun1)(2,7); /* обращение к функции */

В рассмотренном примере указатель на функцию fun1 описан как указатель на функцию с двумя параметрами, возвращающую значение типа double, и также описана функция fun2. В противном случае, т.е. когда указателю на функцию присваивается функция описанная иначе чем указатель, произойдет ошибка.

Рассмотрим пример использования указателя на функцию в качестве параметра функции вычисляющей производную от функции cos(x).

Пример:

double proiz(double x, double dx, double (*f)(double x) );

double fun(double z);

int main()

{

double x; /* точка вычисления производной*/

double dx; /* приращение */

double z; /* значение производной */

scanf("%f,%f",&x,&dx); /* ввод значений x и dx */

z=proiz(x,dx,fun); /* вызов функции */

printf("%f",z); /* печать значения производной */

return 0;

}

double proiz(double x,double dx, double (*f)(double z) )

{ /* функция вычисляющая производную */

double xk,xk1,pr;

xk=fun(x);

xk1=fun(x+dx);

pr=(xk1/xk-1e0)*xk/dx;

return pr;

}

double fun( double z)

{ /* функция от которой вычисляется производная */

return (cos(z));

}

Для вычисления производной от какой-либо другой функции можно изменить тело функции fun или использовать при вызове функции proiz имя другой функции. В частности, для вычисления производной от функции cos(x) можно вызвать функцию proiz в форме

z=proiz(x,dx,cos);

а для вычисления производной от функции sin(x) в форме

z=proiz(x,dx,sin);

Любая функция в программе на языке СИ может быть вызвана рекурсивно, т.е. она может вызывать саму себя. Компилятор допускает любое число рекурсивных вызовов. При каждом вызове для формальных параметров и переменных с классом памяти auto и register выделяется новая область памяти, так что их значения из предыдущих вызовов не теряются, но в каждый момент времени доступны только значения текущего вызова.

Переменные, объявленные с классом памяти static, не требуют выделения новой области памяти при каждом рекурсивном вызове функции и их значения доступны в течение всего времени выполнения программы.

Классический пример рекурсии - это математическое определение факториала n!:

n! = 1 при n=0;

n*(n-1)! при n>1 .

Функция, вычисляющая факториал, будет иметь следующий вид:

long fakt(int n)

{

return ( (n==1) ? 1 : n*fakt(n-1) );

}

Хотя компилятор языка СИ не ограничивает число рекурсивных вызовов функций, это число ограничивается ресурсом памяти компьютера и при слишком большом числе рекурсивных вызовов может произойти переполнение стека.

Состав проекта

Многомодульный проект создается точно также, как и обычный. Однако текст проекта

разбивается на самостоятельные модули, каждый из которых компилируется отдельно. На рисунке 18приведен пример многомодульного проекта. Как видно из рисунка, он содержит три файла: главный модуль программы module.c, дополнительный модуль module1.c и заголовочный файл для этого модуля module.h.

Главный модуль должен содержать как минимумфункцию main(). Дополнительный модуль, как правило,содержит функции, которые логически можно вынести из

основной программы. И, наконец, заголовочный файлмодуля является как бы интерфейсом модуля и содержитпрототипы функций модуля, которые должны быть

доступны из основной программы и других модулей.Кроме того, заголовочный файл обычно содержитобъявление новых типов данных, используемых в модулеи глобальных переменных. Также в него выносят всемакроопределения, которые должны быть доступны восновной программе или других модулях.Все функции, типы данных, переменные имакроопределения, необходимые для функционирования

самого модуля в заголовочный файл не выносятся. Таким

образом,

Операции сдвига

Операции сдвига осуществляют смещение операнда влево (<<) или вправо (>>) на число битов, задаваемое вторым операндом. Оба операнда должны быть целыми величинами. Выполняются обычные арифметические преобразования. При сдвиге влево правые освобождающиеся биты устанавливаются в нуль. При сдвиге вправо метод заполнения освобождающихся левых битов зависит от типа первого операнда. Если тип unsigned, то свободные левые биты устанавливаются в нуль. В противном случае они заполняются копией знакового бита. Результат операции сдвига не определен, если второй операнд отрицательный.

Преобразования, выполненные операциями сдвига, не обеспечивают обработку ситуаций переполнения и потери значимости. Информация теряется, если результат операции сдвига не может быть представлен типом первого операнда, после преобразования.

Отметим, что сдвиг влево соответствует умножению первого операнда на степень числа 2, равную второму операнду, а сдвиг вправо соответствует делению первого операнда на 2 в степени, равной второму операнду.

Логические операторы

&& и

\!\! или

!не

== равно

!= не равно

Логические побитовые операторы

Существует шесть побитовых оператора:

& оператор И (AND)
| оператор ИЛИ (OR)
^ оператор XOR - исключающее ИЛИ, сложение по модулю 2
~ оператор инверсии
>> оператор сдвига вправо
<< оператор сдвига влево .

Оператор &

Оператор & (И) сравнивает два значения и возвращает значение 1, только в том случае, если оба значения были установлены в 1. Биты сравниваются используя следующую таблицу

1 & 1 == 1
1 & 0 == 0
0 & 1 == 0
0 & 0 == 0

Данный оператор лучше всего использовать для установки маски проверки значений определенных битов. Допустим, у нас есть байт (BYTE), который содержит некоторые битовые флаги, и мы хотим проверить, был ли четвертый бит установлен в единицу.

BYTE b = 50;
if ( b & 0x10 )
cout << "Четвертый бит установлен" << endl;
else
cout << "Четвертый бит читс" << endl;

В результате будет выполнен следующий подсчет

00110010 - b
& 00010000 - & 0x10
----------
00010000 - результат

Теперь мы видим, что четвертый бит был установлен.

Оператор \!(или \!)

Оператор | (ИЛИ) сравнивает два значения и возвращает результат в виде единицы, если хотя бы один из битов будет установлен (равен единице). Биты сравниваются, используя следующую таблицу

1 | 1 == 1
1 | 0 == 1
0 | 1 == 1
0 | 0 == 0

Данный оператор лучше всего использовать для обеспечения установки каких-то определенных битов. Допустим, мы хотим убедиться, что значение третьего бита установлено

BYTE b = 50;
BYTE c = b | 0x04;
cout << "c = " << c << endl;

В результате будет выполнен следующий подсчет

00110010 - b
| 00000100 - | 0x04
----------
00110110 - результат

Оператор ^

Оператор ^ (XOR) сравнивает два значения и возвращает единицу в случае, если значения сравниваемых элементов различаются. То есть в случае, если сравниваемые значения одинаковы, будет возвращено новое значение. Биты сравниваются, используя следующую таблицу

1 ^ 1 == 0
1 ^ 0 == 1
0 ^ 1 == 1
0 ^ 0 == 0

Идеальное использование такого оператора заключается в переключении определенных битов. Допустим, мы хотим изменить третий и четвертый бит

BYTE b = 50;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;
b = b ^ 0x18;
cout << "b = " << b << endl;

В результате будут выполнены следующие подсчеты

00110010 - b
^ 00011000 - ^ 0x18
----------
00101010 - result

00101010 - b
^ 00011000 - ^ 0x18
----------
00110010 - результат

Оператор \^(ВЕЗДЕ \^)

Оператор ~ (поразрядное дополнение или обратный код) действует только на одно значение и инвертирует его, преобразуя все единицы в нули, а нули - единицы. Данный оператор используется для установления определенных бит в ноль и обеспечения того, что все другие биты установлены в единицу, независимо от размера данных. Допустим, мы хотим установить все биты в единицу, за исключением нулевого и первого бита:

BYTE b = \^0x03;
cout << "b = " << b << endl;
WORD w = \^0x03;
cout << "w = " << w << endl;

В результате будут выполнены следующие подсчеты

00000011 - 0x03
11111100 - ~0x03 b

0000000000000011 - 0x03
1111111111111100 - ~0x03 w

Другим вариантом использования является комбинация с оператором & для обеспечения того, что определенные биты установлены в нулевое значение. Допустим, мы хотим очистить четвертый бит

BYTE b = 50;
cout << "b = " << b << endl;
BYTE c = b & ~0x10;
cout << "c = " << c << endl;

В результате будут выполнены следующие подсчеты

00110010 - b
& 11101111 - ~0x10
----------
00100010 - результат

Операторы >> и <<

Операторы >> (сдвиг вправо) и << (сдвиг влево) передвигают биты на определенное число позиций. Оператор >> сдвигает биты с бита с высоким уровнем к нижнему. Оператор << сдвигает биты со стороны нижнего уровня к биту с верхним уровнем. Одним из вариантов использования является выравнивание битов по какой-либо причине (ознакомьтесь с макросами MAKEWPARAM, HIWORD и LOWORD)

BYTE b = 12;
cout << "b = " << b << endl;
BYTE c = b << 2;
cout << "c = " << c << endl;
c = b >> 2;
cout << "c = " << c << endl;

В результате будут выполнены следующие подсчеты

00001100 - b
00110000 - b << 2
00000011 - b >> 2

6.Арифметические операции языка С. Аддитивные операции. Мультипликативные операции. Операция арифметического отрицания. Операции увеличения и уменьшения

%- целочисленное деление, к float и double не применимо.

- - Операция арифметического отрицания

Выражение ++N увеличивает переменную N до использования ее значения, в то время как N++ увеличивает переменную N после того, как ее значение было использовано.

7. Операции последовательного вычисления языка С. Условная операция. Адресные операции. Операция sizeof.

Наши рекомендации