Home

Arrays and Pointers of Classes

 

Classes and Arrays

 

An Array of Object Variables

We have learned to treat classes and structures as normal variables including creating an object by declaring a variable. We can define an object as follows:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

	Point location;



    location.Letter = 'D';

    location.x      =  5;

    location.y      = -2;



    cout << "Point Location " << location.Letter << "("

         << location.x << ", " << location.y << ");" << endl;



	return 0;

}

This would produce:

Point Location D(5, -2);

Remember that you can also use the typedef keyword to customize the name of a data type. For example, the Point class can be type-defined as follows:

typedef Point Coordinate;

Coordinate location;

As you would declare an array of variables, you can also declare an array of objects and manipulate each member of the array as if it were a regular variable. To do this, type the name of the class, followed by a valid C++ name for the variable, and followed by a dimension included in square brackets. Here is an example:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

	Point coordinates[4];



	return 0;

}

Using the typedef keyword, you can create a common name that would represent an array of objects. This can be done as follows:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

    typedef Point Points[4];

    Points coordinates;



    return 0;

}

In this case, the name Points means the same thing as declaring an array of 4 Point variables.

After declaring a variable as an array of objects, each member of the array has its own value you can assign, change, or retrieve. To get the value stored in a member variable of a member of an array, access it using its index. For example, to access the value of the third member variable of the first member of the above array, you could write the following:

coordinates[0].y;

 

 

Initializing an Array of Objects

There are various ways you can initialize an array of objects. You can initialize the variable as a whole, when declaring it. Still, there are at least two ways you can initialize a variable when declaring it. To do this, assign an opening and closing curly brackets combination to the variable. Inside the curly brackets, provide a list of the values of each member variable object, first in the order they appear in the class, second in the incremental order of the array. Here is an example:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

	Point Coordinates[4] = {'P', -4,  0, 'Q', -3, -2,

				'L',  5,  1, 'M', 2,  6};



    cout << "Point Coordinates";

    cout << "\n" << Coordinates[0].Letter << "("

         << Coordinates[0].x << ", " << Coordinates[0].y << ");";

    cout << "\n" << Coordinates[1].Letter << "("

         << Coordinates[1].x << ", " << Coordinates[1].y << ");";

    cout << "\n" << Coordinates[2].Letter << "("

         << Coordinates[2].x << ", " << Coordinates[2].y << ");";

    cout << "\n" << Coordinates[3].Letter << "("

         << Coordinates[3].x << ", " << Coordinates[3].y << ");\n";

	return 0;

}

This would produce:

Point Coordinates

P(-4, 0);

Q(-3, -2);

L(5, 1);

M(2, 6);

Another technique you can use, which would make the initialization easier to read, consists of including the values of each member of the array in its own set of curly brackets. It would be done as follows:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

	Point Coordinates[4] = { {'P', -4,  0}, {'Q', -3, -2},

				 {'L',  5,  1}, {'M', 2,  6}};



    cout << "Point Coordinates";

    cout << "\n" << Coordinates[0].Letter << "("

         << Coordinates[0].x << ", " << Coordinates[0].y << ");";

    cout << "\n" << Coordinates[1].Letter << "("

         << Coordinates[1].x << ", " << Coordinates[1].y << ");";

    cout << "\n" << Coordinates[2].Letter << "("

         << Coordinates[2].x << ", " << Coordinates[2].y << ");";

    cout << "\n" << Coordinates[3].Letter << "("

         << Coordinates[3].x << ", " << Coordinates[3].y << ");\n";

	return 0;

}

In our introduction to Classes, we also saw that you could declare an instance of a structure when creating it. This was done by providing a name for the variable between the closing curly bracket and the semi-colon. In the same way, when declaring such a variable, you can initialize it directly. Here is an example:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

} Coordinates[4] = {'P', -4,  0, 'Q', -3, -2,'L',  5,  1, 'M', 2,  6};



int main()

{

    cout << "Point Coordinates";

    cout << "\n" << Coordinates[0].Letter << "("

         << Coordinates[0].x << ", " << Coordinates[0].y << ");";

    cout << "\n" << Coordinates[1].Letter << "("

         << Coordinates[1].x << ", " << Coordinates[1].y << ");";

    cout << "\n" << Coordinates[2].Letter << "("

         << Coordinates[2].x << ", " << Coordinates[2].y << ");";

    cout << "\n" << Coordinates[3].Letter << "("

         << Coordinates[3].x << ", " << Coordinates[3].y << ");\n";

	return 0;

}

In the same way, you can initialize the array when creating the variable:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

} Coordinates[4] = { {'P', -4,  0}, {'Q', -3, -2},

		     {'L',  5,  1}, {'M', 2,  6}};



int main()

{

    cout << "Point Coordinates";

    cout << "\n" << Coordinates[0].Letter << "("

         << Coordinates[0].x << ", " << Coordinates[0].y << ");";

    cout << "\n" << Coordinates[1].Letter << "("

         << Coordinates[1].x << ", " << Coordinates[1].y << ");";

    cout << "\n" << Coordinates[2].Letter << "("

         << Coordinates[2].x << ", " << Coordinates[2].y << ");";

    cout << "\n" << Coordinates[3].Letter << "("

         << Coordinates[3].x << ", " << Coordinates[3].y << ");\n";

	return 0;

}

The above techniques of initialization are convenient for small objects, traditionally used as C structures. The other technique you can use, also convenient for an array of a more elaborate object, consists of initializing each member of the array. To do this, you assign a value to each member of the object. This can be done as follows:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

    Point Coordinates[2];



    Coordinates[0].Letter = 'P';

    Coordinates[0].x = -4;

    Coordinates[0].y =  0;

    Coordinates[1].Letter = 'Q';

    Coordinates[1].x = -3;

    Coordinates[1].y = -2;



    cout << "Point Coordinates";

    cout << "\n" << Coordinates[0].Letter << "("

         << Coordinates[0].x << ", " << Coordinates[0].y << ");";

    cout << "\n" << Coordinates[1].Letter << "("

         << Coordinates[1].x << ", " << Coordinates[1].y << ");\n";



	return 0;

}

Here is an example of running the program:

Point Coordinates

P(-4, 0);

Q(-3, -2);

Once you have declared and initialized an array of objects, you can use the values of its member variables as you see fit. For example, from the above Point class, you can request the coordinates of two points, write an equation that calculates their distance or one that gets their slope, and display it to the user. This can be done as follows:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



int main()

{

    Point Points[2];

    double Slope;



    cout << "Enter the coordinates of the points\n";

    cout << "x1 = "; cin >> Points[0].x;

    cout << "y1 = "; cin >> Points[0].y;

    cout << "x2 = "; cin >> Points[1].x;

    cout << "y2 = "; cin >> Points[1].y;

    

    Points[0].Letter = 'A';

    Points[1].Letter = 'B';

    

    if( Points[0].x != Points[1].x )

        Slope = (Points[1].y - Points[0].y) / (Points[1].x - Points[0].x);

    else

        Slope = 0.00;



    cout << "The slope of " << Points[0].Letter << "("

         << Points[0].x << ", " << Points[0].y << ") and "

         << Points[1].Letter << "(" << Points[1].x << ", "

         << Points[1].y << ") is " << Slope << endl;



	return 0;

}

Here is an example of running the program:

Enter the coordinates of the points

x1 = -2

y1 = 3

x2 = 1

y2 = -3

The slope of A(-2, 3) and B(1, -3) is -2
 

An Array of Objects as Argument

Like a regular data type, an array of objects can be passed as an argument to a function. To do this, when declaring and when implementing the function, provide the array in the parentheses of the function as you would do with any other regular variable. Here is an example that takes and processes an argument that is an array of objects:

void ProcessCoordinates(Point points[])

{

    cout << "Enter the coordinates of four points\n";

    cout << "x1 = "; cin >> points[0].x;

    cout << "y1 = "; cin >> points[0].y;

    cout << "x2 = "; cin >> points[1].x;

    cout << "y2 = "; cin >> points[1].y;

    cout << "x3 = "; cin >> points[2].x;

    cout << "y3 = "; cin >> points[2].y;

    cout << "x4 = "; cin >> points[3].x;

    cout << "y4 = "; cin >> points[3].y;

}

The above function was using an array whose dimension you know. If you do not know or cannot predict the dimension of an array, you can/should/must pass an additional argument that holds the number of members of the array.

When calling a function that takes a single-dimension array of objects, the name of the argument is sufficient to the compiler. Other than this, you can apply all the other rules we learned for arrays of regular data types:

#include <iostream>

using namespace std;



struct Point

{

    char Letter;

    int x;

    int y;

};



void ProcessCoordinates(Point points[]);

void DisplayCoordinates(Point points[], const int size);



int main()

{

    Point coordinates[4];



    ProcessCoordinates(coordinates);

    cout << endl;

    DisplayCoordinates(coordinates, 4);



    return 0;

}



void ProcessCoordinates(Point points[])

{

    cout << "Enter the coordinates of four points\n";

    cout << "x1 = "; cin >> points[0].x;

    cout << "y1 = "; cin >> points[0].y; points[0].Letter = 'A';

    cout << "x2 = "; cin >> points[1].x;

    cout << "y2 = "; cin >> points[1].y; points[1].Letter = 'B';

    cout << "x3 = "; cin >> points[2].x;

    cout << "y3 = "; cin >> points[2].y; points[2].Letter = 'C';

    cout << "x4 = "; cin >> points[3].x;

    cout << "y4 = "; cin >> points[3].y; points[3].Letter = 'D';

}



void DisplayCoordinates(Point points[], const int size)

{

    cout << "Point Coordinates";

    for(int i = 0; i < size; i++)

        cout << "\n" << points[i].Letter << "("

             << points[i].x << ", " << points[i].y << ");";

}

Here is an example of running the program:

Enter the coordinates of four points

x1 = -1

y1 = 5

x2 = -2

y2 = -4

x3 = 3

y3 = 5

x4 = 2

y4 = -4



Point Coordinates

A(-1, 5);

B(-2, -4);

C(3, 5);

D(2, -4);

Classes and Pointers

 

Declaring a Pointer to an Object

Header File: cone.h

//---------------------------------------------------------------------------

#ifndef ConeH

#define ConeH                                                                

//---------------------------------------------------------------------------

class Cone

{

private:

    double _rad;

    double _hgt;

public:

    Cone();

    Cone(double radius, double height);

    Cone(const Cone& c);

    ~Cone();

    void setRadius(const double radius) { _rad = radius; }

    void setHeight(const double height) { _hgt = height; }

    double getRadius() const { return _rad; }

    double getHeight() const { return _hgt; }

    double CalculateBaseArea() const;

    double CalculateLateralArea() const;

    double CalculateTotalArea() const;

    double CalculateVolume() const;

};

//---------------------------------------------------------------------------

#endif

Source File: cone.cpp

//---------------------------------------------------------------------------

#include <math.h>

#include "Cone.h"

//using namespace std;

//---------------------------------------------------------------------------

Cone::Cone()

    : _rad(0.00), _hgt(0.00)

{

    

}

//---------------------------------------------------------------------------

Cone::Cone(double r, double h)

    : _rad(r), _hgt(h)

{

    

}

//---------------------------------------------------------------------------

Cone::Cone(const Cone& c)

    : _rad(c._rad), _hgt(c._hgt)

{

    

}

//---------------------------------------------------------------------------

Cone::~Cone()

{

    

}

//---------------------------------------------------------------------------

double Cone::CalculateBaseArea() const

{

    // The area of the base of the cone

    return _rad * _rad * 3.14159;

}

//---------------------------------------------------------------------------

double Cone::CalculateLateralArea() const

{

    // The area covered by the tissue that covers the tent

    double radius2 = _rad * _rad;

    double height2 = _hgt * _hgt;

    double slantHeight = sqrt(radius2 + height2);

    return 3.14159 * _rad * slantHeight;

}

//---------------------------------------------------------------------------

double Cone::CalculateTotalArea() const

{

    return CalculateBaseArea() + CalculateLateralArea();

}

//---------------------------------------------------------------------------

double Cone::CalculateVolume() const

