Przypisanie

Operator przypisania ("="), jak sama nazwa wskazuje, przypisuje wartość prawego argumentu lewemu, np.: int a = 5, b; b = a; printf("%d\n", b); /* wypisze 5 */ Operator ten ma łączność prawostronną tzn. obliczanie przypisań następuje z prawa na lewo i zwraca on przypisaną wartość, dzięki czemu może być użyty kaskadowo: int a, b, c; a = b = c = 3; printf("%d %d %d\n", a, b, c); /* wypisze "3 3 3" */ Skrócony zapis

C umożliwia też skrócony zapis postaci a #= b;, gdzie # jest jednym z operatorów: +, -, *, /, %, &, |, ^, > (opisanych niżej). Ogólnie rzecz ujmując zapis a #= b; jest równoważny zapisowi a = a # (b);, np.: int a = 1; a += 5; /* to samo, co a = a + 5; */ a /= a + 2; /* to samo, co a = a / (a + 2); */ a %= 2; /* to samo, co a = a % 2; */ d >>= 2; // Przesuń d w prawo * ang. right shift) o 2 pozycje i przypisz z powrotem do d

Rzutowanie

Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu. Konwersja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podana explicite przez programistę). Oto kilka przykładów konwersji niejawnej: int i = 42.7; /* konwersja z double do int */ float f = i; /* konwersja z int do float */ double d = f; /* konwersja z float do double */ unsigned u = i; /* konwersja z int do unsigned int */ f = 4.2; /* konwersja z double do float */ i = d; /* konwersja z double do int */ char *str = "foo"; /* konwersja z const char* do char* */ const char *cstr = str; /* konwersja z char* do const char* */ void *ptr = str; /* konwersja z char* do void* */ Podczas konwersji zmiennych zawierających większe ilości danych do typów prostszych (np. double do int) musimy liczyć się z utratą informacji, jak to miało miejsce w pierwszej linijce - zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta i w rezultacie zmiennej została przypisana wartość 42.

Zaskakująca może się wydać linijka oznaczona przez . Niejawna konwersja z typu const char* do typu char* nie jest dopuszczana przez standard C. Jednak literały napisowe (które są typu const char*) stanowią tutaj wyjątek. Wynika on z faktu, że były one używane na długo przed wprowadzeniem słówka const do języka i brak wspomnianego wyjątku spowodowałby, że duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod.

Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania, np.: double d = 3.14; int pi = (int)d; /* 1 */ pi = (unsigned)pi >> 4; /* 2 */ W pierwszym przypadku operator został użyty, by zwrócić uwagę na utratę precyzji. W drugim, dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej.

Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne konwersje (tj. konwersja z double do int oraz z int do unsigned int), jednak niektóre konwersje są błędne, np.: const char *cstr = "foo"; char *str = cstr; W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję: const char *cstr = "foo"; char *str = (char*)cstr; Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompilator. Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzie on robił i czy nie ma innego sposobu wykonania danej operacji, który nie wymagałby podejmowania tak drastycznych kroków.

Operatory arytmetyczne

Język C definiuje następujące dwuargumentowe operatory arytmetyczne:

dodawanie ("+"), odejmowanie ("-"), mnożenie ("*"), dzielenie ("/"), reszta z dzielenia ("%") określona tylko dla liczb całkowitych (tzw. dzielenie modulo).

Dzielenie i mnożenie

int a=7, b=2, c; c = a % b; printf ("%d\n",c); /* wypisze "1" */ Należy pamiętać, że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak największy z argumentów. Oznacza to, że operacja wykonana na dwóch liczbach całkowitych nadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej. Dla przykładu, poniższy kod: float a = 7 / 2; printf("%f\n", a); wypisze (wbrew oczekiwaniu początkujących programistów) 3.0, a nie 3.5. Odnosi się to nie tylko do dzielenia, ale także mnożenia, np.: float a = 1000 * 1000 * 1000 * 1000 * 1000 * 1000; printf("%f\n", a); prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali. Aby wymusić obliczenia rzeczywiste należy zmienić typ jednego z argumentów na liczbę rzeczywistą po prostu zmieniając literał lub korzystając z rzutowania, np.: float a = 7.0 / 2; /* wcześniejszy zapis: float a = 7 / 2; */ float b = (float)1000 * 1000 * 1000 * 1000 * 1000 * 1000; printf("%f\n", a); printf("%f\n", b);

