Practice final answers

CS 251, Fall 2014

1
char *doublestring(const char *str) {
  int len;
  for (len = 0;  str[len] != '\0';  len++)
    ;
  // len holds the length of str, excluding nullbyte

  char *ret = new char[2*len+1];
  for (int i = 0; i < len;  i++)
    ret[i] = ret[len+i] = str[i];
  ret[2*len] = '\0';
  
  return ret;
} 
2

Note: This code is not a good example of memory management, since the class Name has no copy constructor, destructor, or assignment operator, even though that class uses dynamically allocated memory for the state variable name. Also, did you notice the memory leak in the constructor for Item?

3

Note: only one of the following would need to be answered.

3 a
If a call of new has no corresponding call of delete, then the dynamically allocated memory from that new call would remain for the remainder of the program's run, even if that memory was no longer needed. This situation is called a memory leak, and too many memory leaks could cause poor performance or even a computer crash.
3 b
Programming a subclass S makes it convenient to reuse the implementation of its superclass C without having to rewrite it all, because S would automatically inherit all the state variables and methods of C. This is important for software maintenance, because changes in the code for C will automatically take effect for S, rather than having to make the same code changes in multiple places (which is error-prone). Using subclasses appropriately can also lead to better code design. For example, a TextBook class would make an appropriate subclass of a Book class, because a text book is a book.
4
#include <iostream>
using namespace std;

#include "Car.h"

class Taxi : public Car {
 protected:
  char *driver;
  float rate;
  char *makeString(const char *str) const;
 public:
  Taxi(const Car &cr, const char *dr, float rt) 
   : Car(cr) { driver = makeString(dr); rate = rt; }
  Taxi() 
   : Car() { driver = makeString("");  rate = 0; }
  Taxi(const Taxi &tx)
    : Car(tx) { driver = makeString(tx.driver); rate = tx.rate; }
  ~Taxi() { delete [] driver; driver = 0; }
  Taxi &operator=(const Taxi &tx) 
    { delete [] driver;  Car::operator=(tx); 
      driver = makeString(tx.driver); rate = tx.rate; return *this; }
  void setRate(float rt) { rate = rt; }
  void setDriver(const char *dr) { delete [] driver; driver = makeString(dr); }
  void display() const;
};

void Taxi::display(void) const { 
  Car::display();
  cout << " (" << driver << ", $" << rate << ")"; 
}

char *Taxi::makeString(const char *str) const {
  int len;
  for (len = 0;  str[len] != '\0';  len++)
    ;
  char *newstr = new char[len+1];
  for (int i=0;  i < len;  i++)
    newstr[i] = str[i];
  newstr[len] = '\0';
  return newstr;
}

Note: The trailing keyword const in

  char *makeString(const char *str) const ...

  void display() const ...
indicates that these are const methods, i.e., these methods are safe for const objects to call. The implementations for these methods are safe for const Taxi objects to call because they make no changes in the Taxi (or Car) state variables.

See this handout for a summary of all four uses of const in C++.