Chapter 12. Adding Class to C++

In This Chapter

  • Grouping data into classes

  • Declaring and defining class members

  • Adding active properties to the class

  • Accessing class member functions

  • Overloading member functions

Programs often deal with groups of data: a person's name, rank, and serial number, stuff like that. Any one of these values is not sufficient to describe a person — only in the aggregate do the values make any sense. A simple structure such as an array is great for holding stand-alone values, but it doesn't work well for data groups. This makes good ol' arrays inadequate for storing complex data (such as personal credit records that the Web companies maintain so they can lose them to hackers).

For reasons that will become clear shortly, I'll call such a grouping of data an object. A microwave oven is an object (see Chapter 11 if that doesn't make sense). You are an object (no offence). Your savings account information in a database is an object.

Introducing the Class

How nice it would be if we could create objects in C++ that have the relevant properties of the real-world objects we're trying to model. What we need is a structure that can hold all the different types of data necessary to describe a single object. C++ calls the structure that combines multiples pieces of data into a single object a class.

The Format of a Class

A class consists of the keyword class followed by a name and an open and closed brace. A class used to describe a savings account including account number and balance might appear as follows:

class SavingsAccount
{
  public:
    unsigned accountNumber;
    double balance;
};

The statement after the open brace is the keyword public. (Hold off asking about the meaning of the public keyword. I'll make its meaning public a little later.)

Tip

The alternative keyword struct can be used in place of class. The two keywords are identical except that the public declaration is assumed in the struct and can be omitted. You should stick with class for most programs for reasons that will become clear later in this chapter.

Following the public keyword are the entries it takes to describe the object. The SavingsAccount class contains two elements: an unsigned integer accountNumber and the account balance. We can also say that accountNumber and balance are members or properties of the class SavingsAccount.

To create an actual savings account object, I type something like the following:

SavingsAccount mySavingsAccount;

We say that mySavingsAccount is an instance of the class SavngsAccount.

Tip

The naming convention used here is common: Class names are normally capitalized. In a class name with multiple words such as SavingsAccount, each word is capitalized, and the words are jammed together without an underscore. Object names follow the same rule of jamming multiple words together, but they normally start with a small letter, as in mySavingsAccount. As always, these norms (I hesitate to say rules) are to help out the human reader — C++ doesn't care one way or the other.

Accessing the Members of a Class

The following syntax is used to access the property of a particular object:

// Create a savings account object
SavingsAccount mySave;
mySave.accountNumber = 1234;
mySave.balance = 0;

// Input a second savings account from the keyboard
cout << "Input your account number and balance" << endl;
SavingsAccount urSave;
cin >> urSave.accountNumber;
cin >> urSave.balance;

This code snippet declares two objects of class SavingsAccount, mySave and urSave. The snippet initializes mySave by assigning a value to the account number and a 0 to the balance (as per usual for my savings account). It then creates a second object of the same class, urSave. The snippet reads the account number and balance from the keyboard.

An important point to note in this snippet is that mySave and urSave are separate, independent objects. Manipulating the members of one has no effect on the members of the other (lucky for urSave).

In addition, the name of the member without an associated object makes no sense. I cannot say either of the following:

balance = 0;                 // illegal; no object
SavingsAccount.balance = 0;  // class but still no object

Every savings account has its own unique account number and maintains a separate balance. (There may be properties that are shared by all savings accounts — we'll get to those soon enough — but account and balance don't happen to be among them.)

Activating Our Objects

You use classes to simulate real-world objects. The Savings class tries to represent a savings account. This allows you to think in terms of objects rather than simply lines of code. The closer C++ objects are to modeling the real world, the easier it is to deal with them in programs. This sounds simple enough. However, the Savings class doesn't do a very good job of simulating a savings account.

Simulating real-world objects

Real-world objects have data-type properties such as account numbers and balances, the same as the Savings class. This makes Savings a good starting point for describing a real object. But real-world objects do things. Ovens cook. Savings accounts accumulate interest; CDs charge a substantial penalty for early withdrawal — stuff like that.

Functional programs "do things" through functions. A C++ program might call strcmp() to compare two character strings or max() to return the maximum of two values. In fact, Chapter 23 explains that even stream I/O (cin >> and cout <<) is a special form of function call.

The Savings class needs active properties of its own if it's to do a good job of representing a real concept:

class Savings
{
  public:
    double deposit(double amount)
    {
        balance += amount;
        return balance;
    }

    unsigned accountNumber;
    double balance;
};

In addition to the account number and balance, this version of Savings includes the function deposit(). This gives Savings the ability to control its own future. A class MicrowaveOven has the function cook(), the class Savings has the function accumulateInterest(), and the class CD has a function to penalizeForEarlyWithdrawal().

Functions defined in a class are called member functions.

Why bother with member functions?

Why should you bother with member functions? What's wrong with the good ol' days of functional programming?

class Savings
{
  public:
    unsigned accountNumber;
double balance;
};
double deposit(Savings& s, double amount)
{
    s.balance += amount;
    return s.balance;
}

Here, deposit() implements the "deposit into savings account" function. This functional solution relies on an outside function, deposit(), to implement an activity that savings accounts perform but that Savings lacks. This gets the job done, but it does so by breaking the object-oriented (OO) rules.

The microwave oven has internal components that it "knows" how to use to cook, defrost, and burn to a crisp. Class data members are similar to the parts of a microwave — the member functions of a class perform cook-like functions.

When I make nachos, I don't have to start hooking up the internal components of the oven in a certain way to make it work. Nor do I rely on some external device to reach into a mess of wiring for me. I want my classes to work the same way my microwave does (and, no, I don't mean "not very well"). I want my classes to know how to manipulate their internals without outside intervention.

