Lecture Notes. Day One

From Structure to Class

In C: you concentrate on writing functions: Groups of actions for performing some task are formed into functions; Functions are grouped to form programs. Data plays primarily a supportive role for performing the actions.

In C++: you concentrate on creating your own user-defined types, or, classes. Data and a set of functions that manipulate the data form a class. The data components: data members. The function components: member functions. An instance of a built-in type can be called a variable, an instance of a class is called an object.

class in C++ is a natural evolution of the C notion of struct. After getting an acquaintance with classes, you will realize, that structures in C++ are classes with public members only.

C structure, which simply prints in a time format, is given below:

  1. // time_struct_1.cpp

  2. #include <iostream.h>

  3. struct Time    // key word "struct"

  4. {

  5.    int hour;

  6.    int minute;

  7.    int second;

  8. };


  9. void print(const Time &);

  10. main()

  11. {

  12.    Time t; // a variable of type Time is declared...

  13.    // access of structure members with a structure object:

  14.    t.hour = 19;

  15.    t.minute=1;

  16.    t.second=38;

  17.    cout << "You entered time as ";

  18.    print(t);


  19.    return 0;

  20. }


  21. void print(const Time &t)

  22. {

  23.    cout << ( (t.hour == 0 || t.hour == 12) ? 12 : t.hour % 12 )

  24.         << ":" << ( t.minute < 10 ? "0" : "") << t.minute

  25.         << ":" << ( t.second < 10 ? "0" : "") << t.second

  26.         << (t.hour < 12 ? " AM" : " PM");

  27. }

In this program we meet with the conditional operator, the only ternary operator in C++, which takes three operands. The argument of a print function, which is evidently of type void, is a const reference to the structure type Time. It is elegant to call for the Time variable via reference, and safety, because this is const.

Let us make a step towards a class-like style:

  1. // time_struct_2.cpp

  2. #include <iostream.h>

  3. struct Time   

  4. {

  5.    void print();

  6.    void setTime(int, int, int);

  7.    int hr;

  8.    int mn;

  9.    int sc;

  10. };


  11. main()

  12. {

  13.    Time t; // a variable of type Time is declared...

  14.    t.setTime(19,1,38);

  15.    cout << "You entered time as ";

  16.    t.print();

  17.    t.setTime(4,59,79);

  18.    cout << "... and now as ";

  19.    t.print();


  20.    return 0;

  21. }


  22. void Time::setTime(int h, int m, int s)

  23. {

  24.    hr = h;

  25.    mn = m;

  26.    sc = s;

  27. }


  28. void Time::print()

  29. {

  30.    cout << ( (hr == 0 || hr == 12) ? 12 : hr % 12 )

  31.         << ":" << ( mn < 10 ? "0" : "") << mn

  32.         << ":" << ( sc < 10 ? "0" : "") << sc

  33.         << (hr < 12 ? " AM" : " PM");

  34. }

What's a difference? We included print into the member functions of structure Time, and instead of setting time through the Time variable t, we set it with using setTime. This allows us to get a direct access to other Time members, hr, mn, sc.

Exercize:

Think about what changes should be done in the program for initializing objects in Standard Time format: hr:mn:sc:am(pm)

Now, if we wish to arrive at the same functionality in terms of Class Time, the changes are really minimal. In the code below you meet with a new special function Time(), which is a constructor function. Old, but new elements now are data members, which are declared private. Before, being the structure members, they were accessible via a structure variable (object). The interface is different now: the private data members are seen by the public member functions. Also, we delegate an additional control functionality to setTime public member in order to disallow such misprints as in time_struct_2.cpp.

  1. // time_class_1.cpp

  2. #include <iostream.h>


  3. class Time

  4. {

  5. public:

  6.    Time(); // new element: constructor

  7.    void setTime(int, int, int);

  8.    void print();

  9. private:

  10.    int hr;

  11.    int mn;

  12.    int sc;

  13. };

  14. // Time constructor initializes data members to zero

  15. // which will be printed as 12 AM

  16. Time::Time() { hr = mn = sc = 0; }


  17. void Time::setTime(int h, int m, int s) // improved...

  18. {

  19.    hr = (h >=0 && h < 24) ? h : 0;

  20.    mn = (m >=0 && m < 60) ? m : 0;

  21.    sc = (s >=0 && s < 60) ? s : 0;

  22. }

  23. void Time::print()

  24. {

  25.    cout << ( (hr == 0 || hr == 12) ? 12 : hr % 12 )

  26.         << ":" << ( mn < 10 ? "0" : "") << mn

  27.         << ":" << ( sc < 10 ? "0" : "") << sc

  28.         << (hr < 12 ? " AM" : " PM");

  29. }

  30. main()

  31. {

  32.    Time t; // a variable of type Time is declared...

  33.    t.setTime(19,1,38);

  34.    cout << "You entered time as ";

  35.    t.print();

  36.    t.setTime(4,59,79);

  37.    cout << "... and now as ";

  38.    t.print();


  39.    return 0;

  40. }

