Show Menu
Cheatography

C++ Cheatsheet, Class Object-Oriented Programming Cheat Sheet (DRAFT) by

Schema Riassuntivo delle Implementazioni di Classi in C++ (OOP).

This is a draft cheat sheet. It is a work in progress and is not finished yet.

Suddiv­isione tra file .h e .cpp

File Header (.h)
Contiene la dichia­razione della classe, ovvero la sua interf­accia pubblica e privata. In questo file si defini­scono gli attributi e i prototipi delle funzioni membro.

File di Implem­ent­azione (.cpp)
Contiene il codice effettivo delle funzioni dichiarate nel file .h. Qui vengono implem­entati i metodi della classe.

Person.h

#ifndef PERSON_H
#define PERSON_H

#include <string>

class Person {
private:
    std::string name;
    int age;

public:
    // Constructor
    Person(std::string n, int a);

    // Getter methods
    std::string getName() const;
    int getAge() const;

    // Setter methods
    void setName(std::string n);
    void setAge(int a);

    // Display method
    void display() const;
};

#endif
Utilizzo di header guard per evitare che il file venga incluso più volte

Person.cpp

#include "Person.h"
#include <iostream>

// Constructor
Person::Person(std::string n, int a) : name(n), age(a) {}

// Getter methods
std::string Person::getName() const {
    return name;
}

int Person::getAge() const {
    return age;
}

// Setter methods
void Person::setName(std::string n) {
    name = n;
}

void Person::setAge(int a) {
    age = a;
}

// Display method
void Person::display() const {
    std::cout << "Name: " << name << ", Age: " << age << std::endl;
}

main.cpp

#include <iostream>
#include "Person.h"

int main() {
    // Create an instance of Person
    Person p1("Alice", 25);

    // Display initial values
    p1.display();

    // Modify attributes
    p1.setName("Bob");
    p1.setAge(30);

    // Display updated values
    p1.display();

    return 0;
}
Name: Alice, Age: 25
Name: Bob, Age: 30
Name: Charlie, Age: 20
Student ID: 12345

Incaps­ula­mento

L'inca­psu­lamento è il principio OOP che nasconde l'impl­eme­nta­zione di una classe e fornisce un'interf­accia pubblica per interagire con essa.

✔ Protegge i dati dall'a­ccesso non autori­zzato.
✔ Permette di modificare l'impl­eme­nta­zione senza cambiare l'inte­rfaccia pubblica.
✔ Aumenta la modularità e la manute­nib­ilità del codice.

Tipo di Dato Astratto (ADT - Abstract Data Type)

Un Tipo di Dato Astratto (ADT) è un modello di dati che definisce solo il compor­tamento (inter­faccia) senza rivelarne i dettagli interni.

ADT → Descrive solo cosa fa una struttura dati, senza preocc­uparsi di come è implem­entata.
Incaps­ula­mento → Protegge i dati interni e li rende access­ibili solo attraverso metodi contro­llati.

Membri di una classe

I membri di una classe sono le componenti che defini­scono le sue caratt­eri­stiche e il suo compor­tam­ento. Si dividono in due categorie princi­pali:

Membri Dati (Attri­buti)
Sono le variabili che rappre­sentano lo stato dell'o­ggetto.

Membri Funzione (Metodi)
Sono le funzioni che defini­scono il compor­tamento della classe, operando sui suoi membri dati.
Specif­­ic­atori di accesso
private: access­ibili solo all'in­terno della classe.
protected: access­ibili nella classe e nelle sue derivate.
public: access­ibili da qualsiasi parte del programma.

Specif­icatori di accesso predef­initi

La differenza principale tra
struct
e
class
riguarda gli specif­icatori di accesso predef­initi:

-
struct
→ Accesso predef­inito: public
-
class
→ Accesso predef­inito: private

Scope delle Classi

Scope (ambito) di una classe determina dove e come i suoi membri possono essere access­ibili e utiliz­zati. Il concetto di scope è fondam­entale per gestire la visibilità e l'orga­niz­zazione del codice.

Dentro una classe, i membri possono essere definiti con tre specif­icatori di accesso:
- public → Access­ibile ovunque nel programma.
- protected → Access­ibile solo dalle classi derivate.
- private → Access­ibile solo all'in­terno della stessa classe.

