Operador O operador quando redefinido precisa de ser

  • Slides: 24
Download presentation

Operador [ ] O operador [ ] , quando redefinido, precisa de ser um

Operador [ ] O operador [ ] , quando redefinido, precisa de ser um membro da classe! O operador recebe um único argumento. class CVector { unsigned m_n. Elements; int* m_ar. Elements; 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_n. Elements); return m_ar. Elements[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

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 CInt. Ptr& CInt. Ptr: : operator , (const CInt. Ptr& r) const { cout << "operador , " << endl; return r; } void D() const { std: : cout << *m_p. Int << std: : endl; }; int main () { CInt. Ptr p 1(10), p 2(3); (p 1, p 2). D(); //3 return 0; } Este operador é raramente redefinido.

Operadores new e delete Quando cria algum objecto na zona dinâmica da memória, primeiro

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

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) { void* p = malloc(size); if (p) memset(p, 0, size); return p; } void operator delete [] (void* p) { free (p); } void operator delete (void* p) { free(p); } CInt. Ptr p 1(10); return 0; } : : operator new (sizeof(double)) : : operator delete (d) : : operator new [] (sizeof(int) * 5) : : operator delete [] (str)

Se redefinir os operadores new e delete para uma classe, estas funções por defeito

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* CInt. Ptr: : operator new (size_t size) { void* p = malloc(size); if (p) memset(p, 0, size); return p; } void CInt. Ptr: : operator delete (void* p) { free (p); } void* CInt. Ptr: : operator new [] (size_t size) { void* p = malloc(size); if (p) memset(p, 0, size); return p; } void CInt. Ptr: : operator delete [] (void* p) { free (p); } int main () { CInt. Ptr* p 1 = new CInt. Ptr(10); delete p 1; CInt. Ptr* p 2 = new CInt. Ptr[3]; delete [] p 2; return 0; }

class BASE { int a [3]; public: BASE() { cout << "cbt"; } virtual

class BASE { int a [3]; public: BASE() { cout << "cbt"; } virtual ~BASE() { cout << "dbt"; } 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 << "cdt"; } ~DERIVED() { cout << "ddt"; } }; void main(void) { BASE *p = new DERIVED; delete p; } BASE* p = new DERIVED; delete p; BASE* p 1 = new BASE; delete p 1; 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

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

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 x 1 e a necessidade de redefinição do operador decremento nas versões postfix e prefix:

