Operatory. Dorota Pylak

Podobne dokumenty
Operatory 1. Priorytety i wiązanie

Słowa kluczowe i nazwy

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Wstęp do informatyki- wykład 5 Instrukcja selekcji if-else Operatory arytmetyczne i logiczne

Podstawy programowania w języku C i C++

Operatory. Operatory bitowe i uzupełnienie informacji o pozostałych operatorach. Programowanie Proceduralne 1

Języki i paradygmaty programowania

Język C zajęcia nr 11. Funkcje

Podstawy programowania skrót z wykładów:

Wstęp do informatyki- wykład 7

Instrukcja do ćwiczeń nr 4 typy i rodzaje zmiennych w języku C dla AVR, oraz ich deklarowanie, oraz podstawowe operatory

Część 4 życie programu

Wstęp do informatyki- wykład 5 Instrukcja selekcji if-else Operatory arytmetyczne i logiczne Wyrażenie warunkowe operator selekcji Instrukcja switch

Podstawy Programowania Podstawowa składnia języka C++

1 Wskaźniki. 1.1 Główne zastosowania wskaźników

3. Instrukcje warunkowe

Zmienne, stałe i operatory

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 16 kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 27

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Wstęp do informatyki- wykład 6

Programowanie komputerowe. Zajęcia 4

JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM. Wykład 6

Programowanie komputerowe. Zajęcia 1

Operatory w C++ Operatory arytmetyczne. Operatory relacyjne (porównania) Operatory logiczne. + dodawanie - odejmowanie * mnożenie / dzielenie % modulo

Lab 9 Podstawy Programowania

Informacje wstępne #include <nazwa> - derektywa procesora umożliwiająca włączenie do programu pliku o podanej nazwie. Typy danych: char, signed char

Wskaźniki i dynamiczna alokacja pamięci. Spotkanie 4. Wskaźniki. Dynamiczna alokacja pamięci. Przykłady

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

Podstawy programowania w języku C

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

Operatory AND, OR, NOT, XOR Opracował: Andrzej Nowak Bibliografia:

1 Podstawy c++ w pigułce.

Techniki Programowania wskaźniki

Wstęp do wskaźników w języku ANSI C

Operatory cd. Relacyjne: ==!= < > <= >= bool b; int i =10, j =20; dzielenie całkowitych wynik jest całkowity! Łączenie tekstu: + string s = "Ala ma ";

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

ZASADY PROGRAMOWANIA KOMPUTERÓW

C++ wprowadzanie zmiennych

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

1 Wskaźniki i zmienne dynamiczne, instrukcja przed zajęciami

Podstawy informatyki. Elektrotechnika I rok. Język C++ Operacje na danych - wskaźniki Instrukcja do ćwiczenia

Informatyka I. Typy danych. Operacje arytmetyczne. Konwersje typów. Zmienne. Wczytywanie danych z klawiatury. dr hab. inż. Andrzej Czerepicki

Wskaźniki. nie są konieczne, ale dają językowi siłę i elastyczność są języki w których nie używa się wskaźników typ wskaźnikowy typ pochodny:

I - Microsoft Visual Studio C++

1. Operacje logiczne A B A OR B

Podstawowe elementy proceduralne w C++ Program i wyjście. Zmienne i arytmetyka. Wskaźniki i tablice. Testy i pętle. Funkcje.

MATERIAŁY DO ZAJĘĆ II

Samodzielnie wykonaj następujące operacje: 13 / 2 = 30 / 5 = 73 / 15 = 15 / 23 = 13 % 2 = 30 % 5 = 73 % 15 = 15 % 23 =

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Tablice (jedno i wielowymiarowe), łańcuchy znaków

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Operacje wykonywane są na operandach (argumentach operatorów). Przy operacji dodawania: argumentami operatora dodawania + są dwa operandy 2 i 5.

Program 14. #include <iostream> #include <ctime> using namespace std;

Wstęp do programowania

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

Arytmetyka liczb binarnych

1 Podstawy c++ w pigułce.

Wstęp do programowania

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Struktura pliku projektu Console Application

KURS C/C++ WYKŁAD 7. struct Punkt { int x, y; int kolor; };

DYNAMICZNE PRZYDZIELANIE PAMIECI

KURS C/C++ WYKŁAD 6. Wskaźniki

Wstęp do informatyki- wykład 4 Deklaracja zmiennych Typy

Wstęp do informatyki- wykład 12 Funkcje (przekazywanie parametrów przez wartość i zmienną)

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Wstęp do informatyki- wykład 7

dr inż. Jarosław Forenc

Programowanie strukturalne. dr inż. Tadeusz Jeleniewski

Ok. Rozbijmy to na czynniki pierwsze, pomijając fragmenty, które już znamy:

Podstawy i języki programowania

Typ użyty w deklaracji zmiennej decyduje o rodzaju informacji, a nazwa zmiennej symbolicznie opisuje wartość.

Wstęp do informatyki- wykład 8 Pętla while, do while,for -pętla w pętli- przykłady Operator rzutowania Manipulatory

Języki i metodyka programowania. Typy, operatory, wyrażenia. Wejście i wyjście.

( wykł. dr Marek Piasecki )

> C++ wskaźniki. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki 26 kwietnia 2017

Wstęp do informatyki- wykład 2

Znaki w tym systemie odpowiadają następującym liczbom: I=1, V=5, X=10, L=50, C=100, D=500, M=1000

Podstawy programowania. 1. Operacje arytmetyczne Operacja arytmetyczna jest opisywana za pomocą znaku operacji i jednego lub dwóch wyrażeń.

Wstęp do informatyki- wykład 1 Systemy liczbowe

4. Funkcje. Przykłady

Programowanie w C++ Wykład 5. Katarzyna Grzelak. 26 marca kwietnia K.Grzelak (Wykład 1) Programowanie w C++ 1 / 40

> C++ dynamiczna alokacja/rezerwacja/przydział pamięci. Dane: Iwona Polak. Uniwersytet Śląski Instytut Informatyki

dr inż. Jarosław Forenc

utworz tworzącą w pamięci dynamicznej tablicę dwuwymiarową liczb rzeczywistych, a następnie zerującą jej wszystkie elementy,

Języki i metodyka programowania. Wprowadzenie do języka C

Programowanie Komputerów

Funkcje. Deklaracja funkcji. Definicja funkcji. Wykorzystanie funkcji w programie.

Wiadomości wstępne Środowisko programistyczne Najważniejsze różnice C/C++ vs Java

Podstawy algorytmiki i programowania - wykład 2 Tablice dwuwymiarowe cd Funkcje rekurencyjne

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

ARCHITEKTURA SYSTEMÓW KOMPUTEROWYCH

Podstawy języka C++ Maciej Trzebiński. Praktyki studenckie na LHC IFJ PAN. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. M. Trzebiński C++ 1/16

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Programowanie komputerowe. Zajęcia 2

Wykład 2 Składnia języka C# (cz. 1)

Transkrypt:

Operatory Dorota Pylak

Operatory- podstawowe pojęcia 2 Operatorami są pojawiające się w tekście programu niealfanumeryczne leksemy (znak dodawania, znak gwiazdki, znak procentu, itd.) które są interpretowane jako żądanie wywołania odpowiednich funkcji operujących na danych określonych przez wyrażenia sąsiadujące z danym operatorem są to argumenty (lub operandy) tego operatora. Operatory generalnie dzielą się na jedno- i dwuargumentowe. Zapis operatorów dwuargumentowych jest infiksowy, czyli operator stawiany jest pomiędzy swoimi argumentami. Z kolei zapis operatorów jednoargumentowych, z dwoma wyjątkami, jest prefiksowy (przedrostkowy), czyli operator stawiamy przed argumentem. Istnieje w C/C++ również jeden operator trzyargumentowy: (operator warunkowy? :). Priorytety i wiązanie Z samego zapisu infiksowego nie wynika jeszcze kolejność wykonania działań w złożonych wyrażeniach. Np. w wyrażeniu a + b / c zmienna b może być traktowana jako prawy argument operatora dodawania albo lewy argument operatora dzielenia. Aby uniknąć tego rodzaju wieloznaczności, wprowadzono pojęcie priorytetu operatorów.

Priorytet operatorów 3 Operatory zostały podzielone względem priorytetów się na szereg grup, w ramach których priorytety są jednakowe. W sytuacjach, jak opisana powyżej, najpierw zostanie wykonana operacja opisywana przez ten z tych dwóch operatorów który ma wyższy priorytet. Jeśli natomiast oba mają ten sam priorytet, kolejność wykonywania operacji będzie określona ich wiązaniem: od lewej do prawej dla operatorów o wiązaniu lewym i od prawej do lewej dla operatorów o wiązaniu prawym. Aby ta reguła nie prowadziła do sprzeczności, operatory o takim samym priorytecie mają taki sam kierunek wiązania. A zatem w wyrażeniu a + b / c najpierw zostanie wykonane dzielenie, gdyż ma wyższy priorytet od dodawania. Ale w wyrażeniu a + b - c najpierw zostanie wykonane dodawanie, gdyż ma ten sam priorytet co odejmowanie, a oba operatory mają wiązanie lewe. Dla operatora przypisania wiązanie jest prawe. Tak więc w wyrażeniu a=b=c przypisanie b=c wykonane zostanie najpierw, a jego wynik (wartość b po tej operacji) przypisany będzie do zmiennej a.

