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.