2.15.  Exercise: Classes

[ fromfile: classes-exercises.xml id: classes-exercises ]

  1. Example 2.24, Example 2.25, and Example 2.26 are part of a single program. Use them together for the following problems.

    Example 2.24. src/early-examples/thing/thing.h

    #ifndef THING_H_
    #define THING_H_
    
    class Thing {
     public:
        void set(int num, char c);
        void increment();
        void show();
     private:
        int m_Number;
        char m_Character;
    };
    #endif
    
    

    Example 2.25. src/early-examples/thing/thing.cpp

    #include <QTextStream>
    #include "thing.h"
    
    
    void Thing::set(int num, char c) {
      m_Number = num;
      m_Character = c;
    }
    
    void Thing::increment() {
      ++m_Number;
      ++m_Character;
    }
    
    void Thing::show() {
      QTextStream cout(stdout);
      cout << m_Number << '\t' << m_Character << endl;
    }
    
    

    Example 2.26. src/early-examples/thing/thing-demo.cpp

    #include <QTextStream>
    #include "thing.h"
    
    
    void display(Thing t, int n) {
        int i;
        for (i = 0; i < n; ++i)
            t.show();
    }
    
    
    int main() {
      QTextStream cout(stdout);
      Thing t1, t2;
      t1.set(23, 'H');
      t2.set(1234, 'w');
      t1.increment();
      //cout << t1.m_Number;  
      display(t1, 3);
      //cout << i << endl;
      t2.show();
      return 0;
    }
    
    

    1. Uncomment the two commented out lines of code in Example 2.26, and try to build the program using the commands:

        qmake -project
        qmake
        make
        

      Explain the difference between the errors reported by the compiler.

    2. Add public member functions to the definition of the class Thing so that the data members can be kept private, and the client code can still output their values.

  2. Given the UML diagram in Figure 2.5, define the class, and each member function specified, for an enhanced Fraction class. You can use Example 2.4 as a starting point.

    Figure 2.5.  Fraction Class Diagram

    Fraction Class Diagram

    Write some client code to test all the new operations and verify that proper calculations are done.

  3. Suppose that you want to write an application for a company that matches employers and job seekers. A first step would be to design appropriate classes. Look at Figure 2.6 as a starting point. In this diagram, the Person has two subobjects: Employer and Position.

    Figure 2.6.  The Company Chart

    The Company Chart

    To do this exercise, you need to use forward class declarations (Section 2.10).

    1. Write classes for Person, Position, and Employer as described in Figure 2.6.

    2. For Person::getPosition() and getEmployer(), create and return something funny if the person has not yet been hired by a company.

    3. For the hire(...) function, set the Person's state so that future calls to getPosition() and getEmployer() give the correct result.

    4. In the main() program, create at least two Employers, the "StarFleet Federation" and the "Borg."

    5. Create at least two employees, Jean-Luc Picard and Wesley Crusher.

    6. For each class, write a toString() function that gives you a string representation of the object.

    7. Write a main program that creates some objects and then prints out each company's list of employees.

  4. Critique the design shown in Figure 2.6. What problems do you see with it? In particular, how would you write Employer::getEmployees() or Position::getEmployer()? Suggest how to improve its design.

  5. Define a class to represent a modern automobile.

    Figure 2.7. Hondurota

    Hondurota

    Here are some features of this class.

    • In addition to the four named data members, the constructor should initialize the speed. Zero seems reasonable – it would be awkward to construct a moving automobile.

    • The drive() function should be reasonably smart:

      • It should not permit the car to drive if there is no fuel.

      • It should adjust the odometer and the fuel amount correctly.

      • It should return the amount of fuel left in the tank.

    • The addFuel() function should adjust the fuel amount correctly and return the resulting amount of fuel in the tank. addFuel(0) should fill the tank to its capacity.

    Write client code to test this class.

  6. The previous problem really did not make use of the speed member. The drive() function assumed an average speed and used an average fuel consumption rate. Now use the speed member to make things a bit more realistic.

    Add a member function to the Hondurota class that has the prototype

     double highwayDrive(double distance, double speedLimit); 

    The return value is the elapsed time for the trip.

    When driving on a highway, it is usually possible to travel at or near the speed limit. Unfortunately, various things happen that can cause traffic to move more slowly, sometimes much more slowly.

    Another interesting factor is the effect that changing speed has on the fuel consumption rate. Most modern automobiles have a speed that is optimal for fuel efficiency (e.g., 45 mph). Calculating how long it will take to travel a particular distance on the highway, and how much fuel the trip will consume, is the job of this new function.

    • Write the function so that it updates the speed, the odometer, and the fuel amount every minute until the given distance has been traveled.

    • Use 45 mph as the speed at which fuel is consumed precisely at the stored m_FuelConsumptionRate.

    • Use an adjusted consumption rate for other speeds, increasing the rate of consumption by 1% for each mile per hour that the speed differs from 45mph.

    • Your car should stop if it runs out of fuel.

    • Assume that you are traveling at the speed limit, except for random differences that you compute each minute by generating a random speed adjustment between -5 mph and +5 mph. Don't allow your car to drive faster than 40 mph above the speed limit. Of course, your car should not drive slower than 0 mph. If a random speed adjustment produces an unacceptable speed, generate another one.

    Write client code to test this function.

  7. Be the computer and predict the output of Example 2.28.

    Example 2.27. src/statics/static3.h

    #ifndef _STATIC_3_H_
    #define _STATIC_3_H_
    
    #include <string>
    using namespace std;
    
    class Client {
     public:
        Client(string name) : m_Name(name), m_ID(s_SavedID++)
            { }
        static int getSavedID() {
            if(s_SavedID > m_ID) return s_SavedID;
            else return 0;
        }
        string getName() {return m_Name;}
        int getID() {return m_ID; }
     private:
        string m_Name;
        int m_ID;
        static int s_SavedID ;
    };
    
    #endif
    

    Example 2.28. src/statics/static3.cpp

    #include "static3.h"
    #include <iostream>
    
    int Client::s_SavedID(1000);
    
    int main() {
        Client cust1("George");
        cout << cust1.getID() << endl;
        cout << Client::getName() << endl;
    }
    
    

  8. Figure 2.8.  Date UML Class Diagram

    Date UML Class Diagram


    Design and implement a Date class based on Figure 2.8, subject to the following restrictions and suggestions.

    • Each Date must be stored as a single integer equal to the number of days since the fixed base date, January 1, 1000 (or some other date if you prefer). Call that data member m_DaysSinceBaseDate.

    • The base year should be stored as a static int data member (e.g., 1000 or 1900).

    • The class has a constructor and a set function that have month, day, year parameters. These three values must be used to compute the number of days from the base date to the given date. We have specified a private member function named ymd2dsbd() to do that calculation for both.

    • The toString() function returns a representation of the stored date in some standard string format that is suitable for display (e.g., yyyy/mm/dd). This involves reversing the computation used in the ymd2dsbd() function described above. We have specified a private member function named getYMD() to do that calculation. We have also suggested a parameter for the toString() function (bool brief) to provide a choice of date formats.

    • We have specified some static utility functions (e.g., leapyear()) that are static because they do not affect the state of any Date objects.

    • Make sure you use the correct rule for determining whether a given year is a leap year!

    • Create a file named date.h to store your class definition.

    • Create a file named date.cpp that contains the definitions of all the functions declared in date.h.

    • Write client code to test your Date class thoroughly.

    • Your class should handle "invalid" dates in a reasonable way (e.g., year earlier than the base year, month or day out of range, etc.).

    • Here is the code for setToToday() that makes use of the system clock to determine today's date. You need to #include <time.h> (from the C Standard Library) to use this code.

      void Date::setToToday() {
        struct tm *tp = 0;
        time_t now;
        now = time(0);
        tp = localtime(&now);
        set(1900 + tp->tm_year, 1 + tp->tm_mon, tp->tm_mday);
      }
      

    • getWeekDay() function returns the name of the week day corresponding to the stored date. Use this in the fancy version of toString(). Hint: Jan 1, 1900, was a Monday.

  9. Consider the class shown in Example 2.29.

    Example 2.29. src/destructor/demo/thing.h

    #ifndef THING_H_
    #define THING_H_
    
    #include <iostream>
    #include <string>
    using namespace std;
    
    class Thing {
     public:
        Thing(int n) : m_Num(n) {
            
        }
        ~Thing() {
            cout << "destructor called: " 
                 << m_Num << endl;
        }
        
     private:
        string m_String;
        int m_Num;
    };
    #endif
    
    

    The client code in Example 2.30 constructs several objects in various ways and destroys most of them.

    Example 2.30. src/destructor/demo/destructor-demo.cpp

    #include "thing.h"
    
    void function(Thing t) {
        Thing lt(106);
        Thing* tp1 = new Thing(107);
        Thing* tp2 = new Thing(108);
        delete tp1;
    }
    
    int main() {
        Thing t1(101), t2(102);
        Thing* tp1 = new Thing(103);
        function(t1);
        {  1
            Thing t3(104);
            Thing* tp = new Thing(105);
        }
        delete tp1;
        return 0;
    }
    
    

    1

    Nested block/scope.


    Here is the output of this program.

    destructor called: 107
    destructor called: 106
    destructor called: 101
    destructor called: 104
    destructor called: 103
    destructor called: 102
    destructor called: 101
    
    1. How many objects were created but not destroyed?

    2. Why does 101 appear twice in the list?