Chapter 14 More about Classes

14.1 Instance and Static Members

Concept:
  • Each object (or instance) of a class gets its own set of instance variables.
  • A static member variable is shared among all instances of a class.
  • A static member function can be called without creating any instances of the class.

Instance Variables

  • Every object created from a class has its own distinct copies of the class’s member variables.
  • These are known as instance variables because they belong to a specific instance.
  • For example, if you create two Rectangle objects, box1 and box2, each will have its own separate width and length variables.
Rectangle box1, box2;

box1.setWidth(5);
box1.setLength(10);

box2.setWidth(500);
box2.setLength(1000);
  • Calling box1.getWidth() returns the value from box1, and box2.getWidth() returns the value from box2.
cout << box1.getWidth() << " " << box2.getWidth() << endl;

Static Members

  • It’s possible to create members that belong to the class itself, rather than to any specific instance.
  • These are called static member variables and static member functions.
  • A static variable is not stored within an instance; it’s a single, shared variable for the entire class.
  • Static functions can operate only on static member variables, not on instance variables.

Static Member Variables

  • A member variable declared with the static keyword exists as a single copy in memory, shared by all objects of the class.
  • This is useful for tasks like counting the number of instances created.
  • The Tree class below uses a static variable objectCount to track how many Tree objects exist.
Contents of Tree.h
 class Tree
 {
 private:
     static int objectCount;    
 public:
     Tree()
       { objectCount++; }

     int getObjectCount() const
        { return objectCount; }
 };

 int Tree::objectCount = 0;
  • A static member variable is declared inside the class using the static keyword.
  • It must be defined outside the class to allocate memory for it. This is where it’s typically initialized.
  • Uninitialized static member variables are automatically set to 0, but explicit initialization is good practice.
  • In the Tree class, the constructor increments objectCount each time a new object is created.

🗊 Program 14-1

 #include <iostream>
 #include "Tree.h"
 using namespace std;

 int main()
 {
     Tree oak;
     Tree elm;
     Tree pine;

     cout << "We have " << pine.getObjectCount()
          << " trees in our program!\n";
     return 0;
 }

💻 Program Output


  • Even though three objects (oak, elm, pine) are created, they all share a single objectCount variable.

  • Any object of the class can be used to access the static variable’s value.

cout << "We have " << oak.getObjectCount() << " trees\n";
cout << "We have " << elm.getObjectCount() << " trees\n";
cout << "We have " << pine.getObjectCount() << " trees\n";
  • A more practical example is the Budget class, which uses a static member corpBudget to track the total budget for all company divisions.
Contents of Budget.h (Version 1)
 #ifndef BUDGET_H
  #define BUDGET_H

class Budget
 {
 private:
     static double corpBudget;  
     double divisionBudget;     
 public:
     Budget()
        { divisionBudget = 0; }

     void addBudget(double b)
        { divisionBudget += b;
          corpBudget += b; }

     double getDivisionBudget() const
        { return divisionBudget; }

     double getCorpBudget() const
       { return corpBudget; }
 };

 double Budget::corpBudget = 0;

 #endif

🗊 Program 14-2

 #include <iostream>
 #include <iomanip>
 #include "Budget.h"
 using namespace std;

 int main()
 {
     int count;                        
     const int NUM_DIVISIONS = 4;      
     Budget divisions[NUM_DIVISIONS];  

     for (count = 0; count < NUM_DIVISIONS; count++)
     {
         double budgetAmount;
         cout << "Enter the budget request for division ";
         cout << (count + 1) << ": ";
         cin >> budgetAmount;
         divisions[count].addBudget(budgetAmount);
     }

     cout << fixed << showpoint << setprecision(2);
     cout << "\nHere are the division budget requests:\n";
     for (count = 0; count < NUM_DIVISIONS; count++)
     {
         cout << "\tDivision " << (count + 1) << "\t$ ";
         cout << divisions[count].getDivisionBudget() << endl;
     }
     cout << "\tTotal Budget Requests:\t$ ";
     cout << divisions[0].getCorpBudget() << endl;

     return 0;
 }

💻 Program Output











Static Member Functions

  • A static member function is declared by placing the static keyword in its prototype.
static ReturnType FunctionName (ParameterTypeList);
  • Key points about static member functions:

    • They cannot access non-static (instance) member data.
    • Static member variables exist for the entire lifetime of the program, even before any class objects are created.
    • Static member functions can be called before any instances of the class are created. This allows them to access static member variables for setup or initialization tasks.
  • The Budget class is modified below to include a static function mainOffice that adds to the corporate budget before any division objects are created.

Contents of Budget.h (Version 2)
 #ifndef BUDGET_H
 #define BUDGET_H

 class Budget
 {
 private:
     static double corpBudget;  
     double divisionBudget;     
 public:
     Budget()
         { divisionBudget = 0; }

     void addBudget(double b)
         { divisionBudget += b;
           corpBudget += b; }

     double getDivisionBudget() const
         { return divisionBudget; }

     double getCorpBudget() const
         { return corpBudget; }

     static void mainOffice(double);  
 };

 #endif
Contents of Budget.cpp
 #include "Budget.h"

 double Budget::corpBudget = 0;


 void Budget::mainOffice(double moffice)
 {
     corpBudget += moffice;
 }

🗊 Program 14-3

 #include <iostream>
 #include <iomanip>
 #include "Budget.h"
 using namespace std;

 int main()
 {
     int count;                     
     double mainOfficeRequest;      
     const int NUM_DIVISIONS = 4;   

     cout << "Enter the main office's budget request: ";
     cin >> mainOfficeRequest;
     Budget::mainOffice(mainOfficeRequest);

     Budget divisions[NUM_DIVISIONS]; 

     for (count = 0; count < NUM_DIVISIONS; count++)
     {
         double budgetAmount;
         cout << "Enter the budget request for division ";
         cout << (count + 1) << ": ";
         cin >> budgetAmount;
         divisions[count].addBudget(budgetAmount);
     }

     cout << fixed << showpoint << setprecision(2);
     cout << "\nHere are the division budget requests:\n";
     for (count = 0; count < NUM_DIVISIONS; count++)
     {
         cout << "\tDivision " << (count + 1) << "\t$ ";
         cout << divisions[count].getDivisionBudget() << endl;
     }
     cout << "\tTotal Budget Requests:\t$ ";
     cout << divisions[0].getCorpBudget() << endl;

     return 0;
 }

💻 Program Output












  • Static member functions are typically called using the class name and the scope resolution operator (::).
Budget::mainOffice(amount);
Note:
  • If an object of the class exists, you can also call a static member function using the object name and the dot operator, just like a regular member function.

14.2 Friends of Classes

Concept:
  • A friend is a function or an entire class that is not a member of a class but is granted access to its private members.
  • Normally, private members are accessible only by member functions of the same class.
  • A friend function is an exception; it’s an external function that can access the private members of a class.
  • A friend can be a standalone function or a member function of another class.
  • A class must explicitly declare which functions or other classes are its friends by using the friend keyword in its declaration.
friend ReturnType FunctionName (ParameterTypeList)
  • In this example, the addBudget function from the AuxiliaryOffice class is declared as a friend of the Budget class.
Contents of Budget.h (Version 3)
 #ifndef BUDGET_H
 #define BUDGET_H
 #include "Auxil.h"

 class Budget
 {
 private:
     static double corpBudget;  
     double divisionBudget;     
 public:
     Budget()
         { divisionBudget = 0; }

     void addBudget(double b)
         { divisionBudget += b;
           corpBudget += b; }

     double getDivisionBudget() const
         { return divisionBudget; }

     double getCorpBudget() const
         { return corpBudget; }

     static void mainOffice(double);

     friend void AuxiliaryOffice::addBudget(double, Budget &);
 };

 #endif
  • The friend declaration gives AuxiliaryOffice::addBudget permission to access the private members of Budget.
  • Note that the Budget object to be modified is passed by reference to the friend function.
Contents of Auxil.h
 #ifndef AUXIL_H
 #define AUXIL_H

 class Budget; 


 class AuxiliaryOffice
 {
 private:
     double auxBudget;
 public:
     AuxiliaryOffice()
         { auxBudget = 0; }

     double getDivisionBudget() const
         { return auxBudget; }

     void addBudget(double, Budget &);
 };

 #endif
Contents of Auxil.cpp
 #include "Auxil.h"
 #include "Budget.h"


 void AuxiliaryOffice::addBudget(double b, Budget &div)
 {
     auxBudget += b;
     div.corpBudget += b;
 }
  • A forward declaration (class Budget;) is used in Auxil.h.
  • This tells the compiler that the Budget class exists without providing its full definition.
  • It is necessary because the addBudget function prototype in AuxiliaryOffice uses a Budget reference parameter, and the compiler needs to know that Budget is a type.
Contents of Auxil.cpp
 #include "Auxil.h"
 #include "Budget.h"


 void AuxiliaryOffice::addBudget(double b, Budget &div)
 {
     auxBudget += b;
     div.corpBudget += b;
 }
  • Inside AuxiliaryOffice::addBudget, the code can directly access div.corpBudget, which is a private static member of the Budget class.

