Structures (struct) and classes (class) are data types that can contain multiple variables (members) and related functions (methods).
Note
The main difference between them is that classes allow inheritance, polymorphism, and encapsulation, while structures do not. Additionally, structure members are public by default and class members are private by default.
Structure
Public by default, does not require member functions or constructors (though they can be added manually). Usually used to store simple data without needing encapsulation or complex methods.
struct Person {
std::string name; // Public member
private: // Private members
std::string address;
};Class
Private by default, can contain methods and constructors, and has access to inheritance, polymorphism, and encapsulation.
class Animal {
int id; // Private member
public: // Public members
std::string species;
int age;
};Constructors
The constructor initializes the object when created. It has the same name as the class and returns nothing.
class Person {
public:
std::string name;
Person() { name = "Unknown"; } // Default constructor
Person(std::string n) : name(n) {} // Constructor with parameter
Person(const Person& other) : name(other.name) {} // Copy
};Initialization list
Initializes members before executing the constructor body. It is the recommended way:
class Person {
std::string name;
public:
Person(std::string n) : name(n) {} // initializes name with n
};Pointers to objects
Animal a("Dog", 5);
std::cout << a.species; // We use . on objects
a.greet();
Animal* ptr = &a; // ptr is a pointer to object a
std::cout << ptr->species; // We use -> on pointers
ptr->greet();
std::cout << (*ptr).species; // Another way to access a member
(*ptr).greet(); // Another way to call a methodNote
To access members of an object use
.. To access members of a pointer to an object use->(which is equivalent to dereferencing and then using.).
Operator overloading
C++ allows defining how operators (+, ++, ==, <, <<, etc.) behave when used with objects of a class or structure.
This is done by defining a function named operator followed by the operator:
class Point {
public:
int x;
int y;
Point(int x, int y) : x(x), y(y) {}
Point operator+(const Point& other) {
return Point(x + other.x, y + other.y);
}
};Now you can use + directly with Point objects:
Point a(2, 3);
Point b(4, 5);
Point c = a + b;
std::cout << c.x << ", " << c.y;
// 6, 8Destructor
The destructor runs automatically when the object goes out of scope (or is deleted). It is used to free resources. It has ~ and the class name:
class Resource {
public:
Resource() { /* acquire the resource */ }
~Resource() { /* release the resource */ }
};Note
The destructor is called automatically when leaving scope, even if there is an exception. This is the basis of RAII.
Warning
If the class has a destructor, it is important to think about the class’s copy and move handling (Rule of Three). Otherwise there can be double frees or leaks.
Separating declaration and definition
Defining methods outside the class
Methods can be declared inside the class and defined later, outside of it, using Class::method. This works the same for class and struct:
class Person {
public:
std::string name;
Person(std::string n); // Declaration only
void greet(); // Declaration only
};
// Definition outside the class
Person::Person(std::string n) : name(n) {}
void Person::greet() {
std::cout << "Hello, I'm " << name << std::endl;
}Note
A method can only be defined once, but can be declared as many times as you want.
Different files
The normal way to organize a project is to separate declarations (what exists) from definitions (how it works) in different files. This way other files can use the class just by including the header, without including the implementation.
Header file .h
Contains only the declarations, includes the headers the interface needs, and uses include guards:
#ifndef PERSONA_H
#define PERSONA_H
class Person {
public:
std::string name;
Person(std::string n); // Declaration only
void greet(); // Declaration only
};
#endifWarning
Include guards (
#ifndef/#define/#endif) prevent the file from being included twice. Without them, the class would be declared repeatedly and cause a compilation error.#pragma onceis also valid.
Implementation file .cpp
Contains the definitions. Includes its own header to verify they match the declarations:
#include "Person.h"
Person::Person(std::string n) : name(n) {}
void Person::greet() {
std::cout << "Hello, I'm " << name << std::endl;
}Important
Standard library headers are included with
<...>and project headers with"...".
File using the class
Just needs to include the header to use the class:
#include "Person.h"
int main() {
Person p("Ana");
p.greet();
return 0;
}Compiling
You only need to pass the implementation files (.cpp) to the compiler.
g++ main.cpp Person.cpp -o program