Introduction
Data-encapsulation is key to the functionality of many high level or later generation programming languages such as C++ and Java. It is simply a means of creating “containers” called objects, and holding multiple variables of varying data types called member variables inside of those containers.
C++ offers three methods of data-encapsulation; struct, union and class. This is where C++ become object oriented.

  1. Struct and Union
    • Common uses
    • Declaration and syntax
    • Accessing member variables
    • Accessing pointer member variables
  2. Classes
    • Member functions
    • Levels of access: public vs. private vs. protected
    • Data hiding
    • Declaring and defining a class
    • Scope resolution operator
    • Conditional preprocessor directives
    • Constructors and destructors
  3. The relationship between class, struct and union
    • Defaulting to public or private
    • Programming standards
  4. Advanced methodology
    • Initializer lists
    • Overloading operators
    • Templated classes

1. Struct and Union
A struct and a union are fairly similar, so they will be treated as one entity. A struct is often useful to hold records of data, like a bank statement, or creating real-world objects, like dates and time. The syntax is as follows.

struct {
float savings;
float checking;

float mortgage;
float interest;
float fees;
} BankStatement;

union {
short month;
short day;
int year;
} DATE;

typedef struct {
int hour;
int min;
int sec;
char AmPm;
} TIME;

A struct can also have a struct object as a member variable.

typedef struct {
bool deposit;
bool withdraw;
float Amount;

float newBalance;
DATE date;
TIME time;
} Transaction;

There are no limits to how many, or what data type a struct can hold. Remember though, a struct can become quite large sometimes because it’s size in memory is that of the sum total of the memory size of all of it’s member variables.

Now that we have all of these objects, what do we do with them? Accessing a member variable is very simple and uses dot-notation: object.member_variable

TIME newTime() {
TIME time;
cout << "Hour: ";
cin >> time.hour;

cout << "Minute: ";
cin >> time.min;

//... yadda yadda yadda
return time;
}

Voilà! That was easy, now lets take a look at pointer objects. When any object, a struct, union, or class, is declared as a pointer, it no longer uses dot-notation, but pointer-notation: object->member_variable. The arrow, or “pointer” is used to signify the difference between a normal object and a pointer.

DATE newDate() {
DATE date { 07, 06, 2002 };
/* A struct can be initialized in this manner.
month=07, day=10, year=2002

*/
char dummy;
cout << "Enter new date (mm/dd/yyyy): ";
cin >> date.month >> dummy >> date.day >> dummy >> date.year;

return date;
}

As an alternative, the above code can request the date format to be (mm dd yyyy) and eliminate the need for the char dummy variable.

2. Classes
A class is where C++ become the most object oriented that it can. Classes offer many abilities and methods of storing, handling, and retrieving data. A class is very similar to a struct in that it can have member variables, but it can also have member functions. These are functions within the class. Member functions are called the same way member variables are:

object.member_function(...)

class CAT {
int lives;
int getLives(); //returns the number of lives
};

In a class, as well as in a struct, there are levels of access, they are public, private, and protected. We wont worry about protected access, thought, since that pertains to inheritance, and is out of the scope of this tutorial.

Anything that is in the public section of the class can be referenced and changed by anything outside of the class like a normal variable. In the private section, opposite is true. Nothing but member functions can access, change or call member variables or functions.

class CAT {
public:
int getLives(); //returns the number of lives
private:
int lives;
};

Data hiding is the principle of using private member variables within a class. Standards dictate that classes use private member variables whenever possible, and to create member functions to access and change their values. This way, there is no ambiguous access to them, and it is all done through the member functions. All member variables and functions in a class default to private unless otherwise instructed.

Now that we have declared the class, we must define it’s member functions. There are three ways to do this; within the class declaration, outside of the class declaration but in the same file, or outside of the class declaration in a separate file.

The first method is the easiest, but is only suggested for small functions that would be inline otherwise.

class CAT {
public:
int getLives() { return lives; }
private:
int lives;
};

The line

int getLives() { return lives; }

is the same as

inline int getLives() { return lives; }

since member functions defined inside of a class defaults to inline.

The second method is easy but will involve a few extra steps and a new operator called the scope resolution operator.

class CAT {
public:
int getLives(); //returns the number of lives
private:
int lives;
};

int CAT::getLives() {
//returns the number of lives
return lives;
}

The scope resolution operator is the :: that you saw. This tells the compiler that the function getLives() is a member function of the CAT class.