🗊 Program 14-4

 #include <iostream>
 #include <iomanip>
 #include "Budget.h"
 using namespace std;

 int main()
 {
     int count;                    
     double mainOfficeRequest;     
     const int NUM_DIVISIONS = 4;  

     cout << "Enter the main office's budget request: ";
     cin >> mainOfficeRequest;
     Budget::mainOffice(mainOfficeRequest);

     Budget divisions[NUM_DIVISIONS]; 
     AuxiliaryOffice auxOffices[4];   

     for (count = 0; count < NUM_DIVISIONS; count++)
     {
         double budgetAmount; 

        cout << "Enter the budget request for division ";
        cout << (count + 1) << ": ";
        cin >> budgetAmount;
        divisions[count].addBudget(budgetAmount);

        cout << "Enter the budget request for that division's\n";
        cout << "auxiliary office: ";
        cin >> budgetAmount;
        auxOffices[count].addBudget(budgetAmount, divisions[count]);
     }

     cout << fixed << showpoint << setprecision(2);
     cout << "\nHere are the division budget requests:\n";
     for (count = 0; count < NUM_DIVISIONS; count++)
     {
        cout << "\tDivision " << (count + 1) << "\t\t$";
        cout << divisions[count].getDivisionBudget() << endl;
        cout << "\tAuxiliary office:\t$";
        cout << auxOffices[count].getDivisionBudget() << endl << endl;
     }
     cout << "Total Budget Requests:\t$ ";
     cout << divisions[0].getCorpBudget() << endl;
     return 0;
 }

💻 Program Output
























  • An entire class can be made a friend of another class.
friend class AuxiliaryOffice;
  • Declaring an entire class as a friend gives all of its member functions access to the private members of the declaring class.
  • This is often not a good practice, as it’s better to grant friendship only to the specific functions that need it.

Checkpoint

  1. 14.1 What is the difference between an instance member variable and a static member variable?

  2. 14.2 Static member variables are declared inside the class declaration. Where do you write the definition statement for a static member variable?

  3. 14.3 Does a static member variable come into existence in memory before, at the same time as, or after any instances of its class?

  4. 14.4 What limitation does a static member function have?

  5. 14.5 What action is possible with a static member function that isn’t possible with an instance member function?

  6. 14.6 If class X declares function f as a friend, does function f become a member of class X?

  7. 14.7 Class Y is a friend of class X, which means the member functions of class Y have access to the private members of class X. Does the friend key word appear in class Y’s declaration or in class X’s declaration?

14.3 Memberwise Assignment

Concept:
  • The assignment operator (=) can be used to copy the data from one object to another of the same class.
  • By default, this operation copies the value of each member variable from the source object to the corresponding member variable in the destination object.
  • Objects can be assigned to one another, just like other variables (except C-style arrays).
  • This process is called memberwise assignment.

🗊 Program 14-5

 #include <iostream>
 #include "Rectangle.h"
 using namespace std;

 int main()
 {
     Rectangle box1(10.0, 10.0);    
     Rectangle box2 (20.0, 20.0);   

     cout << "box1's width and length: " << box1.getWidth()
          << " " << box1.getLength() << endl;
     cout << "box2's width and length: " << box2.getWidth()
          << " " << box2.getLength() << endl << endl;

     box2 = box1;

     cout << "box1's width and length: " << box1.getWidth()
          << " " << box1.getLength() << endl;
     cout << "box2's width and length: " << box2.getWidth()
          << " " << box2.getLength() << endl;

     return 0;
 }

💻 Program Output





  • The statement box2 = box1; copies the width and length from box1 into box2.

  • Memberwise assignment also happens during initialization.

  • Assignment occurs between two existing objects.

  • Initialization happens when a new object is created.

Rectangle box1(100.0, 50.0);

Rectangle box2 = box1;
  • In the code above, box2 is created and initialized with the member values of box1.

14.4 Copy Constructors

Concept:
  • A copy constructor is a special constructor that is automatically called when a new object is created and initialized with the data of an existing object of the same class.
  • Default memberwise assignment is not always safe, especially for classes that manage dynamic memory (i.e., contain pointer members).
  • Consider the StudentTestScores class, which uses a pointer testScores to a dynamically allocated array.
Contents of StudentTestScores.h (Version 1)
 #ifndef STUDENTTESTSCORES_H
 #define STUDENTTESTSCORES_H
 #include <string>
 using namespace std;

 const double DEFAULT_SCORE = 0.0;

 class StudentTestScores
 {
 private:
     string studentName; 
     double *testScores; 
     int numTestScores;  

     void createTestScoresArray(int size)
     { numTestScores = size;
       testScores = new double[size];
       for (int i = 0; i < size; i++)
           testScores[i] = DEFAULT_SCORE;}

 public:
     StudentTestScores(string name, int numScores)
     { studentName = name;
       createTestScoresArray(numScores); }

     ~StudentTestScores()
     { delete [] testScores; }

     void setTestScore(double score, int index)
     { testScores[index] = score; }

     void setStudentName(string name)
     { studentName = name; }

     string getStudentName() const
     { return studentName; }

     int getNumTestScores() const
     { return numTestScores; }

     double getTestScore(int index) const
     { return testScores[index]; }
 };
 #endif
  • The constructor for StudentTestScores dynamically allocates memory for the test scores.
StudentTestScores("Maria Jones Tucker", 5);
  • If one object is initialized from another using default memberwise assignment:
StudentTestScores student2 = student1;
  • The pointer testScores is copied, but not the memory it points to.

  • This results in a shallow copy, where both student1.testScores and student2.testScores point to the same block of memory.

  • This leads to serious problems:

    • Modifying the array through one object affects the other.
    • When one object is destroyed, its destructor frees the memory. The other object is then left with a dangling pointer to deallocated memory, which can cause a crash.
  • The solution is to write a custom copy constructor to perform a deep copy.

  • A copy constructor is a constructor that takes a single reference parameter of the same class type.

  • It is called automatically during initialization (e.g., StudentTestScores student2 = student1;).

  • It creates a new, independent copy of the dynamically allocated resources.

StudentTestScores(StudentTestScores &obj)
{ studentName = obj.studentName;
  numTestScores = obj.numTestScores;
  testScores = new double[numTestScores];
  for (int i = 0; < length; i++)
       testScores[i] = obj.testScores[i]; }
Note:
  • C++ requires that a copy constructor’s parameter must be a reference.
  • With this copy constructor, student2 will have its own dynamically allocated array, containing a copy of the data from student1.

Using const Parameters in Copy Constructors

  • A copy constructor should not modify its argument.
  • It is good practice to make the reference parameter const to prevent accidental modification of the source object.
StudentTestScores(const StudentTestScores &obj)
{ studentName = obj.studentName;
  numTestScores = obj.numTestScores;
  testScores = new double[numTestScores];
  for (int i = 0; i < numTestScores; i++)
       testScores[i] = obj.testScores[i]; }
  • Here is the revised class with the copy constructor added.
Contents of StudentTestScores.h (Version 2)
 #ifndef STUDENTTESTSCORES_H
 #define STUDENTTESTSCORES_H
 #include <string>
 using namespace std;

 const double DEFAULT_SCORE = 0.0;

 class StudentTestScores
 {
 private:
     string studentName;  
     double *testScores;  
     int numTestScores;   

     void createTestScoresArray(int size)
     { numTestScores = size;
       testScores = new double[size];
       for (int i = 0; i < size; i++)
           testScores[i] = DEFAULT_SCORE; }

 public:
     StudentTestScores(string name, int numScores)
     { studentName = name;
       createTestScoresArray(numScores); }

     StudentTestScores(const StudentTestScores &obj)
     { studentName = obj.studentName;
       numTestScores = obj.numTestScores;
       testScores = new double[numTestScores];
       for (int i = 0; i < numTestScores; i++)
           testScores[i] = obj.testScores[i]; }

     ~StudentTestScores()
     { delete [] testScores; }

     void setTestScore(double score, int index)
     { testScores[index] = score; }

     void setStudentName(string name)
     { studentName = name; }

     string getStudentName() const
     { return studentName; }

     int getNumTestScores() const
     { return numTestScores; }

     double getTestScore(int index) const
     { return testScores[index]; }
 };
 #endif

Copy Constructors and Function Parameters

  • When an object is passed by value to a function, the copy constructor is called to initialize the function’s parameter.
  • The parameter must be a reference to prevent an infinite loop.
  • If the parameter were not a reference (i.e., passed by value), the copy constructor would be called to create the parameter, which would in turn call the copy constructor to create its own parameter, and so on.

The Default Copy Constructor

  • If you do not provide a copy constructor for your class, C++ automatically creates a default copy constructor.
  • The default copy constructor performs a memberwise assignment (a shallow copy), which is the behavior that can cause problems with pointer members.

Checkpoint

  1. 14.8 Briefly describe what is meant by memberwise assignment.

  2. 14.9 Describe two instances when memberwise assignment occurs.

  3. 14.10 Describe a situation in which memberwise assignment should not be used.

  4. 14.11 When is a copy constructor called?

  5. 14.12 How does the compiler know that a member function is a copy constructor?

  6. 14.13 What action is performed by a class’s default copy constructor?

14.5 Operator Overloading

Concept:
  • C++ allows you to redefine the behavior of standard operators when they are used with class objects.

Operator Overloading

  • You can enable standard operators, like +=, to work with your class objects.
  • For instance, instead of today.add(5);, you could write the more intuitive today += 5;.
  • To achieve this, the operator must be overloaded, which means you provide a special function defining its behavior for your class.
Note:
  • You have already seen operator overloading in action. For example, the / operator performs floating-point division or integer division depending on the data types of its operands.

The this Pointer

  • The this pointer is a special, built-in pointer that exists in every class.
  • It is passed as a hidden argument to all non-static member functions.
  • The this pointer always points to the specific instance of the class that is calling the member function.
  • For example, when student1.getStudentName() is called, this points to the student1 object. When student2.getStudentName() is called, this points to student2.

