Chapter 22

Structured Play: Making Classes Do Things

In This Chapter

arrow Adding member functions to a class

arrow Defining the member function

arrow Invoking the member function

arrow Accessing one member from another member

arrow Overloading member functions

Classes were introduced to the C language as a convenient way to group unalike-but-related data elements — for example, the Social Security number and name of the same person. (That’s the way I introduce them in Chapter 19.) C++ expanded the concept of classes to give them the ability to mimic objects in the real world. That’s the essence of the difference between C and C++.

In the previous chapter, I review at a high level the concept of object-oriented programming. In this chapter, I make it more concrete by examining the active features of a class that allow it to better mimic the object-oriented world we live in.

Activating Our Objects

C++ uses classes to simulate real-world objects. However, the classes in Chapter 19 are lacking in that regard because classes do things. (The classes in Chapter 19 don’t have any verbs associated with them — they don’t do anything.) Consider for example, a savings account. It is necessary for a Savings class to save the owner’s name, probably her Social Security number, certainly her account number and balance. But this isn’t sufficient. Objects in the real world do things. Ovens cook. Savings accounts accumulate interest. CDs charge a substantial penalty for early withdrawal. Stuff like that.

Consider the problem of handling deposits in a Savings account class. Procedural programs do things via functions. Thus, a procedural program might create a separate function that takes as its argument a pointer to a Savings account object that it wants to update followed by the amount to deposit.

technicalstuff.eps Never mind (for now) exactly how to pass a pointer to a Savings account object. You get to see more about that in the next chapter.

But that’s not the way that savings accounts work in the real world. When I drive up to the bank window and tell them I want to make a deposit to my savings account, the teller doesn’t hand me a ledger into which I note the deposit and write the new balance. She doesn’t do it herself either. Instead, she types in the amount of the deposit at some terminal and then places that amount in the till. The machine spits out a deposit slip with the new balance on it that she hands me, and it’s all done. Neither of us touches the bank’s books directly.

This may seem like a silly exercise but consider why the bank doesn’t do things “the procedural way.” Ignore for a minute the temptation I might have to add a few extra zeros to the end of my deposit before adding it up. The bank doesn’t do things this way for the same reason that I don’t energize my microwave oven by connecting and disconnecting wires inside the box — the bank wants to maintain tight controls on what happens to its balances.

This care extends to programmers as well. You can rest easy at night knowing that not every programmer gets direct access to bank balances either. Only the most trusted of programmers get to write the code that increments and decrements bank balances.

To make the Savings class mimic a real-world savings account, it needs active properties of its own, such as deposit() and withdrawal() (and chargePenalty() for who-knows-why, in my case). Only in this way can a Savings class be held responsible for its state.

Creating a Member Function

A function that is part of a class definition is known as a member function. The data within the class is known as data members. Member functions are the verbs of the class; data members are the nouns.

technicalstuff.eps Member functions are also known as methods because that’s what they were called in the original object-oriented language, Smalltalk. The term methods had meaning to Smalltalk, but it has no special meaning in C++, except that it’s easier to say and sounds more impressive in a conversation. I’ll try not to bore you with this trivia, but you will hear the term method bandied about at object-oriented parties, so you might as well get used to it. I’ll try to stick with the term member functions, but even I slip into technical jargon from time to time.

Note: Functions that you’ve seen so far that are not members of a class don’t have a special name. I refer to them as non-member functions when I need to differentiate them from their member cousins.

There are three aspects to adding a member function to a class: defining the function, naming the function, and calling the function. Sounds pretty obvious when you say it that way.

Defining a member function

The following class demonstrates how to define two key member functions, deposit() and withdraw(), in a class Savings account:

  // Savings - a simple savings account class
class Savings
{
  public:
    int    nAccountNumber;
    double dBalance;

    // deposit - deposit an amount to the balance;
    //           deposits must be positive number; return
    //           the resulting balance or zero on error
    double deposit(double dAmount)
    {
        // no negative deposits - that's a withdrawal
        if (dAmount < 0)
        {
            return 0.0;
        }

        // okay - add to the balance and return the total
        dBalance += dAmount;
        return dBalance;
    }

    // withdraw - execute a withdrawal if sufficient funds
    //            are available
    double withdraw(double dAmount)
    {
        if (dBalance < dAmount)
        {
            return 0.0;
        }

        dBalance -= dAmount;
        return dBalance;
    }
};

remember.eps A real savings-account class would have a lot of other information such as the customer’s name. Adding that extra stuff doesn’t help explain the concepts, however, so I’ve left it off to keep the listings as short as possible.

You can see that the definition of the deposit() and withdraw() member functions look just like those of any other function, except they appear within the definition of the class itself. There are some other subtle differences that I address later in this chapter.

technicalstuff.eps It’s possible to define a member function outside the class, as you see a little later in this chapter.

Naming class members

A member function is a lot like a member of a family. The full name of the deposit function is Savings::deposit(double) just as my name is Stephen Davis. My mother doesn’t call me that unless I’m in trouble. Normally, members of my family just call me by my first name, Stephen. Similarly, from within the Savings class, the deposit function is known simply as deposit(double).

The class name at the beginning indicates that this is a reference to the deposit() function that is a member of the class Savings. The :: is simply a separator between the class name and the member name. The name of the class is part of the extended name of the member function, in the same way that like Stephen Davis is my extended name. (See Chapter 11 if you need a refresher on extended names.)

tip.eps Classes are normally named using nouns that describe concepts such as Savings or SavingsAccount. Member functions are normally named with associated verbs like deposit() or withdraw(). Other than that, member functions follow the same naming convention as other functions. Data members are normally named using nouns that describe specific properties such as szName or nSocialSecurityNumber.

