|

Static Member Data - Static member
data is shared among all instances of a class. In other words, if
there were a
Monster class, and if "int Count" were declared to be
static, and there were 20 Monster objects, there would be only
one
instance of the integer Count among all 20 Monster objects. Static
member data is a compromise between global
data (available throughout program) and member data (available only to each object. A static
data member belongs to the class rather than the object - they exist one per class throughout the class. Static members can be accessed without having an object if they have been declared in the class and they are public. They can be used as counters across instances of a class. Since they are not part of an object, their declaration does not allocate memory. They must be defined and initialized
outside the declaration of the class, though their prototype may
be declared inside the class.
"The
real problem is not whether machines think but
whether men do."
- B. F. Skinner
"A
person who WON'T think has no advantage over one who
CAN'T think."
- Paul Lutus |
//static data members
#include <iostream.h>
class Monster
{
public:
Monster(int
strength = 1):itsStrength(strength)
{
//Every time a Monster object is created, we will
increment the static data member in the constructor.
HowManyMonsters++; }
virtual ~Monster() { HowManyMonsters--; }
//Accessor Methods
virtual int
GetStrength() { return
itsStrength; }
virtual void SetStrength(int
strength) { itsStrength = strength; }
//Static
data member, so it must be initialized OUTSIDE the
class
static int HowManyMonsters;
private:
int
itsStrength;
};
//Static member data, must be initialized OUTSIDE
the class. Only one for all Monster objects.
int Monster::HowManyMonsters = 0;
int main()
{
int i;
const int MaxMonsters = 5;
//MonsterHouse
is an array of 5 Monster pointers
Monster *MonsterHouse[MaxMonsters];
for(i = 0; i<MaxMonsters; i++)
//Create a new Monster on the heap and have this
element point to it.
MonsterHouse[i] =
new Monster(i*100);
for(i = 0; i<MaxMonsters; i++)
{
cout << "There are ";
cout << Monster::HowManyMonsters;
cout << " Monsters left!\n";
cout << "Deleting the one which has strength ";
cout << MonsterHouse[i]->GetStrength();
cout << ".\n";
//Delete Monster object to clean up.
Initialize it to 0 to avoid accidents.
delete
MonsterHouse[i];
MonsterHouse[i] = 0;
}
return 0;
} |
Output:
There are 5 Monsters left!
Deleting the one which is 0 years old
There are 4 Monsters left!
Deleting the one which is 1 years old
There are 3 Monsters left!
|
Deleting the one which is 2 years old
There are 2 Monsters left!
Deleting the one which is 3 years old
There are 1 Monsters left!
Deleting the one which is 4 years old |
The declaration of "HowManyMonsters" does not define an integer - unlike non-static members, no storage space is set aside. The variable must be declared and defined outside since, it is not in the object
instance. It is a common mistake to forget to define static member variables of classes
outside the class. This will generate a compiler error.
Static member functions - Static member functions, like static member
data, exists not in the instance of the object but in scope of class.
They can be called without having an object instance of the class.
They exist outside the class and are singly shared by all objects of the
class. Static member functions have no "this" pointer, and so
they can not be declared "const". It
is important to note that, static member functions cannot access any non-static member variables.
This is because a class's member data variables are accessed via member functions using the "this" pointer.
The example below serves as an illustration of a static member function
- and a company with a high turnover rate :)
(wink wink):
//static data members
#include <iostream.h>
class Employee
{
public:
Employee(int age = 1):itsAge(age){HowManyEmployees++; }
virtual ~Employee() { HowManyEmployees--; }
virtual int GetAge() {
return itsAge; }
virtual void SetAge(int age) { itsAge = age; }
//Public accessor method for private data.
Return type must also include static.
static int GetHowMany() { return
HowManyEmployees; }
private:
int itsAge;
//HowManyEmployees
is private, so it must be accessed via a public
accessor method this time.
static int HowManyEmployees;
};
int Employee::HowManyEmployees = 0;
//Declare EmployeeCount function
void EmployeeCount();
int main()
{
int i;
const int MaxEmployees = 6;
Employee *EmployeeGroup[MaxEmployees];
for (i = 0; i<MaxEmployees; i++)
{
EmployeeGroup[i] =
new Employee(i);
EmployeeCount();
}
for ( i = 0; i<MaxEmployees; i++)
{
delete EmployeeGroup[i];
EmployeeGroup[i] = 0;
EmployeeCount();
}
return 0;
}
void EmployeeCount()
{
cout << "There are " << Employee::GetHowMany() << "
Employees alive!\n";
}
|
Output:
There are 1 Employees working
here.
There are 2 Employees working here.
There are 3 Employees working here.
There are 4 Employees working here.
There are 5 Employees working here.
There are 6 Employees working here. |
There are 5 Employees working here.
There are 4 Employees working here.
There are 3 Employees working here.
There are 2 Employees working here.
There are 1 Employees working here.
There are 0 Employees working here. |
The EmployeeCount() function can access the public static accessor even though it has no access to an
instane of an Employee object. This brings us to the subject of
containment.
Containment - Containment refers to the member data of one class that includes
within it the objects of another class. The outer class is said to
CONTAIN the inner class, hence the name. An employee class might contain string objects,
and these would be instance of another class, the String class.
The file below will later become our header file.
//Containment,
Operator overloading and a peek inside the string
class
#include <iostream.h>
#include
<string.h>
class String
{
public:
//3 overloaded constructors
String();
String(const char *const);
String(const String
&);
//Destructor
~String();
//Overloaded
C++ operators
char
&
operator[](int offset);
char
operator[](int offset)
const;
String operator+(const String&);
void operator+=(const String&);
String & operator= (const String
&);
//Here we overload the datastream operator (<<) to
work with cout. It returns an ostream object.
//We must use a friend function
declared outside the class.
friend ostream
& operator<<(ostream
& theStream,
String &
theString);
//General accessors
int GetLen()const {
return itsLen; }
const
char
* GetString()
const {
return itsString; }
//Static member data - must be defined outside the
class.
static int ConstructorCount;
private:
String (int); // private constructor
char * itsString;
int itsLen;
};
//----------------------------------------------------------------------------------
//1st overloaded constructor
definition. This default constructor creates string of 0 bytes.
String::String()
{
itsString = new char[1];
itsString[0] = '\0';
itsLen=0;
cout << "\tDefault string constructor.\n";
ConstructorCount++;
}
//2nd
overloaded constructor. It is a private (helper) constructor, used only by class methods
//for creating a newstring of the required size. It
is null filled.
String::String(int len)
{
itsString = new
char[len+1];
int i;
for(i = 0; i<=len; i++)
itsString[i] = '\0';
itsLen=len;
cout << "\tString(int) constructor\n";
ConstructorCount++;
}
//3rd
Overloaded Constructor
String::String(const char *
const cString)
{
itsLen = strlen(cString);
itsString = new
char[itsLen+1];
int i;
for(i = 0; i<itsLen; i++)
itsString[i] = cString[i];
itsString[itsLen]='\0';
cout << "\tString(char*) constructor\n";
ConstructorCount++;
}
//Copy
constructor
String::String (const String
& rhs)
{
itsLen=rhs.GetLen();
itsString = new char[itsLen+1];
int i;
//Copy the char's one by one from one array to the
next.
for( i = 0; i<itsLen;i++)
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
cout << "\tString(String&) constructor\n";
ConstructorCount++;
}
//Destructor
String::~String ()
{
delete [] itsString;
//delete the entire array of char
itsLen = 0;
cout << "\tString destructor\n";
}
//Overloaded
C++ operator equals, frees existing memory, then copies string and size.
String & String::operator=(const String
& rhs)
{
if(this ==
&rhs)
return
*this;
delete [] itsString;
itsLen=rhs.GetLen();
itsString = new
char[itsLen+1];
int i;
for(i = 0; i<itsLen;i++)
itsString[i] = rhs[i];
itsString[itsLen] = '\0';
return *this;
cout << "\tString operator=\n";
}
//Overloaded
C++ operator non-constant offset, returns a reference to
a character so it can be changed.
char & String::operator[](int offset)
{
if (offset > itsLen)
return itsString[itsLen-1];
else
return itsString[offset];
}
//Overloaded
C++ operator constant offset, for use with
const objects. (see copy constructor)
char String::operator[](int offset)
const
{
if(offset > itsLen)
return itsString[itsLen-1];
else
return itsString[offset];
}
//Overloaded
C++ operator +, creates a new string by adding current string to rhs.
String String::operator+(const String & rhs)
{
int totalLen = itsLen + rhs.GetLen();
int i,j;
String temp(totalLen);
for(i = 0; i<itsLen; i++)
temp[i] = itsString[i];
for(j = 0; j<rhs.GetLen(); j++, i++)
temp[i] = rhs[j];
temp[totalLen]='\0';
return temp;
}
//Overloaded
C++ operator +=,changes current string, returns nothing.
void String::operator+=(const String& rhs)
{
int rhsLen = rhs.GetLen();
int totalLen = itsLen
+ rhsLen;
int i,j;
String temp(totalLen);
for(i = 0; i<itsLen; i++)
temp[i] = itsString[i];
for(j = 0; j<rhs.GetLen(); j++, i++)
temp[i] = rhs[i-itsLen];
temp[totalLen]='\0';
*this = temp;
}
//Overloaded
C++ operator << - declared as a friend
function above inside the class.
//Returns an object of type ostream.
ostream &
operator<<
( ostream &
theStream, String &
theString)
{
theStream <<
theString.itsString;
return theStream;
}
//ConstructorCount is static, so it must be defined
here outside the class.
int String::ConstructorCount = 0;
void main()
{
String string1;
String string2;
string1 = "Those slick NSA G-men. They\'re always
watching us!";
string2 = string1;
cout << endl << endl;
cout << "The contents of string1 are:\n" << string1;
cout << endl << endl;
cout << "The contents of string2 are:\n" << string2;
cout << endl << endl;
} |
Output:
Default string constructor.
Default string constructor.
String(char*) constructor
String destructor
The contents of string1 are:
Those slick NSA G-men. They're always watching us!
The contents of string2 are:
Those slick NSA G-men. They're always watching us!
String destructor
String destructor |
In this example, we have defined a
header class to act as our own string class. Though not exactly
the standard library string class, this give us a glimpse of what goes
on behind the scenes of the standard string class. Notice that all
copying and sorting is done through loops that cycle through array
elements, one by one. This illustrates the concept that a string
is composed of a collection of char values stored as elements in an
array. You will notice a new keyword, "friend".
Let's talk about that for a minute.
Friend classes - Friend classes are for classes created together
or entwined in a set. Creating friend classes expose member data
or functions to another class that should only be visible to objects of
that class. It revokes the "private:" declaration.
Friendship is not transferred, inherited or commutative. Use
friend classes sparingly, use public accessor methods instead whenever
possible.
Friend functions - Friendship can be granted to functions of a
class, rather than a whole class. It is better to declare member
functions of the other class to be friends rather than the entire class.
In this situation, we declared the overloaded
operator<< function a friend class so that it could be accessed
and manipulated by ostream. Also note
the static member variable "ConstructorCount" is declared. It is initialized
outside the class, as per static member data requirements. This
variable is incremented as each string constructor is called.
In the class listed below, we will
make use of our custom String class. To compile this project, you
will need to create a project with 1 source file and 1 header file.
Add the header file and call it "StringClass.h", and paste all of the
code in the program listed in the table above into this file, minus the
main() function and all of the code between its brackets. Use
#include to add <iostream.h> and <string.h>. Then add the
source file - call it whatever you wish. Then paste the code in
the table below into that second file. It will contain our main()
function. Use #include to include the
"StringClass.h" header file you created previously. Then compile
and link the project.
#include
"StringClass.h"
class Employee
{
public:
//Overloaded constructors
Employee();
Employee(char *,
char *,
char *,
long);
//Destructor
~Employee();
//Copy constructor
Employee(const Employee&);
Employee & operator= (const Employee &);
//Accessor
methods
const String
& GetFirstName()
const {
return itsFirstName; }
void SetFirstName(const String
& fName) { itsFirstName = fName; }
const String
& GetLastName()
const {
return itsLastName; }
void SetLastName(const String
& lName) { itsLastName = lName; }
const String
& GetAddress()
const {
return itsAddress; }
void SetAddress(const
String & address) { itsAddress = address; }
void SetSalary(long salary) { itsSalary = salary; }
long GetSalary()
const {
return itsSalary; }
private:
String itsFirstName;
String itsLastName;
String itsAddress;
long itsSalary;
};
//Overloaded constructor definitions
Employee::Employee():
itsFirstName(""),
itsLastName(""),
itsAddress(""),
itsSalary(0)
{}
Employee::Employee(char * firstName, char
* lastName,
char * address,
long salary):
itsFirstName(firstName),
itsLastName(lastName),
itsAddress(address),
itsSalary(salary)
{}
//Copy constructor definition
Employee::Employee(const Employee
& rhs):
itsFirstName(rhs.GetFirstName()),
itsLastName(rhs.GetLastName()),
itsAddress(rhs.GetAddress()),
itsSalary(rhs.GetSalary())
{}
//Destructor definition
Employee::~Employee() {}
//Overloaded assignment operator =. Copies all
class's data members to object passed in.
Employee & Employee::operator= (const Employee
& rhs)
{
if(this == &rhs)
return *this;
itsFirstName = rhs.GetFirstName();
itsLastName = rhs.GetLastName();
itsAddress = rhs.GetAddress();
itsSalary = rhs.GetSalary();
return
*this;
}
int main()
{
Employee Charles("Charles","Germany","44 Alamanda Drive", 20000);
Charles.SetSalary(50000);
String LastName("Gaines");
Charles.SetLastName(LastName);
Charles.SetFirstName("Robert");
cout << endl << endl;
cout << "Name: ";
cout << Charles.GetFirstName().GetString();
cout << " " << Charles.GetLastName().GetString();
cout << ".\nAddress: ";
cout << Charles.GetAddress().GetString();
cout << ".\nSalary: " ;
cout << Charles.GetSalary();
cout << endl << endl;
return 0;
} |
Output:
Name: Robert Gaines
Address: 44 Alamanda Drive
Salary: 50000
Here, an Employee object is created and 4 values are passed in to initialize it.
The Employee accessor method, SetSalary(), is called with a
const value of 50000. Then a string
is created and and initialized using a C++ string constant. This string object is then used as an argument to
the SetLastName() accessor method. SetFirstName() is called with another string constant.
Notice that Employee does not have a function "SetFirstName()" that takes a character string as its argument.
Instead, SetFirstName() requires a const string reference.
The compiler resolves this, since it knows how to make a string from a constant character string. It was told how to do this
when we overloaded the assignment operator with the function:
Employee & operator= (const Employee &);. Although string objects and
data members are CONTAINED in the Employee class, employee objects do not have
any special access to them. The accessor functions of the string class must provide the interface,
as the data members are private.
Filtering access to contained members - The string class provides the operator+.
Then the Employee class blocks the operator+ from being called on Employee objects by declaring that all string accessors, such as "GetFirstName()", return a
const reference. Operator+ can't be a const function because it changes the object it is called on. Therefore
the following would cause a compile time error:
String buffer =
Charles.GetFirstName() + Charles.GetLastName();
The accessor method
GetFirstName() returns a const string, and we can't call operator+ on a const object. To fix this,
we must overload the GetFirstName() function to be non-const. Changing the return value is not sufficient, to overload the function name
we must also change the constancy of the function itself. Example:
const String & GetFirstName() const { return itsFirstName; }
//overloaded String and GetFirstName() { return itsFirstName; }
//overloaded
The
cost of containment - The user of the Employee class pays a price in
memory and performance each time a string object is constructed or a copy of
Employee is made. This is another issue of
copying by value vs. copying by reference.
When Employee objects are passed by value, all their CONTAINED string objects are copied as well, and
their copy constructors are called. This is expensive, it takes memory and time. When
Employee objects are passed by reference/pointer, this overhead is saved. Work hard NEVER to pass anything larger than 4 bytes by value.
If it exceed 4 bytes, try to pass it by reference.
©2004 C. Germany
|