Módulo 8: Estruturas (Structs) e Classes¶
Bem-vindo ao Módulo 8 do curso "Programação em Arduino: Conceitos Fundamentais sem Hardware". Neste módulo, você irá aprofundar seu conhecimento sobre estruturas (structs
) e classes na linguagem de programação Arduino (C/C++). Estruturas e classes são fundamentais para a programação orientada a objetos, permitindo a criação de tipos de dados personalizados e a organização eficiente do código.
Objetivos do Módulo¶
- Compreender o conceito de estruturas (
structs
) e classes na programação. - Aprender a declarar e utilizar estruturas em Arduino.
- Introduzir conceitos básicos de programação orientada a objetos (POO) com classes.
- Trabalhar com atributos e métodos dentro de classes.
- Entender o conceito de encapsulamento, herança e polimorfismo.
- Implementar construtores e destrutores em classes.
- Resolver exercícios práticos para consolidar o conhecimento sobre estruturas e classes.
1. Introdução às Estruturas (Structs
) e Classes¶
1.1 O que são Estruturas (Structs
)?¶
Uma estrutura (struct
) é uma coleção de variáveis agrupadas sob um único nome para representar uma entidade mais complexa. Cada variável dentro de uma estrutura é chamada de membro ou campo da estrutura.
1.2 O que são Classes?¶
Uma classe é uma extensão das estruturas, incorporando não apenas dados (atributos) mas também funções (métodos) que operam sobre esses dados. Classes são a base da programação orientada a objetos (POO), permitindo a criação de objetos que possuem propriedades e comportamentos.
1.3 Diferenças entre struct
e class
¶
- Acesso Padrão:
- Em
structs
, os membros são públicos por padrão. -
Em
classes
, os membros são privados por padrão. -
Funcionalidades:
Structs
são adequadas para agrupamentos simples de dados.Classes
suportam POO completa, incluindo encapsulamento, herança e polimorfismo.
2. Trabalhando com Estruturas (Structs
)¶
2.1 Declaração e Inicialização de Structs
¶
Sintaxe:
Exemplo:
2.2 Utilizando Structs
no Arduino¶
Exemplo Prático:
struct Sensor {
int id;
float valor;
};
Sensor sensor1;
Sensor sensor2;
void setup() {
Serial.begin(9600);
sensor1.id = 1;
sensor1.valor = 23.5;
sensor2.id = 2;
sensor2.valor = 47.8;
Serial.print("Sensor ");
Serial.print(sensor1.id);
Serial.print(": ");
Serial.println(sensor1.valor);
Serial.print("Sensor ");
Serial.print(sensor2.id);
Serial.print(": ");
Serial.println(sensor2.valor);
}
void loop() {
// Não há código no loop
}
Explicação:
- Define uma estrutura
Sensor
com membrosid
evalor
. - Declara duas variáveis
sensor1
esensor2
do tipoSensor
. - Inicializa os membros e imprime os valores no Monitor Serial.
3. Introdução à Programação Orientada a Objetos (POO) com Classes¶
3.1 Conceitos Básicos de POO¶
- Objeto: Uma instância de uma classe que possui estado e comportamento.
- Classe: Um molde para criar objetos, definindo atributos e métodos.
- Encapsulamento: O ato de esconder os detalhes internos de uma classe e expor apenas o necessário.
- Herança: Permite que uma classe herde atributos e métodos de outra.
- Polimorfismo: Permite que objetos de diferentes classes sejam tratados de forma uniforme.
3.2 Benefícios da POO¶
- Reutilização de Código: Criação de classes reutilizáveis.
- Organização: Estruturação lógica do código.
- Manutenção: Facilita a atualização e correção de código.
- Escalabilidade: Permite o crescimento do projeto de forma ordenada.
4. Declaração e Uso de Classes¶
4.1 Declaração de uma Classe¶
Sintaxe:
class NomeClasse {
public:
// Atributos
tipo atributo1;
tipo atributo2;
// Métodos
void metodo1();
tipo metodo2();
};
Exemplo:
class Motor {
public:
int rpm;
float temperatura;
void ligar() {
Serial.println("Motor ligado.");
}
void desligar() {
Serial.println("Motor desligado.");
}
float lerTemperatura() {
return temperatura;
}
};
4.2 Utilizando Classes no Arduino¶
Exemplo Prático:
class Motor {
public:
int rpm;
float temperatura;
void ligar() {
Serial.println("Motor ligado.");
}
void desligar() {
Serial.println("Motor desligado.");
}
float lerTemperatura() {
return temperatura;
}
};
Motor motor1;
void setup() {
Serial.begin(9600);
motor1.rpm = 1500;
motor1.temperatura = 75.0;
motor1.ligar();
Serial.print("RPM: ");
Serial.println(motor1.rpm);
Serial.print("Temperatura: ");
Serial.println(motor1.lerTemperatura());
}
void loop() {
// Não há código no loop
}
Explicação:
- Define uma classe
Motor
com atributosrpm
etemperatura
e métodosligar
,desligar
elerTemperatura
. - Cria um objeto
motor1
da classeMotor
. - Inicializa os atributos e chama os métodos para ligar o motor e imprimir os valores no Monitor Serial.
5. Construtores e Destrutores¶
5.1 Construtores¶
Um construtor é um método especial que é chamado automaticamente quando um objeto é criado. Ele é usado para inicializar os atributos do objeto.
Sintaxe:
Exemplo:
class Sensor {
public:
int id;
float valor;
// Construtor
Sensor(int sensorId, float sensorValor) {
id = sensorId;
valor = sensorValor;
}
void imprimirDados() {
Serial.print("Sensor ");
Serial.print(id);
Serial.print(": ");
Serial.println(valor);
}
};
void setup() {
Serial.begin(9600);
Sensor sensor1(1, 23.5);
Sensor sensor2(2, 47.8);
sensor1.imprimirDados();
sensor2.imprimirDados();
}
void loop() {
// Não há código no loop
}
Explicação:
- A classe
Sensor
possui um construtor que inicializa os atributosid
evalor
. - Cria dois objetos
sensor1
esensor2
com valores iniciais. - Chama o método
imprimirDados
para exibir os dados no Monitor Serial.
5.2 Destrutores¶
Um destruidor é um método especial que é chamado automaticamente quando um objeto é destruído ou sai de escopo. É usado para liberar recursos ou realizar tarefas de limpeza.
Sintaxe:
Exemplo:
class Motor {
public:
Motor() {
Serial.println("Motor criado.");
}
~Motor() {
Serial.println("Motor destruído.");
}
void ligar() {
Serial.println("Motor ligado.");
}
};
void setup() {
Serial.begin(9600);
{
Motor motor1;
motor1.ligar();
} // motor1 é destruído aqui
}
void loop() {
// Não há código no loop
}
Explicação:
- A classe
Motor
possui um construtor que imprime uma mensagem ao ser criado e um destruidor que imprime uma mensagem ao ser destruído. - O objeto
motor1
é criado dentro de um bloco de código{}
e é destruído ao final do bloco.
6. Encapsulamento, Herança e Polimorfismo¶
6.1 Encapsulamento¶
Encapsulamento é o conceito de esconder os detalhes internos de uma classe e expor apenas o necessário através de métodos públicos.
Exemplo:
class ContaBancaria {
private:
float saldo;
public:
ContaBancaria(float saldoInicial) {
saldo = saldoInicial;
}
void depositar(float valor) {
saldo += valor;
}
void sacar(float valor) {
if(valor <= saldo) {
saldo -= valor;
} else {
Serial.println("Saldo insuficiente.");
}
}
float getSaldo() {
return saldo;
}
};
void setup() {
Serial.begin(9600);
ContaBancaria minhaConta(1000.0);
minhaConta.depositar(500.0);
minhaConta.sacar(200.0);
Serial.print("Saldo Atual: ");
Serial.println(minhaConta.getSaldo());
}
void loop() {
// Não há código no loop
}
Explicação:
- A classe
ContaBancaria
encapsula o atributosaldo
como privado. - Métodos públicos
depositar
,sacar
egetSaldo
permitem a manipulação segura do saldo.
6.2 Herança¶
Herança permite que uma classe (classe derivada) herde atributos e métodos de outra classe (classe base), promovendo a reutilização de código.
Exemplo:
class Veiculo {
public:
int rodas;
void mover() {
Serial.println("Veículo em movimento.");
}
};
class Carro : public Veiculo {
public:
string modelo;
void buzinar() {
Serial.println("Buzinando!");
}
};
void setup() {
Serial.begin(9600);
Carro meuCarro;
meuCarro.rodas = 4;
meuCarro.modelo = "Sedan";
Serial.print("Modelo: ");
Serial.println(meuCarro.modelo.c_str());
Serial.print("Rodas: ");
Serial.println(meuCarro.rodas);
meuCarro.mover();
meuCarro.buzinar();
}
void loop() {
// Não há código no loop
}
Explicação:
- A classe
Carro
herda da classeVeiculo
, recebendo o atributorodas
e o métodomover
. - Adiciona o atributo
modelo
e o métodobuzinar
.
6.3 Polimorfismo¶
Polimorfismo permite que objetos de diferentes classes sejam tratados de forma uniforme através de interfaces comuns.
Exemplo:
class Forma {
public:
virtual void desenhar() {
Serial.println("Desenhando uma forma genérica.");
}
};
class Circulo : public Forma {
public:
void desenhar() override {
Serial.println("Desenhando um círculo.");
}
};
class Retangulo : public Forma {
public:
void desenhar() override {
Serial.println("Desenhando um retângulo.");
}
};
void setup() {
Serial.begin(9600);
Forma *formas[2];
formas[0] = new Circulo();
formas[1] = new Retangulo();
for(int i = 0; i < 2; i++) {
formas[i]->desenhar();
}
// Liberação de memória
for(int i = 0; i < 2; i++) {
delete formas[i];
}
}
void loop() {
// Não há código no loop
}
Explicação:
- Define uma classe base
Forma
com um método virtualdesenhar
. - As classes
Circulo
eRetangulo
sobrescrevem o métododesenhar
. - Permite que diferentes objetos sejam chamados de forma polimórfica através de ponteiros da classe base.
7. Exemplos Práticos¶
7.1 Criando uma Classe para Representar um Sensor¶
class Sensor {
private:
int id;
float valor;
public:
// Construtor
Sensor(int sensorId, float sensorValor) {
id = sensorId;
valor = sensorValor;
}
// Métodos para acessar e modificar os atributos
int getId() {
return id;
}
float getValor() {
return valor;
}
void setValor(float novoValor) {
valor = novoValor;
}
void imprimirDados() {
Serial.print("Sensor ");
Serial.print(id);
Serial.print(": ");
Serial.println(valor);
}
};
void setup() {
Serial.begin(9600);
Sensor sensor1(1, 23.5);
Sensor sensor2(2, 47.8);
sensor1.imprimirDados();
sensor2.imprimirDados();
// Atualizando o valor do sensor1
sensor1.setValor(25.0);
Serial.println("Após atualização:");
sensor1.imprimirDados();
}
void loop() {
// Não há código no loop
}
Explicação:
- Define a classe
Sensor
com atributos privadosid
evalor
. - Inclui métodos públicos para acessar e modificar esses atributos.
- No
setup
, cria dois objetossensor1
esensor2
, imprime seus dados e atualiza o valor desensor1
.
7.2 Implementando uma Classe de LED com Métodos para Ligar e Desligar¶
class LED {
private:
int pin;
public:
// Construtor
LED(int ledPin) {
pin = ledPin;
pinMode(pin, OUTPUT);
}
// Método para ligar o LED
void ligar() {
digitalWrite(pin, HIGH);
}
// Método para desligar o LED
void desligar() {
digitalWrite(pin, LOW);
}
// Método para alternar o estado do LED
void alternar() {
int estado = digitalRead(pin);
digitalWrite(pin, !estado);
}
};
LED meuLED(13); // LED conectado ao pino 13
void setup() {
Serial.begin(9600);
Serial.println("Controlando o LED.");
meuLED.ligar();
delay(1000);
meuLED.desligar();
delay(1000);
}
void loop() {
meuLED.alternar();
delay(500);
}
Explicação:
- Define a classe
LED
com um atributo privadopin
. - Inclui métodos para ligar, desligar e alternar o estado do LED.
- No
setup
, liga e desliga o LED uma vez. - No
loop
, alterna o estado do LED a cada meio segundo.
7.3 Criando uma Classe para Gerenciar um Array de Sensores¶
class GerenciadorSensores {
private:
Sensor sensores[5];
int quantidade;
public:
// Construtor
GerenciadorSensores() : sensores{Sensor(1, 23.5), Sensor(2, 47.8), Sensor(3, 30.2), Sensor(4, 50.0), Sensor(5, 25.5)} {
quantidade = 5;
}
// Método para imprimir todos os sensores
void imprimirTodos() {
for(int i = 0; i < quantidade; i++) {
sensores[i].imprimirDados();
}
}
// Método para atualizar o valor de um sensor específico
void atualizarSensor(int id, float novoValor) {
for(int i = 0; i < quantidade; i++) {
if(sensores[i].getId() == id) {
sensores[i].setValor(novoValor);
Serial.print("Sensor ");
Serial.print(id);
Serial.println(" atualizado.");
return;
}
}
Serial.println("Sensor não encontrado.");
}
};
GerenciadorSensores gerenciador;
void setup() {
Serial.begin(9600);
Serial.println("Gerenciador de Sensores:");
gerenciador.imprimirTodos();
// Atualizando o valor do sensor 3
gerenciador.atualizarSensor(3, 35.0);
Serial.println("Após atualização:");
gerenciador.imprimirTodos();
}
void loop() {
// Não há código no loop
}
Explicação:
- Define a classe
GerenciadorSensores
que gerencia um array de 5 objetosSensor
. - Inclui métodos para imprimir todos os sensores e atualizar o valor de um sensor específico.
- No
setup
, imprime todos os sensores, atualiza o sensor comid
3 e imprime novamente.
8. Exercícios Práticos¶
Exercício 1: Criar uma Classe para Representar um Veículo¶
-
Tarefa: Desenvolva uma classe
Veiculo
que possui atributos comomarca
,modelo
eano
. Inclua métodos para ligar, desligar e exibir as informações do veículo. -
Exemplo de Código:
class Veiculo {
private:
String marca;
String modelo;
int ano;
bool ligado;
public:
// Construtor
Veiculo(String m, String mo, int a) : marca(m), modelo(mo), ano(a), ligado(false) {}
// Métodos
void ligar() {
if(!ligado) {
ligado = true;
Serial.println("Veículo ligado.");
} else {
Serial.println("Veículo já está ligado.");
}
}
void desligar() {
if(ligado) {
ligado = false;
Serial.println("Veículo desligado.");
} else {
Serial.println("Veículo já está desligado.");
}
}
void exibirInfo() {
Serial.print("Marca: ");
Serial.println(marca);
Serial.print("Modelo: ");
Serial.println(modelo);
Serial.print("Ano: ");
Serial.println(ano);
Serial.print("Estado: ");
Serial.println(ligado ? "Ligado" : "Desligado");
}
};
Veiculo meuCarro("Toyota", "Corolla", 2020);
void setup() {
Serial.begin(9600);
meuCarro.exibirInfo();
meuCarro.ligar();
meuCarro.exibirInfo();
meuCarro.desligar();
meuCarro.exibirInfo();
}
void loop() {
// Não há código no loop
}
Exercício 2: Implementar Herança com Classes Animal
e Cachorro
¶
-
Tarefa: Crie uma classe base
Animal
com atributos e métodos gerais, e uma classe derivadaCachorro
que herda deAnimal
e adiciona comportamentos específicos. -
Exemplo de Código:
class Animal {
protected:
String nome;
int idade;
public:
// Construtor
Animal(String n, int i) : nome(n), idade(i) {}
// Método para exibir informações
void exibirInfo() {
Serial.print("Nome: ");
Serial.println(nome);
Serial.print("Idade: ");
Serial.println(idade);
}
// Método virtual
virtual void emitirSom() {
Serial.println("Animal emite som.");
}
};
class Cachorro : public Animal {
public:
// Construtor
Cachorro(String n, int i) : Animal(n, i) {}
// Sobrescreve o método emitirSom
void emitirSom() override {
Serial.println("Cachorro diz: Au Au!");
}
};
void setup() {
Serial.begin(9600);
Animal meuAnimal("Genérico", 5);
Cachorro meuCachorro("Rex", 3);
Serial.println("Informações do Animal:");
meuAnimal.exibirInfo();
meuAnimal.emitirSom();
Serial.println("\nInformações do Cachorro:");
meuCachorro.exibirInfo();
meuCachorro.emitirSom();
}
void loop() {
// Não há código no loop
}
Explicação:
- A classe
Animal
possui atributosnome
eidade
, e métodos para exibir informações e emitir som. - A classe
Cachorro
herda deAnimal
e sobrescreve o métodoemitirSom
para emitir um som específico. - No
setup
, cria objetosmeuAnimal
emeuCachorro
e chama seus métodos.
Exercício 3: Implementar Encapsulamento com a Classe Lampada
¶
-
Tarefa: Crie uma classe
Lampada
que possui atributos privadosestado
(ligada/desligada) eintensidade
. Inclua métodos públicos para ligar, desligar, aumentar e diminuir a intensidade, e exibir o estado atual. -
Exemplo de Código:
class Lampada {
private:
bool ligada;
int intensidade; // De 0 a 100
public:
// Construtor
Lampada() : ligada(false), intensidade(0) {}
// Métodos
void ligar() {
ligada = true;
Serial.println("Lâmpada ligada.");
}
void desligar() {
ligada = false;
intensidade = 0;
Serial.println("Lâmpada desligada.");
}
void aumentarIntensidade(int valor) {
if(ligada) {
intensidade += valor;
if(intensidade > 100) intensidade = 100;
Serial.print("Intensidade aumentada para: ");
Serial.println(intensidade);
} else {
Serial.println("Lâmpada está desligada. Não é possível aumentar a intensidade.");
}
}
void diminuirIntensidade(int valor) {
if(ligada) {
intensidade -= valor;
if(intensidade < 0) intensidade = 0;
Serial.print("Intensidade diminuída para: ");
Serial.println(intensidade);
} else {
Serial.println("Lâmpada está desligada. Não é possível diminuir a intensidade.");
}
}
void exibirEstado() {
Serial.print("Estado da Lâmpada: ");
Serial.println(ligada ? "Ligada" : "Desligada");
Serial.print("Intensidade: ");
Serial.println(intensidade);
}
};
Lampada minhaLampada;
void setup() {
Serial.begin(9600);
minhaLampada.exibirEstado();
minhaLampada.ligar();
minhaLampada.aumentarIntensidade(30);
minhaLampada.aumentarIntensidade(50);
minhaLampada.diminuirIntensidade(20);
minhaLampada.exibirEstado();
minhaLampada.desligar();
minhaLampada.exibirEstado();
}
void loop() {
// Não há código no loop
}
Explicação:
- A classe
Lampada
encapsula os atributosligada
eintensidade
como privados. - Inclui métodos públicos para manipular o estado e a intensidade da lâmpada.
- No
setup
, demonstra o uso dos métodos da classeLampada
.
9. Conceitos Importantes¶
9.1 Encapsulamento¶
- Definição: Esconder os detalhes internos de uma classe e expor apenas os necessários através de métodos públicos.
- Benefícios:
- Segurança: Protege os dados contra acessos indevidos.
- Manutenção: Facilita a alteração interna sem afetar o código externo.
9.2 Herança¶
- Definição: Permite que uma classe derive atributos e métodos de outra classe.
- Tipos de Herança:
- Pública (
public
): Os membros públicos e protegidos da classe base permanecem públicos e protegidos na classe derivada. - Privada (
private
): Todos os membros herdados se tornam privados na classe derivada.
9.3 Polimorfismo¶
- Definição: Permite que objetos de diferentes classes sejam tratados de forma uniforme através de interfaces comuns.
- Métodos Virtuais: Facilita o polimorfismo, permitindo que métodos sejam sobrescritos em classes derivadas.
9.4 Gerenciamento de Memória¶
- Importância: Classes e objetos podem consumir memória significativa, especialmente em microcontroladores com recursos limitados.
- Boas Práticas:
- Evitar Alocação Dinâmica Desnecessária: Use objetos estáticos sempre que possível.
- Liberar Memória Alocada Dinamicamente: Utilize
delete
edelete[]
apropriadamente.
9.5 Boas Práticas de Programação Orientada a Objetos¶
- Nomeação Clara: Use nomes significativos para classes, métodos e atributos.
- Responsabilidade Única: Cada classe deve ter uma única responsabilidade ou funcionalidade.
- Modularização: Divida o código em módulos e classes que representam entidades do mundo real ou conceitos lógicos.
- Reutilização de Código: Utilize herança e composição para reutilizar código de forma eficiente.