zestaw 1

Transkrypt

zestaw 1
EGZAMIN PROGRAMOWANIE II (10 czerwca 2010) – pytania i odpowiedzi 1. Napisz wskaźnik do funkcji fun tak zdeklarowanej: T* fun( int, double const& ) const; Odpowiedź: definicja wskaźnika musi być precyzyjna, inaczej nie byłby to wskaźnik do tej funkcji T* (*ptr)(int, double const&) const; 2. Oto definicja klasy, oraz kawałek kodu w programie. Dopisz w miejsce kropek to, czego w tej klasie brakuje. class MojaKlasa { public: MojaKlasa( const MojaKlasa& ); virtual ~MojaKlasa(); MojaKlasa(); }; // gdzieś w programie MojaKlasa tablica[10]; Tablica wymaga konstruktora domyślnego, ponieważ zdefiniowano już konstruktor kopiujący, nie będzie wygenerowany konstruktor zwykły i trzeba go napisać samemu. 3. Napisz deklarację zmiennej o nazwie liczba, typu całkowitego bezznakowego: extern unsigned liczba; // może też być: extern unsigned int liczba; Zwracam uwagę, że pytano o deklarację, a nie definicję (definicja jest też deklaracją, ale jednak należało precyzyjnie udzielić odpowiedzi na pytanie). 4. Napisz słowny opis do następujących definicji: double ( *alfa[4] ) ( int*& ); Na przykład tak: alfa to 4‐elementowa tablica wskaźników do funkcji, przyjmującej jako argument wskaźnik do int przez referencję, a zwracającej double. Czasami w odpowiedzi pisano o jakimś „wskaźniku do referencji”, coś takiego nie istnieje! 5. Wymień operatory, które przy przeciążaniu muszą być metodami składowymi klasy: = () [] ‐> 6. Co się stanie, jeśli nie powiedzie się takie rzutowanie: dynamic_cast< T& > ( x ); Zostanie zgłoszony wyjątek (typu bad_cast) 7. Mamy takie wskaźniki: double const * ptr1; double * const ptr2; Napisz jak należy wykonać operację rzutowania (operatorem z języka C++) tak, żeby można było przypisać „ptr1 = ptr2”: Operację tą należy wykonać tak: ptr1 = const_cast< double const* > ( ptr2 ); 8. Ile razy w wywołaniu funkcji fun będą wywoływane jakiekolwiek konstruktory klasy T ? Definicja funkcji: T fun( const T arg ) try { T t(); throw T(); } catch( T ) { return T(); } Odpowiedź: 4 razy. Najpierw argument przekazany przez wartość (cc‐tor), potem utworzenie obiektu przy zgłoszeniu wyjątku (c‐tor), potem przechwycenie wyjątku ale przez wartość – czyli robiona kopia (cc‐tor) i wreszcie w instrukcji return też zrobienie obiektu (c‐tor). Może warto skomentować, że T t(); to jest tylko deklaracja bezargumentowej funkcji o nazwie t, zwracającej przez wartość typ T (a więc żaden obiekt nie jest tu tworzony). 9. W poniższym kodzie są cztery błędy (punkt przyznany tylko za poprawne zaznaczenie wszystkich). Zaznacz wyraźnie poprawki na kodzie: struct Klasa { enum Funkcja = (znak „=” nieprawidłowy) { eUczen, ePrzewodniczacy, Skarbnik } ; (brak średnika) Klasa( Funkcja* o ) : osoba ( *o ) { } // alternatywnie, np.: Klasa(Funkcja& o) albo przez wartość private: Funkcja osoba; } ; (brak średnika) 10. Co się dzieje jeśli po zgłoszeniu wyjątku, podczas usuwania lokalnych obiektów, destruktor jednego z nich też zgłosi wyjątek (krótko opisz sytuację domyślną). Odpowiedź: wywołana jest funkcja terminate(), która z kolei wywołuje funkcję abort() 11. Mamy klasę, a w niej pewną deklarację przyjaźni (jest to tylko fragment kodu) class Klasa { friend void fun( unsigned ) {} }; Napisz w kodzie programu jak byś mógł wywołać taką funkcję: int main() { // tu kawałek twojego kodu fun( 5 ); // lub cokolwiek podobnego – fun w każdym razie nie jest metodą składową klasy! } 12. Dla obiektów klasy T napisz deklarację przyrostkowego operatora inkrementacji w postaci funkcji globalnej: const T operator++(T&, int); // ewentualnie bez const, ale zwracanie nie przez referencję! Tyle wystarczyło, bo pytano o deklarację, można było dodać coś na temat przyjaźni – choć to już zależy przecież od szczegółów definicji klasy T, których tutaj nie znamy. 13. Mamy dwie klasy, bazową A i pochodną B. Co trzeba wpisać w klasie B w miejsce kropek, żeby poniższy program się kompilował? class A { public: int a; }; class B : A { public: // zauważmy, że A jest dziedziczone prywatnie! using A::a; // ewentualnie samo: A::a; ‐ zachowujemy publiczny dostęp mimo prywatnego dziedziczenia }; int main() { int A::*ptr = &B::a; } Wskaźnikowi do składowej ptr przypisano potem obiekt z klasy pochodnej, ale ten odziedziczony. Nie może więc być rozwiązaniem np. ponowna definicja zmiennej a w klasie B, bo wtedy taki wskaźnik, który nic kompletnie nie wie o klasie B (ponad to, co ona odziedziczyła) nie mógłby być ustawiony na a. 14. Ilu argumentowy jest przeciążony operator‐> oraz co musi zwracać? Odpowiedź: Operator przeciążany jest jednoargumentowy i musi (ostatecznie) zwracać wskaźnik, tzn. może zwracać obiekt z kolejnym przeciążonym operator‐> ale na końcu musi być zwracany wskaźnik. 15. Jakie dwie operacje wykonuje operator new podczas swojego działania? Odpowiedź: 1) zdobycie pamięci (co się oczywiście wiąże z pozyskaniem/zwróceniem adresu do niej) 2) wywołanie konstruktora obiektu Na tą drugą operację nie mamy wpływu (zawsze jest wykonywana przy wywołaniu new), pierwszą można modyfikować podczas przeciążania operatora. 16. Napisz (dokończ) poprawnie definicję konstruktora klasy C, jeśli klasy bazowe A i B wyglądają następująco: class A { public: A(int) {} }; class B : public virtual A { public: B(char) : A(7) {} }; class C : B { public: // na przykład: C() : A(7), B(‘a’) {} }; Klasa A jest dziedziczona wirtualnie, więc za jej konstrukcję odpowiada typ najbardziej pochodny. Ponieważ klasa A nie ma konstruktora domyślnego, więc trzeba go jawnie zapisać na liście inicjatorów konstruktora klasy C, podając jakiś parametr (właściwego typu). 17. Mamy klasę bazową A oraz zdefiniowany w niej operator= następnie klasę pochodną B. W definicji operatora „=” z klasy B zapisz w dowolny sposób linię dotyczącą wywołania operatora z klasy bazowej oraz ostatnią linię: class A { public: A& operator=( const A& src ); }; class B : public A { public: B& operator=( const B& src ) { // jakiś kod …sposób zapisu jest kilka do wyboru, np. A::operator=( src ); // tu wywołaj operator= z klasy bazowej w dowolny sposób // jakiś kod …na koniec trzeba oczywiście zwrócić sam obiekt (nie wskaźnik!) return *this; // tu napisz co musi być na końcu } }; 18. Wskaż wśród poniższych fałszywą odpowiedź: a. funkcja globalna: static void fun() {} b. funkcja globalna: void fun() const {} c. funkcja składowa klasy T: void T::fun() const volatile {} d. funkcja składowa struktury T: struct T { static void fun() {} }; Odpowiedź b. jest niepoprawna, ponieważ funkcja globalna nie może mieć modyfikatora cv (const / volatile). Reszta odpowiedzi jest poprawna! 19. Dla klasy A napisz operator konwersji do typu std::string class A { const char* ptr; public: operator std::string() { return ptr; } }; Można oczywiście (po uprzednim zadeklarowaniu użycia) opuścić std:: , można też inaczej zapisać zawartość, np. tworząc obiekt typu string w środku, ale tak jak wyżej podano, jest najkrócej i najprościej. 20. Co to jest this ? – jakiego jest typu np. dla klasy T, gdzie jest przekazywane i czemu służy? Odpowiedź: 1) this jest wskaźnikiem i jest on typu T * const (czyli stały wskaźnik) 2) przekazywany niejawnie wszystkim niestatycznym metodom składowym klasy – można w związku z tym zauważyć, że jeśli metoda jest const i/lub volatile, to takiż przydomek nabywa również ten wskaźnik, czyli np. dla metody stałej będzie on typu const T * const 3) zawiera adres konkretnej instancji (obiektu), dzięki niemu metody wiedzą na jakim egzemplarzu danych pracują (każdy obiekt ma swoje dane) 21. Dla klasy A napisz (dokończ) konstruktor, który będzie „chroniony” tak zwanym try funkcyjnym (razem z dowolną procedurą obsłużenia zgłoszonego wyjątku). Ponadto odpowiedz na pytanie, czym kończy się obsłużenie wyjątku zgłoszonego wewnątrz takiego try ? class A { public: // można to było dowolnie to napisać, ale składniowo poprawnie w sensie właściwych nawiasów, np.: A() try : (tu coś na liście inicjatorów) { tu zasadnicza treść } catch( … ) { tu blok obsługi wyjątku } }; Odpowiedź: try funkcyjny wraz z blokiem obsługi wyjątku zawsze kończy się ponownym zgłoszeniem wyjątku (czyli tak jak gdyby na końcu bloku catch napisać throw; lub zgłosić inny wyjątek) 22. W klasie A zadeklarowana jest metoda czysto wirtualna, która potem jest też zdefiniowana: class A { public: virtual void fun() = 0; }; void A::fun() { } // definicja Wybierz z poniższych fałszywe stwierdzenie: a. metodę fun() można wywołać w konstruktorze klasy A b. metodę fun() można wywołać w destruktorze klasy A c. metodę fun() w klasie dziedziczącej z klasy A nie trzeba już definiować, bo została zdefiniowana d. można na nią ustawić wskaźnik na metodę składową: void (A::*p)() = &A::fun; 23. Jeśli mamy klasę A: class A { public: virtual Foo* fun(); } to jakie kryteria musi spełniać typ Bar w klasie pochodnej: class B : public A { public: Bar* fun(); } żeby dla wyołania (new B)‐>fun(); działał polimorfizm? Odpowiedź: żeby możliwe było w ogóle napisanie wirtualnej funkcji fun() o takich samych parametrach, oraz żeby działał polimorfizm, typ Bar musi być typem pochodnym od typu Foo. Nie rozumiem tego, jeśli ktoś pisał, że Bar musi być „taki sam” albo „tego samego typu” co Foo. Oczywiście tak można potocznie powiedzieć, ale nie wyczerpuje to istoty sprawy. 24. Mamy klasę: class A { public: A() { cout << ” OK ”; } A( const A& ) { cout << ” OK ”; } ~A() { cout << ” OK ”; } }; Ile razy zobaczymy na ekranie OK w wyniku działania poniższego kodu: int main() { A *ptr = new A; A obj = *(new A); } Odpowiedź: 4 razy. W pierwszej linii tylko konstruktor, w drugiej konstruktor, po czym konstruktor kopiujący (bo jest tworzony obiekt obj), na koniec destruktor obiektu obj. Zasoby pokazywane przez wskaźnik ptr nie są usuwane (brak linijki delete ptr) tym samym nie ma destruktora tych zasobów. 25. Które z poniższych porównań typów, wykorzystujące RTTI, zwraca wartość true ? a. typeid( short ) == typeid( 3 ) b. typeid( float ) == typeid( 3.14 ) c. typeid( float*const ) == typeid( new float ) d. typeid( float const* ) == typeid( *(new double) ) Liczba 3 sama w sobie oznacza typ int, liczba 3.14 sama w sobie oznacza typ double (było to na wykładzie dyskutowane przy okazji stałych dosłownych języka). Natomiast w przypadku wskaźnika float * const przydomek „const” nie dotyczy wskazywanego typu, tylko określa charakter wskaźnika (…że jest on stały). Wskazywany typ to float* co zgadza się z prawą stroną (new float). 26. Dla klasy A: class A { public: int a; }; napisz definicję operatora operator<< tak, żeby możliwe było w programie: A obj; cout << obj; Taki operator nie musi być przyjacielem, bo klasa A ma jeden składnik ‐ publicznie dostępny. ostream& operator<<( ostream& stream, const A & src ) { return stream << src.a; } // gdy ktoś deklarował przyjaźń, to mimo powyższego akceptowałem to :‐) 27. Klasę A uzupełnij tak, żeby możliwe było w programie przypisanie: A obj = 77; class A { int a; // na przykład: A(int n) : n( a ) {} }; Linijka z programu: A obj = 77; nie oznacza w żadnym wypadku operacji przypisania! Ponieważ obj jest właśnie tworzony, działa tu konstruktor kopiujący. Ponieważ jednak argument tego konstruktora nie pasuje typem (bo jest to liczba 77, czyli int), musi nastąpić jakaś konwersja. Jest ona możliwa gdy dostarczymy konstruktor konwersji o jednym argumencie typu int. 28. Napisz wskaźnik, jaki powinien być po lewej stronie poniższej operacji: int zmienna = 3; int (*ptr)[4] = new int[zmienna][4]; 29. W pewny zakresie utworzono kilka obiektów. Wszystkie powinny zostać usunięte, napisz konieczne instrukcje to wykonujące: { double *p1 = new double(3); auto_ptr<char> p2( new char ); short *p3 = new short[5]; // teraz usuwanie, napisz co trzeba delete p1; // bo utworzono jeden obiekt do wskaźnika p1 delete [] p3; // bo utworzono tablicę obiektów do wskaźnika p3 } p2 oczywiście nie usuwamy, bo obiekt auto_ptr jest właśnie obiektem na stosie, sam się usunie 30. Podaj wynik następującej operacji (pytanie „z haczykiem”): 2 ^ 1 ‐ !0 Odpowiedź: To było jedyne pytanie, na które całkiem poprawna odpowiedź wymagała dużej erudycji… i zupełnie przypadkiem spora część z Państwa, mimo podążenia złym tokiem rozwiązania, dostała poprawny wynik. Rezultat tego obliczenia to 2. Natomiast poprawna do niego droga jest następująca: operator ^ to nie jest oczywiście żadne podnoszenie do potęgi, bo takiego operatora w c++ nie ma, ten operator oznacza w tym języku różnicę bitową („bitwise exclusive or”). Trzeba więc umieć sobie przedstawić liczbę 1 oraz 2 w postaci binarnej, np. 0001 oraz 0010. Różnica bitowa na tych liczbach daje 0011, czyli 3. A jednak nie tak się to liczy w tym przypadku! Dlaczego? Ponieważ operator odejmowania (‐) ma wyższy priorytet niż operator ^. Jeszcze wyższy priorytet ma negacja logiczna, czyli operator! . A zatem, kolejno mamy: !0 sprawia, że 0 jest konwertowane na false, następnie zamieniane operatorem ! na true. Wartości logicznej true odpowiada 1 (a nie „dowolna liczba” – to jest w drugą stronę konwersji…) i ponieważ dalej mamy do czynienia z odejmowaniem 1 ‐ !0 to true jest zamieniane właśnie na 1: zatem 1 – 1 daje 0. Ostatnia operacja: 2^0 daje 2. 

Podobne dokumenty