Classes are another concept of data structures. They can contain data members but also can contain functions as members. When you construct a class and list objects after its construction (object_names), you are creating objects, which are instances or entities of classes.

*credits to Geeksforgeeks*

If you recall about structures in Data Structure, you are aware that structs have member types and member names. In a C++ class, it has an access specifier preceding the member in the new line.

To construct a class, you can use either class or struct keyword. The syntax is almost identical as structs:

class class_name {
	access_specifier1:
		member1_type member1
	access_specifier2:
		member2_type member2
	access_specifier3:
		member3_type member3
	...
} object_names;

Similar to structures, classes have a body of members with an optional access specifier. Object names are quite optional, especially in this syntax, class name was specified.

Where members are accessed differently, classes have access specifiers that controls the members’ accessibility from the inside and outside:

  • private — only accessible within the same members of the class
  • public — accessible from anywhere
  • protected — accessible from the other members and derived members of class

The members without an access specifier above them automatically defaults to private access identifier. Take this code snippet as an example:

class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		void setCalories(int);
} apple;

apple is an object of class Fruit. Inheritably we could retrieve the calories of a fruit by doing apple.getCalories(). However, since we haven’t configured the number of calories in the apple, the return calorie value is 0.

If you notice the difference between variables and classes, their syntax of declaration are the same but with different types. Think of how variables in the beginning had the basic data types such as char, int, double, bool, & void . Class types are just another type.

Let’s say that the apple is 95 calories. (look at Nutritionix)

apple.setCalories(95);
printf("Calories of an apple is %d", apple.getCalories());

In the shortened version of the example, we used the setCalories function to configure the apple’s calories as 95. Of course it’s not possible to modify the object’s property calories to any number from the outside because calories is a private class.

apple.calories = 15; // Will not work

Instead, you have to configure that property under the namespace of that class through a function assigned inside the class. Here’s the rest of the example:

#include <stdio.h>
using namespace std;
 
class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		void setCalories(int);
} apple, bananas;
 
void Fruit::setCalories(int cal) {
	calories = cal;
}
 
int main() {
	apple.setCalories(95);
	printf("Calories of an apple is %d", apple.getCalories());
 
	return 0;
}

In the example above, we used the scope operator ( :: ) to define the setCalories function outside of the class.

To reinstate the issue about modifying the calories property, it is not possible to manually modify it outside of the class definition. Between lines 13-15, Fruits::setCalories is definitely an outside class member because it was okay to modify setCalories in a public class compared to private class properties calories, carbs, & sugar.


Constructor

A useful method of setting up objects of classes is making a constructor. Think of it as an initializer of the object before the object is created.

Nonetheless, to create an initializer of the class, you have to reference its name after the scope operator ( ::) followed by the class identifier. No type of function is needed for a class initializer.

Using the same example above,

#include <stdio.h>
using namespace std;
 
class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		void setCalories(int);
} apple;
 
void Fruit::setCalories(int cal) {
	calories = cal;
}
 
Fruit::Fruit(int cal) {
	calories = cal;
}
 
int main() {
	Fruit bananas (105);
	printf("Calories of a banana is %d", banana.getCalories());
 
	return 0;
}

In the example above, bananas was created with a list of arguments wrapper in parentheses to pursue the parameters of the initializer. These arguments were passed upon the initializer which was then read by the initializer to modify the properties of the class before returning the class object in line 18.


Overloading constructors

A constructor of the class can be overloaded with different versions that match up the parameters from the line of code that calls the class constructor.

#include <stdio.h>
using namespace std;
 
class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		void setCalories(int);
} apple;
 
void Fruit::setCalories(int cal) {
	calories = cal;
}
 
// Constructor without parameters
Fruit::Fruit() {
	calories = 5;
}
 
Fruit::Fruit(int cal) {
	calories = cal;
}
 
int main() {
	Fruit tomato;
	printf("Calories of a tomato is %d", tomato.getCalories());
	
	Fruit bananas (105);
	printf("Calories of a banana is %d", banana.getCalories());
 
	return 0;
}

See that in the example, when we created the object tomato, it called the first constructor in line 13 because of how the void parameters matched with the void arguments in line 22.

In line 25, the banana called for the second constructor because it matched the first parameter type with the first argument type.

The constructor without a set of parameters is called the default constructor because it’s called when the object is being initialized without given arguments. If you declare a class object with an empty set of functional form or arguments wrapped in parenthesis, it WILL NOT call for the first constructor because it is considered as a function declaration (without definition), not object declaration.

Fruit tomato; // calls for the default constructor
Fruit tomato(); // DOES NOT call for the default constructor

Uniform initialization

C++ has multiple syntaxes for class constructors. One of which, the most common used version, is the variable initialization syntax.

class_name object_name = init_value;

However, it only calls on the constructor with a single parameter. Another possible syntax is uniform initialization, where the functional form is no longer in parenthesis but curly braces {}.

class_name object_name { argument1, argument2, argument3, ... }

