Introduzione a C++
Antonio Cisternino
Presentazione a cura di Paolo Cignoni
Hello World
[Hello.java]
package hello;
// says that we are part of a package named hello
public class Hello // declare a class called Hello
{
public static void main(String args[]) // declare the function main
// that takes an array of Strings
{
System.out.println("Hello world!"); // call the static method
// println on the class System.out
// with the parameter "Hello world!"
}
}
[Hello.C]
#include <iostream>
using namespace std;
// include declarations for the "cout" output stream
// the cout stream is in the std namespace
// this tells the compiler to look in the std
// namespace, you can also write std::cout
int main(int argc, char *argv[])
{
// declare the function main that
// takes an int and an array of strings
// and returns an int as the exit code
cout << "Hello world!" << endl; // this inserts "Hello world!"
// and an newline character into the cout
// output stream
}
Differenze Salienti
Esistono funzioni globali (che non fanno
parte di alcuna classe): main()
Il C++ non ha il concetto dei packages
In c++ si deve dire esplicitamente in quali
file il compilatore deve cercare le
definizioni che gli servono:
#include<… >
Classi
[Foo.java]
public class Foo
// declare a class Foo
{
protected int m_num; // declare an instance variable of type
int
public Foo()
// declare and define a constructor for
Foo
{
m_num = 5;
// constructor initializes m_num variable
}
}
[Foo.H]
class Foo
// declare a class Foo
{
public:
// begin the public section
Foo();
// declare a constructor for Foo
protected:
// begin the protected section
int m_num;
// declare an instance variable of type
int
};
[Foo.C]
#include "Foo.H"
Foo::Foo()
{
m_num = 5;
}
// definition for Foo's constructor
// the constructor initializes the m_num
// instance variable
Differenze Salienti
I sorgenti C++ sono splittati in
header (.h): che contengono le dichiarazioni
delle classi (e quindi la loro interfaccia)
program files (.cpp) che contiene le definizioni
dei vari metodi delle classi (e quindi le loro
implementazioni)
[Foo.H]
class Foo {
public:
Foo();
~Foo();
int myMethod(int a, int b);
}; // note the semicolon after the class declaration!
[Foo.C]
#include "Foo.H"
#include <iostream>
Foo::Foo()
Foo
{
cout <<
int a =
cout <<
}
Foo::~Foo()
{
cout <<
endl;
}
// scope operator :: helps define constructor for class
"I am a happy constructor that calls myMethod" << endl;
myMethod(5,2);
"a = " << a << endl;
"I am a happy destructor that would do cleanup here." <<
int Foo::myMethod(int a, int b)
{
return a+b;
}
Sintassi
Notare l’uso dello scope operator ::
Serve a dire a quale classe appartiene il
metodo che sto definendo
Attenzione al ‘;’ dopo la dichiarazione di
classe!
Altrimenti può capitare che l’errore venga
segnalato molto dopo…
Costruttori e liste di inizializzatori
Come in Java, l’inizializzazione delle
variabili membro di una classe viene fatta
nel costruttore della classe.
Come in Java il costruttore può avere vari
parametri
A differenza di Java i membri di una classe
possono essere inizializzati prima della
chiamata del costruttore.
Meccanismo della lista di inizializzatori
Inizializer list
[Foo.H]
class Foo
{
public:
Foo();
protected:
int m_a, m_b;
private:
double m_x, m_y;
};
[Foo.C] // with initializer list
#include "Foo.H"
#include <iostream>
using namespace std;
Foo::Foo() : m_a(1), m_b(4), m_x(3.14), m_y(2.718)
{
cout << "The value of a is: " << m_a << endl;
}
// o equivalentemente
Foo::Foo()
{
m_a = 1; m_b = 4; m_x = 3.14; m_y = 2.718;
std::cout << "The value of a is: " << m_a << endl;
}
Distruttori
In uno dei precedenti esempi oltre al
costruttore era presente anche un metodo
Foo::~Foo
Questo metodo è chiamato distruttore non
ha parametri e viene invocato quando
un’istanza di una classe viene distrutta.
Come il costruttore non ritorna nulla
Overloading
Come in java è possibile avere più di una
funzione con lo stesso nome
C++ usa il tipo di parametri per decidere
quale funzione chiamare.
Attenzione, ai cast impliciti che il C++ può
fare e che potrebbero far scegliere al
compilatore un metodo al posto di un altro
Overloading e Cast impliciti
include <iostream>
using namespace std;
void Foo::print(int a)
{
cout << "int a = " << a << endl;
}
void Foo::print(double a)
{
cout << "double a = " << a << endl;
}
On an instance "foo" of type "Foo", calling
foo.print(5);
will output
int a = 5
whereas
foo.print(5.5)
will output
double a = 5.5
Parametri di default
class Foo
{
public:
Foo();
void setValues(int a, int b=5)
protected:
int m_a, m_b;
};
void Foo::setValues(int a, int b)
{
m_a=a;
m_b=b;
}
if we have an instance "foo" of class "Foo" and
we did the following:
foo.setValues(4);
it would be the same as if we had coded:
foo.setValues(4,5);
Parametri di default
Nota che i parametri senza default
DEVONO precedere tutti i parametri con
default
Non si può saltare parametri nel mezzo di
una chiamata
I parametri di default si specificano nel .h
e non nell .cpp
Ereditarietà

