http://www.sysexpand.com/  

SysExpand .NET Code Base

Lessons on OOP > Lesson 1 - From Structure to Class

Lesson 1 - From Structure to Class

In this lesson we will start developing an object-oriented code through which we plan to describe all major object-oriented concepts.

Problem on which we will demonstrate programming techniques is quite simple. Suppose that we wish to describe 2D geometric figures: circles, rectangles, triangles, ellipses, polygons, etc. More specific requests will be added as we progress through the lessons. At this stage we will only say that each shape is defined by its location and other parameters which are shape-specific (e.g. radius for circle, width and height for rectangle, etc.). In addition, every shape will be given an optional user-friendly name. This list of requirements is sufficient for us to start coding.

Background in Structures

Historically, objects did not come out of nowhere. They have evolved from structures, or data records if you wish. We will start developing our 2D geometry code by by first defining a shape, as a yet unknown geometric figure which only has its location and given name exposed. All other features will come later. As of this moment, we are only interested in knowing a point (x, y) which defines position of the shape in two-dimensional Cartesian space, as well as an array of characters which contains shape's user-friendly name. Actual meaning of the "location" will be deferred to particular, specialized shapes. For circle and ellipse, center will be the location; for rectangle, its lower left corner will be the location. These details are of no importance at this stage.

Here is the structure in C programming language which defines shape as described above:

struct Shape
{
    char *name;
    float locationX;
    float locationY;
};

This piece of code declares a record containing two floating point values for coordinates and pointer to characters for shape's name. Name is appointed in form of an array of characters, which is recorded on the heap, i.e. outside the Shape structure itself, with only pointer kept in the Shape record. The following picture shows two instances of the Shape structure allocated dynamically on heap and pointed to by two local pointer variables. Each of the Shape structures also points to the array of characters allocated on heap to contain shape's name.

Structures layout

Adding Functions to Operate on Structure

Now that we have a structure defined, we will write a couple of functions to make it operational. But before defining the functions, we will first mention several coding conventions which we will follow. It would be worth the effort to follow logic of object-oriented programming, since we are on the course of writing object-oriented code. First convention will be writing a so-called constructor: A function invoked when new instance is created to initialize its contents. Multiple constructors with different sets of arguments can exist, for user's convenience. Second convention regards to function called destructor, which releases resources occupied by the instance; it is called just before the instance itself is released. Finally, we will enforce all functions operating on the Shape structure to receive pointer to the structure as their first argument and every function will operate on that instance passed to it. Conventionally, such pointer will be called "this" pointer, in order to emphasize its role.

We are ready to declare the desired functions. Note that all functions will have Shape_ prefix added in order to prevent name collisions in the future.

/* Listing of shape.h */
struct Shape
{
    char *name;
    float locationX;
    float locationY;
};

void Shape_Constructor(struct Shape *_this);
void Shape_Constructor1(struct Shape *_this, float locationX, float locationY);
void Shape_SetName(struct Shape *_this, const char *name);
void Shape_PrintOut(struct Shape *_this);
void Shape_Destructor(struct Shape *_this);

Implementations of these functions are quite straightforward. We will place them in shape.c file.

/* Listing of shape.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "shape.h"

void Shape_Constructor(struct Shape *_this)
{
    _this->locationX = 0.0F;
    _this->locationY = 0.0F;
    _this->name = keyword=NULL;
}

void Shape_Constructor1(struct Shape *_this, float locationX, float locationY)
{
    _this->locationX = locationX;
    _this->locationY = locationY;
    _this->name = NULL;
}

void Shape_SetName(struct Shape *_this, const char *name)
{
    if (_this->name != NULL)
        free(_this->name);
    _this->name = (char*)calloc(strlen(name) + 1, sizeof(char));
    strcpy(_this->name, name);
}

void Shape_PrintOut(struct Shape *_this)
{
    if (_this->name != NULL)
        printf("%s", _this->name);
    else
        printf("<null>");
    printf("'s location is (%.2f, %.2f).\n", _this->locationX, _this->locationY);
}

void Shape_Destructor(struct Shape *_this)
{
    if (_this->name != NULL)
        free(_this->name);
}

Note that functions above are testing whether name pointer is null before accessing it. Note also that none of the functions is testing _this pointer before accessing it. This may look strange, but it has been done as a convention. We are on the path of object-oriented programming, in which this pointer is guaranteed to be non-null, or otherwise function could not be called in the first place. More on this topic will be said later. At this point it is safe to assume that _this pointer, which is our custom implementation of C++ this pointer, is always valid, i.e. non-null.

We are now ready to demonstrate use of the Shape structure.

/* Listing of main.c */
#include <stdlib.h>
#include "shape.h"

