Szukaj…


Wprowadzenie

Operator w języku programowania to symbol, który informuje kompilator lub tłumacza, aby wykonał określoną operację matematyczną, relacyjną lub logiczną i dał wynik końcowy.

C ma wiele potężnych operatorów. Wiele operatorów C jest operatorami binarnymi, co oznacza, że mają dwa operandy. Na przykład w a / b , / jest operatorem binarnym, który akceptuje dwa operandy ( a , b ). Czy istnieją jedni operatorzy, którzy biorą jeden operand (na przykład: ~ , ++ ) i tylko jeden operator trójskładnikowy ? : .

Składnia

  • operator expr1
  • operator wyraż2
  • expr1 operator expr2
  • expr1? wyrażenie2: wyrażenie3

Uwagi

Operatorzy mają arsenał , pierwszeństwo i skojarzenie .

  • Arity wskazuje liczbę operandów. W C istnieją trzy różne arie operatorów:

    • Unary (1 operand)
    • Binarny (2 operandy)
    • Trójskładnik (3 operandy)
  • Pierwszeństwo wskazuje, którzy operatorzy najpierw „wiążą” swoje operandy. To znaczy, który operator ma pierwszeństwo w operowaniu operandami. Na przykład język C jest zgodny z konwencją, że mnożenie i dzielenie mają pierwszeństwo przed dodawaniem i odejmowaniem:

    a * b + c
    

    Daje taki sam wynik jak

    (a * b) + c
    

    Jeśli nie było to pożądane, pierwszeństwo można wymusić za pomocą nawiasów, ponieważ mają one najwyższy priorytet ze wszystkich operatorów.

    a * (b + c)
    

    To nowe wyrażenie da wynik różniący się od poprzednich dwóch wyrażeń.

    Język C ma wiele poziomów pierwszeństwa; Poniżej znajduje się tabela wszystkich operatorów, w malejącej kolejności pierwszeństwa.

    Tabela pierwszeństwa

    Operatorzy Stowarzyszenie
    () [] -> . z lewej na prawą
    ! ~ ++ -- + - * (dereferencja) (type) sizeof od prawej do lewej
    * (mnożenie) / % z lewej na prawą
    + - z lewej na prawą
    << >> z lewej na prawą
    < <= > >= z lewej na prawą
    == != z lewej na prawą
    & z lewej na prawą
    ^ z lewej na prawą
    | z lewej na prawą
    && z lewej na prawą
    || z lewej na prawą
    ?: od prawej do lewej
    = += -= *= /= %= &= ^= |= <<= >>= od prawej do lewej
    , z lewej na prawą
  • Asocjatywność wskazuje, w jaki sposób operatory o równym priorytecie wiążą się domyślnie, i istnieją dwa rodzaje: od lewej do prawej i od prawej do lewej . Przykładem wiązania od lewej do prawej jest operator odejmowania ( - ). Ekspresja

    a - b - c - d
    

    ma trzy odejmowania o identycznym priorytecie, ale daje taki sam wynik jak

    ((a - b) - c) - d
    

    ponieważ skrajnie lewy - najpierw wiąże się z dwoma operandami.

    Przykładem asocjatywności od prawej do lewej są operatory dereferencyjne * i post-increment ++ . Oba mają równy priorytet, więc jeśli są używane w wyrażeniu takim jak

    * ptr ++
    

    , jest to równoważne z

    * (ptr ++)
    

    ponieważ skrajnie prawy, jednoargumentowy operator ( ++ ) najpierw wiąże się z jednym operandem

Operatorzy relacyjni

Operatory relacyjne sprawdzają, czy konkretna relacja między dwoma operandami jest prawdziwa. Wynik jest oceniany na 1 (co oznacza prawda ) lub 0 (co oznacza fałsz ). Ten wynik jest często wykorzystywany do wpływania na przepływ sterowania (przez if , while , for ), ale może być również przechowywany w zmiennych.

Równa się „==”

Sprawdza, czy dostarczone argumenty są równe.

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. */

Uwaga: Tego operatora nie należy mylić z operatorem przypisania ( = )!

Nie równa się „! =”

Sprawdza, czy dostarczone argumenty nie są równe.

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. */

