Classes in C++


C++ is an object oriented programming language. Almost everything in C++ is an object, with its attributes and methods.

Class creates a user-defined data structure, which holds its own data members (attributes) and member functions (methods), which can be accessed and used by creating an instance of that class.

Classes are like a blueprint for objects outlining possible behaviors and states that every object of a certain type could have.

Classes are defined via the keyword class, like this:

// Define an empty class Employee
class Employee {

};

Objects/Instances

An Object is an instance of a Class. A class is like a blueprint while an instance is a copy of the class with actual values. It’s not an idea anymore, it’s an actual employee, like a person named "Andrew" who is 30 years old.

You can have many employees to create many different instances, but without the class as a guide, you would be lost, not knowing what information is required.

After a class is defined, we can create objects of the class. There are two ways of creating objects: locally and dynamically.

Local objects

Local objects behave the same as local variables. They only exist within the scope where they were created. We create a local object just like declaring a variable:

// Define an empty class Employee
class Employee {

};

int main() {
	Employee employee;
}

Dynamic objects

Dynamic objects are not bounded by the existance of the scope in which they were created. For example, dynamic objects created inside a function continue to exist even after the function returns.

A dynamic object is created using the new operator that returns a pointer to the newly constructed object:

// Define an empty class Employee
class Employee {

};

int main() {
	Employee *employee = new Employee();
}

Notice the *employee. This is beacause employee is not an Employee object, but a pointer the created object. We use this pointer to access and manipulate the actual object, just like we use a remote controller for a TV.

Attributes/Properties

A class attribute is a C++ variable that belongs to a class and is shared between all the objects of this class. Let's add some attributes to our Employee class:

class Employee {
	public:
		string name;
		int age;
};

int main() {
	Employee emp1; // local object
	
	emp1.name = 'Andrew';
	cout << emp1.name << "\n"; // prints 'Andrew'
	
	Employee *emp2 = new Employee(); // pointer to dynamic object
	
	emp2->age = 40;
	cout << emp2->age << "\n"; // prints 40
}

We added two properties: the string name and the int age. Then, we created two different Employee objects emp1 and *emp2.

The attributes of local objects can be accessed and modified using the dot operator (.), like we did above with emp1.name.

The attributes of dynamic objects can be accessed and modified through the pointer using the arrow operator (->), like we did above with emp2->age.

The keyword public used inside the class is called an access specifier. name and age are public attributes, whch means they can be accessed from anywhere in the code, not just inside the class code.

Constructors

In real life, we want to set the attributes specific to each object when creating it. For example, an employee named Andrew of 30 years old and another one named Mary of 25 years old. Here the constructor comes to rescue.

A constructor is a special type of method (function) which is used to initialize the instance members of the class.

In C++ the constructor method has the same name as that of its class and is always called when an object is created.

Let's add a constructor to our Employee class:

class Employee {
	public:
		string name;
		int age;
		// constructor:
		Employee(string name, int age) {
			this->name = name;
			this->age = age;
		}
};

int main() {
	Employee emp1('Andrew', 30); // emp1.name = 'Andrew', emp1.age = 30
	cout << emp1.name << "\n";
	
	Employee *emp2 = new Employee('Mary', 25); // emp2->name = 'Mary', emp2->age = 25
	cout << emp2->age << "\n";
}

The "this"

Every object in C++ has access to its own address through an important pointer called this pointer.

The this pointer is an implicit parameter to all member functions. Therefore, inside a member function, this may be used to access and manipulate the attributes of that object, like we did above with this->name = name.

Member functions

Member functions are functions that belong to the class:

class Employee {
	public:
		string name;
		int age;
		
		// constructor:
		Employee(string name, int age) {
			this->name = name;
			this->age = age;
		}
		
		//member functions:
		void printName() {
			cout << name << "\n"; // it is not mandatory to use "this" to access an attribute
		}
		
		void printEmployee() {
			printName();
			cout << age << "\n";
		}
};

int main() {
	Employee emp1('Andrew', 30);
	
	emp1.printEmployee(); // prints "Andrew", 30
	
	Employee *emp2 = new Employee('Mary', 25);
	
	emp2->printEmployee(); // prints "Mary", 25
}

As you can see, we access functions just like we access attributes: using the dot syntax (.) for local objects and using the arrow operator (->) for dynamic objects.

The function call uses the attributes of the object it was called on. That's why the first printEmployee() prints 'Andrew' and 30 and the second prints 'Mary' and 25.

Struct

Struct is just like a class in C++ but a little easier to use.

The only difference between a struct and class in C++ is the default accessibility of member variables and methods. By default, in a struct they are public; in a class they are private.

struct Employee {
	string name;
	int age;
	
	// constructor:
	Employee(string name, int age) {
		this->name = name;
		this->age = age;
	}
	
	//member functions:
	void printName() {
		cout << name << "\n";
	}
		
	void printEmployee() {
		printName();
		cout << age << "\n";
	}
};

