class class_name {
public:
declarations of visible data members and methods
private:
declarations of hidden data members and methods
protected:
declarations of data members and methods visible to derived classes only
};
In the public section we declare all methods and attributes available to any other object, and in the
private section we declare those what can be accessed only by the methods of this class. The following example illustrates
the definition of the new class MovingObject. The complete definition of a new class usually consists of two
parts: definition and implementation. In the definition we describe the structure of the class
class MovingObject {
public:
enum Direction {North, West, South, East}; // declaring a new type for directions
MovingObject();
int getX();
int getY();
void step();
void speed_up();
void slow_down();
void turn(Direction new_dir);
private:
int x, y; // current coordinates of the object
int dir; // direction of the object
int speed; // speed of the object
};
MovingObject::MovingObject()
{
x = y = 0;
speed = 1;
dir = South;
}
inline int MovingObject::getX()
{
return x;
}
inline int MovingObject::getY()
{
return y;
}
void MovingObject::step()
{
switch( dir ){
case North: y += speed; break;
case West: x -= speed; break;
case South: y -= speed; break;
case East: x += speed; break;
}
}
inline void MovingObject::speed_up()
{
speed++;
}
inline void MovingObject::slow_down()
{
if( speed ) speed--; // decrease speed only if it's greater than zero
}
inline void MovingObject::turn(Direction new_dir)
{
dir = new_dir;
}
In the definition of the MovingObject class we would like you to pay attention to the line
enum Direction {North, West, South, East};
In this line we define a new enumerated type Direction that can take one of the possible 4 values (North,
West, South, and East). In fact, it was done only to define four new constants available
both inside and outside the class. Since the type is defined in the public section of the class definition these
constants can be accessed from objects of other types as In the implementation section we would like you to notice the keyword inline in front of some methods. To explain the difference between normal methods and inline methods we need to recall how the C++ compiler translates the member functions into machine code. When a function call is made, the compiler usually generates the machine code that jumps to another place in the program code where there is the code for the function called. When the machine code for the function has been executed, there is a jump back in the program to the position where the call was made. The machine code for the function itself is therefore found in only one position, and all calls will result in a jump to that position. Due to this procedure, for each call there should be a set of instructions that allow to save the address of the point the call is made from and all parameters for the function is a special location (program stack) before the jump is made to ensure the jump back to the right place. If a function is small (consists of one or two statements), then amount of service instructions to perform the call is comparable to the amount of instructions in the function itself. When this is the case it would be more efficient to execute the machine code for the function in the place where the call is made instead of jumping to another location. This is can be done using the inline functions. If a function specified to be inline, then the machine code for the function is duplicated at every location where program calls the function. Another way to make a class method to be inline is to put its implementation directly in the definition of the class.
Please find the complete example on the example page.
Thus, for our example we can create the following header file movingobject.h
#ifndef _MOVINGOBJECT_H #define _MOVINGOBJECT_H class MovingObject { public: enum Direction {North, West, South, East}; // declaring a new type for directions MovingObject(); ... private: int x, y; ... }; // all inline functions #endif
#ifndef _MOVINGOBJECT_Hmakes sure that variable _MOVINGOBJECT_H is not yet defined. If it is, then this file will not be compiled again. Thus, the following lines will be processed only if this is the first time the compiler reads the file. The next line
#define _MOVINGOBJECT_Hdefines the variable _MOVINGOBJECT_H to avoid future recompiling of the file. And the second important thing we would like to notice here is, that the header file should include not only the definition of the class, but also the definition of all inline functions. Only definitions of normal functions go to the method implementation file.
Now we put definitions of all normal (non inline) functions into the file movingobject.cpp and do not forget to include the header file using the #include directive to let the compiler know where to look for the definition of the class:
#include "movingobject.h"
MovingObject::MovingObject()
{
x = y = 0;
speed = 1;
dir = South;
}
void MovingObject::step()
{
switch( dir ){
case North: y += speed; break;
case West: x -= speed; break;
case South: y -= speed; break;
case East: x += speed; break;
}
}
MovingObject train;
To illustrate how these special methods work, let's modify the MovingObject class from example above by editing its constructor and adding a destructor:
public:
...
MovingObject() { x=y=0; speed=1; dir=West; cout<<"Constructor is working\n"; };
~MovingObject() { cout<<"Destructor is working\n"; };
...
Now, if we use the following main function
1: void main()
2: {
3: MovingObject car;
4: cout<<"We will start a nested block now\n---\n";
5: {
6: MovingObject boat;
7: cout<<"We are inside the nested block\n";
8: }
9: cout<<"---\nThe nested block is over now\n";
10: }
we will see the following output
Constructor is working We will start a nested block now --- Constructor is working We are inside the nested block Destructor is working --- The nested block is over now Destructor is workingThe first message "Constructor is working" corresponds to line 3 - we defined an object of the MovingObject class and by doing this we invoked the constructor. The second "Constructor is working" message corresponds to line 6 where we defined another object of the MovingObject class. The first message "Destructor is working" corresponds to line 8 where the scope of the variable boat ends. Since the scope ends, the destructor is automatically called. And finally, the last "Destructor is working" message is generated by line 10, where the scope of the variable car ends.
Since the name for the destructor is fixed and destructors may not take any arguments, a class can have only one destructor. However, one class can have a list of constructors that differ by number or type of parameters they take; that is, we can overload constructors just like any other method or function. A constructor that takes no arguments is called a default constructor. It's a very good idea always create a default constructor for a class. Constructors may take parameters. For example, we can describe a constructor for the MovingObject class that puts the object in a given point and another constructor that also sets the initial speed and direction:
class MovingObject {
...
MovingObject(); // default constructor
MovingObject(int X, int Y);
MovingObject(int X, int Y, int s, Direction d);
...
};
...
MovingObject::MovingObject(int X, int Y)
{
x = X;
y = Y;
speed = 1;
dir = South;
}
MovingObject::MovingObject(int X, int Y, int s, Direction d)
{
x = X;
y = Y;
speed = (s>=0) ? s : 1;
dir = d;
}
...
To pass arguments to the constructor, we need to put these arguments inside the
parenthesis in the declaration statement:
MovingObject robot(10, 10, 2, MovingObject::South);
MovingObject car(10, 10, 2, MovingObject::South); MovingObject track = car;By default the assignment operator creates an exact copy of all attributes of the object. For example, after executing the lines from the example above, object track will also be located at the square with coordinates (10, 10), will have speed 2, and will be heading South.
It seems like exactly what we need, but now let's take a look what happens when we assign one TString object defined as
class TString {
public:
TString();
TString(char *s);
void Print();
void Set(char *s);
private:
char *str;
int size;
int length;
};
to another. Assume that originally the first
TString object str1 contains the string "Hello, world!"; that is, it actually contains
a pointer to the memory allocated somewhere else, the length 13, and the amount of bytes allocated (let's say
16):
TString str2 = str1;then all the fields will be copied. That is, the new picture will look like:
str1.Set("Bye, world!");
the second object will "contain" the same value:
The following example contains the code that illustrates the discussed issue.
To avoid such a situation, we can define the copy constructor. The copy constructor is a constructor that takes an object of the same type as the argument. In fact, this constructor should take a reference to the copying object. For example, the copy constructor for the TString class can de defined as
TString::TString(const TString &s)
{
size = s.size;
length = s.length;
str = new char[size];
strcpy(str, s.str);
}
If such a constructor is defined, then the line
TString str2 = str1;will call str2.TString(str1). Thus, the picture after the initialization of the second TString object will look like
We would like to note one very important detail: the copy constructor can be called only with initialization and not with ordinary assignments. If we write, for example,
TString str2 = str1; // The copy constructor is called str2 = str1; // The assignment operator is calledthe copy constructor will be called for in the first line but not in the second. Instead, the assignment operator will be called in the second line.
In fact, the default assignment operator does simple copying of all the attributes. On the example page you can find the example that illustrates this. In other words, to avoid the situation described above, we need to redefine the assignment operator.
C++ allows us to do that. From C++ point of view an operator is just a function with special syntax and parameters. In particular, the assignment operator takes only one argument (the one of the right hand side of the equal sign). That is, the notation str1 = str2 is very similar (from the compiler point of view) to
str1.Use_Assignment_Operator(str2);The syntax to redefine this operator is:
class TString {
public:
...
const TString& operator = ( const TString &str );
...
};
As you can see, it looks like we define a new method with a strange name operator =.
This method takes one argument of the TString type and returns a value of the same type.
Strictly speaking, it returns a value of the TString& type; that is, it returns a
modifiable value, but due to the modifier const, which appears before the type, we prohibit
to change this value. This was done to prevent the method from creating a temporarily copy of the whole
object and putting this object to the stack to return to the calling line. This may cause an additional
problem because the destructor will be called for that stack copy when it's no longer needed. Shortly,
this is a nice way to declare that we return an object of the TString type.
Note: we need to do all of these things because our object does not contain all the data. It contains a
pointer and some important data is stored at the location pointed by the pointer.
const TString& TString::operator = (const TString &s)
{
delete[] str;
str = new char[s.size];
strcpy(str, s.string);
length = s.length;
size = s.size;
return *this;
}
(Please find the complete example on the example page.)
We have seen code like this several times, but there is one new detail. In the very last line of the
function we use new keyword this. Before explaining this new keyword, let us ask a
question: How can we return the current object? Note that we want to return the whole object not just
a part of it (like size, length) and not a copy of the object. To do things like this, C++
provides a special predefined pointer this, which is always points to the current object.
Since we as using the pointer inside a class method, the this pointer points to the whole
object invoking the method (in case a = b it's the object a). We also need to use
the star operator to return the object itself not a pointer to the object.
MovingObject::MovingObject(int X, int Y) : x(X), y(Y), speed(1), dir(South) { }
MovingObject::MovingObject(int X, int Y, int s, Direction d) : x(X), y(Y)
{
speed = (s>=0) ? s : 1;
dir = d;
}
Please note that despite the moving all initialization from the function body to the initialization list the method still
has the body, but it is empty.
We also would like to notice that we don't have to move all initializations to the list. The second constructor has coordinates x and y initialized in the list and two other attributes are initialized in the body. In this constructor this doesn't matter if we use initialization list or carry out ordinary assignments; but sometimes it is necessary to use an initialization list, for instance if we have to initialize data members that are constants or references, since we are not allowed to assign to these. Moreover, initialization list can give more effective code when we initialize data members that are objects. It is therefore advisable to use initialization lists if possible.
If a function does not access the data of any object and yet you want the function to be a member of the class, you can make it a static function. Static functions can be invoked in the normal way, using a calling object of the class. However, it is more common and clearer to invoke a static function using the class name and scope resolution operator, as in the following example:
Robot::TotalHPLeft()
Because a static function does not need a calling object, the definition of a static function cannot use anything that depends on a calling object. A static function definition cannot use any nonstatic variables or nonstatic member functions, unless the nonstatic variable or function has a calling object that is a local variable or some object otherwise created in the definition. If that last sentence seems hard to understand, just use the simple rule that the definition of a static function cannot use anything that depends on a calling object.
To define a static data member or a method all you need to do is to put the keyword static in front of the definition. Please note, though, that every static variable must be initialized outside of the class definition. Also, a static variable cannot be initialized more than once. For example, if in a new class Robot we define a private static variable TotalHP, then it should be initialized as follows
class Robot {
public:
static int getTotalHP() { return totalHP; };
private:
static int TotalHP;
};
int Robot::TotalHP = 10;
This may seem to be contrary to the notion of private. However, the author of the class is expected to do the
initialization, typically in the same file as the class implementation. In that case, no programmer who uses the class
can initialize the static variable, since a static variable cannot be initialized a second time.
The robot program example illustrates the idea of static data members and methods creating a class Robot. Each object of the type Robot has a random numbers of hit points, but the total sum of the initial hit points cannot be greater than the value of shared variable TotalHP, which is initialized by 30. In the main function we start creating an array of robots and we stop when there is no hit points left to give to the next robot. Please also note that the destructor if the class Robot returns the initial amount of hit points given to a robot when a robot dies.
Let us illustrate the syntax of creating a friend function by an example. Suppose we need to write a function that takes an array of instances of the MovingObject class and returns the index of the fastest object. We would not like to make this function to be a member of the MovingObject class, but it still needs to have an access to the private attribute speed. Of course, we could write a method getSpeed(), but it would be faster to access the variable directly, than to call a special function. Thus, to develop a function FindFatsest() we need to provide the access to private attributes of the class. To do this, we will insert the declaration of the function with the keyword friend inside the class definition and then write the function as usual C++ function:
class MovingObject {
public:
MovingObject(); // default constructor
...
friend int FindFastest(int count, MovingObject *obj);
...
};
int FindFastest(int count, MovingObject *obj)
{
int i, max_speed, ind;
if( count < 0 ) return -1;
max_speed = obj[0].speed;
ind = 0;
for(i=1;i<count;i++)
if( max_speed < obj[i].speed ){
max_speed = obj[i].speed;
ind = i;
}
return ind;
}
Not only functions can be friends. Another class can be declared as a friend. In this case all of the methods of this friend class can access the private members of the other class. This is how we can make the class Board to be a friend of the MovingObject class:
class MovingObject {
public:
friend class Board;
...
};
Please note that friendship is a one-way relation in C++.