C Language
operatori
Ricerca…
introduzione
Un operatore in un linguaggio di programmazione è un simbolo che indica al compilatore o all'interprete di eseguire un'operazione matematica, relazionale o logica specifica e di produrre un risultato finale.
C ha molti potenti operatori. Molti operatori C sono operatori binari, il che significa che hanno due operandi. Ad esempio, in a / b
, /
è un operatore binario che accetta due operandi ( a
, b
). Esistono alcuni operatori unari che accettano un operando (ad esempio: ~
, ++
) e un solo operatore ternario ? :
Sintassi
- operatore expr1
- operatore expr2
- expr1 operator expr2
- expr1? expr2: expr3
Osservazioni
Gli operatori hanno un'arità , una precedenza e un'associatività .
Arity indica il numero di operandi. In C esistono tre diverse organizzazioni di operatori:
- Unario (1 operando)
- Binario (2 operandi)
- Ternario (3 operandi)
Precedenza indica quali operatori "eseguono il bind" prima dei loro operandi. Cioè, quale operatore ha la priorità di operare sui suoi operandi. Ad esempio, il linguaggio C obbedisce alla convenzione secondo cui la moltiplicazione e la divisione hanno la precedenza sull'addizione e sulla sottrazione:
a * b + c
Dà lo stesso risultato di
(a * b) + c
Se questo non è ciò che si voleva, la precedenza può essere forzata usando le parentesi, poiché hanno la precedenza più alta di tutti gli operatori.
a * (b + c)
Questa nuova espressione produrrà un risultato che differisce dalle precedenti due espressioni.
Il linguaggio C ha molti livelli di precedenza; Di seguito viene riportata una tabella di tutti gli operatori, in ordine decrescente di precedenza.
Tabella di precedenza
operatori Associatività ()
[]
->
.
da sinistra a destra !
~
++
--
+
-
*
(dereferenziazione)(type)
sizeof
da destra a sinistra *
(moltiplicazione)/
%
da sinistra a destra +
-
da sinistra a destra <<
>>
da sinistra a destra <
<=
>
>=
da sinistra a destra ==
!=
da sinistra a destra &
da sinistra a destra ^
da sinistra a destra |
da sinistra a destra &&
da sinistra a destra ||
da sinistra a destra ?:
da destra a sinistra =
+=
-=
*=
/=
%=
&=
^=
|=
<<=
>>=
da destra a sinistra ,
da sinistra a destra L'associatività indica come gli operatori con precedenza uguale si legano per impostazione predefinita e sono disponibili due tipi: da sinistra a destra e da destra a sinistra . Un esempio di associazione da sinistra a destra è l'operatore di sottrazione (
-
). L'espressionea - b - c - d
ha tre sottrazioni di precedenza identiche, ma dà lo stesso risultato di
((a - b) - c) - d
perché il più a sinistra
-
si lega prima ai suoi due operandi.Un esempio di associatività da destra a sinistra sono gli operatori dereferenzia
*
e post-incremento++
. Entrambi hanno la stessa precedenza, quindi se sono usati in un'espressione come* ptr ++
, questo è equivalente a
* (ptr ++)
perché l'operatore all'estrema destra (
++
) si lega innanzitutto al suo unico operando.
Operatori relazionali
Gli operatori relazionali verificano se una relazione specifica tra due operandi è vera. Il risultato viene valutato su 1 (che significa vero ) o 0 (che significa falso ). Questo risultato viene spesso utilizzato per influenzare il flusso di controllo (tramite if
, while
, for
), ma può anche essere memorizzato in variabili.
Uguale a "=="
Controlla se gli operandi forniti sono uguali.
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. */
Attenzione: questo operatore non deve essere confuso con l'operatore di assegnazione ( =
)!
Non uguale a "! ="
Controlla se gli operandi forniti non sono uguali.
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. */
Questo operatore restituisce in modo efficace il risultato opposto a quello dell'operatore di uguale ( ==
).
Non "!"
Controlla se un oggetto è uguale a 0
.
Il !
può anche essere usato direttamente con una variabile come segue:
!someVal
Questo ha lo stesso effetto di:
someVal == 0
Maggiore di ">"
Controlla se l'operando di sinistra ha un valore maggiore rispetto all'operando di destra
5 > 4 /* evaluates to 1. */
4 > 5 /* evaluates to 0. */
4 > 4 /* evaluates to 0. */
Meno di "<"
Controlla se l'operando di sinistra ha un valore inferiore rispetto all'operando di destra
5 < 4 /* evaluates to 0. */
4 < 5 /* evaluates to 1. */
4 < 4 /* evaluates to 0. */
Maggiore o uguale "> ="
Controlla se l'operando di sinistra ha un valore maggiore o uguale all'operando di destra.
5 >= 4 /* evaluates to 1. */
4 >= 5 /* evaluates to 0. */
4 >= 4 /* evaluates to 1. */
Minore o uguale "<="
Controlla se l'operando di sinistra ha un valore inferiore o uguale all'operando di destra.
5 <= 4 /* evaluates to 0. */
4 <= 5 /* evaluates to 1. */
4 <= 4 /* evaluates to 1. */
Operatori di assegnazione
Assegna il valore dell'operando di destra alla posizione di memoria indicata dall'operando di sinistra e restituisce il valore.
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'. */
Diverse operazioni aritmetiche hanno un operatore di assegnazione composto .
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 */
Una caratteristica importante di questi compiti composti è che l'espressione sul lato sinistro ( a
) viene valutata solo una volta. Ad esempio se p
è un puntatore
*p += 27;
dereferenze p
una sola volta, mentre il seguente lo fa due volte.
*p = *p + 27;
Va anche notato che il risultato di un compito come a = b
è ciò che è noto come un valore . Pertanto, l'assegnazione ha effettivamente un valore che può quindi essere assegnato a un'altra variabile. Ciò consente il concatenamento di assegnazioni per impostare più variabili in una singola istruzione.
Questo valore può essere utilizzato nelle espressioni di controllo delle istruzioni if
(o cicli o istruzioni switch
) che proteggono il codice sul risultato di un'altra espressione o chiamata di funzione. Per esempio:
char *buffer;
if ((buffer = malloc(1024)) != NULL)
{
/* do something with buffer */
free(buffer);
}
else
{
/* report allocation failure */
}
Per questo motivo, è necessario prestare attenzione per evitare un errore di battitura comune che può portare a bug misteriosi.
int a = 2;
/* ... */
if (a = 1)
/* Delete all files on my hard drive */
Ciò avrà risultati disastrosi, poiché a = 1
valuterà sempre 1
e quindi l'espressione di controllo dell'istruzione if
sarà sempre vera (leggi di più su questo errore comune qui ). L'autore quasi certamente intendeva usare l'operatore di uguaglianza ( ==
) come mostrato di seguito:
int a = 2;
/* ... */
if (a == 1)
/* Delete all files on my hard drive */
Associatività dell'operatore
int a, b = 1, c = 2;
a = b = c;
Questo assegna c
a b
, che restituisce b
, che è assegnato a a
. Ciò accade perché tutti gli operatori di assegnazione hanno una associatività corretta, vale a dire che l'operazione più a destra nell'espressione viene valutata per prima e procede da destra a sinistra.
Operatori aritmetici
Aritmetica di base
Restituisce un valore che è il risultato dell'applicazione dell'operando della mano sinistra all'operando di destra, usando l'operazione matematica associata. Si applicano le normali regole matematiche di commutazione (cioè l'addizione e la moltiplicazione sono commutative, la sottrazione, la divisione e il modulo non lo sono).
Addition Operator
L'operatore di addizione ( +
) viene utilizzato per aggiungere due operandi insieme. Esempio:
#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;
}
Operatore di sottrazione
L'operatore di sottrazione ( -
) viene utilizzato per sottrarre il secondo operando dal primo. Esempio:
#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;
}
Operatore di moltiplicazione
L'operatore di moltiplicazione ( *
) viene utilizzato per moltiplicare entrambi gli operandi. Esempio:
#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;
}
Da non confondere con l'operatore *
dereferenziazione.
Operatore di divisione
L'operatore di divisione ( /
) divide il primo operando del secondo. Se entrambi gli operandi della divisione sono numeri interi, restituirà un valore intero e scarterà il resto (utilizzare l'operatore modulo %
per calcolare e acquisire il resto).
Se uno degli operandi è un valore in virgola mobile, il risultato è un'approssimazione della frazione.
Esempio:
#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;
}
Operatore di modulo
L'operatore modulo ( %
) riceve solo operandi interi e viene utilizzato per calcolare il resto dopo che il primo operando è stato diviso per il secondo. Esempio:
#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;
}
Operatori di incremento / decremento
Gli operatori di incremento ( a++
) e di decremento ( a--
) sono diversi in quanto modificano il valore della variabile a cui li si applica senza un operatore di assegnazione. È possibile utilizzare gli operatori di incremento e decremento prima o dopo la variabile. Il posizionamento dell'operatore modifica i tempi di incremento / decremento del valore prima o dopo l'assegnazione alla variabile. Esempio:
#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 */
}
}
Come nell'esempio per c
e d
, entrambi gli operatori hanno due forme, come notazione prefisso e notazione postfissa. Entrambi hanno lo stesso effetto nell'incrementare ( ++
) o decrementare ( --
) la variabile, ma differiscono in base al valore che restituiscono: le operazioni di prefisso eseguono prima l'operazione e quindi restituiscono il valore, mentre le operazioni postfissa determinano prima il valore che deve essere restituito, quindi eseguire l'operazione.
A causa di questo comportamento potenzialmente contro-intuitivo, l'uso di operatori di incremento / decremento all'interno di espressioni è controverso.
Operatori logici
AND logico
Esegue un AND logico booleano dei due operandi restituendo 1 se entrambi gli operandi sono diversi da zero. L'operatore AND logico è di tipo int
.
0 && 0 /* Returns 0. */
0 && 1 /* Returns 0. */
2 && 0 /* Returns 0. */
2 && 3 /* Returns 1. */
OR logico
Esegue un OR logico booleano dei due operandi restituendo 1 se uno degli operandi è diverso da zero. L'operatore logico OR è di tipo int
.
0 || 0 /* Returns 0. */
0 || 1 /* Returns 1. */
2 || 0 /* Returns 1. */
2 || 3 /* Returns 1. */
NOT logico
Esegue una negazione logica. L'operatore logico NOT è di tipo int
. L'operatore NOT verifica se almeno un bit è uguale a 1, in tal caso restituisce 0. Altrimenti restituisce 1;
!1 /* Returns 0. */
!5 /* Returns 0. */
!0 /* Returns 1. */
Valutazione del cortocircuito
Ci sono alcune proprietà cruciali comuni a &&
e ||
:
- l'operando di sinistra (LHS) è completamente valutato prima di valutare l'operando di destra (RHS),
- c'è un punto di sequenza tra la valutazione dell'operando di sinistra e l'operando di destra,
- e, soprattutto, l'operando di destra non viene valutato affatto se il risultato dell'operando di sinistra determina il risultato complessivo.
Ciò significa che:
- se l'LHS valuta "vero" (diverso da zero), l'RHS di
||
non sarà valutato (perché il risultato di 'true OR anything' è 'true'), - se l'LHS restituisce 'false' (zero), l'RHS di
&&
non verrà valutato (perché il risultato di 'false AND anything' è 'false').
Questo è importante in quanto ti permette di scrivere codice come:
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";
}
Se un valore negativo viene passato alla funzione, il value >= 0
termine value < NUM_NAMES
false e il value < NUM_NAMES
non viene valutato.
Incrementa / Decrementa
Gli operatori di incremento e decremento esistono in forma prefissa e postfix .
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 */
Nota che le operazioni aritmetiche non introducono punti di sequenza , quindi certe espressioni con ++
o --
operatori possono introdurre un comportamento indefinito .
Operatore condizionale / Operatore ternario
Valuta il suo primo operando e, se il valore risultante non è uguale a zero, valuta il suo secondo operando. Altrimenti, valuta il suo terzo operando, come mostrato nell'esempio seguente:
a = b ? c : d;
è equivalente a:
if (b)
a = c;
else
a = d;
Questo pseudo-codice lo rappresenta: condition ? value_if_true : value_if_false
. Ogni valore può essere il risultato di un'espressione valutata.
int x = 5;
int y = 42;
printf("%i, %i\n", 1 ? x : y, 0 ? x : y); /* Outputs "5, 42" */
L'operatore condizionale può essere annidato. Ad esempio, il seguente codice determina il più grande dei tre numeri:
big= a > b ? (a > c ? a : c)
: (b > c ? b : c);
L'esempio seguente scrive anche interi in un file e interi dispari in un altro file:
#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;
}
L'operatore condizionale si associa da destra a sinistra. Considera quanto segue:
exp1 ? exp2 : exp3 ? exp4 : exp5
Poiché l'associazione va da destra a sinistra, l'espressione sopra è valutata come
exp1 ? exp2 : ( exp3 ? exp4 : exp5 )
Operatore di virgola
Valuta il suo operando di sinistra, scarta il valore risultante e quindi valuta l'operando dei diritti e il risultato restituisce il valore dell'operando più a destra.
int x = 42, y = 42;
printf("%i\n", (x *= 2, y)); /* Outputs "42". */
L'operatore virgola introduce un punto di sequenza tra i suoi operandi.
Si noti che la virgola utilizzata nelle funzioni chiama che gli argomenti separati NON è l' operatore virgola , piuttosto è chiamato un separatore che è diverso dall'operatore virgola . Quindi, non ha le proprietà dell'operatore virgola .
La precedente chiamata printf()
contiene sia l' operatore virgola che il separatore .
printf("%i\n", (x *= 2, y)); /* Outputs "42". */
/* ^ ^ this is a comma operator */
/* this is a separator */
L'operatore virgola viene spesso utilizzato nella sezione di inizializzazione e nella sezione di aggiornamento di un ciclo for
. Per esempio:
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);
Cast Operatore
Esegue una conversione esplicita nel tipo specificato dal valore risultante dalla valutazione dell'espressione specificata.
int x = 3;
int y = 4;
printf("%f\n", (double)x / y); /* Outputs "0.750000". */
Qui il valore di x
viene convertito in un double
, la divisione promuove anche il valore di y
per double
, e il risultato della divisione, un double
viene passato a printf
per la stampa.
sizeof Operatore
Con un tipo come operando
Valuta nella dimensione in byte, di tipo size_t
, di oggetti del tipo specificato. Richiede parentesi attorno al tipo.
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! */
Con un'espressione come operando
Valuta nella dimensione in byte, di tipo size_t
, di oggetti del tipo dell'espressione data. L'espressione stessa non viene valutata. Le parentesi non sono richieste; tuttavia, poiché l'espressione data deve essere unaria, è consigliabile utilizzarla sempre.
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. */
Puntatore aritmetico
Aggiunta puntatore
Dato un puntatore e un tipo scalare N
, valuta in un puntatore il N
elemento del tipo puntato che riesce direttamente nell'oggetto puntato in memoria.
int arr[] = {1, 2, 3, 4, 5};
printf("*(arr + 3) = %i\n", *(arr + 3)); /* Outputs "4", arr's fourth element. */
Non importa se il puntatore viene usato come valore dell'operando o come valore scalare. Ciò significa che cose come 3 + arr
sono valide. Se arr[k]
è il membro k+1
di un array, quindi arr+k
è un puntatore a arr[k]
. In altre parole, arr
o arr+0
è un puntatore a arr[0]
, arr+1
è un puntatore a arr[2]
, e così via. In generale, *(arr+k)
è uguale a arr[k]
.
Diversamente dalla solita aritmetica, l'aggiunta di 1
a un puntatore a un int
aggiungerà 4
byte al valore dell'indirizzo corrente. Poiché i nomi degli array sono puntatori costanti, +
è l'unico operatore che possiamo usare per accedere ai membri di un array tramite la notazione del puntatore usando il nome dell'array. Tuttavia, definendo un puntatore a un array, possiamo ottenere una maggiore flessibilità per elaborare i dati in un array. Ad esempio, possiamo stampare i membri di un array come segue:
#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;
}
Definendo un puntatore all'array, il programma di cui sopra è equivalente al seguente:
#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;
}
Vedi che i membri dell'array arr
sono accessibili usando gli operatori +
e ++
. Gli altri operatori che possono essere utilizzati con il puntatore ptr
sono -
e --
.
Sottrazione puntatore
Dato due puntatori allo stesso tipo, valuta in un oggetto di tipo ptrdiff_t
che contiene il valore scalare che deve essere aggiunto al secondo puntatore per ottenere il valore del primo puntatore.
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". */
Operatori di accesso
Gli operatori di accesso utente (dot .
E freccia ->
) vengono utilizzati per accedere a un membro di una struct
.
Membro di oggetto
Valuta nel lvalue che indica l'oggetto che è un membro dell'oggetto accesso.
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". */
Membro di oggetto appuntito
Zucchero sintattico per il dereferenziamento seguito dall'accesso dei membri. In effetti, un'espressione della forma x->y
è una scorciatoia per (*x).y
- ma l'operatore della freccia è molto più chiaro, specialmente se i puntatori della struttura sono nidificati.
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". */
Indirizzo-di
L'unario &
operatore è l'indirizzo dell'operatore. Valuta l'espressione data, in cui l'oggetto risultante deve essere un lvalue. Quindi, valuta in un oggetto il cui tipo è un puntatore al tipo di oggetto risultante e contiene l'indirizzo dell'oggetto risultante.
int x = 3;
int *p = &x;
printf("%p = %p\n", (void *)&x, (void *)p); /* Outputs "A = A", for some implementation-defined A. */
dereference
L'operatore unario *
denota un puntatore. Valuta nel lvalue risultante dal dereferenziamento del puntatore che risulta dalla valutazione dell'espressione data.
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". */
indicizzazione
L'indicizzazione è zucchero sintattico per l'aggiunta del puntatore seguita dal dereferenziamento. In effetti, un'espressione della forma a[i]
è equivalente a *(a + i)
- ma la notazione di indice esplicito è preferita.
int arr[] = { 1, 2, 3, 4, 5 };
printf("arr[2] = %i\n", arr[2]); /* Outputs "arr[2] = 3". */
Intercambiabilità dell'indicizzazione
L'aggiunta di un puntatore a un numero intero è un'operazione commutativa (ovvero l'ordine degli operandi non modifica il risultato) quindi pointer + integer == integer + pointer
.
Una conseguenza di ciò è che arr[3]
e 3[arr]
sono equivalenti.
printf("3[arr] = %i\n", 3[arr]); /* Outputs "3[arr] = 4". */
L'uso di un'espressione 3[arr]
invece di arr[3]
è generalmente raccomandato, in quanto influisce sulla leggibilità del codice. Tende ad essere popolare nei concorsi di programmazione offuscati.
Operatore di chiamata di funzione
Il primo operando deve essere un puntatore di funzione (anche un designatore di funzione è accettabile perché verrà convertito in un puntatore alla funzione), identificando la funzione da chiamare e tutti gli altri operandi, se presenti, sono noti collettivamente come argomenti della chiamata di funzione . Valuta nel valore restituito risultante dal richiamo della funzione appropriata con i rispettivi argomenti.
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 */
Operatori bit a bit
Gli operatori bit a bit possono essere utilizzati per eseguire operazioni a livello di bit su variabili.
Di seguito è riportato un elenco di tutti e sei gli operatori bit a bit supportati in C:
Simbolo | Operatore |
---|---|
& | bit a bit AND |
| | bit a bit compreso OR |
^ | OR esclusivo bit a bit (XOR) |
~ | bit a bit no (il proprio complemento) |
<< | spostamento logico sinistro |
>> | spostamento logico giusto |
Il seguente programma illustra l'uso di tutti gli operatori bit a bit:
#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;
}
Le operazioni bit a bit con i tipi firmati dovrebbero essere evitate perché il bit di segno di tale rappresentazione bit ha un significato particolare. Restrizioni particolari si applicano agli operatori di turno:
Lo spostamento a sinistra di 1 bit nel bit firmato è errato e porta a un comportamento indefinito.
Il corretto spostamento di un valore negativo (con il bit di segno 1) è definito dall'implementazione e quindi non è trasferibile.
Se il valore dell'operando di destra di un operatore di spostamento è negativo o è maggiore o uguale alla larghezza dell'operando di sinistra promosso, il comportamento non è definito.
Masking:
Il mascheramento si riferisce al processo di estrazione dei bit desiderati (o alla trasformazione dei bit desiderati in) di una variabile utilizzando operazioni logiche bit a bit. L'operando (una costante o variabile) utilizzato per eseguire il mascheramento è chiamato maschera .
Il mascheramento è utilizzato in molti modi diversi:
- Per decidere il modello di bit di una variabile intera.
- Per copiare una parte di un dato pattern di bit in una nuova variabile, mentre il resto della nuova variabile viene riempita con 0s (usando bitwise AND)
- Per copiare una porzione di un dato pattern di bit in una nuova variabile, mentre il resto della nuova variabile viene riempita con 1s (usando OR bit a bit).
- Per copiare una porzione di un dato pattern di bit in una nuova variabile, mentre il resto del pattern di bit originale viene invertito all'interno della nuova variabile (usando OR esclusivo bit per bit).
La seguente funzione utilizza una maschera per visualizzare il modello di bit di una variabile:
#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
Esegue una query sul requisito di allineamento per il tipo specificato. Il requisito di allineamento è una potenza integrale positiva di 2 che rappresenta il numero di byte tra i quali possono essere allocati due oggetti del tipo. In C, il requisito di allineamento è misurato in size_t
.
Il nome del tipo potrebbe non essere un tipo incompleto né un tipo di funzione. Se viene utilizzato un array come tipo, viene utilizzato il tipo di elemento dell'array.
Questo operatore è spesso accessibile tramite il pratico alignof
macro di <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;}));
}
Uscita possibile:
Alignment of char = 1
Alignment of max_align_t = 16
alignof(float[10]) = 4
alignof(struct{char c; int n;}) = 4
Comportamento di cortocircuito degli operatori logici
Il cortocircuito è una funzionalità che salta la valutazione delle parti di una condizione (se / mentre / ...) quando possibile. Nel caso di un'operazione logica su due operandi, il primo operando viene valutato (vero o falso) e se c'è un verdetto (cioè il primo operando è falso quando si usa &&, il primo operando è vero quando si usa ||) il secondo operando è non valutato.
Esempio:
#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;
}
Dai un'occhiata a te stesso:
#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;
}
Produzione:
$ ./a.out
print function 20
I will be printed!
Il cortocircuito è importante, quando si vuole evitare di valutare termini che sono (computazionalmente) costosi. Inoltre, può influire pesantemente sul flusso del tuo programma come in questo caso: Perché questo programma stampa "forked!" 4 volte?