Le classi possono essere definite dentro uno spazio dei nomi (names­pace) per organi­zzare meglio il codice ed evitare conflitti.

Metodi Interni

I metodi sono definiti dirett­amente all'in­terno della dichia­razione della classe nel file header (
.h
).
✅ Vantaggi: Il compil­atore può ottimi­zzare il codice (inline).
❌ Svantaggi: Il file header diventa più grande e potrebbe causare ricomp­ila­zioni non necess­arie.
Se sono semplici e corti, per sfruttare la compil­azione
inline
.

Metodi Esterni

I metodi sono dichiarati nel file header (
.h
) ma implem­entati separa­tamente in un file
.cpp
.
✅ Vantaggi: Migliora la separa­zione tra dichia­razione e implem­ent­azione, riduce la necessità di ricomp­ila­zione.
❌ Svantaggi: Il compil­atore potrebbe non ottimi­zzarli come funzioni inline.
Se sono più complessi o se si vuole mantenere il codice più organi­zzato.

Membro statico

class Counter {
public:
    static int count;  // Membro statico (condiviso)
};

int Counter::count = 0; // Definizione del membro statico

Metodo virtuale

Un metodo virtuale è un metodo dichiarato nella classe base con la parola chiave
virtual
, che può essere sovras­critto nelle classi derivate. Permette il polimo­rfismo dinamico, in modo che, quando viene chiamato tramite un puntatore o un riferi­mento alla classe base, venga eseguita l'impl­eme­nta­zione della classe derivata, se presente. Questo meccanismo consente di definire compor­tamenti diversi a seconda del tipo reale dell'o­ggetto.

Membri Speciali

Costru­ttori: inizia­lizzano gli oggetti della classe.
Operatori sovrac­car­icati: ridefi­niscono operatori standard (
+
,
=
,
<<
, ecc.).
Costru­ttore di copia (
ClassN­ame­(const ClassN­ame­& other)
)
Operatore di assegn­azione (
ClassN­ame­& operat­or=­(const ClassN­ame­& other)
)
Distru­ttore (
~Class­Name()
)
Costru­ttore di sposta­mento (
ClassN­ame­(Cl­ass­Nam­e&­& other) noexcept
)
Operatore di assegn­azione per sposta­mento (
ClassN­ame­& operat­or=­(Cl­ass­Nam­e&­& other) noexcept
)

Funzione Membro combine come operatore +=

#include <iostream>

class Counter {
private:
    int value;

public:
    // Costruttore
    Counter(int v = 0) : value(v) {}

    // Funzione membro combine() in stile +=
    Counter& combine(const Counter& other) {
        this->value += other.value; // Aggiunge il valore dell'altro oggetto
        return *this; // Restituisce l'oggetto stesso
    }

    // Metodo per visualizzare il valore
    void display() const {
        std::cout << "Value: " << value << std::endl;
    }
};
La funzione membro
combine()
può essere progettata per modificare l’oggetto stesso e restituire una referenza ad esso, proprio come l’oper­atore
+=
.

Name Lookup

Il name lookup (ricerca dei nomi) è il processo con cui il compil­atore trova la dichia­razione di una variabile, funzione o classe. Questo meccanismo è fondam­entale per compre­ndere come vengono risolti i nomi nei diversi scope e nei contesti di eredit­arietà, namespace e classi annidate.

Nelle classi, il compil­atore cerca i nomi in questo ordine:
1️⃣ Classe derivata
2️⃣ Classe base
3️⃣ Namespace globale (se necess­ario)
La ricerca dei nomi segue la regola dell'a­mbito più vicino (scope resolu­tion):
1️⃣ Il compil­atore cerca il nome nel blocco locale.
2️⃣ Se non lo trova, cerca negli scope superiori (funzione, classe, namespace).
3️⃣ Se non esiste nel file, dà un errore di compil­azione.

Membri di Tipo Puntatore

#include <iostream>

class Person {
private:
    std::string* name;  // Puntatore a una stringa dinamica

public:
    // Costruttore
    Person(const std::string& n) {
        name = new std::string(n);  // Allocazione dinamica
    }

    // Distruttore
    ~Person() {
        delete name;  // Libera la memoria
    }