Ten operator skutecznie zwraca wynik przeciwny do wyniku operatora równego ( == ).

Nie "!"

Sprawdź, czy obiekt jest równy 0 .

The ! można również użyć bezpośrednio ze zmienną w następujący sposób:

!someVal

Ma to taki sam efekt jak:

someVal == 0

Większy niż „>”

Sprawdza, czy operand lewej ręki ma większą wartość niż operand prawej ręki

5 > 4      /* evaluates to 1. */
4 > 5      /* evaluates to 0. */
4 > 4      /* evaluates to 0. */

Mniej niż „<”

Sprawdza, czy operand lewej ręki ma mniejszą wartość niż operand prawej ręki

5 < 4      /* evaluates to 0. */
4 < 5      /* evaluates to 1. */
4 < 4      /* evaluates to 0. */

Większy lub równy „> =”

Sprawdza, czy lewy operand ma większą lub równą wartość prawemu operandowi.

5 >= 4      /* evaluates to 1. */
4 >= 5      /* evaluates to 0. */
4 >= 4      /* evaluates to 1. */

Mniejszy lub równy „<=”

Sprawdza, czy lewy operand ma wartość mniejszą lub równą prawemu operandowi.

5 <= 4      /* evaluates to 0. */
4 <= 5      /* evaluates to 1. */
4 <= 4      /* evaluates to 1. */

Operatorzy przydziału

Przypisuje wartość prawego operandu do miejsca przechowywania nazwanego przez lewy operand i zwraca wartość.

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'. */

Kilka operacji arytmetycznych ma operator przypisania złożonego .

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 */

Jedną ważną cechą tych przypisań złożonych jest to, że wyrażenie po lewej stronie ( a ) jest oceniane tylko raz. Np. Jeśli p jest wskaźnikiem

*p += 27;

dereferencje p tylko jeden raz, podczas gdy poniższe robi to dwa razy.

*p = *p + 27;

Należy również zauważyć, że wynikiem przypisania, takim jak a = b jest tak zwana wartość rvalue . Zatem przypisanie faktycznie ma wartość, którą można następnie przypisać do innej zmiennej. Umożliwia to łączenie przypisań w celu ustawienia wielu zmiennych w jednej instrukcji.

Ta wartość może być używana w kontrolujących wyrażeniach instrukcji if (lub pętli lub instrukcji switch ), które chronią część kodu w wyniku innego wyrażenia lub wywołania funkcji. Na przykład:

char *buffer;
if ((buffer = malloc(1024)) != NULL)
{
    /* do something with buffer */
    free(buffer);
}
else
{
    /* report allocation failure */
}

Z tego powodu należy zachować ostrożność, aby uniknąć typowej literówki, która może prowadzić do tajemniczych błędów.

int a = 2;
/* ... */
if (a = 1)
    /* Delete all files on my hard drive */

Będzie to miało katastrofalne skutki, ponieważ a = 1 zawsze będzie oceniać na 1 a zatem wyrażenie kontrolne instrukcji if będzie zawsze prawdziwe (czytaj więcej o tej częstej pułapce tutaj ). Autor prawie na pewno zamierzał użyć operatora równości ( == ), jak pokazano poniżej:

int a = 2;
/* ... */
if (a == 1)
    /* Delete all files on my hard drive */

Stowarzyszenie operatora

int a, b = 1, c = 2;
a = b = c;

To przypisuje c do b , co zwraca b , co jest następnie przypisane do a . Dzieje się tak, ponieważ wszystkie operatory przypisania mają prawą asocjatywność, co oznacza, że operacja w wyrażeniu znajdująca się najbardziej na prawo jest oceniana jako pierwsza i przechodzi od prawej do lewej.

Operatory arytmetyczne

Podstawowa arytmetyka

Zwraca wartość wynikającą z zastosowania operandu po lewej stronie do operandu po prawej stronie, używając powiązanej operacji matematycznej. Obowiązują normalne matematyczne zasady komutacji (tzn. Dodawanie i mnożenie są przemienne, odejmowanie, dzielenie i moduł nie są).

Operator dodawania

Operator dodawania ( + ) służy do dodawania dwóch operandów razem. Przykład:

#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;
}

Operator odejmowania

Operator odejmowania ( - ) służy do odejmowania drugiego operandu od pierwszego. Przykład:

#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;
}

Operator mnożenia

Operator mnożenia ( * ) służy do mnożenia obu operandów. Przykład:

#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;
}

Nie mylić z operatorem * dereferencji.

Operator dywizji

Operator podziału ( / ) dzieli pierwszy operand przez drugi. Jeśli oba operandy dzielenia są liczbami całkowitymi, zwróci wartość całkowitą i odrzuci resztę (użyj operatora modulo % do obliczenia i pozyskania reszty).

Jeśli jeden z argumentów ma wartość zmiennoprzecinkową, wynikiem jest przybliżenie ułamka.

Przykład:

#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;
}

Operator Modulo

Operator modulo ( % ) odbiera tylko argumenty całkowite i służy do obliczenia reszty po podzieleniu pierwszego argumentu przez drugi. Przykład:

#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;
}

Operatory inkrementacji / dekrementacji

Operatory inkrementacji ( a++ ) i dekrementacji ( a-- ) różnią się tym, że zmieniają wartość zmiennej, do której je zastosujesz, bez operatora przypisania. Możesz używać operatorów inkrementacji i dekrementacji przed lub po zmiennej. Umieszczenie operatora zmienia czas zwiększania / zmniejszania wartości na przed lub po przypisaniu jej do zmiennej. Przykład:

#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 */
    }
}

Jako przykład c i d pokazuje oba podmioty dwie formy, a przedrostek oznaczenie i przyrostek oznaczenie. Oba mają taki sam efekt w zwiększaniu ( ++ ) lub zmniejszaniu ( -- ) zmiennej, ale różnią się wartością, którą zwracają: operacje prefiksu wykonują najpierw operację, a następnie zwracają wartość, podczas gdy operacje postfiksowe najpierw określają wartość, która ma być zostać zwrócone, a następnie wykonaj operację.

Z powodu tego potencjalnie sprzecznego z intuicją zachowania użycie operatorów inkrementacji / dekrementacji w wyrażeniach jest kontrowersyjne.

Operatory logiczne

Logiczne AND

Wykonuje logiczną logiczną operację logiczną AND dwóch operandów zwracających 1, jeśli oba operandy są niezerowe. Logiczny operator AND jest typu int .

0 && 0  /* Returns 0. */
0 && 1  /* Returns 0. */
2 && 0  /* Returns 0. */
2 && 3  /* Returns 1. */

Logiczne OR

Wykonuje logiczną logiczną operację OR dwóch operandów, zwracając 1, jeśli którykolwiek z operandów jest niezerowy. Logiczny operator OR jest typu int .

0 || 0  /* Returns 0. */
0 || 1  /* Returns 1.  */
2 || 0  /* Returns 1.  */
2 || 3  /* Returns 1.  */

Logiczne NIE

Wykonuje logiczną negację. Logiczny operator NOT jest typu int . Operator NOT sprawdza, czy przynajmniej jeden bit jest równy 1, jeśli tak, zwraca 0. W przeciwnym razie zwraca 1;

!1 /* Returns 0. */
!5 /* Returns 0. */
!0 /* Returns 1. */

Ocena zwarcia

Istnieje kilka kluczowych właściwości wspólnych dla obu && i || :

  • lewy operand (LHS) jest w pełni oceniany, zanim prawy operand (RHS) jest w ogóle oceniany,
  • między operandem po lewej a operandzie po prawej stronie występuje punkt sekwencyjny,
  • i, co najważniejsze, operand po prawej stronie wcale nie jest oceniany, jeśli wynik operacji po lewej stronie determinuje wynik ogólny.

To znaczy że:

  • jeżeli LHS ocenia się na „prawda” (niezerowa), RHS || nie będzie oceniany (ponieważ wynikiem „prawda LUB cokolwiek” jest „prawda”),
  • jeśli LHS oceni na „fałsz” (zero), RHS && nie zostaną ocenione (ponieważ wynik „fałsz AND cokolwiek” jest „fałsz”).

Jest to ważne, ponieważ umożliwia pisanie kodu, takiego jak:

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";
}

