|
|
In this laboratory assignment we will consider the following:
A. Review of C++ Class Construct
B. Using C++ Constructors
C. Overloaded Functions
D. Overloading Constructors in a C++ Class
E. Overloaded Operators
F. Accessors and Mutators
G. Array of Objects
In Lab 16, we introduced the C++ class construct that is used to define an object. The general form of the class specification section (typically stored in a .h header file) usually resembles something like the following:
class SomeClass { public: // Data members and methods (function prototypes) are // included in this section to provide the public interface // of the object (the data and operations // that are needed to use the object effectively) . . . private: // This section contains data members and methods // that can only be accessed by members of the class. . . . };
We also saw that the implementation section for the class contains implementations for methods of the class. Short methods could be completely defined in the class specification; these are called inline functions. However, except for exceptional circumstances, we usually avoid inline functions. It is considered better if the class specification contains only the specification of the class and no code. The actual implementation of the class should be contained in a separate implementation file.
We learned in Lab 16 to declare (instantiate) an instance of the class as follows:
SomeClass object1;
class LoanClass { 1: void printRate(); float payoff(); void schedule(); void LoanSettings (char t, float r, int m); 2: char type; float rate; int months; float simplePayment(); float compoundPayment(); };
the main function of the program can make a call to simplePayment() for any LoanClass instance.class LoanClass { public: void printRate(); float payoff(); void schedule(); void LoanSettings(char t, float r, int m); private: char type; float rate; int months; float simplePayment(); float compoundPayment(); };
With that brief review, we will introduce some additional features of the C++ class. Consider the Clock class that we discussed in Lab 16. If we wished to declare a Clock object and then initialize the data members in the object, we would first have to declare the object:
The Clock object, yourClock, could now be initialized by calling the setClock() member function. Initializing yourClock to the time 10:15:00 am could be accomplished as follows:Clock yourClock;
yourClock.setClock(10,15,0,"AM");
There is nothing wrong with this method of declaring and initializing an object. However, there is a more convenient way to initialize the data members of an object when defining the object. C++ includes special provisions for such initializations. When a class is defined, a special member function called a constructor can be used. A constructor is a member function that is automatically called when an object of that class is declared. A constructor may be used to initialize the values of data members and to do any other initialization that may be needed.
For example, consider the ifstream class. When we declare an input file stream as:
an instance of the ifstream class is created. The statementifstream ins;
is a call to the member function "open" that connects the input file stream ins to the file "myfile.dat". The following shows an alternate way to declare an input file stream and to open the stream at declaration time:ins.open("myfile.dat");
This second form of declaration and initialization uses a "constructor" of the class ifstream.ifstream ins("myfile.dat");
Suppose we wish to declare an instance of a Clock and initialize the Clock to the time 10:15:00 am at declaration time. If we had an appropriate constructor, we could type:
Clock yourClock(10,15,0,"AM");
We now need to learn how to define a constructor (a special method) to allow us to use the above statement. A constructor is defined the same way as other member functions, except for two rules:
Let us consider a modified version of the Clock class declaration:
class Clock { public: //function prototypes of member functions (methods) //in the public interface //class constructor that initializes the clock to //h hours, m minutes, s seconds and meridian (am or pm). Clock(int h, int m, int s, atring mer); //Set the clock to the specified time void setClock(int h, int m, int s, string mer); //Display the time in standard notation void displayStandard(); //Display the time in military notation void displayMilitary(); private: //declarations of data members that are private int hr, //an hour in the range 1 - 12 min, //a minute in the range 0 - 59 sec, //a second in the range 0 - 59 string meridian; //is the time AM (0) or PM (1) };
Note that the name of the constructor is Clock(), the same as the name of the class. Also note that the prototype for the constructor Clock does not start with void or with any other type. Finally note that the constructor is placed in the public section of the class specification. Normally, you should make your constructors public methods so all clients may access the constructor.
With the redefined Clock class, two objects of type Clock can be declared and initialized as follows:
Assuming that the implementation of the constructor performs the initializing actions promised, the declaration above would declare yourClock to have the time 10:15:00 am and myClock to have the time 1:30:45 pm.Clock yourClock(10, 15, 0, "AM");
Clock myClock(1, 30, 45, "PM");
The implementation of a constructor is given in the same way as any other method. The implementation of the Clock constructor would appear as follows:
//Class constructor that initializes the clock to //h hours, m minutes, s seconds and am or pm. Clock::Clock(int h, int m, int s, string mer) { //hr, min, sec, and ampm are declared in the private //section of the class hr = h; min = m; sec = s; meridian = mer; }
Since the class and the constructor have the same name, the name Clock occurs twice in the function heading. The Clock before the scope resolution operator :: is the name of the class and the Clock after the scope resolution operator is the name of the constructor function. Note that there is no return type specified in the function heading.
In our clock example, the constructor has parameters to allow the hours, minutes, seconds, and meridian to be set. A constructor does not have to have any parameters. If the constructor has no parameters, it is called the default constructor. Often a default constructor is provided to set the data members to default values. The implementation for a default constructor for the clock class might look like:
//The default constructor sets the time on the clock //to 12:00:00am Clock::Clock() { hr = 12; min = 0; sec = 0; meridian = "AM" }
To create an instance of the Clock class using the default constructor, the following statement might appear in a main program:
Clock myClock;
Notice that there are no parenthesis after myClock to indicate that a function with no arguments is being called. Just remember that this is how the default constructor is activated.
A. There is no type
B. void
C. Bool because it returns true or false based on whether or not the initialization was successful.
D. The type is dependent upon the actual class.
class LoanClass { public: LoanClass(char t, float r, int m); void printRate(); float payoff(); void schedule(); private: char type; float rate; int months; float simplePayment(); float compoundPayment(); };
Copy the files storeClassa.h, storeClassa.cpp, and inlab17a.cpp to your account. These files contain the specification and implementation of a class containing information about a store. Examine each of the files carefully to make sure you understand the code.
A. public member function
B. private member function
C. public data member
D. private data member
Constructors will be considered again after we discuss a feature of C++
that allows a function to have multiple definitions.
In the English language, a word may have more than one meaning. We determine the meaning of a word by considering the context in which the word appears. For example, the word string has two meanings -- an object possibly made from twine that might be used to tie around another object or (in computer science) just a sequence of characters. We have no problem determining which meaning is intended when we consider the following sentences:
Similarly, functions may have more than one meaning in C++. When this is the case, we say we have an overloaded function. Thus, the same function name may be defined more than once with different formal parameters.
In C++, we can overload functions and operators by providing multiple definitions for the function or operator. The compiler determines which definition is intended by the context (i.e., function signature) in which the function or operator occurs. Suppose we wished to have two meanings for a function called avg() that would average numbers and we provide the following definitions:
and//Find the average of two integers float avg(int a, int b) { return float(a+b)/2.0; }
The main function might have the following statements//Find the average of an array of n integers float avg(int a[], int n) { int sum = 0; for (int i = 0; i < n; i++) sum += a[i]; return float(sum) / n; }
int a = 2, b = 3; int x[5] = {10, 20, 30, 40, 50}; cout << "The average of 2 and 3 is " << avg(a, b) << endl; cout << "The average of the integers 10, 20, 30, 40," << " 50 is" << avg(x, 5) << endl;
As you can see the meaning of the function avg() is completely determined by the context (what arguments are sent to the function). This property of functions with multiple meanings is called overloading. Overloading is an example of polymorphism, a term derived from a Greek word meaning "many forms". The use of the same function name to mean different things is called polymorphism. We will see that polymorphism is very important in defining objects.
int a = 10, b = 6; int x[4] = {5, 6, 7, 8}; cout << sum(a, b) << endl; cout << sum(x, 4) << endl;B. Which of the following determines which definition of an overloaded function is intended based on the context?
A. The user
B. The compiler
C. The linker
D. The processor
A. The program must add 3 numbers. It must also add 3 arrays.
B. The program must multiply two arrays. It must also add two arrays.
C. A and B
D. None of the above
A constructor is called automatically when an object is declared. In Lab 16, the Clock class and the Date class did not include a constructor. When a class does not include a constructor, the compiler automatically creates a default constructor for the class and leaves the data members uninitialized. Thus when the statement
was used, the compiler provided a default constructor.Date christmas;
Using constructors is an all or nothing situation. If the class contains a constructor, then every time an object of that type is declared, C++ looks for an appropriate constructor definition in the class. Thus the following declaration would be illegal for the modified Clock class that did not contain the default constructor but did contain a different constructor:
Clock newClock; //ILLEGAL declaration
The reason this statement is illegal is that since the Clock class contains a constructor, the compiler will not create a default constructor. Instead the Clock class is searched for a constructor that requires no arguments. Since the class only has one constructor that requires four arguments, this is an illegal declaration. To allow a statement such as the one above, a constructor must be added (an overloaded function) that requires no arguments.
A constructor with no arguments is called a default constructor because it applies in the default case when an object is declared without specifying any arguments. Since it is likely that an object will be declared without giving any arguments, a default constructor should almost always be included with the class. The default constructor for the Clock class might be:
Clock::Clock() {}
Notice that this default constructor does nothing. All data values are left uninitialized. In some cases, the default constructor would be used to initialize appropriate data members to default values. If the overloaded constructor above were added to the Clock class and the statement:
appeared in a C++ program, it would be a legal statement and the default constructor would be called. In summary, two points can be made about supplying default constructors:Clock newClock;
As a second example of using overloaded constructors in a class definition, suppose we wished to have a default constructor to set the time of a Clock object to 12:00 am so that any of the following declarations would work.
Clock myClock(10, 15, 0, "PM"); Clock yourClock;
The default constructor would now be:
//Class constructor that initializes the clock to //12:00 am. Clock::Clock() { hr = 12; min = 0; sec = 0; meridian = "AM"; }
It should be noted that you cannot have two default constructors.
Not only can functions be overloaded but operators, such as ==, !=, +, etc., can also be overloaded. In fact this feature has been used anytime the same operator is used for different types. For example:
int num1, num2; Clock clock1, clock2; //assume that the integer num2 and the Clock clock2 have //been initialized //first use of the operator "=" between integer operands num1 = num2; //second use of the operator "=" between Clock operands clock1 = clock2;
The operator "=" is used twice -- once between two integers and once between two Clock objects. The operator is used to replace the integer num1 by the integer num2 in the first instance. In the second instance, the hr, min, sec, and meridian data in clock1 is replaced by the hr, min, sec and meridian data of clock2.
We might also wish to overload other operators such as !=, <, > so that we could do the following for example:
Clock yourClock, myClock; . . . if (yourClock != myClock) cout << "Your clock is wrong.\n";
We cannot use the operator != between two Clock objects without first defining what this would mean. C++ does provide a default definition of the operator = but does not provide a definition for other operators. When the operator = is used between two objects of a class, C++ copies the data members of the class on the right of the = to the class on the left.
C++ considers an operator to be a function, so an operator can be overloaded also. Overloading an operator, such as !=, is very similar to overloading a function. To overload the != operator, the following function prototype would be included in the Clock specification section.
bool operator!=(Clock rhs);
As can be seen, an operator function named "operator op" overloads the operator op. Thus the function operator!=( ) overloads the operator !=, the function operator<( ) would overload the operator < and so on. There are some restrictions:
The implementation of the function operator != for the Clock class follows below:
bool Clock::operator!=(Clock rhs) { bool notequal = false; //hr, min, sec, meridian are private data members of the lhs clock if ((hr != rhs.hr)||(min != rhs.min)||(sec != rhs.sec)||(meridian != rhs.meridian)) notequal = true; return notequal; } // end operator!=
Now if this overloaded operator were used to compare two instances of the clock class as below, myClock would be passed to the method above as an actual argument to the method. The object yourClock would be passed the message to compare itself with the argument to the right of the !=.
Clock yourClock, myClock; . . . if (yourClock != myClock) cout << "Your clock is wrong.\n";
Suppose we have a Book class defined as follows:
class Book { private: string title; //the title of the book string author; //the author of the book string ISBN; //the ISBN number for the book public: //the default constructor Book(); //an overloaded constructor receiving all data members as arguments Book(string title, string author, string ISBN); //gets (accessors) and sets (mutators) to allow a client to //have access to each data member and to allow the client to //modify each data member. void setAuthor(string a); string getAuthor(); void setTitle(string t); string getTitle(); void setISBN(string i); string getISBN(); //prints all of the data members of a book void print(); };
We have introduced two new types of methods in the book class above. The first is a accessor (aka getter) method. An accessor method allows the client access to an individual data member. The second is a mutator method (aka setter) method. A mutator allows the client to change a data member within reason. The Book class has ultimate control over what is placed into a data member. For example, in the mutator for the ISBN number, the Book class might check to see if the ISBN number is a valid number before assigning it to the data member.
A client must often work with an array of objects. For example suppose that a client is working with an array of books as follows:
Book arrayOfBooks[10];
When this array of books is created, the default constructor is activated for all 10 Book objects that are in the array. Thus the class must have a default constructor or the compiler must provide one.
If the client wished to read the array of books, it might be read using the code below:
string author, title, ISBN; //read the data for 10 books for (int i = 0; i < 10; i++) { cout << "Please enter the author, title, and ISBN for book " << i << endl; //read the author, title, and ISBN -- these might contain spaces so use getline getline(cout, author); getline(cout, title); getline(cout, ISBN); //put the author, title, and ISBN into the ith book object arrayOfBooks[i].setAuthor(author); arrayOfBooks[i].setTitle(title); arrayOfBooks[i].setISBN(ISBN); }
Notice in the code above that using an array of objects is similar to using an array of structs. The index in the array appears first so that the compiler can determine which object is to be used. Next the method that is being used with that object appears. If we wanted to set the title of the 0th book in the array to "The Firm" then we would use the notation
arrayOfBooks[0].setTitle("The Firm");
Submit the required log files by using the command
$ handin lab17log lab17ex3.log lab17ex6.log lab17ex12.log