Overloading the = Operator

  • While copy constructors handle initialization problems (like StudentTestScores student2 = student1;), they do not apply to simple assignment between two existing objects (student2 = student1;).

  • To change the default memberwise assignment behavior, you must overload the assignment operator (=).

  • This is done by creating a special member function called an operator function.

  • The following is an example of an operator= member function for the StudentTestScores class.

 const StudentTestScores operator=(const StudentTestScores &right)
 {  if (this != &right)
    {
       delete[] testScores;
       studentName = right.studentName;
       numTestScores = right.numTestScores;
       testScores = new double[numTestScores];
       for (int i = 0; i < numTestScores; i++)
          testScores[i] = right.testScores[i];
    }
    return *this;
 }
  • The function header can be broken down as follows:

  • Return type: The function returns a const StudentTestScores object. This allows for chaining assignments (e.g., a = b = c;).

  • Function name: The name operator= specifies that this function overloads the = operator.

  • Parameter declaration: The function takes one parameter, const StudentTestScores &right, which is a constant reference to the object on the right side of the = operator. It is a reference for efficiency and const to prevent modification of the source object.

Note:
  • The parameter is commonly named right to indicate it represents the object on the right side of the operator, but you can use any valid identifier.
  • The operator notation student2 = student1; is equivalent to the function call notation student2.operator=(student1);.
  • Because operator= is a member function, it has access to the private members of the right object passed to it.

Checking for Self-Assignment

  • In an operator= function, the this pointer points to the object on the left of the =, and the right parameter refers to the object on the right.

  • It is crucial to check for self-assignment (e.g., student1 = student1;).

  • The check if (this != &right) compares the memory address of the left object (this) with the address of the right object (&right).

  • Without this check, if the class manages dynamic memory, the first step (delete[] testScores;) would deallocate the memory that you are about to copy from, corrupting the object.

  • The corrected version skips the assignment logic if the objects are the same.

 const StudentTestScores operator=(const StudentTestScores &right)
 {  if (this != &right)
    {
       delete[] testScores;
       studentName = right.studentName;
       numTestScores = right.numTestScores;
       testScores = new double[numTestScores];
       for (int i = 0; i < numTestScores; i++)
          testScores[i] = right.testScores[i];
    }
    return *this;
 }

The = Operator’s Return Value

  • The built-in = operator allows for multiple assignments like a = b = c; because each assignment expression returns the value that was assigned.
  • To mimic this behavior, an overloaded operator= function should return the object that received the assignment.
  • The statement return *this; dereferences the this pointer to return the calling object itself, enabling chained assignments.

Copy Assignment

  • An overloaded = operator that copies data from one existing object to another is known as a copy assignment operator.

🗊 Program 14-6

 #include <iostream>
 #include "StudentTestScores.h"
 using namespace std;

 void displayStudent(StudentTestScores);

 int main()
 {
     StudentTestScores student1("Kelly Thorton", 3);
     student1.setTestScore(100.0, 0);
     student1.setTestScore(95.0, 1);
     student1.setTestScore(80, 2);

     StudentTestScores student2("Jimmy Griffin", 5);

     student2 = student1;

     displayStudent(student1);
     displayStudent(student2);
     return 0;
 }

 void displayStudent(StudentTestScores s)
 {
     cout << "Name: " << s.getStudentName() << endl;
     cout << "Test Scores: ";
     for (int i = 0; i < s.getNumTestScores(); i++)
         cout << s.getTestScore(i) << " ";
     cout << endl;
 }

💻 Program Output





🗊 Program 14-7

 #include <iostream>
 #include "StudentTestScores.h"
 using namespace std;

 void displayStudent(StudentTestScores);

 int main()
 {
     StudentTestScores student1("Kelly Thorton", 3);
     student1.setTestScore(100.0, 0);
     student1.setTestScore(95.0, 1);
     student1.setTestScore(80, 2);

     StudentTestScores student2("Jimmy Griffin", 5);
     StudentTestScores student3("Kristen Lee", 10);

     student3 = student2 = student1;

     displayStudent(student1);
     displayStudent(student2);
     displayStudent(student3);
     return 0;
 }

 void displayStudent(StudentTestScores s)
 {
     cout << "Name: " << s.getStudentName() << endl;
     cout << "Test Scores: ";
     for (int i = 0; i < s.getNumTestScores(); i++)
         cout << s.getTestScore(i) << " ";
     cout << endl;
 }

💻 Program Output







Some General Issues of Operator Overloading

  • While you can redefine an operator’s behavior, it is poor practice to change its fundamental meaning (e.g., making = display data instead of assigning it).
  • You cannot change the number of operands an operator takes. For example, = must remain a binary operator.
  • Most C++ operators can be overloaded.
Table 14-1 Operators that May Be Overloaded
+ - * / % ^ & | ~ ! = <
> += -= *= /= %= ^= &= |= << >> >>=
<<= == != <= >= && || ++ -- -<* , -<
[] () new delete
  • The only operators that cannot be overloaded are:
?:    .    .* ::    sizeof

Overloading Math Operators

  • Classes can also benefit from overloaded math operators like + and -.
  • The FeetInches class demonstrates this by allowing two FeetInches objects to be added or subtracted.
Contents of FeetInches.h (Version 1)
 #ifndef FEETINCHES_H
 #define FEETINCHES_H


 class FeetInches
 {
 private:
     int feet;         
     int inches;       
     void simplify();  
 public:
     FeetInches(int f = 0, int i = 0)
         { feet = f;
          inches = i;
          simplify(); }

     void setFeet(int f)
        { feet = f; }

     void setInches(int i)
        { inches = i;
          simplify(); }

     int getFeet() const
        { return feet; }

     int getInches() const
        { return inches; }

     FeetInches operator + (const FeetInches &); 
     FeetInches operator - (const FeetInches &); 
 };

 #endif
Contents of FeetInches.cpp (Version 1)
 #include <cstdlib>  
 #include "FeetInches.h"


 void FeetInches::simplify()
 {
     if (inches >= 12)
     {
         feet += (inches / 12);
         inches = inches % 12;
     }
     else if (inches < 0)
     {
         feet -= ((abs(inches) / 12) + 1);
         inches = 12 - (abs(inches) % 12);
     }
 }


 FeetInches FeetInches::operator + (const FeetInches &right)
 {
     FeetInches temp;

     temp.inches = inches + right.inches;
     temp.feet = feet + right.feet;
     temp.simplify();
     return temp;
 }


 FeetInches FeetInches::operator - (const FeetInches &right)
 {
     FeetInches temp;

     temp.inches = inches - right.inches;
     temp.feet = feet - right.feet;
     temp.simplify();
     return temp;
 }
  • The simplify function normalizes the values, for example, converting 14 inches into 1 foot and 2 inches.
  • The operator+ function for length3 = length1 + length2; works as follows:
    • A temporary FeetInches object, temp, is created to hold the result.
    • The inches from the calling object (length1) and the right object (length2) are added and stored in temp.
    • The feet from both objects are added and stored in temp.
    • temp.simplify() is called to normalize the result.
    • The temp object is returned and assigned to length3.

🗊 Program 14-8

 #include <iostream>
 #include "FeetInches.h"
 using namespace std;

 int main()
 {
     int feet, inches; 

     FeetInches first, second, third;

     cout << "Enter a distance in feet and inches: ";
     cin >> feet >> inches;

     first.setFeet(feet);
     first.setInches(inches);

     cout << "Enter another distance in feet and inches: ";
     cin >> feet >> inches;

     second.setFeet(feet);
     second.setInches(inches);

     third = first + second;

     cout << "first + second = ";
     cout << third.getFeet() << " feet, ";
     cout << third.getInches() << " inches.\n";

     third = first - second;

     cout << "first - second = ";
     cout << third.getFeet() << " feet, ";
     cout << third.getInches() << " inches.\n";

     return 0;
 }

💻 Program Output





Overloading the Prefix ++ Operator

  • Unary operators like ++ are overloaded without parameters because they only modify the calling object.
  • The function for the prefix ++ operator increments the member, simplifies it, and then returns the modified object (*this).
FeetInches FeetInches::operator++()
{
   ++inches;
   simplify();
   return *this;
}
  • Returning *this allows the operator to be used in expressions like distance2 = ++distance1;.

Overloading the Postfix ++ Operator

  • To distinguish the postfix ++ operator from the prefix version, its function signature includes a dummy parameter of type int.
  • The postfix operator should return the object’s state before it was incremented.
  • This is achieved by creating a temporary copy of the object, incrementing the original object, and then returning the temporary copy.
FeetInches FeetInches::operator++(int)
{
   FeetInches temp(feet, inches);
   inches++;
   simplify();
   return temp;
}

🗊 Program 14-9

 #include <iostream>
 #include "FeetInches.h"
 using namespace std;

 int main()
 {
     int count;  

     FeetInches first;

     FeetInches second(1, 5);

     cout << "Demonstrating prefix ++ operator.\n";
     for (count = 0; count < 12; count++)
     {
         first = ++second;
         cout << "first: " << first.getFeet() << " feet, ";
         cout << first.getInches() << " inches. ";
         cout << "second: " << second.getFeet() << " feet, ";
         cout << second.getInches() << " inches.\n";
     }

     cout << "\nDemonstrating postfix ++ operator.\n";
     for (count = 0; count < 12; count++)
     {
         first = second++;
         cout << "first: " << first.getFeet() << " feet, ";
         cout << first.getInches() << " inches. ";
         cout << "second: " << second.getFeet() << " feet, ";
         cout << second.getInches() << " inches.\n";
     }

     return 0;
 }

💻 Program Output



