Jeśli do funkcji zostanie przekazana wartość ujemna, value >= 0 warunek zostanie value < NUM_NAMES za fałsz, a value < NUM_NAMES warunek nie zostanie obliczony.

Przyrost / spadek

Operatory inkrementacji i dekrementacji istnieją w formie prefiksów i postfiksów .

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 */

Zauważ, że operacje arytmetyczne nie wprowadzają punktów sekwencji , więc niektóre wyrażenia z operatorami ++ lub -- mogą wprowadzać niezdefiniowane zachowanie .

Operator warunkowy / operator trójskładnikowy

Ocenia pierwszy argument operacji, a jeśli wynikowa wartość nie jest równa zero, ocenia drugi argument operacji. W przeciwnym razie ocenia swój trzeci argument, jak pokazano w poniższym przykładzie:

a = b ? c : d;

jest równa:

if (b)
    a = c;
else 
    a = d;

Ten pseudo-kod reprezentuje go: condition ? value_if_true : value_if_false . Każda wartość może być wynikiem wartościowanego wyrażenia.

int x = 5;
int y = 42;
printf("%i, %i\n", 1 ? x : y, 0 ? x : y); /* Outputs "5, 42" */

Operator warunkowy może być zagnieżdżony. Na przykład poniższy kod określa większą z trzech liczb:

big= a > b ? (a > c ? a : c)
           : (b > c ? b : c);

Poniższy przykład zapisuje parzyste liczby całkowite do jednego pliku, a nieparzyste liczby całkowite do innego pliku:

#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;
}

Operator warunkowy kojarzy od prawej do lewej. Rozważ następujące:

exp1 ? exp2 : exp3 ? exp4 : exp5

Ponieważ powiązanie jest od prawej do lewej, powyższe wyrażenie jest oceniane jako

exp1 ? exp2 : ( exp3 ? exp4 : exp5 )

Operator przecinka

Ocenia swój lewy operand, odrzuca wynikową wartość, a następnie ocenia swój operand praw i wynik daje wartość jego skrajnego prawa operand.

int x = 42, y = 42;
printf("%i\n", (x *= 2, y)); /* Outputs "42". */

Operator przecinka wprowadza punkt sekwencyjny między operandami.

Zauważ, że przecinek używany w funkcjach wywołuje oddzielne argumenty NIE operator przecinka , a raczej separator różniący się od operatora przecinka . Dlatego nie ma właściwości operatora przecinka .

Powyższe wywołanie printf() zawiera zarówno przecinek, jak i separator .

printf("%i\n", (x *= 2, y)); /* Outputs "42". */
/*           ^        ^ this is a comma operator */
/*           this is a separator */

Operator przecinka jest często używany w sekcji inicjalizacji, a także w sekcji aktualizacji pętli for . Na przykład:

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);

Operator obsady

Wykonuje jawną konwersję na dany typ z wartości wynikającej z oceny danego wyrażenia.

int x = 3;
int y = 4;
printf("%f\n", (double)x / y); /* Outputs "0.750000". */

Tutaj wartość x jest konwertowana na double , podział promuje również wartość y do double , a wynik podziału double jest przekazywany do printf do drukowania.

sizeof Operator

Z typem jako operand

Oblicza rozmiar w bajtach typu size_t , obiektów danego typu. Wymaga nawiasów wokół tego typu.

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! */

Z wyrażeniem jako operand

Oblicza rozmiar w bajtach typu size_t , obiektów typu danego wyrażenia. Samo wyrażenie nie jest oceniane. Nawiasy nie są wymagane; jednak ponieważ podane wyrażenie musi być jednoargumentowe, za najlepszą praktykę uważa się zawsze ich używanie.

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. */

Wskaźnik arytmetyczny

Dodanie wskaźnika

Biorąc pod uwagę wskaźnik i typ skalarny N , zmienia się w wskaźnik do N tego elementu typu wskazanego, który bezpośrednio zastępuje wskazany obiekt w pamięci.

int arr[] = {1, 2, 3, 4, 5};
printf("*(arr + 3) = %i\n", *(arr + 3)); /* Outputs "4", arr's fourth element. */