[Foo.H]
class A
{public:
A(int something);
};
class B : public A
{public:
B(int something);
};
[Foo.C]
#include "Foo.H"
#include <iostream>
using namespace std;
A::A(int something)
{
cout << "Something = " << something << endl;
}
B::B(int something) : A(something)
{
}
Ereditarietà
Simile a java
Normalmente è sempre public
Nota importante, per poter passare
parametri al costruttore della classe da cui
si deriva si deve utilizzare la sintassi della
initializer list
B::B(int something) : A(something)
Esiste anche l’ereditarietà multipla
class B : public A, public C
Funzioni Virtuali
public void someMethod() {
Object obj = new String("Hello");
String output = obj.toString(); // calls
String.toString(),
// not
Object.toString()
}
 Polimorfismo, java sa che obj è in realtà una stringa e
chiama il metodo giusto
 Il tipo dell’oggetto va controllato a runtime.
 Leggero overhead a runtime, contrario alla filosofia del
c++,
 In C++ di default non funziona come in java
 Se quando si subclassa, si fa override di una funzione
viene chiamata quella tipo della variabile usata…
Funzioni Virtuali
class A
{public:
A();
virtual ~A();
virtual void foo();
};
class B : public A
{public:
B();
virtual ~B();
virtual void foo();
};
Sintassi Virtual
Accesso a membri overridden: si usa lo scope
operator ::
Possibilità di definire classi non istanziabili (che
quindi servono solo a definire un’interfaccia) con
funzioni pure virtual (equivalente alla keyword
‘abstract’ in java)
class Foo
{
public:
virtual int abstractMethod() = 0;
}
// The "virtual" and "= 0"
// are the key parts here.
Virtual
#include "Foo.H"
#include <iostream>
using namespace std;
A::foo()
{
cout << "A::foo()" << endl;
}
B::foo()
{
cout << "B::foo() called" << endl;
A::foo();
}
So, if we have an instance "b" of class "B", calling
b.foo();
will output
B::foo() called
A::foo() called
Puntatori e Memoria
 In c++ le variabili si dichiarano (e si inizializzano)
come in java
int number=0;
 In realtà per tipi non primitivi le cose sono diverse…
 Ovvietà:
 La memoria è organizzata in celle, ognuna delle quali è
associata ad un numero unico detto indirizzo
 Ogni variabile è memorizzata in un certo numero di celle.
 Un puntatore è un indirizzo di memoria
 Un puntatore ad una variabile è l’indirizzo di memoria della
prima cella in cui la variabile è memorizzata.
 I puntatori sono variabili
Puntatori e Memoria
Come si dichiara un puntatore?
Operatore * tra il tipo e la variabile
E.g.
 int* myIntegerPointer;