The output should be like this:

   You entered time as 7:01:38 PM
   ... and now as 4:59:00 AM

Even such a short code becomes more readable, if their parts are re-arranged into header, source file and driver (that is a common place of C, too):

Header: Declarations & Class Definition

  1. // time_class_1.h

  2. #ifndef TIME_CLASS_1_H

  3. #define TIME_CLASS_1_H


  4. class Time

  5. {

  6. public:

  7.    Time();

  8.    void setTime(int, int, int);

  9.    void print();

  10. private:

  11.    int hr;

  12.    int mn;

  13.    int sc;

  14. };

  15. #endif

The following directive defines header file TIME_CLASS_1_H: #define TIME_CLASS_1_H. This means that the compiler's preprocessor puts TIME_CLASS_1_H on a list to indicate that it has been seen. The next directive, #ifndef TIME_CLASS_1_H, tests to see whether TIME_CLASS_1_H has been defined. If defined, then everything between this directive and #endif will be skipped. Why it is necessary to do? That is because C++ does not allow you to define a class more than once. In the code below we shall use the include directive #include "time_class_1.h" twice, and with no problems: the class will be defined only one time.

Source file: Constructor, member functions, etc.

  1. // time_class_1.cpp

  2. #include <iostream.h>

  3. #include "time_class_1.h"


  4. Time::Time() { hr = mn = sc = 0; }


  5. void Time::setTime(int h,int m,int s)

  6. {

  7.    hr = (h >=0 && h < 24) ? h : 0;

  8.    mn = (m >=0 && m < 60) ? m : 0;

  9.    sc = (s >=0 && s < 60) ? s : 0;

  10. }

  11. void Time::print()

  12. {

  13.    cout << ( (hr == 0 || hr == 12) ? 12 : hr % 12 )

  14.         << ":" << ( mn < 10 ? "0" : "") << mn

  15.         << ":" << ( sc < 10 ? "0" : "") << sc

  16.         << (hr < 12 ? " AM" : " PM");

  17. }

Main, or, driver for testing:

  1. // time_main_1.cpp

  2. #include <iostream.h>

  3. #include "time_class_1.h"


  4. main()

  5. {

  6.    Time t; // instantiate object of class Time


  7.    t.setTime(19,1,38);

  8.    cout << "You entered time as ";

  9.    t.print();

  10.    t.setTime(4,59,79);

  11.    cout << "... and now as ";

  12.    t.print();


  13.    return 0;

  14. }

How to compile and execute them? You can create a makefile based on the commands:

  >g++ -c time_class_1.cpp time_class_1.h
  >g++ -c time_main_1.cpp time_class_1.h
  >g++ -o time_class time_class_1.o time_main_1.o
  >time_class

The member access specifiers public and private are used to control access to a class’ data members and member functions.

Private class members can be only accessed by member functions (and friend functions) of the class.
Public class members may be accessed by any function in the program.

Initializing Class Objects: Constructors

Constructor: a class member function with the same name as the class name.
Constructor is automatically invoked each time an object of the class is created

Performance can be improved with using a default constructor.
Compare the left column with its right counterpart.

