Classes e objetos

Estado, invariantes, encapsulamento, construção, destruição, métodos const e operadores.

TLDR

Perguntar sobre esta página

ChatGPTClaudePerplexityGrokGeminiCopiar e abrirDeepSeekCopiar e abrir

Dois campos públicos para horas e minutos permitem atribuir 70 aos minutos. O relógio seguinte controla a construção e o avanço para que cada objeto represente sempre uma hora válida.

A classe define a representação e as operações autorizadas sobre ela.

Definir uma classe e criar objetos

A interface pública diz o que o chamador pode fazer. Os membros privados guardam a representação e só são acessíveis aos membros e amigos da classe. Encapsular é manter uma fronteira que preserva o significado dos dados, não apenas escrever getters e setters para cada campo.

Este relógio guarda minutos desde a meia-noite, numa única representação. O seu invariante é 0 <= minutos_ < 1440. O chamador conhece horas e minutos; não precisa de saber a representação escolhida.

Guardar hora_, minuto_ e minutos_ em simultâneo obrigaria a atualizar três representações da mesma informação.

A construção rejeita valores fora do intervalo e o avanço rejeita quantidades fora de 0 a 1440. Cada operação pública conserva o invariante ou recusa a alteração sem a aplicar. Os detalhes de como um erro interrompe a operação ficam na página Exceções e testes; aqui basta saber que um relógio nunca fica num estado inválido.

Programa completo para compilar localmente:

#include <iostream>
#include <stdexcept>

class Relogio {
public:
    Relogio(int hora, int minuto) {
        if (hora < 0 || hora >= 24 || minuto < 0 || minuto >= 60)
            throw std::invalid_argument("Hora invalida");
        minutos_ = 60 * hora + minuto;
    }
    int hora() const { return minutos_ / 60; }
    int minuto() const { return minutos_ % 60; }
    void avancar(int quantidade) {
        if (quantidade < 0 || quantidade > 1440)
            throw std::invalid_argument("Avanco invalido");
        minutos_ = (minutos_ + quantidade) % 1440;
    }
    bool operator==(const Relogio& outro) const {
        return minutos_ == outro.minutos_;
    }
private:
    int minutos_ = 0;
};

int main() {
    Relogio a(23, 50);
    Relogio b = a;
    a.avancar(25);
    const Relogio& leitura = a;
    std::cout << leitura.hora() << ":" << leitura.minuto() << "\n";
    std::cout << b.hora() << ":" << b.minuto() << "\n";
    std::cout << std::boolalpha << (a == Relogio(0, 15)) << "\n";
}

Saída:

0:15
23:50
true

O construtor transforma 23:50 em 60 * 23 + 50 = 1430. Avançar 25 produz 1455, cujo resto por 1440 dá 15, ou seja 0:15. A cópia b conserva 1430 (23:50), porque cada objeto tem o seu próprio minutos_. Consulta por const Relogio& não copia o relógio nem o altera.

Encapsulamento

O construtor rejeita horas fora de 0 a 23 e minutos fora de 0 a 59. avancar aceita 0 a 1440, por isso a soma continua representável.

Nenhuma operação pública permite escrever minutos_ = -1.

A validação tem de acontecer antes de alterar o estado. Se avancar(-5) lança uma exceção, o relógio fica com o mesmo valor. Uma classe que permite um estado inválido e espera que cada chamador o corrija não está a proteger o seu invariante.

Construtores e destrutor

Um construtor tem o nome da classe e não tem tipo de retorno. Uma lista de inicialização, como Marca(const std::string& nome) : nome_(nome), constrói os membros diretamente. Atribuir no corpo acontece depois da inicialização e não a substitui. Membros const e referências precisam de ser inicializados, não podem receber a primeira atribuição no corpo.

As bases são construídas primeiro; os membros são construídos pela ordem de declaração na classe, mesmo que a lista do construtor use outra ordem, e depois corre o corpo do construtor. O destrutor tem o nome ~Classe() e não recebe parâmetros. Depois do seu corpo, os membros são destruídos na ordem inversa.