class_name::member_function(...)

Usually, and to standard, a class is contained two separate files outside of the main program code. The class declaration should be in a header file with the name of the class, “cat.h” The header file will be included in the main program code. A separate file will contain the class definitions, following the naming standards of the first file, but will be a cpp file instead, “cat.cpp” When both the class declaration and definition is in one file, then it should be a header file.

//cat.h
class CAT {
public:
int getLives(); //returns the number of lives

private:
int lives;
};

//cat.cpp
#include "cat.h"
int CAT::getLives() {
//returns the number of lives
return lives;
}

//main.cpp
#include "cat.h"

int main() {
//....
CAT myKittie;
//...
}

The line

#include "cat.h"

in the cat.cpp file tells the compiler where to look for the class declaration.

It is a good idea to make use of conditional preprocessor or precompiled directives. These are simply statements preceded by the # sign. In the class header file, these directives should be used to eliminate the possibility of a class being compiled twice, which will cause compilation errors. It’s quite simple, take a look.

//cat.h
#ifndef _CAT_HEADER_
#define _CAT_HEADER_

class CAT {
public:
int getLives(); //returns the number of lives

private:
int lives;
};
#endif

Now the class file can only be compiled once. This is very useful when writing programs that have files like in the following scheme.

//main.cpp
#include "utils.h"
#include "cat.h"

int main() {
...
return 0;
}
//utils.h
#include "cat.h"

// ...functions and what-not...

Every class automatically has built into it a construct and a destructor. The purpose of a constructor is to initialize any member variables that a class may have and a destructor is useful for doing any clean-up necessary; for example, if the class was dealing with sockets, it could make sure that they were all closed before destroying the socket object. Now, lets add a constructor and destructor, and make our cat class example more realistic.

//cat.h
#ifndef _CAT_HEADER_
#define _CAT_HEADER_

class CAT {
public:
CAT(char *newName, int newLives = 9); // Constructor with default value of newLives being 9

int getLives(); // returns the number of lives
void setLives(int newLives); // allows the programmer to change the amount of lives of the cat

void meow(); // makes the cat meow

char *getName(); // return the cat's name
void setName(char *newName); // allows the programmer to change the cat's name

~CAT(); // Destructor.
private:
char *name;
int lives;
};

#endif

The statement

int newLives = 9

inside the constructor is simply a way of providing a default value for newLives. This eliminates the need, in some cases, to overload a function. The default value, however, is only provided in the function declaration or prototype, and not in it’s actual definition as you will see. Also, notice the destructor and the fact that it has no parameters. This is because destructors take none and can also not be overloaded. The constructor is called when the object is declared and the destructor is called when the object goes out of scope, or if it was created with the new operator and is used with the delete operator.

Now lets define our CAT class in the .cpp file.

//cat.cpp
#include "cat.h"
#include

CAT::CAT(char *newName, int newLives) {
// Constructor with default value of newLives being 9
name = newName;
lives = newLives;
}

int CAT::getLives() {
//returns the number of lives
return lives;
}

void CAT::setLives(int newLives) {
// allows the programmer to change the amount of lives of the cat
lives = newLives;
}

void CAT::meow() {
// makes the cat meow
cout << "Meow";
}

char *CAT::getName() {
// return the cat's name
return name;
}

void CAT::setName(char *newName) {
// allows the programmer to change the cat's name
name = newName;
}

CAT::~CAT() {
// Destructor
cout << name << " had " << lives << " but now it's dead";
lives = 0;
name = "dead";
}

Notice that the constructor and destructor don’t have anything proceeding CAT::

Now that we’ve created our cat class, let’s implement it in a mock program.

//mock.cpp
#include "cat.h"

//iostream need not be included since it is included in cat.cpp

int main(void) {
CAT kittie("charley",5),
cat("puss-n-boots");

cat.setName(kittie.getName()); // sets the name of cat to "charely"
kittie.setName("puss-n-boots");

kittie.setLives(cat.getLives());

cat.meow();

return 0;
}

3. The relationship between class, struct and union
The separating factor between a struct and a union is that a struct can also have member functions just like a class. The difference between a struct and a class is that all member functions and variables in a struct are by default public, but in a class, they default to private as previously discussed.

It is often a good idea to use constructors to initialize the member variables of a struct. Other than that, though, it is against current standards, and usually looked down upon, to use functions in a struct, and is usually considered just being lazy.

4. Advanced methodology
Constructors also support initializer lists. These lists are simply another way to initialize the member variables of a class.