Checkpoint

  1. 14.14 Assume there is a class named Pet. Write the prototype for a member function of Pet that overloads the = operator.

  2. 14.15 Assume dog and cat are instances of the Pet class, which has overloaded the = operator. Rewrite the following statement so it appears in function call notation instead of operator notation:

    dog = cat;
  3. 14.16 What is the disadvantage of an overloaded = operator returning void?

  4. 14.17 Describe the purpose of the this pointer.

  5. 14.18 The this pointer is automatically passed to what type of functions?

  6. 14.19 Assume there is a class named Animal that overloads the = and + operators. In the following statement, assume cat, tiger, and wildcat are all instances of the Animal class:

    wildcat = cat + tiger;

    Of the three objects, wildcat, cat, or tiger, which is calling the operator+ function? Which object is passed as an argument into the function?

  7. 14.20 What does the use of a dummy parameter in a unary operator function indicate to the compiler?

Overloading Relational Operators

  • Relational operators (>, <, ==, etc.) can be overloaded to compare class objects.
  • These operator functions are implemented like other binary operators, but they should always return a bool value (true or false).
  • The example below shows the function for overloading the > operator in the FeetInches class.
bool FeetInches::operator > (const FeetInches &right)
{
   bool status;
   if (feet > right.feet)
      status = true;
   else if (feet == right.feet &&  inches > right.inches)
      status = true;
   else
      status = false;
   return status;
}

🗊 Program 14-10

 #include <iostream>
 #include "FeetInches.h"
 using namespace std;

 int main()
 {
     int feet, inches; 

     FeetInches first, second;

     cout << "Enter a distance in feet and inches: ";
     cin >> feet >> inches;

     first.setFeet(feet);
     first.setInches(inches);

     cout << "Enter another distance in feet and inches: ";
     cin >> feet >> inches;

     second.setFeet(feet);
     second.setInches(inches);

     if (first == second)
         cout << "first is equal to second.\n";
     if (first   > second)
         cout << "first is greater than second.\n";
     if (first   < second)
         cout << "first is less than second.\n";

     return 0;
 }

💻 Program Output




💻 Program Output




💻 Program Output




Overloading the << and >> Operators

  • The stream insertion (<<) and stream extraction (>>) operators can be overloaded to provide a natural syntax for I/O with class objects.

  • This allows you to write cout << distance; instead of calling getter functions, and cin >> distance; instead of calling setter functions.

  • These operators must be overloaded as non-member functions (often as friend functions) because the object on the left (cout or cin) is an instance of an ostream or istream class, not your class.

  • The function to overload << for FeetInches:

ostream &operator << (ostream &strm, const FeetInches &obj)
{
   strm << obj.feet << " feet, " << obj.inches << " inches";
   return strm;
}
  • Parameters: It takes two parameters: a reference to the stream object (ostream &strm) and a reference to your class object (const FeetInches &obj).

  • Return Type: It returns a reference to the stream object (ostream &). This is essential to allow for chaining, such as cout << obj1 << obj2;.

  • The function to overload >> for FeetInches:

istream &operator >> (istream &strm, FeetInches &obj)
{
   cout << "Feet: ";
   strm >> obj.feet;
   cout << "Inches: ";
   strm >> obj.inches;
   obj.simplify();
   return strm;
}
  • Since these functions are not members of the FeetInches class, they cannot access its private members (feet and inches).
  • To grant them access, they must be declared as friends inside the class declaration.
Contents of FeetInches.h (Version 4)
 #ifndef FEETINCHES_H
 #define FEETINCHES_H

 #include <iostream>
 using namespace std;

 class FeetInches; 

 ostream &operator << (ostream &, const FeetInches &);
 istream &operator >> (istream &, FeetInches &);


 class FeetInches
 {
 private:
     int feet;         
     int inches;       
     void simplify();  
 public:
     FeetInches(int f = 0, int i = 0)
         { feet = f;
           inches = i;
           simplify(); }

     void setFeet(int f)
         { feet = f; }

     void setInches(int i)
         { inches = i;
           simplify(); }

     int getFeet() const
        { return feet; }

     int getInches() const
         { return inches; }

     FeetInches operator + (const FeetInches &); 
     FeetInches operator - (const FeetInches &); 
     FeetInches operator ++ ();              
     FeetInches operator ++ (int);           
     bool operator > (const FeetInches &);   
     bool operator < (const FeetInches &);   
     bool operator == (const FeetInches &);  

     friend ostream &operator << (ostream &, const FeetInches &);
     friend istream &operator >> (istream &, FeetInches &);
 };

 #endif

🗊 Program 14-11

 #include <iostream>
 #include "FeetInches.h"
 using namespace std;

 int main()
 {
     FeetInches first, second; 

     cout << "Enter a distance in feet and inches.\n";
     cin >> first;

     cout << "Enter another distance in feet and inches.\n";
     cin >> second;

     cout << "The values you entered are:\n";
     cout << first << " and " << second << endl;
     return 0;
 }

💻 Program Output









Overloading the [ ] Operator

  • The subscript operator ([]) can be overloaded to give a class array-like behavior.
  • This is useful for creating custom array classes that can perform actions like bounds checking, which standard C++ arrays do not.
  • The IntArray class below is an example of an array class with built-in bounds checking.
Contents of IntArray.h
 #ifndef INTARRAY_H
 #define INTARRAY_H

 class IntArray
 {
 private:
   int *aptr;                                  
   intarraySize;                               
   void subscriptError();                      
 public:
   IntArray(int);                              
   IntArray(const IntArray &);                 
   ~IntArray();                                

   int size() const                            
     { return arraySize; }

   const IntArray operator=(const IntArray &); 
   int &operator[](const int &);        
 };
 #endif
Contents of IntArray.cpp
 #include <iostream>
 #include <cstdlib> 
 #include "IntArray.h"
 using namespace std;

 IntArray::IntArray(int s)
 {
    arraySize = s;
    aptr = new int [s];
    for (int count = 0; count < arraySize; count++)
       *(aptr + count) = 0;
 }

 IntArray::IntArray(const IntArray &obj)
 {
    arraySize = obj.arraySize;
    aptr = new int [arraySize];
    for(int count = 0; count < arraySize; count++)
      *(aptr + count) = *(obj.aptr + count);
 }

 IntArray::~IntArray()
 {
    if (arraySize > 0)
       delete [] aptr;
 }

 void IntArray::subscriptError()
 {
    cout << "ERROR: Subscript out of range.\n";
    exit(0);
 }

 const IntArray IntArray::operator=(const IntArray &right)
 {
    if (this != &right)
    {
       delete[] aptr;
       arraySize = right.arraySize;
       aptr = new int[arraySize];
       for (int count = 0; count < arraySize; count++)
          *(aptr + count) = *(right.aptr + count);
    }
    return *this;
 }

 int &IntArray::operator[](const int &sub)
 {
    if (sub < 0 || sub >= arraySize)
       subscriptError();
    return aptr[sub];
 }
  • The operator[] function takes a single parameter, which is the value inside the brackets (the subscript).
int &IntArray::operator[](const int &sub)
{
   if (sub < 0 || sub >= arraySize)
      subscriptError();
   return aptr[sub];
}
  • Inside the function, it first checks if the subscript sub is within the valid range. If not, an error function is called.
  • Crucially, the function must return a reference (int &) to the array element.
  • Returning a reference makes the expression an lvalue, which means it represents a modifiable memory location. This allows the overloaded operator to be used on the left side of an assignment, as in table[5] = 27;.
  • If it returned a simple int, it would be an rvalue, and assignment would not be possible.

🗊 Program 14-12

 #include <iostream>
 #include "IntArray.h"
 using namespace std;

 int main()
 {
     const int SIZE = 10;  

     IntArray table(SIZE);

     for (int x = 0; x < SIZE; x++)
         table[x] = (x * 2);

     for (int x = 0; x < SIZE; x++)
         cout << table[x] << " ";
     cout << endl;

     for (int x = 0; x < SIZE; x++)
         table[x] = table[x] + 5;

     for (int x = 0; x < SIZE; x++)
         cout << table[x] << " ";
     cout << endl;

     for (int x = 0; x < SIZE; x++)
         table[x]++;

     for (int x = 0; x < SIZE; x++)
         cout << table[x] << " ";
     cout << endl;

     return 0;
 }

💻 Program Output




🗊 Program 14-13

 #include <iostream>
 #include "IntArray.h"
 using namespace std;

 int main()
 {
     const int SIZE = 10;  

     IntArray table(SIZE);

     for (int x = 0; x < SIZE; x++)
         table[x] = x;

     for (int x = 0; x < SIZE; x++)
         cout << table[x] << " ";
     cout << endl;

     cout << "Now attempting to use an invalid subscript.\n";
     table[SIZE + 1] = 0;
     return 0;
 }

💻 Program Output




Checkpoint

  1. 14.21 Describe the values that should be returned from functions that overload relational operators.

  2. 14.22 What is the advantage of overloading the << and >> operators?

  3. 14.23 What type of object should an overloaded << operator function return?

  4. 14.24 What type of object should an overloaded >> operator function return?

  5. 14.25 If an overloaded << or >> operator accesses a private member of a class, what must be done in that class’s declaration?

  6. 14.26 Assume the class NumList has overloaded the [] operator. In the expression below, list1 is an instance of the NumList class:

    list1[25]

    Rewrite the expression above to explicitly call the function that overloads the [] operator.

14.6 Object Conversion

Concept:
  • You can write special operator functions to define automatic type conversions from a class object to another data type.
  • Just as C++ automatically converts between built-in types (e.g., int to double), you can enable similar automatic conversions for your class objects.

  • This is achieved by writing a special type of operator function.

  • The function to convert a FeetInches object to a double is shown below:

FeetInches::operator double()
{
   double temp = feet;
   temp += (inches / 12.0);
   return temp;
}
  • Function Header: The function name is operator followed by the destination type (double).

  • No Return Type: The return type is not specified in the header because it is implied by the function’s name. It will always return a double.

  • No Parameters: Conversion operators typically do not take parameters.

  • With this function defined, a FeetInches object can be automatically converted to a double:

d = distance; 

🗊 Program 14-14

 #include <iostream>
 #include "FeetInches.h"
 using namespace std;

 int main()
 {
     double d; 
     int i;    

     FeetInches distance;

     cout << "Enter a distance in feet and inches:\n";
     cin >> distance;

     d = distance;

     i = distance;

     cout << "The value " << distance;
     cout << " is equivalent to " << d << " feet\n";
     cout << "or " << i << " feet, rounded down.\n";
     return 0;
 }

💻 Program Output






Checkpoint

  1. 14.27 When overloading a binary operator such as + or –, what object is passed into the operator function’s parameter?

  2. 14.28 Explain why overloaded prefix and postfix ++ and –– operator functions should return a value.

  3. 14.29 How does C++ tell the difference between an overloaded prefix and postfix ++ or –– operator function?

  4. 14.30 Write member functions of the FeetInches class that overload the prefix and postfix –– operators. Demonstrate the functions in a simple program similar to Program 14-14.

14.7 Aggregation

Concept:
  • Aggregation is when a class contains an instance of another class as one of its members.

Class Aggregation

  • In software design, it’s often useful to build complex objects from simpler ones, just like real-world objects are made of smaller parts.

  • This creates a “has a” relationship. For example, a Course object “has an” Instructor object and “has a” TextBook object.

  • This is also known as a whole-part relationship, where one class (the whole) is composed of other classes (the parts).

  • Using aggregation helps to separate related data into distinct, manageable classes.

  • The Instructor class holds instructor-related data.

Contents of Instructor.h
 #ifndef INSTRUCTOR
 #define INSTRUCTOR
 #include <iostream>
 #include <string>
 using namespace std;

 class Instructor
 {
 private:
     string lastName;     
     string firstName;    
     string officeNumber; 
 public:
     Instructor()
        { set("", "", ""); }

     Instructor(string lname, string fname, string office)
        { set(lname, fname, office); }

     void set(string lname, string fname, string office)
        { lastName = lname;
          firstName = fname;
          officeNumber = office; }

      void print() const
         { cout << "Last name: " << lastName << endl;
           cout << "First name: " << firstName << endl;
           cout << "Office number: " << officeNumber << endl; }
 };
 #endif
  • The TextBook class holds textbook-related data.
Contents of TextBook.h
 #ifndef TEXTBOOK
 #define TEXTBOOK
 #include <iostream>
 #include <string>
 using namespace std;

 class TextBook
 {
 private:
     string title;     
     string author;    
     string publisher; 
 public:
     TextBook()
        { set("", "", ""); }

     TextBook(string textTitle, string auth, string pub)
        { set(textTitle, auth, pub); }

     void set(string textTitle, string auth, string pub)
        { title = textTitle;
          author = auth;
          publisher = pub; }

     void print() const
        { cout << "Title: " << title << endl;
          cout << "Author: " << author << endl;
          cout << "Publisher: " << publisher << endl; }
 };
 #endif
  • The Course class is an aggregate class because it contains Instructor and TextBook objects as members.
Contents of Course.h
 #ifndef COURSE
 #define COURSE
 #include <iostream>
 #include <string>
 #include "Instructor.h"
 #include "TextBook.h"
 using namespace std;

 class Course
 {
 private:
     string courseName;      
     Instructor instructor;  
     TextBook textbook;      
 public:
     Course(string course, string instrLastName,
           string instrFirstName, string instrOffice,
           string textTitle, string author,
           string publisher)
     { 
       courseName = course;

        instructor.set(instrLastName, instrFirstName, instrOffice);

        textbook.set(textTitle, author, publisher); }

     void print() const
     { cout << "Course name: " << courseName << endl << endl;
       cout << "Instructor Information:\n";
       instructor.print();
       cout << "\nTextbook Information:\n";
       textbook.print();
       cout << endl;}
 };
 #endif

🗊 Program 14-15

 #include "Course.h"

 int main()
 {
     Course myCourse("Intro to Computer Science", 
         "Kramer", "Shawn", "RH3010",       
         "Starting Out with C++", "Gaddis", 
         "Pearson");                        

     myCourse.print();
     return 0;
 }

💻 Program Output










Using Member Initialization Lists with Aggregate Classes

  • A constructor’s member initialization list can be used to call the constructors for its member objects.

  • This is an alternative and often more efficient way to initialize member objects.

  • The Course constructor rewritten with a member initialization list:

 Course(string course, string instrLastName, string instrFirstName, 
       string instrOffice, string textTitle, string author,
       string publisher) :
 instructor(instrLastName, instrFirstName, instrOffice),
 textbook(textTitle, author, publisher)
 {
     courseName = course;
 }
  • In the initialization list, you call the constructors for the member objects (instructor and textbook), passing the required arguments.

Aggregation in UML Diagrams

  • In a UML diagram, aggregation is shown with a line connecting two classes, with an open diamond at the end next to the aggregate (or “whole”) class.

14.8 Focus on Object-Oriented Design: Class Collaborations

Concept:
  • In object-oriented programming, it’s common for classes to interact or collaborate to accomplish tasks.
  • Identifying these collaborations is a key part of the design process.
  • In many applications, objects of different classes must work together.

  • An object might require the services of another object to fulfill its own responsibilities.

  • For a collaboration to occur, one object must know about the other object’s public member functions and how to call them.

  • The Stock class below is an example. It holds a stock’s trading symbol and its sharePrice.

Contents of Stock.h
 #ifndef STOCK
 #define STOCK
 #include <string>
 using namespace std;

 class Stock
 {
 private:
     string symbol;     
     double sharePrice; 
 public:
     Stock()
         { set("", 0.0);}

     Stock(const string sym, double price)
         { set(sym, price); }

     Stock(const Stock &obj)
         { set(obj.symbol, obj.sharePrice); }

     void set(string sym, double price)
         { symbol = sym;
           sharePrice = price; }

     string getSymbol() const
         { return symbol; }

     double getSharePrice() const
         { return sharePrice; }
 };
 #endif
  • The StockPurchase class collaborates with the Stock class to simulate buying a stock.
  • To calculate the cost of a purchase, a StockPurchase object needs to get the share price from a Stock object. This requires calling the Stock class’s getSharePrice method.
Contents of StockPurchase.h
 #ifndef STOCK_PURCHASE
 #define STOCK_PURCHASE
 #include "Stock.h"

 class StockPurchase
 {
 private:
     Stock stock;   
     int shares;    
 public:
     StockPurchase()
         { shares = 0;}

     StockPurchase(const Stock &stockObject, int numShares)
         { stock = stockObject;
           shares = numShares; }

     double getCost() const
         { return shares * stock.getSharePrice(); }
 };
 #endif
  • The StockPurchase class demonstrates two collaborations with the Stock class:
    1. Its constructor accepts a Stock object and uses the Stock class’s copy constructor to create a copy.
    2. Its getCost function calls the stock object’s getSharePrice method to calculate the total cost.

🗊 Program 14-16

 #include <iostream>
 #include <iomanip>
 #include "Stock.h"
 #include "StockPurchase.h"
 using namespace std;

 int main()
 {
     int sharesToBuy;  

     Stock xyzCompany("XYZ", 9.62);

     cout << setprecision(2) << fixed << showpoint;
     cout << "XYZ Company's trading symbol is "
          << xyzCompany.getSymbol() << endl;
     cout << "The stock is currently $"
          << xyzCompany.getSharePrice()
          << " per share.\n";

     cout << "How many shares do you want to buy? ";
     cin >> sharesToBuy;

     StockPurchase buy(xyzCompany, sharesToBuy);

     cout << "The cost of the transaction is $"
          << buy.getCost() << endl;
     return 0;
 }

💻 Program Output





Determining Class Collaborations with CRC Cards

  • During design, you can identify necessary collaborations by examining class responsibilities.
  • A class’s responsibilities include what it needs to know and what it needs to do.
  • A popular tool for this is the CRC card, which stands for Class, Responsibilities, and Collaborations.
  • To create CRC cards:
    • Use one index card for each class.
    • Write the class name at the top.
    • Create two columns: one for responsibilities and one for collaborations.
    • For each responsibility, ask if the class needs to interact with another class to fulfill it.
    • If collaboration is needed, write the name of the collaborating class in the right column.
  • The CRC card for StockPurchase shows it must collaborate with the Stock class to know which stock to purchase and to calculate the cost.
  • Completing CRC cards for all classes gives a clear overview of how the classes in an application must interact.

Checkpoint

  1. 14.31 What are the benefits of having operator functions that perform object conversion?

  2. 14.32 Why are no return types listed in the prototypes or headers of operator functions that perform data type conversion?

  3. 14.33 Assume there is a class named BlackBox. Write the header for a member function that converts a BlackBox object to an int.

  4. 14.34 Assume there are two classes, Big and Small. The Big class has, as a member, an instance of the Small class. Write a sentence that describes the relationship between the two classes.

14.9 Focus on Object-Oriented Programming: Simulating the Game of Cho-Han

  • Cho-Han is a Japanese dice game where players bet on whether the sum of two rolled dice is even (Cho) or odd (Han).

  • We will simulate a simplified version of this game with a dealer and two players.

  • The simulation will run for five rounds, with points awarded for correct guesses.

  • The program will use three main classes:

    • Die class: From Chapter 13, used to simulate two six-sided dice.
    • Dealer class: Rolls the dice and determines if the result is Cho or Han.
    • Player class: Represents a player who can make a guess and accumulate points.
  • First, here is the Dealer class. It uses two Die objects.

