Data Members, set Functions and get Functions
In Chapter 2, we declared all of a program's variables in its main function. Variables declared in a function definition's body are known as local variables and can be used only from the line of their declaration in the function to the immediately following closing right brace (}) of the function definition. A local variable must be declared before it can be used in a function. A local variable cannot be accessed outside the function in which it is declared. When a function terminates, the values of its local variables are lost. (You will see an exception to this in Chapter 6 when we discuss static local variables.) Recall from Section 3.2 that an object has attributes that are carried with it as it is used in a program. Such attributes exist throughout the life of the object.
A class normally consists of one or more member functions that manipulate the attributes that belong to a particular object of the class. Attributes are represented as variables in a class definition. Such variables are called data members and are declared inside a class definition but outside the bodies of the class's member-function definitions. Each object of a class maintains its own copy of its attributes in memory. The example in this section demonstrates a GradeBook class that contains a courseName data member to represent a particular GradeBook object's course name.
GradeBook Class with a Data Member, a set Function and a get Function
In our next example, class GradeBook (Fig. 3.5) maintains the course name as a data member so that it can be used or modified at any time during a program's execution. The class contains member functions setCourseName, getCourseName and displayMessage. Member function setCourseName stores a course name in a GradeBook data membermember function getCourseName obtains a GradeBook's course name from that data member. Member function displayMessagewhich now specifies no parametersstill displays a welcome message that includes the course name. However, as you will see, the function now obtains the course name by calling another function in the same classgetCourseName.
Figure 3.5. Defining and testing class GradeBook with a data member and set and get functions.
(This item is displayed on pages 85 - 86 in the print version)
1 // Fig. 3.5: fig03_05.cpp 2 // Define class GradeBook that contains a courseName data member 3 // and member functions to set and get its value; 4 // Create and manipulate a GradeBook object with these functions. 5 #include 6 using std::cout; 7 using std::cin; 8 using std::endl; 9 10 #include // program uses C++ standard string class 11 using std::string; 12 using std::getline; 13 14 // GradeBook class definition 15 class GradeBook 16 { 17 public: 18 // function that sets the course name 19 void setCourseName( string name ) 20 { 21 courseName = name; // store the course name in the object 22 } // end function setCourseName 23 24 // function that gets the course name 25 string getCourseName() 26 { 27 return courseName; // return the object's courseName 28 } // end function getCourseName 29 30 // function that displays a welcome message 31 void displayMessage() 32 { 33 // this statement calls getCourseName to get the 34 // name of the course this GradeBook represents 35 cout << "Welcome to the grade book for " << getCourseName() << "!" 36 << endl; 37 } // end function displayMessage 38 private: 39 string courseName; // course name for this GradeBook 40 }; // end class GradeBook 41 42 // function main begins program execution 43 int main() 44 { 45 string nameOfCourse; // string of characters to store the course name 46 GradeBook myGradeBook; // create a GradeBook object named myGradeBook 47 48 // display initial value of courseName 49 cout << "Initial course name is: " << myGradeBook.getCourseName() 50 << endl; 51 52 // prompt for, input and set course name 53 cout << " Please enter the course name:" << endl; 54 getline( cin, nameOfCourse ); // read a course name with blanks 55 myGradeBook.setCourseName( nameOfCourse ); // set the course name 56 57 cout << endl; // outputs a blank line 58 myGradeBook.displayMessage(); // display message with new course name 59 return 0; // indicate successful termination 60 } // end main
|
Good Programming Practice 3.3
Place a blank line between member-function definitions to enhance program readability. |
A typical instructor teaches more than one course, each with its own course name. Line 39 declares that courseName is a variable of type string. Because the variable is declared in the class definition (lines 1540) but outside the bodies of the class's member-function definitions (lines 1922, 2528 and 3137), line 39 is a declaration for a data member. Every instance (i.e., object) of class GradeBook contains one copy of each of the class's data members. For example, if there are two GradeBook objects, each object has its own copy of courseName (one per object), as we'll see in the example of Fig. 3.7. A benefit of making courseName a data member is that all the member functions of the class (in this case, GradeBook) can manipulate any data members that appear in the class definition (in this case, courseName).
Access Specifiers public and private
Most data member declarations appear after the access-specifier label private: (line 38). Like public, keyword private is an access specifier. Variables or functions declared after access specifier private (and before the next access specifier) are accessible only to member functions of the class for which they are declared. Thus, data member courseName can be used only in member functions setCourseName, getCourseName and displayMessage of (every object of) class GradeBook. Data member courseName, because it is private, cannot be accessed by functions outside the class (such as main) or by member functions of other classes in the program. Attempting to access data member courseName in one of these program locations with an expression such as myGradeBook.courseName would result in a compilation error containing a message similar to
cannot access private member declared in class 'GradeBook'
Software Engineering Observation 3.1
As a rule of thumb, data members should be declared private and member functions should be declared public. (We will see that it is appropriate to declare certain member functions private, if they are to be accessed only by other member functions of the class.) |
Common Programming Error 3.6
An attempt by a function, which is not a member of a particular class (or a friend of that class, as we will see in Chapter 10), to access a private member of that class is a compilation error. |
The default access for class members is private so all members after the class header and before the first access specifier are private. The access specifiers public and private may be repeated, but this is unnecessary and can be confusing.
Good Programming Practice 3.4
Despite the fact that the public and private access specifiers may be repeated and intermixed, list all the public members of a class first in one group and then list all the private members in another group. This focuses the client's attention on the class's public interface, rather than on the class's implementation. |
Good Programming Practice 3.5
If you choose to list the private members first in a class definition, explicitly use the private access specifier despite the fact that private is assumed by default. This improves program clarity. |
Declaring data members with access specifier private is known as data hiding. When a program creates (instantiates) an object of class GradeBook, data member courseName is encapsulated (hidden) in the object and can be accessed only by member functions of the object's class. In class GradeBook, member functions setCourseName and getCourseName manipulate the data member courseName directly (and displayMessage could do so if necessary).
Software Engineering Observation 3.2
We will learn in Chapter 10, Classes: A Deeper Look, Part 2, that functions and classes declared by a class to be friends can access the private members of the class. |
Error-Prevention Tip 3.1
Making the data members of a class private and the member functions of the class public facilitates debugging because problems with data manipulations are localized to either the class's member functions or the friends of the class. |
Member Functions setCourseName and getCourseName
Member function setCourseName (defined in lines 1922) does not return any data when it completes its task, so its return type is void. The member function receives one parameternamewhich represents the course name that will be passed to it as an argument (as we will see in line 55 of main). Line 21 assigns name to data member courseName. In this example, setCourseName does not attempt to validate the course namei.e., the function does not check that the course name adheres to any particular format or follows any other rules regarding what a "valid" course name looks like. Suppose, for instance, that a university can print student transcripts containing course names of only 25 characters or fewer. In this case, we might want class GradeBook to ensure that its data member courseName never contains more than 25 characters. We discuss basic validation techniques in Section 3.10.
Member function getCourseName (defined in lines 2528) returns a particular GradeBook object's courseName. The member function has an empty parameter list, so it does not require additional data to perform its task. The function specifies that it returns a string. When a function that specifies a return type other than void is called and completes its task, the function returns a result to its calling function. For example, when you go to an automated teller machine (ATM) and request your account balance, you expect the ATM to give you back a value that represents your balance. Similarly, when a statement calls member function getCourseName on a GradeBook object, the statement expects to receive the GradeBook's course name (in this case, a string, as specified by the function's return type). If you have a function square that returns the square of its argument, the statement
int result = square( 2 );
returns 4 from function square and initializes the variable result with the value 4. If you have a function maximum that returns the largest of three integer arguments, the statement
int biggest = maximum( 27, 114, 51 );
returns 114 from function maximum and initializes variable biggest with the value 114.
Common Programming Error 3.7
Forgetting to return a value from a function that is supposed to return a value is a compilation error. |
Note that the statements at lines 21 and 27 each use variable courseName (line 39) even though it was not declared in any of the member functions. We can use courseName in the member functions of class GradeBook because courseName is a data member of the class. Also note that the order in which member functions are defined does not determine when they are called at execution time. So member function getCourseName could be defined before member function setCourseName.
Member Function displayMessage
Member function displayMessage (lines 3137) does not return any data when it completes its task, so its return type is void. The function does not receive parameters, so its parameter list is empty. Lines 3536 output a welcome message that includes the value of data member courseName. Line 35 calls member function getCourseName to obtain the value of courseName. Note that member function displayMessage could also access data member courseName directly, just as member functions setCourseName and getCourseName do. We explain shortly why we choose to call member function getCourseName to obtain the value of courseName.
Testing Class GradeBook
The main function (lines 4360) creates one object of class GradeBook and uses each of its member functions. Line 46 creates a GradeBook object named myGradeBook. Lines 4950 display the initial course name by calling the object's getCourseName member function. Note that the first line of the output does not show a course name, because the object's courseName data member (i.e., a string) is initially emptyby default, the initial value of a string is the so-called empty string, i.e., a string that does not contain any characters. Nothing appears on the screen when an empty string is displayed.
Line 53 prompts the user to enter a course name. Local string variable nameOfCourse (declared in line 45) is set to the course name entered by the user, which is obtained by the call to the getline function (line 54). Line 55 calls object myGradeBook's setCourseName member function and supplies nameOfCourse as the function's argument. When the function is called, the argument's value is copied to parameter name (line 19) of member function setCourseName (lines 1922). Then the parameter's value is assigned to data member courseName (line 21). Line 57 skips a line in the output; then line 58 calls object myGradeBook's displayMessage member function to display the welcome message containing the course name.
Software Engineering with Set and Get Functions
A class's private data members can be manipulated only by member functions of that class (and by "friends" of the class, as we will see in Chapter 10, Classes: A Deeper Look, Part 2). So a client of an objectthat is, any class or function that calls the object's member functions from outside the objectcalls the class's public member functions to request the class's services for particular objects of the class. This is why the statements in function main (Fig. 3.5, lines 4360) call member functions setCourseName, getCourseName and displayMessage on a GradeBook object. Classes often provide public member functions to allow clients of the class to set (i.e., assign values to) or get (i.e., obtain the values of) private data members. The names of these member functions need not begin with set or get, but this naming convention is common. In this example, the member function that sets the courseName data member is called setCourseName, and the member function that gets the value of the courseName data member is called getCourseName. Note that set functions are also sometimes called mutators (because they mutate, or change, values), and get functions are also sometimes called accessors (because they access values).
Recall that declaring data members with access specifier private enforces data hiding. Providing public set and get functions allows clients of a class to access the hidden data, but only indirectly. The client knows that it is attempting to modify or obtain an object's data, but the client does not know how the object performs these operations. In some cases, a class may internally represent a piece of data one way, but expose that data to clients in a different way. For example, suppose a Clock class represents the time of day as a private int data member time that stores the number of seconds since midnight. However, when a client calls a Clock object's getTime member function, the object could return the time with hours, minutes and seconds in a string in the format "HH:MM:SS". Similarly, suppose the Clock class provides a set function named setTime that takes a string parameter in the "HH:MM:SS" format. Using string capabilities presented in Chapter 18, the setTime function could convert this string to a number of seconds, which the function stores in its private data member. The set function could also check that the value it receives represents a valid time (e.g., "12:30:45" is valid but "42:85:70" is not). The set and get functions allow a client to interact with an object, but the object's private data remains safely encapsulated (i.e., hidden) in the object itself.
The set and get functions of a class also should be used by other member functions within the class to manipulate the class's private data, although these member functions can access the private data directly. In Fig. 3.5, member functions setCourseName and getCourseName are public member functions, so they are accessible to clients of the class, as well as to the class itself. Member function displayMessage calls member function getCourseName to obtain the value of data member courseName for display purposes, even though displayMessage can access courseName directlyaccessing a data member via its get function creates a better, more robust class (i.e., a class that is easier to maintain and less likely to stop working). If we decide to change the data member courseName in some way, the displayMessage definition will not require modificationonly the bodies of the get and set functions that directly manipulate the data member will need to change. For example, suppose we decide that we want to represent the course name as two separate data memberscourseNumber (e.g., "CS101") and courseTitle (e.g., "Introduction to C++ Programming"). Member function displayMessage can still issue a single call to member function getCourseName to obtain the full course to display as part of the welcome message. In this case, getCourseName would need to build and return a string containing the courseNumber followed by the courseTitle. Member function displayMessage would continue to display the complete course title "CS101 Introduction to C++ Programming," because it is unaffected by the change to the class's data members. The benefits of calling a set function from another member function of a class will become clear when we discuss validation in Section 3.10.
Good Programming Practice 3.6
Always try to localize the effects of changes to a class's data members by accessing and manipulating the data members through their get and set functions. Changes to the name of a data member or the data type used to store a data member then affect only the corresponding get and set functions, but not the callers of those functions. |
Software Engineering Observation 3.3
It is important to write programs that are understandable and easy to maintain. Change is the rule rather than the exception. Programmers should anticipate that their code will be modified. |
Software Engineering Observation 3.4
The class designer need not provide set or get functions for each private data item; these capabilities should be provided only when appropriate. If a service is useful to the client code, that service should typically be provided in the class's public interface. |
GradeBook's UML Class Diagram with a Data Member and set and get Functions
Figure 3.6 contains an updated UML class diagram for the version of class GradeBook in Fig. 3.5. This diagram models class GradeBook's data member courseName as an attribute in the middle compartment of the class. The UML represents data members as attributes by listing the attribute name, followed by a colon and the attribute type. The UML type of attribute courseName is String, which corresponds to string in C++. Data member courseName is private in C++, so the class diagram lists a minus sign (-) in front of the corresponding attribute's name. The minus sign in the UML is equivalent to the private access specifier in C++. Class GradeBook contains three public member functions, so the class diagram lists three operations in the third compartment. Recall that the plus (+) sign before each operation name indicates that the operation is public in C++. Operation setCourseName has a String parameter called name. The UML indicates the return type of an operation by placing a colon and the return type after the parentheses following the operation name. Member function getCourseName of class GradeBook (Fig. 3.5) has a string return type in C++, so the class diagram shows a String return type in the UML. Note that operations setCourseName and displayMessage do not return values (i.e., they return void), so the UML class diagram does not specify a return type after the parentheses of these operations. The UML does not use void as C++ does when a function does not return a value.
Figure 3.6. UML class diagram for class GradeBook with a private courseName attribute and public operations setCourseName, getCourseName and displayMessage.