Previous Table of Contents Next


The first function call results in a call to CStr::cpy, because string1 is an object of type CStr. The result of this call is to set string1’s copy of the data member:

string1.cpy(“My name is ”); // Calls CStr::cpy

The next line of code results in a call to the same function, CStr::cpy. However, this time the call is made through the object string2, so it is string2’s data member that gets changed:

string2.cpy(“Bill.”);       // Calls CStr::cpy

The next two statements result in calls to CStr::cat and CStr::get (the latter is called twice).

stringl.cat(string2.get()); // Calls CStr::cpy and
                            // CStr::get
puts(string1.get());        // Calls CStr::cpy

As a final example, consider another class declaration, CTemp_point. Assume that the function definitions are provided elsewhere in the program.

class CTemp_point {
   int     x, y, z;
   double temp;
public:
   void set_point(int x, y, z);
   void get_point(int *x, *y, *z);
   void set_temp(double new_temp);
   double get_temp(void);
};

With this declaration, we might declare two objects:

CTemp_point mypoint, point_break;

The following function call results in a call to CTemp_point::set_point. If any data members are affected, they will be mypoint’s copy of these members.

mypoint.set_point(0, 0, 0);

Pointers to Objects

Just as you can have pointers to structures and pointers to variables, you can have pointers to objects in C++. (Remember that structures and variables should be considered to be kinds of objects.)

Many C++ programs make heavy use of pointers to objects. To create objects dynamically, you have to use pointers. This means creating objects on the fly, allocating and freeing them at will, without having to give them the lifetime of a program or even the lifetime of a function call (unlike local variables which must be given the lifetime of a function call).

Although you can create and free objects with the malloc and free library functions, C++ supports the new and delete keywords for dynamic object creation. The new keyword returns a pointer. When you use it, you follow it with a type or class name.

CStr *pString;
pString = new CStr;
. . .
delete pString;         // free memory allocated

The use of new and delete is strongly preferred over the use of malloc to create objects, because new and delete automatically call constructors and destructors of the class as appropriate. You’ll learn about constructors and destructors later in this chapter.

The syntax for using pointers to objects in C++ is a natural extension of that for pointers to structures in C. To refer to a data member, use the -> operator:

ptr->member

Not surprisingly, you can use a similar syntax to call member functions for the object pointed to:

ptr->member_function(args)

Reaping the Benefits of Private Data

If you’re reasonably cynical, you may be wondering what’s so wonderful about classes and member functions. After all, isn’t the member-function technology just so much syntactic sugar? What is the difference between these two function calls?

strcpy(string1, “hello”);
stringl.cpy(“hello”);

The next chapter will show a much more dramatic contrast, but I can describe a major benefit right now: in the standard approach, rewriting any code that implements string handling is dangerous and likely to cause programs to fail. But in the second case, you can rewrite the internals of string objects without causing any errors.

One of the most pressing of all problems in software development is this: how do you fix, update, or alter one part of a program without disrupting all the other parts that interact with it? To some extent, object orientation helps to solve this problem, but only if you follow the Golden Rule of Classes: as long as the type information of public members (both functions and data) is unchanged, you can freely rewrite member-function implementations and any and all private members. By member-function implementations, I mean the function definitions. You can freely rewrite these function definitions as long as you don’t change the argument or return types.

The original class declaration of CStr was far from optimal. It can be improved for efficiency and flexibility. The problem is that the current implementation of CStr fixes maximum size at 256 bytes. This limitation may be too small in some cases and overkill in others.

class CStr {
   char sData[256];
public:
   char *get(void);
   int getlength(void);
   void cpy(char *s);
   void cat(char *s);
};

Enough of this inefficiency. A superior implementation uses a pointer that can be set to any address. It also stores length as an integer for more efficient reporting of length.

class CStr {
   char *pData;
   int nLength;
public:
    char *get(void);
    int getlength(void);
    void cpy(char *s);
    void cat(char *s);
};

The internal members of CStr have changed dramatically. But because the public declarations haven’t changed, the rest of the program won’t be affected.

Because sData is gone, all the function definitions must be rewritten. In this implementation, strings are created and re-created as needed through dynamic memory allocation.

#include <string.h>
#include <malloc.h>

char *CStr::get(void) { // Return ptr to string data.
   return pData;
}

int CStr::getlength(void) { // Return length.
   return nLength;
}

void CStr::cpy(char *s) Copy string arg to
   object.
   int n;

   n = strlen(s);
   if (nLength != n) {
       if (pData)
             free(pData);
       pData = (char*) malloc(n + 1);

             nLength = n;
       }
     strcpy(pData, s);
   }


void CStr::cat(char *s) { // Concatenate string arg
   int n;
   char *pTemp;

   n = strlen(s);
   if (n == 0)
        return;
   pTemp = (char*) malloc(n + nLength + 1);
   if (pData) {
       strcpy(pTemp, pData);
       free(pData);
   }
   strcat(pTemp, s);
   pData = pTemp;
   nLength += n;
}


Previous Table of Contents Next