Przegląd operatorów Przedstawimy teraz w formie tabelarycznej wszystkie operatory języka C++, omówimy tylko wybrane. Będziemy korzystać z następujących oznaczeń: klasa - nazwa klasy obiekt - obiekt klasy sklad - składowa klasy lub przestrzeni nazw wsk - wskaźnik wyr - wyrażenie lnaz - l-nazwa (czyli wyrażenie, które może wystąpić po lewej stronie przypisania) pnazw - nazwa przestrzeni nazw typ - nazwa typu nazw - nazwa Operatory podzielone są na 17 grup - każda grupa odpowiada operatorom o tym samym priorytecie. Grupy wymienione są w kolejności od grupy operatorów o priorytecie najwyższym, w dół według malejącego priorytetu. 4

Operatory zasięgu :: 5 Priorytet 17 1 zasięg klasy klasa::sklad 2 zasięg przestrzeni nazw pnazw::sklad 3 zasięg globalny ::nazw Operator zasięgu globalnego '::' Zmienne globalne mogą być przesłonięte, jeśli wewnątrz funkcji (lub bloku) zadeklarujemy inną zmienną o tej samej nazwie (choć niekoniecznie tym samym typie). Wówczas nazwa tej zmiennej w ciele funkcji odnosi się do zmiennej lokalnej. Zmienna globalna istnieje, ale jest w zakresie funkcji (bloku) niewidoczna. Nie znaczy to, że nie mamy do niej dostępu. Do przesłoniętej zmiennej globalnej możemy odwołać się poprzez operator zasięgu ' ::' ( czterokropek ). Jeśli na przykład przesłonięta została zmienna o nazwie k, to do globalnej zmiennej o tej nazwie odwołać się można poprzez nazwę kwalifikowaną ::k. Operatory zasięgu klasy (pozycja pierwsza w tabeli) będzie omówiony na wykładzie o klasach.

Operatory zasięgu :: 6 #include <iostream> using namespace std; int k=0; //zmienna globalna - jest zatem widoczna w funkcji main. int main() { //na razie k i ::k odnoszą się do tej samej zmiennej globalnej. cout << " k: " << k << endl; //0 odwołanie do zmiennej globalnej cout << "::k: " << ::k << endl; //0 odwołanie do zmiennej globalnej int k = 10;//zmienna lokalna przesłania w f-cji zmienna globalna cout << " k: " << k << endl;//10 odniesienie do zmiennej lokalnej cout << "::k: " << ::k << endl;//0 odniesienie do zmiennej globalnej ::k = 1; //możemy zmieniać wartość zmiennej globalnej cout << " k: " << k << endl; //10 cout << "::k: " << ::k << endl; //1 } { //blok wewnętrzny int k = 77;//zmienna k przesłania w bloku lokalną dla main zmienną k cout << "W bloku:" << endl; cout << " k: " << k << endl;//77 mamy dostęp do k z tego bloku cout << "::k: " << ::k << endl;//1 oraz k globalnego }

Priorytet 16 7 Priorytet 16 4 wybór składowej obiekt.sklad 5 wybór składowej wsk->sklad 6 wybór elementu przez indeks wsk[wyr] 7 wywołanie funkcji wyr(lista_wyr) 8 konstrukcja wartości typ(lista_wyr) 9 postdekrementacja lwart-- 10 postinkrementacja lwart++ 11 statyczna identyfikacja typu typeid(typ) 12 dynamiczna identyfikacja typu typeid(wyr) 13 statyczna konwersja static_cast<typ>(wyr) 14 dynamiczna konwersja dynamic_cast<typ>(wyr) 15 konwersja wymuszona reinterpret_cast<typ>(wyr) 16 konwersja uzmienniająca const_cast<typ>(wyr)

8 Grupa operatorów o priorytecie 16 4-5: Pierwsze dwa '.', ' ->'(operator kropka i strzałka ) dotyczą struktur i klas. 6: Operator ' []' wyboru elementu (indeksowania) tablicy. 7: Operator wywołania funkcji oznaczamy, jak zwykle, nawiasami okrągłymi '()': func(k, m, 5) Zatem podanie nazwy funkcji z nawiasami okrągłymi powoduje wywołanie funkcji (jeśli pojawia się w instrukcji wykonywalnej, a nie w definicji lub deklaracji). 8: Operator konstrukcji wartości dotyczy klas. Omówimy go później. Tu tylko wspomnijmy, że w C+ + można konstruować obiekty typów prostych (int, double,...) tak jakby były one obiektami jakiejś klasy. Zatem int(3) kreuje zmienną typu int i inicjuje ją wartością 3. 9-10: Operator postdekrementacji i postinkrementacji zmniejszają (zwiększają) wartość swojego argumentu, który stoi po ich lewej stronie o jeden. Czynią to jednak po obliczeniu wartości wyrażenia, w skład którego wchodzą. Tak więc po int a = 1; int b = a++; wartość a wynosi 2, ale wartość b wynosi 1, bo w trakcie opracowywania drugiej instrukcji a miało wciąż wartość 1; zwiększenie a nastąpi dopiero po zakończeniu wykonywania instrukcji przypisania. Argumentem postinkrementacji i postdekrementacji musi być wyrażenie, które może wystąpić po lewej stronie przypisania, dlatego np. (x+y)++ //BLAD!!! nie ma sensu i jest błędne, gdyż wyrażenie (x+y) nie jest wyrażeniem identyfikującym daną, która ma określony, dostępny w programie adres w pamięci.

