Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 8

This chapter shows several different versions of the SpreadsheetCell class in order to introduce concepts gradually. Thus, the various attempts at the class throughout the chapter do not always illustrate the “best” way to do every aspect of class writing. In particular, the early examples omit important features that would normally be included, but have not yet been introduced. You can download the final version of the class as described in the Introduction.

Writing Classes

When you write a class you specify the behaviors, or methods, that will apply to objects of that class and the properties, or data members, that each object will contain.

There are two elements to writing classes: defining the classes themselves and defining their methods.

Class Definitions

Here is a first attempt at a simple SpreadsheetCell class, in which each cell can store only a single number:

// SpreadsheetCell.h class SpreadsheetCell

{

public:

void setValue(double inValue);

double getValue();

protected:

double mValue;

};

As described in Chapter 1, every class definition begins with the keyword class and the name of the class. A class definition is a statement in C++, so it must end with a semicolon. If you fail to terminate your class definition with a semicolon, your compiler will probably give you several errors, most of which will appear to be completely unrelated.

Class definitions usually go in a file with the name ClassName.h.

Methods and Members

The two lines that look like function prototypes declare the methods that this class supports:

void setValue(double inValue); double getValue();

The line that looks like a variable declaration declares the data member for this class:

double mValue;

Each object will contain its own mValue variable. However, the implementation of the methods is shared across all objects. Classes can contain any number of methods and members. You cannot give a member the same name as a method.

158

Gaining Proficiency with Classes and Objects

Access Control

Every method and member in a class is subject to one of three access specifiers: public, protected, or private. An access specifier applies to all method and member declarations that follow it, until the next access specifier. In the SpreadsheetCell class, the setValue() and getValue() methods have public access, while the mValue member has protected access:

public:

void setValue(double inValue); double getValue();

protected:

double mValue;

The default access specifier for classes is private: all method and member declarations before the first access specifier have the private access specification. For example, moving the public access specifier below the setValue() method declaration gives setValue() private access instead of public:

class SpreadsheetCell

{

void setValue(double inValue); // now has private access

public:

double getValue();

protected:

double mValue;

};

In C++, structs can have methods just like classes. In fact, the only difference between a struct and a class is that the default access specifier for a struct is public and the default for a class is private.

The following table summarizes the meanings of the three access specifiers:

Access Specification

Meaning

When to Use

public

Any code can call a public method or

Behaviors (methods)

 

access a public member of an object.

that you want clients to use.

 

 

Access methods for private

 

 

and protected data members.

protected

Any method of the class can call a

 

protected method and access a

 

protected member.

 

Methods of a subclass (see Chapter 10)

 

can call a protected method or access

 

a protected member of an object.

“Helper” methods that you do not want clients to use.

Most data members.

Table continued on following page

159

Chapter 8

Access Specification Meaning

When to Use

private

Only methods of the class can call

 

a private method and access a

 

private member.

 

Methods in subclasses cannot access

 

private methods or members.

Only if you want to restrict access from subclasses.

Access specifiers are at the class level, not the object level, so methods of a class can access protected or private methods and members on any object of that class.

Order of Declarations

You can declare your methods, members, and access control specifiers in any order: C++ does not impose any restrictions such as methods before members or public before private. Additionally, you can repeat access specifiers. For example, the SpreadsheetCell definition could look like this:

class SpreadsheetCell

{

public:

void setValue(double inValue);

protected:

double mValue;

public:

double getValue();

};

However, for clarity it is a good idea to group public, protected, and private declarations, and to group methods and members within those declarations. In this book, we order the definitions and access specifiers in our classes as follows:

class ClassName

{

public:

//Method declarations

//Member declarations

protected:

//Method declarations

//Member declarations

private:

//Method declarations

//Member declarations

};

160

Gaining Proficiency with Classes and Objects

Defining Methods

The preceding definition for the SpreadsheetCell class is enough for you to create objects of the class. However, if you try to call the setValue() or getValue() methods, your linker will complain that those methods are not defined. That’s because the class definition specifies the prototypes for the methods, but does not define their implementations. Just as you write both a prototype and a definition for a stand-alone function, you must write a prototype and a definition for a method. Note that the class definition must precede the method definitions. Usually the class definition goes in a header file, and the method definitions go in a source file that includes that header. Here are the definitions for the two methods of the SpreadsheetCell class:

// SpreadsheetCell.cpp #include “SpreadsheetCell.h”

void SpreadsheetCell::setValue(double inValue)

{

mValue = inValue;

}

double SpreadsheetCell::getValue()

{

return (mValue);

}

Note that the name of the class followed by two colons precedes each method name:

void SpreadsheetCell::setValue(double value)

The :: is called the scope resolution operator. In this context, the syntax tells the compiler that the coming definition of the setValue() method is part of the SpreadsheetCell class. Note also that you do not repeat the access specification when you define the method.

Accessing Data Members

Most methods of a class, such as setValue() and getValue(), are always executed on behalf of a specific object of that class (the exceptions are static methods, which are discussed below). Inside the method body, you have access to all the data members of the class for that object. In the previous definition for setValue(), the following line changes the mValue variable inside whatever object calls the method:

mValue = inValue;

If setValue() is called for two different objects, the same line of code (executed once for each object) changes the variable in two different objects.

Calling Other Methods

You can call methods of a class from inside another method. For example, consider an extension to the SpreadsheetCell class. Real spreadsheet applications allow text data as well as numbers in the cells. When you try to interpret a text cell as a number, the spreadsheet tries to convert the text to a number. If the text does not represent a valid number, the cell value is ignored. In this program, strings that are not

161

Chapter 8

numbers will generate a cell value of 0. Here is a first stab at a class definition for a SpreadsheetCell that supports text data:

#include <string>

using std::string;

class SpreadsheetCell

{

public:

void setValue(double inValue); double getValue();

void setString(string inString);

string getString();

protected:

string doubleToString(double inValue);

double stringToDouble(string inString);

double mValue; string mString;

};

This version of the class stores both text and numerical representations of the data. If the client sets the data as a string, it is converted to a double, and a double is converted to a string. If the text is not a valid number, the double value is 0. This class definition shows two new methods to set and retrieve the text representation of the cell and two new protected helper methods to convert a double to a string and vice versa. These helper methods use string streams, which are covered in detail in Chapter 14. Here are the implementations of all the methods:

#include “SpreadsheetCell.h”

#include <iostream> #include <sstream> using namespace std;

void SpreadsheetCell::setValue(double inValue)

{

mValue = inValue;

mString = doubleToString(mValue);

}

double SpreadsheetCell::getValue()

{

return (mValue);

}

void SpreadsheetCell::setString(string inString)

{

mString = inString;

mValue = stringToDouble(mString);

}

string SpreadsheetCell::getString()

{

162

Gaining Proficiency with Classes and Objects

return (mString);

}

string SpreadsheetCell::doubleToString(double inValue)

{

ostringstream ostr;

ostr << inValue; return (ostr.str());

}

double SpreadsheetCell::stringToDouble(string inString)

{

double temp;

istringstream istr(inString);

istr >> temp;

if (istr.fail() || !istr.eof()) { return (0);

}

return (temp);

}

Note that each of the set methods calls a helper method to perform a conversion. With this technique, both mValue and mString are always valid.

The this Pointer

Every normal method call passes a pointer to the object for which it is called as a “hidden” first parameter with the name this. You can use this pointer to access data members or call methods, and can pass it to other methods or functions. It is also sometimes useful for disambiguating names. For example, you could have defined the SpreadsheetCell class such that the setValue() method took a parameter named mValue instead of inValue. In that case, setValue() would look like this:

void SpreadsheetCell::setValue(double mValue)

{

mValue = mValue; // Ambiguous! mString = doubleToString(mValue);

}

That line is confusing. Which mValue do you mean: the mValue that was passed as a parameter, or the mValue that is a member of the object? In order to disambiguate the names you can use the this pointer:

void SpreadsheetCell::setValue(double mValue)

{

this->mValue = mValue;

mString = doubleToString(this->mValue);

}

However, if you use the naming conventions described in Chapter 7, you will never encounter this type of name collision.

163