{

    // The interior volume available for inhabiting the tent

    return (3.14159 * _rad * _rad * _hgt) / 3;

}

//---------------------------------------------------------------------------

Like a regular data type, you can declare a class as a pointer, you can pass it to a function as a reference or as a pointer, and you can dynamically create an instance of a class. To declare a pointer to a class, type its name, followed by an asterisk and followed by a name for the variable. Here is an example:

Cone *pCone;

Like a pointer to a regular data type, after declaring a pointer to a class, you should let the compiler know what variable the pointer is pointing to. This can easily be done by assigning the address of an existing variable as follows:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone iceCone;

    Cone *pCone = &iceCone;

  

    double radius, height;



    cout << "Enter the cone's dimensions\n";

    cout << "Radius: ";

    cin >> radius;

    cout << "Height: ";

    cin >> height;



    pCone->setRadius(radius);

    pCone->setHeight(height);



    cout << "Cone's Characteristics";

    cout << "\nRadius:       " << pCone->getRadius();

    cout << "\nHeight:       " << pCone->getHeight();

    cout << "\nBase Area:    " << pCone->CalculateBaseArea();

    cout << "\nTotal Area:   " << pCone->CalculateTotalArea();

    cout << "\nTexture Area: " << pCone->CalculateLateralArea() << endl;



    return 0;

}

Here is an example of running the program:

Enter the cone's dimensions

Radius: 38.44

Height: 20.72

Cone's Characteristics

Radius:       38.44

Height:       20.72

Base Area:    4642.12

Total Area:   9915.67

Texture Area: 5273.55
 

Dynamic Objects

One of the advantages of using pointers is that they give you the ability to use memory only as needed. This is done by using the asterisk * and the new operators. The syntax of declaring a pointer to an object is:

ClassName* VariableName = new ClassName;

The ClassName is the class whose variable you want to declare. It could be in the program or one that shipped with the compiler. Once again, the asterisk lets the compiler know that the class is declared as a pointer. The variable name follows the same rules we have applied to other variables so far.

There are two main ways you can provide values to the member variables of your dynamic object. You can request their values from the user, from another object or function, or you can initialize the variable. Still there are at least two ways you can initialize the variable. When declaring it, since you would be using a constructor to build the object, you can use the constructor that takes the number of values you want to initialize. Consider the above Cone class. This class provides three constructors, one of which takes two arguments to initialize a complete Cone variable. You can use this constructor when using the new operator. In the parentheses, provide a value for each member variable. Here is an example:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone *pCone = new Cone(25.55, 20.15);



    cout << "Cone's Characteristics";

    cout << "\nRadius:       " << pCone->getRadius();

    cout << "\nHeight:       " << pCone->getHeight();

    cout << "\nBase Area:    " << pCone->CalculateBaseArea();

    cout << "\nTotal Area:   " << pCone->CalculateTotalArea();

    cout << "\nTexture Area: " << pCone->CalculateLateralArea() << endl;



	return 0;

}

This would produce:

Cone's Characteristics

Radius:       25.55

Height:       20.15

Base Area:    2050.84

Total Area:   4662.71

Texture Area: 2611.88

If you want to use, or have used, the default constructor to create the object, you can then use one of its methods to initialize the variable. Here is an example:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone *pCone = new Cone();



    pCone->setRadius(25.55);

    pCone->setHeight(20.15);



    cout << "Cone's Characteristics";

    cout << "\nRadius:       " << pCone->getRadius();

    cout << "\nHeight:       " << pCone->getHeight();

    cout << "\nBase Area:    " << pCone->CalculateBaseArea();

    cout << "\nTotal Area:   " << pCone->CalculateTotalArea();

    cout << "\nTexture Area: " << pCone->CalculateLateralArea() << endl;



	return 0;

}

Of course, at any time, you can change the values of the pointer using either one of its constructors or its methods. After using a variable that was declared using the new operator, you can reclaim the memory space it was using. This is done with the delete operator.

Here is an example:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone *pCone = new Cone();



    pCone->setRadius(25.55);

    pCone->setHeight(20.15);



    cout << "Cone's Characteristics";

    cout << "\nRadius:       " << pCone->getRadius();

    cout << "\nHeight:       " << pCone->getHeight();

    cout << "\nBase Area:    " << pCone->CalculateBaseArea();

    cout << "\nTotal Area:   " << pCone->CalculateTotalArea();

    cout << "\nTexture Area: " << pCone->CalculateLateralArea() << endl;



    pCone = new Cone(44.42, 35.72);



    cout << "\nCone's Characteristics";

    cout << "\nRadius:       " << pCone->getRadius();

    cout << "\nHeight:       " << pCone->getHeight();

    cout << "\nBase Area:    " << pCone->CalculateBaseArea();

    cout << "\nTotal Area:   " << pCone->CalculateTotalArea();

    cout << "\nTexture Area: " << pCone->CalculateLateralArea() << endl;



	return 0;

}

This would produce:

Cone's Characteristics

Radius:       25.55

Height:       20.15

Base Area:    2050.84

Total Area:   4662.71

Texture Area: 2611.88



Cone's Characteristics

Radius:       44.42

Height:       35.72

Base Area:    6198.79

Total Area:   14153.2

Texture Area: 7954.38

Pointers to Arrays of Objects

Instead of using one pointer to an object, you can declare an array of pointers to object. The syntax used is:

ClassName *VariableName[Dimension];

The ClassName must be an existing object. You can use your own object or one that shipped with the compiler. After the asterisk operator, type a valid name for a C++ variable. The dimension, which is (an estimate of) the number of elements of the array, is included in square brackets.

After declaring the variable, you can initialize each member of the array using the new operator. This operator allocates enough memory space for the member of the array. This means that the initialization with the new operator can be followed by an actual initialization of the member variables of the array.

