Operatory arytmetyczne
Transkrypt
Operatory arytmetyczne
Operatory • C/C++ dopuszczają wiele róŜnorakich operatorów • podstawowe grupy operatorów o operatory arytmetyczne o operatory logiczne o operatory bitowe • kaŜdy operator charakteryzuje o liczba argumentów o sposób działania o priorytet o typ łączności Operatory arytmetyczne Podstawowe: + – * / Są to operatory dwuargumentowe: arg1 op arg2 Wynik moŜe zaleŜeć od typu: int a=5, b=2, wi; float c=5.0, d=2.0, wf; wi=a/b; wf=c/d; /* wartość : 2 */ /* wartość : 2.5 */ Operator % • operator dwuargumentowy • moŜna stosować go tylko do typów całkowitych • pozwala na uzyskanie reszty z dzielenia, np. 10 % 3 - ma wartość 1 12 % 4 - ma wartość 0 • dla liczb ujemnych wynik (podobnie jak dla całkowitego dzielenia) moŜe zaleŜeć od maszyny Przykład zastosowania: #include <iostream> #include <iomanip> using namespace std; int main(void) { for (int k=0; k<64; k=k+1) { if (k%8) cout <<"\t"; else cout <<"\n"; cout << setw(2) <<k; } cout << "\n"; return 0; } 0 8 16 24 32 40 48 56 1 9 17 25 33 41 49 57 2 10 18 26 34 42 50 58 3 11 19 27 35 43 51 59 4 12 20 28 36 44 52 60 5 13 21 29 37 45 53 61 6 14 22 30 38 46 54 62 7 15 23 31 39 47 55 63 Priorytet + – * / % : taki sam priorytet : taki sam priorytet (wyŜszy niŜ + –) Łączność – określenie sposobu grupowania. Łączność lewostronna: a+b+c+d jest identyczne z (((a + b) + c) + d) Jednoargumentowe operatory + i – + arg : nie robi nic - arg : zmienia wartość arg na przeciwną Dla operatorów jednoargumentowych typ łączności jest określony przez kierunek działania operatora Operatory zwiększania i zmniejszania • bardzo często (np. w pętlach) mamy wyraŜenia: k=k+1; /* zwiększ k o jeden */ n=n-1; /* zmniejsz n o jeden */ • w C – specjalne operatory k++; /* równoznaczne k=k+1; */ n--; /* równoznaczne n=n-1; */ • dwie formy tych operatorów: o przedrostkowa (prefix) – operator stoi z lewej strony argumentu (przed argumentem) o przyrostkowa (postfix) – operator stoi z prawej strony argumentu (po argumencie) • róŜnica w znaczeniu – chodzi o moment, w którym argument podlega zwiększeniu (zmniejszeniu) o forma przedrostkowa (przed) – najpierw zmieniamy wartość zmiennej a potem tak zmodyfikowaną zmienną stosujemy w wyraŜeniu o forma przyrostkowa (po) – najpierw obliczamy wartość wyraŜenia uŜywając starej wartości argumentu, a na koniec zmieniamy wartość zmiennej Przykład: int x,n=5; x=n++; printf(”%d %d\n”,n,x); int x,n=5; x=++n; printf(”%d %d\n”,n,x); 6 5 6 6 Operator przypisania = • operator dwuargumentowy, prawostronnie łączny • kaŜde wyraŜenie samo w sobie jest wyraŜeniem mającym wartość równą wartości przypisywanej wyraŜenie: (m=2) ma wartość równą 2. • w C/C++ mamy szereg innych operatorów przypisania Operatory logiczne Operatory relacji < <= > >= mniejszy niŜ ... mniejszy lub równy ... większy niŜ ... większy lub równy ... • priorytety równe (niŜsze niŜ operatorów arytmetycznych) • łączne lewostronnie == != jest równy ... jest róŜny od ... • priorytet równy, niŜszy niŜ < ... • łączne lewostronnie Uwaga: rozróŜnienie pomiędzy = a == Przykład: int a=5, b=100; if (a==b) { cout<<“Liczby rowne\n”); cout<<a<< “ “ <<b<< “\n”; } else cout << ”Liczby rozne\n”; int a=5, b=100; if (a=b) { cout << “Liczby rowne\n”; cout<<a<< “ “ <<b<< “\n”; } else cout << ”Liczby rozne\n”; Liczby rozne Liczby rowne 100 100 Zagadka – co wypisze program? #include <iostream> using namespace std; int main() { int a=-2, b=-1, c=0; if (a<b<c) cout << “Prawda.\n”; else cout << ”Falsz.\n”; } Suma logiczna i iloczyn logiczny || suma logiczna (logiczne LUB) && iloczyn logiczny (logiczne I) • priorytet && wyŜszy niŜ || ; priorytety obu niŜsze niŜ priorytety operatorów relacji i przyrównania zaś wyŜsze niŜ operatorów przypisania • sposób obliczania: od lewej do prawej. Obliczenia są przerywane, gdy wartość jest juŜ rozstrzygnięta (a==0) && (k>10) && n++ (a>0) || (k<5) && i++ Operator negacji ! zmienia wartość róŜną od zera na 0, wartość 0 na 1 Najczęstsze uŜycie: zamiast: if (a==0) stosujemy: if (!a) Operatory bitowe • pozwalają na manipulację pojedynczymi bitami składającymi się na słowo • moŜna je stosować jedynie do zmiennych typu całkowitego: char, short, int, long (tak ze znakiem jak i bez) << >> & | ^ ~ przesunięcie w lewo przesunięcie w prawo bitowy iloczyn logiczny (AND) bitowa suma (OR) bitowa róŜnica symetryczna (XOR) bitowa negacja Przesunięcie w lewo • składnia: zmienna << liczba_miejsc • działanie: bierze wzorzec bitów zapisany w zmiennej, przesuwa w lewo (uzupełniając z prawa zerami), zwraca jako wynik nowy wzór short a=0x40f2; short w; w=a << 3; a: w: 0100 0000 1111 0010 0000 0111 1001 0000 W pewnych okolicznościach operacja ta jest równowaŜna pomnoŜeniu przez 2liczba_miejsc Przesunięcie w prawo • składnia: zmienna >> liczba_miejsc • działanie: bierze wzorzec bitów zapisany w zmiennej, przesuwa w prawo. Dla liczb dodatnich lub unsigned bity z lewej uzupełniamy zerami. Dla liczb ujemnych – uzupełnienie zaleŜy od maszyny (moŜe być uzupełnienie zerami bądź jedynkami). unsigned short a=0x40f2; unsigned short w; w=a >> 3; a: w: 0100 0000 1111 0010 0000 1000 0001 1110 signed short a=0xff00; signed short w; w=a >> 3; a: 1111 1111 0000 0000 w: 0001 1111 1110 0000 w’: 1111 1111 1110 0000 (uzupełnienie zerami) (uzupełnienie jedynkami) Bitowa suma, iloczyn, róŜnica symetryczna, negacja Przykład: short short short a = m b = m c = m d = ~ m=0x0f0f; k=0x0ff0; a,b,c,d; & k; | k; ^ k; m; /* m: 0000 1111 0000 1111 */ /* k: 0000 1111 1111 0000 */ /* /* /* /* a: b: c: d: 0000 0000 0000 1111 1111 1111 0000 0000 0000 1111 1111 1111 0000 1111 1111 0000 */ */ */ */ Zastosowania operatorów bitowych: • iloczyn bitowy – „zasłanianie” pewnego zbioru bitów przykład: n=n & 0177; /* zeruje wszystkie bity oprócz 7 najmłodszych, 0177 : 0111 1111 */ • suma bitowa – “ustawianie” bitów przykład: x = x | SET_ON; /* ustawia 1 na tych pozycjach zmiennej x, gdzie są 1 w obiekcie SET_ON */ • przyspieszenie wykonania programu: o if(n&1)… lepsze niŜ: if(n%2==1) … o n<<=1 lepsze niŜ n*=2 • manipulacje na poziomie bitów: o przykład: funkcja getbits(unsigned x, int , int n), która ze zmiennej x wycina n-bitowe pole od pozycji p, dosunięte do prawej strony wyniku unsigned getbits(unsigned x, int p, int n) { // uwaga – numeracja od lewej; najmlodszy ma nr 0 unsigned y,m; y=x>>(p+1-n); //przesunięcie pola do skraju m= ~(~0 << n) ; return y&m ; } o problem: napisz funkcje setbits(x,p,n,y) zwracającą wartość x zmodyfikowaną tak, Ŝe jej n bitów (począwszy od p-tego) jest zastąpionych przez n skrajnych bitow z prawej strony y o jakie warunki sprawdzają wyraŜenia: if(v&(v-1)==0) {..}; if(v&&(v&(v-1))==0)) {…}; RóŜnica między operatorami bitowymi a logicznymi • operatory logiczne (&&, || ): o rozkład bitów nieistotny o waŜne jest tylko, czy wartość róŜni się od 0 o moŜliwy wynik: 0 lub 1 • operatory bitowe (&, | ): o zaglądają do wnętrza słowa - rozkład bitów istotny o wynik zaleŜy od układu bitów obu argumentów, moŜe być prawie dowolną liczbą – operatory bitowe przypominają operatory arytmetyczne Przykład: int m=3, k=2; int lk, la, bk, bk; lk = m && k; bk = m & k; la = m || k; ba = m | k; /* /* /* /* lk bk la ba = = = = 1 2 1 3 */ */ */ */ Pozostałe operatory przypisania • w C mamy często wyraŜenia typu x = x +2 które zapisujemy skrótowo: x += 2; • taki zapis moŜna uogólnić na inne operatory dwuargumentowe: + - * / % << >> & ^ | • dla tych operatorów zapis wyr1 op= wyr2 jest równowaŜny zapisowi: wyr1 = (wyr1) op (wyr2) Przykłady: k /= 5 k %=5 j <<=3 m &=n x -= z oznacza: oznacza: oznacza: oznacza: oznacza: k=k/5 k=k%5 j=j << 3 m=m & n x=x - z Uwaga – • nawiasy waŜne: x *= y+1 oznacza x=x * (y+1) a nie x=x * y + 1 • kolejność: op= -= oznacza co innego niŜ =- WyraŜenie warunkowe (operator ? : ) • składnia: wyr1 ? wyr2 : wyr3 • działanie: wyliczenie wyr1, jeŜeli ≠ 0 (prawda) to oblicza się wyr2 i ono staje się wartością całego wyraŜenia, w przeciwnym wypadku jest nią wyr3 • przykład: if (a>b) z=a; else z=b; jest równowaŜne: z=(a>b)?a:b; Operator przecinkowy • kilka wyraŜeń oddzielonych przecinkiem jest teŜ wyraŜeniem, którego wartością jest wartość skrajnego prawego wyraŜenia • poszczególne wyraŜenie są obliczane od lewej do prawej • przykład: wartością wyraŜenia: (2 + 4, a+5, c-4, 77+2) jest 79 Specyfikatory typów/dostępu do pamięci • specyfikator typu (w deklaracji/definicji obiektu) – słowo kluczowe poprzedzające nazwę typu, określające pewien atrybut deklarowanego obiektu • const – wskazuje na obiekty, które moŜna inicjalizować, ale nie moŜna zmieniać float pi=3.141; pi=pi+1; //definicja + inicjalizacja //legalna instrukcja const float pi=3.141; pi=pi+1; //definicja + inicjalizacja //błąd !!!! o moŜliwe sposoby definiowania stałych poprzez dyrektywę #define poprzez kwalifikator const Przykład: #define PI 3.1415926 const double PI=3.1415926; RóŜnice: gdy uŜyjemy #define to identyfikator nie jest znany kompilatorowi; gdy const – jest. To daje większą szansę wykrycia błędów składniowych przez kompilator. • volatile – wskazuje, Ŝe obiekt moŜe się zmieniać w sposób niewidoczny dla kompilatora o zmiana taka moŜe nastąpić np. na wskutek bezpośredniego (sprzętowego sprzęŜenia części pamięci która zawiera obiekt z urządzeniem zewnętrznym (np. zegar, miernik temperatury, itp.) o specyfikator volatile – ostrzeŜenie, Ŝe obiekt moŜe być tak zmienny (volatile: ulotny) – nie naleŜy od jego obsługi stosować Ŝadnych optymalizacji • register – powiadamia kompilator, Ŝe dana zmienna będzie intensywnie uŜywana o zmienna taka moŜe być umieszczona w rejestrach bądź szybkiej pamięci procesora – przyśpieszenie programu o deklarację moŜna stosować tylko do zmiennych automatycznych: register int x; register char c; oraz argumentów formalnych funkcji: int f(register unsigned m, register long n) • nie ma pewności, Ŝe kompilator umieści w rejestrach kaŜdą tak zadeklarowaną zmienną (ograniczania sprzętowe) • nie ma moŜliwości uzyskania adresu zmiennej rejestrowej • dla większości współczesnych kompilatorów deklaracja register to informacja nadmiarowa Konwersja typów • gdy argumentami operatora są obiekty róŜnych typów, to są one przekształcane do wspólnego typu • zasada ogólna – automatycznie wykonywane jest przekształcenie w którym argument „ciaśniejszy” jest przekształcany do typu „obszerniejszego” bez straty informacji Reguły przekształceń dla operatorów arytmetycznych nie uŜywających typu unsigned: • jeŜeli jeden argument long double, to drugi teŜ jest przekształcany do long double • w przeciwnym razie, jeŜeli jeden double, to drugi jest przekształcany do double • w przeciwnym razie – jeŜeli jeden float to drugi jest przekształcany teŜ do float • w przeciwnym razie wszystkie obiekty char i short są przekształcane do int • następnie, jeŜeli jeŜeli którykolwiek argument ma kwalifikator long, to inne teŜ są przekształcone do long Jawna konwersja – operatory rzutowania • w dowolnym momencie moŜna wymusić konwersję stosując operator rzutowania • stary rodzaj rzutowania (w C) - składnia (nazwa_typu) wyraŜenie • działanie – powoduje przekształcenie wyraŜenia do typu nazwa_typu • w C++ - dodatkowa składnia (w stylu wywołania funkcji nazwa_typu(wyraŜenie) Przykład: float x=5.3, y=3.2; int k; k= (int) x % (int) y; float x=5.3, y=3.2; int k; k= int(x)%int(y); • rzutowanie – powinno być stosowane z umiarem o wymusza na kompilatorze zaniechanie badania zgodności typów o powstrzymuje komunikaty o niewłaściwych akcjach programisty dotyczących typów o błędy – częsty powód to rzutowania; potrzeba ich szybkiego wyszukania o dotychczasowe formy operatorów rzutowania – trudne do automatycznego wyszukania • w C++ - nowe operatory rzutowania, pozwalające na łatwe odnalezienie miejsc ich uŜycia • składnia static_cast<nazwa_typu>(wyraŜenie) const_cast<nazwa_typu>(wyraŜenie) dynamic_cast<nazwa_typu>(wyraŜenie) reinterpret_cast<nazwa_typu>(wyraŜenie) • zasady uŜycia static_cast „dobrze się zachowujące” i „dość dobrze się zachowujące” (w tym operacje nie wymagające rzutowania); sprawdza w czasie kompilacji const_cast rzutowanie usuwające modyfikatory const i volatile dynamic_cast do bezpiecznego „rzutowania w dół”; sprawdza w czasie wykonania reinterpret_cast rzutowanie ze zmianą znaczenia; najbardziej niebezpieczny rodzaj rzutowania, zwłaszcza w przypadku wskaźników Przykład – static_cast #include <iostream> using namespace std; void func(int) { } int main() { int i = 100; long l; float f; // Typowe konwersje bez uŜycia rzutowania l = i; f = i; // Dozwolone równierz l = static_cast<long>(i); f = static_cast<float>(i); // Konwersje zawęŜające i = l; // MoŜliwa utrata cyfr i = f; // Mozliwa utrata informacji // Aby kompilator nie "marudził", robimy jawne rzutowania i = static_cast<int>(l); i = static_cast<int>(f); char c = static_cast<char>(i); // wymuszenie konwersji z void* void* vp = &i; // cout << *vp; niedozwolone cout << *(char*) vp << endl; // rzutowanie w stylu C cout << *static_cast<long*>(vp) << endl; //O.K. // Konwersja w styku C - powoduje niebezpieczna konwersje: float* fp = (float*)vp; // ale nowy sposob jest rownie niebezoieczny: fp = static_cast<float*>(vp); // Niejawne (automatyczne) konwersje // wykonywane przez kompilator double d = 0.0; int x = d; // Automatyczna konwersja typu x = static_cast<int>(d); // Konwersja bardziej jawna func(d); // Automatyczna konwersja typu func(static_cast<int>(d)); // Konwersja bardziej jawna } Priorytety operatorów Operator określanie zasięgu mazwa globalna wybór składowej wybór składowej indeksowanie wywołanie funkcji rzutowanie post inkrementacja post dekrementacja identyfikacja typu wg nazwy identyfikacja typu wyraŜenia konwersja – sprawdz. w czasie wykonania konwersja – sprawdz. w czasie kompilacji konwersja nie sprawdzana konwersja const/volatile rozmiar obiektu rozmiar typu pre inkrementacja pre dekrementacja negacja bitowa negacja logiczna minus jednoargumentowy adres argumentu wyłuskanie utwórz (przydziel pamięć) utwórz (przydziel pamięć) i inicjalizuj utwórz w zadanym miejscu utwórz w zadanym miejscu i inicjalizuj usuń (zwolnij pamięć) usuń tablicę rzutowanie (konwersja typu) Wybór składowej wsk. i nazwą obiektu Wybór składowej wsk. i wsk_do_obiektu mnoŜenie dzielenie modulo (reszta z dzielenia) dodawanie odejmowanie przesunięcie w lewo przesunięcie w prawo Składnia nazwa_klasy::składowa nazwa_przestrzeni::zmienna ::nazwa_globalna ::nazwa_kwalifikowana obiekt . nazwa wskaźnik -> składnik wskaźnik [ wyraŜenie ] nazwa_funkcji ( lista_wyraŜeń) typ (wyraŜenie) lwartość ++ lwartość –– typeid(typ) typeid(wyraŜenie) dynamic_cast<typ>(wyraŜenie) static_cast<typ>( wyraŜenie) reinterpret_cast<typ>(wyraŜenie) const_cast<typ>(wyraŜenie) sizeof(wyraŜenie) sizeof(typ) ++lwartość ––lwartość ~wyraŜenie ! wyraŜenie –wyraŜenie &lwartość *wyraŜenie new typ new typ new typ new typ delete wskaźnik delete [ ] wskaźnik (typ)wyraŜenie obiekt . *wsk_do_składnika wsk-> *wsk_do_składnika wyraŜenie * wyraŜenie wyraŜenie / wyraŜenie wyraŜenie % wyraŜenie wyraŜenie + wyraŜenie wyraŜenie – wyraŜenie wyraŜenie << wyraŜenie wyraŜenie >> wyraŜenie iloczyn bitowy wyraŜenie < wyraŜenie wyraŜenie <= wyraŜenie wyraŜenie > wyraŜenie wyraŜenie >= wyraŜenie wyraŜenie == wyraŜenie wyraŜenie != wyraŜenie wyraŜenie & wyraŜenie bitowa róŜnica symetryczna (XOR) wyraŜenie ^ wyraŜenie bitowa suma wyraŜenie | wyraŜenie koniunkcja (AND) wyraŜenie && wyraŜenie alternatywa (OR) wyraŜenie || wyraŜenie wyraŜenie warunkowe Operatory przypisania: =, *=, /=, %=, +=, –=, <<=, >>=, &=, |=, ^= rzucenie wyjątku wyraŜenie ? wyraŜenie : wyraŜenie wyraŜenie = wyraŜenie wyraŜenie op= wyraŜenie throw wyraŜenie przecinek wyraŜenie, wyraŜenie mniejszy mniejszy lub równy większy większy lub równy równe róŜne • operatory w jednej grupie – ten sam priorytet • im wyŜsza grupa – tym większy priorytet • typy łączności: o operatory jednoargumentowe i operatory przypisania – prawostronnie łączne o pozostałe operatory – lewostronnie łączne Uwaga: gdy nie jesteś pewien priorytetów, stosuj nawiasy