class Time
{
public:
   Time();
......
Time::Time() { hr=mn=sc=0; }
class Time
{
public:
   Time(int = 0, int = 0, int = 0);
......
Time::Time(int hr, int mn, int sc)
{ setTime(hr, mn, sc); }

Then, instead of formal setting of time with setTime as, for example, t.setTime(14,31,68), it will be possible to set data members “directly” via t(14,31,68). Then, t(14,31) means t(14,31,0), t(14) means t(14,0,0), t means t(0,0,0).
So, main() in time_main_1.cpp could be re-written like this:


  1. #include <iostream.h>

  2. #include “time_class_1.h”


  3. main()

  4. {

  5.    Time t1(19,1,38), t2(4,59,79);


  6.    cout << "You entered time as ";

  7.    t1.print();

  8.    cout << "... and now as ";

  9.    t2.print();


  10.    return 0;

  11. }

Using Destructors:

Name for the destructor: ~ClassName()
Destructor’s task: not to destroy the object, but terminate housekeeping before the system reclaims the object’s memory space.

Destructors are the proper functions for classes whose objects contain dynamically allocated storage.

Example of Constructor & Destructors hierarchies

  1. // create_and_destroy.h

  2. // Definition of Class CREATURES

  3. #ifndef CREATE_AND_DESTROY_H

  4. #define CREATE_AND_DESTROY_H

  5. // user-defined enumeration type:

  6. enum NUMBER{GLOBAL,LOCMN1,STATMN,LOCFN1,STATFN,LOCFN2,LOCM2};

  7. class Creatures

  8. {

  9. public:

  10.    Creatures(NUMBER); // constructor

  11.    ~Creatures();     // destructor

  12. private:

  13.    NUMBER creatureNumber;

  14. };

  15. #endif

  1. // create_and_destroy.cpp

  2. #include <iostream.h>

  3. #include "create_and_destroy.h"

  4. Creatures::Creatures(NUMBER value)

  5. {

  6.    creatureNumber = value;

  7.    cout << "Make a Creature # " << value;

  8. }

  9. Creatures::~Creatures()

  10. {

  11.    cout << "Destroy a Creature # " << value;

  12. }

  1. // create_and_destroy_test.cpp

  2. #include <iostream.h>

  3. #include "create_and_destroy.h"

  4. void create();

  5. Creatures cr0(GLOBAL);

  6. main()

  7. {

  8.    cout << "(global object created before main)" << endl;

  9.    Creatures cr1(LOCMN1);

  10.    cout << "(local object created in main)" << endl;

  11.    static Creatures cr2(STATMN);

  12.    cout << "(static object created in main)" << endl;

  13.    create();

  14.    Creatures cr6(LOCMN2);

  15.    cout << "(local object created in main)" << endl;

  16.    return 0;

  17. }


  18. void create()

  19. {

  20.    Creatures cr3(LOCFN1);

  21.    cout << "(local object created in create)" << endl;

  22.    static Creatures cr4(STATFN);

  23.    cout << "(static object created in create)" << endl;

  24.    Creatures cr5(LOCFN2);

  25.    cout << "(local object created in create)" << endl;

  26. }

The first column includes the object’s numbers in order of their occurence in create_and_destroy_test.cpp. Please, before executing a program, fill the second column with the numbers in order of the same object’s destruction.

NUMBER is a user-defined type called an enumeration. Very efficient in combination with the switch statement.

Local variable declared with the keyword static is known only in function in which it is defined. static local variable retains its value when the function is exited. When the function is called next time, static local variable contains the value at the moment of function's last exiting.

Exercize:

Add new functionalies to our Time class.

  1. In setTime: a possibility to set Time from a keybord, including an elementary dialog, like this: "Set hours in London: ",..., "Set minutes in London at Big Ben: ",..., etc.

  2. Use the print functions, first, printTimeInLondon() whose output would be "Local time in London hr:mn:sc AM(PM) of today", second, printTimeInBoston() with a sort of the output like this: "Local time in Boston hr:mn:sc AM(PM) of today(yesterday)"

  3. Optional: Instead of today-yesterady identification of the date, think how to set a current date in London and Boston and send the information to the output in the following format: "Local time in London hr:mn:sc AM(PM) of mm/dd/yy" & "Local time in Boston hr:mn:sc AM(PM) of mm/dd/yy".
1