    void display() const {
        std::cout << "Name: " << *name << std::endl;
    }
};

int main() {
    Person p("Alice");
    p.display();
    return 0;  // Il distruttore libera la memoria
}
Sono utilizzati per gestire risorse dinamiche come memoria allocata su heap, file, socket, ecc. Se non gestiti corret­tam­ente, possono causare memory leaks e dangling pointers.

new
alloca memoria per name.
✔ delete nel distru­ttore evita memory leaks.

Uso di std::u­niq­ue_ptr

#include <iostream>
#include <memory>  // Per unique_ptr

class Person {
private:
    std::unique_ptr<std::string> name;

public:
    Person(const std::string& n) : name(std::make_unique<std::string>(n)) {}

    void display() const {
        std::cout << "Name: " << *name << std::endl;
    }
};

int main() {
    Person p1("Alice");
    Person p2 = std::move(p1);  // Usa il move constructor di unique_ptr

    p2.display();  // Output: Name: Alice
    // p1.display();  // ❌ Errore: p1 è stato svuotato

    return 0;
}
Con smart pointers, non dobbiamo gestire
new
e
delete
manual­mente.

✔ Gestisce automa­tic­amente la memoria (non serve delete).
✔ Evita shallow copy, perché
unique_ptr
non è copiabile.
 

this in Funzioni Membro Costanti

In una funzione membro dichiarata
const
,
this
diventa un puntatore a un oggetto costante (
const Person*
), quindi non si possono modificare i dati membri.

Esempio di Method Chaining con this

class Person {
private:
    std::string name;
    int age;

public:
    Person(std::string n, int a) : name(n), age(a) {}

    // Metodi che ritornano *this per concatenare chiamate
    Person& setName(std::string n) {
        this->name = n;
        return *this;
    }

    Person& setAge(int a) {
        this->age = a;
        return *this;
    }

    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

int main() {
    Person p1("Alice", 25);

    // Method chaining grazie a 'this'
    p1.setName("Bob").setAge(30).display(); // Output: Name: Bob, Age: 30

    return 0;
}
this
è spesso usato per ritornare l’oggetto corrente e permettere la chaining dei metodi.

Puntatore Implicito this

Il puntatore
this
è un puntatore implicito che ogni oggetto di una classe possiede e che punta a se stesso.

✔ È dispon­ibile automa­tic­amente in tutti i metodi non statici di una classe.
✔ Contiene l'indi­rizzo dell'o­ggetto corrente.
✔ Viene utilizzato per distin­guere membri della classe da parametri con lo stesso nome e per ritornare l’oggetto corrente in metodi concat­enati (method chaining).

Costru­ttori Sovrac­car­icati

#include <iostream>

class Person {
private:
    std::string name;
    int age;

public:
    // 1️⃣ Costruttore predefinito
    Person() : name("Unknown"), age(0) {}

    // 2️⃣ Costruttore con un parametro
    Person(std::string n) : name(n), age(0) {}

    // 3️⃣ Costruttore con due parametri
    Person(std::string n, int a) : name(n), age(a) {}

    // Metodo per mostrare i dati
    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};
Il sovrac­carico dei costru­ttori permette di definire più costru­ttori nella stessa classe, ognuno con parametri diversi.

Se una classe non ha un costru­ttore definito esplic­ita­mente, il compil­atore genera automa­tic­amente un costru­ttore predef­inito sintet­izzato.
- Non prende parametri.
- Non inizia­lizza esplic­ita­mente i membri della classe (se non con valori predef­initi dei tipi).
- Viene creato automa­tic­amente solo se non ci sono altri costru­ttori definiti.

Costru­ttore di Default Esplicito (= default)

class Example {
public:
    Example() = default; // Richiede al compilatore di generare il costruttore di default
};
Possiamo chiedere al compil­atore di generare esplic­ita­mente un costru­ttore predef­inito usando
= default
che funziona esatta­mente come il costru­ttore predef­inito sintet­izzato.

Uso di = delete

l'oper­atore
= delete
impedisce l'uso di funzioni o operatori specifici.

= delete
impedisce l’uso di funzioni o operatori specifici.
✔ Blocca la copia e l'asse­gna­zione, utile per classi con gestione esclusiva delle risorse.
✔ Evita conver­sioni implicite indesi­derate.
✔ Può impedire l'allo­cazione dinamica (
new
).