Nie ma znaczenia, czy wskaźnik jest używany jako wartość argumentu, czy wartość skalarna. Oznacza to, że takie rzeczy jak 3 + arr są poprawne. Jeśli arr[k] jest elementem k+1 tablicy, to arr+k jest wskaźnikiem do arr[k] . Innymi słowy, arr lub arr+0 to wskaźnik do arr[0] , arr+1 to wskaźnik do arr[2] i tak dalej. Zasadniczo *(arr+k) jest takie samo jak arr[k] .

W przeciwieństwie do zwykłej arytmetyki, dodanie 1 do wskaźnika do int doda 4 bajty do bieżącej wartości adresu. Ponieważ nazwy tablic są stałymi wskaźnikami, + jest jedynym operatorem, którego możemy użyć, aby uzyskać dostęp do członków tablicy za pomocą notacji wskaźnika za pomocą nazwy tablicy. Jednak definiując wskaźnik do tablicy, możemy uzyskać większą elastyczność przetwarzania danych w tablicy. Na przykład możemy wydrukować elementy tablicy w następujący sposób:

#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;
}

Po zdefiniowaniu wskaźnika do tablicy powyższy program jest równoważny z następującym:

#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;
}

Sprawdź, czy dostęp do elementów tablicy arr uzyskuje się za pomocą operatorów + i ++ . Innymi operatorami, których można używać ze wskaźnikiem ptr- i -- .

Odejmowanie wskaźnika

Biorąc pod uwagę dwa wskaźniki tego samego typu, przekształca się w obiekt typu ptrdiff_t który przechowuje wartość skalarną, którą należy dodać do drugiego wskaźnika, aby uzyskać wartość pierwszego wskaźnika.

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". */

Dostęp do operatorów

Operatorzy dostępu członek (dot . A strzałka -> ) są używane do dostępu członkiem struct .

Członek obiektu

Ocenia wartość do wartości oznaczającej obiekt będący członkiem obiektu, do którego uzyskano dostęp.

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". */

Członek wskazanego obiektu

Cukier syntaktyczny do dereferencji, a następnie dostęp członków. W rzeczywistości wyrażenie postaci x->y jest skrótem dla (*x).y - ale operator strzałek jest znacznie wyraźniejszy, szczególnie jeśli zagnieżdżone są wskaźniki struktury.

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". */

Adres

Unary & operator to adres operatora. Ocenia podane wyrażenie, w którym wynikowy obiekt musi być wartością. Następnie przekształca się w obiekt, którego typ jest wskaźnikiem typu obiektu wynikowego i zawiera adres obiektu wynikowego.

int x = 3;
int *p = &x;
printf("%p = %p\n", (void *)&x, (void *)p); /* Outputs "A = A", for some implementation-defined A. */

Dereferencja

Jednoargumentowy * operator wyłacza wskaźnik. Ocenia wartość do wartości wynikającej z dereferencji wskaźnika, która wynika z oceny podanego wyrażenia.

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". */

Indeksowanie

Indeksowanie to cukier składniowy do dodawania wskaźnika, po którym następuje dereferencja. W efekcie wyrażenie w postaci a[i] jest równoważne *(a + i) - ale preferowana jest wyraźna notacja indeksu dolnego.

int arr[] = { 1, 2, 3, 4, 5 };
printf("arr[2] = %i\n", arr[2]); /* Outputs "arr[2] = 3". */

Wymienność indeksowania

Dodanie wskaźnika do liczby całkowitej jest operacją przemienną (tzn. Kolejność operandów nie zmienia wyniku), więc pointer + integer == integer + pointer .

Konsekwencją tego jest to, że arr[3] i 3[arr] są równoważne.

printf("3[arr] = %i\n", 3[arr]); /* Outputs "3[arr] = 4". */

Użycie wyrażenia 3[arr] zamiast arr[3] zasadniczo nie jest zalecane, ponieważ wpływa to na czytelność kodu. Zwykle jest popularny w zaciemnionych konkursach programistycznych.

Operator wywołania funkcji

