Reliable Software Logo

C++ In Action: Language

Member function scope

Member function scope, strings as arrays of chars, calling other member functions.


In the next example we will show how the body of a member function forms its own local scope. We will be able to define objects within that scope, create sub-scopes, and so on.

So here's a new twist in our input object.

#include <iostream>
using std::cout;
using std::cin;

class  InputNum
{
public:
    InputNum ()
    {
        cout << "Enter number ";
        cin >> _num;
    }

    int GetValue () const {  return _num; }

    void AddInput ()
    {
        InputNum aNum;  // get a number from user
        _num = _num + aNum.GetValue ();
    }
        
private:
    int _num;
};

int main()
{
    InputNum num;
    cout << "The value is " << num.GetValue() << "\n";
    num.AddInput();
    cout << "Now the value is " << num.GetValue() << "\n";
    return 0;
}
					

A new method was added to InputNum. It uses a local object of type InputNum to prompt the user for a number. This number is then retrieved using GetValue() and added to the original value.

Look at the following series of pictures that visualize the execution of the program.

Figure 6 While executing main, the constructor of object num of class InputNum is called. It prompts the user to input a number. The user inputs 5. This value is stored in num's private data member _num.


Figure 7 The GetValue method of the object num is called. It retrieves and returns the value stored in num's private data member _num. This value is 5.


Figure 8 The AddInput method of the object num is called next.


Figure 9 The AddInput method of num creates the object aNum of the class InputNumber. The constructor of aNum prompts the user to input a number. User enters 11 and this value is stored in aNum's private data member _num.


Figure 10 While still executing the AddInput method of num, the method GetValue of the object aNum is called. It retrieves and returns the value stored in aNum's private data member _num. This value, equal to 11, is then added to the num's private data member _num. Its value was 5, but after the addition of 11 it changes to 16.


Figure 11 The method GetValue of the object num is called again. It retrieves and returns the value of num's private data member _num, which is now equal to 16.

In C++ you can do standard arithmetic operations on numbers and variables. Below is a list of basic arithmetic operators.

Arithmetic Binary Operators
+ addition a + b
- subtraction a - b
* multiplication a * b
/ division a / b
% division modulo a % b
Arithmetic Unary Operators
+ plus +a
- minus -a
Assignment Operator
= assignment a = b

Some of the operators require a word of explanation. For instance, why are there two division operators?

First of all, if you are operating on floating point numbers (for instance, of the type double) you should use the regular division operator /. Even if only one of the two numbers or variables is a decimal fraction, like 3.14, or a double, the result of the division will be a number of the type double. However, if both operands are integers--literal numbers without a decimal point or variables defined as int (or other integral types that we'll talk about soon)--applying operator / results in an integer equal to the integral part of the quotient. For instance, 3 / 2 will produce 1, which is the integral part of 1.5. Similarly, 10 / 3 will produce 3, etc.

The % operator, which can only be applied to integral types, yields the remainder from the division. For instance, 3 % 2 (pronounced "3 modulo 2") is equal to 1. Similarly, 11 % 3 yields 2, etc.

It is always true that

(a / b) * b + a % b is equal to a

However, the results of applying operators / and % when one of the numbers is negative are not well defined (although they still fulfill the equality above). So don't rely on them if you are dealing with numbers of either sign. If you think this sucks, you are right. The arguments for implementing such behavior in C++ are really lame: compatibility with C and efficient implementation in terms of processor instructions. I rest my case.

The unary plus does nothing. It is there for symmetry with the unary minus, which inverts the sign of a number. So if a = 2 then -a yields -2 and +a yields 2.

One thing is kind of awkward in the last program. The prompt "Enter number " is always the same. The user has no idea what's going on. Wouldn't it be nice to vary the prompt depending on the context? Ideally we would like to have code like this, in main:

InputNum num( "Enter number " );
num.AddInput( "Another one  " );
num.AddInput( "One more     " );

Some of the InputNum's methods would have to take strings as arguments. No problem. Here's how it's done:

#include <iostream>
using std::cout;
using std::cin;

class  InputNum
{
public:
    InputNum (char msg [])
    {
        cout << msg;
        cin >> _num;
    }

    int GetValue () const {  return _num; }

    void AddInput (char msg [])
    {
        InputNum aNum (msg);
        _num = GetValue () + aNum.GetValue ();
    }
        
private:
    int _num;
};

const char SumString [] = "The sum is   ";

int main()
{
    InputNum num ("Enter number ");
    num.AddInput ("Another one  ");
    num.AddInput ("One more     ");
    cout << SumString << num.GetValue () << "\n";
    return 0;
}
					

Several things are going on here. A new built-in type, char, has been introduced. As the name suggests, this type is used to store characters. Unless you work on some really weird machine, you can safely assume that chars are 8-bit quantities.

We also learn that a string is an array of characters--that's the meaning of brackets after the name of the string variable. What we don't see here is the fact that literal strings are zero terminated; that is, after the last explicit character, a null char is added by the compiler. For instance "Hi" is really an array of three characters, 'H', 'i', and 0 (or '\0' for purists). The null character is distinctly different from the character representing the digit zero. The latter is written as '0'. See the difference? Null character: 0 or '\0', zero: '0'. Since we are at it, "\n" is an array of two characters '\n' and '\0'. This terminating null tells various functions operating on strings, including our friend cout, where the end of the string is. Otherwise it would have no clue.

By the way, instead of sending the string "\n" to the output, you can get the same result sending a special thing called endl. The syntax is, cout << endl; and it works like magic (meaning, I don't want to explain it right now). Since endl is defined in the standard library, you either have to prefix it with std:: or declare, up front, your intent of using std::endl.

Another curious thing about this program is the way we call GetValue() inside AddInput(). We make two calls, one with no object specified, the other targeted at aNum. We know that the second one invokes GetValue on the object aNum. The first one, it turns out, invokes the GetValue() method on the object we are sitting in. The "this" object, as we say in the C++ argot. It simply retrieves its own value of _num. It does it, however, in a way that isolates us from the details of its implementation. Here it may look like an overkill of generality (why don't we just use _num directly?), but many situations it may mean a big win in future maintenance.

Some of you are probably asking yourselves the question: how expensive it is. Calling a member function instead of retrieving the value of _num directly. Or even, why not make _num public and shortcut our way to it everywhere. The syntax for accessing a public data member is:

myNum._num

You won't believe it, but the call is actually free. I'm not kidding, it costs zero additional cycles. Nada, nil, niente, zilch! (Later, I'll explain the meaning of inline functions).


To summarize, the body of a member function forms a separate local scope. This is also true about the body of a constructor and of a destructor. One can define objects within this local scope as well as create sub-scopes inside of it.

A string is a null terminated array of chars. Arrays are declared with rectangular brackets following their names. Arrays of chars can be initialized with explicit literal strings. Arrays may be defined within global or local scopes and passed as arguments to other functions.


NextNext: Types