È un puntatore ad intero, e.g. una variabile
che contiene l’indirizzo di una variabile di tipo
intero.
Puntatori e Memoria
Come si fa a far puntare un puntatore a
qualcosa?
(come si fa a far sì che una variabile
puntatore contenga come valore l’indirizzo
di un’altra variabile?)
Operatore &:
int* myIntegerPointer;
int myInteger = 1000;
myIntegerPointer = &myInteger;
Puntatori e Memoria
 Come si fa a modificare quel che punta un puntatore?
 (come si fa a modificare la variabile il cui indirizzo è
memorizzato in un certo puntatore (e non quindi il puntatore
stessso)?)
 Come si fa a dereferenziare un puntatore?
 Ancora lo * (inteso come operatore di
dereferenziazione)
int* myIntegerPointer;
int myInteger = 1000;
myIntegerPointer = &myInteger;
means "the memory address of myInteger."
*myIntegerPointer means "the integer at memory address
myIntegerPointer."
myIntegerPointer
Puntatori e Memoria
#include <iostream>
using namespace std;
int main(int argc, char **argv
int myInteger = 1000;
int *myIntegerPointer = &m
// print the value of the integer before changing it
cout << myInteger << endl;
// dereference ptr and add 5 to the referenced integer
*myIntegerPointer += 5;
// print the value of the integer after
// changing it through the pointer
cout << myInteger << endl;
}
Altri esempi
Cosa succede se si dereferenzia un
puntatore e si memorizza in un’altra
variabile?
 int myInteger = 1000;
//
value 1000
int* myIntegerPointer = &myInteger;
//
int mySecondInteger = *myIntegerPointer;
integer
//
the integer
//
pointer
set up an integer with
get a pointer to it
// now, create a second
whose value is that of
pointed to by the above
Cosa succede se cambio il valore di myInteger?
Cambia anche mySecondInteger?
#include <iostream>
using namespace std;
int main(int argc, char **argv) {
int myInteger = 1000;
int *myIntegerPointer = &myInteger;
// declare another integer whose value is the same
// at memory address <myIntegerPointer>
int mySecondInteger = *myIntegerPointer;
// print the value of the first integer before changing it
cout << myInteger << endl;
// dereference the ptr and add 5 to the referenced integer
*myIntegerPointer += 5;
// print the value of the integer after changing
// it through the pointer
cout << myInteger << endl;
// print the value of the second integer
cout << mySecondInteger << endl;
}
Si può avere più puntatori alla stessa
variabile.
Cambiando il valore della variabile
memorizzata a quel indirizzo, ovviamente il
cambiamento è visto da tutti i puntatori a
quella variabile
Puntatori a puntatori
Siccome i puntatori sono variabili se ne
può avere e memorizzare l’indirizzo
 int myInteger = 1000;
int* myIntegerPointer = &myInteger;
int** myIntegerPointerPointer;
;
myIntegerPointerPointer = &myIntegerPointer
 (*myIntegerPointerPointer) == myIntegerPointer ==
l’indirizzo di memoria di myInteger
 (**myIntegerPointerPointer) == quel che è memorizzato
all’indirizzo myIntegerPointer == myInteger
Puntatori a oggetti
 [Foo.H]
class Foo {
public:
Foo();
// default constructor
Foo(int a, int b); // another constructor
~Foo();
// destructor
void bar();
// random method
int blah;
// random public instance
variable
};
 in java
Foo myFooInstance = new Foo(0, 0);
 In c++
Foo* myFooInstance = new Foo(0, 0)
Puntatori a oggetti
 in java
Foo myFooInstance = new Foo(0, 0);
 In c++
Foo* myFooInstance = new Foo(0, 0)
Per usare l’oggetto creato (e.g. accedere ai suoi
metodi e membri pubblici) occorre
dereferenziarlo
(*myFooInstance).bar();
Oppure usando l’operatore freccia
myFooInstance->bar();
Istanze di oggetti
 In java il solo modo di creare oggetti e di fare new e
memorizzare il riferimento ottenuto in una variabile
 In C++ è possibile dichiarare (ed ottenere) oggetti senza
