Поиск…


Вступление

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

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

C11

Запросит требование выравнивания для указанного типа. Требование выравнивания представляет собой положительную интегральную мощность 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

http://en.cppreference.com/w/c/language/_Alignof

Короткое замыкание логических операторов

Короткое замыкание - это функциональность, которая пропускает оценку состояния условия (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 раза?



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow