Podstawy programowania C/C++ Wykład 3
semestr II - studia dzienne zawodowe
Zakres tematyczny:
Instrukcje skoku
break,
continue,
return,
goto.
Modyfikator const (stałe nazwane)
Operatory języka C/C++
operatory arytmetyczne
operatory przypisania
operatory logiczne
operator sizeof
operator przecinek
operatory binarne
łączność operatorów
Przekształcanie typów
1. Instrukcje skoku
Do tej grupy instrukcji należą:
break;
continue;
return wyrażenie;
goto etykieta;
1.1. Instrukcja break
Służy do przerwania działania pętli for, while, do...while oraz instrukcji switch. Instrukcja ta, przenosi sterowanie do instrukcji bezpośrednio występującej po pętli lub po instrukcji switch. Jeśli mamy do czynienia z pętlami zagnieżdżonymi break przerywa tylko tą petlę lub instrukcję switch w której zostało użyte. Instrukcja break przerywa pętlę zanim wyrażenie testujące pętlę osiągnie wartość 0.
Program #include <stdlib.h> //random(), #include <stdio.h> void main (){ int licz, i, los; los = 7; randomize(); licz=0; do { licz++; if (random (30) = = los) break; } while (1); printf ("7 wylosowano za %d \ razem″ ,licz); } |
Program #include <stdlib.h> #include <iostream.h> void main(){ int licz, i, los; randomize(); licz=0; // czy poprawnie? for (i=1; i<10; i++) { do { licz++; if (random (30) = = i) break; } while (1); cout<<" i wylosowano za "<<licz <<" razem"; } } |
1.2. Instrukcja continue
Instrukcja ta może być używana tylko w pętli for, while, do...while. Powoduje przeniesienie sterowania na koniec pętli.
Program
#include <stdio.h>
#include <conio.h>
void main (){
int i;
float iloczyn;
clrscr ();
iloczyn =1;
for (i=1; i<30; i++ {
if ( (i % 3) = = 0) continue; //skok na koniec pętli,
iloczyn = iloczyn * i;
//koniec pętli: continue przenosi sterowanie w to miejsce.
}
}
1.3. Instrukcja return
Return pozwala funkcji na natychmiastowe przeniesienie sterowania do miejsca wywołania funkcji i przekazania wartości stojącej po słowie return (w przypadku funkcji main do systemu operacyjnego).
int main (){
..............
return 1;
}
1.4. Instrukcja goto
Instrukcja ta, wykonuje bezwarunkowe przeniesienie sterowania do miejsca gdzie występuje dana etykieta. Etykieta musi być umieszczona w tej samej funkcji. Poniższy program ilustruje użycie goto do przerwania pętli:
float w, i; i=0; while (1) { i++; w=1/(i+1); if (w<0.001) goto wyraz; } wyraz: w=sgrt (w); printf (″w=%f”, w);
|
i=0; do { i++; w=1/(i+1); } while (w>0.001); w=sgrt (w); printf (″w=%f”, w);
|
Instrukcja ta, wykonuje bezwarunkowe przeniesienie sterowania do miejsca gdzie występuje dana etykieta. Poniższy program ilustruje użycie goto do przerwania zagnieżdżonej pętli:
for(i=0; i<n; ++i)
for(j=0; j<m; ++j)
if (tab[i][j] = = a) goto znaleziono;
znaleziono: cout<<znaleziono element;
Modyfikator const (stałe nazwane)
float pi = 3.14; wartość pi może zostać zmieniona w programie
Obiekt, którego wartość nie ulega zmianie, nazywa się obiektem stałym. Deklaracja obiektu stałego.
const float pi = 3.14;
Inicjacjalizacja obiektu wartością musi sie odbyć w miejscu jego deklaracji. Potem nie jest to możliwe.
Deklaracja const float pi;
jest błędna, ponieważ deklarujemy obiekt stały więc jego wartość musi być inicjalizowana.
Poprawna:
const float pi = 3.14;
Grupa poniższych linii jest błędna, ponieważ stała nie może uzyskać wartości w instrukcji przypisania.
pi = 2.00; //błąd
pi = 3.14; //mimo, że próbujemy przypisać obiektowi const taką samą wartość będzie sygnalizowany błąd.
Dyrektywa preprocesora.
#define ESC 27
if (znak = = ESC) {...};
do {
.....
}
while (znak != ESC);
3. Operatory języka C/C++
a = i++ + j;
x*=(a!=0)?a:b++;
Operatory należą do jednej z dwu grup:
- operatory jednoargumentowe (unarne) wiązane z jednym argumentem.
- operatory dwuargumentowe (binarne) wiązane z dwoma argumentami.
3.1. Operatory arytmetyczne
- operatory : + - * /
Należą do grupy operatorów binarnych (ponieważ operują na dwóch obiektach - argumentach).
- operator % czyli modulo
Jest operatorem binarnym. W wyniki działania tego operatora otrzymujemy resztę z dzielenia argumentów stojących po obu stronach operatora:
45 % 6 -> 3
Argumenty tego operatora muszą być typu całkowitego. Argumenty tego operatora muszą być typu całkowitego. Jeśli argumenty operatora są nieujemne to , to reszta z dzielenia jest nieujemna w przeciwnym razie znak reszty zależy od implementacji.
Dla Borland 3.1, znak wyniku op1 % op2 jest zgodny ze znakiem oprandu op1.
z = - 45 % 6; z = - 45 % -6; z = 45 % -6;
z = -3 z = -3 z = 3
Priorytet operatorów *, /, % jest wyższy niż operatorów +, -.
- operatory jednoargumentowe + , -
Operator - zmienia wartość danego wyrażenia na przeciwną :
-a - (2*a + b)
- operatory inkrementacji i dekrementacji
++; --;
Realizują operacje zwiększania i zmniejszania o jeden.
Operatory dekrementacji i inkrementacji mogą mieć dwie formy:
- przedrostkową ( prefix): ++i; --i;
- przyrostkową (postfix): i++; i--;
W obu przypadkach wynikiem jest zmiana wartości i, ale wyrażenie ++i zwiększa i przed użyciem jej wartości, natomiast wyrażenie i++ zwiększa zmienną i dopiero po użyciu jej poprzedniej wartości. Jest to istotne w kontekście, w którym ważna jest wartość zmiennej i, a nie tylko jej zmiana np:
i = 10;
x = i++;
cout<<" x = "<<x;
daje w rezultacie x = 10, ale
i = 10;
x = ++i;
cout<<" x = "<<x;
daje w rezultacie x = 11.
int m=3, n=1, r;
r = (m++) + (++n); //r=3+2=5
Ponieważ są to operatory unarne (jednoargumentowe) to wyrażenie :
(i + j)++ jest błędne;
W przypadku, gdy chodzi tylko o sam efekt zwiększania o jeden, operatory post i prefiks-owe są równoważne.
for (i=11; i<34; i++) {...}
for (i=11; i<34; ++i) {...}
int i=1;
s[i] = i++; porządek wartościowania jest niezdefiniowany, może być wartościowane jako s[1]=1 lub jako s[2]=1.
3.2. Operatory przypisania
operatory: = += -= *= /= %=
float b = 15; float w,a = 5.2;
w=a+b;
Jeżeli, argumenty nie mają takiego samego typu, to o ile jest to możliwe wykonana zostaje niejawna konwersja typów.
int i=1.2; float b= 5/2; float b= 5.0/2;
Jeśli oba argumenty sa typu arytmetycznego to przed wykonaniem przypisania prawy argument jest przekształcany do typu lewego argumentu.
i = i + 2; bardziej zwięzły zapis: i += 2;
Uwaga!!!
x*= y + 1;
jest odpowiednikiem wyrażenia:
x = x * (y+1); NIE : x = x * y + 1;
Działa tu bowiem zasada:
wyr1 operator= wyr2 to samo co wyr1 = (wyr1) operator (wyr2)
3.3. Operatory logiczne
- operatory relacji
Są to operatory: > >= < <=
W wyniku działania tych operatorów otrzymujemy odpowiedź: prawda (true 1) lub fałsz (false 0) Priorytet tych operatorów jest taki sam .
- operatory przyrównania
Są to operatory = = !=.
Ich priorytet jest niższy niż priorytet operatorów relacji
Program 1
#include<iostream.h>
void main() {
int x = 10, x1 = 10;
if (x = 3) //błąd - przypisanie, zamiast porównanie. Kompilator generuje ostrzeżenie
x= 2*x1;
else
x= 2 - x1;
cout<<"x=*<<x};
- operatory sumy i iloczynu logicznego
Są to operatory:
| | - realizujący sumę logiczną (LUB - alternatywa)
&& - realizujący iloczyn logiczny (I - koniunkcja)
Argumenty mogą być typu arytmetycznego lub wskaźnikowego. Wynik jest typu int.
Przykład 1
#include<iostream.h>
void main(){
int ch;
ch = getche();
if ((ch >= 'a') && (ch <= 'z')) | | ((ch >= 'A') && (ch <= 'Z'))
cout<<"wprowadzony znak jest literą";
else if((ch >= '0') && (ch <= '9'))
cout<<"wprowadzony znak jest cyfrą";
else
cout<<"Wprowadzony znak nie jest cyfrą i nie jest literą";
}
Weźmy np. fragment kodu:
if ((x1 = = 0) && (x1 = = 15) && (z>10))
Operatory && i | | gwarantują wartościowanie argumentów od lewej do prawej.
Dla operatora && drugi argument nie będzie obliczony, jeśli pierwszy ma wartość zero.
Dla operatora | | drugi argument nie będzie obliczony, jeśli pierwszy ma wartość niezerową.
int i = -6, d = 4; //i=6, d=4
if ((i>0) && (d++)) {
...............
}
W instrukcji tej nie tylko wykonywana jest operacja logiczna , ale także chcemy zwiększyć wartość zmiennej d. Jeżeli, wyrażenie i>0 nie będzie prawdziwe to nie dojdzie do zwiększenia d.
int i = -6, d = 4; //i=6, d=4
if ((i>0) | | (d++)) {
...............
}
Priorytet operatora && jest większy niż operatora ||, ale oba są niższe niż operatorów przyrównania i relacji, więc w wyrażeniu:
i < gr - 1 && (c = getchar()) != '\n' | | c!= EOF
nie potrzeba dodatkowych nawiasów.
Ponieważ jednak priorytet operatora != jest wyższy niż przypisania, więc w wyrażeniu ( c=getchar() ) != '\n' potrzebne są nawiasy. Najpierw bowiem ma zostać przypisana wartość zmiennej c, a potem dopiero przyrównana do '\n'.
czytelnie: (i < (gr - 1)) && ((c = getchar()) != '\n' ) | | (c!= EOF)
- operator negacji !
Operator jest jednoargumentowy i stoi zawsze po lewej stronie operandu:
!n
Jeśli n = 0, to wartością wyrażenia jest PRAWDA.
Jeśli n różne od 0, to wartością wyrażenia jest FAŁSZ.
Instrukcja: if (!n) jest równoważna instrukcji: if (n = = 0)
Przykład 1
int x;
x= !(-12); -> x=0
x= !(120); -> x=0
x= !(0); -> x=1
int i = 0;
if (!i) cout<<"Wartość i jest zerowa";
while (i-2)
while ((2*x) && y)
while ((2*x) && (!y))
3.4. Operator sizeof ()
Operator: sizeof(), podaje rozmiary:
a) typów zmiennych sizeof (nazwa_typu)
b) obiektów sizeof(nazwa_obiektu)
Przykład 9
#include<iostream.h>
void main(){
int i;
cout <<"rozmiar typu integer w bajtach " << sizeof(int);
cout<<" \n rozmiar zmiennej i w bajtach " << sizeof(i);
}
3.5. Operator przecinkowy
Kilka wyrażeń oddzielonych przecinkiem wartościuje się od lewej do prawej, przy czym wartość lewego wyrażenia przepada.
i = 5;
b = (a = i, a=a+1);
Jeśli kilka wyrażeń stoi obok siebie i są oddzielone przecinkami, to wartością zmiennej b jest wartość prawego argumentu, czyli
b = 6 a = 6
Operator przecinkowy może wystąpić wyłącznie w nawiasie.
Jeżeli pominiemy nawiasy to:
b = a = i, a=a+1;
b = 5; a=6;
int x, a, c;
a=5; c=7;
c = (x=a, a=c, x);
czyli: x=5, a=7, c=5
Wyrażenie jest wartościowane od lewej do prawej.
3.6. Operatory bitowe
Operatory bitowe:
<< - przesunięcie w lewo
>> - przesunięcie w prawo
& - bitowy iloczyn logiczny
| - bitowa suma logiczna
^ - bitowa różnica symetryczna (bitowe exclusive OR)
~ - bitowa negacja
- operator przesunięcia w lewo <<
Jest to operator unarny, operujący na argumentach całkowitych: zmienna << ile miejsc
Służy do przesuwania bitów argumentu stojącego po lewej stronie operatora o liczbę pozycji określoną przez drugi argument (jego wartość musi być dodatnia). Zwolnione bity zostają uzupełnione zerami:
int x = 0x0010;
int wynik;
wynik = x << 2;
Powyższy fragment programu przesunie bity liczby x o 2 miejsca w lewo:
x = 0000 0000 0001 0000 //16 0x0010
wynik = 0000 0000 0100 0000 //64 0x0040
można:
x = x << 2.
Operacja ta ( x<<2) jest równoważna pomnożeniu zmiennej przez cztery:
x 0x0001 = 1
x<<2 0x0100 = 4
- operator przesunięcia w prawo >>
zmienna >> ile miejsc
Działa na operandach całkowitych. Przesuwa bity operandu stojącego polewej stronie operatora o ilość bitów wskazaną przez operand prawy.
Bity z prawej strony są gubione.
Jeśli operator pracuje na danej unsigned czyli bez znaku, to bity z lewego brzegu są uzupełnione zerami , jeśli jest to liczba ze znakiem, to bity z lewego brzegu są uzupełnione zerami lub jedynkami zależnie od implementacji, w pakiecie Borland 3.3 najbardziej znaczące bity uzupełniane są bitem znaku.
unsigned int x = 0x0ff0;
unsigned int wynik;
wynik = x >>2;
x 0000 1111 1111 0000
wynik 0000 0011 1111 1100
signed int f = 0xff00; //-256
signed int wynik;
wynik = x>>2; //-64
x 1111 1111 0000 0000
wynik 0011 1111 1100 0000 //uzupełnienie zerami
wynik1 1111 1111 1100 0000 //uzupełnienie bitem znaku
Implementacja Borland C++ daje wynik1.
- operatory negacji , iloczynu, sumy, różnicy symetrycznej
Operatory te działają na operandach całkowitych.
b1 |
b2 |
~ b1 (not) |
b1 & b2 (and) |
b1 | b2 (or) |
b1 ^ b2 (xor) |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
0 |
1 |
1 |
0 |
int x1 = 0x0f0f;
int x2 = 0x0ff0;
x1 0000 1111 0000 1111
x2 0000 1111 1111 0000
x1 & x2 0000 1111 0000 0000 zasłania pewną grupę bitów
x1 | x2 0000 1111 1111 1111 służy do ustawiania bitów
x1 ^ x2 0000 0000 1111 1111 tam gdzie bity operandów są
takie same ustawia 0, a gdzie różne 1
~ x1 1111 0000 1111 0000 zamienia bit 1 na 0 i odwrotnie.
#include<stdio.h>
void main(){
unsigned char upr=0;
char pisz, czyt, kas;
pisz=1;
upr = pisz<<2;
printf ("%d ",upr);
czyt=2;
upr = upr | czyt;
printf ("%d ",upr);
kas=0;
upr = upr | kas;
printf ("%d ",upr);
char maska=4;
pisz = (upr & maska)>>2;
printf ("\n%d ",pisz);
maska=2;
czyt = (upr & maska)>>1;
printf ("\n%d ",czyt);
maska=1;
kas = upr & maska;
printf ("\n%d ",kas);
getch()
};
3.7. Priorytet i łączność operatorów
Priorytety operatorów (od najwyższego) podaje zestawienie:
15. () [] -> L
14. ! ~ ++ -- + - * & sizeof P // adres i wskaźnik
13. * / % L
12. + - L
11. << >> L
10. < <= > >= L
9. == != L
8. & L
7. ^ L
6. | L
5. && L
4. || L
3. ? : P
2. = += -= *= /= %= ^= |= <<= >>= P
1. , L
Litery L i P określają w jaki sposób grupowane jest wykonywanie wyrażenia i tak np:
dla wyrażenia z operatorem + (L)
a + b + c + d + e
lewostronna łączność oznacza:
((((a + b) + c) + d) + e)
natomiast dla wyrażenia z operatorem = (P)
a = b = c = d = e
oznacza:
(a = ( b = (c = (d = e))))
5. Przekształcanie typów
Konwersja wykonywana jest w następujący sposób, zasady powinny być stosowane wg kolejności wymieniania:
- jeśli jeden argument jest long double drugi przekształcany jest do long double.
- jeśli nie to jeśli jeden argument jest double drugi przekształcany jest do double.
- jeśli nie to jeśli jeden argument jest float drugi przekształcany jest do float.
- jeśli nie to jeśli jeden argument jest unsigned long drugi przekształcany jest do unsigned long
- jeśli nie to jeśli jeden argument jest signed long drugi przekształcany jest do signed long
- jeśli nie to jeśli jeden argument jest unsigned int drugi przekształcany jest do unsigned int
- w przeciwnym przypadku oba operandy są typu int
Sposoby wykonywania konwersji
- char na int
powielanie bitu znaku dla signed char lub wypełnienie zerami dla unsigned char
-short na int
ta sama wartość
- unsigned short na unsigned int
ta sama wartość
-konwersja dłuższego typu całkowitego do mniejszego
Odrzucenie bardziej znaczących bitów i w razie ich ustawienia utrata informacji
- konwersja między typami calkowitymi o tych samych dlugościach
bezpośrednie skopiowanie wartości jednej zmiennej do drugiej. Warto wiedzieć, że można uzyskać ciekawe wyniki np.:
int -1000 po konwersji do unsigned int 64536
( bit znaku traktowany jako bit o wadze 2 do 15)
- konwersja rzeczywiste na całkowite
odrzucenie części ułamkowej liczby rzeczywistej
- konwersja całkowite na rzeczywiste
nadanie odpowiedniej liczbie rzeczywistej wartości liczby całkowitej
- konwersje między typami rzeczywistymi o różnych rozmiarach
zaokrąglenie do najbliższej wartości docelowego typu.
Oprócz automatycznego przekształcania typów, w dowolnym wyrażeniu można jawnie wymusić przekształcenie typów za pomocą:
- jednoargumentowego operatora rzutowania (casting)
Operator ten może mieć dwie formy:
(nazwa_typu) zmienna //forma języka C/C++ - notacja rzutowa
nazwa_typu (zmienna) //forma C++ - notacja funkcyjna
Notacja funkcyjna stosowana tylko do typów które mają prostą nazwę.
int i; float f;
i = (int) f; f = (float) i;
i = int (f); f = float (i);
f = i/2; f = float (i/2); f=float (i) /2;
Programowanie w języku C/C++
6
wykład 3
1 wykład 3
INSTRUKCJE
Instrukcje iteracyjne (pętle)
Instrukcje skoku
Instrukcje wyboru
break
while
if...else
continue
if
do..while
goto
switch
for
return