To initialize a member of the array, you have various options. You must first call the new operator for each member of the array. Once again, you can use one of the constructors. Consider the above Cone object. We saw that one of its constructors took two arguments to create an object. You can use such a constructor to initialize a member of the array. Here are examples:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone *pCone[3];



    pCone[0] = new Cone(25.55, 20.15);

    pCone[1] = new Cone(12.44, 18.62);

    pCone[2] = new Cone(48.12, 38.84);



    cout << "Cone's Characteristics\n";

    for(int i = 0; i < 3; i++)

    {

        cout << "\n - Cone No. " << i + 1 << " -";

        cout << "\nRadius:       " << pCone[i]->getRadius();

        cout << "\nHeight:       " << pCone[i]->getHeight();

        cout << "\nBase Area:    " << pCone[i]->CalculateBaseArea();

        cout << "\nTotal Area:   " << pCone[i]->CalculateTotalArea();

        cout << "\nTexture Area: " << pCone[i]->CalculateLateralArea() << endl;

    }



	return 0;

}

This would produce:

Cone's Characteristics



 - Cone No. 1 -

Radius:       25.55

Height:       20.15

Base Area:    2050.84

Total Area:   4662.71

Texture Area: 2611.88



 - Cone No. 2 -

Radius:       12.44

Height:       18.62

Base Area:    486.172

Total Area:   1361.33

Texture Area: 875.159



 - Cone No. 3 -

Radius:       48.12

Height:       38.84

Base Area:    7274.46

Total Area:   16622.9

Texture Area: 9348.43

If you had already created some objects and want to use their values, you can assign the desired variable to the member of the array of your choice. Here are examples:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone *pCone[3];

    Cone conic(66.14, 50.82);

    Cone *iceCreamCone = new Cone(8.74, 6.82);



    pCone[0] = &conic;

    pCone[1] = iceCreamCone;

    pCone[2] = new Cone(48.12, 38.84);



    cout << "Cone's Characteristics\n";

    for(int i = 0; i < 3; i++)

    {

        cout << "\n - Cone No. " << i + 1 << " -";

        cout << "\nRadius:       " << pCone[i]->getRadius();

        cout << "\nHeight:       " << pCone[i]->getHeight();

        cout << "\nBase Area:    " << pCone[i]->CalculateBaseArea();

        cout << "\nTotal Area:   " << pCone[i]->CalculateTotalArea();

        cout << "\nTexture Area: " << pCone[i]->CalculateLateralArea() << endl;

    }



    return 0;

}

This would produce:

Cone's Characteristics



 - Cone No. 1 -

Radius:       66.14

Height:       50.82

Base Area:    13742.9

Total Area:   31074.1

Texture Area: 17331.3



 - Cone No. 2 -

Radius:       8.74

Height:       6.82

Base Area:    239.979

Total Area:   544.373

Texture Area: 304.395



 - Cone No. 3 -

Radius:       48.12

Height:       38.84

Base Area:    7274.46

Total Area:   16622.9

Texture Area: 9348.43

Another technique you can use is to first allocate memory space for a member of the array, then use one of the methods of the object to initialize that particular member of the array. This can be taken care of with set methods.

After using the array, you can delete each of its members using the delete operator. Here is an example:

#include <iostream>

#include "cone.h"

using namespace std;



int main()

{

    Cone *pCone[3];



    pCone[0] = new Cone;

    pCone[0]->setRadius(25.55);

    pCone[0]->setHeight(20.15);

                               

    pCone[1] = new Cone;

    pCone[1]->setRadius(12.44);

    pCone[1]->setHeight(18.62);

    

    pCone[2] = new Cone;

    pCone[2]->setRadius(48.12);

    pCone[2]->setHeight(38.84);



    cout << "Cone's Characteristics\n";

    for(int i = 0; i < 3; i++)

    {

        cout << "\n - Cone No. " << i + 1 << " -";

        cout << "\nRadius:       " << pCone[i]->getRadius();

        cout << "\nHeight:       " << pCone[i]->getHeight();

        cout << "\nBase Area:    " << pCone[i]->CalculateBaseArea();

        cout << "\nTotal Area:   " << pCone[i]->CalculateTotalArea();

        cout << "\nTexture Area: " << pCone[i]->CalculateLateralArea() << endl;

    }



    delete pCone[0];

    delete pCone[1];

    delete pCone[2];



    return 0;

}
 

An Object Passed as Pointer

To pass an object as pointer, when declaring and when implementing the object, use the asterisk operator between the class name and the variable name. Consequently, when defining the function, access the members of the object using the arrow operator:

void RequestDimensions(Cone* cn)

{

    double Radius, Height;



    cout << "Enter the cone's dimensions\n";

    cout << "Radius: ";

    cin >> Radius;

    cout << "Height: ";

    cin >> Height;



    cn->setRadius(Radius);

    cn->setHeight(Height);

}

When calling such a function, if the variable was declared as a regular item, in the parentheses of the function, call the variable using the reference operator:

#include <iostream>

#include "cone.h"

using namespace std;



void RequestDimensions(Cone* cn)

{

    double Radius, Height;



    cout << "Enter the cone's dimensions\n";

    cout << "Radius: ";

    cin >> Radius;

    cout << "Height: ";

    cin >> Height;



    cn->setRadius(Radius);

    cn->setHeight(Height);

}



int main()

{

    Cone typeCone;



    RequestDimensions(&typeCone);



    cout << "\nCone's Characteristics";

    cout << "\nRadius:       " << typeCone.getRadius();

    cout << "\nHeight:       " << typeCone.getHeight();

    cout << "\nBase Area:    " << typeCone.CalculateBaseArea();

    cout << "\nTotal Area:   " << typeCone.CalculateTotalArea();

    cout << "\nTexture Area: " << typeCone.CalculateLateralArea();



    return 0;

}

Here is an example of running the program:

Enter the cone's dimensions

Radius: 24.58

Height: 20.64



Cone's Characteristics

Radius:       24.58

Height:       20.64

Base Area:    1898.07

Total Area:   4376.58

Texture Area: 2478.5

As you may have realized, when an argument is passed as pointer, if the function modifies its value, the object would be altered. This has the same effect as passing an object by reference because, once more, the function has access to the memory location of the argument. Consequently, there is an advantage of speed when passing an argument as pointer. If, on the other hand, you do not want the function to modify the value of the argument, you can pass it as a constant pointer. Here is an example:

#include <iostream>

#include "cone.h"

using namespace std;



