C++ O que é POO

Até agora, nos seus programas, os dados ficavam de um lado (variáveis, structs) e as ações ficavam do outro (funções). Funciona, mas, quando o programa cresce, fica difícil saber qual função mexe em qual dado. A Programação Orientada a Objetos (POO) junta as duas coisas: cada "coisa" do programa carrega os seus dados e as ações que sabe fazer. Esta aula é a porta de entrada do Nível 3. Ela explica a ideia, sem pressa, antes de você escrever a sua primeira classe na próxima aula.

Você vai aprender
  • O que é uma classe (o molde) e o que é um objeto (a coisa feita com o molde).
  • Como o mesmo programa da lojinha da Ana fica escrito sem POO e com POO.
  • Por que tanta gente organiza programas assim.
  • A ideia dos quatro pilares: encapsulamento, herança, polimorfismo e abstração.
  • O que muda na POO do C++ em relação a Java e Python: objetos na pilha, cópias de verdade e nada de coletor de lixo.
  • Dois erros de quem está começando: usar o molde como se fosse objeto e escrever new como em Java.

Todos os programas desta aula foram compilados e executados na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp/poo, com o g++ 15.2 e o comando padrão do guia, g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa (veja Compilar com g++).


A ideia: a ficha de cadastro da lojinha

A Ana tem uma lojinha de papelaria. Para não se perder, ela imprimiu uma ficha de cadastro de produto, com três campos em branco: Nome, Preço e Estoque. No verso, a ficha traz as instruções do que se pode fazer com um produto: "mostrar na vitrine", "vender", "repor".

A ficha em branco não é um produto. Ninguém compra uma ficha em branco. Ela é só o molde: diz quais informações todo produto tem e o que todo produto sabe fazer. Quando a Ana preenche uma cópia da ficha com "caneta, R$ 3, 50 unidades", aí sim ela tem um produto de verdade. Outra cópia, preenchida com "caderno, R$ 12, 8 unidades", é outro produto.

Na lojinhaEm POOEm C++
A ficha em branco (o molde)classeclass Produto { … };
Uma ficha preenchida (a caneta, o caderno)objeto (ou instância)Produto caneta{…};
Os campos: nome, preço, estoqueatributos (dados do objeto)variáveis dentro da classe, os membros de dados
As instruções do verso: mostrar, vendermétodos (ações do objeto)funções dentro da classe, as funções-membro

Guarde a frase: a classe é o molde; o objeto é a coisa feita com o molde. Uma classe, muitos objetos. Outra comparação que ajuda: a receita de bolo (classe) e os bolos que saem do forno (objetos). A receita diz "tem farinha, ovos e açúcar, e vai ao forno"; cada bolo tem a sua própria farinha, e comer um bolo não some com o outro.

"Instância" é só outro nome para objeto: "a caneta é uma instância da classe Produto" quer dizer "a caneta é um objeto feito com o molde Produto". Você vai encontrar as duas palavras em livros e mensagens de erro.


1. O jeito que você já conhece: dados de um lado, funções do outro

Na aula de Structs, você aprendeu a juntar dados num tipo só. Um produto da lojinha, escrito assim, fica com os dados numa struct e as ações em funções soltas, que recebem o produto como parâmetro:

#include <iostream>
#include <string>

struct Produto {
    std::string nome;
    int preco;
    int estoque;
};

void mostrar(const Produto& p) {
    std::cout << p.nome << ": R$ " << p.preco << " (" << p.estoque << " no estoque)\n";
}

void vender(Produto& p, int quantidade) {
    p.estoque -= quantidade;
}

int main() {
    Produto caneta{"caneta", 3, 50};
    vender(caneta, 2);
    mostrar(caneta);
}
$ g++ -std=c++23 -Wall -Wextra solto.cpp -o solto
$ ./solto
caneta: R$ 3 (48 no estoque)

Nada de novo aqui: a struct guarda os dados, mostrar recebe o produto por referência constante (só lê) e vender recebe por referência (para poder mudar o estoque). Os preços desta parte do guia são em reais inteiros, para não desviar a atenção com casas decimais.