Adding a Member Function

To demonstrate member functions, start by defining a class Student. One possible representation of such a class follows (taken from the program CallMemberFunction):

class Student
{
  public:
    // add a completed course to the record
    float addCourse(int hours, float grade)
    {
        // calculate the sum of all courses times
        // the average grade
        float weightedGPA;
        weightedGPA = semesterHours * gpa;

        // now add in the new course
        semesterHours += hours;
        weightedGPA += grade * hours;
        gpa = weightedGPA / semesterHours;
// return the new gpa
        return gpa;
    }

    int  semesterHours;
    float gpa;
};

The function addCourse(int, float) is called a member function of the class Student. In principle, it's a property of the class like the data members semesterHours and gpa.

Sometimes functions that are not members of a class are class "plain ol' functions," but I'll refer to them simply as nonmembers.

Tip

The member functions do not have to precede the data members as in this example. The members of a class can be listed in any order — I just prefer to put the functions first.

Note

For historical reasons, member functions are also called methods. This term originated in one of the original object-oriented languages. The name made sense there, but it makes no sense in C++. Nevertheless, the term has gained popularity in OO circles because it's easier to say than "member function." (The fact that it sounds more impressive probably doesn't hurt, either.) So, if your friends start spouting off at a dinner party about "methods of the class," just replace methods with member functions and reparse anything they say.

Calling a Member Function

The following CallMemberFunction program shows how to invoke the member function addCourse():

//
//  CallMemberFunction - define and invoke a function
//               that's a member of the class Student
//
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

class Student
{
  public:
    // add a completed course to the record
    float addCourse(int hours, float grade)
    {
// calculate the sum of all courses times
        // the average grade
        float weightedGPA;
        weightedGPA = semesterHours * gpa;

        // now add in the new course
        semesterHours += hours;
        weightedGPA += grade * hours;
        gpa = weightedGPA / semesterHours;

        // return the new gpa
        return gpa;
    }

    int  semesterHours;
    float gpa;
};

int main(int nNumberofArgs, char* pszArgs[])
{
     // create a Student object and initialize it
     Student s;
     s.semesterHours = 3;
     s.gpa = 3.0;

     // the values before the call
     cout << "Before: s = (" << s.semesterHours
          << ", "     << s. gpa
          << ")" << endl;

    // the following subjects the data members of the s
    // object to the member function addCourse()
    cout << "Adding 3 hours with a grade of 4.0" << endl;
    s.addCourse(3, 4.0); // call the member function

    // the values are now changed
    cout << "After: s = (" << s.semesterHours
         << ", "     << s. gpa
         << ")"      << endl;

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    system("PAUSE");
    return 0;
}