fare new esplicite o usare puntatori
Foo myFooInstance(0, 0);
 Dichiara una variabile di tipo foo e chiama il costruttore;
 Se si voleva usare il costruttore di default
Foo myFooInstance;
oppure equivalentemente:
Foo myFooInstance();
Istanze di oggetti
Per accedere a membri e funzioni pubbliche di
un istanza si fa come in java.
 myFooInstance.bar();
myFooInstance.blah = 5;
Istanze di oggetti possono essere create anche
senza associalre esplicitamente ad una variabile:
 // ... suppose the class Bar defines
// the method setAFoo(Foo foo) ...
Bar bar;
bar.setAFoo( Foo(5,3) ); // pass an instance of Foo
Istanze di Oggetti
Come i puntatori, le istanze possono
essere variabili locali o variabili membri di
una classe;
Il costruttore può esser chiamato nella
lista di inizializzatori del costruttore della
classe
[Bar.H]
#include "Foo.H" // must include Foo.H since
// we declare an instance of it
class Bar {
public:
Bar(int a, int b);
protected:
Foo m_foo; // declare an instance of Foo
};
[Bar.C]
Bar::Bar(int a, int b) : m_foo(a,b) // call Foo::Foo(int,int)
// and initialize m_foo
{
Foo fooLocal; // create another instance of Foo, this time
// as a local var
// do something with the two Foos, m_foo and fooLocal
}
References
Supponiamo di voler riferire un istanza di
un oggetto con più di un nome.
Una soluzione sono i puntatori
Una seconda soluzione, più sicura è di
usare i references
References
[main.C]
#include <iostream>
using namespace std;
int main(int argc, char **argv) {
int foo = 10;
int& bar = foo;
bar += 10;
cout << "foo is: " << foo << endl;
cout << "bar is: " << bar << endl;
foo = 5;
cout << "foo is: %d\n" << foo << endl;
cout << "bar is: %d\n" << bar << endl;
}
References
References sono come puntatori solo che:
Possono essere assegnato SOLO alla
creazione
Non possono essere null
Si accede al loro contenuto senza
operatori di dereferenziazione
References e classi
 Siccome i references possono essere assegnati solo alla
