Aprenda C++

  • Página Inicial
  • Contato!
  • Tudo sobre C++ Parte 1!
  • Tudo sobre C++ Parte 2!
  • Tudo sobre C++ Parte 3!
  • Tudo sobre C++ Parte 4!
  • Tudo sobre C++ Parte 5!
  • Tudo sobre C++ Parte 6!
  • Tudo sobre C++ Parte 7!
  • Tudo sobre C++ Parte 8!
  • Tudo sobre C++ Parte 9!
  • Tudo sobre C++ Parte 10!
  • Tudo sobre C++ Parte 11!
  • Tudo sobre C++ Parte 12!
  • Tudo sobre C++ Parte 13!
  • Tudo sobre C++ Parte 14!
  • Tudo sobre C++ Parte 15!
  • Tudo sobre C++ Parte 16!
  • Orientação a Objetos em C++ - Parte 4

    Classes em C++ Moderno

    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;
    
    

    Encapsulamento

    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.

    Polimorfismo

    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.