void RequestDimensions(Cone*);

void ShowCharacteristics(const Cone *pCone);



int main()

{

    Cone typeCone;



    RequestDimensions(&typeCone);



    ShowCharacteristics(&typeCone);



    return 0;

}



void RequestDimensions(Cone* cn)

{

    double Radius, Height;



    cout << "Enter the cone's dimensions\n";

    cout << "Radius: ";

    cin >> Radius;

    cout << "Height: ";

    cin >> Height;



    cn->setRadius(Radius);

    cn->setHeight(Height);

}



void ShowCharacteristics(const Cone *pCone)

{

    cout << "\nCone's Characteristics";

    cout << "\nRadius:       " << pCone->getRadius();

    cout << "\nHeight:       " << pCone->getHeight();

    cout << "\nBase Area:    " << pCone->CalculateBaseArea();

    cout << "\nTotal Area:   " << pCone->CalculateTotalArea();

    cout << "\nTexture Area: " << pCone->CalculateLateralArea();

}

 

 

Classes and Pointers to Functions

 

A Pointer to Function as a Member Variable

When studying pointers to functions, we learned to use a programmer's type-defined declaration to create an alias to a pointer to function. We used an example such as the following:

typedef double (*Multiply)(const double a);

With this declaration, the word Multiply can be used in place of a function that takes a double-precision value as argument and the function returns a double. We learned that such a Multiply name could be used to declare a variable that in fact would be used as a function. Based on this feature of the C++ language, the Multiply name can also be used to declare a member variable of an object. Such a member variable would be interpreted as a function. The declaration can be done as follows:

typedef double (*Multiply)(const double a);



struct TSquare

{

    Multiply Perimeter;

};

As done with the regular pointer to function, you do not implement the Perimeter method. In fact, so far, the compiler does not know what to do with the Perimeter member. Therefore, you must formally define a function that can implement the behavior that the Perimeter member is supposed to use. Such a function can be defined as follows:

double Perimetre(const double x)

{

    return x * 4;

}

Even with this definition of the Perimetre() function, there is no relationship between the Perimeter member of the TSquare class and the Perimetre() independent function, and the compiler does not see any relationship between them. This means that, until you join these two functions, the compiler would not know what to do with the member of the object. To use the Perimeter member of the TSquare object, you should first assign it an appropriate function that has the same signature as its alias. Then you can use the member of the class as if it were a regular method of the object. Here is an example of how this would be done:

//---------------------------------------------------------------------------

#include <iostream>

using namespace std;



//---------------------------------------------------------------------------

                                                      

typedef double (*Multiply)(const double a);

//---------------------------------------------------------------------------

struct TSquare

{

    Multiply Perimeter;

};

//---------------------------------------------------------------------------

double Perimetre(const double x)

{

    return x * 4;

}

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    TSquare Sqr;



    double Side = 25.55;

    Sqr.Perimeter = Perimetre;



    cout << "Square Characteristics";

    cout << "\nSide:      " << Side;

    cout << "\nPerimeter: " << Sqr.Perimeter(Side);



    

    

    return 0;

}

//---------------------------------------------------------------------------

Using this same approach, you can declare different types of pointers to function as you see fit and using the function signature of your choice. Keep in mind that, when an alias is used to declare a variable, because it is declaring a variable type and not a function, you cannot create various aliases with the same name. This causes a name conflict, even if you try using different function signatures. Here is an example:

//---------------------------------------------------------------------------

#include <iostream>



using namespace std;



//---------------------------------------------------------------------------



typedef double (*Multiply)(const double a);

typedef double (*Multiple)(const double x, const double y);

//---------------------------------------------------------------------------

struct TSquare

{

    Multiply Perimeter;

    Multiple Area;

};

//---------------------------------------------------------------------------

double Perimetre(const double x)

{

    return x * 4;

}

//---------------------------------------------------------------------------

double Surface(const double x, const double y)

{

    return x * y;

}

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    TSquare Sqr;



    double Side = 25.55;

    Sqr.Perimeter = Perimetre;

    Sqr.Area = Surface;



    cout << "Square Characteristics";

    cout << "\nSide:      " << Side;

    cout << "\nPerimeter: " << Sqr.Perimeter(Side);

    cout << "\nArea:      " << Sqr.Area(Side, Side);



    

    

    return 0;

}

//---------------------------------------------------------------------------

Pointers to Method

Many of the functions and classes you will be using when creating applications are from already existing libraries. The idea is to reduce your efforts and be a little faster and productive. You also will be creating a lot of functions and classes. Once you have created such functionalities, there will be no reason to always write the same code over and over again. For example, if you had previously created a class that can process a rectangle object, it would not be good to create a new rectangle class when you need one, you can just refer to one of your classes that can do the job. Code reuse is a tremendous feature of object oriented programming and you should take advantage of it.

We have just learned that you can use a pointer to function to declare a member or members of an object. We learned that, to do this, you must have a function that would implement the desired functionality of the member variable. What about the reverse? Can you create a pointer to a method and use it as a declarable type?

The C++ language allows you to use a member function of an object as pointer to function. To do this, the C++ language require that the name of the method be fully qualified because the compiler would need to know which object possesses that method. The syntax you would use to create such an alias is:

typedef ReturnType (ClassName::*VariableName)(Arguments, if any);

The typedef keyword lets the compiler know that you are creating an alias. The return type must be either a regular data type (int, char, double and their variants) or an object (class a struct). Between the first parentheses, provide the name of the object (class or structure) that "owns" the method whose alias you want to create. The VariableName is a valid C++ name of a variable (and not the function member). If the method takes argument(s), you can provide the type(s) of argument(s) in the second parentheses. If the method does not take any argument, leave the (second) parentheses empty.

Imagine you create a special class that would regularly help you perform arithmetic operations. Such a class can be structured as follows:

class TArithmetic

{

public:

    double Addition(double Value1, double Value2);

    double Subtraction(double Value1, double Value2);

    double Multiplication(double Value1, double Value2);

    double Division(double Numerator, double Denominator);

};

Imagine that, in your program, you want to perform an addition of two values and produce a result. You can create an alias to the Addition method as follows:

typedef double (TArithmetic::*Add)(double x, double y);

With this definition, the word Add can be used in place of a function that takes two double-precision values as arguments and returns a real value. To use this alias, you must point it to the desired method of the object. This can be done as follows:

Add Plus = &TArithmetic::Addition;

Now, the name Plus can be used to call the Addition() method of the TArithmetic class. Of course, because using a class, you must first declare a variable from it. Because Plus is in fact a pointer, you must call it as such. Here is an example of how Plus can be used as a function:

//---------------------------------------------------------------------------

#include <iostream>



using namespace std;





//---------------------------------------------------------------------------



class TArithmetic

{

public:

    double Addition(double Value1, double Value2);

    double Subtraction(double Value1, double Value2);

    double Multiplication(double Value1, double Value2);

    double Division(double Numerator, double Denominator);

};

//---------------------------------------------------------------------------

double TArithmetic::Addition(double Val1, double Val2)

{

    return Val1 + Val2;

}

//---------------------------------------------------------------------------

double TArithmetic::Subtraction(double Val1, double Val2)

{

    return Val1 - Val2;

}

//---------------------------------------------------------------------------

double TArithmetic::Multiplication(double Val1, double Val2)

{

    return Val1 * Val2;

}

double TArithmetic::Division(double Num, double Den)

{

    if( Den == 0 )

        return 0;

    // else is implied

    return Num / Den;

}

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    typedef double (TArithmetic::*Add)(double x, double y);

    

    TArithmetic Oper;

    Add Plus = &TArithmetic::Addition;



    double APlusB = (Oper.*Plus)(244, 125);



    cout << "244 + 125 = " << APlusB;



    

    

    return 0;

}

//---------------------------------------------------------------------------

When we studied pointers to functions, we learned to declare them as follows:

double (*Add)(double x, double y);

It would be a good idea to use this alias as a pointer to methods of a class. This could be done as follows:

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    double (*Add)(double x, double y);

    

    TArithmetic Oper;

    Add = Oper.Addition;



    double APlusB = Add(244, 125);



    cout << "244 + 125 = " << APlusB;



    

    

    return 0;

}

//---------------------------------------------------------------------------
 

Objects and Pointers to Function

As mentioned already when studying pointers to functions, callback functions are heavily used in Microsoft Windows programming. Besides the regular variables, these functions, called procedures, can take objects as argument. These functions are a little more restrictive.

A pointer to function is a type of function whose name can be assigned to a variable as if it were a regular variable. This eases their use in Win32 procedures and VCL's events. The syntax of declaring a pointer to function is:

DataType (*FunctionName)(ObjectName);

The DataType is the kind of value the function will return.

The FunctionName must be a valid name for a function. Because you are creating a pointer to function, the name of the function must be followed by an asterisk.

This time, unlike pointer to functions of void or other regular data types, a pointer to function that involves an object type must necessarily have at least one argument of class or structure type.

To illustrate this use of pointers to function, we will take an example:

On a Cartesian coordinate system, a line is known as a junction of two points. One of the operations you can perform on such a line is to calculate its length. The equation used is:

To calculate this distance, you can declare a function that would take two points as arguments. This function can be implemented and tested as follows:

//---------------------------------------------------------------------------

#include <iostream>

#include <math.h>



using namespace std;





//---------------------------------------------------------------------------





namespace Geometry

{

struct TPoint

{

    double x;

    double y;

    TPoint(double X=0, double Y=0) : x(X), y(Y) {}

};

}

//---------------------------------------------------------------------------

double Distance(Geometry::Point Pt1, Geometry::Point Pt2)

{

    double L1 = pow(Pt2.x - Pt1.x, 2);

    double L2 = pow(Pt2.y - Pt1.y, 2);

    return sqrt(L1 + L2);

}

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    Geometry::Point P(-3, 2);

    Geometry::Point Q(4, -1);

    double Length = Distance(P, Q);



    cout << "The distance between P(-3, 2) and Q(4, -1) is " << Length;



    

    

    return 0;

}

//---------------------------------------------------------------------------

On a Cartesian coordinate system, a triangle is represented by three points:

Because this definition does not distinguish a particular type of triangle, it allows calculating the perimeter of a triangle, which is simply done by adding the lengths of the three sides. To perform this calculation, you can create a function that takes three points as arguments. Because a function that performs the distance calculation is already available, you can pass three of its pointers to the new function. Each argument is passed as a pointer to a function that takes two Point objects. After specifying the arguments, pass the necessary arguments that will actually be passed to the function that performs the distance calculations. Such a function can be declared as follows:

double Perimeter(double (*Length1)(Point A, Point B),

                 		 double (*Length2)(Point B, Point C),

                      	 double (*Length3)(Point A, Point C),

                      	 Point First, Point Second, Point Third);

To implement this function, you must call the Distance function for each argument passed as pointer to function. Other than than, you would follow the same rules for implementing functions. Our Perimeter() function can be implemented as follows:

//---------------------------------------------------------------------------

double Perimeter(

                      double (*Length1)(Point A, Point B),

                      double (*Length2)(Point B, Point C),

                      double (*Length3)(Point A, Point C),

                      Point First, Point Second, Point Third )

{

    double AB = (*Length1)(First, Second);

    double BC = (*Length2)(Second, Third);

    double AC = (*Length3)(First, Third);

    return AB + BC + AC;

}

//---------------------------------------------------------------------------

So far, the compiler does not know how the calculations are performed for the calls in the Perimeter() function. You let the compiler know this when calling the Perimeter() function. In this case, you provide the name of each function that performs the needed calculation. For our example, the name of the function is Distance. Therefore, you would pass it three times. In the function call, the name of the function is used because the actual argument is a pointer to function. The Perimeter() function can be tested as follows:

//---------------------------------------------------------------------------

#include <iostream>

#include <math.h>



using namespace std;





//---------------------------------------------------------------------------





namespace Geometry

{

struct TPoint

{

    double x;

    double y;

    TPoint(double X=0, double Y=0) : x(X), y(Y) {}

};

}

//---------------------------------------------------------------------------

double Distance(Geometry::Point Pt1, Geometry::Point Pt2)