creazione, references che sono membri di una classe
devono essere assegnati nella lista di inizializzatori del
costruttore della classe
[Bar.H]
class Foo;
class Bar {
protected:
Foo & m_foo; // declare an reference to a bar
public:
Bar(Foo & fooToStore) : m_foo(fooToStore) {}
};
Da puntatori a variabili
Foo* fooPointer = new Foo(0, 0); // create a pointer to
// Foo and give it a value
Foo myFooInstance = *fooPointer; // dereference the
pointer
// and assign it to myFooInstance;
// A copy is made (!)
Modificando myFooInstance NON si modifica anche l’oggetto
puntato da fooPointer
Costruttore di copia
Particolare tipo di costruttore utilizzato
quando si vuole inizializzare un oggetto
con un altro dello stesso tipo.
Usato nel passaggio di parametri…
class Foo {
Foo(const Foo &classToCopy);
// copy constructor
};
Memory Management
Due grandi categorie di storage:
Local, memoria valida solo all’interno di un
certo scope (e.g. dentro il corpo di una
funzione), lo stack;
Global, memoria valida per tutta
l’esecuzione del programma, lo heap.
Local Storage
{
int myInteger; // memory for an integer allocated
// ... myInteger is used here ...
Bar bar; // memory for instance of class Bar
allocated
// ... bar is used here ...
}
‘{’ e ‘}’ sono i delimitatori di un blocco in c++,
Non è detto che corrisponda ad una funzione…
Non possiamo usare bar, o myInteger fuori dallo
scope del blocco!
Global Storage
Per allocare memoria nel global storage
(e.g. per avere puntatori ad oggetti la cui
validità persista sempre) si usa l’operatore
new.
[Bar.H]
class Bar {
public:
Bar();
Bar(int a);
void myFunction(); // this method would be defined
// elsewhere (e.g. in Bar.C)
protected:
int m_a;
};
Bar::Bar(){ m_a = 0;}
Bar::Bar(int a){ m_a = a;}
[main.C]
#include "Bar.H"
int main(int argc, char *argv[])
{ // declare a pointer to Bar; no memory for a Bar instance is
// allocated now p currently points to garbage
Bar * p;
{
// create a new instance of the class Bar (*p)
// store pointer to this instance in p
p = new Bar();
if (p == 0) {
// memory allocation failed
return 1;
}
}
// since Bar is in global storage, we can still call methods on it
// this method call will be successful
p->myFunction();
}
Delete esplicite
In java si alloca con new e la memoria
viene liberata automaticamente dal
garbage collector
In c++ NO. Ci deve pensare l’utente a
disallocare esplicitamente quel che ha
esplicitanmente allocato con una new
delete p; // memory pointed to by p is deallocated
Solo oggetti creati con new possono
essere deallocati con delete.
Memory Management
[Foo.H]
#include "Bar.H"
class Foo {
private:
Bar* m_barPtr;
public:
Foo() {}
~Foo() {}
void funcA() {
m_barPtr = new Bar;
}
void funcB() {
// use object *m_barPtr
}
void funcC() {
// ...
delete m_barPtr;
}
};
Memory Management
{
Foo
Foo myFoo; // create local instance of
myFoo.funcA(); // memory for *m_barPtr
is allocated
// ...
myFoo.funcB();
// ...
myFoo.funcB();
// ...
myFoo.funcC(); // memory for *m_barPtr
is deallocated
}
Memory Management
{
Foo myFoo;
//...
myFoo.funcB(); // oops, bus error in funcB()
myFoo.funcA(); // memory for *m_barPtr is
allocated
myFoo.funcA(); // memory leak, you lose track of
the memory previously
// pointed to by m_barPtr when
new instance stored
//...
myFoo.funcB();
}
// memory leak! memory pointed to by m_barPtr in
myFoo is never deallocated
Memory Management
Due grandi categorie di storage:
Local, memoria valida solo all’interno di un
certo scope (e.g. dentro il corpo di una
funzione), lo stack;
Global, memoria valida per tutta
l’esecuzione del programma, lo heap.
Local Storage
{
int myInteger; // memory for an integer allocated
// ... myInteger is used here ...
Bar bar; // memory for instance of class Bar
allocated
// ... bar is used here ...
}
‘{’ e ‘}’ sono i delimitatori di un blocco in c++,
Non è detto che corrisponda ad una funzione…
Non possiamo usare bar, o myInteger fuori dallo
scope del blocco!
Global Storage
Per allocare memoria nel global storage
(e.g. per avere puntatori ad oggetti la cui
validità persista sempre) si usa l’operatore
new.
[Bar.H]
class Bar {
public:
Bar();
Bar(int a);
void myFunction(); // this method would be defined
// elsewhere (e.g. in Bar.C)
protected:
int m_a;
};
Bar::Bar(){ m_a = 0;}
Bar::Bar(int a){ m_a = a;}
[main.C]
#include "Bar.H"
int main(int argc, char *argv[])
{ // declare a pointer to Bar; no memory for a Bar instance is
// allocated now p currently points to garbage
Bar * p;
{
// create a new instance of the class Bar (*p)
// store pointer to this instance in p
p = new Bar();
if (p == 0) {
// memory allocation failed
return 1;
}
}
// since Bar is in global storage, we can still call methods on it
// this method call will be successful
p->myFunction();
}
Delete esplicite
In java si alloca con new e la memoria
viene liberata automaticamente dal
garbage collector
In c++ NO. Ci deve pensare l’utente a
disallocare esplicitamente quel che ha
esplicitanmente allocato con una new
delete p; // memory pointed to by p is deallocated
Solo oggetti creati con new possono
essere deallocati con delete.
Memory Management
[Foo.H]
#include "Bar.H"
class Foo {
private:
Bar* m_barPtr;
public:
Foo() {}
~Foo() {}
void funcA() {
m_barPtr = new Bar;
}
void funcB() {
// use object *m_barPtr
}
void funcC() {
// ...
delete m_barPtr;
}
};
Memory Management
{
Foo myFoo; // create local instance of Foo
myFoo.funcA(); // memory for *m_barPtr is
allocated
// ...
myFoo.funcB();
// ...
myFoo.funcB();
// ...
myFoo.funcC(); // memory for *m_barPtr is
deallocated
}
Memory Management
{
Foo myFoo;
//...
myFoo.funcB(); // oops, bus error in funcB()
myFoo.funcA(); // memory for *m_barPtr is allocated
myFoo.funcA(); // memory leak, you lose track of the memory
previously
// pointed to by m_barPtr when new instance
stored
//...
myFoo.funcB();
}
// memory leak! memory pointed to by m_barPtr in myFoo
// is never deallocated
Costruttori e Distruttori
La soluzione corretta è quella di delegare
il compito di allocare e disallocare ai
costruttori e distruttori degli oggetti
[Foo.H]
#include "Bar.H"
class Foo {
private:
Bar* m_barPtr;
public:
Foo();
~Foo();
void funcA() {
...
}
void funcB() {
// use object *m_barPtr
}
void funcC() { ...
}
};
Foo::Foo(){
Foo::~Foo(){
m_barPtr = new Bar; }
delete m_barPtr; }
Istanze, puntatori e riferimenti
Piccolo riepilogo
 L’allocazione, disallocazione della memoria delle