You can define a different deposit() function that has nothing to do with the Savings class — there are Stephens out there who have nothing to do with my family. (I mean this literally: I know several Stephens who want nothing to do with my family.) For example, Checking::deposit(double) or River::deposit() are easily distinguishable from Savings::deposit(double).

technicalstuff.eps A non-member function can appear with a null class name. For example, if there were a deposit function that was not a member of any class, its extended name would be ::deposit().

Calling a member function

Before I show you how to invoke a member function, let me quickly refresh you on how to access a data member of an object. Given the earlier definition of the Savings class, you could write the following:

  void fn()
{
    Savings s;

    s.nAccountNumber = 0;
    s.dBalance = 0.0;
}

The function fn() creates a Savings object s and then zeros the data members nAccountNumber and dBalance of that object.

Notice that the following does not make sense:

  void fn()
{
    Savings s1, s2;

    nAccountNumber = 0;  // doesn't work
    dBalance = 0.0;
}

Which nAccountNumber and dBalance are you talking about? The account number and balance of s1 or s2. Or some other object entirely? A reference to a data member makes sense only in the context of an object.

Invoking a member function is the same. You must first create an object and then you can invoke the member function on that object:

  void fn()
{
    // create and initialize an object s
    Savings s = {0, 0.0};

    // now make a deposit of $100
    s.deposit(100.0);

    // or a withdrawal
    s.withdraw(50.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 functions. The right side of the dot looks like a conventional function call, but an object appears on the left side of the dot.

This syntax makes sense when you think about it. In the call s.deposit(), s is the savings object to which the deposit() is to be made. You can’t make a deposit without knowing to which account. Calling a member function without an object makes no more sense than referencing a data member without an object.

Accessing other members from within a member function

I can see it now: You repeat to yourself, “You can’t access a member without reference to an object. You can’t access a member without reference to an object. You can’t… .” And then, wham, it hits you. Savings::deposit() appears to do exactly that:

  double deposit(double dAmount)
{
    // no negative deposits - that's a withdrawal
    if (dAmount < 0)
    {
        return 0.0;
    }

    // okay - add to the balance and return the total
    dBalance += dAmount;
    return dBalance;
}

The Savings::deposit() function references dBalance without an explicit reference to any object. It’s like that TV show: “How Do They Do It?”

So, okay, which is it? Can you or can you not reference a member without an object? Believe me, the answer is no, you cannot. When you reference one member from within another member of the same class without explicitly referring to an object, the reference is implicitly against the “current object.”

What is the current object? Go back and look at the example in greater detail. I am pulling out just the key elements of the example here for brevity’s sake:

  class Savings
{
  public:
    int    nAccountNumber;
    double dBalance;

    double deposit(double dAmount)
    {
        dBalance += dAmount;
        return dBalance;
    }
};

void fn()
{
    // create and initialize two objects
    Savings s1 = {0, 0.0};
    Savings s2 = {1, 0.0};

    // now make a deposit of $100 to one account
    s1.deposit(100.0);

    // and then the other
    s2.deposit(50.0);
}

When deposit() is invoked with s1, the unqualified reference to dBalance refers to s1.dBalance. At that moment in time, s1 is the “current object.” During the call to s2.deposit(50.0), s2 becomes the current object. During this call, the unqualified reference to dBalance refers to s2.dBalance.

technicalstuff.eps The “current object” has a name. It’s called this as in “this object.” Clever, no? Its type is “pointer to an object of the current class.” I say more about this in Chapter 23 when I talk about pointers to objects.

Keeping a Member Function after Class

One of the things I don’t like about C++ is that it provides multiple ways of doing most things. In keeping with that penchant for flexibility, C++ allows you to define member functions outside the class as long as they’re declared within the class.

The following is an example of the withdraw() function written outside the class declaration (once again, I’ve left out the error checking to make the example as short as possible):

  // this part normally goes in the Savings.h include file
class Savings
{
  public:
    int    nAccountNumber;
    double dBalance;

    double deposit(double dAmount);
};

// this part appears in a separate Savings.cpp file
double Savings::deposit(double dAmount)
{
    dBalance += dAmount;
    return dBalance;
}

Now the definition of Savings contains nothing more than the prototype declaration of the member function deposit(). The actual definition of the function appears later. Notice, however, that when it does appear, it appears with its full extended name, including the class name — there is no default class name outside of the class definition.

This form is ideal for larger member functions. In these cases, the number of lines of code within the member functions can get so large that it obscures the definition of the class itself. In addition, this form is useful when defining classes in their own C++ source modules. The definition of the class can appear in an include file, Savings.h, while the definition of the function appears in a separately compiled Savings.cpp.

Overloading Member Functions

You can overload member functions just like you overload any other functions. Remember, however, that the class name is part of the extended name. That means that the following is completely legal:

  class Student
{
  public:
    double grade();  // return Student's grade
    double grade(double dNewGPA); // set Student's grade
};

class Hill
{
  public:
    double grade(double dSlope); // set the slope
};
    void grade(double);

void fn()
{
    Student s;
    Hill h;

    // set the student's grade
    s.grade(3.0);

    // now query the grade
    double dGPA = s.grade();

    // now grade a hill to 3 degrees slope
    h.grade(3.0);

    // call the non-member function
    grade(3.0);
}

When calling a member function, the type of the object is just as important as the number and type of the arguments. The first call to grade() invokes the function Student::grade(double) to set the student’s grade-point average. The second call is to Student::grade(), which returns the student’s grade-point average without changing it.

The third call is to a completely unrelated function, Hill::grade(double), that sets the slope on the side of the hill. And the final call is to the non-member function ::grade(double).

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

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