new
:
e se não houver memória? Excepção
bad_alloc
ou operador especial nothrow
.delete
.new[]
e delete[]
.Há uma variável do tipo
int j = 20;
int f()
{
int i = 10;
...
}
int
que tem um nome (i
) e que dura
enquanto a função estiver a ser executada... É
automática! E há uma variável do tipo int
que
tem nome (j
) e que dura enquanto o programa durar. É estática!
Hoje vamos ver como construir variáveis sem nome e com uma duração arbitrária, controlada directamente pelo programador. Esta variáveis chamam-se dinâmicas, exactamente porque têm uma duração arbitrária.
Se uma variável dinâmica não tem nome, como nos podemos referir a ela?
Discutir. Referir aula anterior. Concluir que se manipulam através de ponteiros.
Escrever:
O que se passa? Em
int* p = new int(10);
p
fica o endereço da variável dinâmica
criada, que é do tipo int
e tem valor 10.Explicar bem sintaxe. Falar dos argumentos do construtor. Fazer diagrama de objectos UML. Não colocar nome na variável dinâmica!
Escrever classe:
Como criar uma variável dinâmica do tipo
class Aluno {
public:
Aluno(string const& nome, int const número)
: nome_(nome), número_(número) {
}
string const& nome() const {
return nome_;
}
int número() const {
return número_;
}
private:
string nome_;
int número_;
};
Aluno
com nome "Zé"
e número 100?
Discutir. Concluir:
Como escrever o aluno no ecrã?
Aluno* pa = new Aluno("Zé", 100);
Discutir. Concluir:
Como destruir uma variável dinâmica?
cout << pa->nome() << ' ' << pa->número() << endl;
A partir desse momento o ponteiro
delete pa;
pa
contém LIXO!
Repare-se:
Qual a duração da variável dinâmica?
void f(int* p)
{cout << *p << endl;
delete p;
}
int main()
{
int* pi = new int(10);
f(pi);
}
Este código é exemplificativo! Não estou a defender que deve ser assim sempre! Aliás, não deve ser:
Regras:
Erros mais comuns:
Discutir.
for(int i = 0; i != 100000000; ++i) {
int* p = new int(i);
}
É uma chamada fuga de memória: variáveis dinâmicas construídas e nunca destruídas.
Fazer diagrama UML. Mostrar que também é fuga de memória.
int* p = new int(10);
p = new int(20);
Discutir com base em diagrama UML!
double* p = new double(1.1);
delete p;
delete p;
Que acontece durante um new
?
Discutir: reserva de memória (espaço a ocupar pela variável) e chamada do construtor (inicialização desse espaço) Deixar muito clara a importância dos construtores.
Qual é então o problema do seguinte código?
O único construtor de
Aluno* pa = new Aluno;
Aluno
tem dois parâmetros sem
valores
por omissão!
Discutir solução.
E que acontece quando se destrói?
Explicar destrutor. Acrescentar destrutor a Aluno. Explicar
que Aluno
não precisa de destrutor! É só para
exemplificar!
Explicar sintaxe. Explicar resultado de:
class Aluno {
public:
Aluno(string const& nome, int const número)
: nome_(nome), número_(número) {
}
~Aluno() {
cout << "Arghhh! Destruiram-me!" << endl;
}
string nome() const {
return nome_;
}
int número() const {
return número_;
}
private:
string nome_;
int número_;
};
Mas também se podem criar matrizes dinâmicas! Para isso usam-se os operadores
Aluno* pa = new Aluno;
delete pa;
new[]
e delete[]
:
Uma matriz dinâmica tem de ser destruída com
int* p = new int[10];
for(int i = 0; i != 10; ++i)
p[i] = i;
for(int i = 0; i != 10; ++i)
cout << p[i] << endl;
delete[] p;
delete[]
!
Como são construídos os elementos da matriz? Com o construtor por omissão. Excepto se forem de tipos básicos, que não são inicializados...
Que acontece quando não há memória? O operador lança uma excepção, que é um conceito que discutiremos numa próxima aula. O resultado prático é que o programa aborta. Mas depois veremos como se pode capturar a excepção. Alternativamente pode-se usar:
Neste caso se não houver memória o ponteiro fica com o valor singular 0.
#include <new>
...int* p = new(nothrow) int(20);
Explicar valor singular: nenhum objecto pode ter endereço 0!
Deixar claro que é melhor a solução com excepções!
#include <new>
...
int* p = new(nothrow) int(20);
if(p == 0) {
cerr << "Não havia memória! Que fazer?" << endl;
exit(1);
}
Começar por escrever classe do semestre passado!
Discutir solução antiga. Propor nova. Discuti-la passo a passo.class PilhaInt {
public:
typedef int Item;
PilhaInt();
bool estáVazia() const;bool estáCheia() const;
int altura() const;
Item const& topo() const;
void põe(Item const& item);void tira();
Item& topo();
private:static int const capacidade = 100;
Item itens[número_máximo_de_itens];
int número_de_itens;
};
Fazer construtor e
class PilhaInt {
public:
typedef int Item;
PilhaInt();~PilhaInt();
bool vazia() const;bool cheia() const;
int altura() const;
Item const& topo() const;
void põe(Item const& item);void tira();
Item& topo();
private:static int const capacidade_inicial = 32;
int capacidade_actual;
Item* itens;
int número_de_itens;
};
põe()
. Discutir passo a passo!
Explicar método de crescimento! Se o crescimento tivesse de ser feito a
cada inserção a implementação era muito ineficiente! Assim o impacte
do redimensionamento é negligenciável (o impacte amortizado é constante, de
cerca de duas cópias por inserção, de outra forma seria de n/2 para a
n-ésima inserção).
Justificar bem destrutor! Fuga de memória!
inline PilhaInt::PilhaInt()
: capacidade_actual(capacidade_inicial),
itens(new Item[capacidade_actual]),
número_de_itens(0) {}
void PilhaInt::põe(Item const& item) {
if(número_de_itens == capacidade_actual) {
capacidade_actual *= 2;
Item* novos_itens = new Item[capacidade_actual];
for(int i = 0; i != número_de_itens; ++i)
novos_itens[i] = itens[i];
delete[] itens;
itens = novos_itens;
}
itens[número_de_itens++] = item;
}
Quem destrói a matriz dinâmica dos itens quando uma pilha é destruída? Quem a construiu foi a pilha, quem a destrói deve ser a pilha. Conclusão: é necessário um destrutor!
Discutir resultado de:
PilhaInt::~PilhaInt() {
delete[] itens;
}
Explicar problema. Mencionar construtor por cópia: definido pelo compilador e que se limita a copiar os atributos! Deixar solução para a próxima aula!
PilhaInt p;
p.põe(2);
p.põe(3);
p.põe(4);
PilhaInt cópia = p;
cópia.tira();
PilhaInt outra;
outra = p;