Section 1 - Methods Defined in Classes
6B.1.1 Instance Methods.
The classes we have created so far have contained only data. Classes would not be very useful if they did not also encapsulate the behavior of that data in the form of methods. To that end, classes are allowed to declare their own member methods. Classes can have many different kinds of member methods. Some might be meant to be called from outside our class (i.e., from methods of other classes). Some might be intended only for use inside our class (i.e., invoked from other methods in the same class). However, all the methods of a given class should be somehow related to the data of that class.
All the methods we will describe in the early part of these lessons will be called instance methods. None of these methods will have the static modifier keyword in their heading; that's what makes these methods instance methods. If a method does have the keyword static in front of its definition, it will not be termed an instance method, but instead will be called a static class method. I realize this doesn't mean much to you yet, but we need the terminology, just the same. For now, we will not consider static class methods, only instance methods.
The first kinds of instance methods we will consider are accessor and mutator methods. These are methods that our client (any part of our program outside the class) uses to manipulate our class's member data.
6B.1.2 Where Class Methods are Defined
The definition of class methods are typically, and appropriately, defined separately from the class prototype. By that I mean that classes are defined in two pieces. First, the class prototype which has only the member data and the instance method prototypes. For example:
class Galaxy { private: string name; double magnitude; public: // default constructor Galaxy(); // mutators and accessors bool setMagnitude(double mag); bool setName(string theName); string getName(); double getMagnitude(); };
You will note that the data is declared private, and the instance methods are in the public section. But the instance methods are not full method definitions. They are only prototypes of the methods (function headers terminated by a semicolon). This is collectively called the class prototype, and goes near the top of your file, before main().
Then, further down, often after main()'s definition, we would have the definition of the various instance methods. The definitions would look something like this:
// -------- Galaxy Member Functions ------------ // default constructor Galaxy::Galaxy() { // code deleted for now } // mutators "set" methods bool Galaxy::setMagnitude(double mag) { // code deleted for now } // etc ...
The syntax is a little odd because of the Galaxy:: tagged on to the method names. But if you think about it you will realize that something like this is needed. In our class prototype, we advertised the existence of these methods, but we never defined them. So we have to do so somewhere. By delaying their definitions until much later, we need a way to associate the method definitions with the class in which they appear. By gluing the class name Galaxy:: on to the method definition, this saves the day. There may be many classes in our program and many setMagnitude() methods, but this method definition is for the Galaxy class because its name is Galaxy::setMagnitude().
6B.1.3 Inline Method Definitions
In other languages, you must define your class methods directly inside the class prototype, that is between the braces where the class first appears. This is called in-lining because you are defining your methods in-line with the class. This is bad, especially in C++. It makes it hard to see what the class has in it. It is better to provide only method prototypes in your class prototype and defer the definition of the methods until later, often in a separate file.
If you inline your class methods in your homework, you will lose points. Lots of them. We don't want that to happen, so all you have to do to avoid it is write your programs and classes exactly as you see me do my examples and you will be safe. (This is to help you become a real C++ programmer).
6B.1.4 Defining Accessor and Mutator Methods
The two class examples we have seen so far were fun, but dangerous. By this I mean that the client (the main() method or whoever was using the class to declare objects) could access -- that is "get at" -- the member data of the class by simply dereferencing any field using the object.
garfield.petsName = "odie";
This was because we declared the member data using the public modifier, rather than declaring it using the private keyword (or no keyword at all, which is the same as private). That's bad. You have to think of it this way. You are designing your class, and another member of your team is writing the main() method which uses your class. This other person might not understand the issues involved in your various member data. Maybe you don't want to allow weights greater than 250 pounds because this program is for a petting zoo, and we don't want large animals sitting on the children. Or, more fundamentally, we might want all pet names to be at least three characters long (and less than 30).
Currently there is no protection. If you wrote that class and gave it to your colleague to use you would deserve every abuse he or she heaped on it because you took no protective measures. I know you don't really want that to be the case, which is why we are now going to do two things:
- Declare all of our member data to be private.
- Supply accessor/mutator methods in our class that can be used to access or modify the data
Why will supplying accessor/mutator methods help? Just watch. Here are the definitions of the Galaxy class methods (the class prototype was shown above). Unlike the Pet class it not only has member data, but also member methods. This is the first time we have seen a method defined inside one of our new classes. The following definitions for the member methods could appear directly below the prototype, or miles away, after main():
// -------- Galaxy Member Functions ------------ // default constructor Galaxy::Galaxy() { name = "undefined"; magnitude = 0.0; } // mutators "set" methods bool Galaxy::setMagnitude(double mag) { if (mag < -3 || mag > 30) return false; // else magnitude = mag; return true; } bool Galaxy::setName(string theName) { if (theName.length() < 2) return false; // else we fall through name = theName; return true; } // accessor "get" methods string Galaxy::getName() { return name; } double Galaxy::getMagnitude() { return magnitude; }
We usually call the set() methods mutator methods, and the get() methods accessor methods. However, I often use the single term accessor for both types of methods.

6B.1.5 Invoking Accessor/Mutator Methods
We now have a class called Galaxy which will be used by our local astronomer-programmers to do a variety of things from controlling a remote telescope to archiving newly discovered galaxies. Currently there are only two data in the Galaxy, the name and the magnitude. Magnitudes are numbers that represent the apparent brightness of the galaxy and typically have values in the range 5 to 15 (but for our purposes will always lie between -3 and 30). So we want to protect the class data from getting incorrect values. Likewise, we want to protect against invalid names being assigned to the private name member.
That's where the methods setName() and setMagnitude() come in. Notice how they take formal parameters from some unknown client and assume that the client is sloppy. They will not set the private data until after the values passed in to these methods have been checked for "reasonableness."
We could place this class in our source (that is, our .cpp) file and start using it. Here is how a client main() might use this Galaxy class:
// main method --------------------------------- int main() { // declare the objects Galaxy gal1, gal2; // try to set the data gal1.setName("X"); gal1.setMagnitude(100); gal2.setName("Stephan's Third"); gal2.setMagnitude(13.2); // let's see what happened cout << "Gal #1 name: " << gal1.getName() << endl; cout << "Gal #1 mag: " << gal1.getMagnitude() << endl; cout << "Gal #2 name: " << gal2.getName()<< endl; cout << "Gal #2 mag: " << gal2.getMagnitude()<< endl; return 0; }
There are two things we need to do at once.
- Understand the mechanics of defining and using class methods.
- Understand how these methods protect the member data of the class.
We can gain this understanding by answering the following questions:
- Why did I call one of the methods a constructor, and why does it have the same name as the name of the class?
- Why do the methods get to use the member data without dereferencing them through objects?
- Why are some methods referred to as accessor methods, and why do they seem to be in pairs, setNNN() and getNNN()?
- What exactly is the meaning of private and public?
Next we answer these questions.
6B.1.6 The Full Galaxy Class and Client/main() Program
Before we go on to answer these important questions, let's show the full file that contains everything, including a second constructor that we have not yet discussed (but will soon). Notice how the program is organized: class prototype comes first, then the main() (client) method, then the class method definitions and, finally, a sample run. Your assignments should be in the same format. However, don't forget that we have a lot to learn about classes yet, so this example will not contain all the essential ingredients. We'll cover the rest soon. Meanwhile, here is what we have so far. It works and it is in the correct form:
#include<iostream> #include <string> using namespace std; // ---------------- the class prototype --------------------------------- class Galaxy { private: string name; double magnitude; public: // default constructor Galaxy(); // 2-parameter constructor (to be discussed) Galaxy(string myName, double myMag); // mutators and accessors bool setMagnitude(double mag); bool setName(string theName); string getName(); double getMagnitude(); }; // ------------------ the main method --------------------------------- int main() { // declare the objects Galaxy gal1, gal2; // try to set the data gal1.setName("X"); gal1.setMagnitude(100); gal2.setName("Stephan's Third"); gal2.setMagnitude(13.2); // let's see what happened cout << "Gal #1 name: " << gal1.getName() << endl; cout << "Gal #1 mag: " << gal1.getMagnitude() << endl; cout << "Gal #2 name: " << gal2.getName()<< endl; cout << "Gal #2 mag: " << gal2.getMagnitude()<< endl; return 0; } // ------------ Galaxy member functions definitions ------------ // default constructor Galaxy::Galaxy() { name = "undefined"; magnitude = 0.0; } // 2-parameter constructor (to be discussed) Galaxy::Galaxy(string myName, double myMag) { if (myName.length() > 1) name = myName; else name = "undefined"; if (myMag >= -3 && myMag <= 30) magnitude = myMag; else magnitude = 0.0; } // mutators "set" methods bool Galaxy::setMagnitude(double mag) { if (mag < -3 || mag > 30) return false; // else magnitude = mag; return true; } bool Galaxy::setName(string theName) { if (theName.length() < 2) return false; // else we fall through name = theName; return true; } // accessor "get" methods string Galaxy::getName() { return name; } double Galaxy::getMagnitude() { return magnitude; } /* ------------------ Paste of Run from Above Program ---------- Gal #1 name: undefined Gal #1 mag: 0 Gal #2 name: Stephan's Third Gal #2 mag: 13.2 Press any key to continue . . . ---------------------------------------------------------------- */