Adding Class to C++ - Introduction to Classes - C++ For Dummies (2014)

C++ For Dummies (2014)

Part III

Introduction to Classes

Chapter 12

Adding Class to C++

In This Chapter

arrow Grouping data into classes

arrow Declaring and defining class members

arrow Adding active properties to the class

arrow Accessing class member functions

arrow 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 standalone 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 offense). 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 multiple 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.)

image 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 classSavingsAccount.

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 SavingsAccount.

image 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.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.0; // illegal; no object
SavingsAccount.balance = 0.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 in Chapter 18 — 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 accounts 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 account. But real-world accounts do things. 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. The class Savings needs a function accumulateInterest(), and the class CD a function to penalizeForEarlyWithdrawal().

image 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?

image I'm using the term “functional programming” synonymously with “procedural programming”, the way programming was done before object-oriented programming came along.

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
double addCourse(int hours, double grade)
{
// calculate the sum of all courses times
// the average grade
double 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;
double gpa;
};

The function addCourse(int, double) 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.

image 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.

image 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
double addCourse(int hours, double grade)
{
// calculate the sum of all courses times
// the average grade
double 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;
double 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
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
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 makes no sense. 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. So how do they do that?


image Naming the current object

How does the member function know what the current object is? It’s not magic — the address of the object is passed to the member function as an implicit and hidden first argument. In other words, the following conversion is taking place:

s.addCourse(3, 2.5)

is like

Student::addCourse(&s, 3, 2.5)

(Note that you can’t actually use the explicit syntax; this is just the way C++ sees it.)

Inside the function, this implicit pointer to the current object has a name, in case you need to refer to it. It is called this, as in “Which object? This object.” Get it? The type of this is always a pointer to an object of the appropriate class.

Anytime a member function refers to another member of the same class without providing an object explicitly, C++ assumes that the programmer meant this. You also can refer to this explicitly, if you like. I could have written Student::addCourse() as follows:

double Student::addCourse(int hours, double grade)
{
double weightedGPA;
weightedGPA = this->semesterHours * this->gpa;

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

The effect is the same whether you explicitly include this, as in the preceding example, or leave it implicit, as you did before.


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

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 theStudent t object, these same references are to t.semesterHours and t.gpa instead.

image 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 Telescope 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 colons 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 non-member function by using a null class name. The non-member function addCourse, for example, can be referred to as ::addCourse(int, double), 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
double addCourse(int hours, double grade)
{
return hours * grade;
}

class Student
{
public:
// add a completed course to the record
double addCourse(int hours, double grade)
{
// call some external function to calculate the
// weighted grade
double 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;
double gpa;
};

Here, I want the member function Student::addCourse() to call the non-member 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()
double deposit(double amount)
{
balance += amount;
return balance;
}

unsigned int accountNumber;
double balance;
};

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
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
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 than the details of banking. These details are neatly tucked away in their own include files.

image 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.


image Inlining member functions

Member functions defined in the class default to inline (unless they have been specifically outlined by a compiler switch or for any number of very technical reasons). Mostly, this is because a member function defined in the class is usually very small, and small functions are prime candidates for inlining.

Remember that an inline function is expanded where it is invoked. (See Chapter 10 for a comparison of inline functions and macros.) An inline function executes faster because the processor doesn’t have to jump over to where the function is defined — inline functions usually take up more memory because they are copied into every call instead of being defined just once.

There is another good but more technical reason to inline member functions defined within a class. Remember that C++ structures are normally defined in include files, which are then included in the .CPP source files that need them. Such include files should not contain data or functions because these files are compiled multiple times. Including an inline function is okay, however, because it (like a macro) expands in place in the source file. The same applies to C++ classes. By defaulting member functions defined in classes inline, you avoid the preceding problem.


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.

image 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 define member function
double deposit(double amount);
unsigned int accountNumber;
double 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.

image 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)
double Savings::deposit(double 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
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
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
double grade();
// grade -- set the grade and return previous value
double grade(double newGPA);
// ...data members and other stuff...
};
class Slope
{
public:
// grade -- return the percentage grade of the slope
double grade();
// ...stuff goes here too...
};

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

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

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

Slope o;
double 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.”)