{

    double L1 = pow(Pt2.x - Pt1.x, 2);

    double L2 = pow(Pt2.y - Pt1.y, 2);

    return sqrt(L1 + L2);

}

//---------------------------------------------------------------------------

using namespace Geometry;

double Perimeter(

                      double (*Length1)(Point A, Point B),

                      double (*Length2)(Point B, Point C),

                      double (*Length3)(Point A, Point C),

                      Point First, Point Second, Point Third )

{

    double AB = (*Length1)(First, Second);

    double BC = (*Length2)(Second, Third);

    double AC = (*Length3)(First, Third);

    return AB + BC + AC;

}

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    Geometry::Point Pt1(-3, -2);

    Geometry::Point Pt2(2, -2);

    Geometry::Point Pt3(4, 2);



    double Perim = Perimeter(Distance, Distance, Distance, Pt1, Pt2, Pt3);



    cout << "Perimeter: " << Perim << endl;



    

    

    return 0;

}

//---------------------------------------------------------------------------

This would produce:

Perimeter: 17.5344



Press any key to continue...

To simplify the declaration of the function that takes an argument as pointer to function, you can type define the argument using the typedef keyword. This can be done as follows:

//---------------------------------------------------------------------------

#include <iostream>

#include <math.h>



using namespace std;





//---------------------------------------------------------------------------





namespace Geometry

{

struct TPoint

{

    double x;

    double y;

    TPoint(double X=0, double Y=0) : x(X), y(Y) {}

};

}

//---------------------------------------------------------------------------

double Distance(Geometry::Point Pt1, Geometry::Point Pt2)

{

    double L1 = pow(Pt2.x - Pt1.x, 2);

    double L2 = pow(Pt2.y - Pt1.y, 2);

    return sqrt(L1 + L2);

}

//---------------------------------------------------------------------------

typedef double (*SideLength)(Geometry::Point First,

                                        Geometry::Point Second);

double Perimeter(SideLength, SideLength, SideLength,

                            Geometry::Point First, Geometry::Point Second,

                            Geometry::Point Third );

//---------------------------------------------------------------------------

using namespace Geometry;



int main(int argc, char* argv[])

{

    Point Pt1(-3, -2);

    Point Pt2( 2, -2);

    Point Pt3( 4,  2);



    double Perim = Perimeter(Distance, Distance, Distance, Pt1, Pt2, Pt3);



    cout << "Perimeter: " << Perim << endl;



    

    

    return 0;

}

//---------------------------------------------------------------------------

double Perimeter(

                      double (*Length1)(Point A, Point B),

                      double (*Length2)(Point B, Point C),

                      double (*Length3)(Point A, Point C),

                      Point First, Point Second, Point Third )

{

    double AB = (*Length1)(First, Second);

    double BC = (*Length2)(Second, Third);

    double AC = (*Length3)(First, Third);

    return AB + BC + AC;

}

//---------------------------------------------------------------------------

In this program, SideLength is another name for a pointer to function that takes two Point as arguments.

Based on this approach, you can also calculate the Perimeter of a rectangle or any quadratic or other geometric figure whose corners can be identified on a Cartesian coordinate system:

Classes and Self-Return

 

Self Returning an Object

The constructors are not the only member functions that can be declared with the name of the class. C++ allows you to manipulate the members of a class without using an external function. This technique uses a member function that can return the parent class. To have a function that can refer to the object itself, in the body of the class, declare a method that holds the same name as the class, followed by a valid name of a function and the parentheses. Here is an example:

//---------------------------------------------------------------------------

#ifndef FlatShapesH

#define FlatShapesH

//---------------------------------------------------------------------------

namespace FlatShapes

{

struct TTriangle

{

private:

    double TriBase;

    double TriHeight;

    double getBase() const;

    void   setBase(const double B);

    double getHeight() const;

    void   setHeight(const double H);

public:

    void   setDimensions(const double B, const double H);

    TTriangle();

    TTriangle(double B, double H);

    TTriangle(const TTriangle &Tri);

    ~TTriangle();

    double Area() const { return TriBase * TriHeight / 2; }



    TTriangle Additional();



    __property double Base = { read = getBase, write = setBase };

    __property double Height = { read = getHeight, write = setHeight };

};

}

//---------------------------------------------------------------------------

#endif

An example of using such a function would consist of changing the value of each member of the class. Since the function is declared as returning the value of the same variable, you can implement it as follows:

//---------------------------------------------------------------------------







#include "FlatShapes.h"

//---------------------------------------------------------------------------



//---------------------------------------------------------------------------

namespace FlatShapes

{

TTriangle::TTriangle()

    : TriBase(0.00), TriHeight(0.00)

{

}

//---------------------------------------------------------------------------

TTriangle::TTriangle(double B, double H)

    : TriBase(B), TriHeight(H)

{

}

//---------------------------------------------------------------------------

TTriangle::TTriangle(const TTriangle &Tri)

    : TriBase(Tri.TriBase), TriHeight(Tri.TriHeight)

{

}

//---------------------------------------------------------------------------

TTriangle::~TTriangle()

{

}

//---------------------------------------------------------------------------

double TTriangle::getBase() const

{

    return (TriBase < 0 ) ? 0 : TriBase;

}

//---------------------------------------------------------------------------

void   TTriangle::setBase(double B)

{

    TriBase = ( B < 0 ) ? 0 : B;

}

//---------------------------------------------------------------------------

double TTriangle::getHeight() const

{

    return (TriHeight < 0) ? 0 : TriHeight;

}

//---------------------------------------------------------------------------

void   TTriangle::setHeight(double H)

{

    TriHeight = (H < 0 ) ? 0 : H;

}

//---------------------------------------------------------------------------

void   TTriangle::setDimensions(const double B, const double H)

{

    setBase(B);

    setHeight(H);

}

//---------------------------------------------------------------------------

TTriangle TTriangle::Additional()

{

    TTriangle Tri;



    // The constant double values used here were randomly chosen

    Tri.TriBase   = TriBase + 12.52;

    Tri.TriHeight = TriHeight + 8.95;



    return Tri;

}

//---------------------------------------------------------------------------

}

