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