C Language
операторы
Поиск…
Вступление
Оператор на языке программирования является символом, который сообщает компилятору или интерпретатору выполнять определенную математическую, реляционную или логическую операцию и дает конечный результат.
C имеет много мощных операторов. Многие операторы C являются двоичными операторами, что означает, что они имеют два операнда. Например, в a / b
, /
является двоичным оператором, который принимает два операнда ( a
, b
). Есть некоторые унарные операторы, которые берут один операнд (например: ~
, ++
) и только один тернарный оператор ? :
.
Синтаксис
- Оператор expr1
- оператор expr2
- Оператор expr1 expr2
- expr1? expr2: expr3
замечания
Операторы имеют арность, предшествование и ассоциативность.
Arity указывает количество операндов. В C существуют три различные категории операторов:
- Унарный (1 операнд)
- Двоичные (2 операнда)
- Тройной (3 операнда)
Приоритет указывает, какие операторы «связывают» сначала с их операндами. То есть оператор имеет приоритет для работы с операндами. Например, язык C подчиняется соглашению о том, что умножение и деление имеют приоритет над сложениями и вычитанием:
a * b + c
Дает тот же результат, что и
(a * b) + c
Если это не то, что нужно, приоритет можно принудительно использовать с помощью круглых скобок, поскольку они имеют наивысший приоритет для всех операторов.
a * (b + c)
Это новое выражение приведет к результату, который отличается от предыдущих двух выражений.
Язык C имеет много уровней приоритета; Ниже приведена таблица всех операторов в порядке убывания приоритета.
Таблица приоритетов
операторы Ассоциативность ()
[]
->
.
слева направо !
~
++
--
+
-
*
(разыменование)(type)
sizeof
справа налево *
(умножение)/
%
слева направо +
-
слева направо <<
>>
слева направо <
<=
>
>=
слева направо ==
!=
слева направо &
слева направо ^
слева направо |
слева направо &&
слева направо ||
слева направо ?:
справа налево =
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
справа налево ,
слева направо Ассоциативность показывает, как по умолчанию назначаются операторы с приоритетом приоритета, и существует два вида: слева направо и справа налево . Примером привязки Left-to-Right является оператор вычитания (
-
). Выражениеa - b - c - d
имеет три вычитания с одинаковым приоритетом, но дает тот же результат, что и
((a - b) - c) - d
потому что самое левое
-
связывается сначала с двумя его операндами.Примером правоотношенной ассоциативности являются операторы разыменования
*
и post-increment++
. Оба имеют одинаковый приоритет, поэтому, если они используются в выражении, таком как* ptr ++
, это эквивалентно
* (ptr ++)
потому что самый правый, унарный оператор (
++
) связывает сначала свой единственный операнд.
Реляционные операторы
Операторы отношения проверяют, является ли конкретное отношение между двумя операндами истинным. Результат оценивается в 1 (что означает true ) или 0 (что означает false ). Этот результат часто используется для влияния на поток управления (через if
, while
, for
), но также может храниться в переменных.
Равно "=="
Проверяет, равны ли поставленные операнды.
1 == 0; /* evaluates to 0. */
1 == 1; /* evaluates to 1. */
int x = 5;
int y = 5;
int *xptr = &x, *yptr = &y;
xptr == yptr; /* evaluates to 0, the operands hold different location addresses. */
*xptr == *yptr; /* evaluates to 1, the operands point at locations that hold the same value. */
Внимание: этот оператор не следует путать с оператором присваивания ( =
)!
Не равно "! ="
Проверяет, не равны ли поставленные операнды.
1 != 0; /* evaluates to 1. */
1 != 1; /* evaluates to 0. */
int x = 5;
int y = 5;
int *xptr = &x, *yptr = &y;
xptr != yptr; /* evaluates to 1, the operands hold different location addresses. */
*xptr != *yptr; /* evaluates to 0, the operands point at locations that hold the same value. */
Этот оператор фактически возвращает противоположный результат к ==
оператора equals ( ==
).
Не "!"
Проверьте, равен ли объект 0
.
!
также можно использовать непосредственно с переменной следующим образом:
!someVal
Это имеет тот же эффект, что и:
someVal == 0
Больше, чем ">"
Проверяет, имеет ли левый операнд большее значение, чем правый операнд
5 > 4 /* evaluates to 1. */
4 > 5 /* evaluates to 0. */
4 > 4 /* evaluates to 0. */
Меньше, чем "<"
Проверяет, имеет ли левый операнд меньшее значение, чем правый операнд
5 < 4 /* evaluates to 0. */
4 < 5 /* evaluates to 1. */
4 < 4 /* evaluates to 0. */
Больше или равно "> ="
Проверяет, имеет ли левый операнд большее или равное значение правильному операнду.
5 >= 4 /* evaluates to 1. */
4 >= 5 /* evaluates to 0. */
4 >= 4 /* evaluates to 1. */
Меньше или равно "<="
Проверяет, имеет ли левый операнд меньшее или равное значение правильному операнду.
5 <= 4 /* evaluates to 0. */
4 <= 5 /* evaluates to 1. */
4 <= 4 /* evaluates to 1. */
Операторы присваивания
Присваивает значение правого операнда ячейке хранения, названной левым операндом, и возвращает значение.
int x = 5; /* Variable x holds the value 5. Returns 5. */
char y = 'c'; /* Variable y holds the value 99. Returns 99
* (as the character 'c' is represented in the ASCII table with 99).
*/
float z = 1.5; /* variable z holds the value 1.5. Returns 1.5. */
char const* s = "foo"; /* Variable s holds the address of the first character of the string 'foo'. */
Несколько арифметических операций имеют составной оператор присваивания .
a += b /* equal to: a = a + b */
a -= b /* equal to: a = a - b */
a *= b /* equal to: a = a * b */
a /= b /* equal to: a = a / b */
a %= b /* equal to: a = a % b */
a &= b /* equal to: a = a & b */
a |= b /* equal to: a = a | b */
a ^= b /* equal to: a = a ^ b */
a <<= b /* equal to: a = a << b */
a >>= b /* equal to: a = a >> b */
Важной особенностью этих составных назначений является то, что выражение в левой части ( a
) оценивается только один раз. Например, если p
является указателем
*p += 27;
разыгрывания p
только один раз, тогда как следующее делает это дважды.
*p = *p + 27;
Следует также отметить, что результатом присвоения, такого как a = b
является то, что известно как rvalue . Таким образом, присваивание фактически имеет значение, которое затем может быть присвоено другой переменной. Это позволяет цепочки присвоений задавать несколько переменных в одном выражении.
Это значение rvalue может использоваться в управляющих выражениях операторов if
(или циклов или операторов switch
), которые защищают некоторый код от результата другого выражения или вызова функции. Например:
char *buffer;
if ((buffer = malloc(1024)) != NULL)
{
/* do something with buffer */
free(buffer);
}
else
{
/* report allocation failure */
}
Из-за этого следует проявлять осторожность, чтобы избежать общей опечатки, которая может привести к таинственным ошибкам.
int a = 2;
/* ... */
if (a = 1)
/* Delete all files on my hard drive */
Это будет иметь катастрофические результаты, так как a = 1
всегда будет оцениваться до 1
и, таким образом, контролирующее выражение оператора if
всегда будет истинным (подробнее об этом распространенном явлении см. Здесь ). Автор почти наверняка имел в виду использовать оператор равенства ( ==
), как показано ниже:
int a = 2;
/* ... */
if (a == 1)
/* Delete all files on my hard drive */
Ассоциативность операторов
int a, b = 1, c = 2;
a = b = c;
Это присваивает c
в b
, который возвращает b
, который присваивается a
. Это происходит потому, что все операторы присваивания имеют правую ассоциативность, что означает, что самая правая операция в выражении сначала оценивается и продолжается справа налево.
Арифметические операторы
Основная арифметика
Возвращает значение, которое является результатом применения левого операнда в правый операнд, используя соответствующую математическую операцию. Применяются нормальные математические правила коммутации (т. Е. Сложение и умножение являются коммутативными, вычитание, деление и модуль не являются).
Оператор добавления
Оператор сложения ( +
) используется для добавления двух операндов вместе. Пример:
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 7;
int c = a + b; /* c now holds the value 12 */
printf("%d + %d = %d",a,b,c); /* will output "5 + 7 = 12" */
return 0;
}
Оператор вычитания
Оператор вычитания ( -
) используется для вычитания второго операнда из первого. Пример:
#include <stdio.h>
int main(void)
{
int a = 10;
int b = 7;
int c = a - b; /* c now holds the value 3 */
printf("%d - %d = %d",a,b,c); /* will output "10 - 7 = 3" */
return 0;
}
Оператор умножения
Оператор умножения ( *
) используется для умножения обоих операндов. Пример:
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 7;
int c = a * b; /* c now holds the value 35 */
printf("%d * %d = %d",a,b,c); /* will output "5 * 7 = 35" */
return 0;
}
Не следует путать с *
оператора разыменования.
Оператор отдела
Оператор деления ( /
) делит первый операнд на второй. Если оба операнда деления являются целыми числами, он вернет целочисленное значение и отбросит остаток (используйте для вычисления и получения остатка оператор modulo %
).
Если один из операндов является значением с плавающей запятой, результатом является аппроксимация фракции.
Пример:
#include <stdio.h>
int main (void)
{
int a = 19 / 2 ; /* a holds value 9 */
int b = 18 / 2 ; /* b holds value 9 */
int c = 255 / 2; /* c holds value 127 */
int d = 44 / 4 ; /* d holds value 11 */
double e = 19 / 2.0 ; /* e holds value 9.5 */
double f = 18.0 / 2 ; /* f holds value 9.0 */
double g = 255 / 2.0; /* g holds value 127.5 */
double h = 45.0 / 4 ; /* h holds value 11.25 */
printf("19 / 2 = %d\n", a); /* Will output "19 / 2 = 9" */
printf("18 / 2 = %d\n", b); /* Will output "18 / 2 = 9" */
printf("255 / 2 = %d\n", c); /* Will output "255 / 2 = 127" */
printf("44 / 4 = %d\n", d); /* Will output "44 / 4 = 11" */
printf("19 / 2.0 = %g\n", e); /* Will output "19 / 2.0 = 9.5" */
printf("18.0 / 2 = %g\n", f); /* Will output "18.0 / 2 = 9" */
printf("255 / 2.0 = %g\n", g); /* Will output "255 / 2.0 = 127.5" */
printf("45.0 / 4 = %g\n", h); /* Will output "45.0 / 4 = 11.25" */
return 0;
}
Оператор Modulo
Оператор modulo ( %
) принимает только целые операнды и используется для вычисления остатка после того, как первый операнд делится на второй. Пример:
#include <stdio.h>
int main (void) {
int a = 25 % 2; /* a holds value 1 */
int b = 24 % 2; /* b holds value 0 */
int c = 155 % 5; /* c holds value 0 */
int d = 49 % 25; /* d holds value 24 */
printf("25 % 2 = %d\n", a); /* Will output "25 % 2 = 1" */
printf("24 % 2 = %d\n", b); /* Will output "24 % 2 = 0" */
printf("155 % 5 = %d\n", c); /* Will output "155 % 5 = 0" */
printf("49 % 25 = %d\n", d); /* Will output "49 % 25 = 24" */
return 0;
}
Операторы приращения / уменьшения
Операторы increment ( a++
) и a--
( a--
) отличаются друг от друга тем, что они изменяют значение переменной, к которой вы применяете их, без оператора присваивания. Вы можете использовать операторы increment и decment либо до, либо после переменной. Размещение оператора изменяет время нарастания / уменьшения значения до или после его назначения переменной. Пример:
#include <stdio.h>
int main(void)
{
int a = 1;
int b = 4;
int c = 1;
int d = 4;
a++;
printf("a = %d\n",a); /* Will output "a = 2" */
b--;
printf("b = %d\n",b); /* Will output "b = 3" */
if (++c > 1) { /* c is incremented by 1 before being compared in the condition */
printf("This will print\n"); /* This is printed */
} else {
printf("This will never print\n"); /* This is not printed */
}
if (d-- < 4) { /* d is decremented after being compared */
printf("This will never print\n"); /* This is not printed */
} else {
printf("This will print\n"); /* This is printed */
}
}
Как показывает пример для c
и d
, оба оператора имеют две формы: префиксную нотацию и постфиксную нотацию. Оба имеют одинаковый эффект при добавлении ( ++
) или уменьшении ( --
) переменной, но отличаются значением, которое они возвращают: операции префикса выполняют операцию сначала, а затем возвращают значение, тогда как операции postfix сначала определяют значение, которое возвращаться, а затем выполнять операцию.
Из-за этого потенциально контр-интуитивного поведения использование операторов increment / decment внутри выражений противоречиво.
Логические операторы
Логические И
Выполняет логическое логическое AND-IN двух операндов, возвращающих 1, если оба операнда отличны от нуля. Логический оператор И имеет тип int
.
0 && 0 /* Returns 0. */
0 && 1 /* Returns 0. */
2 && 0 /* Returns 0. */
2 && 3 /* Returns 1. */
Логический ИЛИ
Выполняет логическое логическое OR-IN двух операндов, возвращающих 1, если какой-либо из операндов отличен от нуля. Логический оператор OR имеет тип int
.
0 || 0 /* Returns 0. */
0 || 1 /* Returns 1. */
2 || 0 /* Returns 1. */
2 || 3 /* Returns 1. */
Логическое НЕ
Выполняет логическое отрицание. Логический оператор NOT имеет тип int
. Оператор NOT проверяет, равен ли хотя бы один бит 1, если он возвращает 0. Иначе он возвращает 1;
!1 /* Returns 0. */
!5 /* Returns 0. */
!0 /* Returns 1. */
Оценка короткого замыкания
Существуют некоторые важные свойства, общие как для &&
и для ||
:
- левый операнд (LHS) полностью оценивается до того, как будет оценен правый операнд (RHS)
- существует точка последовательности между оценкой левого операнда и правого операнда,
- и, что более важно, правый операнд вообще не оценивается, если результат левого операнда определяет общий результат.
Это означает, что:
- если LHS оценивает значение «true» (отличное от нуля), RHS
||
не будет оценен (потому что результат «true OR any» равен «true») - если LHS оценивает значение «false» (ноль), RHS
&&
не будет оцениваться (потому что результат «false AND anything» равен «false»).
Это важно, поскольку позволяет писать код, например:
const char *name_for_value(int value)
{
static const char *names[] = { "zero", "one", "two", "three", };
enum { NUM_NAMES = sizeof(names) / sizeof(names[0]) };
return (value >= 0 && value < NUM_NAMES) ? names[value] : "infinity";
}
Если отрицательное значение передается функции, value >= 0
определяет value < NUM_NAMES
false, а value < NUM_NAMES
термин не оценивается.
Приращение / уменьшение
Операторы increment and decment существуют в префиксной и постфиксной форме.
int a = 1;
int b = 1;
int tmp = 0;
tmp = ++a; /* increments a by one, and returns new value; a == 2, tmp == 2 */
tmp = a++; /* increments a by one, but returns old value; a == 3, tmp == 2 */
tmp = --b; /* decrements b by one, and returns new value; b == 0, tmp == 0 */
tmp = b--; /* decrements b by one, but returns old value; b == -1, tmp == 0 */
Обратите внимание, что арифметические операции не вводят точки последовательности , поэтому некоторые выражения с ++
или --
операторами могут вводить неопределенное поведение .
Условный оператор / Тернарный оператор
Вычисляет свой первый операнд и, если результирующее значение не равно нулю, оценивает его второй операнд. В противном случае он оценивает свой третий операнд, как показано в следующем примере:
a = b ? c : d;
эквивалентно:
if (b)
a = c;
else
a = d;
Этот псевдокод представляет это: condition ? value_if_true : value_if_false
. Каждое значение может быть результатом оцененного выражения.
int x = 5;
int y = 42;
printf("%i, %i\n", 1 ? x : y, 0 ? x : y); /* Outputs "5, 42" */
Условный оператор может быть вложенным. Например, следующий код определяет большее из трех чисел:
big= a > b ? (a > c ? a : c)
: (b > c ? b : c);
Следующий пример записывает даже целые числа в один файл и нечетные целые числа в другой файл:
#include<stdio.h>
int main()
{
FILE *even, *odds;
int n = 10;
size_t k = 0;
even = fopen("even.txt", "w");
odds = fopen("odds.txt", "w");
for(k = 1; k < n + 1; k++)
{
k%2==0 ? fprintf(even, "\t%5d\n", k)
: fprintf(odds, "\t%5d\n", k);
}
fclose(even);
fclose(odds);
return 0;
}
Условный оператор ассоциируется справа налево. Рассмотрим следующее:
exp1 ? exp2 : exp3 ? exp4 : exp5
Поскольку ассоциация справа налево, указанное выше выражение оценивается как
exp1 ? exp2 : ( exp3 ? exp4 : exp5 )
Comma Operator
Вычисляет его левый операнд, отбрасывает полученное значение и затем оценивает его операнд прав, и результат дает значение его самого правого операнда.
int x = 42, y = 42;
printf("%i\n", (x *= 2, y)); /* Outputs "42". */
Оператор запятой вводит точку последовательности между ее операндами.
Обратите внимание, что запятая, используемая в функциях, вызывает, что отдельные аргументы НЕ являются оператором запятой , скорее это называется разделителем, который отличается от оператора запятой . Следовательно, он не обладает свойствами оператора запятой .
Вышеупомянутый вызов printf()
содержит как оператор запятой, так и разделитель .
printf("%i\n", (x *= 2, y)); /* Outputs "42". */
/* ^ ^ this is a comma operator */
/* this is a separator */
Оператор запятой часто используется в секции инициализации, а также в секции обновления цикла for
. Например:
for(k = 1; k < 10; printf("\%d\\n", k), k += 2); /*outputs the odd numbers below 9/*
/* outputs sum to first 9 natural numbers */
for(sumk = 1, k = 1; k < 10; k++, sumk += k)
printf("\%5d\%5d\\n", k, sumk);
Оператор ротации
Выполняет явное преобразование в заданный тип из значения, полученного в результате вычисления данного выражения.
int x = 3;
int y = 4;
printf("%f\n", (double)x / y); /* Outputs "0.750000". */
При этом величина x
преобразуется в double
деление способствует значение y
к double
, тоже, и результат отделения, double
передается printf
для печати.
Оператор размера
С типом в качестве операнда
Вычисляет размер в байтах типа size_t
объектов данного типа. Требуется круглые скобки вокруг типа.
printf("%zu\n", sizeof(int)); /* Valid, outputs the size of an int object, which is platform-dependent. */
printf("%zu\n", sizeof int); /* Invalid, types as arguments need to be surrounded by parentheses! */
С выражением в качестве операнда
Оценивает размер в байтах типа size_t
объектов типа данного выражения. Само выражение не оценивается. Скобки не требуются; однако, поскольку данное выражение должно быть унарным, считается, что наилучшей практикой всегда пользоваться ими.
char ch = 'a';
printf("%zu\n", sizeof(ch)); /* Valid, will output the size of a char object, which is always 1 for all platforms. */
printf("%zu\n", sizeof ch); /* Valid, will output the size of a char object, which is always 1 for all platforms. */
Арифметика указателей
Добавление указателя
С учетом указателя и скалярного типа N
вычисляется в указатель на N
й элемент указанного типа, который непосредственно преследует объект с указателем в памяти.
int arr[] = {1, 2, 3, 4, 5};
printf("*(arr + 3) = %i\n", *(arr + 3)); /* Outputs "4", arr's fourth element. */
Не имеет значения, используется ли указатель в качестве значения операнда или скалярного значения. Это означает, что действуют такие вещи, как 3 + arr
. Если arr[k]
является k+1
элементом массива, то arr+k
является указателем на arr[k]
. Другими словами, arr
или arr+0
является указателем на arr[0]
, arr+1
является указателем на arr[2]
и т. Д. В общем случае *(arr+k)
совпадает с arr[k]
.
В отличие от обычной арифметики добавление 1
к указателю на int
добавит 4
байта к текущему значению адреса. Поскольку имена массива являются постоянными указателями, +
является единственным оператором, который мы можем использовать для доступа к элементам массива посредством нотации указателя с использованием имени массива. Однако, определяя указатель на массив, мы можем получить большую гибкость для обработки данных в массиве. Например, мы можем напечатать элементы массива следующим образом:
#include<stdio.h>
static const size_t N = 5
int main()
{
size_t k = 0;
int arr[] = {1, 2, 3, 4, 5};
for(k = 0; k < N; k++)
{
printf("\n\t%d", *(arr + k));
}
return 0;
}
Определив указатель на массив, вышеуказанная программа эквивалентна следующему:
#include<stdio.h>
static const size_t N = 5
int main()
{
size_t k = 0;
int arr[] = {1, 2, 3, 4, 5};
int *ptr = arr; /* or int *ptr = &arr[0]; */
for(k = 0; k < N; k++)
{
printf("\n\t%d", ptr[k]);
/* or printf("\n\t%d", *(ptr + k)); */
/* or printf("\n\t%d", *ptr++); */
}
return 0;
}
Посмотрите, что члены массива arr
получают доступ с помощью операторов +
и ++
. Другие операторы, которые могут использоваться с указателем ptr
, -
и --
.
Вычитание указателя
Учитывая два указателя на один и тот же тип, оценивается объект типа ptrdiff_t
который содержит скалярное значение, которое должно быть добавлено ко второму указателю, чтобы получить значение первого указателя.
int arr[] = {1, 2, 3, 4, 5};
int *p = &arr[2];
int *q = &arr[3];
ptrdiff_t diff = q - p;
printf("q - p = %ti\n", diff); /* Outputs "1". */
printf("*(p + (q - p)) = %d\n", *(p + diff)); /* Outputs "4". */
Операторы доступа
Операторы доступа члена (точка .
И стрелка ->
) используются для доступа к члену struct
.
Участник объекта
Вычисляет значение lvalue, обозначающее объект, являющийся членом объекта доступа.
struct MyStruct
{
int x;
int y;
};
struct MyStruct myObject;
myObject.x = 42;
myObject.y = 123;
printf(".x = %i, .y = %i\n", myObject.x, myObject.y); /* Outputs ".x = 42, .y = 123". */
Участник объекта с указателем
Синтаксический сахар для разыменования, за которым следует доступ членов. Эффективно выражение вида x->y
является сокращением для (*x).y
- но оператор стрелки намного ясен, особенно если указатели структуры вложены.
struct MyStruct
{
int x;
int y;
};
struct MyStruct myObject;
struct MyStruct *p = &myObject;
p->x = 42;
p->y = 123;
printf(".x = %i, .y = %i\n", p->x, p->y); /* Outputs ".x = 42, .y = 123". */
printf(".x = %i, .y = %i\n", myObject.x, myObject.y); /* Also outputs ".x = 42, .y = 123". */
Адресов
Унарный оператор &
является адресом оператора. Он оценивает данное выражение, где результирующий объект должен быть lvalue. Затем он вычисляет объект, тип которого является указателем на тип результирующего объекта и содержит адрес результирующего объекта.
int x = 3;
int *p = &x;
printf("%p = %p\n", (void *)&x, (void *)p); /* Outputs "A = A", for some implementation-defined A. */
разыменовывают
Оператор унарного *
разыскивает указатель. Он оценивает значение lvalue, возникающее в результате разыменования указателя, который возникает в результате оценки данного выражения.
int x = 42;
int *p = &x;
printf("x = %d, *p = %d\n", x, *p); /* Outputs "x = 42, *p = 42". */
*p = 123;
printf("x = %d, *p = %d\n", x, *p); /* Outputs "x = 123, *p = 123". */
индексирование
Индексирование - это синтаксический сахар для добавления указателя, за которым следует разыменование. Эффективно выражение вида a[i]
эквивалентно *(a + i)
но явное обозначение индекса является предпочтительным.
int arr[] = { 1, 2, 3, 4, 5 };
printf("arr[2] = %i\n", arr[2]); /* Outputs "arr[2] = 3". */
Взаимозаменяемость индексации
Добавление указателя в целое число является коммутативной операцией (т. Е. Порядок операндов не изменяет результат), так что pointer + integer == integer + pointer
.
Следствием этого является то, что arr[3]
и 3[arr]
эквивалентны.
printf("3[arr] = %i\n", 3[arr]); /* Outputs "3[arr] = 4". */
Использование выражения 3[arr]
вместо arr[3]
обычно не рекомендуется, так как оно влияет на читаемость кода. Он имеет тенденцию быть популярным в запутанных конкурсах программирования.
Оператор вызова функции
Первый операнд должен быть указателем на функцию (признак функции также приемлем, поскольку он будет преобразован в указатель на функцию), идентифицируя функцию для вызова, а все другие операнды, если они есть, все вместе известны как аргументы вызова функции , Вычисляет возвращаемое значение, вызванное вызовом соответствующей функции с соответствующими аргументами.
int myFunction(int x, int y)
{
return x * 2 + y;
}
int (*fn)(int, int) = &myFunction;
int x = 42;
int y = 123;
printf("(*fn)(%i, %i) = %i\n", x, y, (*fn)(x, y)); /* Outputs "fn(42, 123) = 207". */
printf("fn(%i, %i) = %i\n", x, y, fn(x, y)); /* Another form: you don't need to dereference explicitly */
Побитовые операторы
Побитовые операторы могут использоваться для выполнения операций с битовым уровнем по переменным.
Ниже приведен список всех шести побитовых операторов, поддерживаемых в C:
Условное обозначение | оператор |
---|---|
& | побитовое И |
| | побитовое включение ИЛИ |
^ | побитовое исключающее ИЛИ (XOR) |
~ | побитовое (дополнение) |
<< | логический сдвиг влево |
>> | логическая сдвиг вправо |
Следующая программа иллюстрирует использование всех побитовых операторов:
#include <stdio.h>
int main(void)
{
unsigned int a = 29; /* 29 = 0001 1101 */
unsigned int b = 48; /* 48 = 0011 0000 */
int c = 0;
c = a & b; /* 32 = 0001 0000 */
printf("%d & %d = %d\n", a, b, c );
c = a | b; /* 61 = 0011 1101 */
printf("%d | %d = %d\n", a, b, c );
c = a ^ b; /* 45 = 0010 1101 */
printf("%d ^ %d = %d\n", a, b, c );
c = ~a; /* -30 = 1110 0010 */
printf("~%d = %d\n", a, c );
c = a << 2; /* 116 = 0111 0100 */
printf("%d << 2 = %d\n", a, c );
c = a >> 2; /* 7 = 0000 0111 */
printf("%d >> 2 = %d\n", a, c );
return 0;
}
Следует избегать побитовых операций с подписанными типами, поскольку знаковый бит такого битового представления имеет особое значение. Конкретные ограничения применяются к операторам сдвига:
Перемещение влево 1 бит в бит бит является ошибочным и приводит к неопределенному поведению.
Правильное смещение отрицательного значения (со знаком бит 1) является реализацией, и поэтому не переносится.
Если значение правого операнда оператора сдвига отрицательное или больше или равно ширине продвинутого левого операнда, поведение не определено.
Маскировка:
Маскировка относится к процессу извлечения желаемых битов из (или преобразования желаемых бит) переменной с использованием логических побитовых операций. Операнд (константа или переменная), который используется для выполнения маскировки, называется маской .
Маскировка используется по-разному:
- Чтобы решить битовый шаблон целочисленной переменной.
- Чтобы скопировать часть данного битового шаблона в новую переменную, в то время как остальная часть новой переменной заполняется 0 (с помощью побитового И)
- Чтобы скопировать часть данного битового шаблона в новую переменную, в то время как остальная часть новой переменной заполняется 1 с (с использованием побитового ИЛИ).
- Чтобы скопировать часть заданного битового шаблона в новую переменную, в то время как оставшаяся часть исходного битового шаблона инвертируется в новой переменной (с использованием побитового исключения OR).
Следующая функция использует маску для отображения битовой диаграммы переменной:
#include <limits.h>
void bit_pattern(int u)
{
int i, x, word;
unsigned mask = 1;
word = CHAR_BIT * sizeof(int);
mask = mask << (word - 1); /* shift 1 to the leftmost position */
for(i = 1; i <= word; i++)
{
x = (u & mask) ? 1 : 0; /* identify the bit */
printf("%d", x); /* print bit value */
mask >>= 1; /* shift mask to the right by 1 bit */
}
}
_Alignof
Запросит требование выравнивания для указанного типа. Требование выравнивания представляет собой положительную интегральную мощность 2, представляющую количество байтов, между которыми могут быть выделены два объекта типа. В C требование выравнивания измеряется в size_t
.
Имя типа не может быть неполным, а не типом функции. Если в качестве типа используется массив, используется тип элемента массива.
К этому оператору часто обращаются через alignof
макросов alignof
из <stdalign.h>
.
int main(void)
{
printf("Alignment of char = %zu\n", alignof(char));
printf("Alignment of max_align_t = %zu\n", alignof(max_align_t));
printf("alignof(float[10]) = %zu\n", alignof(float[10]));
printf("alignof(struct{char c; int n;}) = %zu\n",
alignof(struct {char c; int n;}));
}
Возможный выход:
Alignment of char = 1
Alignment of max_align_t = 16
alignof(float[10]) = 4
alignof(struct{char c; int n;}) = 4
Короткое замыкание логических операторов
Короткое замыкание - это функциональность, которая пропускает оценку состояния условия (if / while / ...), когда это возможно. В случае логической операции над двумя операндами первый операнд оценивается (с истинным или ложным), и если есть вердикт (т.е. первый операнд является ложным при использовании &&, первый операнд имеет значение true при использовании ||), второй операнд Не Оценено.
Пример:
#include <stdio.h>
int main(void) {
int a = 20;
int b = -5;
/* here 'b == -5' is not evaluated,
since a 'a != 20' is false. */
if (a != 20 && b == -5) {
printf("I won't be printed!\n");
}
return 0;
}
Проверьте это самостоятельно:
#include <stdio.h>
int print(int i) {
printf("print function %d\n", i);
return i;
}
int main(void) {
int a = 20;
/* here 'print(a)' is not called,
since a 'a != 20' is false. */
if (a != 20 && print(a)) {
printf("I won't be printed!\n");
}
/* here 'print(a)' is called,
since a 'a == 20' is true. */
if (a == 20 && print(a)) {
printf("I will be printed!\n");
}
return 0;
}
Выход:
$ ./a.out
print function 20
I will be printed!
Короткое замыкание важно, когда вы хотите избежать оценки терминов, которые (вычислительно) являются дорогостоящими. Более того, это может сильно повлиять на поток вашей программы, как в этом случае: почему эта программа печатает «разветвленный!»? 4 раза?