O problema aparece quando o programa cresce. Numa loja de verdade haveria mostrar, vender, repor, aplicarDesconto, mudarPreco… todas soltas, espalhadas pelos arquivos, e nada liga essas funções ao Produto além do parâmetro. Pior: qualquer parte do programa pode mexer em p.estoque diretamente, inclusive deixando o estoque negativo.

2. O mesmo programa com POO

Agora o mesmo produto, escrito como uma classe. Não se preocupe em decorar a sintaxe: a próxima aula, Classes e Objetos, explica cada pedaço. Aqui, repare só em onde as coisas estão.

#include <iostream>
#include <string>

class Produto {
public:
    std::string nome;
    int preco;
    int estoque;

    void mostrar() {
        std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
    }

    void vender(int quantidade) {
        estoque -= quantidade;
    }
};

int main() {
    Produto caneta{"caneta", 3, 50};
    Produto caderno{"caderno", 12, 8};

    caneta.vender(2);
    caderno.vender(1);

    caneta.mostrar();
    caderno.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra loja.cpp -o loja
$ ./loja
caneta: R$ 3 (48 no estoque)
caderno: R$ 12 (7 no estoque)
class Produto { … };
A classe: o molde. Diz que todo produto tem nome, preço e estoque e sabe se mostrar e ser vendido. Ela sozinha não cria produto nenhum.
public:
"O que vem a seguir pode ser usado de fora da classe." A aula public e private explica por que às vezes queremos o contrário.
std::string nome; int preco; int estoque;
Os atributos: os campos da ficha. Cada objeto tem os seus.
void mostrar() { … }
Um método: uma função que mora dentro da classe. Repare que ela não recebe o produto como parâmetro: usa nome, preco e estoque direto, porque sempre trabalha sobre o objeto que a chamou.
Produto caneta{"caneta", 3, 50};
Cria um objeto: uma ficha preenchida, chamada caneta.
caneta.vender(2);
Pede à caneta: "venda 2 de você". O ponto (.) liga o objeto à ação. Compare com vender(caneta, 2) da versão sem POO.

A grande mudança está na última linha da tabela: em vez de "função, faça isto com este produto", você diz "produto, faça isto". O objeto vem primeiro, e a ação pertence a ele. E caneta.vender(2) mexeu só no estoque da caneta: o caderno continua com o dele. Cada objeto tem os próprios dados.

Nada mudou na saída. POO não deixa o programa mais rápido nem faz coisas que o C++ "sem POO" não faria. Ela é uma forma de organizar o código. A diferença aparece quando o programa tem 50 arquivos e 10 pessoas mexendo nele.

Experimente: crie um terceiro produto, borracha, que custa R$ 2 e tem 30 unidades. Venda 5 borrachas e mostre. Depois tente vender mais borrachas do que existem e veja o que acontece com o estoque.

#include <iostream>
#include <string>

class Produto {
public:
    std::string nome;
    int preco;
    int estoque;

    void mostrar() {
        std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
    }

    void vender(int quantidade) {
        estoque -= quantidade;
    }
};

int main() {
    Produto caneta{"caneta", 3, 50};
    Produto caderno{"caderno", 12, 8};

    caneta.vender(2);
    caderno.vender(1);

    caneta.mostrar();
    caderno.mostrar();
}

3. Por que usar POO

Ninguém é obrigado a usar POO em C++. Mas ela resolve problemas reais que aparecem quando o programa cresce:

ProblemaComo a POO ajuda
"Onde está a função que mexe no estoque?"Tudo o que é de Produto está dentro de Produto. Para entender um produto, você lê uma classe.
Alguém deixou o estoque negativoA classe pode proibir que mexam nos dados direto e obrigar todo mundo a passar pelos métodos, que conferem as regras.
A loja agora vende livros, que têm autorUma classe nova pode reaproveitar o Produto e acrescentar só o que falta.
Mostrar uma lista misturada de livros e canetas, cada um do seu jeitoObjetos diferentes podem responder à mesma ordem (mostrar()), cada um à sua maneira.
O programa ficou grande demais para caber na cabeçaCada classe vira uma "caixa preta": você usa caneta.vender(2) sem pensar em como ela funciona por dentro.

Veja o segundo problema acontecendo. Com os dados abertos, nada impede isto (a classe Produto é a mesma de loja.cpp; mostramos só o main):

int main() {
    Produto caneta{"caneta", 3, 50};
    caneta.estoque -= 60;   // vendeu mais do que tinha!
    caneta.preco = -3;      // preço negativo?!
    caneta.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra furo.cpp -o furo
$ ./furo
caneta: R$ -3 (-10 no estoque)

O compilador não reclamou de nada, e o programa obedeceu: estoque −10, preço −3. Para o C++, são só números. Quem sabe que estoque negativo não existe é você, e a POO dá as ferramentas para colocar essa regra dentro da classe. É o primeiro pilar.


4. Os quatro pilares (só a ideia)

Livros e entrevistas de emprego falam dos "quatro pilares da POO". São quatro ideias. Cada uma tem a sua própria aula mais adiante; aqui você só precisa reconhecer o nome e o que ele quer dizer.

Encapsulamento: proteger os dados

A classe esconde os seus dados e só deixa mexer neles pelos métodos. Quem quer mudar o preço chama caneta.mudarPreco(4), e o método recusa preço negativo. É o que teria evitado o "R$ −3" acima. É como o caixa da loja: o cliente não abre a gaveta do dinheiro, ele pede ao caixa. Em C++, isso se faz com private e public. Aula: public e private.

Herança: aproveitar um molde para fazer outro

A Ana passou a vender livros. Um livro é um produto (tem nome, preço e estoque) e mais um autor. Em vez de copiar tudo, a classe Livro herda de Produto e acrescenta só o autor. Diz-se que "Livro é um Produto". Aula: Herança.

Polimorfismo: a mesma ordem, respostas diferentes

"Poli" = muitas, "morfo" = formas. A Ana tem uma lista com canetas e livros e manda cada um se mostrar(). A caneta mostra nome e preço; o livro mostra também o autor. Quem chama não precisa saber o que cada um é: dá a mesma ordem e cada objeto responde do seu jeito. Em C++, isso usa a palavra virtual. Aula: Polimorfismo (virtual).

Abstração: mostrar só o que importa

Quem vende uma caneta não precisa saber como o estoque é guardado: precisa de "vender" e "repor". Abstrair é pensar no que uma coisa faz, deixando o como escondido. Em C++, isso chega ao extremo nas classes abstratas: moldes que só dizem "todo produto sabe calcular o frete", sem dizer como, deixando cada tipo de produto completar. Aula: Classes Abstratas.

PilarEm uma fraseFerramenta em C++Aula
EncapsulamentoOs dados ficam protegidos; só os métodos mexem neles.private, publicpublic e private
HerançaUma classe nova aproveita outra e acrescenta coisas.class Livro : public ProdutoHerança
PolimorfismoA mesma chamada faz coisas diferentes conforme o objeto.virtual, overridePolimorfismo
AbstraçãoMostrar o que o objeto faz, esconder como faz.classes e métodos abstratos (= 0)Classes Abstratas
Não precisa usar os quatro sempre. A maioria das classes do dia a dia usa só encapsulamento. Herança e polimorfismo são ferramentas para quando você tem famílias de coisas parecidas. Usar herança "porque é POO" deixa o código mais complicado, não melhor.

5. POO em C++ × Java e Python

Se você já ouviu falar de POO em Java ou Python, algumas coisas em C++ vão parecer estranhas. Se nunca ouviu, esta seção também vale: são as características que tornam o C++ diferente.

Objetos nascem na pilha, sem new

Em Java, todo objeto é criado com new. Em C++, o jeito normal é declarar o objeto como uma variável comum: Produto caneta{"caneta", 3, 50};. Ele fica na pilha (stack), a mesma região de memória das variáveis locais (aula Como a Memória Funciona), nasce na linha da declaração e some sozinho no fim do bloco { }, igual a um int.

O new existe em C++, mas faz outra coisa: cria o objeto em outra região (o heap) e devolve um ponteiro para ele (aula new e delete). Por isso, o hábito de Java dá erro. Veja na seção Quando dá errado.

Não existe coletor de lixo

Java, Python, C# e JavaScript têm um coletor de lixo (garbage collector): de tempos em tempos, um programa auxiliar procura objetos que ninguém usa mais e libera a memória deles. Você não sabe exatamente quando isso acontece.

O C++ não tem coletor de lixo. Em compensação, ele sabe exatamente quando cada objeto morre: um objeto na pilha morre no } que fecha o bloco dele. Nesse momento, o C++ chama uma função especial da classe, o destrutor, que pode fechar arquivos, devolver memória e fazer qualquer limpeza. Essa garantia é a base de uma técnica muito importante do C++, o RAII, que você vai ver em Destrutores e RAII. Para objetos criados com new, quem libera é você, ou melhor, um ponteiro inteligente.

b = a faz uma cópia de verdade

Esta é a diferença que mais surpreende quem vem de outras linguagens. Em C++, um objeto se comporta como um int: atribuir copia. (A classe é a mesma de loja.cpp, só com o método mostrar.)

int main() {
    Produto a{"caneta", 3, 50};
    Produto b = a;          // b é uma CÓPIA de a
    b.nome = "caneta azul";
    b.preco = 4;

    a.mostrar();
    b.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra copia.cpp -o copia
$ ./copia
caneta: R$ 3 (50 no estoque)
caneta azul: R$ 4 (50 no estoque)

Mudar b não mexeu em a: são duas fichas, cada uma com os seus dados. Em Python (e em Java), a mesma ideia dá outro resultado, porque b = a não copia o objeto: só cria outro nome para o mesmo objeto.

class Produto:
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco

a = Produto("caneta", 3)
b = a                   # b é OUTRO NOME para o mesmo objeto
b.nome = "caneta azul"
print(a.nome)
$ python3 copia.py
caneta azul          « mudou o b, e o a mudou junto: é o mesmo objeto

Em C++, quando você quer "outro nome para o mesmo objeto", usa uma referência (Produto& b = a;) ou um ponteiro, e isso fica escrito no código, à vista. Como as cópias podem ser caras (copiar um objeto grande copia tudo o que está dentro), o C++ tem regras próprias para copiar e mover objetos: aula Cópia e Movimento.

Nem tudo precisa estar numa classe

Em Java, todo código mora dentro de uma classe, até o main. Em C++, não: o main é uma função solta, e você pode misturar classes, funções soltas e templates à vontade. O C++ é uma linguagem multiparadigma: a POO é uma das ferramentas, não a única.

AssuntoC++Java / Python
Onde o objeto nasceNormalmente na pilha: Produto p{…};Sempre no heap (Java: new Produto(…); Python: Produto(…))
Quem libera a memóriaO próprio C++, no fim do bloco (destrutor); com new, você ou um ponteiro inteligenteO coletor de lixo, quando ele quiser
b = aCopia o objeto inteiroOs dois nomes apontam para o mesmo objeto
Código fora de classesPode (o main é solto)Java: não; Python: pode
Proteção dos dadosprivate conferido pelo compiladorJava: private; Python: só por convenção (_nome)

Quando dá errado

Usar o molde como se fosse um objeto
#include <iostream>
#include <string>

class Produto {
public:
    std::string nome;
    int preco;
    int estoque;
};

int main() {
    Produto.preco = 5;
}
$ g++ -std=c++23 -Wall -Wextra molde.cpp -o molde
molde.cpp: In function ‘int main()’:
molde.cpp:12:12: error: expected unqualified-id before ‘.’ token
   12 |     Produto.preco = 5;
      |            ^

O que aconteceu: Produto é a classe, a ficha em branco. Não existe "o preço da ficha em branco": existe o preço desta caneta, daquele caderno. O g++ diz que, antes do ponto, esperava o nome de um objeto (um unqualified-id, um nome simples), não de um tipo. O Clang, compilador do editor do guia, é mais direto: error: cannot use dot operator on a type ("não dá para usar o operador ponto num tipo").

Como resolver: crie um objeto e use o ponto nele: Produto caneta{"caneta", 3, 50}; e depois caneta.preco = 5;.

Criar o objeto com new, como em Java
#include <iostream>
#include <string>

class Produto {
public:
    std::string nome;
    int preco;
    int estoque;
};

int main() {
    Produto caneta = new Produto();
}
$ g++ -std=c++23 -Wall -Wextra novo.cpp -o novo
novo.cpp: In function ‘int main()’:
novo.cpp:12:22: error: conversion from ‘Produto*’ to non-scalar type ‘Produto’ requested
   12 |     Produto caneta = new Produto();
      |                      ^~~~~~~~~~~~~

O que aconteceu: em C++, new Produto() não devolve um Produto: devolve um Produto*, um ponteiro para um produto criado no heap. A mensagem diz: "foi pedida uma conversão de Produto* para o tipo Produto", e essa conversão não existe. O Clang diz o mesmo com outras palavras: no viable conversion from 'Produto *' to 'Produto'.

Como resolver: no C++, você quase nunca precisa de new. Escreva só Produto caneta{}; (ou com os valores dentro das chaves). Se um dia precisar mesmo do objeto no heap, use std::make_unique<Produto>() (aula Ponteiros Inteligentes), nunca um new solto.


Referência rápida

TermoSignificadoExemplo
ClasseO molde: diz que dados e ações um tipo de coisa temclass Produto { … };
Objeto / instânciaUma coisa feita com o molde, com os próprios dadosProduto caneta{"caneta", 3, 50};
Atributo (membro de dados)Uma variável que cada objeto carregaint estoque;
Método (função-membro)Uma função que trabalha sobre o objeto que a chamoucaneta.vender(2);
EncapsulamentoEsconder os dados e expor métodosprivate: / public:
HerançaUma classe aproveita outraclass Livro : public Produto
PolimorfismoMesma chamada, comportamentos diferentesvirtual void mostrar();
AbstraçãoO que faz, não como fazvirtual int frete() = 0;

Exercícios

1. Molde ou objeto?

Na linha Produto lapis{"lápis", 1, 100};, qual palavra é a classe?

2. Quem é o objeto?

Na mesma linha, qual é o nome do objeto criado?

3. Chamar a ação

Complete para pedir ao objeto lapis que venda 10 unidades, no estilo POO:

4. Qual pilar da POO impediria alguém de escrever caneta.preco = -3;?

5. Em C++, depois de Produto b = a; e b.preco = 9;, quanto vale a.preco, se antes valia 3?

6. Quando um objeto criado como Produto caneta{…}; dentro de uma função deixa de existir?

7. A Ana quer uma classe Livro que tenha tudo de Produto e mais o autor. Que pilar ela vai usar?

Missão final: o estilo POO

Missão: crie um objeto mochila que custa R$ 80 e tem 5 unidades. Venda 1 mochila e 5 canetas chamando o método de cada objeto (objeto.vender(…)) e mostre os dois. A saída deve ter:

caneta: R$ 3 (45 no estoque)
mochila: R$ 80 (4 no estoque)
#include <iostream>
#include <string>

class Produto {
public:
    std::string nome;
    int preco;
    int estoque;

    void mostrar() {
        std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
    }

    void vender(int quantidade) {
        estoque -= quantidade;
    }
};

int main() {
    Produto caneta{"caneta", 3, 50};
    // crie a mochila aqui

    // venda e mostre aqui
}
#include <iostream>
#include <string>

class Produto {
public:
    std::string nome;
    int preco;
    int estoque;

    void mostrar() {
        std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
    }

    void vender(int quantidade) {
        estoque -= quantidade;
    }
};

int main() {
    Produto caneta{"caneta", 3, 50};
    Produto mochila{"mochila", 80, 5};

    caneta.vender(5);
    mochila.vender(1);

    caneta.mostrar();
    mochila.mostrar();
}

Resumo

  • Classe é o molde (a ficha em branco); objeto (ou instância) é a coisa feita com o molde (a ficha preenchida). Uma classe, muitos objetos, cada um com os próprios dados.
  • Na POO, dados (atributos) e ações (métodos) moram juntos. Em vez de vender(caneta, 2), você escreve caneta.vender(2).
  • POO organiza o código; não deixa o programa mais rápido. Ela ajuda a achar as coisas, proteger os dados e reaproveitar código.
  • Os quatro pilares: encapsulamento (proteger os dados), herança (aproveitar um molde), polimorfismo (mesma ordem, respostas diferentes) e abstração (o que faz, não como faz).
  • Em C++, objetos nascem normalmente na pilha, sem new, e morrem no fim do bloco. Não há coletor de lixo: o destrutor roda na hora certa.
  • Em C++, b = a copia o objeto. Para "outro nome do mesmo objeto", use referência ou ponteiro.
  • Próxima aula: escrever a sua primeira classe, em Classes e Objetos.