int main(char args[])
{

    struct Shape *shape1 = NULL;
    struct Shape *shape2 = NULL;

    shape1 = (struct Shape*)malloc(sizeof(struct Shape));
    Shape_Constructor1(shape1, 1.0F, 2.0F);
    Shape_SetName(shape1, "Shape1");
    Shape_PrintOut(shape1);

    shape2 = (struct Shape*)malloc(sizeof(struct Shape));
    Shape_Constructor1(shape2, 2.0F, 3.0F);
    Shape_SetName(shape2, "Shape2");
    Shape_PrintOut(shape2);

    Shape_Destructor(shape1);
    free(shape1);

    Shape_Destructor(shape2);
    free(shape2);

}

This code produces output:

Shape1's location is (1.00, 2.00).
Shape2's location is (2.00, 3.00).

Observe how clean and simple this code is. Reusability of the functions has been employed in its finest. Even more important, which cannot be fully recognized at this stage, is the fact that logic has been removed from the code which calls it. For instance, we are not setting locationX and locationY of the shape directly - it is now done by the constructor in a way which is suddenly becoming an operation of no special interest to us (as long as we are happy with how the constructor does it). Even better illustration is setting the shape's name, as it involves testing and reallocating dynamic memory. Setting the name via the appropriate method, instead of directly manipulating the name pointer from the structure is always the preferred way, even when we are not keen to become object-oriented.

Upgrading Structure to Class

Object-oriented programming boils down to merging structure and functions operating on that structure into one entity - a class. In its roots, class is a structure with functions (now called methods). Off course, there is more than that, but at this point it is sufficient to understand a class as a structure with functions.

In our shapes example this means that Shape structure will now be enriched with constructors, destructor, SetName and PrintOut methods. The way in which it is done in C++ programming language is straight-forward. In the fresh C++ project we will first declare the class in a header file. For convenience, we will name the header file shape.hpp so to avoid confusion with its C ancestor shape.h (in general, extension .h is also used in C++).

// Listing of shape.hpp
class Shape
{
public:
    Shape();
    Shape(float locationX, float locationY);
    ~Shape();
    void SetName(const char *name);
    void PrintOut();
private:
    float locationX;
    float locationY;
    char *name;
};

Note how similar declaration of the Shape class is to the contents of shape.h file in C. This class consists of three private fields (we will discuss private and public keywords when time comes) - same fields as those in the structure. It also exposes public methods (public means that methods can be called from outside the class), which are one-to-one the same as functions declared in C code. The only difference is that constructors are now named the same as the class, and so is the destructor, only prefixed by the tilde (~) sign. This is by convention and will always be so C++.

Now comes the implementation of Shape's methods, which is provided in the shape.cpp file.

// Listing of shape.cpp
#include <iostream>
#include #include <stdlib.h>
#include #include <string.h>
#include "shape.hpp"

using namespace std;

Shape::Shape() // This is the Shape_Constructor function with implicit this argument
{
    locationX = 0.0F;
    locationY = 0.0F;
    name = NULL;
}

Shape::Shape(float locationX, float locationY) // This is the Shape_Constructor1 function with implicit this argument
{
    this->locationX = locationX; // this pointer is always available
    this->locationY = locationY;
    name = NULL;
}

Shape::~Shape() // This is the Destructor function with implicit this argument
{
    if (name != NULL)
        delete[] name;
    // There is no reason to delete this pointer –
    // it will be freed by the delete statement itself
}

void Shape::SetName(const char *name) // This is SetName function with implicit this argument
{
    if (this->name != NULL)
        delete[] this->name;
    this->name = new char[strlen(name) + 1];
    strcpy(this->name, name);
}

void Shape::PrintOut() // This is the PrintOut function with implicit this argument
{
    cout.flags(ios::fixed);
    cout.precision(2);
    cout << name << "'s location is (" << locationX << ", " << locationY << ")" << endl;
}

Once again, note how similar this file is to shape.c provided earlier in the C version of the code.

After Shape has been rewritten into class, main function also slightly changes as presented in the following listing.

// Listing of main.cpp
#include "shape.hpp"

int main()
{

    Shape *shape1 = new Shape(1.0F, 2.0F);
    shape1->SetName("Shape1");
    shape1->PrintOut();

    Shape *shape2 = new Shape(2.0F, 3.0F);
    shape2->SetName("Shape2");
    shape2->PrintOut();

    delete shape1;    // This will invoke shape1's destructor, i.e. ~Shape method
    delete shape2;

}

Note that methods are now called directly on the object. There is no reason to call a global function in object-oriented language and then to pass it the pointer to the object. This is because object itself is already associated with its methods. When a method of one object is called, compiler is there to pass pointer to that object into the method. In C++ this pointer serves the purpose when accessing the object on which method was called.

See also:

 

Published: Jul 14, 2012; Modified: Jul 17, 2012

webmasters