Construção e destruição em âmbitos
#include <iostream>
#include <string>

class Marca {
public:
    explicit Marca(const std::string& nome) : nome_(nome) {
        std::cout << "Criar " << nome_ << "\n";
    }
    ~Marca() { std::cout << "Destruir " << nome_ << "\n"; }
private:
    std::string nome_;
};

int main() {
    Marca a("A");
    {
        Marca b("B");
        Marca c("C");
        std::cout << "Dentro\n";
    }
    std::cout << "Fora\n";
}
Resultado

Executa o código para ver a saída e os gráficos.

Saída:

Criar A
Criar B
Criar C
Dentro
Destruir C
Destruir B
Fora
Destruir A
Ponto do programaObjetos vivosEfeito
Antes do bloco interiorAconstruir A
Depois de criar cA, B, Cconstruir B e depois C
Saída do bloco interiorAdestruir C e depois B
Fim de mainnenhumdestruir A

A ordem inversa permite que os objetos mais recentes sejam limpos antes dos que já existiam ao entrarem no bloco. Não chames o destrutor de um objeto automático; um objeto dinâmico é destruído pelo dono, por exemplo através de unique_ptr.

explicit num construtor evita certas conversões implícitas para o tipo. Neste caso, exige que a construção de uma Marca seja intencional. O exemplo escreve mensagens para tornar a ordem visível; em código habitual, o destrutor trata recursos e evita lançar exceções.

Métodos const

O const depois dos parâmetros, como em int hora() const, permite chamar o método num objeto constante ou por uma referência constante. Dentro do método, this dá acesso constante ao objeto. avancar não é constante porque altera o estado.

Uma consulta que devolve um apontador modificável para um membro pode permitir alterar estado indiretamente. Por isso, const-correctness também depende dos tipos devolvidos, não apenas da palavra const no método. Usa resultados por valor ou acessos de leitura quando a interface promete só consultar.

this, static e visibilidade

Numa chamada a.avancar(25), this aponta para a, por isso this->minutos_ e minutos_ acedem ao mesmo membro dentro do método; usa this quando ajuda a distinguir nomes ou a devolver o próprio objeto.

Um membro static pertence à classe e não a um objeto específico. Neste exemplo, conta chamadas ao construtor sem argumentos:

// Excerto: numera as chamadas ao construtor sem argumentos.
struct Contador {
    static int criados;
    int id;
    Contador() : id(++criados) {}
};
int Contador::criados = 0;

Depois de Contador a; Contador b;, criados vale 2, com a.id == 1 e b.id == 2. A cópia implícita Contador c = a copiaria o identificador sem chamar esse construtor nem incrementar criados. Por isso, este contador não mede todos os objetos vivos nem garante identificadores únicos perante cópias.

Uma função estática não tem this; só pode aceder a membros de instância se receber um objeto. Uma variável estática local tem duração até ao fim do programa, embora o seu nome só seja visível na função.

Em class, os membros são privados por defeito. public expõe o contrato. protected dá acesso também a classes derivadas e aumenta a quantidade de código capaz de alterar a representação. Preferir estado privado facilita manter o invariante, inclusive quando existe herança.

Sobrecarga de operadores

operator== define a comparação entre dois relógios e devolve bool. Como ambos usam a mesma representação normalizada, basta comparar minutos_. Um operador deve conservar o significado habitual. Não uses + para uma operação surpreendente que destrói ou altera o operando esquerdo. Quando o operador precisa de simetria entre tipos diferentes, prefere uma função livre que recebe os dois operandos; um operador membro recebe o operando esquerdo através de this.

A sobrecarga não permite criar operadores novos, mudar a precedência ou modificar operações entre dois tipos fundamentais. Pelo menos um dos operandos deve ter tipo de classe ou enumeração. friend concede acesso privado a uma função específica, não torna todos os campos públicos.

Copiar objetos com memória própria