Grupa operatorów o priorytecie 16 9 11-12: Operator identyfikacji typu typeid pozwala na uzyskanie identyfikatora typu podczas kompilacji, a więc statycznie, jak również identyfikatora typu obiektuw czasie wykonania programu, a więc dynamicznie (RTTI; run-time type identification). 13-16: Operatory konwersji (rzutowania) pozwalają na, w miarę bezpieczną, konwersję wartości jednego typu na wartość innego typu. Ponieważ stosowanie konwersji często, choć nie zawsze, świadczy o złej konstrukcji programu i stwarza okazję do użycia błędnych lub zależnych od implementacji konstrukcji programistycznych, nadano tym operatorom celowo tak długą i niewygodną do pisania formę.

Priorytet 15 10 17 pobranie rozmiaru obiektu sizeof wyr 18 pobranie rozmiaru typu sizeof(typ) 19 predekrementacja --lwart 20 preinkrementacja ++lwart 21 negacja bitowa wyr 22 negacja logiczna!wyr 23 minus jednoargumentowy -wyr 24 plus jednoargumentowy +wyr 25 wyłuskanie adresu &lwart 26 dereferencja *wyr 27 przydział pamięci na obiekt new typ 28 przydział pam. na obiekt z inicjowaniem new typ(lista_wyr) 29 lokalizujący przydział pamięci new (lista_wyr) typ 30 jak wyżej, z inicjowaniem new (lista_wyr) typ(lista_wyr)

Priorytet 15 31 zwolnienie pamięci delete wsk 32 zwolnienie pamięci tablicowej delete [] wsk 33 rzutowanie (konwersja) (typ)wyr 17-18: Pobieranie rozmiaru za pomocą operatora sizeof. Rezultat jest typu size_t (który jest tożsamy z pewnym typem całkowitym bez znaku, np. unsigned long). Operator ten jest jednoargumentowy. Argumentem może być nazwa typu (w nawiasie okrągłym) lub wyrażenie (nawias jest wtedy niekonieczny). 19-20: Operatory predekrementacji i preinkrementacji działają podobnie do postdekrementacji i postinkrementacji. Są jednak ważne różnice: operatory te zmniejszają (zwiększają) swój argument przed jego użyciem do obliczenia wartości wyrażenia, w skład którego wchodzą. Tak więc po int a = 1; int b = ++a; wartość a wynosi 2, ale wartość b wynosi również 2, bo w trakcie opracowywania drugiej instrukcji zmienna a została zwiększona jeszcze przed wykonaniem przypisania. Wynikiem działania operatora preinkrementacji lub predekrementacji jest l-nazwa czyli wyrażenie, które może wystąpić po lewej stronie przypisania, natomiast dla operatorów postinkrementacji lub postdekrementacji tak nie jest. Dlatego int k = 5; int m = (++k)--; jest prawidłowe, natomiast m = ++k--; // ZLE!!!. 11

Priorytet 15 12 21-22: Na pozycjach 21 i 22 występują operatory negacji: bitowej i logicznej. Omówimy je poniżej razem z innymi operatorami logicznymi i bitowymi. 23-26: Jednoargumentowy operator ' +' jest właściwie operatorem identycznościowym, czyli takim, który nic nie robi (ang. no-op). Istnieje tylko dla wygody, aby wyrażenia typu ' k = +5' miały sens. Operatory wyłuskania adresu & i dereferencji * będą omówione na II roku. 27-32: Operatory new i delete z pozycji 27-32 służą do dynamicznego alokowania i zwalniania pamięci: będą omówione na II roku. 33: Ostatni operator oznaczany parą nawiasów okrągłych, to operator rzutowania. Jest on operatorem jednoargumentowym: wynikiem działania tego operatora na wartość pewnego typu jest odpowiadająca jej wartość innego typu - tego wymienionego w nawiasie. Na przykład w drugiej instrukcji fragmentu double x = 7; int k = (int)x; rzutowanie wartości zmiennej x jest wskazane, gdyż wartość ta, jako wartość szerszego typu, może być wpisana do zmiennej typu węższego, jakim jest typ int, tylko ze stratą informacji (precyzji).

Priorytet 14, 13 i 12 Priorytet 14 34 wybór składowej przez wskaźnik wsk->*wsk 35 wybór składowej przez wskaźnik wsk.*wsk Priorytet 13 36 mnożenie wyr * wyr 37 dzielenie wyr / wyr 38 reszta z dzielenia wyr % wyr Priorytet 12 39 dodawanie wyr + wyr 40 odejmowanie wyr - wyr 34-36: Dwa operatory wyboru składowej, rzadko stosowane, które omówione będą wykładzie na temat klas w C++. 36-40: Operatory arytmetyczne mają oczywiste znaczenie. Operator reszty ' %' wymaga argumentów typu całkowitego. Wiemy, że dzielenie liczb typu całkowitego daje w wyniku liczbę całkowitą, czyli ewentualna część ułamkowa jest obcinana. Jeśli wynik jest dodatni, to dokładna wartość ilorazu jest obcinana w dół, czyli w kierunku zera. Jeśli wynik jest ujemny, to w większości współczesnych implementacji obcięcie jest również w kierunku zera. Dla operatora reszty spełniona jest zawsze zasada a = (a/b)*b + a%b dla b różnego od zera. 13