int main() {
	Employee emp1('Andrew', 30);
	
	emp1.printEmployee();
	
	Employee *emp2 = new Employee('Mary', 25);
	
	emp2->printEmployee();
}


Assignment
Follow the Coding Tutorial and let's write some classes.


Hint
Look at the examples above if you get stuck.


Introduction

In this lesson, we will delve into the concept of classes in C++, a fundamental aspect of object-oriented programming (OOP). Understanding classes is crucial for creating complex and modular code. Classes allow us to model real-world entities and their interactions, making our code more intuitive and maintainable. This concept is widely used in software development, game development, and systems programming.

Understanding the Basics

Before diving into the complexities of classes, it's essential to grasp the basic concepts:

Let's start with a simple example of defining a class and creating an object:

class Employee {
    // Class definition
};

int main() {
    Employee employee; // Creating an object
}

Main Concepts

Now, let's explore the key concepts and techniques involved in working with classes:

Attributes

Attributes are variables that belong to a class. They define the properties of the objects created from the class. For example:

class Employee {
public:
    string name;
    int age;
};

In this example, the Employee class has two attributes: name and age.

Constructors

Constructors are special methods used to initialize objects. They have the same name as the class and are called automatically when an object is created:

class Employee {
public:
    string name;
    int age;

    // Constructor
    Employee(string name, int age) {
        this->name = name;
        this->age = age;
    }
};

The constructor initializes the name and age attributes when an Employee object is created.

Member Functions

Member functions are functions that belong to a class. They define the behaviors of the objects. For example:

class Employee {
public:
    string name;
    int age;

    // Constructor
    Employee(string name, int age) {
        this->name = name;
        this->age = age;
    }

    // Member function
    void printEmployee() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

The printEmployee function prints the details of an Employee object.

Examples and Use Cases

Let's look at some examples to understand how classes can be used in various contexts:

Example 1: Local Objects

int main() {
    Employee emp1("Andrew", 30); // Local object
    emp1.printEmployee(); // Output: Name: Andrew, Age: 30
}

Example 2: Dynamic Objects

int main() {
    Employee* emp2 = new Employee("Mary", 25); // Dynamic object
    emp2->printEmployee(); // Output: Name: Mary, Age: 25
    delete emp2; // Clean up dynamic memory
}

Common Pitfalls and Best Practices

When working with classes, it's essential to avoid common mistakes and follow best practices:

Advanced Techniques

Once you are comfortable with the basics, you can explore advanced techniques such as:

Inheritance

Inheritance allows a class to inherit attributes and methods from another class. This promotes code reuse and establishes a relationship between classes:

class Manager : public Employee {
public:
    string department;

    Manager(string name, int age, string department) : Employee(name, age) {
        this->department = department;
    }

    void printManager() {
        printEmployee();
        cout << "Department: " << department << endl;
    }
};

Polymorphism

Polymorphism allows methods to be used interchangeably, even if they belong to different classes. This is achieved through function overloading and overriding:

class Employee {
public:
    virtual void work() {
        cout << "Employee working" << endl;
    }
};

class Manager : public Employee {
public:
    void work() override {
        cout << "Manager working" << endl;
    }
};

int main() {
    Employee* emp = new Manager();
    emp->work(); // Output: Manager working
    delete emp;
}

Code Implementation

Here is a complete example demonstrating the use of classes, attributes, constructors, and member functions:

#include <iostream>
#include <string>
using namespace std;

class Employee {
public:
    string name;
    int age;

    // Constructor
    Employee(string name, int age) {
        this->name = name;
        this->age = age;
    }

    // Member function
    void printEmployee() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

int main() {
    Employee emp1("Andrew", 30); // Local object
    emp1.printEmployee(); // Output: Name: Andrew, Age: 30

    Employee* emp2 = new Employee("Mary", 25); // Dynamic object
    emp2->printEmployee(); // Output: Name: Mary, Age: 25
    delete emp2; // Clean up dynamic memory

    return 0;
}

Debugging and Testing

Debugging and testing are crucial for ensuring the correctness of your code:

Example test case:

#include <gtest/gtest.h>
#include "Employee.h"

TEST(EmployeeTest, Constructor) {
    Employee emp("John", 28);
    EXPECT_EQ(emp.name, "John");
    EXPECT_EQ(emp.age, 28);
}

TEST(EmployeeTest, PrintEmployee) {
    Employee emp("John", 28);
    testing::internal::CaptureStdout();
    emp.printEmployee();
    string output = testing::internal::GetCapturedStdout();
    EXPECT_EQ(output, "Name: John, Age: 28\n");
}

int main(int argc, char **argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

Thinking and Problem-Solving Tips

When approaching problems related to classes, consider the following strategies:

Conclusion

In this lesson, we covered the fundamental concepts of classes in C++, including attributes, constructors, member functions, and advanced techniques like inheritance and polymorphism. Mastering these concepts is essential for writing modular,