The syntax for calling a member function looks like a cross between the syntax for accessing a data member and that used for calling a function. The right side of the dot looks like a conventional function call, but an object is on the left of the dot.

In the call s.addCourse(), we say that "addCourse() operates on the object s" or, said another way, "s is the student to which the course is to be added." You can't fetch the number of semester hours without knowing from which student to fetch those hours — you can't add a student to a course without knowing which student to add. Calling a member function without an object makes no more sense than referencing a data member without an object.

Accessing other members from a member function

I can see it clearly: You repeat to yourself, "Accessing a member without an object makes no sense. Accessing a member without an object. Accessing ..." Just about the time you've accepted this, you look at the member function Student::addCourse() and Wham! It hits you: addCourse() accesses other class members without reference to an object. Just like the TV show: "How Do They Do That?"

Okay, which is it, can you or can't you? Believe me, you can't. When you reference a member of Student from addCourse(), that reference is against the Student object with which the call to addCourse() was made. Huh? Go back to the CallMemberFunction example. A stripped-down version appears here:

int main(int nNumberofArgs, char* pszArgs[])
{
    Student s;
    s.semesterHours = 10;
    s.gpa      = 3.0;
    s.addCourse(3, 4.0); // call the member function

    Student t;
    t.semesterHours = 6;
    t.gpa      = 1.0;    // not doing so good
    t.addCourse(3, 1.5); // things aren't getting
                         // much better

    system("PAUSE");
    return 0;
}

When addCourse() is invoked with the object s, all of the otherwise unqualified member references in addCourse() refer to s as well. Thus, the reference to semesterHours in addCourse() refers to s.semesterHours, and gpa refers to s.gpa. But when addCourse() is invoked with the Student t object, these same references are to t.semesterHours and t.gpa instead.

Note

Note

The object with which the member function was invoked is the "current" object, and all unqualified references to class members refer to this object. Put another way, unqualified references to class members made from a member function are always against the current object.