Quando todos os membros gerem os seus recursos, a cópia gerada costuma servir. Se um membro é um apontador dono, copiar só o endereço não duplica o recurso. A página Cópia, movimento e propriedade constrói essa cópia independente.

Exercícios

Preservar o estado quando há erro

No Relogio da lição, cria-se Relogio r(23, 55). Chama-se r.avancar(10) e depois tenta-se r.avancar(-1), apanhando a exceção. Qual é o estado final e porquê? Considera depois acrescentar void set_minutos(int n) { minutos_ = n; } à interface pública. Dá uma chamada que viole o invariante e explica por que tornar o campo privado não basta se esse método existir.

Pista

Segue os minutos desde a meia-noite e repara onde está a validação.

Solução

O estado final é 0:5: 23 * 60 + 55 = 1435, 1435 + 10 = 1445 e 1445 % 1440 = 5. O primeiro avanço atravessa a meia-noite. O segundo não altera o objeto porque testa o argumento antes da atribuição. O invariante continua válido e a falha conserva o estado anterior. A impressão é 0:5, sem zero à esquerda. Um r.set_minutos(-1) introduziria um estado inválido por um método público. O encapsulamento depende de todos os caminhos de escrita conservarem o invariante, não apenas do acesso privado aos campos.

Confere a tua resposta:

  • Converti 23:55 para 1435 minutos.
  • Obtive 0:5 depois do avanço.
  • Justifiquei que a exceção deixa o estado intacto.
  • Mostrei uma chamada ao setter que viola o invariante.

Erros frequentes

Não apliques a quantidade inválida e só depois tentes corrigir o relógio.

Voltar à explicação
Construir e destruir membros

Uma classe declara os membros A primeiro_; e depois B segundo_;, mas o construtor lista segundo_(…), primeiro_(…). Qual é a ordem de construção e destruição dos membros?

Pista

A ordem de declaração na classe define a construção, não a lista do construtor.

Solução

Constrói primeiro primeiro_, depois segundo_ e só então corre o corpo do construtor. Na destruição, o corpo do destrutor corre antes de destruir os membros, que são destruídos como segundo_ e primeiro_. A lista deve seguir a ordem de declaração para não sugerir outra ordem.

Confere a tua resposta:

  • Usei a ordem de declaração para a construção.
  • Inverti a ordem para a destruição.
  • Distingui os corpos do construtor e destrutor dos membros.

Erros frequentes

Não inicializes um membro a partir de outro que ainda não foi construído.

Voltar à explicação

À tua maneira

Aparência

Modo de cor
Tema
Cor de destaque

Leitura

Fonte de leitura

Álgebra, lógica e uma ideia de cada vez.

Código

Fonte do código

Navegação

Cursor de leitura, sem edição. h, j, k, l movem; / pesquisa nesta página. v seleciona caracteres; V seleciona linhas; y copia. Tab permite destacar ou comentar. Espaço ou Enter abre blocos recolhíveis. Nos cartões, h, j, k, l mudam o foco.

CSS personalizado

Ativa uma sugestão ou guarda o teu CSS. Só muda este navegador.

Usa --page, --text e --accent para acompanhar o tema.

Seletores e recuperação

.site-header, .prose, .course-card, [data-reading-history], [data-playground] e .page-actions.

Abre a aparência com a tecla vírgula ou no rodapé. Se o CSS esconder os controlos, abre esta página sem CSS personalizado.

Pesquisar

Escreve para pesquisar em todo o site.

para escolher · Enter para abrir · Esc para fechar

Copiar fórmula

A cópia automática não está disponível. Copia o LaTeX abaixo.

Pesquisar nesta página

Enter procura · n / N repetem · Esc cancela. Ctrl / Cmd K pesquisa todo o site.

Atalhos de teclado

Clica numa tecla para a mudar. Esc cancela. Backspace desativa.

PesquisarCtrl / Cmd K

Os atalhos não interferem enquanto escreves. Tab e Enter funcionam sempre.