Priorytet 11, 10 i 9 Priorytet 11 41 przesunięcie bitowe w lewo wyr << wyr 42 przesunięcie bitowe w prawo wyr >> wyr Priorytet 10 43 mniejsze od wyr < wyr 44 mniejsze lub równe wyr <= wyr 45 większe od wyr > wyr 46 większe lub równe wyr >= wyr Priorytet 9 47 równe wyr = = wyr 48 nierówne wyr! = wyr 43-48: Operatory relacyjne. Wyrażenie ' a == b' ma wartość logiczną odpowiadającą na pytanie czy wartość a jest równa wartości b. Wyrażenie ' a!= b' ma wartość logiczną odpowiadającą na pytanie czy wartość a jest różna od wartości b. Argumentami mogą być dwa skalary lub dwa adresy. Wynikiem operacji jest wartość logiczna true lub false. Wartości logiczne reprezentowane są w zasadzie przez wartości całkowite: wartość 0 jest równoważna false, a dowolna wartość niezerowa true. Obowiązuje to również dla wartości wskaźnikowych: wartość pusta (NULL) jest interpretowana jako false, a każda inna jako true. 14

Operatory bitowe - negacja bitowa Priorytet 8 49 koniunkcja bitowa wyr & wyr Priorytet 7 50 bitowa różnica symetryczna wyr ^ wyr Priorytet 6 51 alternatywa bitowa wyr wyr 21, 41-42, 49-51: Operatory bitowe występują na pozycjach 21 (~ negacja bitowa), 41 i 42 (<<, >> przesunięcie bitowe) i 49-51 (& koniunkcja, ^ różnica symetryczna i alternatywa bitowa). Argumentami muszą być wartości całkowite, wynik też jest typu całkowitego. Operatory bitowe nie interesują się wartością liczbową argumentów, ale ich reprezentacją bitową. Przypomnijmy, że zwyczajowo numeruje się bity reprezentujące wartości zmiennych, poczynając od zera, od bitu najmniej znaczącego (odpowiadającego współczynnikowi przy zerowej potędze dwójki) do bitu najbardziej znaczącego. Operator bitowej negacji (' ') działając na wartość całkowitą zwraca nową wartość, w której wszystkie bity ulegają odwróceniu: tam, gdzie w argumencie był bit ustawiony (czyli miał umowną wartość 1), w wartości wynikowej będzie on nieustawiony (co odpowiada umownej wartości 0) - jak na rysunku, gdzie zilustrowane jest działanie operatora negacji bitowej dla jednobajtowej wartości typu char:. 15

Operatory bitowe - alternatywa bitowa 16 Alternatywa bitowa (' ') jest operatorem dwuargumentowym: dla kolejnych pozycji sprawdzane są pojedyncze bity w obu argumentach i obliczana ich suma logiczna: w wyniku bit na odpowiadającej pozycji jest jedynką (bit ustawiony), jeśli w którymkolwiek argumencie bit na tej pozycji był ustawiony, a zero, gdy w obu argumentach na tej pozycji również występowało zero (jak na rysunku poniżej). Alternatywa bitowa (tzw. ORowanie) jest często stosowana do ustawiania najrozmaitszych opcji. Na przykład w C++, otwarte do czytania lub pisania pliki mają szereg trybów, którym odpowiadają pewne stałe całkowite, np. ios::in, ios::out, zdefiniowane w klasie ios (dlatego odwołujemy się do nich poprzez operator zakresu klasy - czterokropek). Stałe te są pełnymi potęgami dwójki, a więc w ich reprezentacji bitowej występuje tylko jedna jedynka na odpowiedniej pozycji - dla ios::out na pozycji pierwszej, dla ios::trunc na pozycji czwartej (licząc, jak zwykle, od zera) itd. Zatem stałą określającą tryb otwartego pliku jako pliku jednocześnie do pisania i do czytania będzie ios::in ios::out i będzie zawierać jedynki na pozycjach zerowej i pierwszej (konkretne pozycje mogą zależeć od implementacji - należy zawsze odwoływać się do tych stałych poprzez ich nazwy).