Scope Resolution (And I Don't Mean How Well Your Microscope Works)

The :: between a member and its class name is called the scope resolution operator because it indicates the class to which a member belongs. The class name before the colon is like the family last name, while the function name after the colons is like the first name — the order is similar to a Chinese name, family name first.

You use the :: operator to describe a nonmember function by using a null class name. The nonmember function addCourse, for example, can be referred to as ::addCourse(int, float), if you prefer. This is like a function without a home.

Normally the :: operator is optional, but there are a few occasions when this is not so, as illustrated here:

// addCourse - combine the hours and grade into
//             a weighted grade
float addCourse(int hours, float grade)
{
    return hours * grade;
}

class Student
{
  public:
    // add a completed course to the record
    float addCourse(int hours, float grade)
    {
        // call some external function to calculate the
        // weighted grade
        float weightedGPA= ::addCourse(semesterHours,gpa);

        // now add in the new course
        semesterHours += hours;

        // use the same function to calculate the weighted
        // grade of this new course
        weightedGPA += ::addCourse(hours, grade);
        gpa = weightedGPA / semesterHours;

        // return the new gpa
        return gpa;
     }

     int  semesterHours;
     float gpa;

};

Here, I want the member function Student::addCourse() to call the nonmember function ::addCourse(). Without the :: operator, however, a call to addCourse() from Student refers to Student::addCourse(). This would result in the function calling itself.

Defining a Member Function in the Class

A member function can be defined either in the class or separately. When defined in the class definition, the function looks like the following, which is contained in the include file Savings.h:

// Savings - define a class that includes the ability
//           to make a deposit
class Savings
{
  public:
    // define a member function deposit()
    float deposit(float amount)
    {
        balance += amount;
        return balance;
    }

    unsigned int accountNumber;
    float  balance;
};

Note

Using an include like this is pretty slick. Now a program can include the class definition (along with the definition for the member function), as follows in the venerable SavingsClass_inline program:

//
//  SavingsClassInline - invoke a member function that's
//                       both declared and defined within
//                       the class Student
//
#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;
#include "Savings.h"

int main(int nNumberofArgs, char* pszArgs[])
{
    Savings s;
    s.accountNumber = 123456;
    s.balance = 0.0;

    // now add something to the account
    cout << "Depositing 10 to account "
         << s.accountNumber << endl;
    s.deposit(10);
    cout << "Balance is " << s.balance << endl;

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    system("PAUSE");
    return 0;
}

This is cool because everyone other than the programmer of the Savings class can concentrate on the act of performing a deposit rather the details of banking. These details are neatly tucked away in their own include files.

Note

The #include directive inserts the contents of the file during the compilation process. The C++ compiler actually "sees" your source file with the contents of the Savings.h file included. See Chapter 10 for details on include files.

Keeping a Member Function After Class

For larger functions, putting the code directly in the class definition can lead to some large, unwieldy class definitions. To prevent this, C++ lets you define member functions outside the class.

Tip

A function that is defined outside the class is said to be an outline function. This term is meant to be the opposite of an inline function that has been defined within the class. Your basic functions such as those we have defined since Chapter 5 are also outline functions.

When written outside the class declaration, the Savings.h file declares the deposit() function without defining it as follows:

// Savings - define a class that includes the ability
//           to make a deposit
class Savings
{
  public:
    // declare but don't
    float deposit(float amount);
    unsigned int accountNumber;
    float  balance;
};

The definition of the deposit() function must be included in one of the source files that make up the program. For simplicity, I defined it within main.cpp.

Tip

You would not normally combine the member function definition with the rest of your program. It is more convenient to collect the outlined member function definitions into a source file with an appropriate name (such as Savings.cpp). This source file is combined with other source files as part of building the executable program. I describe this in Chapter 21.

//
//  SavingsClassOutline - invoke a member function that's
//                        declared within a class but
//                        defined in a separate file
//
#include <cstdio>
#include <cstdlib>
#include <iostream>

using namespace std;
#include "Savings.h"

// define the member function Savings::deposit()
// (normally this is contained in a separate file that is
// then combined with a different file that is combined)
float Savings::deposit(float amount)
{
    balance += amount;
    return balance;
}
// the main program
int main(int nNumberofArgs, char* pszArgs[])
{
    Savings s;
    s.accountNumber = 123456;
    s.balance = 0.0;

    // now add something to the account
    cout << "Depositing 10 to account "
         << s.accountNumber << endl;
    s.deposit(10);
    cout << "Balance is " << s.balance << endl;

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    system("PAUSE");
    return 0;
}

This class definition contains nothing more than a prototype declaration for the function deposit(). The function definition appears separately. The member function prototype declaration in the structure is analogous to any other prototype declaration and, like all prototype declarations, is required.

Notice how the function nickname deposit() was good enough when the function was defined within the class. When defined outside the class, however, the function requires its extended name, Savings::deposit().

Overloading Member Functions

Member functions can be overloaded in the same way that conventional functions are overloaded. (See Chapter 6 if you don't remember what that means.) Remember, however, that the class name is part of the extended name. Thus, the following functions are all legal:

class Student
{
  public:
    // grade -- return the current grade point average
    float grade();
    // grade -- set the grade and return previous value
    float grade(float newGPA);
    // ...data members and other stuff...
};
class Slope
{
  public:
    // grade -- return the percentage grade of the slope
    float grade();
    // ...stuff goes here too...
};

// grade - return the letter equivalent of a number grade
char grade(float value);

int main(int argcs, char* pArgs[])
{
    Student s;
    s.grade(3.5);        // Student::grade(float)
    float v = s.grade(); // Student::grade()

    char c = grade(v);   // ::grade(float)

    Slope o;
    float m = o.grade(); // Slope::grade()
    return 0;
}

Each call made from main() is noted in the comments with the extended name of the function called.

When calling overloaded functions, not only the arguments of the function but also the type of the object (if any) with which the function is invoked are used to resolve the call. (The term resolve is object-oriented talk for "decide at compile time which overloaded function to call." A mere mortal might say "differentiate.")

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.27.171