Pierwszy operand musi być wskaźnikiem funkcji (desygnator funkcji jest również akceptowalny, ponieważ zostanie przekonwertowany na wskaźnik do funkcji), identyfikując funkcję do wywołania, a wszystkie inne argumenty, jeśli istnieją, są łącznie znane jako argumenty wywołania funkcji . Ocenia wartość zwracaną wynikającą z wywołania odpowiedniej funkcji z odpowiednimi argumentami.

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 */

Operatory bitowe

Operatory bitowe mogą być używane do wykonywania operacji na poziomie bitów na zmiennych.
Poniżej znajduje się lista wszystkich sześciu operatorów bitowych obsługiwanych w C:

Symbol Operator
I bitowe ORAZ
| bitowe włącznie OR
^ bitowe wyłączne OR (XOR)
~ nie bitowe (uzupełnienie)
<< logiczne przesunięcie w lewo
>> logiczne przesunięcie w prawo

Poniższy program ilustruje użycie wszystkich operatorów bitowych:

#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;
}

Należy unikać operacji bitowych z podpisanymi typami, ponieważ bit znaku takiej reprezentacji bitowej ma szczególne znaczenie. Szczególne ograniczenia dotyczą operatorów zmiany:

  • Przesunięcie w lewo o 1 bit do podpisanego bitu jest błędne i prowadzi do nieokreślonego zachowania.

  • Prawidłowe przesunięcie wartości ujemnej (z bitem znaku 1) jest zdefiniowane w implementacji i dlatego nie jest przenośne.

  • Jeśli wartość prawego argumentu operatora zmiany jest ujemna lub jest większa lub równa szerokości promowanego lewego argumentu, zachowanie jest niezdefiniowane.

Maskowanie:

Maskowanie odnosi się do procesu wydobywania pożądanych bitów z (lub przekształcania pożądanych bitów) zmiennej przy użyciu logicznych operacji bitowych. Operand (stały lub zmienny) używany do maskowania nazywa się maską .

Maskowanie stosuje się na wiele różnych sposobów:

  • Aby zdecydować o strukturze bitów zmiennej całkowitej.
  • Aby skopiować część danego wzorca bitowego do nowej zmiennej, podczas gdy pozostała część nowej zmiennej jest wypełniona zerami (używając bitowego AND)
  • Aby skopiować część danego wzorca bitowego do nowej zmiennej, podczas gdy pozostała część nowej zmiennej jest wypełniona 1s (przy użyciu bitowego OR).
  • Aby skopiować część danego wzorca bitowego do nowej zmiennej, podczas gdy pozostała część oryginalnego wzorca bitowego zostanie odwrócona w obrębie nowej zmiennej (używając bitowej wykluczającej OR).

Poniższa funkcja wykorzystuje maskę do wyświetlenia wzoru bitowego zmiennej:

#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

Odpytuje o wymaganie wyrównania dla określonego typu. Wymaganie wyrównania jest dodatnią potęgą całkowitą równą 2, reprezentującą liczbę bajtów, pomiędzy którą można przydzielić dwa obiekty tego typu. W C wymóg wyrównania jest mierzony w size_t .

Nazwa typu nie może być niepełnym typem ani typem funkcji. Jeśli jako typ zostanie użyta tablica, użyty zostanie typ elementu tablicowego.

Do tego operatora często można uzyskać dostęp za pomocą wygodnego makra alignof z <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;}));    
}

Możliwe wyjście:

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

Zachowanie zwarciowe operatorów logicznych

Zwarcie to funkcja, która pomija ocenę części stanu (jeśli / podczas / ...), gdy jest to możliwe. W przypadku operacji logicznej na dwóch operandach pierwszy operand jest oceniany (na prawdę lub fałsz), a jeśli istnieje werdykt (tj. Pierwszy operand jest fałszywy przy użyciu &&, pierwszy operand jest prawdziwy przy użyciu ||), drugi operand jest nie ocenione.

Przykład:

#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;
}

Sprawdź to sam:

#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;
}

Wynik:

$ ./a.out
print function 20
I will be printed!

Zwarcie jest ważne, gdy chcesz uniknąć oceny warunków, które są (obliczeniowo) kosztowne. Co więcej, może silnie wpłynąć na przebieg twojego programu, jak w tym przypadku: Dlaczego ten program wypisuje „rozwidlony!” 4 razy?



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow