KURS C/C++ WYKŁAD 7

Transkrypt

KURS C/C++ WYKŁAD 7
KURS C/C++
WYKŁAD 7
Typy pochodne.
Referencje
Referencja jest inną nazwą zmiennej.
Referencje tworzymy przy pomocy unarnego operatora &:
int a;
int &refer = a;
// referencja musi być inicjowana
Powyższe instrukcje deklarują zmienną całkowitą a i informują kompilator, że inną nazwa zmiennej
a jest refer.
int a = 123;
int &refer = a;
cout<< '\n'<< a;
//123
cout<< '\n'<< refer;
//123
refer ++;
cout<< '\n'<< a;
//124;
cout<< '\n'<< refer;
//124
Referencja nie jest kopią zmiennej, ale tą samą zmienną pod inną nazwą.
int a = 123;
int &refer = a;
cout<< &a << ' ' <<& refer;
Zostaną wyświetlone te same adresy.
* Referencje nie mogą istnieć bez zmiennej do której są przypisane i nie można na nich
przeprowadzać żadnych operacji jako na niezależnych obiektach. W związku z tym przy
deklarowaniu jednocześnie inicjujemy referencje.
Struktury i unie
1.2.1. Struktury
* Struktura jest agregatem elementów dowolnego typu.
Struktura jest klasą której wszystkie składowe są publiczne.
* Deklarację struktury rozpoczyna słowo kluczowe struct.
Struct Pracownik
struct Punkt
{
{
char nazwisko_imie{50];
int x, y;
int rok_ur;
int kolor;
char stanowisko[30];
};
};
Po słowie struct występuje nazwa struktury tu Pracownik (Punkt) zwana etykietą struktury.
Nazwy zmiennych występujących w strukturze nazywamy składowymi struktury.
Definicja struktury jest definicją nowego typu złożonego.
Zdefiniowaliśmy typ Pracownik i Punkt.
Deklaracje zmiennych kierownik i dyrektor, które są strukturami typu Pracownik.
struct Pracownik kierownik, dyrektor;
oraz
struct Punkt lewy_g, prawy_d;
Inny sposób deklaracji:
struct Pracownik
struct Punkt
{
{
char nazwisko_imie{50];
int x, y;
int rok_ur;
int kolor;
char stanowisko[30];
}lewy_g, prawy_d;
} kierownik, dyrektor;
Deklaracja struktury, która nie zawiera listy zmiennych jedynie opisuje wzorzec struktury.
Nie rezerwuje pamięci.
* Rozkład elementów w strukturze:
- adresy komponentów struktury rosnące w kolejności
definiowania
- adres pierwszego komponentu - jest nim adres struktury
- rozmiar struktury nie zawsze jest równy sumie rozmiarów
komponentów.
Do ustalenia rozmiaru struktury używać przeznaczonego do tego celu operatora sizeof (struct
Pracownik).
* Składowe różnych struktur mogą mieć te same nazwy bez obawy o konflikt.
* Inicjacja struktury:
Strukturę można zainicjować dopisując na końcu jej definicji listę wartości początkowych
jej składowych np:
Pracownik kierownik = {"Wincenty Witos", 1947, "kierownik"};
struct Pracownik
{
char nazwisko_imie{50];
int rok_ur;
char stanowisko[30];
} kierownik = {"Wincenty Witos",1947, "kierownik"};
struct Punkt lewy_g={x1, y1, k1}, prawy_d = {12, 14,RED};
struct Punkt
{
int x, y;
int kolor;
} lewy_g = {x1, y1, k1}, prawy_d = { 12, 14, RED};
Odwołania do składowych struktury
* Wybór elementu (operator .)
nazwa_zmiennej_typu_struktura.składowa
Dla zmiennej strukturalnej Pracownik odwołania są następujące:
gets (kierownik.imie_nazwisko);
cout<<kierownik.imie_nazwisko<< kierownik.rok_ur;
dla zmiennej strukturalnej Punkt odwołania są następujące:
int xg, yg;
cin>>lewy_g.x >> lewy_g.y ;
xg = lewy_g.x; yg = lewy_g.y;
* Wybór elementu (operator ->)
Pobranie adresu (operator &)
struct Pracownik *wsk, kierownik;
struct Punkt *ptr, lewy_kwadrat;
wsk = &kierownik;
ptr = &lewy_kwadrat;
nazwa_zmiennej_typu_adres_do_struktury->składowa
cout<<wsk->imie_nazwisko<<wsk->stanowisko;
ptr ->x = 18;
ptr->y= 4;
struct Pracownik *wsk;
wsk = new Pracownik;
strcpy (wsk->imie_nazwisko,�Kazimierz Brandys�);
cin >> wsk->stanowisko;
cin >>wsk->rok_ur;
char bank[]=�BPH�;
struct Test{
char p[50];
} *wsk;
wsk = new Test;
strcpy (wsk->p, bank); //cin>>wsk->p;
printf ("adres = %p
znaki: %s
wsk->p,
ekran: FFC4
pierwszy znak = %c",
wsk->p,
*wsk->p);
BPH
pierwszy znak = B
dl = strlen (wsk->p);
for (i=0; i<dl; i++) printf (" %c", *(wsk->p+i));
for (i=0; i<dl; i++) printf (" %c", *wsk->p++); //błąd
char *temp=wsk;
temp=wsk->p;
for (i=0; i<dl; i++) printf (" %c", *temp->p++); //poprawnie
Po wykonaniu pętli, adres będzie zmieniony, przesunie się o
dl*sizeof (char)
char bank[]=�BPH�;
struct Test
{
char *p;
} *wsk = new Test;
wsk->p=bank; //przypisujemy adres
printf ("adres: %p znaki: %s
pierwszy znak = %c", wsk->p, wsk->p, *wsk->p);
ekran: adres:FFC4 znaki: BPH
pierwszy znak = B
w poniższej pętli adres wsk się nie zmienia
dl = strlen (wsk->p);
for (i=0 i<dl; i++) printf (" %c", *(wsk->p+i));
Ekran:BPH
char *temp=wsk; - po przejściu przez poniższą pętlę adres będzie zmieniony, przesunie się
o 3 * sizeof (char)
for (i=0 i<dl; i++) printf (" %c", *wsk->p++));
* Zgnieżdżanie struktur
struct DATA {int d,m,r;};
struct OSOBA{
char nazwisko[30], imie[20], adres[50];
int plec;
struct DATA ur;
};
struct PRACOWNIK{
struct OSOBA osoba;
long pensja;
char dzial[20];
}
deklaracja z inicjalizacją:
struct
PRACOWNIK
przyjety
=
{"Linda","Jan","adres",
31, 2, 1940, 2000, "Grafika"};
0,
cout<<przyjęty.pensja;
cout<<przyjęty.dzial;
cout<<przyjęty.osoba.nazwisko;
cout<<przyjety.osoba.ur.d;
pobranie danych z klawiatury :
gets (przyjety.osoba.nazwisko);
cin>>przyjety.osoba.ur.d;
przypisanie danych:
przyjety.osoba.plec = 0;
przyjety.osoba.ur.r = 1940;
W zagnieżdżonych strukturach do składowych wykonuje się przy pomocy operacji przywoływnia
zmiennych zaczynając od struktury zdefiniowanej jako struktura najwyższego poziomu:
Jeśli dostęp do którejkolwiek ze struktur odbywa się poprzez wskaźnik wówczas operator .
zastępuje się operatorem ->.
* Tablice struktur.
struct Punkt dane[3];
Każdy element tablicy jest strukturą.
Można dokonać deklaracji:
struct Punkt dane[ ] = {0,0, 1, 1, 1,5, 16,8,12 };
struct Punkt {int x,y,kolor;} dane[ ]={0,0,1, 1, 1,5, 16,8,12}; Dostęp do składowych struktur, które
są umieszczonwe w tablicach:
const int il=3;
struct Punkt dane[il];
dostęp do składowej w dane[0]:
dane[0].x = 13;
struct Punkt *wsk_dane[il]; // 3 adresy do struktury
for (i=0; i<il; i++){
wsk_dane[i]= new Punkt; // rezerwacja miejsca dla
// struktury w ilości sizeof (Punkt)
wsk_dane[i]->x=i;
wsk_dane[i]->y=10*i;
wsk_dane[i]->kolor=i;
}
struct Punkt *wsk_st, *p1;
p1=wsk_st= new Punkt[il];
for (i=0; i<il; i++){
wsk_st->x=wsk_dane[i]->x;
wsk_st->y=wsk_dane[i]->y;
wsk_st->kolor=wsk_dane[i]-> kolor ;
wsk_st++;
}
Unie
Są dwa sposoby oszczędzania pamięci:
1.Używanie tej samej pamięci do przechowywania w różnym
unie
2. Umieszczenie w jednym bajcie więcej niż jednego obiektu
czasie różnych obiektów - pola bitowe
W programie potrzebna jest nazwa oddziału lub nazwa banku, ale nie są one używane jednocześnie.
struct element {
char nazwa_oddziału[50];
char nazwa_banku[30];
};
Deklaracji unii dokonuje się jej przy pomocy słowa kluczowego union.
union bank {
char nazwa_oddziału[50];
char nazwa_banku[30];
};
Nazwana unia jest strukturą, w której każda składowa ma ten sam adres. Unia oszczędza pamięć bo
pozwala przechowywać w tym samym miejscu pamięci obiekty różnych typów.
Dla unii rezerwuje się tyle pamięci ile wynosi liczba bajtów potrzebna na przechowanie najdłuższej
składowej unii.
Unia która ma nazwę jest pełnoprawnym i samodzielnym typem. Operacje dozwolone na
strukturach są dozwolone na uniach.
union bank {
//definicja typu bank
char nazwa_oddziału[50];
char nazwa_banku[30];
};
union bank un; //deklaracja zmiennej un typu bank
cout<<"rozmiar typu bank wynosi "<<sizeof (bank);
EKRAN: rozmiar typu bank wynosi 50
Inicjalizacja wartości składowych unii.
Unię można zainicjować podczas deklaracji, należy jednak pamiętać , że można to zrobić wartością
o typie jej pierwszej składowej, jeżeli typy nie będa zgodne wówczas o ile to możliwe zostanie
dokonana konwersja, w przeciwnym wypadku błąd.
union bank {
char nazwa_oddziału[50];
char nazwa_banku[30];
}un={�BPH oddział I}�;
union test {
char nazwa[50];
int numer;
}un={"BPH"} ;
union test {
char nazwa[50];
int numer;
}un={245};
union test {
//bez błędu kompilacji
int numer;
char nazwa[50];
}un={"BPH"} ; //błąd nie można dokonać konewersji
union Opis{
union Opis {
int a;
int a;
float b;
float b;
char c;
char c;
}dana = {65.19};
}dana = {�A�};
efekt:
efekt:
dana.a=65;
dana.a=65;
W obu przypadkach dokonana zostanie konwersja do typu int.
* Wybór elementu (operator . )
cout<<"dana.a="<<dana.a;
dana.a=65
cout<<"dana.b="<<dana.b;
dana.b=9.10e-44
cout<<"dana.c="<<dana.c;
dana.c=A //kod litery - 65
dana.a=12;
lub
dana.b=34.56;
union Opis = 12;
lub dana.c=�a�;
//bład, instrukcja jest niepoprawna
Kompilator nie wie, która składowa unii jest aktualnie używana, a więc właściwa kontrola typów
jest niemożliwa. Musimy sami kontrolować typy i odczytywać wartość korzystając z tego pola za
pomocą którego była ona zapisywana.
if (i) dana.a=16; else dana.b = - 100;
w = sqrt (dana.a); - błąd bo nie jest pewne czy przypisano
wartość zmiennej dana.a czy dana.b
if (i) w=sqrt (dana.a) else w= dana.b; //poprawnie
* Wybór elementu (operator -> )
wskaźnik_do_unii -> skladowa.
Odwołanie do unii przez adres:
union test {
char nazwa[50];
int numer;
}dana, *adr;
adr = &dana; lub
// union test dana, *adr;
adr= new test;
odwołanie do składowych unii:
adr->numer = 102;
strcpy (adr->nazwa, �UniZeto�);
Unie anonimowe
union {
int a;
float b;
char c;
};
Tak zdefiniowana unia jest tzw. unia anomimową. Takie unie same nie mają nazwy, jak też nie ma
nazwy jedyny egzemplarz tej unii.
Do składników tej unii odwołujemy bezpośrednio poprzez nazwę składowej.
a = 4;
b = 1.2;
z=�?�;
struct element {
char typ;
union {
char nazwa_banku[30];
char nazwa_oddziału[50];
};
}st;
switch (st->typ) {
case �o�: cout<<st.nazwa_banku; break;
case �b�: cout<<st. nazwa_oddzialu;
}
union {
int a;
};
int a; //Bład redefinicja zmiennej a co wynika z anonimowosci unii
Pola Bitowe:
Jest to zbiór przylegających do siebie bitów, znajdujących się w jednej jednostce pamięci zwanej
słowem.
np: unsigned int odczyt :1;
unsigned int zapis :3;
Pola bitowe musi to być typu całkowitego int, signed int lub unsigned int.
Każdemu polu jest przydzielona liczba bitów wynikająca z deklaracji, ale nie więcej niż 16 dla
jednego pola bitowego (Borland 3.1).
Pola bitowe mogą się znaleźć tylko w strukturach, uniach i klasach.
struct dostep {
unsigned int odczyt
uns
:2;
igned int zapis
:2;
unsigned int przeglad :4
unsigned int brak
:1;
};
struct dostep flag= {2, 0, 5, 1 };
Możliwe rozmieszczenie bitów w pamięci (zależy to od implementacji) przedstawia rysunek:
15
14
13
12
11
10
9
8
brak
7
6
5
4
przeglad
3
2
1
zapis
0
odczyt
W stosunku do pól bitowych nie można:
- stosować sizeof (zwraca długość w bajtach)
- pobierać adresu
- definiować tablic i adresów na pola bitowe
- stosować makra offsetof (podaje położenie pola w strukturze, w bajtach od początku
struktury)
- pola bitowe można definiować tylko w strukturach, uniach i klasach.
- deklarować referencji do pól bitowych.
Operatory bitowe
W jednym słowie można przechowywać szereg informacji na poszczególnych bitach słowa. Do
pracy na poszczególnych bitach słowa służą operatory bitowe:
<< - przesunięcie w lewo
>> - przesunięcie w prawo
& - bitowy iloczyn logiczny
|
- bitowa suma logiczna
^ - bitowa różnica symatryczna (bitowe exclusive OR)
~ - bitowa negacja
- operator przesunięcia w lewo <<
Jest to operator unarny, operujący na operandach całkowitych (niezależnie od znaku):
zmienna << ile miejsc
Służy do przesuwania bitów operandu stojącego po lewej stronie operatora o liczbę pozycji
określoną przez drugi operand (jego wartość musi być dodatnia). Zwalniane bity zostają
uzupełnione zerami:
int x = 0x1010
int wynik;
wynik = x << 2;
Powyższy fragment programu przesunie bity liczby x o 2 miejsca w lewo.
x
= 0001 0000 0001 0000
wynik = 0100 0000 0100 0000
Jeśli chcemy przesunąć bity w danej zmiennej i tam też tą operację zapisać, to działamy podobnie
jak w przypadku wyrażenia : a = a + 5, czyli:
x = x << 2.
Operacja ta ( x<<2) jest równoważna pomnożeniu zmiennej przez cztery:
x
0001 = 1
x<<2
0100 = 4
- operator przesunięcia w prawo >>
Operator, działa jedynie na operandach całkowitych. Przesuwa bity operandu stojącego polewej
stronie operatora o ilość bitów wskazaną przez operand prawy.
Jeśli operator pracuje na danej unsigned to to bity z lewego brzegu są uzupełnione zerami, jeśli
operator pracuje na danej signed( ze znakiem) to bity z lewego brzegu są uzupełnione bitem
znaku lub zerami zależnie od implementacji.
unsigned int x = 0x0ff0;
unsigned int wynik;
wynik = x >>2;
x
0000 1111 1111 0000
wynik 0000 0011 1111 1100
signed int f = 0xff00;
signed int wynik;
wynik = x>>2;
x
wynik
1111 1111 0000 0000
0011 1111 1100 0000
wynik1 1111 1111 1100 0000
Implementacja Borland C++ daje wynik1.
- operatory iloczynu, sumy, różnicy symetrycznej
Operatory te działają na operandach całkowitych.
int x1 = 0x0f0f;
int x2 = 0x0ff0;
x1
0000 1111 0000 1111
x2
0000 1111 1111 0000
x1 & x2 0000 1111 0000 0000 - bitowa koniunkcja
x1 | x2 0000 1111 1111 1111 - bitowa alternatywa
x1 ^ x2
0000 0000 1111 1111 - bitowa różnica (XOR exclusive or)
- operator negacji
Operator jednoargumentowy działa na argumencie całkowitym.
x1
~ x1
0000 1111 0000 1111
1111 0000 1111 0000 zamienia 1 na 0 i 0 na 1
Wartość bitu
B1 & B2
B1 ^ B2
B1 | B2
~ B1
B1
B2
iloczyn
różnica
suma
negacja
0
0
0
0
0
1
1
0
0
1
1
0
0
1
0
1
1
1
1
1
1
0
1
0
Przykład
int dostep;
//globalna
modul_ustaw_bity (){
int czytaj, pisz;
czytaj=1; pisz=1<<1;
// zapisz
dostep=czytaj | pisz;
// 0001 | 0010 = 0011
}
rozkład bitów:
czytaj
pisz:
0001
0001<<1-> 0010
dostęp:
0011
int czytaj, pisz, maska;
// odczytaj
maska=1;
czytaj = dostep & maska;
maska=2; // 2- 0010
pisz=(dostep & 0002) >>1;
}
rozkład bitów:
maska = 1
0001
dostep:
0011
czytaj:
0001
maska= 2
0010
dostep:
0011
pisz:
0010>>1-> 0001
- różnica między operatorami logicznymi i bitowymi
Należy pamiętać, że wynikiem działania operatorów logicznych jest prawda lub falsz.
x=2 y=5
if (x && y)
1 && 1 ⇒ 1
Natomiast operatory bitowe wkraczają do wnętrza słowa , analizują poszczególne bity.
if (x & y)
2&5
x&y
0010
2
0101
5
0000
0