Operador [ ]
O operador [ ] , quando redefinido, precisa de ser um membro da classe!
O operador recebe um único argumento.
class CVector
{
unsigned m_nElements;
int* m_arElements;
public:
int& operator[](unsigned pos);
CVector& operator=(const CVector& rv);
CVector(unsigned el = 0);
CVector(const CVector& v);
virtual ~CVector();
};
int& CVector::operator[](unsigned pos)
{
assert(pos < m_nElements);
return m_arElements[pos];
}
int main ()
{
CVector v (5);
v [3]= 3;
int k = v[2];
return 0;
}
Operador , (vírgula)
O operador vírgula garante que o operando esquerdo é avaliado antes do
operando direito.
O resultado do operador vírgula é o resultado da avaliação do operando direito.
cout << (7,8) << endl;
8
const CIntPtr& CIntPtr::operator , (const CIntPtr& r) const
{
cout << "operador ," << endl;
return r;
}
void D() const { std::cout << *m_pInt << std::endl; };
int main ()
{
CIntPtr p1(10), p2(3);
(p1, p2).D(); //3
return 0;
}
Este operador é
raramente redefinido.
Operadores new e delete
Quando cria algum objecto na zona dinâmica da memória, primeiro é chamado o
operador new() e a seguir é chamado o construtor para o objecto.
Quando remove um objecto criado na zona dinâmica da memória, primeiro é
chamado o destrutor apropriado e a seguir é chamado o operador delete().
Os construtores e os destrutores são chamados sempre e é impossível alterar
a maneira como estes são chamados. Contudo é possível controlar como a
memória é reservada e libertada.
Para tal pode-se redefinir os operadores new e delete que serão chamados
em vez da versão por defeito.
É possível redefinir os operadores new e delete globais (para serem utilizados
por todos os objectos criados na zona dinâmica da memória) ou locais (para
serem utilizados por todos os objectos de uma classe específica criados na
zona dinâmica da memória).
Se redefinir os operadores new e delete globais perde completamente acesso às
versões existentes por defeito!
Por essa razão, no caso geral, não é aconselhada a redefinição dos operadores
new e delete globais.
int main ()
{
void* operator new(size_t size)
double *d = new double;
{ void* p = malloc(size);
delete d;
if (p)
memset(p, 0, size);
int* str = new int[5];
return p;
delete [] str;
}
void* operator new [] (size_t size)
CIntPtr p1(10);
{ void* p = malloc(size);
if (p)
return 0;
memset(p, 0, size);
}
return p;
}
::operator new (sizeof(double))
void operator delete [] (void* p)
::operator delete (d)
{
free (p);
}
void operator delete (void* p)
{
free(p);
}
::operator new [] (sizeof(int) * 5)
::operator delete [] (str)
Se redefinir os operadores new e delete para uma classe, estas funções por defeito
são estáticas. Como consequência não têm o ponteiro this e não modificam o
estado do objecto, apenas reservam a memória para o construtor poder inicializá-la e
apagam esta memória depois da execução do destrutor.
void* CIntPtr::operator new (size_t size)
{
void* p = malloc(size);
if (p)
memset(p, 0, size);
return p;
}
void CIntPtr::operator delete (void* p)
{
free (p);
}
void* CIntPtr::operator new [] (size_t size)
{
void* p = malloc(size);
if (p)
memset(p, 0, size);
return p;
}
void CIntPtr::operator delete [] (void* p)
{
free (p);
}
int main ()
{
CIntPtr* p1 = new CIntPtr(10);
delete p1;
CIntPtr* p2 = new CIntPtr[3];
delete [] p2;
return 0;
}
class BASE
{
int a [3];
public:
BASE() { cout << "cb\t"; }
virtual ~BASE() { cout << "db\t"; }
void* operator new(size_t t)
{
void* p = malloc(t);
return p;
}
void operator delete(void* p)
{
free (p);
}
};
class DERIVED : public BASE
{
int b [2];
public:
DERIVED() { cout << "cd\t"; }
~DERIVED() { cout << "dd\t"; }
};
void main(void)
{
BASE *p = new DERIVED;
delete p;
}
BASE* p = new DERIVED;
delete p;
BASE* p1 = new BASE;
delete p1;
No caso do
destrutor
não virtual
temos:
cb cd db
// O resultado: cb
// O resultado: dd
cd
db
Dado que por defeito os operadores locais new e delete são funções estáticas, elas
não podem ser declaradas como virtuais.
Os operadores locais new e delete são herdadas pelas classes derivadas.
Redefinição de operadores unários postfix e prefix
A linguagem C++ permite distinguir entre redefinição de operadores
unários prefix e postfix, tais como ++x e x++. Ou seja, o compilador
vai ter que distinguir, por exemplo, entre uma versão postfix e uma
versão prefix do operador ++ .
Para que a redefinição do operador incremento se possa utilizar na
versão de pós-incremento e na versão de pré-incremento, é necessário
que cada uma delas, quando analisadas pelo compilador, tenha algo
que as distinga. Assim, as versões prefix são redefinidas como
habitualmente e as versões postfix é que vão ter uma pequena
diferença.
As funções operadores podem ser ou membros da classe ou funções
amigas da classe
EXEMPLO: Supondo uma classe X com um objecto x1
e a necessidade de redefinição do operador
decremento nas versões postfix e prefix:
class X
{
//...............
public:
X& operator--();
// versão prefix
const X operator--(int); // versão postfix
};
--x1;
x1--;
Na versão prefix o compilador ao ver uma
expressão do tipo --x1 gera o seguinte código:
x1.operator--()
Pelo contrário, o compilador ao ver uma
expressão do tipo x1-- gera o seguinte código:
x1.operator--(qualquer_inteiro)
Este valor “qualquer_inteiro”, apenas serve para distinguir a lista de
argumentos utilizada pela versão postfix da lista utilizada pela versão
prefix.
Nem todos os operadores unários do C++ podem ter versões prefix
e postfix, só o ++ e o -- é que é possível redefinir nas duas versões.
const CIntPtr CIntPtr::operator++(int)
{
CIntPtr temp = *this;
(*m_pInt)++;
return temp;
}
const CIntPtr CIntPtr::operator--(int)
{
CIntPtr temp = *this;
(*m_pInt)--;
return temp;
}
CIntPtr& CIntPtr::operator++()
{
(*m_pInt)++;
return *this;
}
CIntPtr& CIntPtr::operator--()
{
(*m_pInt)--;
return *this;
}
int main ()
{
CIntPtr p1, p2(7)
p1 = p2++;
//7
p1 = ++p2;
//9
p1 = --p2;
//8
p1 = p2--;
//8
return 0;
}
Operador -> (desreferência)
Pode ser redefinido como um operador unário postfix. Possibilita a utilização de
objectos como se fossem ponteiros.
class P
{
CIntPtr* p;
public:
CIntPtr* operator->() { return p; };
P() { p = new CIntPtr(3); };
~P() { delete p; };
};
class CIntPtr
{
int* m_pInt;
public:
CIntPtr();
CIntPtr(int);
CIntPtr(const CIntPtr& r);
virtual ~CIntPtr();
void D() const
{
std::cout << *m_pInt << std::endl;
};
//...
int main ()
{
P ptr;
ptr->D();
(ptr.operator ->())->D();
};
return 0;
}
O operador ->, quando redefinido, tem que ser uma função membro.
O valor de retorno deve ser obrigatoriamente ou um ponteiro ou um objecto do tipo
ao qual podemos aplicar o operador ->.
O operador -> é normalmente redefinido para criar ponteiros inteligentes:
class CContentor
{
unsigned m_nSize;
CIntPtr** m_arP;
public:
CContentor() { m_arP = 0; m_nSize = 0; };
~ CContentor() { delete [] m_arP; };
void Add(CIntPtr* p)
{
CIntPtr** array = new CIntPtr* [m_nSize+1];
memcpy(array, m_arP, m_nSize * sizeof(CIntPtr*));
array[m_nSize++] = p;
delete [] m_arP;
m_arP = array;
}
...
...
class iterator;
friend class iterator;
class iterator
{
CContentor& m_cont;
unsigned m_nIndex;
public:
iterator (CContentor& m) : m_cont(m) { m_nIndex = 0; };
CIntPtr* operator->() const { return m_cont.m_arP [m_nIndex]; };
iterator operator++(int)
{
iterator ant = *this;
if (m_nIndex < m_cont.m_nSize)
m_nIndex++;
return ant;
};
bool operator*() const
{
if (m_nIndex < m_cont.m_nSize)
return true;
return false;
}
};
};
O it é um ponteiro inteligente:
int main ()
{
CContentor m;
CIntPtr p1(1), p2(2), p3(3), p4(4);
m.Add(&p1); m.Add(&p2); m.Add(&p3); m.Add(&p4);
CContentor::iterator it(m);
while (*it)
it++->D();
return 0;
}
Sobrecarga de operadores
A declaração de um operador sobrecarregado é semelhante à
declaração duma função. A única diferença é que o nome desta
função deve ter a forma operator x onde x é o operador que está
a ser sobrecarregado.
O número de argumentos dum operador sobrecarregado é:
0 –unário/membro;
1 – unário/global ou binário/membro;
2 – binário/global.
Argumentos
Caso o argumento dum operador só seja analisado e não
modificado, este deve ser passado como uma referência para
um objecto constante:
... operator + (const type& r);
Caso o argumento dum operador seja modificado dentro da
função, este deve ser passado como uma referência para um
objecto não constante (esta situação surge em todos os
operadores de atribuição: =, +=, etc.):
... operator *= (type& l, const type& r);
Valores de retorno
O tipo do valor de retorno depende do significado do operador!
Os operadores de atribuição devolvem normalmente referências
para objectos não constantes:
type& operator = (const type& r);
Os operadores lógicos devolvem normalmente valores do tipo
bool:
bool operator >= (const type& r);
Caso o operador produza um valor novo, torna-se necessário
criar um objecto novo e devolvé-lo por valor (que pode ser
constante ou não):
const type operator - (const type& r);
Optimização do retorno
const type type::operator+ (const type& r)
{
type tmp (alguma_coisa + r. alguma_coisa);
return tmp;
}
const type type::operator+ (const type& r)
{
return type(alguma_coisa + r. alguma_coisa);
}
Membro ou não membro?
Quando o operando esquerdo precisa de ser um lvalue o operador respectivo é
normalmente declarado como membro.
Operador
Uso recomendado
Todos os operadores unários
membro
= () [] -> ->*
membro obrigatoriamente
Operadores de atribuição (além do =)
membro
Operadores binários que não modificam o
estado do objecto
não membro
Uma das razões principais de uso de operadores redefinidos como funções globais
em vez das funções membro é que neste caso a conversão automática entre tipos
pode ser aplicada a qualquer operando (esquerdo e direito).
Membro ou não membro?
Quando o operando esquerdo é um objecto da classe, o operador
respectivo pode ser definido como membro desta classe.
Quando o operando esquerdo não é um objecto da classe, o
operador deve ser definido como uma função não membro.
friend ostream& operator << (ostream& os, const type& r);
CIntPtr p1;
std::cin >> p1;
std::cout << p1 << std::endl;
//operadores de entrada/saída
friend std::ostream& operator << (std::ostream& os, const CIntPtr& a);
friend std::istream& operator >> (std::istream& is, CIntPtr& a);
using std::ostream;
ostream& operator << (ostream& os, const CIntPtr& a)
{
return os << *a.m_pInt;
}
using std::istream;
istream& operator >> (istream& is, CIntPtr& a)
{
return is >> *a.m_pInt;
}
using std::ostream;
ostream& operator << (ostream& os, const CVector& v)
{
for (unsigned i = 0; i < v.m_nElements; i++)
os << v.m_arElements[i] << '\t';
os << std::endl;
return os;
}
using std::istream;
istream& operator >> (istream& is, CVector& v)
{
char c; CVector aux(1);
delete [] v.m_arElements; v.m_arElements = 0;
v.m_nElements = 0;
while ( (c = is.get()) && c != '\n')
{
if (isdigit (c))
// só funciona para inteiros
{
// compostos por um só dígito
aux[0] = c - '0';
v = v + aux;
}
}
return is;
}
Quando o operando esquerdo é um objecto da classe, o operador
respectivo pode ser definido como membro desta classe.
Quando o operando esquerdo não é um objecto da classe, o
operador deve ser definido como uma função não membro.
CIntPtr(int);
const CIntPtr operator+ (const CIntPtr& r);
CIntPtr p1(1), p2(2), p3(3);
p1 = p2 + p3; //5
p1 = p2 + 10; //14
p1 = 10 + p2;
CIntPtr(int);
const CIntPtr friend operator+ (const CIntPtr& l, const CIntPtr& r);
Download

ppt1