Operatory arytmetyczne

Transkrypt

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