Brother HL-5595DNH jest technicznym podręcznikiem referencyjnym, który zawiera wszystkie potrzebne informacje o drukarce Brother HL-5595DNH. Podręcznik zawiera szczegółowe informacje o zestawie instalacyjnym, funkcjach drukowania, bezpieczeństwie, ustawieniach, instrukcjach konserwacji i konfiguracji. Podręcznik zawiera również informacje oprzydatnych funkcjach drukowania, które ułatwiają pracę, takich jak wstępne wydruki, wydruki w formacie wielostronicowym, wydruki wielokrotne, wydruki paginacji i rekomendowane ustawienia jakości wydruku. Podręcznik zawiera również informacje o dostępnych narzędziach do zarządzania, w tym instrukcje dotyczące instalowania, wykorzystywania i aktualizowania oprogramowania oraz sterowników drukarki.
Ostatnia aktualizacja: Techniczny podręcznik referencyjny, ogólne Brother Hl 5595dnh
Pakiet składa się ze scenariusza 45-minutowych zajęć i filmu. W trakcie zajęć uczniowie, pracując w grupach, utrwalają umiejętności stosowania poprawnych form odmiennych części mowy i wyrazów w związkach składniowych. Film to animacja, która pod pretekstem przesłuchania na komisariacie, w zabawny sposób podkreśla wartość stosowania poprawnych form wyrazów odmiennych, wskazując możliwe nieporozumienia wynikające z nieprzywiązywania wagi do prawidłowej odmiany.
Materiał nie spełnia wymogów WCAG, natomiast może być wykorzystywany jako materiał dydaktyczny.
Aplikacje dostępne w
- 9. 2. 1 Operatory zasięgu
- 9. 2 Grupa operatorów o priorytecie 16
- 9. 3 Grupa operatorów o priorytecie 15
- 9. 4 Grupa operatorów o priorytecie 14
- 9. 5 Operatory arytmetyczne
- 9. 6 Operatory relacyjne i porównania
- 9. 7 Operatory bitowe
- 9. 8 Operatory logiczne
- 9. 9 Operatory przypisania
- 9. 10 Operator warunkowy
- 9. 11 Operator zgłoszenia wyjątku
- 9. 12 Operator przecinkowy
- 9. 13 Alternatywne nazwy operatorów
W tabeli poniżej przedstawiono operatory języka C++.W prawej kolumnie użyte są oznaczenia:
klasa: nazwa klasy | ob: obiekt klasy |
sklad: składowa klasy lub przestrzeni nazw | wsk: wskaźnik |
wyr: wyrażenie | lwar: l-wartość |
pnaz: nazwa przestrzeni nazw | typ: nazwa typu |
naz: nazwa |
9. 1 Operatory zasięgu
Operatory te (priorytet 17 w tabeli), zapisywane są za pomocąsymbolu „czterokropka” ('::'). Operator zasięgu globalnegojuż znamy (patrzrozdział o zasięgu i widoczności deklaracji).Przypomnijmy, że::xjest nazwą globalnej zmiennejxzadeklarowanej poza wszystkimi funkcjami i klasami. Użycie„czterokropka” jest konieczne tylko wtedy, gdy nazwa (w naszymprzypadkux) jest w danym bloku (funkcji) nazwą innejzmiennej, lokalnej, która wobec tego przesłoniła zmienną globalnąo tej samej nazwie.Operatory zasięgu klasy (pozycja pierwsza w tabeli) omówimy wrozdziale o klasach,a przestrzenie nazww rozdziale o przestrzeniach nazw.
9. 2 Grupa operatorów o priorytecie 16Pierwsze dwa (operator „kropka” i „strzałka”, dotyczą strukturi klas, poznamy jew rozdziale o strukturach.Operator '[]' (indeksowania)tablicy już znamyz rozdziału o arytmetyce wskaźników.Operator wywołaniafunkcjioznaczamy nawiasami okrągłymi '()': func(k, m, 5)
Zatem podanie nazwy funkcji z nawiasami okrągłymipowoduje wywołanie funkcji (jeśli pojawia się w instrukcjiwykonywalnej, a nie w definicji lub deklaracji).Ale nie zawsze nazwa funkcji występujez nawiasami. Czasem chcemy odnieść się do funkcji jako takiej, jakoobiektu, a nie powodować jej wywołanie. W takich sytuacjachużywamy nazwy funkcji bez nawiasów — ma ona wtedy interpretacjęwskaźnika do funkcji. Tego rodzaju wskaźniki omówimy wrozdziale o wskaźnikach funkcyjnych.Operator konstrukcji wartości(Typ(lista_wyr))jest nam nieznany. Ponieważ dotyczy klas, omówimy go wrozdziale o tworzeniu obiektów.Tu tylko wspomnijmy, że w C++ można konstruować obiekty typówprostych (int,double,... ) tak jakby były one obiektamijakiejś klasy. Zatemint(3) kreuje zmienną typuinti inicjuje ją wartością 3 — składnia jest wobec tego taka,jak gdybyśmy tworzyli obiekt klasyi wysyłali wartość 3do konstruktora (tak zwanego konstruktora kopiującego).Przyrostkowe operatory zmniejszenia i zwiększenia(postdekrementacji i postinkrementacji)zmniejszają (zwiększają) wartość swojego argumentu (którywyjątkowo stoi po ich lewej stronie) o jeden. Czynią to jednakpo obliczeniu wartości wyrażenia, w skład którego wchodzą.Tak więc po int a = 1;int b = a++;wartośćawynosi 2, ale wartośćbwynosi 1, bo w trakcie opracowywania drugiej instrukcjimiało wciąż wartość 1; zwiększenienastąpi dopieropo zakończeniu wykonywania instrukcji przypisania.Argumentem postinkrementacji i postdekrementacji musi zawsze byćl-wartość; np. (x+y)++nie ma sensu i jest błędne, gdyż wyrażenie(x+y) niejest l-wartością (choć jest p-wartością).Tak więc argument operatorów postdekrementacji i postinkrementacjimusi być l-wartością: ale co z rezultatem działania tego operatorana l-wartość? W Javiewartości uzyskane za pomocą tych operatorów nigdy same niesą l-wartościami. W C/C++ jest trochę inaczej: dla przyrostkowychoperatorów zmniejszenia i zwiększenia wynik nie jestl-wartością, ale dla operatorów przedrostkowych wynik jestl-wartością.Dlatego int k = 5;int m = (++k)--;jest prawidłowe. Wyrażenie w nawiasach jest l-wartością, bo użytyzostał operator preinkrementacji; można było zatem zastosowaćnastępnie operator postdekrementacji. Wynikiem działania tego z koleioperatora nie jest już l-wartość, ale ponieważ użyliśmy jejtylko po prawej stronie przypisania, więc wszystko jest w porządku.Wartośćkbędzie oczywiście wynosić po tymprzypisaniu 5, a wartością zmiennejmbędzie 6.Gdybyśmy nie użyli nawiasów int k = 5;int m = ++k--; // ZLE!!! kod byłby błędny: ponieważ priorytet postdekrementacji jest wyższyniż preinkrementacji, więc najpierw obliczone byłoby wyrażeniek-. Wynik nie byłby l-wartością — patrzrozdział o l-wartościach —więc podziałanie nań operatorem '++' spowodowałoby błąd.Warto pamiętać, że wyrażenie++w nie jestcałkowicie równoważne instrukcjiw=w+1. Ta druga forma jestnormalną instrukcją przypisania, a zatem najpierw zostanie obliczonawartość wyrażenia po prawej stronie, a potem lokalizacjal-wartości po lewej stronie. Zatem jeśliwjest wyrażeniemzłożonym (np. zawiera wywołania funkcji), to wyrażenie to będzieobliczane dwukrotnie. Natomiast podczas opracowywania wyrażenia++w, samobędzie obliczane jednokrotnie.Rzadko ma to jakieś znaczenie, ale czasem zrozumienie tego może nasustrzec przed trudno wykrywalnymi błędami.Operator identyfikacji typutypeidpozwala na uzyskanieidentyfikatora typu podczas kompilacji, a więc statycznie,jak również identyfikatora typu obiektu (ogólniep-wartości) w czasie wykonania programu, a więc dynamicznie(RTTI; run-time typeidentification). Ten temat omówimy bardziej szczegółowo wrozdziale o RTTI,ale jeden przykład zastosowania tego operatora podany jest poniżejw programie sizes. cpp.Operatory konwersji (rzutowania)pozwalają na, w miarę bezpieczną, konwersję wartości jednego typuna wartość innego typu. Ponieważ stosowanie konwersji często,choć nie zawsze, świadczy o złej konstrukcji programu i stwarzaokazję do użycia błędnych lub zależnych od implementacjikonstrukcji programistycznych, nadano tym operatorom celowotak długą i niewygodną do pisania formę. Konwersje rozpatrzymyw rozdziale im poświęconym.9. 3 Grupa operatorów o priorytecie 15Pierwsze trzy z opertatorów tej grupydotyczą pobierania rozmiaru za pomocą operatorasizeof. Reultat jest typusize_t (który jest tożsamy z pewnym typem całkowitymbez znaku, np.unsigned long). Operator ten jest jednoargumentowy.Argumentem może być nazwa typu (w nawiasie okrągłym) lub wyrażenie (nawiasjest wtedy niekonieczny) albo tak zwany pakiet. Rozpatrzmy przykład:
P53:Operator sizeof
1. #include <iostream> 2. #include <typeinfo> 3. using namespace std; 4. 5. typedef int TABINT15[15]; ➊ 6. 7. void siz(TABINT15 t1, TABINT15& t2) { ➋ 8. cout << "G. t1 w siz: " << sizeof t1 << endl; 9. cout << "H. t2 w siz: " << sizeof t2 << endl; 10. } 11. 12. int main() { 13. TABINT15 tab1; ➌ 14. int tab2[15]; ➍ 15. int *tab3 = tab2; ➎ 16. 17. if (typeid(tab1) == typeid(tab2)) 18. cout << "A. Typy tab1 i tab2 takie same" << endl; 19. else 20. Typy tab1 i tab2 nie takie same" << endl; 21. 22. if (typeid(tab2) == typeid(tab3)) 23. cout << "B. Typy tab2 i tab3 takie same" << endl; 24. else 25. Typy tab2 i tab3 nie takie same" << endl; 26. 27. cout << "C. TABINT15: " << sizeof(TABINT15) << endl; ➏ 28. cout << "D. tab1: " << sizeof tab1 << endl; ➐ 29. cout << "E. tab2: " << sizeof(tab2) << endl; ➑ 30. cout << "F. tab3: " << sizeof tab3 << endl; ➒ 31. siz(tab2, tab2); 32. }Wynik tego programu A. Typy tab1 i tab2 takie sameB. Typy tab2 i tab3 nie takie sameC. TABINT15: 60D. tab1: 60E. tab2: 60F. tab3: 8G. t1 w siz: 8H. t2 w siz: 60
ilustruje warte zrozumienia własności C/C++.Na początku dołączamy plik nagłówkowytypeinfo, aby mieć dostęp do narzędzi związanych z identyfikacją typów(patrz rozdział o RTTI).W linii ➊ wprowadzamy, za pomocą znanej już nam instrukcjitypedef, inną nazwę typu „piętnastoelementowatablica liczb całkowitych” (patrzrozdział o instrukcji typedef).Jak widać z linii ➏ i z pierwszej liniiwydruku, operatorsizeofprawidłowo rozpoznał rozmiartypuTABINT15. Zauważmy też, że nie można tu pominąćnawiasów, boTABINT15jest nazwą typu.W liniach ➌ i ➍ definiujemy tablicetab1itab2nadwa sposoby — za pomocą wprowadzonej nazwyi bezpośrednio. Porównując typy tych zmiennychwidzimy, że rzeczywiście są one takie same (linia 'A' wydruku).W linii ➎ definiujemy zmiennątab3int*i przypisujemy do niej wartość zmiennejtab2. Przypisaniejest prawidłowe, ale nie zapominajmy, że zachodzi przy tym konwersjastandardowa: typynie są takie same,co widzimy z linii 'B' wydruku.W liniach ➏-➒ drukujemy rozmiary typui zmiennychtab1,tab3.Wszystkie rozmiary, za wyjątkiemtab3, wynoszą 60,co odpowiada tablicy piętnastu czterobajtowych liczb.Natomiast rozmiarjest 8, gdyż jest to zmiennatypu wskaźnikowego, a nie tablicowego.W ostatniej linii posyłamy tę samą tablicępoprzez dwa argumenty do funkcjisiz. Pierwszy parametrfunkcji jest typuTABINT15, więc wydawałoby się,że funkcja „wie”, że argument będzie tablicą. Drukując jednak(linia 'G' wydruku) wewnątrz funkcji rozmiar zmiennejt1,widzimy, że jest to wskaźnik o rozmiarze 8 — jest tak,gdyż przy wysyłaniudo funkcji przez wartośći tak zmienna została zrzutowana do typu wskaźnikowego(na stosie został położony adres pierwszego elementu tablicyi nic więcej).Drugi parametr funkcjisizjest zadeklarowany jakoreferencja. Teraz żadnej konwersji nie ma, bo nie jest w ogóletworzona żadna zmienna lokalna, której należałoby przypisaćwartość argumentu. Wewnątrz funkcjit2jest teraz innąnazwą dokładnie tej samej zmiennej, która w funkcjimainnazywa siętab2. Zatem i informacja o typie jest ta samai 'sizeof t2' drukuje 60 (linia 'H' wydruku).Przedrostkowe operatory zmniejszenia i zwiększenia(predekrementacji i preinkrementacji) działają podobnie do przyrostkowychoperatorów zmniejszenia i zwiększenia. Są jednak ważneróżnice: operatory te zmniejszają (zwiększają) swój argumentprzed jego użyciem do obliczenia wartościwyrażenia, w skład którego wchodzą.int b = ++a;wynosi również 2, bo w trakcie opracowywania drugiej instrukcjizmiennazostała zwiększona jeszcze przed wykonaniemprzypisania. Wynikiem działania operatora preinkrementacjilub predekrementacji jest l-wartość (pamiętamy, że taknie było dlaoperatorów postinkrementacji lub postdekrementacji) —tak więc wyrażenie++++a byłoby legalne.Operatory negacji:bitoweji logicznejomówimy poniżej razem z innymi operatorami logicznymi i bitowymi.Operator jednoargumentowy '+'jestwłaściwie operatorem identycznościowym, czyli takim, który „nic nie robi” (ang. no-op). Istnieje tylkodla wygody, aby wyrażenia typu 'k = +5' miały sens.Operatory wyłuskania adresu i dereferencji (& i*)były już omówione wrozdziale na temat typów danych.Operatorynewdeletesłużą dodynamicznego alokowania i zwalniania pamięci: będą omówionew rozdziale o zarządzaniu pamięcią.Ostatni operator z tej grupy, oznaczany parą nawiasów okrągłych,to operator rzutowania. Jest on operatoremjednoargumentowym: wynikiem działania tego operatora na p-wartośćpewnego typu jest odpowiadająca tej wartościp-wartość innego typu — tego wymienionego w nawiasie.Z operatora tego należy korzystać oględnie;w większości przypadków jego zastosowanie świadczy raczejo złym stylu programowania. Czasem jest jednak przydatny.Na przykład w drugiej instrukcji fragmentu double x = 7;int k = (int)x;rzutowanie wartości zmiennejjest wskazane,gdyż wartość ta, jako wartość szerszego typu, może byćwpisana do zmiennej typu węższego, jakim jest typint, tylko zestratą informacji (precyzji). Choć nie jest to błąd, to kompilatorzwykle wypisuje ostrzeżenia; jeśli zastosujemy jawne rzutowanie,ostrzeżeń nie będzie.Rzutowanie zawsze działa na p-wartość i w wynikudaje inną p-wartość, ale nigdy l-wartość. W szczególnościrzutowanie nie zmienia typu żadnej zmiennej —typu istniejącej zmiennej zmienić się nie da!Zamiast operatora rzutowania w C++ zaleca się stosowaniebezpieczniejszych operatorów konwersji, wymienionych w tabeli w grupie odpowiadjącej priorytetowi 16.Omówimy je bardziej szczegółowo9. 4 Grupa operatorów o priorytecie 14Należą do tej grupy dwa operatory wyboru składowej,które omówimy w rozdziałach na temat klas w C++(rozdział o wskaźnikach na składowe).9. 5 Operatory arytmetyczneWymienione w tabeli operatory arytmetycznemająoczywisteznaczenie i ich działanie jest niemal takie samo jakw większości innych języków. Występują czasem drobne różnice:np. w Javie operator reszty'%' może mieć dowolne agumenty numeryczne, również typudouble.W C/C++ operator ten wymaga argumentów typu całkowitego.Pewien kłopot mogą sprawiać wyrażenia z wartościami ujemnymi jakoargumentami. Wiemy, że dzielenie liczb typu całkowitego daje w wynikuliczbę całkowitą, czyli ewentualna część ułamkowa jest obcinana.Jeśli wynik jest dodatni, to dokładna wartość ilorazu jest obcinanaw dół, czyli w kierunku zera, natomiast jeśli wynik jest ujemny,to obcięcie jest w górę, a więc też w kierunku zera.Dla operatora reszty spełniona jest zawsze zasada a = (a/b)*b + a%bdlaróżnego od zera. Wynika z niej, przy założeniu,że obcinaniew dzieleniu całkowitoliczbowym jest zawsze w kierunku zera,zasada następująca: wartość a%b
jest równa co do modułuwartości |a|%|b|
i ma znak taki, jaki znak ma a
(kreski oznaczają wartość bezwzględną). Ilustruje to poniższyprogramik:
P54:mod. cppOperator reszty
2. using namespace std; 3. 4. int main() { 5. int i, j; 7. i = 19; j = 7; cout << " 19 / 7 = " << i/j << endl; 8. i =-19; j = 7; cout << "-19 / 7 = " << i/j << endl; 9. i = 19; j =-7; cout << " 19 / -7 = " << i/j << endl; 10. i =-19; j =-7; cout << "-19 / -7 = " << i/j << endl; 12. i = 19; j = 7; cout << " 19% 7 = " << i%j << endl; 13. i =-19; j = 7; cout << "-19% 7 = " << i%j << endl; 14. i = 19; j =-7; cout << " 19% -7 = " << i%j << endl; 15. i =-19; j =-7; cout << "-19% -7 = " << i%j << endl; 16. }którego wynikiem jest 19 / 7 = 2-19 / 7 = -219 / -7 = -2-19 / -7 = 219% 7 = 5-19% 7 = -519% -7 = 5-19% -7 = -5Ta zasada będzie inna, jeśli obcinanie w dzieleniu całkowitoliczbowymjest zawsze w dół, a nie w kierunku zera (tak może sie zdarzyć dla starychkompilatorów). Dlatego lepiej unikać stosowania operatora resztydla liczb ujemnych.9. 6 Operatory relacyjne i porównaniaOperatory relacyjne('<', '<=', '>','>=')i porównania==', '! =') mają oczywistąinterpretację. Wyrażenie '==b' mawartość logiczną odpowiadającą na pytanie czy wartośćjest równa wartościb. Wyrażenie! =b' ma wartość logicznąodpowiadającą na pytanie czy wartośćjest różna od wartościb.Argumentami mogą być dwa skalary,czyli p-wartości liczbowe, lub (choć nie zawsze) dwa adresy(wartości zmiennych wskaźnikowych lub wynik operacji wyłuskania adresu).Jest to nieco inaczej niż w Javie, gdzie adresy (czy raczej odniesienia)mogły być argumentami wyłącznie operatorów porównania==' i '! ='), ale nie operatorów relacyjnych.W C++ można porównywać adresy za pomocą operatorów relacyjnych podwarunkiem, że są to adresy elementów tej samej tablicy.Wynikiem operacji jest wartość logicznatruelubfalse. Jak mówiliśmy (patrzrozdziału o typie logicznym),wartości logiczne reprezentowane są w zasadzieprzez wartości całkowite: wartość 0 jest równoważnafalse,a dowolna wartość niezerowatrue. Obowiązuje torównież dla wartości wskaźnikowych: wartośćpusta (nullptr) jest interpretowana jakofalse, akażda inna jakotrue.9. 7 Operatory bitoweOperatory bitowe wymienione są w tabeli na pozycjach odpowiadającychpriorytetom 11 (przesunięcia bitowe), 8, 7 i 6.Argumentami muszą być wartości całkowite, wynik też jest typu całkowitego.Obowiązują przy tym normalne reguły konwersji argumentów do typu wspólnego.Operatory bitowe nie „interesują” się wartościa liczbowąargumentów, ale ich reprezentacją bitową. Przypomnijmy, żezwyczajowo numeruje się bity reprezentujące wartości zmiennych,poczynając od zera, od bitu najmniej znaczącego (odpowiadającegowspółczynnikowi przy zerowej potędze dwójki) do bitu najbardziejznaczącego. Reprezentując graficznie układ bitów, bit zerowyumieszcza się po prawej stronie, a bit najbardziej znaczący po lewej(patrzrozdział o typach całkowitych).Rozpatrzmy zatem bardziej szczegółowo działanie poszczególnychoperatorów bitowych na wartości liczbowe. Jeden z nich — negacjabitowa — jest jednoargumentowy, pozostałe są dwuargumentowe.Operator bitowej negacji ('∼'),działając na wartość całkowitą zwracanową 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 onnieustawiony (coodpowiada umownej wartości 0) — jak na rysunku, gdzie dlauproszczenia zilustrowane jest działanie opertatora negacjibitowej dla wartości typuchar, a więc jednobajtowej(ośmiobitowej). Oczywiście negacja jest inwolutywna, czyli dwukrotnejej zastosowanie prowadzi do wartości wyjściowej.Alternatywa bitowa('|') jest operatorem dwuargumentowym: dla kolejnychpozycji sprawdzane są pojedyncze bity w obu argumentach i obliczanaich suma logiczna: w wyniku bit na odpowiadającej pozycji jestjedynką (bit ustawiony), jeśli w którymkolwiek argumencie bitna tej pozycji był ustawiony, a zero, gdy w obu argumentach na tejpozycji również występowało zero (jak na rysunku poniżej).
Alternatywa bitowa (tzw. ORowanie) jest często stosowana doustawiania najrozmaitszych opcji. Na przykład w C++, otwartedo czytania lub pisania pliki mają szereg trybów, którym odpowiadająpewne stałe całkowite, np.ios::in,ios::out,zdefiniowane w klasieios(dlatego odwołujemy się do nichpoprzez operator zakresu klasy — czterokropek).W programie bits. cpp drukujemy reprezentacjębitową kilku tego rodzaju stałych. Widzimy, że są to pełne potęgidwójki, a więc w ich reprezentacji bitowej występuje tylko jednajedynka na odpowiedniej pozycji — dlaios::outpozycji 4, dlaios::appna pozycji 0 itd.Zatem na przykład stałą określającą trybotwartego pliku jako pliku jednocześnie do pisania i do czytaniabędzieios::in | ios::outi będzie zawieraćjedynki na pozycjach 3 i 4 (konkretne pozycje mogązależeć od implementacji — należy zawsze odwoływać się dotych stałych poprzez ich nazwy).Koniunkcja bitowa('&') jest też operatorem dwuargumentowym: dla kolejnychpozycji sprawdzane są pojedyncze bity w obu argumentach i obliczanyich iloczyn logiczny: w wyniku bit na odpowiadającej pozycji jestjedynką (bit ustawiony), jeśli w obu argumentach bitna tej pozycji był ustawiony, a zero, gdy w którymkolwiekz argumentów na tej pozycji występowało zero (patrz rysunek).
Koniunkcja bitowa (tzw. ANDowanie) jest często stosowana dotzw. maskowania. Wspomnieliśmy, że stała określająca trybpliku ma na pozycji trzeciej (licząc od zera) jedynkę, jeśliustawiony plik został otwarty w trybiein, a zero,jeśli nie (co to dokładnie znaczy, dowiemy się wrozdzialo o operacjach we/wy).Jeśli stała określająca tryb nazywa siętryb, tomaskowanie jej ze stałą 8 (= 23) odpowie na pytanie, czy bitinjest czy nie jest ustawiony. Reprezentacja 8 składasię z samych zer, z wyjątkiem pozycji trzeciej(czwarty bit od prawej), na której bitjest ustawiony. Zatem obliczając koniunkcję dostaniemy na wszystkichinnych pozycjach na pewno zero, na pozycji czwartej zaś jedynkę,jeśli wtrybten bit był ustawiony, a zero, jeśli nie był.Zatem wartość wyrażeniatryb & 8będzie niezerowawtedy i tylko wtedy, jeśli bitbył w zmiennejustawiony, niezależnie od stanu innych bitów w tejzmiennej.Operator bitowej różnicy symetrycznej('^') jest też operatorem dwuargumentowym: dla kolejnychjest ich różnica symetryczna: wynikowy bit na odpowiadającej pozycjijest jedynką (bit ustawiony), jeśli w obu argumentach bityna tej pozycji były różne, a zerem jeśli w obu argumentachna tej pozycji występowały bity takie same — dwa zera albo dwiejedynki.
Różnica symetryczna (obliczanie jej nazywane bywa XORowaniem) maciekawą i użyteczną własność,wynikającą z natępującej tabelki logicznej dla tego działania:z której wynika, że dwukrotne XORowanie dowolnego bitu b zdowolną maską m przywraca pierwotną wartość tego bitu — wtabeli kolumna pierwsza i ostatnia są takie same. Ta własnośćXORowania jest wykorzystywana między innymi w grafice komputerowej.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 argumentma układ bitów jakw górnej części rysunku. Przesunięcie w tej zmiennej bitóww lewo o dwa, 'w = w << 2', odpowiada przesunięciuwszystkich bitów o dwie pozycje w lewo (w kierunku pozycji bardziejznaczących). Bity „wychodzące” z lewej strony są traconebezpowrotnie. Z prawej strony „wchodzą” bity zerowe. Tak więcpo wykonaniu tej instrukcji reprezentacja zmiennejtaka jak w środkowej części rysunku. Analogicznie, po przesunięciuteraz bitów w prawo o trzy pozycje ('w = w >> 3') otrzymamyreprezentację zmiennejjak w dolnej części rysunku(pod pewnymi warunkami — patrz niżej).
O ile przesuwanie w lewo jest zawsze dobrze określone wedługwspomnianych zasad, rzecz jest bardziej skomplikowana przyprzesuwaniu 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.W Javieistnieją dwa operatory bitowego przesunięcia w prawo:w przypadku operatora '>>' z lewej strony „wchodzi”taki bit, jaki był przed przesunięciem na najstarszej pozycji(czyli z lewej strony) — jeśli było to zero, wchodzi zero,jeśli jedynka, wchodzi jedynka. Mówimy, że reprodukowanyjest bit znaku. Taka konwencja powoduje, że dlaliczb ze znakiem (patrzrozdział o typach całkowitych)przesunięcie w prawo o jedną pozycję odpowiada dla liczb parzystychdzieleniu przez dwa, zarówno dla liczb dodatnich,jak i ujemnych — podobnie jak przesuwanie w lewo odpowiadamnożeniu przez potęgę dwójki (dla liczb nieparzystych i dlaprzesuwania w prawo jest tu pewna dodatkowa komplikacja, w którąnie będziemy się wgłębiać, jest to dobre ćwiczenie sprawdzającerozumienie bitowych reprezentacji liczb; ma to związek z konwencjąobcinania części ułamkowej przy dzieleniu całkowitoliczbowym —w dół czy w kierunku zera? ). Inny operator wJavie, '>>>', oznacza przesunięcia w prawo takie, żez lewej strony wchodzą zawsze zera.W C/C++ sprawa nie jest taka jasna. Istnieją tu specjalne typy bezznaku (unsigned), za to nie istnieje „potrójny”operator przesunięcia '>>>' w prawo.Wobec tego w większości implementacji przyjęto następującąkonwencję: jeśli typem wartości lewego argumentu jesttyp bez znaku (unsigned), to przy przesuwaniuw prawo wchodzą z lewej strony bity zerowe;jeśli natomiast typem wartości lewego argumentu jesttyp ze znakiem (signed), to przy przesuwaniuw prawo reprodukowany jest z lewej strony bit znaku, czyli skrajnylewy 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 przesunieęcia, zawsze powinien być nieujemny i mniejszyod rozmiaru w bitach wartości, na której dokonujemy przesunięcia.W Javiejest dobrze określone przez specyfikację języka, co się dzieje,jeśli te warunki nie są spełnione; w C/C++ może to zależećod implementacji i wobec tego lepiej takich konstrukcji unikać.Na zakończenie rozpatrzmy przykład ilustrujący to, o czymmówiliśmy na temat operacji bitowych.
P55:Operacje na bitach
4. void bitsChar(char k) { 5. int bits = 8*sizeof(k); ➊ 6. unsigned char mask = 1<<(bits-1); 7. for (int i = 0; i < bits; i++) { 8. cout << (k & mask ? 1 : 0); 9. mask >>= 1; ➋ 10. } 11. cout << endl; 12. } 13. 14. void bitsShort(short k) { 15. int bits = 8*sizeof(k); 16. unsigned short mask = 1<<(bits-1); 17. for (int i = 0; i < bits; i++) { 18. cout << (k & mask ? 1 : 0); 19. mask >>= 1; 20. } 21. cout << endl; 22. } 23. 24. void bitsInt(int k) { 25. int bits = 8*sizeof(k); 26. unsigned int mask = 1<<(bits-1); 27. for (int i = 0; i < bits; i++) { 28. cout << (k & mask ? 1 : 0); 29. mask >>= 1; 30. } 31. cout << endl; 33. 34. int main() { 35. short s = -1; int i = 259; 36. 37. cout << "char 'a': "; bitsChar('a'); 38. cout << "short -1: "; bitsShort(s); 39. cout << "int 259: "; bitsInt(i); 40. cout << endl; 41. cout << "ios::in: "; bitsInt(ios::in); 42. cout << "ios::out: "; bitsInt(ios::out); 43. cout << "ios::app: "; bitsInt(ios::app); 44. cout << "ios::in | ios::out\n "; 45. bitsInt(ios::in | ios::out); 46. }Na początku tego programu definiujemy trzy niemal identyczne funkcje,których zadaniem jest wypisanie bitowej reprezentacjiargumentu. Funkcje różnią się tylko typem argumentu: może nimbyćchar,shortint.W rozdziale o szablonach funkcjidowiemy się, jak można było uniknąć pisania trzech wersjitak podobnych funkcji.Przyjrzyjmy się jednej z tych funkcji, na przykład funkcjibitsChar. W linii ➊ sprawdzamy, jaki jest rozmiar w bitachwartości argumentu(tu oczywiście wiemy, że będzie to 8,bosizeof(k) dlacharda jedynkę).Następnie tworzymy maskęmaskunsigned char.Chodzi nam o to, aby długość maski była taka jak długośćk, ale by była to zmienna na pewno bez znaku — w tensposób przy przesuwaniu w prawo będą z lewej strony „wchodzić”zera. Maskę inicjujemy wartością '1 << 7'(bobitswynosi 8). Reprezentacja jedynki to siedem bitówzerowych i jeden, prawy (czyli najmłodszy), bit ustawiony.Przesuwając ten układ bitów w lewo otrzymamy liczbę, którejreprezentacją jest jedynka i siedem zer (jedynka tym razem z lewejstrony). Robimy to po to, by pętla drukująca, która teraz następuje,przebiegała przez kolejne bity liczbyod lewej do prawej,a nie odwrotnie.Następnie w pętli obliczamy koniunkcję bitowąz maskąmask. Ponieważ maska ma tylko jeden bitustawiony, w ten sposób sprawdzamy, czy odpowiedni bit jest teżustawiony wk. Jeśli tak, to wynikiem koniunkcji będziejakaś wartość niezerowa, interpretowana jakow pierwszymargumencie operatora selekcji, a zatem wartością tejże selekcji(k&mask? 1: 0)' będzie jedynka, która zostaniewypisana na ekranie. Jeśli wodpowiedni bit nie jestustawiony, wydrukowane zostanie zero.W linii ➋ przesuwamy bity w masce o jeden w prawo. Ponieważzadbaliśmy, aby maska była typu bez znaku, z lewej strony będąwchodzić same zera. Zatem w kolejnych przebiegach pętlimaska cały czas będzie zawierać dokładnie jedną jedynkę,„wędrującą” od lewej do prawej. A więc w kolejnychprzebiegach pętli sprawdzone i wydrukowane będą, w kolejności odlewej do prawej, wszystkie bity zmiennejk.Podobnie działają pozostałe dwie funkcje:bitShortbitInt— jedyna różnica to typ argumentu.W programie głównym drukujemy reprezentację bitową kilkuliczb całkowitych. Wyniki wyglądają następująco: char 'a': 01100001short -1: 1111111111111111int 259: 00000000000000000000000100000011ios::in: 00000000000000000000000000001000ios::out: 00000000000000000000000000010000ios::app: 00000000000000000000000000000001ios::in | ios::out00000000000000000000000000011000Widać, że (jak o tym mówiliśmy wrozdziale o typach całkowitych),reprezentacją liczby -1 są jedynki na wszystkich bitach.Znak 'a' odpowiada, jak łatwo policzyć, wartościcałkowitej26 +25 + 1 = 64 + 32 + 1 = 97, co rzeczywiściejest kodem ASCII litery 'a'.Dalej ilustrujemy to, co mówiliśmy o stałychitd.Widzimy, że są to pełne potęgijedynka na odpowiedniej pozycji: w stałejios::truncna pozycji czwartej (licząc od zera), a w stałejna pozycji pierwszej. Obliczając ich alternatywę (sumę) bitowąotrzymujemy liczbę, w której reprezentacji bitowej te i tylko tedwa bity są ustawione (ostatnia linia wydruku).9. 8 Operatory logiczneArgumentami operatorów logicznych&&' (koniunkcja), '' (alternatywa)! ' (negacja)mogą być zarówno wartości typubooljak i wartości całkowite;w tym ostatnim przypadku wartości zostaną zinterpretowane wedługnormalnej zasady: 0→niezerotrue. Obliczonawartość jest typu logicznego: alternatywa (suma logiczna) daje wynikwtedy i tylko wtedy gdy choć jeden z argumentów matrue, natomiast koniunkcja (iloczyn logiczny)ma wartośćtylko jeśli oba argumenty są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 niebędzie w ogóle obliczany, jeśli lewy argument okazałsię równyfalse— całe wyrażenie musi bowiemwtedy mieć wartośćniezależnie od wartościprawego argumentu;
- dla alternatywy prawy argument niebędzie obliczany, jeśli lewy okazał się— całe wyrażenie musi bowiem mieć wtedy wartośćniezależnie od wartości prawego argumentu.
Na przykład, jeślia,rsą zmiennymi typu całkowitego, to przypisanie r = a && b;jest równoważne if (a == 0)r = 0;else{if (b == 0) r = 0;else r = 1;}a przypisanie r = a || b; if (a ! = 0)r = 1;if (b ! = 0) r = 1;else r = 0;Rozpatrzmy jeszcze jeden przykład ilustrujący skrótowośćdwuargumentowych operatorów logicznych:
P56:skrot. cppSkrótowość operatorów koniunkcji i alternatywy
4. bool fun(int k) { 5. k = k - 3; 6. cout << "Fun zwraca " << k << endl; 7. return k; 8. } 9. 10. int main() { 11. if ( fun(1) && fun(2) && fun(3) && fun(4) ) ➊ 12. cout << "Koniunkcja true" << endl; 13. else 14. cout << "Koniunkcja false" << endl; 15. 16. if ( fun(1) || fun(2) || fun(3) || fun(4) ) ➋ 17. cout << "Alternatywa true" << endl; 18. else 19. cout << "Alternatywa false" << endl; 20. }W linii ➊ sprawdzany jest warunek w postaci koniunkcjiwartości logicznych zwracanych przez funkcjęfun(równiedobrze mogłyby to być wartości całkowite). Ponieważfun(3) zwraca 0 czyli logicznefalse, funkcjaw ogóle nie zostanie już wywołana z argumentem 4, bo wynik już jestznany: wartością całego wyrażenia w nawiasie musi byćniezależnie od tego, co zwróciłaby funkcjaargumentu 4. Widzimy to z wydruku Fun zwraca -2Fun zwraca -1Fun zwraca 0Koniunkcja falseFun zwraca -2Alternatywa truePodobnie dla alternatywy w linii ➋. Już pierwsze wywołaniefunkcjidało wynik(odpowiada toniezerowej wartości zwracanej, w tym przypadku -2). Alternatywajest prawdziwa, gdy choć jeden argument jesttrue, wobec tegopo wywołaniufun(1) wynik całego wyrażenia jest znany(true) i wywołańfun(2),fun(3)fun(4) nie będzie.9. 9 Operatory przypisaniaW grupie o priorytecie 2 wymienione sąoperatory zwykłego przypisania=') oraz złożone operatory przypisania.Lewa strona przypisania musi być zawsze l-wartością. Tak więc double x;x + 2 = 7; // NIEjest niepoprawne, natomiast double x, *y = &x;*(y + 2) = 7;byłoby legalne (choć prawdopodobnie bez sensu), bo wyłuskaniewartości daje w wyniku l-wartość.Wykonanie przypisaniapolega na obliczeniu wartości prawej strony i umieszczeniu wynikupod adresem l-wartości występującej po stronie lewej. Zauważmyasymetrię: prawa strona mówi co policzyć, lewa gdziezapisać wynik.Wartością i typem całego wyrażenia przypisania jest wartość ityp lewej strony po wykonaniu przypisania.Całe przypisanie jest l-wartością. Na przykład int m = 1, n = 2;(m=n) = 6;cout << m << " " << n << endl;drukuje '6 2'.W programie poniżej
P57:przypis. cppWykorzystanie wartości przypisania
4. int main() 5. { 6. int k; 7. while ( (k = cin. get()) ! = '\n' ) 8. cout << "Wprowadzono znak '" << (char)k 9. << ”, o kodzie ASCII " << k << endl;w linii 7 przypisujemy dowartośc znaku — czyli jegokod ASCII — odczytanąz klawiatury za pomocą metodygetwywołanej na rzeczobiektucin— patrzrozdział o operacjach we/wy.Całe przypisanie '(k=cin. get())' ma wartośćpo przypisaniu; tę wartość porównujemy z predefiniowaną stałąEOF, oznaczającą koniec strumienia danych. Zauważmy,że nawias wokół tego wyrażenia był konieczny, bowiempriorytet porównania,! =, jest wyższy niżpriorytet operatora przypisania, a nam chodzi o to, aby najpierwdokonać przypisania, a dopiero jego wynik porównać zEOF.Przykładowe uruchomienie tego programu daje cpp> g++ -pedantic-errors -Wall -o przypis przypis. cppcpp>. /przypisAla ma... [ENTER]Wprowadzono znak 'A', o kodzie ASCII 65Wprowadzono znak 'l', o kodzie ASCII 108Wprowadzono znak 'a', o kodzie ASCII 97Wprowadzono znak ' ', o kodzie ASCII 32Wprowadzono znak 'm', o kodzie ASCII 109Wprowadzono znak '. ', o kodzie ASCII 46cpp>Dzięki temu, że wartością całego wyrażenia z przypisaniem jestwartość lewej strony po jego wykonaniu,przypisania można stosować kaskadowo. Tak więc int k = 7, j, m;int n = m = j = k;jest prawidłowe: ponieważ wiązanie operatora przypisaniajest od prawej, wartością wyrażenia 'm=j=k',równoważnego 'm=(j=k)', jest wartośćpo przypisaniu (czyli w naszym przypadku 7). Ta wartość zostanieużyta do zainicjowania definiowanej zmiennejn.Efektem ubocznym będzie nadanie wartości również zmiennymj. Zauważmy, że instrukcja byłabybłędna, gdyby któraś ze zmiennychm,j,nie była utworzona wcześniej.Złożone operatory przypisaniapozwalają na prostszy zapis niektórych przypisań: tych,w których ta sama l-wartość występuje po lewej i prawej stronieprzypisania. Zamiast instrukcji a = a @ b;gdzie symbol ” oznacza któryś z operatorówmożna użyć instrukcji a @= b;Drobna różnica, najczęściej bez znaczenia,pomiędzy tymi instrukcjami polega na tym, że w drugiej z nichjest obliczana jednokrotnie,a w pierwszej dwukrotnie. Zwykle druga z tych form,'a = b', jest efektywniejsza.Jako przykład zastosowania rozpatrzmy program:
P58:zloz. cppPrzypisania złożone
4. void bitsInt(int k) { 5. unsigned int mask = 1<<31; 6. for (int i = 0; i < 32; i++, mask >>= 1) { 7. cout << (k & mask ? 1 : 0); 8. if (i%8 == 7) cout << " "; 9. } 10. cout << endl; 11. } 12. 13. int main() { 14. unsigned int k = 255<<24 | 153<<16 | 255<<8 | 255; ➊ 15. cout << "k przed: "; bitsInt(k); 16. (k <<= 8) >>= 24; ➋ 17. cout << "k po: "; bitsInt(k); 18. }FunkcjabitsIntjest tu podobna do tej z programu — tak samo służy do drukowania bitowejreprezentacji liczby całkowitej. W tej wersji z góry założyliśmy,że typjest czterobajtowy. Prócz tego przesuwaniemaski przenieśliśmy do części inkrementującej nagłówka pętli,umieszczając tam dwie instrukcje wyrażeniowe oddzieloneprzecinkiem (o operatorze przecinkowym —w jednym z następnych podrozdziałów).Dodaliśmy też drukowanie spacji po każdej grupie ośmiubitów, aby uczynić wydruk bardziej przejrzystym.W linii ➊ konstruujemy liczbę o z góry zadanej reprezenatcjibitowej. Wyrażenie '255 << 24' daje liczbę z samymi jedynkamiw najstarszym bajcie (255 to osiem jedynek, następnie przesunięteo 24 pozycje w lewo). Wyrażenie '153 << 16' to układbajtów 10011001 przesunięty w lewo o 16 pozycji, czylido bajtu trzeciego od lewej. Z kolei '255 << 8' daje osiemjedynek w bajcie drugim, a samo 255 — osiem jedynek w bajcienajmłodszym. Suma (alternatywa) bitowa „składa” wszystkie te bajty:otrzymujemy zatem liczbę o reprezentacji przedstawionej wpierwszej linii wydruku: k przed: 11111111 10011001 11111111 11111111k po: 00000000 00000000 00000000 10011001Operator przypisania złożonego zastosowaliśmy w linii ➋.Wyrażenie '(k <<= 8)' powoduje przesunięcie w zmiennejwszystkich bitów w lewo o osiem pozycji. Zatem zawartośćbajtu najstarszego zostaje „zgubiona”, bajt 10011001przechodzi na jego pozycję, a kolejne dwa, czyli pierwszy i drugi,stają się drugim i trzecim (od lewej). Bajt najmłodszy wypełnianyjest zerami. Wynik całego wyrażenia jest l-wartością, a zatem ma senszastosowanie do niego następnego przypisania złożonego: tym razemprzesuwamy zawartość zmiennejo 24 pozycje w prawo.Ponieważ zmiennajest bez znaku, z lewej strony„wchodzą” same zera, a 24 najmłodsze bity „wychodzą” z prawejstrony. Układ bitów 10011001 po tej operacji znajduje sięna pozycji najmłodszego bajtu. W efekcie widzimy, że cała operacjadaje w efekcie liczbę równą tej, której reprezentacja binarna zawartabyła w trzecim od lewej bajcie wyjściowej liczby. W podobny sposóbmoglibyśmy „wyciąć” zawartość pozostałych bajtów. Takie wycinaniepojedynczych bajtów stosuje się na przykład przy kodowaniu trzech(albo czterech) składowych koloru w jednej liczbie.9. 10 Operator warunkowyOperator warunkowy (selekcji)jest jedynym operatorem trzyargumentowym. Jego składnia: b ? w1 : w2Najpierw obliczana jest wartość wyrażeniai ewentualnie konwertowana do typubool.Jeśli jest totrue, obliczane jest wyrażeniew1, a wyrażeniew2jest ignorowane.Wartością całego wyrażenia jest wartośćw1.false, obliczane jest wyrażeniew2, a wyrażeniew1Wartością całego wyrażenia jest wtedy wartośćw2.Jeśli zarównosą l-wartościami,to i wartość operatora warunkowego jest l-wartością.Klasyczny przykład zastosowania operatora selekcji to implementacjamaxzwracającej większą z wartości swychargumentów: int maxim(int a, int b) {return a > b ? a : b;Inny przykład zastosowania operatora selekcji podany zostanie wnastępnym podrozdziale.9. 11 Operator zgłoszenia wyjątkuJako przedostatni w tabeli występujeoperator zgłoszenia wyjątkuthrow: omówimy gow rozdziale o wyjątkach.9. 12 Operator przecinkowyOperator przecinkowyjest dwuargumentowy: po dwóch stronach przecinka dwa wyrażenia wyr1 , wyr2Działanie jego polega na:obliczeniu wyrażeniawyr1i zignorowaniuresultatu;wyr2; jego wartość stajesię wartością całego wyrażenia.Często operator przecinkowy stosuje się w części inicjalizacyjnejlub inkrementującej nagłówka pętlifor; przykład tolinia 7 programu .Inny, nieco dziwaczny, przykład ilustruje program:
P59:przec. cppOperator przecinkowy
5. int r = 0; 7. 8. while (cin >> k, k) { ➊ 9. r += k > 0 ? (cout << "Dodatnia\n" , +1) 10. : (cout << "Ujemna\n" , -1); 11. } 12. cout << "Roznica ilosci dodatnich i ujemnych: " 13. << r << endl; 14. }Operator przecinkowy jest tu użyty w linii ➊ w warunkupętliwhile. Mamy tu pierwsze wyrażenie,'cin >> k', wczytujące kolejną daną z klawiatury,i drugie, po prostuk, dzięki któremu pętlaskończy się, gdy wczytana zostanie liczba 0 (gdyż wartościącałego wyrażenia przecinkowego jest wartość prawego argumentu).W pętli dododawana jest (operator złożonego przypisania)wartość nieco skomplikowanego wyrażenia. Jest to operator selekcjipodrozdział o operatorze selekcji— sprawdzający znakk; w każdym przypadku rezultatbędzie znów wartością wyrażenia przecinkowego. Dladodatnichbędzie to wartość (cout << "Dodatnia\n" , +1)czyli +1 z efektem ubocznym polegającym na wypisaniu słowa"Dodatnia". Analogicznie, dlaujemnychododjęte zostanie 1, a jako efekt uboczny wypisanebędzie słowo "Ujemna". Po wyjściu z pętli wypisywanajest różnica między ilością wczytanych liczb dodatnichi ujemnych. Na przykład wynik programu może być następujący: cpp> przec2Dodatnia6-3Ujemna-130Roznica ilosci dodatnich i ujemnych: 29. 13 Alternatywne nazwy operatorówNiektóre operatory mają też formę czysto tekstową:Tabela:Alternatywne nazwy operatorów tekstowa symboliczna tekstowa symboliczna and && and_eq &= bitand & bitor | compl ∼ not ! not_eq ! = or or_eq |= xor ^ xor_eq ^= Forma tekstowa może być zamiennie stosowana z formą wyrażonąza pomocą symboli nieliterowych.T. R. Werner, 25 lutego 2017; 22:31
Najważniejsza część zamienna
Wiele lat temu Ron Slee napisał: NAJWAŻNIEJSZA CZĘŚĆ ZAMIENNA TO TA, KTÓREJ NIE MASZ. To jest punkt widzenia klienta, z którym nie ma co dyskutować. Jeśli nie wyślesz mu tylko jednej pozycji z zamówienia liczącego kilkadziesiąt numerów katalogowych, to tak jakbyś nic nie wysłał. Bo jak ma zmontować rozebraną skrzynię biegów, jeśli tym brakującym elementem jest podkładka […]
4 pytania do kierownika magazynu części zamiennych
Jest wiele przyczyn, że klient nie otrzymuje natychmiast potrzebnej mu części zamiennej. Jedną z nich – najłatwiejszą do usunięcia – jest małe zaangażowanie pracowników logistyki części zamiennych. Kilkanaście lat temu Ron Slee podpowiedział doskonały sposób podniesienia poziomu jakości ich usług. Pracownik logistyki musi odpowiedzieć na cztery pytania, gdy zamierza zakończyć pracę i iść do domu: Czy wysłałeś […]
Umowa serwisowa cz. 1 – partnerstwo, zaufanie, elastyczność
Co zrobić, aby maszyny były gotowe do pracy, gdy będą potrzebne zgodnie z harmonogramem prac? Co zrobić, aby koszty ich posiadania i eksploatacji nie były wyższe, niż zaplanowane w kosztorysie? Jednym z lepszych sposobów utrzymania pod kontrolą kosztów kluczowych maszyn jest zawarcie dobrej umowy serwisowej. Taka umowa powinna również zmniejszyć ryzyko przestojów z przyczyn technicznych. Dobra umowa […]
Bezpośredni link do pobrania Techniczny podręcznik referencyjny, ogólne Brother Hl 5595dnh
Ostatnia aktualizacja Techniczny podręcznik referencyjny, ogólne Brother Hl 5595dnh
(x+y)++nie ma sensu i jest błędne, gdyż wyrażenie(x+y) niejest l-wartością (choć jest p-wartością).Tak więc argument operatorów postdekrementacji i postinkrementacjimusi być l-wartością: ale co z rezultatem działania tego operatorana l-wartość? W Javiewartości uzyskane za pomocą tych operatorów nigdy same niesą l-wartościami. W C/C++ jest trochę inaczej: dla przyrostkowychoperatorów zmniejszenia i zwiększenia wynik nie jestl-wartością, ale dla operatorów przedrostkowych wynik jestl-wartością.Dlategoint k = 5;int m = (++k)--;jest prawidłowe. Wyrażenie w nawiasach jest l-wartością, bo użytyzostał operator preinkrementacji; można było zatem zastosowaćnastępnie operator postdekrementacji. Wynikiem działania tego z koleioperatora nie jest już l-wartość, ale ponieważ użyliśmy jejtylko po prawej stronie przypisania, więc wszystko jest w porządku.Wartośćkbędzie oczywiście wynosić po tymprzypisaniu 5, a wartością zmiennejmbędzie 6.Gdybyśmy nie użyli nawiasówint k = 5;int m = ++k--; // ZLE!!! kod byłby błędny: ponieważ priorytet postdekrementacji jest wyższyniż preinkrementacji, więc najpierw obliczone byłoby wyrażeniek-. Wynik nie byłby l-wartością — patrzrozdział o l-wartościach —więc podziałanie nań operatorem '++' spowodowałoby błąd.Warto pamiętać, że wyrażenie++w nie jestcałkowicie równoważne instrukcjiw=w+1. Ta druga forma jestnormalną instrukcją przypisania, a zatem najpierw zostanie obliczonawartość wyrażenia po prawej stronie, a potem lokalizacjal-wartości po lewej stronie. Zatem jeśliwjest wyrażeniemzłożonym (np. zawiera wywołania funkcji), to wyrażenie to będzieobliczane dwukrotnie. Natomiast podczas opracowywania wyrażenia++w, samobędzie obliczane jednokrotnie.Rzadko ma to jakieś znaczenie, ale czasem zrozumienie tego może nasustrzec przed trudno wykrywalnymi błędami.Operator identyfikacji typutypeidpozwala na uzyskanieidentyfikatora typu podczas kompilacji, a więc statycznie,jak również identyfikatora typu obiektu (ogólniep-wartości) w czasie wykonania programu, a więc dynamicznie(RTTI; run-time typeidentification). Ten temat omówimy bardziej szczegółowo wrozdziale o RTTI,ale jeden przykład zastosowania tego operatora podany jest poniżejw programie sizes. cpp.Operatory konwersji (rzutowania)pozwalają na, w miarę bezpieczną, konwersję wartości jednego typuna wartość innego typu. Ponieważ stosowanie konwersji często,choć nie zawsze, świadczy o złej konstrukcji programu i stwarzaokazję do użycia błędnych lub zależnych od implementacjikonstrukcji programistycznych, nadano tym operatorom celowotak długą i niewygodną do pisania formę. Konwersje rozpatrzymyw rozdziale im poświęconym.9. 3 Grupa operatorów o priorytecie 15Pierwsze trzy z opertatorów tej grupydotyczą pobierania rozmiaru za pomocą operatorasizeof. Reultat jest typusize_t (który jest tożsamy z pewnym typem całkowitymbez znaku, np.unsigned long). Operator ten jest jednoargumentowy.Argumentem może być nazwa typu (w nawiasie okrągłym) lub wyrażenie (nawiasjest wtedy niekonieczny) albo tak zwany pakiet. Rozpatrzmy przykład:
P53:Operator sizeof
1. #include <iostream> 2. #include <typeinfo> 3. using namespace std; 4. 5. typedef int TABINT15[15]; ➊ 6. 7. void siz(TABINT15 t1, TABINT15& t2) { ➋ 8. cout << "G. t1 w siz: " << sizeof t1 << endl; 9. cout << "H. t2 w siz: " << sizeof t2 << endl; 10. } 11. 12. int main() { 13. TABINT15 tab1; ➌ 14. int tab2[15]; ➍ 15. int *tab3 = tab2; ➎ 16. 17. if (typeid(tab1) == typeid(tab2)) 18. cout << "A. Typy tab1 i tab2 takie same" << endl; 19. else 20. Typy tab1 i tab2 nie takie same" << endl; 21. 22. if (typeid(tab2) == typeid(tab3)) 23. cout << "B. Typy tab2 i tab3 takie same" << endl; 24. else 25. Typy tab2 i tab3 nie takie same" << endl; 26. 27. cout << "C. TABINT15: " << sizeof(TABINT15) << endl; ➏ 28. cout << "D. tab1: " << sizeof tab1 << endl; ➐ 29. cout << "E. tab2: " << sizeof(tab2) << endl; ➑ 30. cout << "F. tab3: " << sizeof tab3 << endl; ➒ 31. siz(tab2, tab2); 32. }Wynik tego programuA. Typy tab1 i tab2 takie sameB. Typy tab2 i tab3 nie takie sameC. TABINT15: 60D. tab1: 60E. tab2: 60F. tab3: 8G. t1 w siz: 8H. t2 w siz: 60ilustruje warte zrozumienia własności C/C++.Na początku dołączamy plik nagłówkowytypeinfo, aby mieć dostęp do narzędzi związanych z identyfikacją typów(patrz rozdział o RTTI).W linii ➊ wprowadzamy, za pomocą znanej już nam instrukcjitypedef, inną nazwę typu „piętnastoelementowatablica liczb całkowitych” (patrzrozdział o instrukcji typedef).Jak widać z linii ➏ i z pierwszej liniiwydruku, operatorsizeofprawidłowo rozpoznał rozmiartypuTABINT15. Zauważmy też, że nie można tu pominąćnawiasów, boTABINT15jest nazwą typu.W liniach ➌ i ➍ definiujemy tablicetab1itab2nadwa sposoby — za pomocą wprowadzonej nazwyi bezpośrednio. Porównując typy tych zmiennychwidzimy, że rzeczywiście są one takie same (linia 'A' wydruku).W linii ➎ definiujemy zmiennątab3int*i przypisujemy do niej wartość zmiennejtab2. Przypisaniejest prawidłowe, ale nie zapominajmy, że zachodzi przy tym konwersjastandardowa: typynie są takie same,co widzimy z linii 'B' wydruku.W liniach ➏-➒ drukujemy rozmiary typui zmiennychtab1,tab3.Wszystkie rozmiary, za wyjątkiemtab3, wynoszą 60,co odpowiada tablicy piętnastu czterobajtowych liczb.Natomiast rozmiarjest 8, gdyż jest to zmiennatypu wskaźnikowego, a nie tablicowego.W ostatniej linii posyłamy tę samą tablicępoprzez dwa argumenty do funkcjisiz. Pierwszy parametrfunkcji jest typuTABINT15, więc wydawałoby się,że funkcja „wie”, że argument będzie tablicą. Drukując jednak(linia 'G' wydruku) wewnątrz funkcji rozmiar zmiennejt1,widzimy, że jest to wskaźnik o rozmiarze 8 — jest tak,gdyż przy wysyłaniudo funkcji przez wartośći tak zmienna została zrzutowana do typu wskaźnikowego(na stosie został położony adres pierwszego elementu tablicyi nic więcej).Drugi parametr funkcjisizjest zadeklarowany jakoreferencja. Teraz żadnej konwersji nie ma, bo nie jest w ogóletworzona żadna zmienna lokalna, której należałoby przypisaćwartość argumentu. Wewnątrz funkcjit2jest teraz innąnazwą dokładnie tej samej zmiennej, która w funkcjimainnazywa siętab2. Zatem i informacja o typie jest ta samai 'sizeof t2' drukuje 60 (linia 'H' wydruku).Przedrostkowe operatory zmniejszenia i zwiększenia(predekrementacji i preinkrementacji) działają podobnie do przyrostkowychoperatorów zmniejszenia i zwiększenia. Są jednak ważneróżnice: operatory te zmniejszają (zwiększają) swój argumentprzed jego użyciem do obliczenia wartościwyrażenia, w skład którego wchodzą.int b = ++a;wynosi również 2, bo w trakcie opracowywania drugiej instrukcjizmiennazostała zwiększona jeszcze przed wykonaniemprzypisania. Wynikiem działania operatora preinkrementacjilub predekrementacji jest l-wartość (pamiętamy, że taknie było dlaoperatorów postinkrementacji lub postdekrementacji) —tak więc wyrażenie++++a byłoby legalne.Operatory negacji:bitoweji logicznejomówimy poniżej razem z innymi operatorami logicznymi i bitowymi.Operator jednoargumentowy '+'jestwłaściwie operatorem identycznościowym, czyli takim, który „nic nie robi” (ang. no-op). Istnieje tylkodla wygody, aby wyrażenia typu 'k = +5' miały sens.Operatory wyłuskania adresu i dereferencji (& i*)były już omówione wrozdziale na temat typów danych.Operatorynewdeletesłużą dodynamicznego alokowania i zwalniania pamięci: będą omówionew rozdziale o zarządzaniu pamięcią.Ostatni operator z tej grupy, oznaczany parą nawiasów okrągłych,to operator rzutowania. Jest on operatoremjednoargumentowym: wynikiem działania tego operatora na p-wartośćpewnego typu jest odpowiadająca tej wartościp-wartość innego typu — tego wymienionego w nawiasie.Z operatora tego należy korzystać oględnie;w większości przypadków jego zastosowanie świadczy raczejo złym stylu programowania. Czasem jest jednak przydatny.Na przykład w drugiej instrukcji fragmentudouble x = 7;int k = (int)x;rzutowanie wartości zmiennejjest wskazane,gdyż wartość ta, jako wartość szerszego typu, może byćwpisana do zmiennej typu węższego, jakim jest typint, tylko zestratą informacji (precyzji). Choć nie jest to błąd, to kompilatorzwykle wypisuje ostrzeżenia; jeśli zastosujemy jawne rzutowanie,ostrzeżeń nie będzie.Rzutowanie zawsze działa na p-wartość i w wynikudaje inną p-wartość, ale nigdy l-wartość. W szczególnościrzutowanie nie zmienia typu żadnej zmiennej —typu istniejącej zmiennej zmienić się nie da!Zamiast operatora rzutowania w C++ zaleca się stosowaniebezpieczniejszych operatorów konwersji, wymienionych w tabeli w grupie odpowiadjącej priorytetowi 16.Omówimy je bardziej szczegółowo9. 4 Grupa operatorów o priorytecie 14Należą do tej grupy dwa operatory wyboru składowej,które omówimy w rozdziałach na temat klas w C++(rozdział o wskaźnikach na składowe).
9. 5 Operatory arytmetyczneWymienione w tabeli operatory arytmetycznemająoczywisteznaczenie i ich działanie jest niemal takie samo jakw większości innych języków. Występują czasem drobne różnice:np. w Javie operator reszty'%' może mieć dowolne agumenty numeryczne, również typudouble.W C/C++ operator ten wymaga argumentów typu całkowitego.Pewien kłopot mogą sprawiać wyrażenia z wartościami ujemnymi jakoargumentami. Wiemy, że dzielenie liczb typu całkowitego daje w wynikuliczbę całkowitą, czyli ewentualna część ułamkowa jest obcinana.Jeśli wynik jest dodatni, to dokładna wartość ilorazu jest obcinanaw dół, czyli w kierunku zera, natomiast jeśli wynik jest ujemny,to obcięcie jest w górę, a więc też w kierunku zera.Dla operatora reszty spełniona jest zawsze zasada
a = (a/b)*b + a%bdlaróżnego od zera. Wynika z niej, przy założeniu,że obcinaniew dzieleniu całkowitoliczbowym jest zawsze w kierunku zera,zasada następująca: wartośća%b
jest równa co do modułuwartości|a|%|b|
i ma znak taki, jaki znak maa
(kreski oznaczają wartość bezwzględną). Ilustruje to poniższyprogramik:P54:mod. cppOperator reszty
2. using namespace std; 3. 4. int main() { 5. int i, j; 7. i = 19; j = 7; cout << " 19 / 7 = " << i/j << endl; 8. i =-19; j = 7; cout << "-19 / 7 = " << i/j << endl; 9. i = 19; j =-7; cout << " 19 / -7 = " << i/j << endl; 10. i =-19; j =-7; cout << "-19 / -7 = " << i/j << endl; 12. i = 19; j = 7; cout << " 19% 7 = " << i%j << endl; 13. i =-19; j = 7; cout << "-19% 7 = " << i%j << endl; 14. i = 19; j =-7; cout << " 19% -7 = " << i%j << endl; 15. i =-19; j =-7; cout << "-19% -7 = " << i%j << endl; 16. }którego wynikiem jest19 / 7 = 2-19 / 7 = -219 / -7 = -2-19 / -7 = 219% 7 = 5-19% 7 = -519% -7 = 5-19% -7 = -5Ta zasada będzie inna, jeśli obcinanie w dzieleniu całkowitoliczbowymjest zawsze w dół, a nie w kierunku zera (tak może sie zdarzyć dla starychkompilatorów). Dlatego lepiej unikać stosowania operatora resztydla liczb ujemnych.9. 6 Operatory relacyjne i porównaniaOperatory relacyjne('<', '<=', '>','>=')i porównania==', '! =') mają oczywistąinterpretację. Wyrażenie '==b' mawartość logiczną odpowiadającą na pytanie czy wartośćjest równa wartościb. Wyrażenie! =b' ma wartość logicznąodpowiadającą na pytanie czy wartośćjest różna od wartościb.Argumentami mogą być dwa skalary,czyli p-wartości liczbowe, lub (choć nie zawsze) dwa adresy(wartości zmiennych wskaźnikowych lub wynik operacji wyłuskania adresu).Jest to nieco inaczej niż w Javie, gdzie adresy (czy raczej odniesienia)mogły być argumentami wyłącznie operatorów porównania==' i '! ='), ale nie operatorów relacyjnych.W C++ można porównywać adresy za pomocą operatorów relacyjnych podwarunkiem, że są to adresy elementów tej samej tablicy.Wynikiem operacji jest wartość logicznatruelubfalse. Jak mówiliśmy (patrzrozdziału o typie logicznym),wartości logiczne reprezentowane są w zasadzieprzez wartości całkowite: wartość 0 jest równoważnafalse,a dowolna wartość niezerowatrue. Obowiązuje torównież dla wartości wskaźnikowych: wartośćpusta (nullptr) jest interpretowana jakofalse, akażda inna jakotrue.
9. 7 Operatory bitoweOperatory bitowe wymienione są w tabeli na pozycjach odpowiadającychpriorytetom 11 (przesunięcia bitowe), 8, 7 i 6.Argumentami muszą być wartości całkowite, wynik też jest typu całkowitego.Obowiązują przy tym normalne reguły konwersji argumentów do typu wspólnego.Operatory bitowe nie „interesują” się wartościa liczbowąargumentów, ale ich reprezentacją bitową. Przypomnijmy, żezwyczajowo numeruje się bity reprezentujące wartości zmiennych,poczynając od zera, od bitu najmniej znaczącego (odpowiadającegowspółczynnikowi przy zerowej potędze dwójki) do bitu najbardziejznaczącego. Reprezentując graficznie układ bitów, bit zerowyumieszcza się po prawej stronie, a bit najbardziej znaczący po lewej(patrzrozdział o typach całkowitych).Rozpatrzmy zatem bardziej szczegółowo działanie poszczególnychoperatorów bitowych na wartości liczbowe. Jeden z nich — negacjabitowa — jest jednoargumentowy, pozostałe są dwuargumentowe.Operator bitowej negacji ('∼'),działając na wartość całkowitą zwracanową 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 onnieustawiony (coodpowiada umownej wartości 0) — jak na rysunku, gdzie dlauproszczenia zilustrowane jest działanie opertatora negacjibitowej dla wartości typuchar, a więc jednobajtowej(ośmiobitowej). Oczywiście negacja jest inwolutywna, czyli dwukrotnejej zastosowanie prowadzi do wartości wyjściowej.Alternatywa bitowa('|') jest operatorem dwuargumentowym: dla kolejnychpozycji sprawdzane są pojedyncze bity w obu argumentach i obliczanaich suma logiczna: w wyniku bit na odpowiadającej pozycji jestjedynką (bit ustawiony), jeśli w którymkolwiek argumencie bitna tej pozycji był ustawiony, a zero, gdy w obu argumentach na tejpozycji również występowało zero (jak na rysunku poniżej).
Alternatywa bitowa (tzw. ORowanie) jest często stosowana doustawiania najrozmaitszych opcji. Na przykład w C++, otwartedo czytania lub pisania pliki mają szereg trybów, którym odpowiadająpewne stałe całkowite, np.ios::in,ios::out,zdefiniowane w klasieios(dlatego odwołujemy się do nichpoprzez operator zakresu klasy — czterokropek).W programie bits. cpp drukujemy reprezentacjębitową kilku tego rodzaju stałych. Widzimy, że są to pełne potęgidwójki, a więc w ich reprezentacji bitowej występuje tylko jednajedynka na odpowiedniej pozycji — dlaios::outpozycji 4, dlaios::appna pozycji 0 itd.Zatem na przykład stałą określającą trybotwartego pliku jako pliku jednocześnie do pisania i do czytaniabędzieios::in | ios::outi będzie zawieraćjedynki na pozycjach 3 i 4 (konkretne pozycje mogązależeć od implementacji — należy zawsze odwoływać się dotych stałych poprzez ich nazwy).Koniunkcja bitowa('&') jest też operatorem dwuargumentowym: dla kolejnychpozycji sprawdzane są pojedyncze bity w obu argumentach i obliczanyich iloczyn logiczny: w wyniku bit na odpowiadającej pozycji jestjedynką (bit ustawiony), jeśli w obu argumentach bitna tej pozycji był ustawiony, a zero, gdy w którymkolwiekz argumentów na tej pozycji występowało zero (patrz rysunek).
Koniunkcja bitowa (tzw. ANDowanie) jest często stosowana dotzw. maskowania. Wspomnieliśmy, że stała określająca trybpliku ma na pozycji trzeciej (licząc od zera) jedynkę, jeśliustawiony plik został otwarty w trybiein, a zero,jeśli nie (co to dokładnie znaczy, dowiemy się wrozdzialo o operacjach we/wy).Jeśli stała określająca tryb nazywa siętryb, tomaskowanie jej ze stałą 8 (= 23) odpowie na pytanie, czy bitinjest czy nie jest ustawiony. Reprezentacja 8 składasię z samych zer, z wyjątkiem pozycji trzeciej(czwarty bit od prawej), na której bitjest ustawiony. Zatem obliczając koniunkcję dostaniemy na wszystkichinnych pozycjach na pewno zero, na pozycji czwartej zaś jedynkę,jeśli wtrybten bit był ustawiony, a zero, jeśli nie był.Zatem wartość wyrażeniatryb & 8będzie niezerowawtedy i tylko wtedy, jeśli bitbył w zmiennejustawiony, niezależnie od stanu innych bitów w tejzmiennej.Operator bitowej różnicy symetrycznej('^') jest też operatorem dwuargumentowym: dla kolejnychjest ich różnica symetryczna: wynikowy bit na odpowiadającej pozycjijest jedynką (bit ustawiony), jeśli w obu argumentach bityna tej pozycji były różne, a zerem jeśli w obu argumentachna tej pozycji występowały bity takie same — dwa zera albo dwiejedynki.
Różnica symetryczna (obliczanie jej nazywane bywa XORowaniem) maciekawą i użyteczną własność,wynikającą z natępującej tabelki logicznej dla tego działania:z której wynika, że dwukrotne XORowanie dowolnego bitu b zdowolną maską m przywraca pierwotną wartość tego bitu — wtabeli kolumna pierwsza i ostatnia są takie same. Ta własnośćXORowania jest wykorzystywana między innymi w grafice komputerowej.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 argumentma układ bitów jakw górnej części rysunku. Przesunięcie w tej zmiennej bitóww lewo o dwa, 'w = w << 2', odpowiada przesunięciuwszystkich bitów o dwie pozycje w lewo (w kierunku pozycji bardziejznaczących). Bity „wychodzące” z lewej strony są traconebezpowrotnie. Z prawej strony „wchodzą” bity zerowe. Tak więcpo wykonaniu tej instrukcji reprezentacja zmiennejtaka jak w środkowej części rysunku. Analogicznie, po przesunięciuteraz bitów w prawo o trzy pozycje ('w = w >> 3') otrzymamyreprezentację zmiennejjak w dolnej części rysunku(pod pewnymi warunkami — patrz niżej).
O ile przesuwanie w lewo jest zawsze dobrze określone wedługwspomnianych zasad, rzecz jest bardziej skomplikowana przyprzesuwaniu 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.W Javieistnieją dwa operatory bitowego przesunięcia w prawo:w przypadku operatora '>>' z lewej strony „wchodzi”taki bit, jaki był przed przesunięciem na najstarszej pozycji(czyli z lewej strony) — jeśli było to zero, wchodzi zero,jeśli jedynka, wchodzi jedynka. Mówimy, że reprodukowanyjest bit znaku. Taka konwencja powoduje, że dlaliczb ze znakiem (patrzrozdział o typach całkowitych)przesunięcie w prawo o jedną pozycję odpowiada dla liczb parzystychdzieleniu przez dwa, zarówno dla liczb dodatnich,jak i ujemnych — podobnie jak przesuwanie w lewo odpowiadamnożeniu przez potęgę dwójki (dla liczb nieparzystych i dlaprzesuwania w prawo jest tu pewna dodatkowa komplikacja, w którąnie będziemy się wgłębiać, jest to dobre ćwiczenie sprawdzającerozumienie bitowych reprezentacji liczb; ma to związek z konwencjąobcinania części ułamkowej przy dzieleniu całkowitoliczbowym —w dół czy w kierunku zera? ). Inny operator wJavie, '>>>', oznacza przesunięcia w prawo takie, żez lewej strony wchodzą zawsze zera.W C/C++ sprawa nie jest taka jasna. Istnieją tu specjalne typy bezznaku (unsigned), za to nie istnieje „potrójny”operator przesunięcia '>>>' w prawo.Wobec tego w większości implementacji przyjęto następującąkonwencję: jeśli typem wartości lewego argumentu jesttyp bez znaku (unsigned), to przy przesuwaniuw prawo wchodzą z lewej strony bity zerowe;jeśli natomiast typem wartości lewego argumentu jesttyp ze znakiem (signed), to przy przesuwaniuw prawo reprodukowany jest z lewej strony bit znaku, czyli skrajnylewy 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 przesunieęcia, zawsze powinien być nieujemny i mniejszyod rozmiaru w bitach wartości, na której dokonujemy przesunięcia.W Javiejest dobrze określone przez specyfikację języka, co się dzieje,jeśli te warunki nie są spełnione; w C/C++ może to zależećod implementacji i wobec tego lepiej takich konstrukcji unikać.Na zakończenie rozpatrzmy przykład ilustrujący to, o czymmówiliśmy na temat operacji bitowych.
P55:Operacje na bitach
4. void bitsChar(char k) { 5. int bits = 8*sizeof(k); ➊ 6. unsigned char mask = 1<<(bits-1); 7. for (int i = 0; i < bits; i++) { 8. cout << (k & mask ? 1 : 0); 9. mask >>= 1; ➋ 10. } 11. cout << endl; 12. } 13. 14. void bitsShort(short k) { 15. int bits = 8*sizeof(k); 16. unsigned short mask = 1<<(bits-1); 17. for (int i = 0; i < bits; i++) { 18. cout << (k & mask ? 1 : 0); 19. mask >>= 1; 20. } 21. cout << endl; 22. } 23. 24. void bitsInt(int k) { 25. int bits = 8*sizeof(k); 26. unsigned int mask = 1<<(bits-1); 27. for (int i = 0; i < bits; i++) { 28. cout << (k & mask ? 1 : 0); 29. mask >>= 1; 30. } 31. cout << endl; 33. 34. int main() { 35. short s = -1; int i = 259; 36. 37. cout << "char 'a': "; bitsChar('a'); 38. cout << "short -1: "; bitsShort(s); 39. cout << "int 259: "; bitsInt(i); 40. cout << endl; 41. cout << "ios::in: "; bitsInt(ios::in); 42. cout << "ios::out: "; bitsInt(ios::out); 43. cout << "ios::app: "; bitsInt(ios::app); 44. cout << "ios::in | ios::out\n "; 45. bitsInt(ios::in | ios::out); 46. }Na początku tego programu definiujemy trzy niemal identyczne funkcje,których zadaniem jest wypisanie bitowej reprezentacjiargumentu. Funkcje różnią się tylko typem argumentu: może nimbyćchar,shortint.W rozdziale o szablonach funkcjidowiemy się, jak można było uniknąć pisania trzech wersjitak podobnych funkcji.Przyjrzyjmy się jednej z tych funkcji, na przykład funkcjibitsChar. W linii ➊ sprawdzamy, jaki jest rozmiar w bitachwartości argumentu(tu oczywiście wiemy, że będzie to 8,bosizeof(k) dlacharda jedynkę).Następnie tworzymy maskęmaskunsigned char.Chodzi nam o to, aby długość maski była taka jak długośćk, ale by była to zmienna na pewno bez znaku — w tensposób przy przesuwaniu w prawo będą z lewej strony „wchodzić”zera. Maskę inicjujemy wartością '1 << 7'(bobitswynosi 8). Reprezentacja jedynki to siedem bitówzerowych i jeden, prawy (czyli najmłodszy), bit ustawiony.Przesuwając ten układ bitów w lewo otrzymamy liczbę, którejreprezentacją jest jedynka i siedem zer (jedynka tym razem z lewejstrony). Robimy to po to, by pętla drukująca, która teraz następuje,przebiegała przez kolejne bity liczbyod lewej do prawej,a nie odwrotnie.Następnie w pętli obliczamy koniunkcję bitowąz maskąmask. Ponieważ maska ma tylko jeden bitustawiony, w ten sposób sprawdzamy, czy odpowiedni bit jest teżustawiony wk. Jeśli tak, to wynikiem koniunkcji będziejakaś wartość niezerowa, interpretowana jakow pierwszymargumencie operatora selekcji, a zatem wartością tejże selekcji(k&mask? 1: 0)' będzie jedynka, która zostaniewypisana na ekranie. Jeśli wodpowiedni bit nie jestustawiony, wydrukowane zostanie zero.W linii ➋ przesuwamy bity w masce o jeden w prawo. Ponieważzadbaliśmy, aby maska była typu bez znaku, z lewej strony będąwchodzić same zera. Zatem w kolejnych przebiegach pętlimaska cały czas będzie zawierać dokładnie jedną jedynkę,„wędrującą” od lewej do prawej. A więc w kolejnychprzebiegach pętli sprawdzone i wydrukowane będą, w kolejności odlewej do prawej, wszystkie bity zmiennejk.Podobnie działają pozostałe dwie funkcje:bitShortbitInt— jedyna różnica to typ argumentu.W programie głównym drukujemy reprezentację bitową kilkuliczb całkowitych. Wyniki wyglądają następująco:char 'a': 01100001short -1: 1111111111111111int 259: 00000000000000000000000100000011ios::in: 00000000000000000000000000001000ios::out: 00000000000000000000000000010000ios::app: 00000000000000000000000000000001ios::in | ios::out00000000000000000000000000011000Widać, że (jak o tym mówiliśmy wrozdziale o typach całkowitych),reprezentacją liczby -1 są jedynki na wszystkich bitach.Znak 'a' odpowiada, jak łatwo policzyć, wartościcałkowitej26 +25 + 1 = 64 + 32 + 1 = 97, co rzeczywiściejest kodem ASCII litery 'a'.Dalej ilustrujemy to, co mówiliśmy o stałychitd.Widzimy, że są to pełne potęgijedynka na odpowiedniej pozycji: w stałejios::truncna pozycji czwartej (licząc od zera), a w stałejna pozycji pierwszej. Obliczając ich alternatywę (sumę) bitowąotrzymujemy liczbę, w której reprezentacji bitowej te i tylko tedwa bity są ustawione (ostatnia linia wydruku).9. 8 Operatory logiczneArgumentami operatorów logicznych&&' (koniunkcja), '' (alternatywa)! ' (negacja)mogą być zarówno wartości typubooljak i wartości całkowite;w tym ostatnim przypadku wartości zostaną zinterpretowane wedługnormalnej zasady: 0→niezerotrue. Obliczonawartość jest typu logicznego: alternatywa (suma logiczna) daje wynikwtedy i tylko wtedy gdy choć jeden z argumentów matrue, natomiast koniunkcja (iloczyn logiczny)ma wartośćtylko jeśli oba argumenty są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
Na przykład, jeślia,rsą zmiennymi typu całkowitego, to przypisanie
- dla koniunkcji ('&&') prawy argument niebędzie w ogóle obliczany, jeśli lewy argument okazałsię równyfalse— całe wyrażenie musi bowiemwtedy mieć wartośćniezależnie od wartościprawego argumentu;
- dla alternatywy prawy argument niebędzie obliczany, jeśli lewy okazał się— całe wyrażenie musi bowiem mieć wtedy wartośćniezależnie od wartości prawego argumentu.
r = a && b;jest równoważneif (a == 0)r = 0;else{if (b == 0) r = 0;else r = 1;}a przypisanier = a || b;if (a ! = 0)r = 1;if (b ! = 0) r = 1;else r = 0;Rozpatrzmy jeszcze jeden przykład ilustrujący skrótowośćdwuargumentowych operatorów logicznych:P56:skrot. cppSkrótowość operatorów koniunkcji i alternatywy
4. bool fun(int k) { 5. k = k - 3; 6. cout << "Fun zwraca " << k << endl; 7. return k; 8. } 9. 10. int main() { 11. if ( fun(1) && fun(2) && fun(3) && fun(4) ) ➊ 12. cout << "Koniunkcja true" << endl; 13. else 14. cout << "Koniunkcja false" << endl; 15. 16. if ( fun(1) || fun(2) || fun(3) || fun(4) ) ➋ 17. cout << "Alternatywa true" << endl; 18. else 19. cout << "Alternatywa false" << endl; 20. }W linii ➊ sprawdzany jest warunek w postaci koniunkcjiwartości logicznych zwracanych przez funkcjęfun(równiedobrze mogłyby to być wartości całkowite). Ponieważfun(3) zwraca 0 czyli logicznefalse, funkcjaw ogóle nie zostanie już wywołana z argumentem 4, bo wynik już jestznany: wartością całego wyrażenia w nawiasie musi byćniezależnie od tego, co zwróciłaby funkcjaargumentu 4. Widzimy to z wydrukuFun zwraca -2Fun zwraca -1Fun zwraca 0Koniunkcja falseFun zwraca -2Alternatywa truePodobnie dla alternatywy w linii ➋. Już pierwsze wywołaniefunkcjidało wynik(odpowiada toniezerowej wartości zwracanej, w tym przypadku -2). Alternatywajest prawdziwa, gdy choć jeden argument jesttrue, wobec tegopo wywołaniufun(1) wynik całego wyrażenia jest znany(true) i wywołańfun(2),fun(3)fun(4) nie będzie.9. 9 Operatory przypisaniaW grupie o priorytecie 2 wymienione sąoperatory zwykłego przypisania=') oraz złożone operatory przypisania.Lewa strona przypisania musi być zawsze l-wartością. Tak więc
double x;x + 2 = 7; // NIEjest niepoprawne, natomiastdouble x, *y = &x;*(y + 2) = 7;byłoby legalne (choć prawdopodobnie bez sensu), bo wyłuskaniewartości daje w wyniku l-wartość.Wykonanie przypisaniapolega na obliczeniu wartości prawej strony i umieszczeniu wynikupod adresem l-wartości występującej po stronie lewej. Zauważmyasymetrię: prawa strona mówi co policzyć, lewa gdziezapisać wynik.Wartością i typem całego wyrażenia przypisania jest wartość ityp lewej strony po wykonaniu przypisania.Całe przypisanie jest l-wartością. Na przykładint m = 1, n = 2;(m=n) = 6;cout << m << " " << n << endl;drukuje '6 2'.W programie poniżejP57:przypis. cppWykorzystanie wartości przypisania
4. int main() 5. { 6. int k; 7. while ( (k = cin. get()) ! = '\n' ) 8. cout << "Wprowadzono znak '" << (char)k 9. << ”, o kodzie ASCII " << k << endl;w linii 7 przypisujemy dowartośc znaku — czyli jegokod ASCII — odczytanąz klawiatury za pomocą metodygetwywołanej na rzeczobiektucin— patrzrozdział o operacjach we/wy.Całe przypisanie '(k=cin. get())' ma wartośćpo przypisaniu; tę wartość porównujemy z predefiniowaną stałąEOF, oznaczającą koniec strumienia danych. Zauważmy,że nawias wokół tego wyrażenia był konieczny, bowiempriorytet porównania,! =, jest wyższy niżpriorytet operatora przypisania, a nam chodzi o to, aby najpierwdokonać przypisania, a dopiero jego wynik porównać zEOF.Przykładowe uruchomienie tego programu dajecpp> g++ -pedantic-errors -Wall -o przypis przypis. cppcpp>. /przypisAla ma... [ENTER]Wprowadzono znak 'A', o kodzie ASCII 65Wprowadzono znak 'l', o kodzie ASCII 108Wprowadzono znak 'a', o kodzie ASCII 97Wprowadzono znak ' ', o kodzie ASCII 32Wprowadzono znak 'm', o kodzie ASCII 109Wprowadzono znak '. ', o kodzie ASCII 46cpp>Dzięki temu, że wartością całego wyrażenia z przypisaniem jestwartość lewej strony po jego wykonaniu,przypisania można stosować kaskadowo. Tak więcint k = 7, j, m;int n = m = j = k;jest prawidłowe: ponieważ wiązanie operatora przypisaniajest od prawej, wartością wyrażenia 'm=j=k',równoważnego 'm=(j=k)', jest wartośćpo przypisaniu (czyli w naszym przypadku 7). Ta wartość zostanieużyta do zainicjowania definiowanej zmiennejn.Efektem ubocznym będzie nadanie wartości również zmiennymj. Zauważmy, że instrukcja byłabybłędna, gdyby któraś ze zmiennychm,j,nie była utworzona wcześniej.Złożone operatory przypisaniapozwalają na prostszy zapis niektórych przypisań: tych,w których ta sama l-wartość występuje po lewej i prawej stronieprzypisania. Zamiast instrukcjia = a @ b;gdzie symbol ” oznacza któryś z operatorówmożna użyć instrukcjia @= b;Drobna różnica, najczęściej bez znaczenia,pomiędzy tymi instrukcjami polega na tym, że w drugiej z nichjest obliczana jednokrotnie,a w pierwszej dwukrotnie. Zwykle druga z tych form,'a = b', jest efektywniejsza.Jako przykład zastosowania rozpatrzmy program:P58:zloz. cppPrzypisania złożone
4. void bitsInt(int k) { 5. unsigned int mask = 1<<31; 6. for (int i = 0; i < 32; i++, mask >>= 1) { 7. cout << (k & mask ? 1 : 0); 8. if (i%8 == 7) cout << " "; 9. } 10. cout << endl; 11. } 12. 13. int main() { 14. unsigned int k = 255<<24 | 153<<16 | 255<<8 | 255; ➊ 15. cout << "k przed: "; bitsInt(k); 16. (k <<= 8) >>= 24; ➋ 17. cout << "k po: "; bitsInt(k); 18. }FunkcjabitsIntjest tu podobna do tej z programu — tak samo służy do drukowania bitowejreprezentacji liczby całkowitej. W tej wersji z góry założyliśmy,że typjest czterobajtowy. Prócz tego przesuwaniemaski przenieśliśmy do części inkrementującej nagłówka pętli,umieszczając tam dwie instrukcje wyrażeniowe oddzieloneprzecinkiem (o operatorze przecinkowym —w jednym z następnych podrozdziałów).Dodaliśmy też drukowanie spacji po każdej grupie ośmiubitów, aby uczynić wydruk bardziej przejrzystym.W linii ➊ konstruujemy liczbę o z góry zadanej reprezenatcjibitowej. Wyrażenie '255 << 24' daje liczbę z samymi jedynkamiw najstarszym bajcie (255 to osiem jedynek, następnie przesunięteo 24 pozycje w lewo). Wyrażenie '153 << 16' to układbajtów 10011001 przesunięty w lewo o 16 pozycji, czylido bajtu trzeciego od lewej. Z kolei '255 << 8' daje osiemjedynek w bajcie drugim, a samo 255 — osiem jedynek w bajcienajmłodszym. Suma (alternatywa) bitowa „składa” wszystkie te bajty:otrzymujemy zatem liczbę o reprezentacji przedstawionej wpierwszej linii wydruku:k przed: 11111111 10011001 11111111 11111111k po: 00000000 00000000 00000000 10011001Operator przypisania złożonego zastosowaliśmy w linii ➋.Wyrażenie '(k <<= 8)' powoduje przesunięcie w zmiennejwszystkich bitów w lewo o osiem pozycji. Zatem zawartośćbajtu najstarszego zostaje „zgubiona”, bajt 10011001przechodzi na jego pozycję, a kolejne dwa, czyli pierwszy i drugi,stają się drugim i trzecim (od lewej). Bajt najmłodszy wypełnianyjest zerami. Wynik całego wyrażenia jest l-wartością, a zatem ma senszastosowanie do niego następnego przypisania złożonego: tym razemprzesuwamy zawartość zmiennejo 24 pozycje w prawo.Ponieważ zmiennajest bez znaku, z lewej strony„wchodzą” same zera, a 24 najmłodsze bity „wychodzą” z prawejstrony. Układ bitów 10011001 po tej operacji znajduje sięna pozycji najmłodszego bajtu. W efekcie widzimy, że cała operacjadaje w efekcie liczbę równą tej, której reprezentacja binarna zawartabyła w trzecim od lewej bajcie wyjściowej liczby. W podobny sposóbmoglibyśmy „wyciąć” zawartość pozostałych bajtów. Takie wycinaniepojedynczych bajtów stosuje się na przykład przy kodowaniu trzech(albo czterech) składowych koloru w jednej liczbie.9. 10 Operator warunkowyOperator warunkowy (selekcji)jest jedynym operatorem trzyargumentowym. Jego składnia:
b ? w1 : w2Najpierw obliczana jest wartość wyrażeniai ewentualnie konwertowana do typubool.Jeśli jest totrue, obliczane jest wyrażeniew1, a wyrażeniew2jest ignorowane.Wartością całego wyrażenia jest wartośćw1.false, obliczane jest wyrażeniew2, a wyrażeniew1Wartością całego wyrażenia jest wtedy wartośćw2.Jeśli zarównosą l-wartościami,to i wartość operatora warunkowego jest l-wartością.Klasyczny przykład zastosowania operatora selekcji to implementacjamaxzwracającej większą z wartości swychargumentów:int maxim(int a, int b) {return a > b ? a : b;Inny przykład zastosowania operatora selekcji podany zostanie wnastępnym podrozdziale.9. 11 Operator zgłoszenia wyjątkuJako przedostatni w tabeli występujeoperator zgłoszenia wyjątkuthrow: omówimy gow rozdziale o wyjątkach.
9. 12 Operator przecinkowyOperator przecinkowyjest dwuargumentowy: po dwóch stronach przecinka dwa wyrażenia
wyr1 , wyr2Działanie jego polega na:obliczeniu wyrażeniawyr1i zignorowaniuresultatu;wyr2; jego wartość stajesię wartością całego wyrażenia.Często operator przecinkowy stosuje się w części inicjalizacyjnejlub inkrementującej nagłówka pętlifor; przykład tolinia 7 programu .Inny, nieco dziwaczny, przykład ilustruje program: P59:przec. cppOperator przecinkowy
5. int r = 0; 7. 8. while (cin >> k, k) { ➊ 9. r += k > 0 ? (cout << "Dodatnia\n" , +1) 10. : (cout << "Ujemna\n" , -1); 11. } 12. cout << "Roznica ilosci dodatnich i ujemnych: " 13. << r << endl; 14. }Operator przecinkowy jest tu użyty w linii ➊ w warunkupętliwhile. Mamy tu pierwsze wyrażenie,'cin >> k', wczytujące kolejną daną z klawiatury,i drugie, po prostuk, dzięki któremu pętlaskończy się, gdy wczytana zostanie liczba 0 (gdyż wartościącałego wyrażenia przecinkowego jest wartość prawego argumentu).W pętli dododawana jest (operator złożonego przypisania)wartość nieco skomplikowanego wyrażenia. Jest to operator selekcjipodrozdział o operatorze selekcji— sprawdzający znakk; w każdym przypadku rezultatbędzie znów wartością wyrażenia przecinkowego. Dladodatnichbędzie to wartość(cout << "Dodatnia\n" , +1)czyli +1 z efektem ubocznym polegającym na wypisaniu słowa"Dodatnia". Analogicznie, dlaujemnychododjęte zostanie 1, a jako efekt uboczny wypisanebędzie słowo "Ujemna". Po wyjściu z pętli wypisywanajest różnica między ilością wczytanych liczb dodatnichi ujemnych. Na przykład wynik programu może być następujący:cpp> przec2Dodatnia6-3Ujemna-130Roznica ilosci dodatnich i ujemnych: 29. 13 Alternatywne nazwy operatorówNiektóre operatory mają też formę czysto tekstową:
Tabela:Alternatywne nazwy operatorów tekstowa symboliczna tekstowa symboliczna and && and_eq &= bitand & bitor | compl ∼ not ! not_eq ! = or or_eq |= xor ^ xor_eq ^= Forma tekstowa może być zamiennie stosowana z formą wyrażonąza pomocą symboli nieliterowych.T. R. Werner, 25 lutego 2017; 22:31
Najważniejsza część zamienna
Wiele lat temu Ron Slee napisał: NAJWAŻNIEJSZA CZĘŚĆ ZAMIENNA TO TA, KTÓREJ NIE MASZ. To jest punkt widzenia klienta, z którym nie ma co dyskutować. Jeśli nie wyślesz mu tylko jednej pozycji z zamówienia liczącego kilkadziesiąt numerów katalogowych, to tak jakbyś nic nie wysłał. Bo jak ma zmontować rozebraną skrzynię biegów, jeśli tym brakującym elementem jest podkładka […]
4 pytania do kierownika magazynu części zamiennych
Jest wiele przyczyn, że klient nie otrzymuje natychmiast potrzebnej mu części zamiennej. Jedną z nich – najłatwiejszą do usunięcia – jest małe zaangażowanie pracowników logistyki części zamiennych. Kilkanaście lat temu Ron Slee podpowiedział doskonały sposób podniesienia poziomu jakości ich usług. Pracownik logistyki musi odpowiedzieć na cztery pytania, gdy zamierza zakończyć pracę i iść do domu: Czy wysłałeś […]
Umowa serwisowa cz. 1 – partnerstwo, zaufanie, elastyczność
Co zrobić, aby maszyny były gotowe do pracy, gdy będą potrzebne zgodnie z harmonogramem prac? Co zrobić, aby koszty ich posiadania i eksploatacji nie były wyższe, niż zaplanowane w kosztorysie? Jednym z lepszych sposobów utrzymania pod kontrolą kosztów kluczowych maszyn jest zawarcie dobrej umowy serwisowej. Taka umowa powinna również zmniejszyć ryzyko przestojów z przyczyn technicznych. Dobra umowa […]
Bezpośredni link do pobrania Techniczny podręcznik referencyjny, ogólne Brother Hl 5595dnh
Ostatnia aktualizacja Techniczny podręcznik referencyjny, ogólne Brother Hl 5595dnh