The three major concepts of C++ - Part 1: Classes






4.45/5 (52 votes)
Feb 26, 2003
10 min read

286075
This article explains the first major concept of C++: Classes
Introduction
This article explains what classes are, what they are useful for and their syntax. Classes are one of the three major concepts of C++, that's why every C++ programmer should know what they are and what they do. The same goes for the other two major concepts (inheritance and polymorphism) which will be explained in the other two parts.
Basic knowledge of classes
Once again, classes are one of the three most important things in C++. They are often used to store data useful for a specific part in your program. For instance: suppose there is a BIG company you run, this company has information about every employee working there. This information could be stored in a class named employee
, a single item of this information is called a member of employee
. This is basically what classes do here, store information on a particular group (at least in this example). But a class can do more, a class can contain member functions which are functions that operate within a class that alter or do something with the members of the class.
Syntax
Suppose the class I discussed earlier, the class would be called employee
. A class name is defined by the keyword class
and then the name. For our employee
class that would be:
class employee
Notice that there is no semicolon following employee
. This is because after the class name comes the class definition. Here's our class definition (including the class name):
const int SIZE = 40; class employee { private: char name[SIZE]; public: void set_name(char arg_name[SIZE]) { strncpy(name, arg_name, SIZE); } void display_name(void) { cout << name; } };
The first line has nothing to do with the class, it just specifies the number of characters name
has as it's maximum. class employee
tells the compiler the class name and that a class definition follows. Notice that the final delimiter has a semicolon after it, this is necessary to tell the compiler that the class definition ended. Now the keywords public
and private
are access specifiers, which I'll explain in a minute. name
is a member of the class while set_name
and display_name
are member functions of the class. This is the BASIC class syntax, classes can and often will do more than this, this is just to explain the basic syntax. I'll get on to constructors and the inline/outline part later.
Objects
Instances of classes are called objects. You can create an instance of a class by doing the following:
classname objectname;
This will allocate the memory needed for a class to exist because when the class is defined it will not be created yet. It will be created when you declare an instance of the class, thus the name object. To create an object of employee
we would do this:
employee mechanic;
Creating objects is done in main
, WinMain
, or any other function that is non-related to the class.
Using objects
The example above showed us how to create an object. This part of the article will show you how to use an object. We declared the object mechanic
, now we want to use it to set name
in our class. We can access a member or member function in the class by using the class member access operator or also called the dot operator (.
). However, we cannot get direct access to private
data, I'll explain more about that later on. So we'll have to use a member function. Using the dot operator we can invoke this. The syntax is as follows:
mechanic.set_name("John Doe");
This will call set_name
of the object mechanic
with the argument "John Doe". The following however (as said before) is NOT possible from within main
or WinMain
or any other function non-related to the class:
mechanic.name = "John Doe"; //wrong
This is because name
was defined under a private
access specifier which I'll discuss later. But this method does work if name
was declared under a public
access specifier.
Pointer notation
Well then what's the deal with a pointer to an object? It's practically the same except that the dot operator is being replaced by the member access operator (->
). Here's an example:
employee mechanic;
employee* ptrmechanic;
ptrmechanic = &mechanic;
ptrmechanic->set_name("John Doe");
This cannot be used by objects! Only by pointers to objects.
Access specifiers
public
and private
are access specifiers. Simply put, they exist for the sake of security. They specify the kind of access given to the members and member functions of the class. In our class, name
is a private
member of employee
while set_name
and display_name
are public
member functions of employee
. The access specifier public
gives the entire program access to the data that follows it. While the access specifier private
only gives the member functions in the class access to the data that follows it. There is one other access specifier: protected
but it only becomes useful in inheritance, which will be what part 2 is all about.
Syntax of access specifiers
The syntax of access specifiers is as follows:
access_specifier: //access_specifier data new_access_specifier: //new_access_specifier data
Note about new_access_specifier
, new
must not be treated as the standard keyword new
in C++. Why did I put two access specifiers there? Well, it's like this: after new_access_specifier
, the data that follows it has the access rules of new_access_specifier
. What I'm trying to say is: the access rules of the access specifier go for the data that follows it before a new access specifier is stated. So the following access rules go in our employee
class: name
is private
data, set_name
and display_name
are public
data.
Tips on when which access specifiers to use
When to use private:
//On class variables to keep them from alteration outside the class.
When to use protected:
//On private data return functions in combination with inheritance.
When to use public:
//On general member functions.
Note: these are ONLY tips and cases in which they are most used, however, you can use private
, protected
and public
anywhere in your class (not inside functions of course), just be well aware of what you use it for.
Constructors and destructors
Constructors
One of the many cases in which the constructor will be used is when it is preferred to have the members of the class being initialized to certain values immediately when the object is declared. Constructors are functions that have no return types and have exactly the same name as the class, this must always be how a constructor is defined! Why you ask? Because a constructor has no need to return a value, and by giving it the same name as the class the compiler can directly see if it is a constructor or not. However, there is always a constructor, even if you not define one. One of the constructor's functions (functions not to be taken as a programming term) is to allocate the amount of memory needed for the class when the object is declared. This is why there is always a constructor. Constructors can take arguments though. A good example of when an argument is taken by a constructor is the copy constructor which takes an object of the same class and copies it's members' values into it's own members' values.
Destructors
Destructors are used to destroy an object at the end of the program. Like the constructor, the destructor is always there, even if you not define one. BUT when there is a written constructor, it is a general good practice to also write a destructor for it.
Syntax
The best way to explain the syntax of constructors and destructors is by changing our employee
class definition:
const int SIZE = 40; class employee { private: char name[SIZE]; public: //constructor, note: wrong initializtion list (array as string) employee() : name("Unemployed") { } ~employee() //destructor { } void set_name(char arg_name[SIZE]) { strncpy(name, arg_name, SIZE); } void display_name(void) { cout << name; } };
Notice that after the employee()
part in the constructor there is a colon, after that comes the so called initialization list. This is used to initialize variables with certain values (which can of course also be the arguments of a constructor). The initialization list is widely used in constructors. In our employee
class the body of the constructor is empty, this does not have to be the case. You can also leave the initialization list out and put the part in the initialization list in the body, like this:
employee() //constructor { strncpy(name, "Unemployed", SIZE); }
This one will work UNLIKE the other one above, because when you're dealing with arrays you cannot specify an explicit initializer. In other words, the initialization list will not work on arrays treated as strings. I put it in anyway because I wanted to show you the basic use of the initialization list.
Syntax of the initialization list
: member(value), member(value) etc.
The member will be initialized with value, this can of course also be a variable or the return value of a function. The basic point is that the member will be initialized with the value between parentheses.
The destructor
The destructor destroys everything created by the class automatically, you can say it was a last will of the class. You do not have to put any delete
keywords in it, except if you allocated any memory with the new
keyword. For everything else it does the work for you. It has the same name as the class preceded by a tilde (~
). Unlike the constructor, the destructor takes no arguments. That sounds logical because you would never need arguments when everything is destroyed. However, the function body does not have to be empty in a destructor. Why you ask? Well there is one good reason why, for example when you want to debug your program you could state something like this:
~employee() //destructor { cout << "Destructor called."; /*the following would be required IF the variable name was allocated with the new keyword*/ delete[] name; }
There is always only one destructor in an entire class, unlike the constructors which there can be more of.
Argumented constructors
When a constructor is argumented, the syntax for the object declaration changes to:
employee(char arg_name[SIZE]) //constructor { strncpy(name, arg_name, SIZE); } employee mechanic; //wrong object declaration employee mechanic("John Doe"); //right object declaration
This is one useful method because now the set_name
function wouldn't be necessary anymore.
Multiple constructors
You can also define multiple constructors for use in one class, like this:
employee() //no argument constructor { strncpy(name, "Unemployed", SIZE); } employee(char arg_name) //one argument constructor { strncpy(name, arg_name, SIZE); }
You can also define multiple constructors which have the same amount of arguments, however, you cannot define multiple constructors that take the same types of arguments. The compiler simply wouldn't know which constructor to call. This way you can choose how to declare your object. The methods showed in the Argumented constructors part will now both work.
Inline and outline member functions
Note: the following part assumes you know what inline/outline means and what it does. Functions defined within a class (as in all the examples so far) are inline by default! As a matter of fact, the compiler makes it inline for you, whether you want to, or not. This is a disadvantage if you have a short member function that is called many times, or a very complex member function. There is however one solution to this, using the scope resolution operator. Consider our employee
class: the member function display_name
may be called many times. It is not crucial when this member function is defined inside the class, but it takes up a lot of memory because it's automatically defined as inline by the compiler. Here's how we solve this:
const int SIZE = 40; class employee { private: char name[SIZE]; public: employee() //constructor { strncpy(name, "Unemployed", SIZE); } ~employee() //destructor { } void set_name(char[SIZE]); //function declarations only void display_name(void); }; // the :: is the scope resolution operator void employee::set_name(char arg_name[SIZE]) { strncpy(name, arg_name, SIZE); } void employee::display_name(void) { cout << name; }
Now the functions are not inline because they are defined outside the class and only declared within it. However, do watch out for the following: if the function is declared inside the class using the keyword inline
and defined outside the class, the function still is inline, but doing this is wasting time because this is the same as declaring and defining the function inside the class.
The scope resolution operator
The scope resolution operator is used to incorporate the member function's name with the class name, or else the compiler won't have a single clue of which class the function was a member of. So it uses the scope resolution operator to bind these names, this way the compiler knows in which class to look for the declaration of the member function. However, from within main
, WinMain
, or any other non-related function of the class, the scope resolution operator cannot be used to call upon the member function of a class. For our employee
example:
employee::display_name(); // from within main would do no good
To do this generates a compile error because the compiler doesn't know with which object to access the member function. Just use the dot operator or the member access operator for pointers.
Final word
Well, I guess that wraps it up. This was the first major concept in C++, there will follow two more articles on the other two: inheritance and polymorphism.
History
- Article posted on 02/26/03