istanze degli oggetti è gestita automaticamente.
Quando una variabile esce dal proprio scope la sua
mem è disallocata (anche se c’era un puntatore che la
riferiva)
 I puntatori ottenuti tramite new sono relativi a
porzioni di memoria di l’utente ha la responsabilità
 I riferimenti sono semplicemente nomi differenti con
cui si riferisce altre variabili/oggetti, quindi non si
può/deve gestirne direttamente la memoria (ma si
deve gestire la mem della variabile cui si riferiscono).
Parametri
In java il passaggio di parametri è sempre per
valore (del riferimento ad un oggetto), cioè si
può cambiare un oggetto usandolo come
argomento di una funzione
In c++ i parametri possono essere passati
per riferimento o per valore (default)
void IncrementByTwo(int foo) { foo += 2; } // foo non
cambia
void IncrementByTwo(int &foo) { foo += 2; } // foo
cambia
Alternativamente, si può ottenere lo stesso
effetto passando (per valore) un puntatore
void IncrementByTwo(int* fooPtr) { *fooPtr += 2; }
Valori di ritorno
I valori di ritorno di funzione possono
essere, al solito, istanze puntatori o
riferimenti
Errore frequente, restituire riferimenti o
puntatori a istanze locali alla funzione
Tali istanze sono distrutte al ritorno dalla
funzione, quindi si ha puntatori o
riferimenti a cose non più allocate
Errore return 1
[FooFactory.C]
#include "FooFactory.H"
#include "Foo.H"
Foo* FooFactory::createBadFoo(int a, int b) {
Foo LocInst (a,b); // creates an local instance of
class Foo
return & LocInst; // returns a pointer to this
instance
} // ERROR! LocInst leaves scope
// and it is destroyed!
Errore ritorno 2
Foo& FooFactory::createBadFoo(int a, int b) {
Foo aLocalFooInstance(a,b); //
instance
//
return aLocalFooInstance;
//
this
//
creates an local
of the class Foo
returns a reference to
instance
} // EEK! aLocalFooInstance leaves scope and is destroyed!
Soluzione corretta
Foo* FooFactory::createFoo(int a, int b) {
return new Foo(a,b); // returns a pointer to an
instance of Foo
}
Foo FooFactory::createFoo(int a, int b) {
return Foo(a,b); // returns an instance of Foo
}
morale:
MAI ritornare puntatori a oggetti che non sono stati
generati con new, a meno che non si sia estremamente
sicuri che le variabili cui si riferiscano non escano dallo
scope
Per chi viene dal C…
Differenze tra new e malloc
Malloc non conosce per che cosa serve la
memoria e quindi è l’utente a dover fare I
conti di quanta ne serve
Malloc non inizializza (calloc inizializza solo a
valori costanti),new chiama il costruttore per
ogni oggetto allocato
Similarmente delete chiama il distruttore per
ogni oggetto disallocato
MAI mescolare new e malloc…
New e delete di array
Obj *op = new Obj[20]; // allocates 20 Obj
Per ogni oggetto allocato viene chiamato il
costruttore
Notare che la delete deve essere fatta con
la delete per array
delete [] op;
Esempio
class Person
{
public:
Person()
{}
Person(char const *n, char const *a,
char const *p);
~Person();
char const *getName() const;
char const *getAddress() const;
char const *getPhone() const;
};
private:
// data fields
char *name;
char *address;
char *phone;
Class Person
Scopo del costruttore è inizializzare i campi dell’oggetto
#include "person.h"
#include <string.h>
Person::Person(char const *n, char const *a,
char const *p)
{
name
= strdupnew(n);
address = strdupnew(a);
phone
= strdupnew(p);
}
Strdup con new
Dove
char *strdupnew(char const *str)
{
return str ? strcpy(new char [strlen(str) + 1],
str) : 0;
}
questo per essere sicuri di non mescolare malloc e new…
(strdup usa malloc!)
#include "person.h"
#include <string.h>
Person::~Person()
{
delete name;
delete address;
delete phone;
}
Tipico uso
#include "person.h"
#include <iostream>
void showPerson()
{
Person
karel("Karel", “sdfdfdee", "038 420 1971"),
*frank = new Person("Frank", "Oostu", "050 403 2223");
cout << karel.getName()
<< ", " <<
karel.getAddress() << ", " <<
karel.getPhone()
<< endl <<
frank->getName()
<< ", " <<
frank->getAddress() << ", " <<
frank->getPhone()
<< endl;
delete frank;
}
Esempio2
Person::~Person()
{cout <<"Person destructor called"<< endl;}
int main()
{
Person *a = new Person[2];
cout << "Destruction with []'s" << endl;
delete [] a;
a = new Person[2];
cout << "Destruction without []'s" << endl;
delete a;
return 0;
}
Generated output:
Destruction with []'s
Person destructor called
Person destructor called
Destruction without []'s
Person destructor called
*/
Il precedente esempio generava memory
leaks, solo il distruttore del primo
elemento viene chiamato se si usa la
sintassi sbagliata della delete
Esempio3
Person::~Person()
{cout <<"Person destructor called"<< endl;}
int main()
{
Person
**a;
a = new Person* [2];
a[0] = new Person [2];
a[1] = new Person [2];
delete [] a;
return 0;
}
No Output!!
Nessun output perché ‘a’ è un array di
puntatori. La disallocazione di un
puntatore (e quindi di NON di un oggetto)
non comporta alcuna azione.
Ogni elemento dell’array andrebbe
disallocato singolarmente.
Operatore assegnamento
L’operatore di assegnamento di default in
C++ fa la copia byte a byte dei campi
delle due strutture.
Se si usa puntatori questo è assai
pericoloso
Operatore Assegnamento
void printperson(Person
{
Person tmp;
tmp = p;
cout << "Name:
"<<
"Address: "<<
"Phone:
"<<
}
const &p)
tmp.getName()
<< endl <<
tmp.getAddress()<< endl <<
tmp.getPhone() << endl;
Operatore Assegnamento
Alla fine p
contiene
puntatori a
memoria
disallocata!
Operatore Assegnamento
Il problema è dovuto
al fatto che
l’operatore
assegnamento ha
fatto la copy
bytewise dei
puntatori ignorando
il loro significato
Approccio
giusto:
Overloading Operatore =
In c++ si può fare overload dei vari
operatori
Sintassi
Basta definire una funzione chiamata
operator=(…)
Si può fare per I vari operatori
operator+()
Ecc.
Operator =
Person &Person::operator=(Person const &other)
{
if (this != &other)
{
delete address;
delete name;
delete phone;
address = strdupnew(other.address);
name = strdupnew(other.name);
phone = strdupnew(other.phone);
}
}
// return current object. The compiler will
// make sure that a reference is returned
return *this;
Note operator=
a=b è equivalente a a.operator=(b);
if(this!=other) serve per evitare
l’autoassegnamento (altrimenti facendo
p=p si distruggerebbe le stringhe prima di
poterle copiare)
Il valore di ritorno serve per
l’assegnamento multiplo a=b=c; (in C++
anche l’assegnamento è un expr.)
Costruttore di copia
Costruttori che hanno in ingresso un
riferimento ad un oggetto della stessa
classe
Serve per inizializzare una variabile con
un’altra
 class Person
{
public:
Person(Person const &other);
};
Costruttori di copia e
assegnamento
Entrambi devono duplicare un oggetto
Il costruttore di copia non deve disallocare
memoria (l’oggetto è stato appena creato)
Il costruttore di copia non deve fare
controlli di autoassegnamento (non si può
inizializzare una var con se stessa)
Costruttori di copia e parametri
void nameOf(Person p)
// no pointer, no
reference
{
// but the Person itself
cout << p.getName() << endl;
}
il costruttore di copia è chiamato quando
si passa un oggetto per valore
Costruttore di copia e return value
Person getPerson()
{
string name, address, phone;
cin >> name >> address >> phone;
Person
p(name.c_str(),
address.c_str(),phone.c_str());
return p;
}
// returns a copy of `p'.
il costruttore di copia è chiamato quando una
funzione restituisce un’istanza di un oggetto.
Const e parametri
 Quando modifica una dichiarazione di dati, const
specifica che l’oggetto o la variabile non è modificabile
 Quando segue la lista dei parametri di una funzione
membro, const specifica che la funzione non modifica
l’oggetto per cui è invocata.
const int i = 5;
i = 10; // Error
i++;
// Error
Const e puntatori
char *const aptr = mybuf; // Constant pointer
// non posso cambiare il valore (indirizzo)
// del puntatore
*aptr = 'a';
// Legal
aptr = yourbuf;
// Error
 un puntatore ad una variabile const può essere
assegnato solo ad un puntatore che è dichiarato const
const char *bptr = mybuf;// Pointer to constant data
// non posso cambiare il contenuto della locazione
// puntata.
*bptr = 'a';
// Error
bptr = yourbuf;
// Legal
 può essere usato per impedire ad una funzione di
cambiare un parametro passato per puntatore
Trucco leggere da destra a sinistra
Const e riferimenti
Un uso importante dei riferimenti costanti
è il loro uso nel passaggio di parametri
void printperson (Person const &p)
Si evita di invocare il costruttore di copia,
pur rimanendo sicuri che non modifica
l’oggetto
Morale
Se la nostra classe contiene puntatori
conviene sempre scrivere accuratamente
Costruttore
Costruttore di copia
Operatore di assegnamento
Distruttore
Template
I Template sono un meccanismo che permette di
definire funzioni e classi basate su argomenti e
oggetti dal tipo non specificato
template class <T> swap(T &a, T &b);
template class <T> class List {...};
Queste funzioni e oggetti generici diventano
codice completo una volta che le loro definizioni
sono usate con oggetti reali
Esempio di template di funzione
Scambio tra due oggetti generici:
template <class T> void swap(T &a, T &b){
T tmp = a;
a = b;
b = tmp;
}
int main(){
int a = 3, b = 16;
double d = 3.14, e = 2.17;
swap(a, b);
swap(d, e);
// swap (d, a); errore in compilazione!
}
return (0);
Template Osservazioni
Notare che la definizione di una funzione
template è simile ad una macro, nel senso che la
funzione template non è ancora codice, ma lo
diventerà una volta che essa viene usata
Il fatto che il compilatore generi codice concreto
solo una volta che una funzione è usata ha come
conseguenza che una template function non può
essere mai raccolta in una libreria a run time
Un template dovrebbe essere considerato come
una sorta di dichiarazione e fornito in un file da
includere.
Scarica

2 - Introduzione a C++