Dzielenie całkowite ( ang. integer division)libc manual : Integer-Divisionstackoverflow question: what-is-the-behavior-of-integer-division zmiennoprzecinkowe ( ang. float division)

Zasady dzielenia liczb całkowitych i zmiennoprzecinkowych w Cdelftstack : c-integer-division by Waqar Aslam Sep 16, 2022 integer / integer = integer float / integer = float integer / float = float

Dodawanie i odejmowanie Operatory dodawania i odejmowania są określone również, gdy jednym z argumentów jest wskaźnik, a drugim liczba całkowita. Ten drugi jest także określony, gdy oba argumenty są wskaźnikami. O takim użyciu tych operatorów dowiesz się więcej w dalszej części książki.

Inkrementacja i dekrementacja

Aby skrócić zapis wprowadzono dodatkowe operatory: inkrementacji ("++") i dekrementacji ("--"), które dodatkowo mogą być pre- lub postfiksowe. W rezultacie mamy więc cztery operatory:

pre-inkrementacja ("++i"), post-inkrementacja ("i++"), pre-dekrementacja ("--i") i post-dekrementacja ("i--").

Operatory inkrementacji zwiększa, a dekrementacji zmniejsza argument o jeden. Ponadto operatory pre- zwracają nową wartość argumentu, natomiast post- starą wartość argumentu. int a, b, c; a = 3; b = a--; /* po operacji b=3 a=2 */ c = --b; /* po operacji b=2 c=2 */ Czasami (szczególnie w C++) użycie operatorów stawianych za argumentem jest nieco mniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tymczasową).

Operatory bitowe

Oprócz operacji znanych z lekcji matematyki w podstawówce, język C został wyposażony także w operatory bitoweBitwise_operations_in_C ( english wikipedia)C MANIPULACJE BITAMI - Karol Kuczmarski „Xion”Bit Twiddling Hacks By Sean Eron AndersonBitwise Operators by joe_query, . Są to: negacja bitowa (NOT)("~"), koniunkcja bitowa (AND)("&"), alternatywa bitowa (OR)("|") i alternatywa rozłączna (XOR) ("^").

Działają one na poszczególnych bitach przez co mogą być szybsze od innych operacji.

Działanie tych operatorów można zdefiniować za pomocą poniższych tabel prawdy ( matryc logicznych):

W pierwszej tabeli a i b oznaczają bity ( albo pole bitowe o długości 1) , a nie liczby typu całkowitego

"~" | a "&" | a | b "|" | a | b "^" | a | b -----+--- -----+---+--- -----+---+--- -----+---+--- 0 | 1 0 | 0 | 0 0 | 0 | 0 0 | 0 | 0 1 | 0 1 | 1 | 1 1 | 1 | 1 0 | 1 | 1 0 | 0 | 1 1 | 0 | 1 1 | 0 | 1 0 | 1 | 0 1 | 1 | 0 1 | 1 | 0

W drugiej tabeli a i b oznaczają 4 bitowe pole ( c nie ma typu 4 bitowego)

a | 0101 = 5 b | 0011 = 3 -------+------ ~a | 1010 = 10 ~b | 1100 = 12 a & b | 0001 = 1 a | b | 0111 = 7 a ^ b | 0110 = 6

Lub bardziej opisowo: negacja bitowa daje w wyniku liczbę, która ma bity równe jeden tylko na tych pozycjach, na których argument miał bity równe zero; koniunkcja bitowa daje w wyniku liczbę, która ma bity równe jeden tylko na tych pozycjach, na których oba argumenty miały bity równe jeden (mnemonik: 1 gdy wszystkie 1); alternatywa bitowa daje w wyniku liczbę, która ma bity równe jeden na wszystkich tych pozycjach, na których jeden z argumentów miał bit równy jeden (mnemonik: 1 jeśli jest 1); alternatywa rozłączna daje w wyniku liczbę, która ma bity równe jeden tylko na tych pozycjach, na których tylko jeden z argumentów miał bit równy jeden (mnemonik: 1 gdy różne).

Przy okazji warto zauważyć, że a ^ b ^ b to po prostu a. Właściwość ta została wykorzystana w różnych algorytmach szyfrowania oraz funkcjach haszujących. Alternatywę wyłączną stosuje się np. do szyfrowania kodu wirusów polimorficznych.

Negacja bitowa

Jak wpływa długość liczby binarnej na wynik negacji ? 1 bit 4 bity 8 bitów a ~a

