O que é uma classe
Uma classe é uma planta para um tipo personalizado que agrupa dados (variáveis-membro) e comportamento (funções-membro) em uma única unidade. Enquanto os tipos embutidos como int e double descrevem valores individuais, uma classe permite modelar um conceito inteiro - uma conta bancária, um ponto 2D, um jogador - como um único valor que você pode passar adiante.
Você já usou classes sem definir nenhuma: std::string e std::vector são classes da biblioteca padrão. Chamar name.length() ou v.push_back(3) é chamar uma função-membro de um objeto. Agora você vai construir seus próprios tipos da mesma forma e depois seguir para os construtores para inicializá-los.
Definindo uma classe e criando objetos
A definição de uma classe lista seus membros entre chaves e termina com ponto e vírgula - esquecer esse ; final é um dos erros mais comuns de iniciantes. Cada objeto que você cria a partir da classe recebe sua própria cópia das variáveis-membro.
rex e luna são dois objetos distintos. Alterar rex.name não afeta luna.name - cada objeto guarda seus próprios dados. Você acessa um membro com o operador ponto . sobre um objeto (ou -> quando tem um ponteiro para ele).
public vs private
Por padrão, tudo em uma class é private: somente as próprias funções-membro da classe podem acessá-lo. O rótulo public: abre os membros para o código externo. Essa divisão é o coração do encapsulamento - você expõe uma interface segura e esconde os dados internos para que não possam entrar em um estado inválido.
Como count é private, nenhum chamador pode inserir um valor negativo ou sem sentido - eles precisam passar por increment(). O const após a lista de parâmetros de value() promete que a função não modificará o objeto, o que permite chamá-la em objetos const e sinaliza a intenção para quem lê o código. Veja os especificadores de acesso para a história completa sobre public, private e protected.
Declaração vs. definição de métodos
Para classes pequenas, definir os métodos inline (como acima) está ótimo. Para classes maiores, é comum declarar os métodos dentro da classe e definir seus corpos fora, usando a sintaxe de escopo ClassName::method. Isso mantém a definição da classe legível como um resumo da interface.
O prefixo Rectangle:: informa ao compilador a qual classe a função pertence. Dentro de uma definição assim você ainda se refere aos membros pelos nomes simples (width, area()) - o compilador sabe que eles pertencem ao objeto sobre o qual o método foi chamado.
O ponteiro this
Dentro de qualquer função-membro não estática, this é um ponteiro para o objeto sobre o qual a função foi invocada. Você normalmente não precisa dele, já que o nome simples de um membro já se refere ao objeto atual. Ele se torna útil quando um parâmetro encobre (shadow) um membro - eles têm o mesmo nome - e você precisa desambiguar.
Sem this->, escrever x = x; dentro de setX atribuiria o parâmetro a si mesmo e deixaria o membro intocado - um bug silencioso. this->x torna tudo inequívoco. Muitos projetos evitam o problema por completo dando nomes diferentes aos parâmetros (por exemplo, setX(int newX)), mas você verá this-> o tempo todo em código real.
Erros comuns
Algumas armadilhas das classes pegam as pessoas repetidamente:
- Esquecer o ponto e vírgula de fechamento. A definição de uma classe termina com
};. Tire o;e você terá uma enxurrada de erros confusos apontando para o que vem depois da classe, não para a classe em si. - Esquecer de inicializar os membros. Membros de tipos embutidos como
intedoublenão são zerados automaticamente. Ler um membro não inicializado é comportamento indefinido. Dê valores padrão aos membros (int count = 0;) ou inicialize-os em um construtor. - Acessar membros private de fora.
c.count = 5;em um membro private é um erro de compilação - use um método público em vez disso. Isso é o encapsulamento funcionando como esperado. - Confundir a classe com um objeto.
Dog.bark();está errado -Dogé o tipo. Você chama métodos sobre objetos:rex.bark();.
// Membro não inicializado - ler 'age' é comportamento indefinido:
class Cat {
public:
int age; // sem valor padrão
};
Cat c;
std::cout << c.age; // valor lixo, não 0
Próximo: Construtores
Definir cada membro à mão depois de criar um objeto - rex.name = ...; rex.age = ...; - é tedioso e fácil de esquecer, que é exatamente como você acaba com membros não inicializados. A próxima página aborda os construtores: funções especiais que rodam automaticamente quando um objeto é criado, permitindo garantir que todo objeto comece em um estado válido com uma sintaxe limpa de uma linha Dog rex("Rex", 4);.
Perguntas frequentes
Qual é a diferença entre uma classe e um objeto em C++?
Uma classe é a planta - ela descreve quais dados (variáveis-membro) e qual comportamento (funções-membro) um tipo possui. Um objeto é uma instância concreta construída a partir dessa planta. class Dog { ... }; define o tipo uma única vez; Dog rex; cria um Dog de verdade que você pode usar. Uma mesma classe pode produzir vários objetos independentes.
Qual é a diferença entre class e struct em C++?
Tecnicamente, apenas o nível de acesso padrão: os membros de uma class são private por padrão, enquanto os de uma struct são public por padrão. Ambos podem ter funções-membro, construtores e herança. Por convenção, struct é usada para agrupamentos simples de dados e class para tipos com comportamento e invariantes a proteger.
O que o ponteiro this faz em uma classe C++?
Dentro de uma função-membro, this é um ponteiro para o objeto sobre o qual a função foi chamada. Use-o para distinguir um parâmetro de um membro (this->x = x;) ou para retornar o objeto atual. Você raramente precisa dele para o acesso normal a membros - escrever x já se refere ao x do objeto atual.