class X { //. . . . public: X& operator--(); // versão prefix const

class X { //. . . . public: X& operator--(); // versão prefix const X operator--(int); // versão postfix }; --x 1; x 1 --; Na versão prefix o compilador ao ver uma expressão do tipo --x 1 gera o seguinte código: x 1. operator--() Pelo contrário, o compilador ao ver uma expressão do tipo x 1 -- gera o seguinte código: x 1. 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ó

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 CInt. Ptr: : operator++(int) { CInt. Ptr temp = *this; (*m_p. Int)++; return temp; } const CInt. Ptr: : operator--(int) { CInt. Ptr temp = *this; (*m_p. Int)--; return temp; } CInt. Ptr& CInt. Ptr: : operator++() { (*m_p. Int)++; return *this; } CInt. Ptr& CInt. Ptr: : operator--() { (*m_p. Int)--; return *this; } int main () { CInt. Ptr p 1, p 2(7) p 1 = p 2++; //7 p 1 = ++p 2; //9 p 1 = --p 2; //8 p 1 = p 2 --; //8 return 0; }

Operador -> (desreferência) Pode ser redefinido como um operador unário postfix. Possibilita a utilização

Operador -> (desreferência) Pode ser redefinido como um operador unário postfix. Possibilita a utilização de objectos como se fossem ponteiros. class P { CInt. Ptr* p; public: CInt. Ptr* operator->() { return p; }; P() { p = new CInt. Ptr(3); }; ~P() { delete p; }; }; int main () { P ptr; ptr->D(); (ptr. operator ->())->D(); return 0; } class CInt. Ptr { int* m_p. Int; public: CInt. Ptr(); CInt. Ptr(int); CInt. Ptr(const CInt. Ptr& r); virtual ~CInt. Ptr(); void D() const { std: : cout << *m_p. Int << std: : endl; }; //. . . };

O operador ->, quando redefinido, tem que ser uma função membro. O valor de

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_n. Size; CInt. Ptr** m_ar. P; public: CContentor() { m_ar. P = 0; m_n. Size = 0; }; ~ CContentor() { delete [] m_ar. P; }; void Add(CInt. Ptr* p) { CInt. Ptr** array = new CInt. Ptr* [m_n. Size+1]; memcpy(array, m_ar. P, m_n. Size * sizeof(CInt. Ptr*)); array[m_n. Size++] = p; delete [] m_ar. P; m_ar. P = array; }. . .

. . . class iterator; friend class iterator; class iterator { CContentor& m_cont; unsigned

. . . class iterator; friend class iterator; class iterator { CContentor& m_cont; unsigned m_n. Index; public: iterator (CContentor& m) : m_cont(m) { m_n. Index = 0; }; CInt. Ptr* operator->() const { return m_cont. m_ar. P [m_n. Index]; }; iterator operator++(int) { iterator ant = *this; if (m_n. Index < m_cont. m_n. Size) m_n. Index++; return ant; }; bool operator*() const { if (m_n. Index < m_cont. m_n. Size) return true; return false; } }; };

O it é um ponteiro inteligente: int main () { CContentor m; CInt. Ptr

O it é um ponteiro inteligente: int main () { CContentor m; CInt. Ptr p 1(1), p 2(2), p 3(3), p 4(4); m. Add(&p 1); m. Add(&p 2); m. Add(&p 3); m. Add(&p 4); CContentor: : iterator it(m); while (*it) it++->D(); return 0; }

Sobrecarga de operadores A declaração de um operador sobrecarregado é semelhante à declaração duma

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

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: 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!

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: 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: : operator+ (const type& r) { type tmp (alguma_coisa

Optimização do retorno const type: : operator+ (const type& r) { type tmp (alguma_coisa + r. alguma_coisa); return tmp; } const 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

Membro ou não membro? Quando o operando esquerdo precisa de ser um lvalue o operador respectivo é normalmente declarado como 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

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); CInt. Ptr p 1; std: : cin >> p 1; std: : cout << p 1 << std: : endl;

//operadores de entrada/saída friend std: : ostream& operator << (std: : ostream& os, const

//operadores de entrada/saída friend std: : ostream& operator << (std: : ostream& os, const CInt. Ptr& a); friend std: : istream& operator >> (std: : istream& is, CInt. Ptr& a); using std: : ostream; ostream& operator << (ostream& os, const CInt. Ptr& a) { return os << *a. m_p. Int; } using std: : istream; istream& operator >> (istream& is, CInt. Ptr& a) { return is >> *a. m_p. Int; }

using std: : ostream; ostream& operator << (ostream& os, const CVector& v) { for

using std: : ostream; ostream& operator << (ostream& os, const CVector& v) { for (unsigned i = 0; i < v. m_n. Elements; i++) os << v. m_ar. Elements[i] << 't'; os << std: : endl; return os; } using std: : istream; istream& operator >> (istream& is, CVector& v) { char c; CVector aux(1); delete [] v. m_ar. Elements; v. m_ar. Elements = 0; v. m_n. Elements = 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

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. CInt. Ptr(int); const CInt. Ptr operator+ (const CInt. Ptr& r); CInt. Ptr p 1(1), p 2(2), p 3(3); p 1 = p 2 + p 3; //5 p 1 = p 2 + 10; //14 p 1 = 10 + p 2; CInt. Ptr(int); const CInt. Ptr friend operator+ (const CInt. Ptr& l, const CInt. Ptr& r);