Linguagem de Programação II Carlos Oberdan Rolim Ciência da Computação Sistemas de Informação Classes Parte II Funções, métodos e classes friends Inconveniente gerado pelo uso de private C++ proporciona uma forma de contorno através de friends que podem ser funções, classes e métodos Funções friends Função friend é uma função nãomembro (não pertence a uma classe) que permite acesso aos membros da seção private de uma classe Quando uma função torna-se friend ela terá os mesmos privilégios de acesso que um método da classe tem As funções friend são um pouco diferentes de métodos Um método é chamado por um objeto específico Uma função friend por não ser membro de classes, não pode ser chamada por um objeto. Ao contrário, deverá receber um objeto como argumento, sobre o qual atuará #include <iostream> #include <cstring> #include <conio.h> using namespace std; void exibe (marido h1, esposa h2){ cout << “Nome” << h1.nome1; cout << “Salario” << h1.salario; cout << “Nome” << h2.nome2; cout << “Salario” << h2.salario2; class esposa; // declaracao antecipada class marido { public: marido(char *, float); ~marido(); friend void exibe(marido, esposa); private: char nome1[10]; float salario1; }; class esposa { public: esposa(char *, float); ~esposa(); friend void exibe(marido, esposa); private: char nome2[10]; } int main(){ marido homem(“joao”,2000,00); esposa mulher(“Maria”, 3000,00); exibe(homem, mulher); getch(); return 0; } Informações adicionais Declaração antecipada (forward declaration) diz ao compilador que a classe está definida mais a frente. É necessária por causa do protótipo friend void exibe(marido, esposa) que faz referência a classe esposa que ainda não existe Como a função recebe um objeto como argumento o protótipo de função foi declarada como friend exibe(homem, mulher); (lembrando que no protótipo não preciso informar nome após o tipo) O cabeçalho de definição da função friend, ao contrário do protótipo, não possui a palavra friend Como não pertence a classe não possui o operador de resolução de escopo :: A passagem de argumentos pode ser feita por valor, por referência ou por endereço. (Lembrando: a passagem de argumentos a uma função friend pode ser feita por endereço e por referência são mais rápidas e ocupam menos espaço em memória que por valor) Passagem por endereço utiliza ponteiros e implica no uso do operador -> cout << “Nome” << h1->nome1; Classes friend É uma classe cujos métodos podem acessar os membros das seções private e protected de outra classe. Quando uma classe é chamada como friend todos os seus métodos são entendidos como friends Mas é possível indicar quais métodos de uma classe serão amigos de uma outra classe Para indicar que uma classe é amiga de outra escreve-se a expressão friend class antes do nome da classe amiga, na declaração da primeira classe class carro { public: carro(char *, int, char *); void exibe(); friend class fcarro; private: char marca [12]; char modelo [7]; int anofab; // Método da classe amiga fcarro void altera( carro & c){ strcpy(c.marca, “Ford”); strcpy(c.modelo, “Focus”); c.anofab = 2008; } int main(){ }; class fcarro{ public: void altera(carro &); }; carro :: carro(char * m1, int a, char * m2){ strcpy(marca, m); strcpy(modelo, m2); anofab = a; } ... Implementação do exibe ... carro carro1(“GM”,2000,”Corsa”); fcarro fcarro1; // Passagem do argumento por //por referencia pois altera as // variaveis-membro da classe carro fcarro1.altera(carro1); getch(); return 0; } Métodos friend É uma classe cujos métodos podem acessar os membros das seções private e protected de outra classe. Quando uma classe é chamada como friend todos os seus métodos são entendidos como friends Há situações em que nem todos os métodos precisam ser friends, assim,seleciona-se os métodos da classe friend que irão acessar as seções private e protected da classe original:os métodos friends Para declarar um método friend, declare o seu protótipo na classe original, iniciando-se pela palavra friend class fcarro; // declaracao antecipada class carro { public: carro(char *, int, char *); void exibe(); friend void fcarro::altera(carro &); private: char marca [12]; char modelo [7]; int anofab; // Método da classe amiga fcarro void altera( carro & c){ strcpy(c.marca, “Ford”); strcpy(c.modelo, “Focus”); c.anofab = 2008; } int main(){ }; carro carro1(“GM”,2000,”Corsa”); class fcarro{ public: void altera(carro &); }; carro :: carro(char * m1, int a, char * m2){ strcpy(marca, m); strcpy(modelo, m2); anofab = a; } ... Implementação do exibe ... fcarro fcarro1; // Passagem do argumento por //por referencia pois altera as // variaveis-membro da classe carro fcarro1.altera(carro1); getch(); return 0; } Classes friend recíprocas Quando uma classe torna-se friend de outra e vice-versa. Métodos da classeA torna-se friend da classeB, e esta por sua vez torna-se friend da classeA class carro { public: friend class fcarro; carro(char *, int, char *); void exibe_fcarro(fcarro &); private: char marca [12]; char modelo [7]; int anofab; }; class fcarro{ public: friend class carro; fcarro(float,char); void altera(carro &); }; int main(){ carro carro1(“GM”,2000,”Corsa”); fcarro fcarro1(1100., ‘G’); // Passagem do argumento por //por referencia pois altera as // variaveis-membro da classe carro fcarro1.altera(carro1); carro1.exibe_fcarro(carro1); getch(); return 0; } ... Implementação dos métodos das classes ... Sobrecarga de operadores A sobrecarga de operadores é uma forma de polimorfismo de C++ que permite alterar o significado de seus operadores, cuja idéia é transformar expressões complexas em expressões mais claras e intuitivas. Por exemplo uma expressão total.soma(p1,p2); pode ser escrita como total = p1 + p2; C++ possui naturalmente vários operadores sobrecarregados Compilador com base na quantidade e nos tipos de operandos envolvidos decidirá qual ação executar Sobrecarga dos tipos básicos também é estentida aos tipos definidos pelos usuários Sobrecarga de operadores Como sobrecarga de operadores é utilizada com tipos definidos por usuários e como uma classe é um tipo definido pelo usuário, logo é definido por classe Quando um operador é sobrecarregado em uma classe, o seu significado muda apenas para ela, o restante do programa utilizará o operador com seu significado original A sobrecarga de operadores utiliza um formato especial de função chamado função operator operator operador ([argumentos]) operador caractere correspondente ao operador a ser sobrecarregado argumentos argumentos a serem passados à função, separador por virgula class texto{ public: texto(char *); void exibe(int); void operator –(int); private: static const int comp = 25; char string1[comp]; str_aux[k] = ‘\o’ ; strcpy(string1,atr_aux); } texto :: texto(char :* string2){ strcpy(string1, string2); } }; void texto:: exibe(int flag){ if(flag == 1) cout << “String original: ” << string1; else cout << “Substring: ” << string1; } void texto :: operator –(int qtd){ int k = 0; char str_aux[comp]; while(k < qtd){ str_aux[k] = string1[k] k++; } int main(){ texto texto1(“Sobrecarga de operad.”); texto1.exibe(); texto1 – 5; texto1.exibe(2); return 0; } Saída: String original: Sobrecarga de operad Substring: Sobre Critérios para sobrecarga de operadores A maioria dos operadores pode ser sobrecarregado da mesma maneira. Os operadores com algumas exceções, não precisam ser necessariamente métodos, porém pelo menos um dos operados precisa ser um tipo definido pelo programador O operador a ser sobrecarregado deve ser um operador válido C++. Por exemplo, o operador $ não pode ser sobrecarregado porque não é um operador válido O operador a se sobrecarregado deve ter pelo menos um operando que seja um tipo definido pelo programador. Isso elimina a possibilidade do programador sobrecarregar operadores que atuam sobre os tipos predefinidos. Assim, o operador + não pode ser redefinido de forma a subtrair valores em vez de adicioná-los Critérios para sobrecarga de operadores Um operador não pode ser utilizado de forma a violar as regras originais de sintaxe. Por exemplo, o operador / não pode ser sobrecarregado para utilizar apenas um operando A precedência dos operadores não pode ser alterada Operadores que não podem ser sobrecarregados: :: . const_cast dynamic_cast reinterpreted_cast static_cast typeid sizeof . * ?: Operadores que podem ser sobrecarregados apenas utilizando-se metodos: = ( ) [ ] -> Operadores que não podem se sobrecarregados utilizandose métodos e funções não-membro + - * / % ^& | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () [] new delete new[ ] delete[ ] Passagem de objetos como argumentos Uma função não precisa ser um método ou função friend para utilizar um objeto como argumento Somente se uma função precisa acessar os membros privados ela terá que ser um método ou função friend Pode-se passar o objeto por valor, referência e endereço. Lembrando: Se for passado por endereço utiliza ponteiro e então devese utilizar -> para acessar variáveis e métodos A passagem por referência pode ser mais conveniente que a passagem por endereço, pois mascara a utilização de ponteiros class Carro{ string marca, modelo; public: Carro(){} Carro(string, string); void exibe(); void altera(Carro); int main() { Carro carro("Fiat", "uno"); carro.exibe(); carro.altera(carro); }; Carro :: Carro(string marca, string modelo){ this->marca = marca; this->modelo = modelo; } return 0; void Carro :: exibe(){ cout << "Marca:" << marca << "\n“; cout << "Modelo:" << modelo << "\n“; } Saída void Carro :: altera( Carro c){ c.marca = "Nova marca“; c.modelo = "Novo modelo“; c.exibe(); Modelo:uno } } Note que o objeto alterado foi o “c” e não o “carro”. Para alterar “carro” deveria ser passado por referencia Marca:Fiat Marca:Fiat Modelo:uno Marca:Nova marca Modelo:Novo modelo class Carro{ string marca, modelo; int main() { Carro carro("Fiat", "uno"); public: carro.exibe(); Carro(){} carro.altera(carro); Carro(string, string); void exibe(Carro); return 0; } }; Carro :: Carro(string marca, string modelo){ Saída this->marca = marca; this->modelo = modelo; Note que o objeto alterado foi o “c” e não o “carro”. Para alterar “carro” deveria ser passado por referencia Marca:Fiat Modelo:uno } Marca:Fiat Modelo:uno void Carro :: exibe(Carro c){ cout << "Marca:" << c.marca << "\n"; cout << "Modelo:" << c.modelo << "\n"; Marca:Nova marca Modelo:Novo modelo Retorno de objetos Da mesma forma que uma função comum um método também pode retornar um valor que pode ser um objeto Retorno é feito com return normalmente class Numero{ int a, b; public: Numero(){} Numero(int, int); Numero inverte(); void mostra(); void Numero :: mostra(){ cout << "A: " << a << "\n“; cout << "B: " << b << "\n"; } int main() { }; Numero n(1,2), n2; Numero :: Numero(int a, int b){ this->a = a; this->b = b; } n.mostra(); n2 = n.inverte(); n2.mostra(); Numero Numero :: inverte(){ Numero n; n.a = b; n.b = a; return n; } return 0; } Saída: A: 1 B: 2 A: 2 B:1 Membros static Uma variável-membro de classe de memória static é uma variável única, criada para uma dada classe, que fica disponível a todos os objetos que vierem a ser criados. É utilizada para compartilhar informações entre objetos de uma classe Existe independente da criação de objetos e permanece na memória durante a execução do programa podendo ser acessada sem a existência de objetos Quando um objeto é criado ele passa a ter as variáveismembro da classe que o originou. Uma variável static não é armazenada nos objetos da classe, mas seu conteúdo fica disponível a todos os objetos que venham a ser criados A sua utilização implica economia de memória e tempo de processamento class carro { public: carro(char *,char *); private: char modelo [7]; char marca[10] static int cont; int main(){ carro carro1(“GM”,”Corsa”); carro carro2(“VW”,”Fusca”); getch(); }; int carro :: cont = 0; return 0; } carro :: carro(char * marc, char * mod){ Saida: strcpy(marca, marc); strcpy(modelo, mod); cont++; cout << “Criado objeto numero” << cont; } carro :: ~carro() { cout << “Destruicao do objeto “ << modelo; } Criado objeto numero 1 Criado objeto numero 2 Destruicao do objeto 2 Destruicao do objeto 1 Métodos static Um método da classe de memória static não precisa ser chamado por um objeto pois não possui um ponteiro this associado, podendo existir independente da existência de objetos class carro { public: static void exibe(); private: char modelo [7]; char marca[10] static int anofab; int main(){ // Chama o metodo static exibe() sem // a existencia de objetos da classe carro //Acessa a variavel compartilhada anofab carro :: exibe(); }; getch(); int carro :: anofab = 2000; carro :: exibe(){ return 0; } cout << “Ano fabricacao ” << anofab; Saida: } Ano fabricacao 2000 Classes e alocação dinâmica de memória A alocação de memória é aquela que ocorre durante a execução de um programa (não em tempo de compilação) e é efetuada pelo operadores new e new[ ] new utilizado para alocação dinâmica de um valor Sintaxe: tipo * ponteiro = new tipo; Exemplo: int * ptr_int = new int; // alocacao de um inteiro struct st_produto * ptr= new struct st_produto; ptr -> nome = “joao”; // acesso ao elemento nome Usar o operador delete para desalocar delete ptr; Classes e alocação dinâmica de memória new[ ] utilizado para alocação dinâmica de um array Sintaxe: tipo * ponteiro = new tipo [ quantidade ]; Exemplo: cin >> qtd; int * ptr = new int[qtd]; // alocacao de um vetor ptr[0] = 1; ptr[1] = 2; Usar o operador delete[ ] para desalocar delete ptr[ ]; class xstring{ public: void le_nome(); void exibe(); ~xtring(); private: char * texto; }; void xstring :: exibe(){ cout << “Ola “ << texto; } void xstring :: le_nome() { texto = new char[30]; cin.get(texto,30); } void xstring :: ~xstring(){ cout << “Obeto “ << texto << “destruido”; delete [ ] texto; } int main(){ xtring nome; nome.le_nome(); nome.exibe(); return 0; } Classes e alocação dinâmica de memória O detalhe mais importante do programa: quando o objeto nome é destruido, o ponteiro texto também o é, mas a memória apontada pelo ponteiro texto continua alocada. Daí a necessidade de utilizar o destrutor, e nesse, o operador delete [ ] para liberar a referida memória. Observe que o programa funciona sem o operador delete [ ], mas omiti-lo não é uma boa prática de programação