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 раза?