No C++ 11, podemos utilizar as classes e objetos de outras formas.
Esse é o código da classe, note que no C++ 11 podemos inicializar diretamente as classes assim, apenas num arquivo h:
class Carro {
public:
int velMax;
int potencia;
const char* nome;
Carro(const char* n, int p): nome(n), potencia(p) { // Lista de inicialização, substituí o uso de this
if(p < 100) {
this->velMax = 120;
}
else if(p < 200) {
this->velMax = 220;
}
else {
this->velMax = 350;
}
}
};
Observe que nesse caso acima não usamos o this, e sim uma lista de inicialização.
E podemos utilizar a inicialização uniforme para criar objetos, dessa forma:
int main() {
Carro c1{"Corsa", 90}; // Sempre na ordem dos atributos na classe
cout << c1.nome << " - " << c1.potencia << " - " << c1.velMax << endl;
// Não se usa delete para excluir esse tipo de objeto
return 0;
}
PS: Observe que não usamos um asterisco de ponteiro nesse caso, e como nesse caso utilizamos a inicialização uniforme, que não usa o new para criar objetos, não usamos a seta neles, apenas um ponto. A seta só utiliza quando usamos new. Também não usamos delete pra excluir objetos não utilizados (também é utilizada apenas com o new).
Podemos usar ponteiros inteligentes para criar objetos da forma moderna também, nesse caso, usamos new e precisamos usar setas:
int main() {
unique_ptr<Carro> c1(new Carro{"Corsa", 90}); // Importe memory
// Nesse caso usamos seta por usar new
cout << c1->nome << " - " << c1->potencia << " - " << c1->velMax << endl;
return 0;
}
E com os ponteiros inteligentes, não precisamos usar delete pra excluir objetos, pois nesse caso sim, ele faz a coleta de lixo automaticamente.
Os ponteiros inteligentes também podem ser usados em containers com objetos, por exemplo:
vector<unique_ptr<Carro>> car;
car.emplace_back(new Carro("Corsa", 90)); // Note que nesse caso usamos emplace_back, não push_back.
car.emplace_back(new Carro("Vectra", 135));
car.emplace_back(new Carro("Camaro", 200));
cout << car[0]->nome << " - " << car[0]->potencia << " - " << car[0]->velMax << endl;
cout << car[1]->nome << " - " << car[1]->potencia << " - " << car[1]->velMax << endl;
cout << car[2]->nome << " - " << car[2]->potencia << " - " << car[2]->velMax << endl;
Basicamente, o encapsulamento é uma técnica de protegermos os membros da nossa classe. Nós sabemos que existe os métodos public e private para isso. Alguns atributos e métodos precisam ser protegidos contra alterações do usuário, e isso fazemos usando o private.
Relembrando o código do projeto Avião, até o momento, por exemplo, temos na classe Aviao que a velocidade máxima é definida pelo tipo dele, mas, pelos atributos estarem públicos, é possível alterar no programa principal, como por exemplo:
Aviao *av = new Aviao(1);
av->imprimir();
av->velMax = 150;
av->imprimir();
delete av;
No caso acima, a velocidade do tipo de avião (jato tem a velocidade máxima de 800, mas conseguimos alterar para 150, o que está fora das regras do planejado).
Para resolver isso, basta definir a propriedade desejada como private, assim ela só poderá ser alterada pela classe:
class Aviao {
public:
int velocidade = 0;
std::string tipo;
Aviao(int tp);
void imprimir();
private:
int velMax;
};
Dessa forma, não será possível alterar o atributo no programa principal e nem fora da classe, dessa forma, chamar velMax dará erro.
Para imprimir, usamos os métodos getters para isso. Coloque isso na declaração da classe, em public:
const int getVelMax();
void setVelMax(int velMax);
Na implementação:
const int Aviao::getVelMax() {
return velMax;
}
void Aviao::setVelMax(int velMax) {
this->velMax = velMax;
}
E no código principal:
Aviao *av = new Aviao(1);
av->imprimir();
cout << av->getVelMax() << endl;
delete av;
Como visto, o método getter foi usado para obter o valor do atributo privado, temos também o setter que podemos alterar, passando a velocidade como parâmetro. Como ambos são public, eles podem alterar o atributo mesmo estando privado, por pertencerem a mesma classe, isso é o encapsulamento.
Veja um uso de método setter:
Aviao *av = new Aviao(1);
av->imprimir();
av->setVelMax(150);
av->imprimir();
delete av;
PS: Caso não queiram utilizar esse método setter, para não poder alterar o atributo, basta apenas retirar ele. Podemos também utilizar métodos setters privados e usar eles para alterar os elementos dentro da própria classe, ao invés de usar os atributos diretamente.
O polimorfismo de sobreposição tem bases na herança, só que nas classes filhas, o método é sobreposto, como um novo. Isso vale tanto pra métodos comuns quando para virtuais puros (abstratos).
Vamos ver a classe Violao, que terá as classes filhas Guitarra e Cavaquinho sobreescrevendo métodos.
Classe Violao:
class Violao {
public:
int cordas;
const char* tipoAmpli;
virtual void quantCordas();
virtual void amplificacao();
Violao();
};
void Violao::quantCordas() {
std::cout << "Violão tem " << this->cordas << " Cordas!" << std::endl;
}
void Violao::amplificacao() {
std::cout << "Violão tem Amplificação " << this->tipoAmpli << "!" << std::endl;
}
Violao::Violao() {
this->cordas = 6;
this->tipoAmpli = "Acústica";
}
PS: Note que todo método que poderá ser sobreescrito, deverá ter a indicação virtual
na declaração da classe pai.
Classe Cavaquinho:
class Cavaquinho : public Violao {
public:
void quantCordas() override;
void amplificacao() override;
Cavaquinho();
};
void Cavaquinho::quantCordas() {
std::cout << "Cavaquinho tem " << this->cordas << " Cordas!" << std::endl;
}
void Cavaquinho::amplificacao() {
std::cout << "Cavaquinho tem Amplificação " << this->tipoAmpli << "!" << std::endl;
}
Cavaquinho::Cavaquinho() {
this->cordas = 4;
this->tipoAmpli= "Acústica";
}
E a classe Guitarra:
class Guitarra : public Violao {
public:
void quantCordas() override;
void amplificacao() override;
Guitarra();
};
void Guitarra::quantCordas() {
std::cout << "Guitarra tem " << this->cordas << " Cordas!" << std::endl;
}
void Guitarra::amplificacao() {
std::cout << "Guitarra tem Amplificação " << this->tipoAmpli << "!" << std::endl;
}
Guitarra::Guitarra() {
this->cordas = 6;
this->tipoAmpli = "Elétrica";
}
PS: Note também que todo método sobreescrito nas classe filhas deverão ter uma indicação override
na declaração (o override só existe no C++11).
E no main, basta chamar assim:
#include <iostream>
#include "Violao.h"
#include "Guitarra.h"
#include "Cavaquinho.h"
using namespace std;
int main() {
Violao *v = new Violao();
v->quantCordas();
v->amplificacao();
Guitarra *g = new Guitarra();
g->quantCordas();
g->amplificacao();
Cavaquinho *c = new Cavaquinho();
c->quantCordas();
c->amplificacao();
delete v;
delete g;
delete c;
return 0;
}
Para implantar um polimorfismo de sobrecarga (no qual um mesmo método tem assinaturas diferentes) é simples, veja um exemplo abaixo:
class Pessoa {
public:
void levantar();
void levantar(const char* tempo);
void levantar(int hora, int minuto);
void levantar(bool folga);
void levantar(float temperatura);
};
void Pessoa::levantar() {
std::cout << "Vou Levantar da Cama!" << std::endl;
}
void Pessoa::levantar(const char* tempo) {
std::string verif = (std::string)tempo;
if(!verif.compare("Chuva") || !verif.compare("Tempestade")) {
std::cout << "Não vou Levantar pra Sair, está Chovendo!" << std::endl;
}
else {
std::cout << "Estou Levantando, o Tempo tá Bom!" << std::endl;
}
}
void Pessoa::levantar(int hora, int minuto) {
if(hora >= 10) {
std::cout << "Acordei muito tarde!" << std::endl;
}
else if(hora < 8) {
std::cout << "Acordei muito cedo!" << std::endl;
}
else {
std::cout << "Acordei na hora certa!" << std::endl;
}
}
void Pessoa::levantar(bool folga) {
if(folga == true) {
std::cout << "Vou dormir mais porque é folga!" << std::endl;
}
else {
std::cout << "Vou levantar pra trabalhar!" << std::endl;
}
}
void Pessoa::levantar(float temperatura) {
if(temperatura < 21) {
std::cout << "Tá frio demais pra levantar!" << std::endl;
}
else {
std::cout << "A temperatura tá boa, vou levantar!" << std::endl;
}
}
E no main, podemos fazer assim:
#include <iostream>
#include "Pessoa.h"
using namespace std;
int main(){
Pessoa *p = new Pessoa();
p->levantar();
p->levantar("Chuva");
p->levantar("Sol");
p->levantar(10, 30);
p->levantar(9, 00);
p->levantar(7, 15);
p->levantar(true);
p->levantar(false);
p->levantar(19.5f);
p->levantar(23.7f);
delete p;
return 0;
}
PS: Note que substituímos o tipo string por const char* (não esqueça do asterisco), pode ser que nem sempre dê pra usar string ou mesmo float (para este funcionar, devemos colocar um f depois do número float, no caso de classes). Independente disso, para a sobrecarga funcionar, os parâmetros deverão ser de tipos diferentes.
A sobrecarga também é possível ser feita em construtores, onde um recebe parâmetros diferente do outro, como por exemplo, um construtor vazio sem parâmetros e outro com parâmetros alterados dentro dele.