Basics of C++ Class

C++ class is a custom data type user defines, which is called user-defined type.
A data type in C++ represents certain types of data.
For example, there are built-in types such as int and double which represent integer and decimal numbers.
Declaring a variable with a data type requires memory and you can perform operations such as addition, subtraction, multiplication, and division with it.
You can also use those variables as function arguments, parameters, and return values.
In that sense, you can define your own data type to operate like built-in data type variables.

Why do we need a user-defined type?

A user-defined type is really necessary when you have to write a program that deals with real-world problems.

Let’s say you are trying to write a program that manages the money that includes coins per person.
You will need to know the number of dollars, quarters, dimes, nickels, and pennies.
For that, you will at least need five integers and you have to carry all those variables in the program which is very error-prone.

It may not be a big problem if you only need to manage money for one person.
But what if you have thousands of people?
It will be impossible to maintain with plain old data types like int.

That’s where a user-defined type becomes the hero.
In this case, the user-defined type will include dollars and all the coins but will act like just one data type – Money.

Example

Let’s actually take a look at how you declare a user-defined type and use it.
I am going to use struct in this example.
(struct and class are really the same except one thing which I will go over later.)

#include <iostream>

using namespace std;

// struct or class is a keyword
// Money is the name of the type.
// {} gives you the scope of the type
// There are five int variables which are called member variables.
struct Money
{
    int numDollars;
    int numQuarters;
    int numDimes;
    int numNickels;
    int numPennies;
};

// you can pass Money as function argument
void printTotalMoney(const Money &money)
{
    // you can use member variables just like built-in data type
    double totalMoney = 
        money.numDollars + 
        money.numQuarters * 0.25 + 
        money.numDimes * 0.1 +
        money.numNickels * 0.05 +
        money.numPennies * 0.01;
    
    cout << "I have total $" << totalMoney << endl;
}

int main()
{
    // declare a variable with data type Money and variable name myWage
    // just like int or double
    Money myWage;
    
    // use . operator in order to access member variable
    // there will be 10 dollars
    myWage.numDollars = 10;
    
    // 2 quarters
    myWage.numQuarters = 2;
    
    // 3 dimes
    myWage.numDimes = 3;
    
    // 4 nickels
    myWage.numNickels = 4;
    
    // 5 pennies
    myWage.numPennies = 5;
    
    printTotalMoney(myWage);
    return 0;
}

The example code declares a UDT called Money.
It has five variables which are called member variables. (member variable of data type called Money)
Once you declare a Money variable, you can use it just like any other variable with built-in types.
In the example code, I used ‘Money’ struct as a function argument which is also legal!
You can use ‘.’ operator in order to access member variables of Money.

One important thing we need to keep in mind is that a compiler needs to know the layout of the struct before it’s used.
In other words, you cannot declare a variable with Money data type before you declare Money struct.

class, private by default
struct, public by default

We observed It is much easier to have a Money data type instead of multiple integers.
However, there is a potential problem we need to deal with.

The problem is that anyone can access the member variables of Money.
It is fine in the above example code since it’s very simple and easy to fix even if there are any bugs.
However, once the program grows and as many functions may access the member variables, errors will likely happen and it will take a long time to debug where it went wrong.

Is there any way to control the access so it would be hard to make mistakes?
That’s where ‘class’ is coming to rescue.

By default, every member variable in a struct is public which means any code can access the members.
However, a class is the exact opposite of a struct.
Every member variable in a class is private by default that no one outside of the class can access the members.
In other words, private member variables can only be used by the class!

#include <iostream>

using namespace std;

// members in a class is private by default
// you don't have type private: explicitly in class
class Money
{
    int numDollars;
    int numQuarters;
    int numDimes;
    int numNickels;
    int numPennies;
};

// this is essentially the same as the above
// you have to type private: explicitly to make members private here
// struct Money
// {
// private:
//     int numDollars;
//     int numQuarters;
//     int numDimes;
//     int numNickels;
//     int numPennies;
// };

int main()
{
    Money temp;
    
    // error! can't access private members!
    // this will cause a compilation error
    // what can we do now???
    temp.numDollars = 10;
    
    return 0;
}

Then, how will Money be useful for us since we can’t access the member variables?
We can make the members public so it can be accessed.

// you have to type public: explicitly since members are 
// private by default in a class
class Money
{
public:
    int numDollars;
    int numQuarters;
    int numDimes;
    int numNickels;
    int numPennies;
};

// this is same as the above.
// members in a struct is public by default
// so you don't have to type public: explicitly like the above
// struct Money
// {
//     int numDollars;
//     int numQuarters;
//     int numDimes;
//     int numNickels;
//     int numPennies;
// };

But there isn’t any point in using class if it will have all public members.
In fact, it is highly discouraged to have public member variables in C++.

Member functions

