For any real world application, more complex data must be manipulated than that which can be represented by the basic C++ data types. For example, even something as simple as a date has three parts: the year, month and day. C++ allows such aggregate types, or structures, to be defined using the structkeyword.

Date.h

Struct Date

{

int day, month, year;

};

 The struct keyword is followed by the name to be given to the structure. The data members of the structure are defined inside the curly brackets. If instances of the structure need to be created when the structure is declared, this can be done as follows:

 

Date.h

Struct Date

{

int day, month, year;

}myDate;

 

Where myDate is an instance of the Date structure. However, this method of creating instances of the structure should be avoided, because structure definitions are usually contained in header files which can be included in multiple source-files, each of which would get its own myDate whether it was required or not. It is better to define structures and classed in a header file or not. It is better to define structures and classes in a header file and not create instances of those structures or classes until they are actually needed.

 

Accessing structure members

To access the members of a structure, the dot (.) operator is used:

DateTest.cpp

#include Date.h

 

void main (void)

{

struct Date myBirthday;

myBirthday.day=19;

myBirthday.month=5;

myBirthday.year=1998;

myBirthday.year++;

}

 

 To access data members when using a pointer to a struct, replace the dot with th arrow operator (->). The notation a->element is identical in function to (*a).element.

 

Member Functions

In C++, structs may also contain functions. In the Date example, this would allow the Date structure to contain functions which could be used to manipulate instances of the Date structure this is done as follows:

 

Date.h

Struct Date {

Int day, month, year;

}

void SetDate(int d, int m, int y);

void Print( void);

 

Defining members functions

These functions must be defined before use. Normally, such definitions are placed in a separate file, Date.cpp in our example.

 

Date.cpp

#include Date.h

using namespace std;

 

void Date::SetDate(int day, int m, int y)

{

day = d;

month = m;

year = y;

}

 

void Date::Print(void)

{

cout << day << / << month << / << year;

}

 

the function definitions are prefaced with Date:: to allow the compiler to bind the function to the appropriate class declarations. This is similar concept to the use of namespaces. The :: operator is known as the scope resolution operator.

 

Using member functions

Like data members, functions belonging to a struct are accesses using the dot or arrow operator.

 

DateTest.cpp

#include Date.h

 

void main(-void)

{

Date myBirthday;

myBirthday.SetDate(19, 5, 1998);

myBirthday.day++;

Date *myBirthdayPtr = &myBirthday;

myBirthdayPtr->Print( );

}

 

Constructors

The SetDate function above is an example of a constructor function. Its sole purpose is to set the value of the Date object to a sensible value (or at the least, a silly one which the caller knows about) C++ provides a special notation for such functions, as shown below:

 

Date.h

 

