In an inheritance scenario, a parent class is configured to provide its child with the basic foundation the child needs. Although a child can implement a new behavior not available on the parent object, sometimes a child object will need a customized implementation of a behavior that has already been configured with its parent. That is what happens for example when inheriting a sphere from a circle class. Both have a characteristic called Area but the area is calculated differently on each shape.
|
Practical Learning: Reviewing Inheritance |
- Create a new C++ Console Application in a folder called Polymorpher
- Save the unit as Main and the project as Polymorph
- Add a new unit and save it as Circle
- Create a TCircle class in the Circle.h file as follows:
//---------------------------------------------------------------------------
#ifndef CircleH
#define CircleH
//---------------------------------------------------------------------------
class TCircle
{
public:
__fastcall TCircle(double r = 0.00);
__fastcall ~TCircle();
void __fastcall setRadius(const double r) { Radius = r; }
double __fastcall getRadius() const { return Radius; }
double __fastcall Area() const;
void __fastcall ShowCharacteristics() const;
protected:
double Radius;
};
//---------------------------------------------------------------------------
#endif
|
- Implement the TCircle class in the Circle.cpp file as follows:
//---------------------------------------------------------------------------
#include <iomanip.h>
#pragma hdrstop
#include "Circle.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
const double PI = 3.14159;
//---------------------------------------------------------------------------
__fastcall TCircle::TCircle(double r)
: Radius(r)
{
}
//---------------------------------------------------------------------------
__fastcall TCircle::~TCircle()
{
}
//---------------------------------------------------------------------------
double __fastcall TCircle::Area() const
{
return Radius * Radius * PI;
}
//---------------------------------------------------------------------------
void __fastcall TCircle::ShowCharacteristics() const
{
cout << "Circle Characteristics";
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nRadius: " << getRadius();
cout << "\nArea: " << Area();
}
//---------------------------------------------------------------------------
|
- Add a new unit and save it as Sphere
- To create a new TSphere class based on the TCircle class, lay its foundation in the Sphere.h file as follows:
//---------------------------------------------------------------------------
#ifndef SphereH
#define SphereH
#include "Circle.h"
//---------------------------------------------------------------------------
class TSphere : public TCircle
{
public:
__fastcall TSphere(double x);
__fastcall ~TSphere() {}
double __fastcall Area() const;
double __fastcall Volume() const;
void __fastcall ShowCharacteristics() const;
private:
};
//---------------------------------------------------------------------------
#endif
|
- Implement the new class as follows:
//---------------------------------------------------------------------------
#include <iomanip.h>
#pragma hdrstop
#include "Sphere.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
const double PI = 3.14159;
//---------------------------------------------------------------------------
__fastcall TSphere::TSphere(double n)
: TCircle(n)
{
}
//---------------------------------------------------------------------------
double __fastcall TSphere::Area() const
{
return 4 * PI * Radius * Radius;
}
//---------------------------------------------------------------------------
double __fastcall TSphere::Volume() const
{
return 4 * PI * Radius * Radius * Radius / 3;
}
//---------------------------------------------------------------------------
void __fastcall TSphere::ShowCharacteristics() const
{
cout << "Circle Characteristics";
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nRadius: " << getRadius();
cout << "\nArea: " << Area();
cout << "\nVolume: " << Volume();
}
//---------------------------------------------------------------------------
|
- To inherit another class, add a new unit and save it as Cylinder
- Create the TCylinder class based on TCircle in the Cylinder.h file as follows:
//---------------------------------------------------------------------------
#ifndef CylinderH
#define CylinderH
#include "Circle.h"
//---------------------------------------------------------------------------
class TCylinder : public TCircle
{
public:
__fastcall TCylinder(double r, double h);
__fastcall ~TCylinder() {}
void __fastcall setHeight(const double h) { Height = h; }
double __fastcall getHeight() const { return Height; }
double __fastcall BaseArea() const;
double __fastcall LateralArea() const;
double __fastcall Area() const;
double __fastcall Volume() const;
void __fastcall ShowCharacteristics() const;
private:
double Height;
};
//---------------------------------------------------------------------------
#endif
|
- Implement the new class in its source file as follows:
//---------------------------------------------------------------------------
#include <iomanip.h>
#pragma hdrstop
#include "Cylinder.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
const double PI = 3.14159;
//---------------------------------------------------------------------------
__fastcall TCylinder::TCylinder(double x, double y)
: TCircle(x), Height(y)
{
}
//---------------------------------------------------------------------------
double __fastcall TCylinder::BaseArea() const
{
return TCircle::Area();
}
//---------------------------------------------------------------------------
double __fastcall TCylinder::LateralArea() const
{
double Circ = Radius * 2 * PI;
return Circ * Height;
}
//---------------------------------------------------------------------------
double __fastcall TCylinder::Area() const
{
double Base = TCircle::Area();
double Lateral = LateralArea();
return (2 * Base) + Lateral;
}
//---------------------------------------------------------------------------
double __fastcall TCylinder::Volume() const
{
double Base = TCircle::Area();
return Base * Height;
}
//---------------------------------------------------------------------------
void __fastcall TCylinder::ShowCharacteristics() const
{
cout << "Circle Characteristics";
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nRadius: " << getRadius();
cout << "\nHeight: " << getHeight();
cout << "\nBase Area: " << BaseArea();
cout << "\nLateral Area: " << LateralArea();
cout << "\nArea: " << Area();
cout << "\nVolume: " << Volume();
}
//---------------------------------------------------------------------------
|
- To prepare a test, change the main() function in the Main.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Circle.h"
#include "Sphere.h"
#include "Cylinder.h"
#pragma argsused
int main(int argc, char* argv[])
{
TCircle e(12.55);
TSphere s(22.25);
TCylinder c(34.05, 28.35);
e.ShowCharacteristics();
cout << "\n\n";
s.ShowCharacteristics();
cout << "\n\n";
c.ShowCharacteristics();
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- To test the program, press F9.
- We can also dynamically declare the objects and access them as pointers. To see an example, change the main() function as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Circle.h"
#include "Sphere.h"
#include "Cylinder.h"
#pragma argsused
int main(int argc, char* argv[])
{
TCircle *e = new TCircle(12.55);
TSphere *s = new TSphere(16.15);
TCylinder *c = new TCylinder(14.25, 10.85);
e->ShowCharacteristics();
cout << "\n\n";
s->ShowCharacteristics();
cout << "\n\n";
c->ShowCharacteristics();
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- Test the program.
When studying inheritance, we learned that there is a special bond between an inherited class and its parent. Not only does the child object have access to the public members of a class but also the child, based on this relationship, has direct access to the members of the protected section (the child is a protégé) of the parent. The program above shows us that, in order to access a class’ members, we can just declare an instance of the class and use either the member access operator “.” or the pointer access operator “->”.
If there is such a good relationship between a class and its children, is it possible to access a member of a child object using an instance of the parent? We will declare only an instance of the parent class,
TCircle, and try to access its child using such a variable.
- Change the main() function as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Circle.h"
#include "Sphere.h"
#include "Cylinder.h"
#pragma argsused
int main(int argc, char* argv[])
{
TSphere *s = new TSphere(15.32);
TCylinder *c = new TCylinder(36.08, 20.24);
TCircle *e = s;
e->ShowCharacteristics();
cout << "\n\n";
e = c;
e->ShowCharacteristics();
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- Test the program.
Notice that, although the pointer variable *e has been initialized with variables *s and *c, only the characteristics of the circle are displayed.
A virtual function is a function that makes sure that, in an inheritance scenario, the right function is called regardless of the expression that calls the function. The last three classes we have used have two functions called Area() and
ShowCharacteristics() each. Let us consider the member function
ShowCharacteristics() that is present in all three classes. If we need an instance of the parent class to call the right member function, in this case Area, we must declare ShowCharacteristics () in the parent class as virtual.
|
Declaring a Virtual Function |
- Click the Circle.h tab and change the declaration of the Area() function as follows:
//---------------------------------------------------------------------------
#ifndef CircleH
#define CircleH
//---------------------------------------------------------------------------
class TCircle
{
public:
__fastcall TCircle(double r = 0.00);
__fastcall ~TCircle();
void __fastcall setRadius(const double r) { Radius = r; }
double __fastcall getRadius() const { return Radius; }
double __fastcall Area() const;
virtual void __fastcall ShowCharacteristics() const;
protected:
double Radius;
};
//---------------------------------------------------------------------------
#endif
|
- Test the program again. Notice that, this time, even though an instance of the TCircle class made the call, the characteristics of the assigned variables display.
- Return to Bcb.
- Since the Area() function is present on the parent as well as the inherited classes, make it virtual in the base class as follows:
//---------------------------------------------------------------------------
#ifndef CircleH
#define CircleH
//---------------------------------------------------------------------------
class TCircle
{
public:
__fastcall TCircle(double r = 0.00);
__fastcall ~TCircle();
void __fastcall setRadius(const double r) { Radius = r; }
double __fastcall getRadius() const { return Radius; }
virtual double __fastcall Area() const;
virtual void __fastcall ShowCharacteristics() const;
protected:
double Radius;
};
//---------------------------------------------------------------------------
#endif
|
- Test the program. Notice that the right functions, as far as their having been assigned to the base class instance goes, are called.
Even though you can use polymorphism to help the compiler identify which function is being called, if you want to access the same function of the base class, you can qualify it with the scope resolution operator “::”.
As you can see, a function with the same name can be declared in both a base and a derived classes. This allows a derived class to define its custom definition of the function. Because the derived class “re-writes” the same function, the derived class is said to override the function. A function can be overridden only if it is virtual. A function can be virtual only if carries the same name, the same return type, and the same
type(s) of argument(s) if any.
Consider a case where you have created a class that is derived from another class. When you dynamically call the inherited class using an instance of the base class (as done in the last example), during the closing of the program, the destructor of the child class is called first, followed by the destructor of the base class. If you dynamically declare an instance of the base class and then invoke the children of the class, you need to make sure that each destructor and the right destructor of the classes that was used is called to destroy the class; this is safe measure to avoid memory leak. This aspect of C++ programming is taken care of by declaring the destructor of the base class as virtual. Whenever the destructor of the parent class is declared virtual, the destructor of an inherited class is also virtual. This ensures that, the program closes, all of the destructors of the base class and its children that were used are called, providing a safe claim of the memory that was used.
To declare a destructor as virtual, type the virtual keyword on its left, in the body of the class. Here is an example:
//---------------------------------------------------------------------------
#ifndef CircleH
#define CircleH
//---------------------------------------------------------------------------
class TCircle
{
public:
__fastcall TCircle(double r = 0.00);
__fastcall virtual ~TCircle();
void __fastcall setRadius(const double r) { Radius = r; }
double __fastcall getRadius() const { return Radius; }
double __fastcall Area() const;
virtual void __fastcall ShowCharacteristics() const;
protected:
double Radius;
};
//---------------------------------------------------------------------------
#endif
|
An abstract class is a class whose role is only meant to lay a foundation for those classes that would need a common behavior or similar characteristics. Therefore, an abstract class is used only as a base class for inheritance. A class is made abstract by declaring at least one its member functions as a “pure” virtual function.
Only a virtual function can be made “pure”. The syntax of declaring a pure function is:
virtual ReturnType FunctionName() = 0;
|
The virtual keyword is required to make sure that a (any) child of this class can implement this function as it deems fit. The ReturnType is the data type that the function will return. The FunctionName is an appropriate name for the function. The = 0 is required. It lets the compiler know that this function is a pure virtual function.
When a function has been declared as pure virtual, you do not need to implement it. Instead, each inherited class of an abstract class must provide its own implementation of the pure member function.
When creating an abstract class, you can declare all of its member functions as pure. Here is an example:
//---------------------------------------------------------------------------
struct TTent
{
virtual int WhatIsTheCapacity() = 0;
virtual double TentArea() = 0;
virtual double TentVolume() = 0;
virtual char* TextureColor() = 0;
};
//---------------------------------------------------------------------------
|
In this case, the class does not need a source file or any implementation because none of its member functions will be implemented.
An abstract base class can also have a mix of pure and non-pure functions. Here is an example:
//---------------------------------------------------------------------------
struct TTent
{
virtual int WhatIsTheCapacity() = 0;
virtual double TentArea() = 0;
virtual double TentVolume() = 0;
virtual char* TextureColor() = 0; // Pure virtual function
virtual char* TextureName(); // Virtual function
int ShapeType(); // Regular member function
};
//---------------------------------------------------------------------------
|
In this case, as we have done with the other classes so far, you must implement the non-pure functions in the base and let the inherited classes implement their own version(s) of the pure virtual
function(s).
|
Implementing Abstract Classes |
- Create a new C++ Console Application and save it in a folder called Polygons
- Save the unit as Main and the project as RegPolygons
- Add a new unit and save it as Polygon
- On the Status Bar, click the Polygon.h tab and create a regular class as follows:
//---------------------------------------------------------------------------
#ifndef PolygonH
#define PolygonH
//---------------------------------------------------------------------------
namespace Shapes
{
class TPolygon
{
public:
__fastcall TPolygon(int s = 0, char *n = "", double r = 0.00);
virtual __fastcall ~TPolygon();
void __fastcall setNbrOfSides(const int n);
int __fastcall getNbrOfSides() const { return NbrOfSides; }
void __fastcall setPolyName(const char *n);
char * __fastcall getPolyName() const { return PolyName; }
void __fastcall setRadius(const double r);
double __fastcall getRadius() const { return Radius; }
double __fastcall CentralAngle() const;
double __fastcall InteriorAngle() const;
protected:
int NbrOfSides;
char *PolyName;
double Radius;
};
}
//---------------------------------------------------------------------------
#endif
|
- Click the Polygon.cpp tab and implement the member functions as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
#include "Polygon.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
namespace Shapes
{
__fastcall TPolygon::TPolygon(int s, char *n, double r)
{
//TODO: Add your source code here
NbrOfSides = s;
Radius = r;
setPolyName(n);
}
//---------------------------------------------------------------------------
__fastcall TPolygon::~TPolygon()
{
//TODO: Add your source code here
delete [] PolyName;
}
//---------------------------------------------------------------------------
void __fastcall TPolygon::setNbrOfSides(const int n)
{
// Avoid a negative number of sides. It wouldn't make sense.
// This program considers only regular polygons.
// The minimum number of sides is 3, which corresponds to an
// equilateral triangle
if( n < 3 )
NbrOfSides = 0;
else if( n < 4 )
NbrOfSides = 3; // Equilateral Triangle
else if( n == 4 )
NbrOfSides = 4; // Square
else if( n == 5 )
NbrOfSides = 5; // Pentagon
else if( n == 6 )
NbrOfSides = 6; // Hexagon
else if( n == 7 || n == 8 )
NbrOfSides = 8; // Octagon
else if( n == 9 || n == 10 )
NbrOfSides = 10;// Decagon
else //if( n == 11 || n == 12 )
NbrOfSides = 12;// Dodecagon
}
//---------------------------------------------------------------------------
void __fastcall TPolygon::setPolyName(const char * n)
{
//TODO: Add your source code here
PolyName = new char[strlen(n) + 1];
strcpy(PolyName, n);
}
//---------------------------------------------------------------------------
void __fastcall TPolygon::setRadius(const double r)
{
//Avoid a negative radius. It wouldn't make sense
if( r < 0 )
Radius = 0.00;
else
Radius = r;
}
//---------------------------------------------------------------------------
double __fastcall TPolygon::CentralAngle() const
{
return 360 / NbrOfSides;
}
//---------------------------------------------------------------------------
double __fastcall TPolygon::InteriorAngle() const
{
return (NbrOfSides - 2) * 180 / NbrOfSides;
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
|
- Prepare the main() function for a test as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <iomanip.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Polygon.h"
#pragma argsused
int main(int argc, char* argv[])
{
Shapes::TPolygon *Pol = new Shapes::TPolygon;
Pol->setRadius(15.55);
Pol->setNbrOfSides(25);
Pol->setPolyName("Unknown");
cout << "Shape Characteristics";
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nName: " << Pol->getPolyName();
cout << "\nSides: " << Pol->getNbrOfSides();
cout << "\nRadius: " << Pol->getRadius();
cout << "\nCentral Angle: " << Pol->CentralAngle() << "*";
cout << "\nInterior Angle: " << Pol->InteriorAngle();
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- Test the program. Return to Bcb.
- To add some pure virtual function and qualify the class as abstract, change the TPolygon class creation as follows:
//---------------------------------------------------------------------------
#ifndef PolygonH
#define PolygonH
//---------------------------------------------------------------------------
namespace Shapes
{
class TPolygon
{
public:
__fastcall TPolygon(int s = 0, char *n = "", double r = 0.00);
virtual __fastcall ~TPolygon();
void __fastcall setNbrOfSides(const int n);
int __fastcall getNbrOfSides() const { return NbrOfSides; }
void __fastcall setPolyName(const char *n);
char * __fastcall getPolyName() const { return PolyName; }
void __fastcall setRadius(const double r);
double __fastcall getRadius() const { return Radius; }
double __fastcall CentralAngle() const;
double __fastcall InteriorAngle() const;
virtual double __fastcall Side() = 0;
virtual double __fastcall Apothem() = 0;
virtual double __fastcall Perimeter() = 0;
double __fastcall Area();
virtual void __fastcall ShapeCharacteristics() = 0;
protected:
int NbrOfSides;
char *PolyName;
double Radius;
};
}
//---------------------------------------------------------------------------
#endif
|
- To implement the newly added Area() member function, add it to the Polygon.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
#include "Polygon.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
namespace Shapes
{
__fastcall TPolygon::TPolygon(int s, char *n, double r)
{
. . .
}
//---------------------------------------------------------------------------
. . .
//---------------------------------------------------------------------------
double __fastcall TPolygon::Area()
{
double p = Perimeter();
double r = Apothem();
return p * r / 2;
}
//---------------------------------------------------------------------------
}
//---------------------------------------------------------------------------
|
- To test the program, press F9.
- Read the errors that you receive. As you can see, you are not allowed to declare an instance of an abstract class:
- Return to Bcb.
- Add a new unit and save it as Triangle
- In the Triangle.h file, derive a new TTriangle class from the TPolygon class as follows:
//---------------------------------------------------------------------------
#ifndef TriangleH
#define TriangleH
#include "Polygon.h"
//---------------------------------------------------------------------------
class TTriangle : public Shapes::TPolygon
{
public:
// We use two constructors so that the user can initialize
// either by not specifying anything or by providing the radius
__fastcall TTriangle();
__fastcall TTriangle(double r);
__fastcall ~TTriangle();
virtual double __fastcall Side();
virtual double __fastcall Apothem();
virtual double __fastcall Perimeter();
double __fastcall Height();
virtual void __fastcall ShapeCharacteristics();
protected:
private:
};
//---------------------------------------------------------------------------
#endif
|
- Implement the class in the Triangle.cpp source file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
#pragma hdrstop
#include "Triangle.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
__fastcall TTriangle::TTriangle()
{
// If the constructor is empty, at least we know the number of sides
// and the name of this polygon
setNbrOfSides(3);
setPolyName("Equilateral Triangle");
setRadius(getRadius());
}
//---------------------------------------------------------------------------
__fastcall TTriangle::TTriangle(double r)
{
setNbrOfSides(3);
setPolyName("Equilateral Triangle");
setRadius(r);
}
//---------------------------------------------------------------------------
__fastcall TTriangle::~TTriangle()
{
}
//---------------------------------------------------------------------------
double __fastcall TTriangle::Side()
{
return Radius * sqrt(3);
}
//---------------------------------------------------------------------------
double __fastcall TTriangle::Apothem()
{
return Radius / 2;
}
//---------------------------------------------------------------------------
double __fastcall TTriangle::Perimeter()
{
double S = Side();
return 3 * S;
}
//---------------------------------------------------------------------------
double __fastcall TTriangle::Height()
{
return Apothem() + Radius;
}
//---------------------------------------------------------------------------
void __fastcall TTriangle::ShapeCharacteristics()
{
cout << "Shape Characteristics";
cout << "\nName: " << getPolyName();
cout << "\nSides: " << getNbrOfSides();
cout << "\nRadius: " << getRadius();
cout << "\nCentral Angle: " << CentralAngle();
cout << "\nInterior Angle: " << InteriorAngle();
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nApothem: " << Apothem();
cout << "\nHeight: " << Height();
cout << "\nPerimeter: " << Perimeter();
cout << "\nArea: " << Area();
}
//---------------------------------------------------------------------------
|
- To prepare a test, change the Main.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Triangle.h"
#pragma argsused
int main(int argc, char* argv[])
{
TTriangle *Tri = new TTriangle;
Tri->setRadius(15.55);
Tri->ShapeCharacteristics();
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- Test the program and return to Bcb.
- Add a new unit and save it as Square
- In the Square.h file, create a regular polygon of a square based on the TPolygon class as follows:
//---------------------------------------------------------------------------
#ifndef SquareH
#define SquareH
#include "Polygon.h"
//---------------------------------------------------------------------------
class TPolySquare : public Shapes::TPolygon
{
public:
__fastcall TPolySquare();
__fastcall TPolySquare(double r);
__fastcall ~TPolySquare() {}
virtual double __fastcall Side();
virtual double __fastcall Apothem();
virtual double __fastcall Perimeter();
virtual void __fastcall ShapeCharacteristics();
protected:
private:
};
//---------------------------------------------------------------------------
#endif
|
- Implement the TPolySquare class as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Square.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
__fastcall TPolySquare::TPolySquare()
{
// If the constructor is empty, at least we know the number of sides
// and the name of this polygon
setNbrOfSides(4);
setPolyName("Square");
setRadius(getRadius());
}
//---------------------------------------------------------------------------
__fastcall TPolySquare::TPolySquare(double r)
{
setNbrOfSides(4);
setPolyName("Square");
setRadius(r);
}
//---------------------------------------------------------------------------
double __fastcall TPolySquare::Side()
{
return Radius * sqrt(2);
}
//---------------------------------------------------------------------------
double __fastcall TPolySquare::Apothem()
{
return sqrt(2) * Radius / 2;
}
//---------------------------------------------------------------------------
double __fastcall TPolySquare::Perimeter()
{
double S = Side();
return 4 * S;
}
//---------------------------------------------------------------------------
void __fastcall TPolySquare::ShapeCharacteristics()
{
cout << "Shape Characteristics";
cout << "\nName: " << getPolyName();
cout << "\nSides: " << getNbrOfSides();
cout << "\nRadius: " << getRadius();
cout << setiosflags(ios::fixed) << setprecision(0);
cout << "\nCentral Angle: " << CentralAngle();
cout << "\nInterior Angle: " << InteriorAngle();
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nApothem: " << Apothem();
cout << "\nPerimeter: " << Perimeter();
cout << "\nArea: " << Area();
}
//---------------------------------------------------------------------------
|
- Add a new unit and save it as Hexagon
- Create a THexagon object derived from the TPolygon class as follows:
//---------------------------------------------------------------------------
#ifndef HexagonH
#define HexagonH
#include "Polygon.h"
//---------------------------------------------------------------------------
class THexagon : public Shapes::TPolygon
{
public:
__fastcall THexagon();
__fastcall THexagon(double r);
__fastcall ~THexagon() {}
virtual double __fastcall Side();
virtual double __fastcall Apothem();
virtual double __fastcall Perimeter();
virtual void __fastcall ShapeCharacteristics();
protected:
private:
};
//---------------------------------------------------------------------------
#endif
|
- Implement the THexagon class as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <iomanip.h>
#include <math.h>
#pragma hdrstop
#include "Hexagon.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
__fastcall THexagon::THexagon()
{
// If the constructor is empty, at least we know the number of sides
// and the name of this polygon
setNbrOfSides(6);
setPolyName("Hexagon");
setRadius(getRadius());
}
//---------------------------------------------------------------------------
__fastcall THexagon::THexagon(double r)
{
setNbrOfSides(6);
setPolyName("Hexagon");
setRadius(r);
}
//---------------------------------------------------------------------------
double __fastcall THexagon::Side()
{
return Radius;
}
//---------------------------------------------------------------------------
double __fastcall THexagon::Apothem()
{
return sqrt(3) * Radius / 2;
}
//---------------------------------------------------------------------------
double __fastcall THexagon::Perimeter()
{
double S = Side();
return 6 * S;
}
//---------------------------------------------------------------------------
void __fastcall THexagon::ShapeCharacteristics()
{
cout << "Shape Characteristics";
cout << "\nName: " << getPolyName();
cout << "\nSides: " << getNbrOfSides();
cout << "\nRadius: " << getRadius();
cout << setiosflags(ios::fixed) << setprecision(0);
cout << "\nCentral Angle: " << CentralAngle();
cout << "\nInterior Angle: " << InteriorAngle();
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\nApothem: " << Apothem();
cout << "\nPerimeter: " << Perimeter();
cout << "\nArea: " << Area();
}
//---------------------------------------------------------------------------
|
- Before testing the program, change the Main.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Triangle.h"
#include "Square.h"
#include "Hexagon.h"
#pragma argsused
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
TTriangle *Tri = new TTriangle;
Tri->setRadius(15.55);
Tri->ShapeCharacteristics();
cout << "\n\nNew Shape";
getchar();
clrscr();
TPolySquare *Sq = new TPolySquare(22.36);
Sq->ShapeCharacteristics();
cout << "\n\nNew Shape";
getchar();
clrscr();
THexagon *Hx = new THexagon;
Hx->setRadius(36.08);
Hx->ShapeCharacteristics();
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- Test the program and return to Bcb
We have learned to create special functions used to read and write data, namely get and set member functions. C++ Builder objects make a specific use of these methods. While their role is still meant to control data values or data display, they can be hidden because the clients of a class do not actually need them.
Customizing a Class’ Interface |
|
When the clients of a class provide (or pass) data to an object, the object has the responsibility of checking that the supplied data holds the appropriate format. If the value provided is not valid, the class must act accordingly. That is how we have used the set member functions so far. This means that, sometimes, the clients of a class do not need to communicate with set member functions. Based on this, you can place the
set(s) functions in the private or protected section. Now, if you do that, you still need to provide accessibility to the outside objects; the clients of an object still need to be able either to initialize an object or to pass values to it. You can use the constructor (or one of the constructors) of an object to initialize it. At times, you will want to pass only one value of of the member variables to a class, instead of initializing the whole class. C++ Builder solves this problem by allowing you to create a special field referred to as a property. A property is a special member of a class that can take a value from “outside”, pass it to the class, and let the class verify the validity of the passed value.
To create a property for a class, use the __property keyword. Here is the syntax:
__property DataType Name;
|
The __property keyword is required; it lets the compiler know that you are creating a property. The DataType must be a valid data type. The Name must be a valid C++ name.
To procide an “interface”, which is the means by which outside objects can communicate with the class, you will sometimes create a property for each member variable; this is because each member variable will still be kept in the private or protected section of the class. Therefore, the data type must be the same as the member variable whose property you are creating. The name of the property should reflected the member variable whose interface it is providing. Based on the name of the variable, you can just add a prefix letter or a few letters. Here is an example of a property:
//---------------------------------------------------------------------------
class TRectangle
{
public:
__fastcall TRectangle(double L = 0.00, double W = 0.00);
virtual __fastcall ~TRectangle();
void __fastcall setLength(const double L);
double __fastcall getLength() const { return Length; }
void __fastcall setWidth(const double W);
double __fastcall getWidth() const { return Width; }
double __fastcall Perimeter() const;
virtual double __fastcall Area() const;
__property double RectLength;
__property double RectWidth;
protected:
double Length;
double Width;
private:
};
//---------------------------------------------------------------------------
|
Since the property is used to communicate with the clients of a class, you do not need the set functions in the public section. You can therefore place the set functions in the protected or private section.
Just as a property takes from ourside of its object, it can also be used to display the data. Following the scenario we described earlier (the property takes data from outside and hands it to the class, the class analyzes and validates it), if the data is valid, it is passed back to the property so that if the client that passed the data or any other client is interested, the property can make that data available. Based on this, you do not need to use the get member functions to display data. Therefore, the get methods also should be placed in the private or protected(for inheritance purposes) section. The TRectangle class above could be restructed as follows:
//---------------------------------------------------------------------------
class TRectangle
{
public:
__fastcall TRectangle(double L = 0.00, double W = 0.00);
virtual __fastcall ~TRectangle();
double __fastcall Perimeter() const;
virtual double __fastcall Area() const;
__property double RectLength;
__property double RectWidth;
protected:
void __fastcall setLength(const double L);
double __fastcall getLength() const { return Length; }
void __fastcall setWidth(const double W);
double __fastcall getWidth() const { return Width; }
double Length;
double Width;
private:
};
//---------------------------------------------------------------------------
|
Now that the interface functions are hidden in the protected (or private) section, you should let the property know “who” (that is, what member function) will be responsible for analyzing the received data. To analyze data that is received from outside, we have seen that we should use a set function. To specify the member function responsible for analyzing data, C++ Builder allows you to assign the set function to a keyword called write. When the set function is assigned as such, you must omit the parentheses that we are used to adding to functions. To start, add the assignment operator to the right of the property name and open a parenthesis. Type write = followed by the name of the set function. Here are two examples:
//---------------------------------------------------------------------------
class TRectangle
{
public:
__fastcall TRectangle(double L = 0.00, double W = 0.00);
virtual __fastcall ~TRectangle();
double __fastcall Perimeter() const;
virtual double __fastcall Area() const;
__property double RectLength = { write = setLength };
__property double RectWidth = { write = setWidth };
protected:
void __fastcall setLength(const double L);
double __fastcall getLength() const { return Length; }
void __fastcall setWidth(const double W);
double __fastcall getWidth() const { return Width; }
double Length;
double Width;
private:
};
//---------------------------------------------------------------------------
|
After data has been analyzed and dealt with, it is passed back to the property that received it. The property then makes it available to the other members (such as methods) of the class that would need it. On the other hand, the property can be used to display that data on the console (when you learn how to create component, you will find out that the proprrty is used to help design a control using this same mechanism). Therefore, you should also let the property know what method will supply the validated data for display. We learned that get methods are used to display data or at least make it available to the outside world. To implement this functionality, C++ Builder provides a keyword called read. As done with the write keyword, you will assign the get function without the parenthese to the read keyword. To separate both assignment, type a comma between them. By convention, the read keyword starts, followed by the write keyword. The abovclass would be created as follows:
//---------------------------------------------------------------------------
namespace Shapes
{
class TRectangle
{
public:
__fastcall TRectangle(double L = 0.00, double W = 0.00);
virtual __fastcall ~TRectangle();
double __fastcall Perimeter() const;
virtual double __fastcall Area() const;
__property double RectLength = { read = getLength, write = setLength };
__property double RectWidth = { read = getWidth, write = setWidth };
protected:
void __fastcall setLength(const double L);
double __fastcall getLength() const { return Length; }
void __fastcall setWidth(const double W);
double __fastcall getWidth() const { return Width; }
double Length;
double Width;
private:
};
} // End of namespace Shapes
//---------------------------------------------------------------------------
#endif
|
Here is the implementation of the class. Notice that there is nothing done about the properties. The constructor(s), the destructor, and interface functions are responsible for analyzing data, validating it, and making it available to the member functions that need it;
//---------------------------------------------------------------------------
#pragma hdrstop
#include "Rectangle.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
namespace Shapes
{
__fastcall TRectangle::TRectangle(double L, double W)
: Length(L), Width(W)
{
}
//---------------------------------------------------------------------------
__fastcall TRectangle::~TRectangle()
{
}
//---------------------------------------------------------------------------
void __fastcall TRectangle::setLength(const double L)
{
// Avoid a negative length
Length = (L < 0) ? 0 : L;
}
//---------------------------------------------------------------------------
void __fastcall TRectangle::setWidth(const double W)
{
// Avoid a negative width
Width = (W < 0) ? 0 : W;
}
//---------------------------------------------------------------------------
double __fastcall TRectangle::Perimeter() const
{
return 2 * (Length + Width);
}
//---------------------------------------------------------------------------
double __fastcall TRectangle::Area() const
{
return Length * Width;
}
// End of namespace Shapes
}
//---------------------------------------------------------------------------
|
Here is a test of the class from the main() function:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Rectangle.h"
#pragma argsused
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
Shapes::TRectangle *R = new Shapes::TRectangle;
R->RectLength = -12.55;
R->RectWidth = 10.85;
cout << "Rectangle Characteristics";
cout << "\nLength: " << R->RectLength;
cout << "\nWidth: " << R->RectWidth;
cout << "\nPerimeter: " << R->Perimeter();
cout << "\nArea: " << R->Area();
delete R;
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
When running this program, notice that the RectLength property is passes a negative value but when it is called back by the function that object, (R), that sent the negative value, the values comes back as 0, as analyzed by the setLength() member function.
|
Introduction to C++ Builder Objects |
- Start a new C++ Console Application using the Console Wizard.
- Save the application in a folder called ClothingStore
- Save the unit as Main and save the project as Store
- Add a new unit and save it as Order
- Click the Order.h tab and create a new TOrder object as follows:
//---------------------------------------------------------------------------
#ifndef OrderH
#define OrderH
//---------------------------------------------------------------------------
class TOrder
{
public:
__fastcall TOrder(char *n = "");
virtual __fastcall ~TOrder();
void __fastcall setItemName(const char* n);
char* __fastcall getItemName() const;
void __fastcall setItemPrice(const double p);
double __fastcall getItemPrice() const;
private:
char *ItemName;
double ItemPrice;
};
//---------------------------------------------------------------------------
#endif
|
- Implement the TOrder class in the Order.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#pragma hdrstop
#include "Order.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
//---------------------------------------------------------------------------
__fastcall TOrder::TOrder(char *n)
{
setItemName(n);
ItemPrice = 0.00;
}
//---------------------------------------------------------------------------
__fastcall TOrder::~TOrder()
{
delete [] ItemName;
}
//---------------------------------------------------------------------------
void __fastcall TOrder::setItemName(const char* n)
{
ItemName = new char[strlen(n) + 1];
strcpy(ItemName, n);
}
//---------------------------------------------------------------------------
char* __fastcall TOrder::getItemName() const
{
return ItemName;
}
//---------------------------------------------------------------------------
void __fastcall TOrder::setItemPrice(const double p)
{
// If the clerk had typed a negative price,
// change it to positive (maybe that was a mistake)
ItemPrice = (p < 0) ? (-p) : p;
}
//---------------------------------------------------------------------------
double __fastcall TOrder::getItemPrice() const
{
return ItemPrice;
}
//---------------------------------------------------------------------------
|
- To prepare a test, change the Main.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Order.h"
#pragma argsused
//---------------------------------------------------------------------------
void __fastcall ProcessOrder(TOrder& o);
void __fastcall DisplayReceipt(const TOrder& e);
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
TOrder Mine;
ProcessOrder(Mine);
clrscr();
DisplayReceipt(Mine);
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
void __fastcall ProcessOrder(TOrder& One)
{
char Name[40];
double Price;
cout << "Item name: ";
gets(Name);
cout << "Marked Price: $";
cin >> Price;
One.setItemName(Name);
One.setItemPrice(Price);
}
//---------------------------------------------------------------------------
void __fastcall DisplayReceipt(const TOrder& g)
{
cout << " ----- Receipt -----";
cout << "\n===================================";
cout << "\n Item Name: " << g.getItemName();
cout << "\n Marked Price: $" << g.getItemPrice();
cout << "\n===================================";
}
//---------------------------------------------------------------------------
|
- Test the program and return to Bcb.
- To declare and call the object as a C++ Builder class, change the main() function as follows:
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
TOrder *Mine = new TOrder;
ProcessOrder(*Mine);
clrscr();
DisplayReceipt(*Mine);
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
|
- Test the program and return to Bcb.
- To add a property for the ItemName member variable, change the TOrder class as follows:
//---------------------------------------------------------------------------
#ifndef OrderH
#define OrderH
//---------------------------------------------------------------------------
class TOrder
{
public:
__fastcall TOrder(char *n = "");
virtual __fastcall ~TOrder();
void __fastcall setItemPrice(const double p);
double __fastcall getItemPrice() const;
__property char *Name = { read=getItemName, write=setItemName };
private:
void __fastcall setItemName(const char* n);
char* __fastcall getItemName() const;
char *ItemName;
double ItemPrice;
};
//---------------------------------------------------------------------------
#endif
|
- To add a property for the ItemPrice member variable, in the ClassExplorer, right-click TOrder and click New Property…:
- In the Property Name text box of the Add Property dialog box, type Price
- Set the data Type to double and make sure that the Visibility is public
- Click the arrow of the Reads combo box and select getItemPrice. Set the Writes combo box to setItemPrice:
- Click OK.
- Move the setItemPrice() and the getItemPrice() methods to the private section of the TOrder class:
//---------------------------------------------------------------------------
#ifndef OrderH
#define OrderH
//---------------------------------------------------------------------------
class TOrder
{
public:
__fastcall TOrder(char *n = "");
virtual __fastcall ~TOrder();
__property char *Name = { read=getItemName, write=setItemName };
__property double Price = { read=getItemPrice, write=setItemPrice };
private:
void __fastcall setItemName(const char* n);
char* __fastcall getItemName() const;
void __fastcall setItemPrice(const double p);
double __fastcall getItemPrice() const;
char *ItemName;
double ItemPrice;
};
//---------------------------------------------------------------------------
#endif
|
- To prepare the program for a test, change the ProcessOrder() function as follows:
//---------------------------------------------------------------------------
void __fastcall ProcessOrder(TOrder& One)
{
char Name[40];
double Price;
cout << "Item name: ";
gets(Name);
cout << "Marked Price: $";
cin >> Price;
strcpy(One.Name, Name);
One.Price = Price;
}
//---------------------------------------------------------------------------
|
- Test the program
- To create a new property, right-click anywhere in the Class Explorer and click New Property…
- In the Property Name of the Add Property dialog, type Discount and, in the Add To Class combo box, select TOrder (otherwise, it
should be selected already.
- From the Type combo box, select double and make sure the Visibility is public.
- Click the Create Get Method check box and accept the suggested name.
- Click the Create Set Method check box and accept the suggested name.
- Click the Create Field check box and accept the suggested name for a new field:
- Click OK.
- Right-click in the Class Explorer and click New Field…
- In the Field Name edit box of the Add Fied dialog box, type Tax and make sure that TOrder is selected in Add To Class combo box. Set the data Type to double and click the private radio button:
- Click OK
- To create a new property, right-click TOrder in the Class Explorer and click New Property…
- Set the Property Name to TaxRate and make sure that the TOrder is selected as the class. Set the Type to double. And make sure that the visibility public. Click the Create Get Method and the Create Set Method check boxes. Accept the suggested names of the Reads and Writes.
- Click the Implement Get Using Member check box which also enables its combo box. Click the arrow of the Implement Get Using Member combo box and select Tax.
- Click the Implement Set Using Member check box. In the Implement Set Using Member combo box, select Tax:
- Click OK:
//---------------------------------------------------------------------------
#ifndef OrderH
#define OrderH
//---------------------------------------------------------------------------
class TOrder
{
public:
__fastcall TOrder(char *n = "");
virtual __fastcall ~TOrder();
__property char *Name = { read=getItemName, write=setItemName };
__property double Price = { read=getItemPrice, write=setItemPrice };
__property double Discount = { read=GetDiscount, write=SetDiscount };
__property double TaxRate = { read=GetTaxRate, write=SetTaxRate };
private:
char *ItemName;
double ItemPrice;
double FDiscount;
double Tax;
void __fastcall setItemName(const char* n);
char* __fastcall getItemName() const;
void __fastcall setItemPrice(const double p);
double __fastcall getItemPrice() const;
void __fastcall SetDiscount(double value);
double __fastcall GetDiscount();
void __fastcall SetTaxRate(double value);
double __fastcall GetTaxRate();
};
//---------------------------------------------------------------------------
#endif
|
- Implement the newly added methods as follows:
//---------------------------------------------------------------------------
void __fastcall TOrder::SetDiscount(double value)
{
//TODO: Add your source code here
// Avoid negative values for a discount
// The minimum discount is 0 which means no discount appies
FDiscount = (value <= 0) ? 0 : value;
}
//---------------------------------------------------------------------------
double __fastcall TOrder::GetDiscount()
{
//TODO: Add your source code here
return FDiscount;
}
//---------------------------------------------------------------------------
void __fastcall TOrder::SetTaxRate(double value)
{
if(Tax < 5.75)
{
Tax = 5.75;
}
else
Tax = value;
}
//---------------------------------------------------------------------------
double __fastcall TOrder::GetTaxRate()
{
return Tax;
}
//---------------------------------------------------------------------------
|
- We need to implement a complete order processing.
In public section of the TOrder class, add the following methods:
//---------------------------------------------------------------------------
#ifndef OrderH
#define OrderH
//---------------------------------------------------------------------------
class TOrder
{
public:
__fastcall TOrder(char *n = "");
virtual __fastcall ~TOrder();
__property char *Name = { read=getItemName, write=setItemName };
__property double Price = { read=getItemPrice, write=setItemPrice };
__property double Discount = { read=GetDiscount, write=SetDiscount };
__property double TaxRate = { read=GetTaxRate, write=SetTaxRate };
double __fastcall DiscountAmount() const;
double __fastcall PriceAfterDiscount() const;
double __fastcall TaxAmount() const;
double __fastcall NetPrice() const;
private:
char *ItemName;
double ItemPrice;
double FDiscount;
double Tax;
void __fastcall setItemName(const char* n);
char* __fastcall getItemName() const;
void __fastcall setItemPrice(const double p);
double __fastcall getItemPrice() const;
void __fastcall SetDiscount(double value);
double __fastcall GetDiscount();
void __fastcall SetTaxRate(double value);
double __fastcall GetTaxRate();
};
//---------------------------------------------------------------------------
#endif
|
- In the Order.cpp file, implement the new method as follows:
//---------------------------------------------------------------------------
double __fastcall TOrder::DiscountAmount() const
{
//TODO: Add your source code here
return ItemPrice * FDiscount / 100;
}
//---------------------------------------------------------------------------
double __fastcall TOrder::PriceAfterDiscount() const
{
//TODO: Add your source code here
return ItemPrice - DiscountAmount();
}
//---------------------------------------------------------------------------
double __fastcall TOrder::TaxAmount() const
{
//TODO: Add your source code here
return PriceAfterDiscount() * Tax / 100;
}
//---------------------------------------------------------------------------
double __fastcall TOrder::NetPrice() const
{
//TODO: Add your source code here
return PriceAfterDiscount() + TaxAmount();
}
//---------------------------------------------------------------------------
|
- To prepare a test, set the Main.cpp file as follows:
//---------------------------------------------------------------------------
#include <iostream.h>
#include <iomanip.h>
#include <conio.h>
#pragma hdrstop
//---------------------------------------------------------------------------
#include "Order.h"
#pragma argsused
//---------------------------------------------------------------------------
void __fastcall ProcessOrder(TOrder& o);
void __fastcall DisplayReceipt(TOrder e);
//---------------------------------------------------------------------------
int main(int argc, char* argv[])
{
TOrder *Mine = new TOrder;
ProcessOrder(*Mine);
clrscr();
DisplayReceipt(*Mine);
cout << "\n\nPress any key to continue...";
getchar();
return 0;
}
//---------------------------------------------------------------------------
void __fastcall ProcessOrder(TOrder& One)
{
char Name[40];
double Price, Disc, TxRate;
cout << "Item name: ";
gets(Name);
cout << "Marked Price: $";
cin >> Price;
cout << "Discount Rate (if none, type 0): ";
cin >> Disc;
cout << "Tax Rate: ";
cin >> TxRate;
strcpy(One.Name, Name);
One.Price = Price;
One.Discount = Disc;
One.TaxRate = TxRate;
}
//---------------------------------------------------------------------------
void __fastcall DisplayReceipt(TOrder g)
{
cout << " ----- Receipt -----";
cout << "\n===================================";
cout << "\n Item Name: " << g.Name;
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\n Marked Price: $" << g.Price;
cout << setiosflags(ios::fixed) << setprecision(0);
cout << "\n Discount Rate: " << g.Discount << "%";
cout << setiosflags(ios::fixed) << setprecision(2);
cout << "\n Discount Amt: " << g.DiscountAmount();
cout << "\n After Discount: $" << g.PriceAfterDiscount();
cout << "\n Tax Rate: " << g.TaxRate << "%";
cout << "\n Tax Amount: $" << g.TaxAmount();
cout << "\n New Price: $" << g.NetPrice();
cout << "\n===================================";
}
//---------------------------------------------------------------------------
|
- Test the program and return to Bcb
|
|