Calling this self-returning function is equivalent to changing the values of the member variables, as illustrated when called in the main() function:

//---------------------------------------------------------------------------

#include <iostream>



 

#include "FlatShapes.h"



//---------------------------------------------------------------------------

using namespace std;

using namespace FlatShapes;



void ShowCharacteristics(const TTriangle*);

//---------------------------------------------------------------------------

int main(int argc, char* argv[])

{

    TTriangle Angular;



    Angular.setDimensions(22.44, 26.38);

    ShowCharacteristics(&Angular);



    cout << endl << endl;

    TTriangle Plane = Angular.Additional();

    ShowCharacteristics(&Plane);



    

    

    return 0;

}

//---------------------------------------------------------------------------

void ShowCharacteristics(const TTriangle* Tri)

{

    cout << " - Triangle Characteristics -";

    cout << "\nBase:   " << Tri->Base;

    cout << "\nHeight: " << Tri->Height;

    cout << "\nArea:   " << Tri->Area();

}

//---------------------------------------------------------------------------

You can use this ability to declare almost any type of function that returns the same class. One particular method can be used to modify the default values of the member variables. Another method can be used to convert the values of another class into those needed by the class.

The this Pointer

C++ proposes an alternative to returning an object from one of its member functions. Instead of explicitly declaring a variable when implementing a function that returns the same object, the compiler simply needs to know what object you want to return: the object that called the method or a newly declared one. If you want to return the same object, you can use a special pointer called this.

As its name implies, the this pointer is a self referencing object, which means it allows you to designate the object that is making the call as the same object you are referring to. Using the this pointer is a technique that allows you to perform any necessary operation on an object without the help of an external function and returning the same object.

Suppose you want to transform the Additional() method of the TTriangle object above so it would return the same variable that calls it. Because the method will return the same object, you do not need to declare a local variable to hold the changed variable. Since the member variables of the object will be modified, the member function cannot be declared as a constant.

When implementing this method, the values of the variables will certainly be modified to implement whatever behavior you want. To return the same object, the this object must be called as a pointer, with *this. Here is the new implementation of the function:

//---------------------------------------------------------------------------

TTriangle TTriangle::Additional()

{

    // The constant double values used here were randomly chosen

    Tri.TriBase   = TriBase + 12.52;

    Tri.TriHeight = TriHeight + 8.95;



    return *this;

}

//---------------------------------------------------------------------------

Using the same approach, you can define and use other methods that return the same object:

//---------------------------------------------------------------------------

#ifndef FlatShapesH

#define FlatShapesH

//---------------------------------------------------------------------------

namespace FlatShapes

{

struct TTriangle

{

private:

    double TriBase;

    double TriHeight;

    double getBase() const;

    void   setBase(const double B);

    double getHeight() const;

    void   setHeight(const double H);

public:

    void   setDimensions(const double B, const double H);

    TTriangle();

    TTriangle(double B, double H);

    TTriangle(const TTriangle &Tri);

    ~TTriangle();

    double Area() const { return TriBase * TriHeight / 2; }



    TTriangle Additional();

    TTriangle AddAConstant(const double d);

    TTriangle AddAnotherObject(const TTriangle& E);



    __property double Base = { read = getBase, write = setBase };

    __property double Height = { read = getHeight, write = setHeight };

};

}

//---------------------------------------------------------------------------

#endif

Such methods can be implemented to return the this pointer and the same object that made the call:

//---------------------------------------------------------------------------







#include "FlatShapes.h"

//---------------------------------------------------------------------------



//---------------------------------------------------------------------------

namespace FlatShapes

{

. . . No Change

//---------------------------------------------------------------------------

TTriangle TTriangle::Additional()

{

    // The constant double values used here were randomly chosen

    TriBase   = TriBase + 12.52;

    TriHeight = TriHeight + 8.95;



    return *this;

}



//---------------------------------------------------------------------------

TTriangle TTriangle::AddAConstant(const double d)

{

    TriBase   = TriBase + d;

    TriHeight = TriHeight + d;



    return *this;

}

//---------------------------------------------------------------------------

TTriangle TTriangle::AddAnotherObject(const TTriangle& E)

{

    TriBase   += E.TriBase;

    TriHeight += E.TriHeight;



    return *this;

}

//---------------------------------------------------------------------------

}

Since the function that returns the this pointer returns it as a pointer, you can reinforce the fact that the variable that is being returned is modified. Therefore, instead of returning the object as a regular variable, you should make the returned value a reference:

//---------------------------------------------------------------------------

#ifndef FlatShapesH

#define FlatShapesH

//---------------------------------------------------------------------------

namespace FlatShapes

{

struct TTriangle

{

private:

    double TriBase;

    double TriHeight;

    double getBase() const;

    void   setBase(const double B);

    double getHeight() const;

    void   setHeight(const double H);

public:

    void   setDimensions(const double B, const double H);

    TTriangle();

    TTriangle(double B, double H);

    TTriangle(const TTriangle &Tri);

    ~TTriangle();

    double Area() const { return TriBase * TriHeight / 2; }



    TTriangle& Additional();

    TTriangle& AddAConstant(const double d);

    TTriangle& AddAnotherObject(const TTriangle& E);



    __property double Base = { read = getBase, write = setBase };

    __property double Height = { read = getHeight, write = setHeight };

};

}

//---------------------------------------------------------------------------

#endif

In this case, remember to use the reference operator, the ampersand:
//---------------------------------------------------------------------------

TTriangle& TTriangle::Additional()

{

    // The constant double values used here were randomly chosen

    TriBase   = TriBase + 12.52;

    TriHeight = TriHeight + 8.95;



    return *this;

}



//---------------------------------------------------------------------------

TTriangle& TTriangle::AddAConstant(const double d)

{

	TriBase   = TriBase + d;

	TriHeight = TriHeight + d;



	return *this;

}

//---------------------------------------------------------------------------

TTriangle& TTriangle::AddAnotherObject(const TTriangle& E)

{

	TriBase   += E.TriBase;

	TriHeight += E.TriHeight;



	return *this;

}

//---------------------------------------------------------------------------
 

Previous Copyright 2005 FunctionX, Inc. Next