In an uniform initialization, the assignment operator (=) is optional.

Consider this example for a constructor with single parameter:

#include <iostream>
#include <stdio.h>
 
using namespace std;
 
class polygon {
    int sides;
    public:
        polygon(int s) { sides = s; };
        int degrees() { return ( sides - 2 ) * 180; };
};
 
int main()
{
    polygon rect = 4;
    printf("The number of degrees of a rectangle is %d", rect.degrees());
 
    return 0;
}

From the example above, you only see that an assignment operator is used in line 16. As mentioned previously about the two syntaxes—variable initialization & uniform initialization, you could rewrite line 16 three other ways:

polygon rect (4); // functional form
polygon rect = {4};
polygon rect {4};

Choosing different syntaxes for class is personal preference. Functional form is a most commonly used syntax for classes, although most new guides would prefer you to use the uniform initialization.


Member initialization in constructors

A class constructor is a function to initialize members of the class. The most common version of a class constructor is one with statements. Over time that method could become quite tedious.

For simplification, member initialization is a nice way of initializing members without statements by creating a colon (:) after the constructor’s definition, followed by a list of member initializations.

Let’s take this example, which most of us have known from the previous sections:

class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		void setCalories(int);
};
 
Fruit::Fruit(int cal) {
	calories = cal;
}

We could actually simplify lines 7, & 10-12 to create a constructor with member initialization. This time, we are going to initialize the other two variables.

class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories
		};
		Fruit(int cal, int carbs=0, int sugar=0) : calories(cal), carbs(carbs), sugar(sugar) {};
};

Alternatively, line 7 is just the same as:

Fruit(int cal, int carbs=0, int sugar=0) : calories(cal) { carbs = carbs, sugar = sugar; }

Or

Fruit(int cal, int carbs=0, int sugar=0) : calories(cal), carbs(carbs), { sugar = sugar; }

In certain aspects, default construction isn’t necessary because it’s a hassle to reinitialize class members in a function with a body of initializations and/or default constructor doesn’t exist for that class.

class Fruit {
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		Fruit(int cal, int carbs=0, int sugar=0) : calories(cal), carbs(carbs), sugar(sugar) {};
};
 
// If line 7 was written like: (unnecessary reinitialization)
Fruit::Fruit(int cal, int carbs=0, int sugar=0) {
	cal = cal;
	carbs = carbs;
	sugar = sugar;
};
 
Fruit pear; // Throws an error because there is no matching constructor without parameters (aka default constructor)

Here’s a real situation where member initializer list is necessary:

#include <iostream>
 
using namespace std;
 
class Shape {
	int width, length, height;
	public:
		double size() { return width*length*height; };
		Shape(int w, int l, int h) : width(w), length(l), height(h) {};
};
 
class Fruit {
	Shape size;
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		Fruit(int cal, int carbs=0, int sugar=0, int w=0, int l=0, int h=0) : calories(cal), carbs(carbs), sugar(sugar), size{w, l, h} {};
};
 
Fruit pomelo { 231, 0, 0, 6, 6, 12 };
 
int main()
{
    cout << "Calories in the pomelo is " << pomelo.getCalories();
 
    return 0;
}

As you can see, the example used class Shape as a member type of size in class Fruit. Inside the Fruit constructor, it became possible to call the Shape non-default constructor by constructing the member size with a list of arguments. Luckily, the method of calling a member constructor inside a class constructor is only done in a member initializer list.

Note that in line 15, you can use uniform initializer syntax by enclosing arguments in curly braces.


Pointers to classes

Similar to Pointers to structures, class objects could be pointed. The syntax is just as follows:

class_name * object_name;

Object members (members of the class object) are accessible through pointers when using the arrow operator (->). Here’s an example:

#include <iostream>
 
using namespace std;
 
class Shape {
	int width, length, height;
	public:
		double size() { return width*length*height; };
		Shape(int w, int l, int h) : width(w), length(l), height(h) {};
};
 
class Fruit {
	Shape size;
	int calories, carbs, sugar;
	public:
		int getCalories() {
			return calories;
		};
		Fruit(int cal, int carbs=0, int sugar=0, int w=0, int l=0, int h=0) : calories(cal), carbs(carbs), sugar(sugar), size{w, l, h} {};
};
 
Fruit pomelo { 231, 0, 0, 6, 6, 12 };
 
int main()
{
	Fruit * foo;
	foo = &pomelo;
	
    cout << "Calories in the pomelo is " << foo->getCalories();
 
    return 0;
}
expressioncan be read as
*xpointed to by x
&xaddress of x
x.ymember y of object x
x->ymember y of object pointed to by x
(*x).ymember y of object pointed to by x (equivalent to the previous one)
x[0]first object pointed to by x
x[1]second object pointed to by x
x[n](n+1)th object pointed to by x
imported from C++ documentation

Definition differences for class with struct and union

The other two keywords for class are struct and union.

  • struct: ALL class members are strictly public access
  • union: Unions have similar format as struct; even though they are classes, they could store one data member at a time