Operatory bitowe - koniunkcja bitowa 17 Koniunkcja bitowa ('&') jest też operatorem dwuargumentowym: dla kolejnych pozycji sprawdzane są pojedyncze bity w obu argumentach i obliczany ich iloczyn logiczny: w wyniku bit na odpowiadającej pozycji jest jedynką (bit ustawiony), jeśli w obu argumentach bit na tej pozycji był ustawiony, a zero, gdy w którymkolwiek z argumentów na tej pozycji występowało zero: Koniunkcja bitowa (tzw. ANDowanie) jest często stosowana do tzw. maskowania. Wspomnieliśmy, że stała określająca tryb pliku ma na pozycji trzeciej (licząc od zera) jedynkę, jeśli ustawiony plik został otwarty w trybie in, a zero, jeśli nie. Jeśli stała określająca tryb nazywa się tryb, to maskowanie jej ze stałą 8 (= 23) odpowie na pytanie, czy bit in jest czy nie jest ustawiony. Reprezentacja 8 składa się z samych zer, z wyjątkiem pozycji trzeciej (czwarty bit od prawej), na której bit jest ustawiony. Zatem obliczając koniunkcję dostaniemy na wszystkich innych pozycjach na pewno zero, na pozycji czwartej zaś jedynkę, jeśli w tryb ten bit był ustawiony, a zero, jeśli nie był. Zatem wartość wyrażenia tryb & 8 będzie niezerowa wtedy i tylko wtedy, jeśli bit in był w zmiennej tryb ustawiony, niezależnie od stanu innych bitów w tej zmiennej.

18 Operatory bitowe - bitowa różnica symetryczna Operator bitowej różnicy symetrycznej ('^') jest też operatorem dwuargumentowym: dla kolejnych pozycji sprawdzane są pojedyncze bity w obu argumentach i obliczana jest ich różnica symetryczna: wynikowy bit na odpowiadającej pozycji jest jedynką (bit ustawiony), jeśli w obu argumentach bity na tej pozycji były różne, a zerem jeśli w obu argumentach na tej pozycji występowały bity takie same - dwa zera albo dwie jedynki. Różnica symetryczna (obliczanie jej nazywane bywa XORowaniem) ma ciekawą i użyteczną własność, wynikającą z natępującej tabelki logicznej dla tego działania: b m b^m (b^m)^m 1 1 0 1 1 0 1 1 0 1 1 0 0 0 0 0 z której wynika, że dwukrotne XORowanie dowolnego bitu b z dowolną maską m przywraca pierwotną wartość tego bitu - w tabeli kolumna pierwsza i ostatnia są takie same. Ta własność XORowania jest wykorzystywana między innymi w grafice komputerowej.

19 Operatory bitowe - przesunięcia bitowe Przesunięcia bitowe (' «' i '»') są operatorami dwuargumentowymi: lewy argument jest tu pewną wartością całkowitą na bitach której operacja jest przeprowadzana, prawy argument, również całkowity, określa wielkość przesunięcia. Wyobraźmy sobie, że lewy argument w ma układ bitów jak w górnej części rysunku: Przesunięcie w tej zmiennej bitów w lewo o dwa, ' w = w «2', odpowiada przesunięciu wszystkich bitów o dwie pozycje w lewo (w kierunku pozycji bardziej znaczących). Bity wychodzące z lewej strony są tracone bezpowrotnie. Z prawej strony wchodzą bity zerowe. Tak więc po wykonaniu tej instrukcji reprezentacja zmiennej w będzie taka jak w środkowej części rysunku. Analogicznie, po przesunięciu teraz bitów w prawo o trzy pozycje (' w = w» 3') otrzymamy reprezentację zmiennej w jak w dolnej części rysunku (pod pewnymi warunkami - patrz niżej). O ile przesuwanie w lewo jest zawsze dobrze określone według wspomnianych zasad, rzecz jest bardziej skomplikowana przy przesuwaniu w prawo. Wychodzące z prawej strony bity są tracone, tak jak te z lewej strony przy przesuwaniu w lewo. Ale nie jest jasne, jakie bity wchodzą z lewej strony przy przesuwaniu w prawo.

Operatory bitowe - przesunięcia bitowe 20 W większości implementacji przyjęto następującą konwencję: jeśli typem wartości lewego argumentu jest typ bez znaku (unsigned), to przy przesuwaniu w prawo wchodzą z lewej strony bity zerowe; jeśli natomiast typem wartości lewego argumentu jest typ ze znakiem (signed), to przy przesuwaniu w prawo reprodukowany jest z lewej strony bit znaku, czyli skrajny lewy bit - jeśli było to zero, to zero, jeśli była to jedynka, to jedynka. Prawy argument operatora przesunięcia, wskazujący na wielkość tego przesunięcia, zawsze powinien być nieujemny i mniejszy od rozmiaru w bitach wartości, na której dokonujemy przesunięcia. Co się dzieje, jeśli te warunki nie są spełnione w C/C++ może zależeć od implementacji i wobec tego lepiej takich konstrukcji unikać. Przykład: Operacje na bitach funkcja wypisująca bitowe (2-kowe) reprezentacje argumentu typu char:

Operatory bitowe - przykład void bitschar(char k) { int bits = 8*sizeof(k); //wyznaczamy rozmiar w bitach wartości argumentu k, tu 8 /* tworzymy maskę mask typu unsigned char. Chodzi o to, aby długość maski była taka jak długość k, ale by była to zmienna na pewno bez znaku w ten sposób przy przesuwaniu w prawo będą z lewej strony wchodzić zera. Maskę inicjujemy wartością ' 1 «7' (bo bits wynosi 8). Reprezentacja jedynki, to 00000001. Przesuwając ten układ bitów o 7 w lewo otrzymamy liczbę 10000000. Robimy to po to, by pętla drukująca, która teraz następuje, przebiegała przez kolejne bity liczby k od lewej do prawej. */ unsigned char mask = 1<<(bits-1); /* w pętli obliczamy koniunkcję bitową k z maską mask. Ponieważ maska ma tylko jeden bit ustawiony, w ten sposób sprawdzamy, czy odpowiedni bit jest też ustawiony w k. Jeśli tak, wartością selekcji ' (k & mask? 1 : 0)' będzie jedynka, która zostanie wypisana na ekranie. Jeśli w k odpowiedni bit nie jest ustawiony, wydrukowane zostanie zero. */ for (int i = 0; i < bits; i++) { } cout << (k & mask? 1 : 0); //wypisujemy wartości kolejnych bitów od lewej mask >>= 1; //przesuwamy bity w masce o jeden w prawo, z lewej wchodzą 0 cout << endl; 21

22 Operatory logiczne Priorytet 5 52 koniunkcja (iloczyn) logiczna wyr && wyr Priorytet 4 53 alternatywa (suma) logiczna wyr wyr 22, 52-53: Operatory logiczne występują na pozycjach 22 (! negacja logiczna; ten operator jest oczywiście jednoargumentowy), 52 i 53 (&& koniunkcja i alternatywa logiczna). Argumenty mogą mieć wartość całkowitą; wówczas wartości zostaną zinterpretowane według normalnej zasady 0 lub NULL false, niezero, nie- NULL true. Obliczona wartość jest typu logicznego: alternatywa (suma logiczna) daje wynik true wtedy i tylko wtedy gdy choć jeden z argumentów ma wartość true, natomiast koniunkcja (iloczyn logiczny) ma wartość true tylko jeśli oba argumenty są true. Koniunkcja i alternatywa są skrótowe. Oznacza to, że prawy argument nie jest w ogóle obliczany, jeśli po obliczeniu lewego wynik jest już przesądzony. Tak więc dla koniunkcji (' &&') prawy argument nie będzie w ogóle obliczany, jeśli lewy argument okazał się równy false - całe wyrażenie musi bowiem wtedy mieć wartość false niezależnie od wartości prawego argumentu; dla alternatywy prawy argument nie będzie obliczany, jeśli lewy okazał się true - całe wyrażenie musi bowiem mieć wtedy wartość true niezależnie od wartości prawego argumentu.

23 Priorytet 3 i 2 i 1 Priorytet 3 54 selekcja wyr? wyr : wyr Priorytet 2 55 przypisanie lwart = wyr 56 dodawanie z przypisaniem lwart += wyr 57 odejmowanie z przypisaniem lwart -= wyr 58 mnożenie z przypisaniem lwart *= wyr 59 dzielenie z przypisaniem lwart /= wyr 60 reszta z przypisaniem lwart %= wyr 61 przesunięcie w lewo z przypisaniem lwart <<= wyr 62 przesunięcie w prawo z przypisaniem lwart >>= wyr 63 iloczyn bitowy z przypisaniem lwart &= wyr 64 alternatywa bitowa z przypisaniem lwart = wyr 65 różnica bitowa z przypisaniem lwart ^= wyr Priorytet 1 66 zgłoszenie wyjątku throw wyr 67 operator przecinkowy wyr, wyr

Operatory przypisania 24 55-65: Na pozycjach 55-65 wymienione są operatory zwykłego przypisania (' =') oraz złożone operatory przypisania. 55: Lewa strona przypisania musi być l-nazwą. Tak więc: double x; x + 2 = 7; // NIE POPRAWNE Wykonanie przypisania polega na obliczeniu wartości prawej strony i umieszczeniu wyniku pod adresem l- wartości występującej po stronie lewej. Zatem: prawa strona mówi co policzyć, lewa gdzie zapisać wynik. Wartością i typem całego wyrażenia przypisania jest wartość i typ lewej strony po wykonaniu przypisania. Całe przypisanie jest l-nazwą. Dzięki temu, że wartością całego wyrażenia z przypisaniem jest wartość lewej strony po jego wykonaniu, przypisania można stosować kaskadowo: int k = 7, j, m; int n = m = j = k; ponieważ wiązanie operatora przypisania jest od prawej, wartością wyrażenia ' m = j = k', równoważnego ' m = (j = k)', jest wartość m po przypisaniu (czyli w naszym przypadku 7). Ta wartość zostanie użyta do zainicjowania definiowanej zmiennej n. Efektem ubocznym będzie nadanie wartości również zmiennym m i j. Instrukcja byłaby błędna, gdyby któraś ze zmiennych m, j, k nie była utworzona wcześniej.

Operatory przypisania 56-65: Złożone operatory przypisania (pozycje 57-65) pozwalają na prostszy zapis niektórych przypisań: tych, w których ta sama l-nazwa występuje po lewej i prawej stronie przypisania. Zamiast instrukcji a = a @ b; gdzie symbol '@ ' oznacza któryś z operatorów :+, -, *, /, %,&,,>>,<<,^ można użyć instrukcji a @= b; Drobna różnica, najczęściej bez znaczenia, pomiędzy tymi instrukcjami polega na tym, że w drugiej z nich wartość a jest obliczana jednokrotnie, a w pierwszej dwukrotnie. Zwykle druga z tych form, ' a @= b', jest efektywniejsza. 54: Operator selekcji jest jedynym operatorem trzyargumentowym. Jego składnia: b? w1 : w2 Najpierw obliczana jest wartość wyrażenia b i ewentualnie konwertowana do typu bool. Jeśli jest to true, obliczane jest wyrażenie w1, a wyrażenie w2 jest ignorowane. Wartością całego wyrażenia jest wartość w1. Jeśli jest to false, obliczane jest wyrażenie w2, a wyrażenie w1 jest ignorowane. Wartością całego wyrażenia jest wtedy wartość w2. Przykład: funkcja max zwracającej większą z wartości swych argumentów: int maxim(int a, int b) { } return a > b? a : b; 25

26 Operator przecinkowy 67: Operator przecinkowy jest dwuargumentowy: po dwóch stronach przecinka dwa wyrażenia wyr1, wyr2 Działanie jego polega na: obliczeniu wyrażenia wyr1 i zignorowaniu resultatu; obliczeniu wyrażenia wyr2; jego wartość staje się wartością całego wyrażenia. Często operator przecinkowy stosuje się w części inicjalizacyjnej lub inkrementującej nagłówka pętli for: #include <iostream> using namespace std; //f-cja drukujaca zapis binarny liczby typu int void printintbinary(int k) { // typ int jest czterobajtowy, dlatego maska 1 i 31 zer unsigned int mask = 1<<31; // przesuwanie maski w części inkrementującej nagłówka pętli, operator, for (int i = 0; i < 32; i++, mask >>= 1) { cout << (k & mask? 1 : 0); if (i%8 == 7) cout << " "; //spacja po każdej grupie ośmiu bajtów } cout << endl; }

Operator przecinkowy i operatory bitowe - przykład 27 int main() { } unsigned int k = 255<<24 153<<16 255<<8 255; /*Wyrażenie ' 255 << 24' daje liczbę z samymi jedynkami w najstarszym bajcie (255 to osiem jedynek, następnie przesunięte o 24 pozycje w lewo). Wyrażenie ' 153 << 16' to układ bajtów 10011001 przesunięty w lewo o 16 pozycji, czyli do bajtu trzeciego od lewej. Z kolei ' 255 << 8' daje osiem jedynek w bajcie drugim, a samo 255 - osiem jedynek w bajcie najmłodszym. Suma (alternatywa) bitowa składa wszystkie te bajty: otrzymujemy: */ cout << "k przed: "; bitsint(k); //k przed: 11111111 10011001 11111111 11111111 (k <<= 8) >>= 24; /* Wyrażenie '(k<<= 8)' powoduje przesunięcie w zmiennej k wszystkich bitów w lewo o osiem pozycji. Zatem zawartość bajtu najstarszego zostaje zgubiona, bajt 10011001 przechodzi na jego pozycję, a kolejne dwa, czyli pierwszy i drugi, stają się drugim i trzecim (od lewej). Bajt najmłodszy wypełniany jest zerami. Następnie przesuwamy zawartość zmiennej k o 24 pozycje w prawo. Ponieważ zmienna k jest bez znaku, z lewej strony wchodzą same zera, a 24 najmłodsze bity wychodzą z prawej strony. cout << "k po: "; bitsint(k); //k po: 00000000 00000000 00000000 10011001

28 Przykład 3 wersje funkcji printcharbinary //zaczynamy od 2^7 i przesuwamy 1 w prawo //uzywamy unsigned char to reprodukowane jest 0 z przodu void printcharbinary1(unsigned char x){ for(unsigned char i=128; i; i>>=1) //2^7=128 =(1000 0000) cout<< ((x & i)?'1':'0'); } //przesuwamy 1 o 7,6,...,0 miejsc w lewo void printbinary2(unsigned char x){ for(int i=7; i>=0; i--) if (x & (1<<i)) cout<<'1'; else cout<<'0'; } //przesuwamy rozpatrywana liczbe o 7,6, 0 bitow w lewo i &1 void printbinary2(unsigned char x){ for(int i=7; i>=0; i--) cout<< ((x >> i) & 1); }