Well, we need to think about what we actually want to do with the class.
It is clear that we want to be able to print total money and we also want to initialize the money to 0 and be able to add money.
That sounds like we will need some functions to do the work.

Just like we are able to have member variables inside class or struct, we can also have functions inside a class, which is called member functions.
We can also decide if member functions will be either public or private.
In this case, let’s implement some public functions to do the works.

Member function implementation inside class declaration

Member functions can be implemented inside the class declaration.

#include <iostream>

using namespace std;

class Money
{
    // remember! class members are private by default!
    // it means we don't even have to write the word 'private'
    int numDollars;
    int numQuarters;
    int numDimes;
    int numNickels;
    int numPennies;
    
// here we need to mark it public since member functions will 
// need to be exposed outside the class
public:
    // initialize the money to 0
    // this is to be called in the beginning
    void initMoney()
    {
        numDollars = 0;
        numQuarters = 0;
        numDimes = 0;
        numNickels = 0;
        numPennies = 0;
    }

    // add dollars, quarters, dimes, nickels and pennis
    void addDollars(int dollars)
    {
        numDollars += dollars;
    }

    void addQuarters(int quarters)
    {
        numQuarters += quarters;
    }

    void addDimes(int dimes)
    {
        numDimes += dimes;
    }

    void addNickels(int nickels)
    {
        numNickels += nickels;
    }

    void addPennies(int pennies)
    {
        numPennies += pennies;
    }

    // print total money
    void printTotalMoney()
    {
        // you can use member variables just like built-in data type
        double totalMoney = 
            numDollars + 
            numQuarters * 0.25 + 
            numDimes * 0.1 +
            numNickels * 0.05 +
            numPennies * 0.01;
        
        cout << "I have total $" << totalMoney << endl;
    }
};

int main()
{
    Money myWage;
    
    // you can call member functions using . operator
    myWage.initMoney();
    
    // add 10 dollars
    myWage.addDollars(10);
    
    // add 2 quarters
    myWage.addQuarters(2);
    
    // add 3 dimes
    myWage.addDimes(3);
    
    // add 4 nickels
    myWage.addNickels(4);
    
    // add 5 pennies
    myWage.addPennies(5);
    
    // print total money
    myWage.printTotalMoney();
    return 0;
}

By having member functions, you can control the access and thus protect the member variables.
You can only reset, add or print the money that it is impossible to apply other operations such as subtraction, multiplication, and division.

One thing I would like to note that it is not recommended to implement member functions inside the class declaration.

Member function implementation outside the class declaration

You can implement member function outside the declaration which is the more recommended way.
One thing to note is that you have to tell the function belongs to the class by typing ‘<class name>::’ before the function name.

#include <iostream>

using namespace std;

class Money
{
    // remember! class members are private by default!
    // it means we don't even have to write the word 'private'
    int numDollars;
    int numQuarters;
    int numDimes;
    int numNickels;
    int numPennies;
    
// here we need to mark it public since member functions will 
// need to be exposed outside the class
public:
    // initialize the money to 0
    // this is to be called in the beginning
    void initMoney();

    // add dollars, quarters, dimes, nickels and pennis
    void addDollars(int dollars);
    void addQuarters(int quarters);
    void addDimes(int dimes);
    void addNickels(int nickels);
    void addPennies(int pennies);

    // print total money
    void printTotalMoney();
};

// please note Money:: is necessary to indicate the function is member of the class
void Money::initMoney()
{
    numDollars = 0;
    numQuarters = 0;
    numDimes = 0;
    numNickels = 0;
    numPennies = 0;
}

void Money::addDollars(int dollars)
{
    numDollars += dollars;
}

void Money::addQuarters(int quarters)
{
    numQuarters += quarters;
}

void Money::addDimes(int dimes)
{
    numDimes += dimes;
}

void Money::addNickels(int nickels)
{
    numNickels += nickels;
}

void Money::addPennies(int pennies)
{
    numPennies += pennies;
}

void Money::printTotalMoney()
{
    // you can use member variables just like built-in data type
    double totalMoney = 
        numDollars + 
        numQuarters * 0.25 + 
        numDimes * 0.1 +
        numNickels * 0.05 +
        numPennies * 0.01;
    
    cout << "I have total $" << totalMoney << endl;
}

int main()
{
    Money myWage;
    
    // you can call member functions using . operator
    myWage.initMoney();
    
    // add 10 dollars
    myWage.addDollars(10);
    
    // add 2 quarters
    myWage.addQuarters(2);
    
    // add 3 dimes
    myWage.addDimes(3);
    
    // add 4 nickels
    myWage.addNickels(4);
    
    // add 5 pennies
    myWage.addPennies(5);
    
    // print total money
    myWage.printTotalMoney();
    return 0;
}

Constructor

