O que é importante: 1. Perceber o que é redefinição de operadores. 2. Perceber a redefinição de operadores binários e unários (postfix e prefix) 3. Perceber a utilização de grandes objectos. 4. Perceber as respectivas regras da linguagem C++. 5. Estar apto a construir programas que utilizem as respectivas construções de POO. Redefinição de operadores (Operator Overloading) A linguagem de programação C++ permite ao programador redefinir a maioria dos operadores, por forma a facilitar o seu uso no contexto em que estão inseridos. O compilador vai gerar o código apropriado, baseado na maneira como o operador é usado. Os programadores podem usar tipos já existentes no C++ mas também podem definir novos tipos. Os tipos já existentes no C++ podem ser usados com a vasta colecção de operadores desta linguagem. Embora o C++ não permita a criação de novos operadores, permite que os operadores existentes sejam redefinidos, para que quando estes forem usados com objectos de classes definidas pelo utilizador, os operadores tenham significado apropriado a esse novo tipo. class CData { unsigned m_Ano; short int m_Mes; short int m_Dia; public: CData(); CData(short int d, short int m, unsigned a); ~CData(); }; int main() { CData hoje; CData ano_novo = CData(1, 1, 2004); return 0; } CData::CData() { time_t t = time(0); struct tm *today = localtime(&t); m_Ano = today->tm_year + 1900; m_Mes = today->tm_mon + 1; m_Dia = today->tm_mday; } CData::~CData() { } CData::CData(short int d, short int m, unsigned a) { m_Ano = a; m_Mes = m; m_Dia = d; } class CData { unsigned m_Ano; short int m_Mes; short int m_Dia; public: CData(); CData(short int d, short int m, unsigned a); ~CData(); int Diferenca (const CData& d) const; }; int main() { CData hoje; CData ano_novo = CData(1, 1, 2004); int quantos_dias_faltam = ano_novo.Diferenca(hoje); return 0; } class CData { unsigned m_Ano; short int m_Mes; short int m_Dia; public: CData(); CData(short int d, short int m, unsigned a); ~CData(); friend int gl_diferenca (const CData& d1, const CData& d2); }; int main() { CData hoje; CData ano_novo = CData(1, 1, 2004); int quantos_dias_faltam = gl_diferenca (ano_novo, hoje); return 0; } int quantos_dias_faltam = ano_novo.Diferenca(hoje); class CData { unsigned m_Ano; short int m_Mes; short int m_Dia; public: CData(); CData(short int d, short int m, unsigned a); ~CData(); int operator - (const CData& d) const; }; int main() { CData hoje; CData ano_novo = CData(1, 1, 2004); int quantos_dias_faltam = ano_novo - hoje; return 0; } int quantos_dias_faltam = gl_diferenca (ano_novo, hoje); A redefinição de operadores representa uma maneira alternativa de chamar funções. Em vez de especificar os argumentos entre os parêntesis, os argumentos podem ser “interligados” com a ajuda de operadores. Para os tipos built-in é a própria linguagem que faz a redefinição: int a1 = 2, b1 = 3; int c1 = a1 + a1 * b1; double a2 = 2.2, b2 = 3.3; double c2 = a2 + a2 * b2; class CConjunto { public: CConjunto operator+(const CConjunto& add) const; CConjunto operator*(const CConjunto& inter) const; //… }; As regras de precedência mantêm-se! CConjunto a3, b3; //... CConjunto c3 = a3 + a3 * b3; A redefinição dos operadores faz-se escrevendo uma função (com cabeçalho e corpo) como normalmente se faz para todas as funções, com a única diferença de o nome da função contém sempre a palavra-chave operator seguida do símbolo do operador que se quer redefinir. a palavra chave do C++ O nome de operador, por exemplo +, -, /, *, ==, !=, ++, --, <, >, <<, etc. o_valor_retorno operator#(lista_dos_argumentos) { // corpo da função operador return o_valor_retorno; } EXEMPLO: Se pretendermos redefinir o operador + de forma a efectuar a soma de dois objectos da classe X, a declaração da função poderá ser a seguinte: class X //definição da classe { int a; public: X(int aa) {a=aa;} // redefinição do operador + X operator+(const X&)const; }; //corpo da função operador+ X X::operator+(const X& xr) const { X tmp(0); tmp.a = a+xr.a; return X (a + xr.a); return tmp; } Esta função operadora pode ser chamada de duas maneiras diferentes: X x1(0), x2(2), x3(3); x1 = x2+x3 ; //chamada implícita x1 = x2.operator+(x3);//chamada explícita x1 = x2 + x3 + x3; x1 = x3.operator+ (x2.operator+ (x3)); Operadores unários e os de atribuição são executados começando do lado direito. Todos os operadores restantes são executados começando do lado esquerdo. X operator+(const X&)const; o operador lista_dos_argumentos o_valor_retorno o nome da função X X::operator+(const X& xr) const { return X(a + xr.a); } void main (void) { X x1=4,x2=5; . . . . . . . . . . . x1=x1+x2; //chamada implícita x1=x1.operator+(x2); //chamada explícita . . . . . . . . . . . } Nem todos os operadores podem ser redefinidos. Os operadores de C++ que podem ser redefinidos são seguintes: + | -= << >= -> ~ *= >> && [] * ! /= >>= || () / = %= <<= ++ new % ^ & < > += ^= &= |= == != <= -->* , new[] delete delete[] Os operadores do C++ que não podem ser redefinidos são: .* :: . ?: sizeof typeid Para além destas, existem outras restrições impostas à redefinição de operadores, por parte da linguagem C++ : 1. A precedência dos operadores não pode ser modificada pela redefinição. No entanto, o uso de parêntesis permite ultrapassar esta restrição. 2. Não é possível modificar o número de operandos de um operador. Os operadores unários que sejam redefinidos, permanecem operadores unários; os operadores binários que sejam redefinidos, permanecem binários; e ao único operador ternário do C++ ( ?: ) não é permitida redefinição. Os operadores & , * , + , - têm versões unárias e binárias, podendo cada uma destas versões ser redefinida separadamente. 3. Não é permitido criar novos operadores. Só os operadores existentes em C++ podem ser redefinidos, pelo que a lista completa dos operadores que se podem redefinir encontra-se na primeira tabela. 1. A precedência dos operadores não pode ser modificada pela redefinição. No entanto, o uso de parêntesis permite ultrapassar esta questão. A = B + C*D; 3 2 1 A = (B + C)*D; 3 1 2 2. Não é possível modificar o número de operandos de um operador. A = B ++C; 3. Não é permitido criar operadores novos . A= B C; EXEMPLO: Supondo que poderíamos criar operadores novos, criou-se o operador <- . Ao escrevermos a seguinte expressão: a<--b o compilador não saberia se deveria interpretar como: a<-(-b) , ou como: a<(--b). Esta é uma das razões para não ser possível criar operadores novos. 4. A redefinição de operadores apenas se aplica a objectos de tipos definidos pelo utilizador, ou à mistura de um objecto pertencente a um tipo definido pelo utilizador com um outro objecto pertencente a um tipo já existente no C++. Pelo menos um operando da função operadora tem de ser um objecto de uma classe, ou uma referência a um objecto de uma classe. Nem sequer é possível definir uma função operadora, que opere exclusivamente com ponteiros. EXEMPLO: O programador não pode redefinir o operador + para efectuar a soma de dois inteiros, mas pode redefinir o operador + para efectuar a soma de dois objectos de uma classe X, definida pelo utilizador. 5. A redefinição de um operador não implica a redefinição automática de operadores relacionados com o primeiro. Ou seja, os operadores só podem ser redefinidos explicitamente e nunca implicitamente. EXEMPLO: Sendo X, uma classe com os operadores + e = redefinidos e sendo x1 e x2 dois objectos dessa classe, é possível escrever a seguinte instrução: x1 = x1 + x2; // Certo No entanto, isto não implica que o operador += se possa utilizar: x1 += x2; // Errado Para que esta instrução esteja correcta, tem que se redefinir também o operador += . Um operador binário pode ser redefinido como uma funçãomembro não estática que recebe um argumento ou como uma função não-membro que recebe dois argumentos. class X { int a; public: X(int aa) {a=aa;} OU X operator+(const X&)const; friend X operator+(const X& xl, const X& xr); }; X X::operator+(const X& xr) const { return X(a + xr.a); } X operator+ (const X& xl, const X& xr) { return X(xl.a + xr.a); } OU Um operador unário pode ser redefinido como uma funçãomembro não estática que não tem argumentos ou como uma função não-membro que recebe um argumento. class X { int a; public: X(int aa) {a=aa;} OU const X operator-() const; friend X operator-(const X& xr); }; const X X::operator- () const { return (-a); } X operator-(const X& xr) { return X(-xr.a); } OU int main() { X x1(0), x2(2), x3(3); x1 = x2 + x3; x1 = x2.operator+ (x3); x1 = operator+ (x2, x3); x1 = -x2; x1 = x2.operator- (); x1 = operator- (x2); return 0; } função-membro OU função não membro OU Implicações de os operadores serem membros da classe ou funções amigas (friend functions) As funções operadoras podem ser funções membro da classe ou funções não membro da classe, sendo estas últimas, normalmente, definidas como amigas. As funções membro da classe usam o ponteiro this, de maneira a obter implicitamente um objecto da classe que é argumento do operador. Pelo contrário, no caso de uma função não membro da classe , todos os argumentos devem ser explicitamente listados. EXEMPLO: Para o caso de operações binárias, uma classe X pode ser declarada da seguinte forma: class X { //.......................... public: X operator+(X); //função membro, o 1º argumento é //implicitamente passado através do this friend X operator-(X,X); //função global, não tem this. X operator/(X,X); //ERRO! Operação ternária. // .......................... }; class X { //.......................... public: X operator+(X); .......................... X x1(...) ,x2(...), x3(...); x1 = x2 + x3; // ou x1 = x2.operator+(x3); função membro é o 1º argumento o 2º argumento O 1º argumento é implicitamente passado através do this class X { //.......................... public: friend X operator+(X,X); .......................... X x1(...) ,x2(...), x3(...); x1 = x2 + x3; // ou x1 = operator+(x2, x3); o 1º argumento o 2º argumento O 1º argumento não pode ser passado através do this