1. Classes 1.1. Palavra chave this 1.2. Palavra chave friend 1.3. Palavra chave const 2. Membros estáticos 2.1. Dados estáticos 2.2. Funções estáticas 1.3. Palavra chave const constantes (para eliminar #define) objectos ponteiros argumentos de funções tipos de retorno das funções em classes Ponteiros const pode ser aplicado ou a ponteiro (i.e. ao endereço que o ponteiro guarda) ou a objecto para o qual o ponteiro aponta. char c1 = 'a', c2 = 'b'; const char* letra = &c1; letra = &c2; *letra = 'c'; //erro *letra = c2; //erro char c1 = 'a', c2 = 'b'; char const* letra = &c1; = letra = &c2; *letra = 'c'; //erro *letra = c2; //erro char c1 = 'a', c2 = 'b'; char* const letra = &c1; letra = &c2; *letra = 'c'; *letra = c2; objecto constante //erro ponteiro constante Ponteiros É possível fazer com que ambas as partes sejam constantes, i.e. o ponteiro (o endereço que o ponteiro guarda) e o objecto para o qual o ponteiro aponta. char c1 = 'a', c2 = 'b'; const char* const letra = &c1; letra = &c2; //erro *letra = c2; //erro É possível atribuir a um ponteiro constante o endereço de um objecto não constante. char c1 = 'a'; const char* const s = &c1; É impossível atribuir a um ponteiro não constante o endereço de um objecto constante. const char c1 = 'a'; char* s = &c1; // erro Arrays de caracteres As regras mencionadas acima não funcionam com os arrays de caracteres. O código seguinte vai compilar, mas vai causar um erro durante a execução! char* str = "sol"; str[1] = 'a'; erro char str [] = "sol"; str[1] = 'a'; Ok Funções void f1 (const int a) { a++; //erro } const int f2 (int a) { return ++a; } int main(int argc, char* argv[]) { f1(7); const int i1 = f2(6); int i2 = f2(6); } Para os tipos built-in não há nenhuma diferença entre devolução de um valor constante e um valor não constante. Devolução de um valor constante torna-se importante para os tipos abstractos. Se uma função retorna um objecto por valor constante, o valor devolvido não pode ser lvalue (i.e. não pode ser modificado). class my_class { int i; public: my_class (int ii) : i(ii) {}; my_class inc () { i++; return *this; }; }; my_class f3 (my_class mc) { mc.inc(); return mc; } não pode ser lvalue int main(int argc, char* argv[]) { my_class obj1(3); my_class obj2 = f3(obj1).inc(); } const my_class f3 (my_class mc) { mc.inc(); return mc; } Se passar um endereço numa função, deve fazer este endereço constante sempre que for possível. void f4 (my_class*) {} void f5 (const my_class*) {} const my_class* const f6() { return 0; } int main(int argc, char* argv[]) { my_class obj5(1); my_class* p1 = &obj5; const my_class* p2 = &obj5; f4(p1); f4(p2); //erro f5(p1); f5(p2); p1 = f6(); //erro p2 = f6(); } Uma função que recebe ponteiros constantes é mais geral! É melhor passar argumentos nas funções por referências constantes! 1) Para os objectos grandes é muito mais eficiente do que passar por valor porque não é necessário chamar o construtor de cópia. 2) Passagem por referências constantes garante que a função não vai alterar o estado do objecto serviço para o utilizador. 3) Na utilização, a sintaxe é idêntica à passagem por valor serviço para o utilizador. 4) É possível passar objectos temporários. Referências int a = 47; int* pa = &a; *pa = 10; int& ra = a; ra = 13; Uma referência (&) é um nome alternativo para um objecto. A referência é parecida com um ponteiro constante que é dereferenciado automaticamente. • • • A referência tem que ser inicializada no ponto da sua definição. Uma vez inicializada, não se pode modificar a referência para que esta referencia qualquer outro objecto. Não se pode ter referências iguais a NULL. Referências (cont.) int* f (int* x) { (*x)++; return x; } int& g (int& x) { x++; return x; } int main() { int a = 0; f(&a); g(a); } As referências são normalmente utilizadas em listas de argumentos de funções. Quando uma referência é utilizada como um argumento da função, qualquer modificação da referência dentro da função resulta na alteração do objecto fora da função. Referências (cont. 2) Com a ajuda das referências pode-se transferir numa função (que aceita referências constantes) um objecto temporário. Ao contrário disso, é impossível transferir um objecto temporário numa função que aceita um ponteiro. void f (int&) {} void g (const int&) {} void p (int*) {} int main() { // f (1); g (1); // p (1); } Optimização do retorno const type type::f (const type& r) { type tmp (/*argumentos*/); return tmp; } const type type::f (const type& r) { return (/*argumentos*/); } Constantes em classes Uma constante definida numa classe representa um valor que depois de inicializado não pode ser alterado. Contudo cada objecto da classe pode ter o seu próprio valor da constante! Portanto é impossível inicializar o valor da constante na definição da classe. Este trabalho tem de ser feito na lista inicializadora do construtor. class my_class { int i; const int max; public: my_class (int ii, int m) : i(ii), max(m) {}; my_class inc () { if (i < max) i++; return *this; }; }; my_class obj6(1, 50); my_class obj7(1, 10); Funções-membros constantes Uma função-membro declarada como constante pode ser chamada para objectos constantes e para objectos não constantes. Assuma-se que uma função membro não constante modifica os dados do objecto para o qual foi chamada. Portanto tais funções não podem ser chamadas para objectos constantes. class my_class { int i; const int max; public: my_class (int ii, int m) : i(ii), max(m) {}; my_class inc () { if (i < max) i++; return *this; }; void Display () const { cout << i << endl; }; }; my_class obj6(1, 50); const my_class obj7(1, 10); obj6.Display(); obj7.Display(); obj6.inc(); obj7.inc(); //erro Cada função que não modifica o estado do objecto tem de ser declarada como constante!!! Os construtores e destrutores nunca podem ser constantes porque quase sempre modificam o estado do objecto. Quando uma função-membro constante é definida fora do corpo da classe, deve levar o sufixo const porque faz parte do tipo da função: void my_class::Display () const { cout << i << endl; }; Nas funções-membros constantes o ponteiro this é do tipo: const my_class *const this; class string { char str[25]; public: void set_string(char* s) { strcpy(str,s); } void display_string(void) const { cout << str << endl; } char* return_string(void) { return str; } }; string str1; const string str2; str1.set_string(“Aveiro”); str1.display_string(); str2.display_string(); str2.set_string(“erro”); // objecto não constante // objecto constante // OK, objecto não constante pode // invocar função não constante // OK, objecto não constante pode // invocar uma função constante // OK objecto constante pode // invocar uma função constante // ERRO, objecto constante não pode // invocar função não constante A diferença entre uma função constante, e uma função não constante mas com argumentos constantes, é a primeira tornar o ponteiro this constante não podendo modificar o estado do objecto, enquanto que a segunda apenas não modifica os argumentos. O tipo do ponteiro this para uma função constante pertencente à classe my_class é: const my_class *const this; Isto significa que nós não podemos alterar o valor do objecto (da classe my_class) através de this sem cast explicito, por exemplo: class my_class { int i; public: void implicit(void) const { i--; void explicit(void) const { ((my_class*)this)->i--; }; } // ERRO } // OK Sumário da palavra chave this 1. this é uma nova palavra chave do C++; 2. Qualquer objecto novo pertencente a uma classe tem o seu próprio ponteiro this. 3. A palavra this é definida dentro de um objecto como sendo um ponteiro para o próprio objecto. 4. this aponta para o início da memória do computador onde está o objecto. 5. O ponteiro this é uma variável local, que não pode ser acedida fora do corpo da função. 6. O ponteiro this é apenas acessível em funções não static. 7. O ponteiro this não necessita de ser declarado porque é uma palavra chave. 8. O ponteiro this pode ser explicitamente usado numa função tal como this ou *this. 9. this é declarado como *const logo não pode ser alterado, mas o objecto por ele apontado pode. 10. O ponteiro this é passado como um argumento escondido para todas as funções, que não seja do tipo static, pertencentes à classe dum determinado objecto. Sumário da palavra chave friend 1. Funções friend são funções globais, logo não pertencem à respectiva classe. No entanto têm acesso a todos os dados e funções pertencentes à classe independentemente dos seus atributos. 2. A utilização de funções friend quebram o escudo protector que uma classe tem por defeito. Por isso só se devem usar em funções estritamente necessárias. 3. A utilização de funções friend permitem um certo acesso aos dados pertencentes à classe. 4. Se funções friend não são membros de uma classe, estas são chamadas como vulgares funções globais. 5. Se funções friend não são membros de uma classe, estas não têm o ponteiro this. 6. Uma classe B que seja definida como friend de outra classe A, não implica que A tenha acesso aos membros privados de B. 7. Duas propriedades de funções e classes amigas, é a de amizade não poder ser herdada nem transitiva. Sumário da palavra chave const 1. Constantes são identificadores que são associados a valores fixos. 2. As constantes têm de ser inicializadas na altura da sua declaração. 3. O valor de variáveis do tipo const não pode ser modificado após a inicialização. 4. O uso de constantes aumenta a facilidade de leitura e compreensão de um programa. 5. O uso de constantes facilita bastante a alteração de parâmetros num programa. 6. Constantes do tipo macro-based são herdadas do C (a directiva do compilador #define). 7. Constantes do tipo formal usam a directiva do compilador const. 8. Funções constantes não alteram o estado da classe à que pertencem. 9. Funções constantes implicam ponteiro this constante. 10. Funções não podem alterar argumentos constantes. 11. Funções constantes podem ser invocadas por objectos constantes e não constantes. 12. Funções não constantes apenas podem ser invocadas por objectos não constantes. Agora vamos considerar alguns exemplos que vão demonstrar algumas construções diferentes da linguagem C++ int b=10; class my_class { int b; int a; public: void display() { cout << a << '\t' << b << endl; } my_class(int a) { my_class::a=a; b =::b; } }; int main(int argc, char* argv[]) { my_class mc=5; mc.display(); // 5 return 0; } 10 class my_class1 { int a; public: void compare(my_class1&); friend void compare(my_class1&, my_class2&); my_class1(int A) : a(A) {} virtual ~my_class1(); }; void my_class1::compare(my_class1 &mc1) { if (this != &mc1) if(a==mc1.a) cout << "equal\n"; else cout << " not equal\n"; else cout << "the same class\n"; } int main(int argc, char* argv[]) { my_class1 mc1=10, mc1n=20; // mc1n=10 - equal mc1.compare(mc1); // the same class mc1.compare(mc1n); // not equal return 0; }