Copy & Move Semantics

#include <iostream>
#include <cstring> // Per strcpy, strlen

class Person {
private:
    char* name;

public:
    // 🏗 1️⃣ Costruttore normale
    Person(const char* n) {
        name = new char[strlen(n) + 1];
        strcpy(name, n);
        std::cout << "Constructor called for " << name << std::endl;
    }

    // 📝 2️⃣ Costruttore di copia (Deep Copy)
    Person(const Person& other) {
        name = new char[strlen(other.name) + 1]; // Nuova allocazione
        strcpy(name, other.name);
        std::cout << "Copy constructor called for " << name << std::endl;
    }

    // 🔄 3️⃣ Operatore di assegnazione (Deep Copy)
    Person& operator=(const Person& other) {
        if (this != &other) { // Evita auto-assegnazione
            delete[] name; // Libera memoria esistente
            name = new char[strlen(other.name) + 1];
            strcpy(name, other.name);
        }
        std::cout << "Copy assignment operator called for " << name << std::endl;
        return *this;
    }

    // 🚀 4️⃣ Costruttore di spostamento (Move Constructor)
    Person(Person&& other) noexcept {
        name = other.name; // Prende il puntatore
        other.name = nullptr; // Resetta il vecchio oggetto
        std::cout << "Move constructor called" << std::endl;
    }

    // ➡️ 5️⃣ Operatore di assegnazione per spostamento (Move Assignment)
    Person& operator=(Person&& other) noexcept {
        if (this != &other) {
            delete[] name; // Libera la memoria esistente

            name = other.name; // Prende la risorsa
            other.name = nullptr; // Resetta l'oggetto originale
        }
        std::cout << "Move assignment operator called" << std::endl;
        return *this;
    }

    // 🗑 Distruttore
    ~Person() {
        delete[] name;
        std::cout << "Destructor called" << std::endl;
    }

    void display() const { std::cout << "Person: " << (name ? name : "Empty") << std::endl; }
};

int main() {
    Person p1("Alice");
    Person p2 = p1; // Chiamata al costruttore di copia
    Person p3("Bob");

    p3 = p1; // Chiamata all'operatore di assegnazione

    Person p4 = std::move(p1); // Chiamata al costruttore di spostamento
    p3 = std::move(p2); // Chiamata all'operatore di assegnazione per spostamento

    return 0;
}
1️⃣ Costru­ttore di copia → Usato quando
p2 = p1
; per creare una copia separata.
2️⃣ Operatore di assegn­azione → Usato quando
p3 = p1
; per assegnare i dati.
3️⃣ Costru­ttore di sposta­mento → Usato con
std::m­ove(p1)
; per evitare la copia.
4️⃣ Operatore di assegn­azione per sposta­mento → Usato con
p3 = std::m­ove(p2)
;.
5️⃣ Distru­ttore → Libera la memoria quando gli oggetti escono dallo scope.

Getter & Setter

#include <iostream>

class Person {
private:
    std::string name;

public:
    // Setter
    void setName(std::string n) { name = n; }

    // Getter
    std::string getName() const { return name; }
};

int main() {
    Person p;
    p.setName("Alice");
    std::cout << "Name: " << p.getName() << std::endl; // Output: Name: Alice
    return 0;
}
✔ Mantengono l'inca­psu­lam­ento.
✔ Possiamo validare i dati prima di modifi­carli.

Classi e Funzioni Amiche (friend)

#include <iostream>

class Person {
private:
    std::string name;

public:
    Person(std::string n) : name(n) {}

    // Dichiarazione della funzione amica
    friend void showPerson(const Person& p);
};

// Definizione della funzione amica
void showPerson(const Person& p) {
    std::cout << "Friend Function - Name: " << p.name << std::endl;
}

int main() {
    Person p("Bob");
    showPerson(p); // Output: Friend Function - Name: Bob
    return 0;
}
Le classi amiche e funzioni amiche possono accedere ai membri privati di una classe senza bisogno di getter­/se­tter.