CAT::CAT(char *newName, int newLives):
name(newName), lives(newLives) {
// Constructor with default value of newLives being 9
}

This works for all primitive data types as well as user defined types and objects.

There are also ways to overload operators. We’ll cover the most commonly used ones first, but here is a list of operators that you can overload
Equating and math operators

+= + ++
-= - –
/= / =
*= *

Boolean operators

==
!=
>=
<=
(bool)
!

Accessing operators

[]

I/O stream operators
>>
<<

Bitwise operators

^ ^=
| |=
& &=
~
<<
>>

Allocation operators

new
delete

Member function/variable accessing operators

->
->*

Data-type operators

char double unsigned
int short unsigned int (etc)
float long long double (etc)

It is often times necessary to overload the equals operator for a given class because it makes the object much more “real” in the sense that the programmer can treat the class object more like a primitive variable. It also makes the source code easier to follow (and type) because you only have to have an equals sign, rather than a value.

//cat.h
const CAT& operator =(CAT newCat);
//cat.cpp

const CAT& CAT::operator =(CAT newCat) {
//copy one CAT to another
name = newCat.name;
lives = newCat.lives;
return *this;
}

Although name and lives are both private member variables, since the function is a member of the cat class, it can access the private member variables of another CAT object.

That was easy, but what’s the “*this” all about? That is called, rightly enough, the this operator and is simply a reference to the object (e.g.: this cat object). It’s purpose in this case, although it has others, is to enable multiple assignment statements by returning a constant reference to the CAT object, like in the following code.

CAT cat1, cat2, kitte("stupid cat",1);
cat1 = cat2 = kittie;

The scope of this tutorial does not extend into the depth of covering all of the possible operator overloads as there are a good deal of them, but the prototype or declaration of each will be given

Equating and math operators

const CAT &operator+= (const CAT & str); // same for -= /= and *=
CAT operator+ (const CAT &lhs, const CAT &rhs); // same for - / and *
//post operators

CAT &operator++(int dummy);
CAT &operator--(int dummy);
//pre operators

CAT &operator++();
CAT &operator--();

The post operators take an integer argument to signify that they are post operations.

object++; // post
--object; // pre

Comparison operators

bool operator== (const CAT &lhs, const CAT &rhs);
bool operator!= (const CAT &lhs, const CAT &rhs);
bool operator< (const CAT &lhs, const CAT &rhs);
bool operator<= (const CAT &lhs, const CAT &rhs);
bool operator> (const CAT &lhs, const CAT &rhs);
bool operator>= (const CAT &lhs, const CAT &rhs);
bool operator bool();
bool operator! ();

The last two comparison operator overload allow the CAT object to be used in if statements as follows.

CAT cat;
if (cat) {} // operator bool()
if (!cat) {} // operator!

Accessing operators

char operator[](int k);

I/O stream operators

ostream &operator<< (ostream &os, const CAT &str);
istream &operator>> (istream &is, CAT &str);

Bitwise operators

CAT operator^ (const CAT &lhs, const CAT &rhs); // same for | ~ and &
CAT operator^= (const CAT &lhs, const CAT &rhs); // same for |= and &=
CAT operator<< (const CAT &lhs, const CAT &rhs);
CAT operator>> (const CAT &lhs, const CAT &rhs);

Allocation operators

void operator new();
void operator delete();

Member function/variable accessing operators

void operator->();
void operator->*();
// The . and .* operators cannot be overloade

Data-type operators

void operator char();
void operator *char();
void operator const char(); // Et cetera
// This above applies to all other defined data types, primative or not.

That is the general idea, albeit limited as it is in this tutorial, behind overloading operators in C++. Not only can you overload an operator for a class, but you can also do it with a struct as well.

Another advanced concept is creating templated classes. These classes are often useful for creating vector or array classes, but because of the depth of the subject, they too will not be covered in tutorial.

If you understood what was covered here, then you have a decent grounding in classes and the object oriented side of C++. It is suggested that you build upon that by reading more specific tutorials on object oriented C++. If you have any comments, questions, or corrections, please don’t hesitate to leave it here.

Tags: , , ,

2 Responses to “Struct, Union and Class”

  1. 網路行銷 says:

    Actually genuinely great weblog article which has received me considering. I by no means looked at this from the stage of look at.

  2. Great site. A lot of useful information here. I’m sending it to some friends!

Leave a Reply

You must be logged in to post a comment.