Struct Date {

int day, month, year;

 

Date( int day, int m, int y)

 

void Print (void);

{

cout << day << / << month << / << year;

};

 

Note that the constructor function is of the same name as the structure it belongs to, and has no return type.

To use the constructor, the following notation is used:

 

DateTest.cpp

#Include Date.h

 

void main (void)

{

Date myBirthday(19, 5, 1973);

 

myBirthday.day++;

Date *myBirthdayPtr = &myBirthday;

myBirthdayPtr->Print( );

}

the use of constructors guarantees that the object contains a known value before it is used. Without a constructor, the myBirthday object would be undefined until SetDate was called.

 

Default constructors, function overloading

Sometimes its not necessary to assign a value to an object every time one is created. In this case a default constructor may be used. This is a constructor, which takes no arguments. The constructor function for a default constructor should fill the objects data members with usable or safe data.

 

Date.h

Struct Date {

int day, month, year;

 

Date (void); //default constructor

Date (int day, int month, int year);

 

void Print (void);

{

cout << day << / << month << / << year;

};

 

Date.cpp

#include Date.h

using namespace std;

 

Date::Date(void)

{

day = 1;

month = 1;

year = 1900;

}

 

Date::Date(int day, int month, int year)

{

day = d;

month = m;

year = y;

}

void Date::Print(void)

{

cout << day << / << month << / << year;

}

 

DateTest.cpp

#include Date.h

 

void main (void)

{

Date myBirthday(19, 5, 1998);

Date date2; //default constructor : 1 Jan 1900

myBirthday.day++;

myBirthday.day = date2.day;

}

 

Note that two definitions of the Date constructor have now been provided: one with no arguments, one with three int arguments. This allows different implementations of the Date constructor to be called depending on the needs of the calling code. This concept of allowing more than one version of the function is known as function overloading , and is central to the use of the C++ language.

 

Dynamic Allocation

It is possible to create new objects under program control, using the new operator. new returns a pointer to a new instance of the type specified. So, to create a new integer:

 

Int* myIntPtr = new int;

new can also be used to create arrays of objects:

 

Date* paydays = new Date[12];

In the above example, the default controller will be called for each element of the paydays array.

 

Delete and destructor functions

Dynamic allocation is often used when objects are created by one module, but used much later in the program. This leads to a problem: how to dispose of dynamically allocated objects. Fortunately, the new operator has an opposite number, called delete. The delete operator destroys an object referred to by a pointer.

 

delete myIntPtr;

for (int count=0; count<12; count++)

delete paydays[count]

 

Note that pointers used by delete are not changed: they still point to where the deleted object used to lie in memory. This can cause serious problems if the pointer is referenced later. Such used-up pointers are known as dangling pointers. Also notice that where an array was allocated using new, each element of the array must be deleted individually. If this behaviour seems odd, remember that an arrays name is only a pointer to its first element. With complex objects, there is often a need to clean up before the object is deleted. To allow such operations, C++ allows each object to define a destructor function . A destructor is implicitly called immediately before the object in question is destroyed.

 

To define a destructor, the following notation is used:

Date.h

Struct Date {

Int day, month, year;

 

Date (void);

Date(int day, int m, iny y);

~Date(void); //destructor

 

void Print (void);

{

cout << day << / << month << / << year;

};

 

Date.cpp

#include Date.h

other member functions omitted for clarity

 

Date::~Date(int day, int m, int y)

{

// whatever cleanup that needs to be done

 

}

 

The tilde character (~) is used to indicate a destructor function. Like constructers, a destructor never returns a value. However, unlike constructors, an object is allowed to have only one destructor. Also, the user of an object should never call the destructor function explicitly: the C++ runtime code will call an objects destructor automatically when it is appropriate to do so. Destructors are essential when an object allocates its own memory storage, or uses new to create sub-objects. The general rule is: if its allocated in the constructor, it must be deleted in the destructor. Destructors play an important role when using inheritance.

 

Hiding data: Classes

One of the most important techniques in object- orientated programming is that of data hiding preventing one module from accessing private data belonging to another. C++ provides this through the use of classes. A class is like a structure in most respects, except that when a class is defined, all data or functions within it are assumed to be private members unless otherwise specified. Private members may only be accessed by functions within the class itself. This prevents other code messing with an objects data without the objects knowledge.

 

To define a class, the class keyword is used (instead of struct). To mark class members as being accessible to code outside of the class, the public keyword is used. All data or functions declared after a public will be accessible to other classes or functions. To hide class data or functions from other classes or functions, use private. A class definition starts as being private by default.

 

In other respects, a class definition is like a struct definition. Here is the Date example using a class.

 

Date.h

 

class Date {

int day, month, year; //these are private

 

public:

Date(void);

Date(int day, int m, int y);

 

Void Print (void);

{

ect ect

};

 

It stands to reason that any constructors for a class should be public. Otherwise, how could other functions create objects of that class?

 

The implementation of the Date class is identical to the existing struct based Date object.

 

Date.cpp

 

#include Date.h

 

Date::Date(void)

{

day = 1;

month = 1;

year = 1900;

}

 

Date::Date(int d, int m, int y)

{

day = d;

month = m;

year = y;

}

 

void Date::Print(void)

{

cout << day << / << month << / << year;

}

 

Accessor functions

Because a class will generally contain some private data, which is of use to the classes users, some method of accessing that data must be provided for those users. This is normally done by providing functions, which return the value of certain private data members. Such functions are known as accessor functions.

 

Date.h

Class Date {

Int day, month, year; //these are private

 

public:

Date(void);

Date(int day, int m, int y);

 

int Day(void);

int Month(void);

int Year(void);

 

void Print(void);

{

cout << day << / << month << / << year;

};

 

Data Abstraction

Using accessor functions in the Date class seems silly: after all, they just return their corresponding data members. So why go to expense of a function call?

 

Accessor functions such as Date::Day() allow the designer of a class to completely change its internal workings without having to worry about other modules being dependent on the class: so long as the class interface (the set of accessor functions) remains the same, the change of implementation is transparent. This ability to re-implement classes, to be written in a relatively short time and optimised later. Or never, as is unfortunately often the case. However, at least the opportunity to improve is there: something that non-object-orientated designs generally don't allow (because of the often fundamental interconnectedness of their different functional parts).

 

As an example of the advantage of data abstraction when optimizing, consider the Date class again. The class is quite inefficient in its storage requirements: the month and date fields are vastly over used, for example (an int can hold at least 65000 values, but here its is asked only to hold twelve, or thirty five, respectively) the Date class could thus be re-implemented more efficiently as follows:

 

class Date

    {

    int days;     //number of days since 1 January, 1900

        public:

                Date (void);

                Date(int day. int m, int y);

                

                int Day(void);

                int Month(void);

                int Year(void);

                void Print (voids);

                ect ect...

             };

notice that various public functions of class Date have not changed in form. the code inside these member functions will change drastically to accommodate the new data representation, but the upheaval is hidden from any users of the Date class.