✔ Una funzione amica non è un membro della classe, ma può accedere ai suoi dati privati.
✔ Può essere una funzione normale o un metodo di un'altra classe.

Eredit­arietà

Tipo di Eredit­arietà
Membri public
Membri protected
Membri private
public
Rimangono public
Rimangono protected
Non access­ibili
protected
Diventano protected
Rimangono protected
Non access­ibili
private
Diventano private
Diventano private
Non access­ibili
L'ered­ita­rietà permette di creare una nuova classe (
classe derivata
) basata su un'altra (
classe base
).

Esempio: Eredit­arietà public cpp Copia Modifica

#include <iostream>

class Person {
protected: // Accessibile dalle classi derivate
    std::string name;

public:
    Person(std::string n) : name(n) {}

    void display() const { std::cout << "Name: " << name << std::endl; }
};

// Classe derivata
class Student : public Person {
private:
    int studentID;

public:
    Student(std::string n, int id) : Person(n), studentID(id) {}

    void show() const {
        display(); // Chiamata al metodo della classe base
        std::cout << "Student ID: " << studentID << std::endl;
    }
};

int main() {
    Student s("Alice", 1234);
    s.show(); 
    // Output:
    // Name: Alice
    // Student ID: 1234
    return 0;
}
✔ Student eredita Person con eredit­arietà public, quindi display() è access­ibile.
✔ name è protected, quindi access­ibile nella classe derivata.

Esempio: Eredit­arietà private cpp Copia Modifica

class Employee : private Person {
private:
    int employeeID;

public:
    Employee(std::string n, int id) : Person(n), employeeID(id) {}

    void show() const {
        display(); // Possibile perché ereditiamo 
private
(membri public diventano private)         std::cout << "Employee ID: " << employeeID << std::endl;     } }; int main() {     Employee e("Bob", 5678);     e.show();     // e.display(); ❌ ERRORE! (diventa private in Employee)     return 0; }
✔ Con private, i membri
public
della classe base diventano privati in
Employee
.
display()
può essere usato solo dentro
Employee
, ma non dall'e­sterno.

Eredit­arietà Multil­ivello

#include <iostream>

class Person {
protected:
    std::string name;
public:
    Person(std::string n) : name(n) {}
    void display() const { std::cout << "Name: " << name << std::endl; }
};

// Student eredita da Person
class Student : public Person {
protected:
    int studentID;
public:
    Student(std::string n, int id) : Person(n), studentID(id) {}
};

// GraduateStudent eredita da Student
class GraduateStudent : public Student {
private:
    std::string thesisTitle;
public:
    GraduateStudent(std::string n, int id, std::string thesis)
        : Student(n, id), thesisTitle(thesis) {}

    void show() const {
        display(); // Da Person
        std::cout << "Student ID: " << studentID << std::endl;
        std::cout << "Thesis: " << thesisTitle << std::endl;
    }
};

int main() {
    GraduateStudent g("Bob", 5678, "AI Research");
    g.show();
    return 0;
}
Una classe può ereditare da una derivata, propagando i membri lungo la gerarchia.

Gradua­teS­tudent
eredita da
Student
, che eredita da
Person
.
display()
è chiamato da
Gradua­teS­tudent
, anche se appartiene a Person`.
studentID
è protetto, quindi access­ibile da
Gradua­teS­tudent
, ma non da
main()
.

Eredit­arietà Multipla

#include <iostream>

class Athlete {
protected:
    std::string sport;
public:
    Athlete(std::string s) : sport(s) {}
    void showSport() const { std::cout << "Sport: " << sport << std::endl; }
};

class Student {
protected:
    int studentID;
public:
    Student(int id) : studentID(id) {}
    void showID() const { std::cout << "Student ID: " << studentID << std::endl; }
};

// Classe che eredita sia da Athlete che da Student
class StudentAthlete : public Athlete, public Student {
public:
    StudentAthlete(std::string s, int id) : Athlete(s), Student(id) {}

    void show() const {
        showSport();
        showID();
    }
};

int main() {
    StudentAthlete sa("Basketball", 2023);
    sa.show();
    return 0;
}
Studen­tAt­hlete
eredita sia da
Athlete
che da
Student
.
✔ Può chiamare i metodi di entrambe le classi (
showSp­ort()
e
showID()
).