Przykładowy program działajacy na liczbie 8 bitowej ( unsigned char ): // gcc b.c -Wall // ./a.out #include

/* https://stackoverflow.com/questions/699968/display-the-binary-representation-of-a-number-in-c Chris Lutz */ void PrintBitsOfUChar(unsigned char v) { int i; // for C89 compatability for(i = 7; i >= 0; i--) putchar('0' + ((v >> i) & 1)); }

void TestBitwiseNot (unsigned char a ){

printf ("decimal number a = %3u;\t ", a ); printf ("it's binary expansion = "); PrintBitsOfUChar(a); printf("\n"); printf ("decimal number ~a = %3u;\t ", (unsigned char) ~a ); printf ("it's binary expansion = "); PrintBitsOfUChar((unsigned char) ~a); printf("\n\n");

int main () { //unsigned char a; //char buffer;

printf("unsigned char has size = 1 byte = 8 bits and range from 0 to 255\n\n");

TestBitwiseNot(0); TestBitwiseNot(255); TestBitwiseNot(5); TestBitwiseNot(3);

return 0; }

Wynik

unsigned char has size = 1 byte = 8 bits and range from 0 to 255

decimal number a = 0; it's binary expansion = 00000000 decimal number ~a = 255; it's binary expansion = 11111111

decimal number a = 255; it's binary expansion = 11111111 decimal number ~a = 0; it's binary expansion = 00000000

decimal number a = 5; it's binary expansion = 00000101 decimal number ~a = 250; it's binary expansion = 11111010

decimal number a = 3; it's binary expansion = 00000011 decimal number ~a = 252; it's binary expansion = 11111100

Przesunięcie bitowe

Dodatkowo, język C wyposażony jest w operatory przesunięcia bitowego w lewo (">"). Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycji podaną jako prawy argument.

[variable]>1 | a>>2 ------+------+------+------+------ 0001 | 0010 | 0100 | 0000 | 0000 0011 | 0110 | 1100 | 0001 | 0000 0101 | 1010 | 0100 | 0010 | 0001 1000 | 0000 | 0000 | 0100 | 0010 1111 | 1110 | 1100 | 0111 | 0011 1001 | 0010 | 0100 | 0100 | 0010

Nie jest to zatem takie straszne na jakie wygląda.

Przesunięcie w lewą stronę oznacza przemieszczenie wszystkich bitów argumentu w lewo o określoną liczbę miejsc oraz wprowadzenie z prawej strony takiej samej ilości zer.

Przesunięcie w prawo oznacza przemieszczenie wszystkich bitów argumentu w prawo o określoną liczbę miejsc oraz powielenie najstarszego bitu na skrajnej lewej pozycji.Język C - Herbert Schildt, Oficyna Wydawnicza LTP, Warszawa 2002:

#include

int main () { int a = 6; printf ("6 > 2 = %d\n", a>>2); /* wypisze 1 */ return 0; }

Inny przykład

#include

int main() { unsigned int d = 0b11010110; // Binary: 11010110, Decimal: 214 int k = 2;

printf("Before shift: %u\n", d);

d >>= k; // Right shift d by 2 positions and assign back to d

printf("After shift: %u\n", d);

return 0; }

W tym przykładzie początkowa wartość d wynosi 214 (w formacie binarnym: 11010110).

Po przesunięciu w prawo o 2 pozycje (k wynosi 2) wynikiem jest 00110101, czyli 53 w systemie dziesiętnym.

Zatem wynikiem będzie:

Before shift: 214 After shift: 53

Zastosowania Operatorów bitowych używamy do: operacji na zbiorach quora : What-are-useful-tricks-in-C++-or-C-that-beginners-rarely-know ? szyfrowania: XOR EncryptionXOR Encryption by Alex Allain kompresji danych ( najmniejszy typ liczbowy ma 1 bajt = 8 bitów ) bit-array in c by Shun Y. Cheung szybkich obliczeń geeksforgeeks: bits-manipulation-important-tacticsgeeksforgeeks : bit-tricks-competitive-programming kod Graya Bit Operations in C/C++ by Robert B. Heckendorn University of Idaho obliczania liczb losowych programowaniu systemów wbudowanych (ang. embedded system) ocfreaks tutorial: embedded-programming-basics-in-c-bitwise-operations programowania grafiki OpenGl: pola bitowe w arumentach funkcji sieciach komputerowych ( adres IP, Maska podsieci ) (a >b)‘ jest równoznaczne z podzieleniem a przez 2^b