Contents of Dealer.h
 #ifndef DEALER_H
 #define DEALER_H
 #include <string>
 #include "Die.h"
 using namespace std;

 class Dealer
 {
 private:
     Die die1;                     
     Die die2;                     
     int die1Value;                
     int die2Value;                

 public:
     Dealer();                    
     void rollDice();             
     string getChoOrHan();        
     int getDie1Value();          
     int getDie2Value();          
 };
 #endif
Contents of Dealer.cpp
 #include "Dealer.h"
 #include "Die.h"
 #include <string>
 using namespace std;

 Dealer::Dealer()
 {
     die1Value = 0;
     die2Value = 0;
 }

 void Dealer::rollDice()
 {
     die1.roll();
     die2.roll();

     die1Value = die1.getValue();
     die2Value = die2.getValue();
 }

 string Dealer::getChoOrHan()
 {
     string result; 

     int sum = die1Value + die2Value;

     if (sum % 2 == 0)
         result = "Cho (even)";
     else
         result = "Han (odd)";

     return result;
 }

 int Dealer::getDie1Value()
 {
     return die1Value;
 }

 int Dealer::getDie2Value()
 {
     return die2Value;
 }
  • Next is the Player class, which stores a player’s name, their guess, and their point total.
Contents of Player.h
 #ifndef PLAYER_H
 #define PLAYER_H
 #include <string>
 using namespace std;

 class Player
 {
 private:
     string name;           
     string guess;          
     int points;            

 public:
     Player(string);        
     void makeGuess();      
     void addPoints(int);   
     string getName();      
     string getGuess();     
     int getPoints();       
 };
 #endif
Contents of Player.cpp
 #include "Player.h"
 #include <cstdlib>
 #include <ctime>
 #include <string>
 using namespace std;

 Player::Player(string playerName)
 {
     srand(time(0));

     name = playerName;
     guess = "";
     points = 0;
 }

 void Player::makeGuess()
 {
     const int MIN_VALUE = 0;
     const int MAX_VALUE = 1;

     int guessNumber; 

     guessNumber = (rand() % (MAX_VALUE - MIN_VALUE + 1)) + MIN_VALUE;

     if (guessNumber == 0)
         guess = "Cho (even)";
     else
         guess = "Han (odd)";
 }

 void Player::addPoints(int newPoints)
 {
     points += newPoints;
 }

 string Player::getName()
 {
     return name;
 }

 string Player::getGuess()
 {
     return guess;
 }

 int Player::getPoints()
 {
     return points;
 }
  • The main function orchestrates the game, playing five rounds and then declaring a winner.

🗊 Program 14-17

 #include <iostream>
 #include <string>
 #include "Dealer.h"
 #include "Player.h"
 using namespace std;

 void roundResults(Dealer &, Player &, Player &);
 void checkGuess(Player &, Dealer &);
 void displayGrandWinner(Player, Player);

 int main()
 {
     const int MAX_ROUNDS = 5;  
     string player1Name;        
     string player2Name;        

     cout << "Enter the first player's name: ";
     cin >> player1Name;
     cout << "Enter the second player's name: ";
     cin >> player2Name;

     Dealer dealer;

     Player player1(player1Name);
     Player player2(player2Name);

     for (int round = 0; round < MAX_ROUNDS; round++)
     {
         cout << "----------------------------\n";
         cout << "Now playing round " << (round + 1)
              << endl;

         dealer.rollDice();

         player1.makeGuess();
         player2.makeGuess();

         roundResults(dealer, player1, player2);
     }

     displayGrandWinner(player1, player2);
     return 0;
 }

 void roundResults(Dealer &dealer, Player &player1, Player &player2)
 {
     cout << "The dealer rolled " << dealer.getDie1Value()
          << " and " << dealer.getDie2Value() << endl;

     cout << "Result: " << dealer.getChoOrHan() << endl;

     checkGuess(player1, dealer);
     checkGuess(player2, dealer);
 }

 void checkGuess(Player &player, Dealer &dealer)
 {
     const int POINTS_TO_ADD = 1; 

     string guess = player.getGuess();

     string choHanResult = dealer.getChoOrHan();

     cout << "The player " << player.getName()
          << " guessed " << player.getGuess() << endl;

     if (guess == choHanResult)
     {
         player.addPoints(POINTS_TO_ADD);
         cout << "Awarding " << POINTS_TO_ADD
              << " point(s) to " << player.getName()
              << endl;
     }
 }

 void displayGrandWinner(Player player1, Player player2)
 {
     cout << "----------------------------\n";
     cout << "Game over. Here are the results:\n";

     cout << player1.getName() << ": "
          << player1.getPoints() << " points\n";

     cout << player2.getName() << ": "
          << player2.getPoints() << " points\n";

     if (player1.getPoints() > player2.getPoints())
     {
         cout << player1.getName()
              << " is the grand winner!\n";
     }
     else if (player2.getPoints() > player1.getPoints())
     {
         cout << player2.getName()
              << " is the grand winner!\n";
     }
     else
     {
         cout << "Both players are tied!\n";
     }
 }

💻 Program Output













































  • The program uses several helper functions to manage the game flow:
    • main: Gets player names, creates the Dealer and Player objects, and runs the main game loop for five rounds.
    • roundResults: Displays the dealer’s roll and the Cho/Han result, then calls checkGuess for each player.
    • checkGuess: Compares a player’s guess to the dealer’s result and awards points if they match.
    • displayGrandWinner: Shows the final scores and declares the winner or a tie.

14.10 Rvalue References and Move Semantics

Concept:
  • A move operation efficiently transfers ownership of resources (like dynamically allocated memory) from a source object to a target object.
  • It is most useful when the source object is a temporary value that is about to be destroyed.
  • C++11 introduced features like move semantics to improve performance, particularly for assignment operations and constructors.
  • Understanding move semantics requires first understanding rvalue references.

Lvalues and Rvalues

  • During program execution, values in memory can be categorized into two types:
    • lvalues: Persistent values that have a name and can be accessed across multiple statements. They can appear on the left side of an assignment operator. Think of variables like int x;.
    • rvalues: Temporary, unnamed values that exist only for the duration of the statement that created them. They cannot appear on the left side of an assignment. Examples include the result of an expression like 2 * 6 or the return value of a function.
Tip:
  • A simple way to think of an lvalue is as anything that can be on the left side of an assignment operator.

Rvalue References

  • A standard reference variable (e.g., int &ref) is an lvalue reference and can only refer to lvalues.
  • C++11 introduced the rvalue reference (e.g., int &&rvalRef), which can refer only to temporary rvalues.
  • The syntax for an rvalue reference uses a double ampersand (&&).
  • An rvalue reference effectively gives a name to a temporary object, turning it into an lvalue and extending its lifetime.
int &&rvalRef = square(5); 
cout << rvalRef << endl;    
  • A key consequence is that a temporary object can have at most one rvalue reference pointing to it, ensuring that only one part of the program has access to it. This is vital for move operations.

Move Semantics

  • Operations like passing objects to functions or returning them from functions can involve a lot of inefficient memory allocation and copying, especially for classes that manage dynamic memory.

  • Move semantics avoids this expensive copying by “stealing” or transferring ownership of the underlying resources from the source object to the destination object.

  • This is safe and efficient because the source is a temporary rvalue that is about to be destroyed anyway.

  • The Person class below demonstrates the overhead of copying.

Contents of Person.h
 #ifndef PERSON_H
 #define PERSON_H
 #include <iostream>
 #include <cstring>
 using namespace std;

 class Person
 {
 private:
    char *name;
 public:
    Person()
    {  cout << "*** default constructor ***\n";
       name = nullptr; }

    Person(char *n)
    {  cout << "*** parameterized constructor ***\n";
       name = new char[strlen(n) + 1];
       strcpy(name, n); }

    Person(const Person &obj)
    {  cout << "*** copy constructor ***\n";
       name = new char[strlen(obj.name) + 1];
       strcpy(name, obj.name); }

    ~Person()
    {  cout << "*** destructor ***\n";
       delete [] name; }

    Person & operator=(const Person &right)
    {  cout << "*** assignment operator ***\n";
       if (this != &right)
       {
          if (name != nullptr)
             delete[] name;
          name = new char[strlen(right.name) + 1];
          strcpy(name, right.name);
       }
       return *this; }

    char *getName() const
    {  return name; }
 };
 #endif

🗊 Program 14-18

 #include <iostream>
 #include <string>
 #include "Person.h"
 using namespace std;

 Person makePerson();
 void displayPerson(Person);

 int main()
 {
     Person person;
     person = makePerson();
     displayPerson(person);
     return 0;
 }

 Person makePerson()
 {
     Person p("Will MacKenzie");
     return p;
 }

 void displayPerson(Person p2)
 {
     cout << p2.getName() << endl;
 }

💻 Program Output











  • The program’s output shows numerous constructor, destructor, and assignment operator calls, highlighting the overhead of creating, copying, and destroying temporary objects.

  • To optimize this, we can add a move assignment operator and a move constructor.

  • Move Assignment Operator: This operator takes an rvalue reference parameter. Instead of deep copying, it swaps the internal pointers of the source and destination objects.

Person& operator=(Person&& right)
{  if (this != &right)
   {
      swap(name, right.name); 
   }
   return *this;
}
  • Move Constructor: This constructor also takes an rvalue reference. It “steals” the pointer from the temporary source object and then nullifies the source’s pointer to prevent it from being deleted by the source’s destructor.
Person(Person&& temp)
{  
   name = temp.name;
   temp.name = nullptr;
}

When to Implement Move Semantics in a Class

  • You should implement move semantics (a move constructor and a move assignment operator) whenever your class contains a pointer or a handle to an external resource, such as dynamically allocated memory.
  • This ensures optimal performance by avoiding unnecessary copies when working with temporary objects.

