> Comp 272 Study Guide Answers Dordal Feb 21, 2004 > 1. Consider the following operations in a class Date: > > class Date { > public: > Date(int month, int date, int year); > String tostring(); > void advance(int ndays); // advances the date by a given number of days > }; > > (a). Modify the design of the class so advance returns a new date, > instead of modifying the existing date. Declare the revised version > with all applicable uses of "const". Date advance(int ndays) const; > (b). Suggest a reasonable collection of accessors, that would give you > ways to access and use the date. You should be able to convert the > entire date to a string, and also extract the year, day, and month fields. String date2str() const; int year() const; int month() const; int day() const; Many variations are possible. > (c). Discuss why it would be a bad idea for the Date class to make any > of its internal fields public. Try to give at least two fundamental > reasons. The reasons apply to all classes: (a) it becomes impossible to change the implementation (b) it becomes impossible to verify class invariants if class users can modify data at will. =============================================================== > 2. (a). Consider a banking system, with classes for checking accounts > and savings accounts. The Checking_Account class has member functions > withdraw(Money amt), deposit(Money amt), and writecheck(Money amt). The > Savings_Account class has member functions withdraw and deposit and > also AddInterest(double interest_rate). > > Arrange these classes in a suitable inheritance hierarchy. Introduce a > common base class with the appropriate operations. Do not write any code! class Account { public: void withdraw(Money amt); void deposit (Money amt); Money balance(); ... } class Checking_Account: public Account { public: void writecheck(Money amt); ... } class Savings_Account: public Account { public: void AddInterest (double interest_rate); ... } =============================================================== > 3. Write a C++ function vsearch(v, x, found) to search a vector v of doubles > for a given value x. If x is found at position v[i], then i is to be returned > and the actual parameter corresponding to found (of type bool) is to be set > to "true"; if i is not found then set found=false. Note that found must be > a reference parameter (declared with &). int vsearch(vector v, int x, bool & found) { // note DECLARATIONS! int i = 0; while (i < v.size() && v[i]!=x) i++; if (i 4. (b). Our implementation derived AdminMailbox from Mailbox. Yet > the MailSystem::process_dialing function still explicitly checked for > extension 9999 to determine if a mailbox was an Admin box or not (in > other words, no "polymorphism" was used). Given this, what was the > advantage (if any) to using inheritance here? The code is simpler. In the smart-MailSystem version, you have to think if you want to avoid entirely separate code for mailboxes and adminmailboxes. With inheritance, AdminMailbox::do_command(int s) becomes if (s==4) create_mailbox; else Mailbox::do_command(s); In other words, we simply add a fourth option but for the other options we pass the message back to the parent Mailbox class. =============================================================== > 5. Consider the following outline of a list interface. Assume the > list are of objects of type X. Note that this interface maintains > a "current" element of the list; the internal "cursor" is in effect > a pointer to the current element. > > class listX { public: > start() // set an internal "cursor" to start of list > next(); // advance cursor to next element > set(); // set current element to a new value > get(); // return current element > add(); // add new element after current cursor position > addbefore(); // like add, but inserts new element in front of current > delete(); // deletes current element > islast(); // true if current element is last element > length(); // return length of list > ... > } > > (a). Provide parameter lists for all the above, including const where > appropriate (where the actual parameter will not be modified). Also > declare whether the member function itself is const or not. void start(); void next(); void set(X x); X get() const; void add(X x); void addbefore(X x) void delete(); Bool islast() const; int length() const; > (b). Explain why both add() and addbefore() are necessary; that is, > give an example of something that can be done with add() but not > addbefore() and something that can be done with addbefore() but not > with add(). add() cannot add to the front of the list; addbefore() can't add to the tail. =============================================================== > 6. The list class in problem 5 has a disadvantage in that there can be > at most one cursor. Implementing a linked-list class with public > pointer fields is that one way to support multiple cursors (pointers) > into the same list, but the price is that a critical data field > is now public, which ties one to a specific implementation, and causes > a loss of some type safety. > > One way to have the best of both worlds is to implement "external > iterators"; that is, one has a class List and also a class ListCursor. > A ListCursor object represents a pointer into a linked list; > alternatively, if the List is implemented as an array, then a > ListCursor represents an array index value. All list operations that > refer to the "current" element would now be made in terms of a > ListCursor object. The class ListCursor would be able to access > the private: data of class List, either because ListCursor was > declared inside class List, or was declared to be a "friend" class. > > Specify the interface for List and ListCursor, and indicate with a > comment what each operation does. As a hint, a constructor for a > ListCursor is specified; it takes a List reference and sets the > ListCursor to mark the first element of the list. There are multiple ways to do this, none of them perfect. The solution below uses a ListCursor as a parameter to all List operations for which the position in the list is applicable. This problem is waaay too hard to *put* on an exam, but as a review question it seemed plausible. But if it doesn't make sense to you, don't worry about it. class List { friend ListCursor; // "friend" means it can access private data public: int length() const; // in the following, a ListCursor is passed // as a "pointer" to the desired element or position. void set(X x, ListCursor L); X get(ListCursor L) const; void add(X x, ListCursor L); void addbefore(X x, ListCursor L) void delete(ListCursor L); }; class ListCursor { friend List; public: ListCursor (List & L); // Sets the listcursor to point to head of L // takes place of start() // these are the only two that simply deal with position, not data! void next(); Bool islast() const; } =============================================================== > 7. Define a class Complex (for complex numbers). Do *not* implement > any of the operations, though! A complex number should have two > data fields, _real and _imag, of type double. You *should* implement > the following constructors, though: > (a) Complex (double, double); // set _real and _imag > (b) Complex (double) // set _real; let _imag = 0; > (c) default ctor class Complex { private: double _real, _imag; public: Complex() : _real(0.0), _imag(0.0) {} Complex(double r) : _real(r), _imag(0.0) {} // imaginary part is 0 Complex(double r, double i) : _real(r), _imag(i) {} Complex add(Complex z, Complex w); ... // more operations }; > 8. Suppose one has the following declarations: > int x = 3; > int * px = &x; > int * q = new int(4); > (*px)++; > (*q) += 3; > px = q; > (*px)+=1; > > What are the values of x, *px, and *q? > How would this problem appear if references were used instead? x = 4 px and q point to the same heap location initialized to 4; that value is now 4+3+1 = 8. > 9. Suppose one has > class Base { > private: > int _x; > public: > Base(int x) : _x(x) {} > virtual int f() {return _x;} > }; > > class Deriv : public Base { > private: > int _y; > public: > Deriv(int x, int y) : Base(x), _y(y) {} > virtual int f() {return Base::f() + _y;} > }; > > Base * b = new Base(3); > Base * bd = new Deriv(4,5); > Deriv* d = new Deriv(7,8); > > What is the output: > cout << b->f() << endl; > cout << bd->f() << endl; > cout << d->f() << endl; 3 9 15 As done in class. Note that only the middle value, 9, has anything to do with object-oriented features of the language; the compiler thinks *bd os of type Base and thus that bd->f() refers to Base::f(), which would return 4. HOWEVER, because f is declared virtual, the compiler does not generate a call to Base::f() but instead follows a pointer attached to the object to the correct f, which turns out to be Deriv::f().