Although member functions made the code better than before there is one thing that bothers – initMoney().
All it does is just initializing the money to 0 in the beginning and it’s very easy to forget calling the function.
What would happen if we forget to init? It really depends on a compiler and we might get some garbage dollars.
In addition to that, what if we want to start Money with a certain amount other than 0?
Do we need to implement another member function to set money?

What we want here is to initialize the money to a certain amount (or 0) when the Money variable is declared.
Fortunately, C++ provides a constructor that does the work!
A constructor is a function that will be called when a class becomes a being. (i.e., when Money variable is declared)
It is supposed to initialize all the member variables properly before referred.

#include <iostream>

using namespace std;

class Money
{
    // remember! class members are private by default!
    // it means we don't even have to write the word 'private'
    int numDollars;
    int numQuarters;
    int numDimes;
    int numNickels;
    int numPennies;
    
// here we need to mark it public since member functions will 
// need to be exposed outside the class
public:
    // constructor.
    // it doesn't return anything since object is already allocated
    // this is only supposed to initialize the members
    // please note that name of constructor must match name of the class
    Money(
        int dollars = 0,
        int quarters = 0,
        int dimes = 0,
        int nickels = 0,
        int pennies = 0);

    // add dollars, quarters, dimes, nickels and pennis
    void addDollars(int dollars);
    void addQuarters(int quarters);
    void addDimes(int dimes);
    void addNickels(int nickels);
    void addPennies(int pennies);

    // print total money
    void printTotalMoney();
};

// constructor can be implemented inside the class
Money::Money(
    int dollars,
    int quarters,
    int dimes,
    int nickels,
    int pennies)
{
    numDollars = dollars;
    numQuarters = quarters;
    numDimes = dimes;
    numNickels = nickels;
    numPennies = pennies;
}

void Money::addDollars(int dollars)
{
    numDollars += dollars;
}

void Money::addQuarters(int quarters)
{
    numQuarters += quarters;
}

void Money::addDimes(int dimes)
{
    numDimes += dimes;
}

void Money::addNickels(int nickels)
{
    numNickels += nickels;
}

void Money::addPennies(int pennies)
{
    numPennies += pennies;
}

void Money::printTotalMoney()
{
    // you can use member variables just like built-in data type
    double totalMoney = 
        numDollars + 
        numQuarters * 0.25 + 
        numDimes * 0.1 +
        numNickels * 0.05 +
        numPennies * 0.01;
    
    cout << "I have total $" << totalMoney << endl;
}

int main()
{
    Money myWage(10, 2, 3, 4, 5);
    
    // print total money
    myWage.printTotalMoney();
    return 0;
}

Recommended implementation of constructor

As you can see from the above, the constructor takes dollars, quarters, dimes, nickels, dimes, and pennies as its arguments and sets the money to them.
However, there is a better way to initialize – using an initializer list.

// Money::Money(
//     int dollars,
//     int quarters,
//     int dimes,
//     int nickels,
//     int pennies)
// {
//     numDollars = dollars;
//     numQuarters = quarters;
//     numDimes = dimes;
//     numNickels = nickels;
//     numPennies = pennies;
// }

// this is using initializer list and recommended way
Money::Money(
    int dollars,
    int quarters,
    int dimes,
    int nickels,
    int pennies) : 
    numDollars(dollars), 
    numQuarters(quarters), 
    numDimes(dimes), 
    numNickels(nickels), 
    numPennies(pennies)
{}

Why is using the initializer list recommended over the other one?
Please note that a class can have another class as a member variable and you will need to call the constructor of the class member.
Everyone tries this at least once but this doesn’t work!

class VendingMachine 
{
    Money money;
public:
    VendingMachine(int dollars, int quarters, int dimes, int nickels, int pennies)
    {
        // creates a local variable which is then ignored when it goes out of scope
        Money money(dollars, quarters, dimes, nickels, pennies);
        
        // OR
        // creates an un-named local Money object which is then tossed away
        Money(dollars, quarters, dimes, nickels, pennies);
        
        // this works, but why bother with it when ctor initializer will work for you?
        // this is really calling copy constructor which I am going to talk in other post
        money = Money(dollars, quarters, dimes, nickels, pennies);
    }
};

The initializer list will solve all the problems here.
It will actually call the constructor of Money properly during the construction of VendingMachine.

class VendingMachine 
{
    Money money;
public:
    VendingMachine(int dollars, int quarters, int dimes, int nickels, int pennies) : 
        money(dollars, quarters, dimes, nickels, pennies) // this is actually calling constructor of Money
    {}
};

Conclusion

We have taken a look at the basics of C++ user defined types – class and struct.
However, there are many more topics to learn about class as this post just explained really basic stuff.
I am going to have other posts that will explain const for member functions, operator overloading, copy constructor & destructor & assignment operator, inheritance & virtual, and pointers to member functions.
Thank you for reading the post!

Leave a Reply

Your email address will not be published. Required fields are marked *