Default Operations Provided by the Compiler

  • The C++ compiler can automatically generate default versions of five special member functions if you don’t provide them:
    • Default constructor
    • Copy constructor
    • Copy assignment operator
    • Move constructor
    • Move assignment operator
    • Destructor
  • However, if you manually define any of these, the compiler will not generate any of the others.
  • This is often called the “Rule of Five”: if you need to write one of these five special members, you should probably write all five to ensure correct resource management.

Review Questions and Exercises

Short Answer

  1. Describe the difference between an instance member variable and a static member variable.

  2. Assume a class named Numbers has the following static member function declaration:

    static void showTotal();

    Write a statement that calls the showTotal function.

  3. A static member variable is declared in a class. Where is the static member variable defined?

  4. What is a friend function?

  5. Why is it not always a good idea to make an entire class a friend of another class?

  6. What is memberwise assignment?

  7. When is a copy constructor called?

  8. How can the compiler determine if a constructor is a copy constructor?

  9. Describe a situation where memberwise assignment is not desirable.

  10. Why must the parameter of a copy constructor be a reference?

  11. What is a default copy constructor?

  12. Why would a programmer want to overload operators rather than use regular member functions to perform similar operations?

  13. What is passed to the parameter of a class’s operator= function?

  14. Why shouldn’t a class’s overloaded = operator be implemented with a void operator function?

  15. How does the compiler know whether an overloaded ++ operator should be used in prefix or postfix mode?

  16. What is the this pointer?

  17. What type of value should be returned from an overloaded relational operator function?

  18. The class Stuff has both a copy constructor and an overloaded = operator. Assume blob and clump are both instances of the Stuff class. For each statement below, indicate whether the copy constructor or the overloaded = operator will be called:

    Stuff blob = clump;
    clump = blob;
    blob.operator=(clump);
    showValues(blob);    
  19. Explain the programming steps necessary to make a class’s member variable static.

  20. Explain the programming steps necessary to make a class’s member function static.

  21. Consider the following class declaration:

    class Thing
    {
    private:
       int x;
       int y;
       static int z;
    public:
       Thing()
          { x = y = z; }
       static void putThing(int a)
          { z = a; }
    };

    Assume a program containing the class declaration defines three Thing objects with the following statement:

    Thing one, two, three;

    How many separate instances of the x member exist?

    How many separate instances of the y member exist?

    How many separate instances of the z member exist?

    What value will be stored in the x and y members of each object?

    Write a statement that will call the PutThing member function before the objects above are defined.

  22. Describe the difference between making a class a member of another class (object aggregation), and making a class a friend of another class.

  23. What is the purpose of a forward declaration of a class?

  24. Explain why memberwise assignment can cause problems with a class that contains a pointer member.

  25. Why is a class’s copy constructor called when an object of that class is passed by value into a function?

Fill-in-the-Blank

  1. If a member variable is declared ____________________, all objects of that class have access to the same variable.

  2. Static member variables are defined ____________________ the class.

  3. A(n) ____________________ member function cannot access any nonstatic member variables in its own class.

  4. A static member function may be called ____________________ any instances of its class are defined.

  5. A(n) ____________________ function is not a member of a class, but has access to the private members of the class.

  6. A(n) ____________________ tells the compiler that a specific class will be declared later in the program.

  7. ____________________ is the default behavior when an object is assigned the value of another object of the same class.

  8. A(n) ____________________ is a special constructor, called whenever a new object is initialized with another object’s data.

  9. ____________________ is a special built-in pointer that is automatically passed as a hidden argument to all nonstatic member functions.

  10. An operator may be ____________________ to work with a specific class.

  11. When overloading the ____________________ operator, its function must have a dummy parameter.

  12. Making an instance of one class a member of another class is called ____________________.

  13. Object aggregation is useful for creating a(n) ____________________ relationship between two classes.

Algorithm Workbench

  1. Assume a class named Bird exists. Write the header for a member function that overloads the = operator for that class.

  2. Assume a class named Dollars exists. Write the headers for member functions that overload the prefix and postfix ++ operators for that class.

  3. Assume a class named Yen exists. Write the header for a member function that overloads the < operator for that class.

  4. Assume a class named Length exists. Write the header for a member function that overloads cout’s << operator for that class.

  5. Assume a class named Collection exists. Write the header for a member function that overloads the [] operator for that class.

True or False

  1. T F Static member variables cannot be accessed by nonstatic member functions.

  2. T F Static member variables are defined outside their class declaration.

  3. T F A static member function may refer to nonstatic member variables of the same class, but only after an instance of the class has been defined.

  4. T F When a function is declared a friend by a class, it becomes a member of that class.

  5. T F A friend function has access to the private members of the class declaring it a friend.

  6. T F An entire class may be declared a friend of another class.

  7. T F In order for a function or class to become a friend of another class, it must be declared as such by the class granting it access.

  8. T F If a class has a pointer as a member, it’s a good idea to also have a copy constructor.

  9. T F You cannot use the = operator to assign one object’s values to another object, unless you overload the operator.

  10. T F If a class doesn’t have a copy constructor, the compiler generates a default copy constructor for it.

  11. T F If a class has a copy constructor, and an object of that class is passed by value into a function, the function’s parameter will not call its copy constructor.

  12. T F The this pointer is passed to static member functions.

  13. T F All functions that overload unary operators must have a dummy parameter.

  14. T F For an object to perform automatic type conversion, an operator function must be written.

  15. T F It is possible to have an instance of one class as a member of another class.

Find the Errors

Each of the following class declarations has errors. Locate as many as you can.

  1. class Box
    {
       private:
          double width;
          double length;
          double height;
       public:
          Box(double w, l, h)
             { width = w; length = l; height = h; }
          Box(Box b) 
             { width = b.width; 
               length = b.length;
               height = b.height; }
       ... Other member functions follow ...
    };
  2. class Circle
    {
       private:
          double diameter;
          int centerX;
          int centerY;
       public:
          Circle(double d, int x, int y)
             { diameter = d; centerX = x; centerY = y; }
          void Circle=(Circle &right)
             { diameter = right.diameter;
               centerX = right.centerX;
               centerY = right.centerY; }
       ... Other member functions follow ...
    };
  3. class Point
    {
       private:
          int xCoord;
          int yCoord;
       public:
          Point (int x, int y)
             { xCoord = x; yCoord = y; }
          void operator+(const &Point right)
             { xCoord += right.xCoord;
               yCoord += right.yCoord;
             }
       ... Other member functions follow ...
    };
  4. class Box
    {
       private:
          double width;
          double length;
          double height;
       public:
          Box(double w, l, h)
             { width = w; length = l; height = h; }
          void operator++()
             { ++width; ++length;}
          void operator++()
             { width++; length++;}
       ... Other member functions follow ...
    };
  5. class Yard
    {
       private:
          float length;
       public:
          yard(float l)
             { length = l; }
          void operator float()
             { return length; }
       ... Other member functions follow ...
    };

Programming Challenges

  1. Numbers Class

    Design a class Numbers that can be used to translate whole dollar amounts in the range 0 through 9999 into an English description of the number. For example, the number 713 would be translated into the string seven hundred thirteen, and 8203 would be translated into eight thousand two hundred three. The class should have a single integer member variable:

    int number;

    and a static array of string objects that specify how to translate key dollar amounts into the desired format. For example, you might use static strings such as

    string lessThan20[20] = {"zero", "one", ..., "eighteen", "nineteen"};
    string hundred = "hundred";
    string thousand = "thousand";

    The class should have a constructor that accepts a nonnegative integer and uses it to initialize the Numbers object. It should have a member function print() that prints the English description of the Numbers object. Demonstrate the class by writing a main program that asks the user to enter a number in the proper range then prints out its English description.

  2. Day of the Year

    Assuming a year has 365 days, write a class named DayOfYear that takes an integer representing a day of the year and translates it to a string consisting of the month followed by day of the month. For example,

    • Day 2 would be January 2.

    • Day 32 would be February 1.

    • Day 365 would be December 31.

    The constructor for the class should take as parameter an integer representing the day of the year, and the class should have a member function print() that prints the day in the month–day format. The class should have an integer member variable to represent the day, and should have static member variables holding string objects that can be used to assist in the translation from the integer format to the month–day format.

    Test your class by inputting various integers representing days and printing out their representation in the month–day format.

  3. Day of the Year Modification

    Modify the DayOfYear class, written in Programming Challenge 2, to add a constructor that takes two parameters: a string object representing a month and an integer in the range 0 through 31 representing the day of the month. The constructor should then initialize the integer member of the class to represent the day specified by the month and day of month parameters. The constructor should terminate the program with an appropriate error message if the number entered for a day is outside the range of days for the month given.

    Add the following overloaded operators:

    ++ prefix and postfix increment operators. These operators should modify the DayOfYear object so it represents the next day. If the day is already the end of the year, the new value of the object will represent the first day of the year.
    -- prefix and postfix decrement operators. These operators should modify the DayOfYear object so it represents the previous day. If the day is already the first day of the year, the new value of the object will represent the last day of the year.
  4. NumDays Class

    Solving the NumDays Problem

    Design a class called NumDays. The class’s purpose is to store a value that represents a number of work hours and convert it to a number of days. For example, 8 hours would be converted to 1 day, 12 hours would be converted to 1.5 days, and 18 hours would be converted to 2.25 days. The class should have a constructor that accepts a number of hours, as well as member functions for storing and retrieving the hours and days. The class should also have the following overloaded operators:

    + Addition operator. When two NumDays objects are added together, the overloaded + operator should return the sum of the two objects’ hours members.
    - Subtraction operator. When one NumDays object is subtracted from another, the overloaded - operator should return the difference of the two objects’ hours members.
    ++ Prefix and postfix increment operators. These operators should increment the number of hours stored in the object. When incremented, the number of days should be automatically recalculated.
    -- Prefix and postfix decrement operators. These operators should decrement the number of hours stored in the object. When decremented, the number of days should be automatically recalculated.
  5. Time Off

    Note:

    This assignment assumes you have already completed Programming Challenge 4.

    Design a class named TimeOff. The purpose of the class is to track an employee’s sick leave, vacation, and unpaid time off. It should have, as members, the following instances of the NumDays class described in Programming Challenge 4:

    maxSickDays A NumDays object that records the maximum number of days of sick leave the employee may take.
    sickTaken A NumDays object that records the number of days of sick leave the employee has already taken.
    maxVacation A NumDays object that records the maximum number of days of paid vacation the employee may take.
    vacTaken A NumDays object that records the number of days of paid vacation the employee has already taken.
    maxUnpaid A NumDays object that records the maximum number of days of unpaid vacation the employee may take.
    unpaidTaken A NumDays object that records the number of days of unpaid leave the employee has taken.

    Additionally, the class should have members for holding the employee’s name and identification number. It should have an appropriate constructor and member functions for storing and retrieving data in any of the member objects.

    Input Validation: Company policy states that an employee may not accumulate more than 240 hours of paid vacation. The class should not allow the maxVacation object to store a value greater than this amount.

  6. Personnel Report

    Note:

    This assignment assumes you have already completed Programming Challenges 4 and 5.

    Write a program that uses an instance of the TimeOff class you designed in Programming Challenge 5. The program should ask the user to enter the number of months an employee has worked for the company. It should then use the TimeOff object to calculate and display the employee’s maximum number of sick leave and vacation days. Employees earn 12 hours of vacation leave and 8 hours of sick leave per month.

  7. Month Class

    Design a class named Month. The class should have the following private members:

    name A string object that holds the name of a month, such as “January,” “February,” and so on.
    monthNumber An integer variable that holds the number of the month. For example, January would be 1, February would be 2, and so on. Valid values for this variable are 1 through 12.

    In addition, provide the following member functions:

    • A default constructor that sets monthNumber to 1 and name to “January.”

    • A constructor that accepts the name of the month as an argument. It should set name to the value passed as the argument and set monthNumber to the correct value.

    • A constructor that accepts the number of the month as an argument. It should set monthNumber to the value passed as the argument and set name to the correct month name.

    • Appropriate set and get functions for the name and monthNumber member variables.

    • Prefix and postfix overloaded ++ operator functions that increment monthNumber and set name to the name of next month. If monthNumber is set to 12 when these functions execute, they should set monthNumber to 1 and name to “January.”

    • Prefix and postfix overloaded -- operator functions that decrement monthNumber and set name to the name of previous month. If monthNumber is set to 1 when these functions execute, they should set monthNumber to 12 and name to “December.”

    Also, you should overload cout’s << operator and cin’s >> operator to work with the Month class. Demonstrate the class in a program.

  8. Date Class Modification

    Modify the Date class in Programming Challenge 1 of Chapter 13. The new version should have the following overloaded operators:

    ++ Prefix and postfix increment operators. These operators should increment the object’s day member.
    -- Prefix and postfix decrement operators. These operators should decrement the object’s day member.
    - Subtraction operator. If one Date object is subtracted from another, the operator should give the number of days between the two dates. For example, if April 10, 2014 is subtracted from April 18, 2014, the result will be 8.
    <<

    couts stream insertion operator. This operator should cause the date to be displayed in the form

    April 18, 2018

    >> cins stream extraction operator. This operator should prompt the user for a date to be stored in a Date object.

    The class should detect the following conditions and handle them accordingly:

    • When a date is set to the last day of the month and incremented, it should become the first day of the following month.

    • When a date is set to December 31 and incremented, it should become January 1 of the following year.

    • When a day is set to the first day of the month and decremented, it should become the last day of the previous month.

    • When a date is set to January 1 and decremented, it should become December 31 of the previous year.

    Demonstrate the class’s capabilities in a simple program.

    Input Validation: The overloaded >> operator should not accept invalid dates. For example, the date 13/45/2018 should not be accepted.

  9. FeetInches Modification

    Modify the FeetInches class discussed in this chapter so it overloads the following operators:

    <=
    >=
    !=

    Demonstrate the class’s capabilities in a simple program.

  10. Corporate Sales

    A corporation has six divisions, each responsible for sales to different geographic locations. Design a DivSales class that keeps sales data for a division, with the following members:

    • An array with four elements for holding four quarters of sales figures for the division.

    • A private static variable for holding the total corporate sales for all divisions for the entire year.

    • A member function that takes four arguments, each assumed to be the sales for a quarter. The value of the arguments should be copied into the array that holds the sales data. The total of the four arguments should be added to the static variable that holds the total yearly corporate sales.

    • A function that takes an integer argument within the range of 0–3. The argument is to be used as a subscript into the division quarterly sales array. The function should return the value of the array element with that subscript.

    Write a program that creates an array of six DivSales objects. The program should ask the user to enter the sales for four quarters for each division. After the data are entered, the program should display a table showing the division sales for each quarter. The program should then display the total corporate sales for the year.

    Input Validation: Only accept positive values for quarterly sales figures.

  11. FeetInches Class Copy Constructor and multiply Function

    Add a copy constructor to the FeetInches class. This constructor should accept a FeetInches object as an argument. The constructor should assign to the feet attribute the value in the argument’s feet attribute, and assign to the inches attribute the value in the argument’s inches attribute. As a result, the new object will be a copy of the argument object.

    Next, add a multiply member function to the FeetInches class. The multiply function should accept a FeetInches object as an argument. The argument object’s feet and inches attributes will be multiplied by the calling object’s feet and inches attributes, and a FeetInches object containing the result will be returned.

  12. LandTract Class

    Make a LandTract class that is composed of two FeetInches objects: one for the tract’s length, and one for the width. The class should have a member function that returns the tract’s area. Demonstrate the class in a program that asks the user to enter the dimensions for two tracts of land. The program should display the area of each tract of land, and indicate whether the tracts are of equal size.

  13. Carpet Calculator

    The Westfield Carpet Company has asked you to write an application that calculates the price of carpeting for rectangular rooms. To calculate the price, you multiply the area of the floor (width times length) by the price per square foot of carpet. For example, the area of floor that is 12 feet long and 10 feet wide is 120 square feet. To cover that floor with carpet that costs $8 per square foot would cost $960. (12 × 10 × 8 = 960.)

    First, you should create a class named RoomDimension that has two FeetInches objects as attributes: one for the length of the room, and one for the width. (You should use the version of the FeetInches class you created in Programming Challenge 11 with the addition of a multiply member function. You can use this function to calculate the area of the room.) The RoomDimension class should have a member function that returns the area of the room as a FeetInches object.

    Next, you should create a RoomCarpet class that has a RoomDimension object as an attribute. It should also have an attribute for the cost of the carpet per square foot. The RoomCarpet class should have a member function that returns the total cost of the carpet.

    Once you have written these classes, use them in an application that asks the user to enter the dimensions of a room and the price per square foot of the desired carpeting. The application should display the total cost of the carpet.

  14. Parking Ticket Simulator

    For this assignment, you will design a set of classes that work together to simulate a police officer issuing a parking ticket. The classes you should design are:

    • The ParkedCar Class: This class should simulate a parked car. The class’s responsibilities are:

      • To know the car’s make, model, color, license number, and the number of minutes that the car has been parked
    • The ParkingMeter Class: This class should simulate a parking meter. The class’s only responsibility is:

      • To know the number of minutes of parking time that has been purchased
    • The ParkingTicket Class: This class should simulate a parking ticket. The class’s responsibilities are:

      • To report the make, model, color, and license number of the illegally parked car

      • To report the amount of the fine, which is $25 for the first hour or part of an hour that the car is illegally parked, plus $10 for every additional hour or part of an hour that the car is illegally parked

      • To report the name and badge number of the police officer issuing the ticket

    • The PoliceOfficer Class: This class should simulate a police officer inspecting parked cars. The class’s responsibilities are:

      • To know the police officer’s name and badge number

      • To examine a ParkedCar object and a ParkingMeter object, and determine whether the car’s time has expired

      • To issue a parking ticket (generate a ParkingTicket object) if the car’s time has expired

    Write a program that demonstrates how these classes collaborate.

  15. Car Instrument Simulator

    For this assignment you will design a set of classes that work together to simulate a car’s fuel gauge and odometer. The classes you will design are:

    • The FuelGauge Class: This class will simulate a fuel gauge. Its responsibilities are

      • To know the car’s current amount of fuel, in gallons.

      • To report the car’s current amount of fuel, in gallons.

      • To be able to increment the amount of fuel by 1 gallon. This simulates putting fuel in the car. (The car can hold a maximum of 15 gallons.)

      • To be able to decrement the amount of fuel by 1 gallon, if the amount of fuel is greater than 0 gallons. This simulates burning fuel as the car runs.

    • The Odometer Class: This class will simulate the car’s odometer. Its responsibilities are:

      • To know the car’s current mileage.

      • To report the car’s current mileage.

      • To be able to increment the current mileage by 1 mile. The maximum mileage the odometer can store is 999,999 miles. When this amount is exceeded, the odometer resets the current mileage to 0.

      • To be able to work with a FuelGauge object. It should decrease the FuelGauge object’s current amount of fuel by 1 gallon for every 24 miles traveled. (The car’s fuel economy is 24 miles per gallon.)

    Demonstrate the classes by creating instances of each. Simulate filling the car up with fuel, then run a loop that increments the odometer until the car runs out of fuel. During each loop iteration, print the car’s current mileage and amount of fuel.