Projeto Contas Bancárias (POO)
Um banco é o exemplo perfeito de por que a orientação a objetos existe: o saldo de uma conta não pode ser mudado por qualquer um, de qualquer jeito. Neste projeto você vai construir, em oito etapas, um pequeno banco em C++: contas que depositam e sacam com validação, uma exceção própria para "saldo insuficiente", uma conta poupança que rende no fim do mês, uma lista de contas de tipos diferentes, um extrato bem alinhado e, no fim, um programa dividido em três arquivos com um menu que você usa digitando comandos.
- Classes com dados
privatee métodospublic(encapsulamento). - Validar valores: primeiro devolvendo
bool, depois com exceções e uma classe de exceção própria. - Herança e polimorfismo:
virtual,overridee destrutor virtual. std::vector<std::unique_ptr<Conta>>: uma lista de contas de tipos diferentes (ponteiros inteligentes).- Texto alinhado com std::format.
- Separar o programa em .h e .cpp e compilar vários arquivos.
- Ler comandos do teclado com getline.
Todos os programas foram compilados e executados na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp/banco, com o g++ 15.2 e o comando padrão do guia, g++ -std=c++23 -Wall -Wextra. Cada etapa tem uma caixa Experimente em que você faz a etapa no navegador.
O que vamos construir
No fim, a pasta do projeto terá três arquivos:
$ ls conta.cpp conta.h main.cpp
E o programa funciona assim (o que vem depois de banco> é o que a Ana digitou):
$ g++ -std=c++23 -Wall -Wextra main.cpp conta.cpp -o banco $ ./banco Banco da Ana. Comandos: l, d N VALOR, s N VALOR, e N, m, q banco> l 1. Ana (Corrente) 2. Bruno (Poupança) banco> d 1 1000 Depósito feito. banco> d 2 1000 Depósito feito. banco> s 1 250 Saque feito. banco> s 2 5000 Recusado: saldo insuficiente (saldo 1000.00, pedido 5000.00) banco> d 3 10 Erro: não existe a conta 3 banco> d 1 -5 Erro: o valor precisa ser positivo banco> m Mês fechado. banco> e 1 Extrato de Ana (Corrente) Depósito +1000.00 Saque -250.00 Saldo 750.00 banco> e 2 Extrato de Bruno (Poupança) Depósito +1000.00 Rendimento +5.00 Saldo 1005.00 banco> q Até logo!
O caminho até lá:
- A classe Conta: titular, saldo privado, depósito.
- Validar depósito e saque devolvendo
bool. - A exceção SaldoInsuficiente:
throw,tryecatch. - A conta poupança: herança,
virtualeoverride. - Uma lista de contas:
std::vector<std::unique_ptr<Conta>>. - O extrato com
std::format. - Separar em conta.h, conta.cpp e main.cpp.
- O menu: comandos digitados pelo usuário.
Antes de começar
Este projeto junta assuntos do Nível 3. Se algum termo for novidade, a aula indicada em cada etapa explica com calma; aqui o foco é ver as peças funcionando juntas. Crie a pasta do projeto:
$ mkdir -p cpp/banco $ cd cpp/banco
Nas etapas 1 a 6, todo o programa fica num arquivo só, banco.cpp, que vai crescendo. Na etapa 7 ele é dividido em três.
Etapa 1 · A classe Conta
Uma conta tem duas informações: o titular (o nome do dono) e o saldo. E sabe fazer uma coisa, por enquanto: receber um depósito. Em C++, isso vira uma classe:
#include <iostream>
#include <string>
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
void depositar(double valor) {
saldo_ += valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(150.0);
ana.depositar(49.90);
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
| Trecho | O que faz |
|---|---|
class Conta { … }; | O molde de toda conta. Repare no ; depois da chave final: esquecê-lo é um erro clássico. |
public: | Daqui para baixo, tudo pode ser usado por quem tem uma conta (o main, por exemplo). |
explicit Conta(std::string titular) : titular_{titular} {} | O construtor: roda quando a conta nasce e guarda o nome em titular_. O explicit impede que um texto solto vire conta "sem querer" (por exemplo, numa função que espera uma Conta e recebe "Ana"). |
void depositar(double valor) | Soma o valor ao saldo. É o único jeito de fazer o saldo crescer. |
double saldo() const | Devolve o saldo para quem quiser ler. O const promete que o método não muda a conta (veja métodos const). |
private: | Daqui para baixo, só os métodos da própria classe enxergam. É o cofre. |
titular_, saldo_ | Os dados. O _ no fim é um costume para diferenciar o dado (saldo_) do método que o lê (saldo()). |
double saldo_ = 0.0; | Toda conta nasce zerada. Nunca deixe um número sem valor inicial. |
Conta ana{"Ana"}; | Cria o objeto ana chamando o construtor. |
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Ana tem R$ 199.9
O std::cout mostra 199.9, sem o zero do fim. Na etapa 6 o std::format vai mostrar 199.90, como se espera de dinheiro.
O cofre funciona?
O motivo de o saldo ser private é que ninguém de fora possa escrever ana.saldo_ = 1000000;. Coloque essa linha no main, logo depois dos depósitos, e compile:
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco
banco.cpp: In function ‘int main()’:
banco.cpp:24:9: error: ‘double Conta::saldo_’ is private within this context
24 | ana.saldo_ = 1000000;
| ^~~~~~
banco.cpp:17:12: note: declared private here
17 | double saldo_ = 0.0;
| ^~~~~~
banco.cpp:24:9: note: field ‘double Conta::saldo_’ can be accessed via ‘double Conta::saldo() const’
24 | ana.saldo_ = 1000000;
| ^~~~~~
| saldo()
Lendo de cima para baixo: error: ‘double Conta::saldo_’ is private within this context ("é privado neste contexto": o main não pode mexer). A primeira note mostra onde ele foi declarado private, e a segunda até sugere o caminho certo: ler pelo método saldo(). É exatamente o que queremos: o compilador protege o saldo para você. Apague a linha e siga em frente.
double e dinheiro
O double guarda números em binário, e a maioria dos centavos não tem representação exata em binário (como 1/3 não tem em decimal). Veja:
#include <format>
#include <iostream>
int main() {
double total = 0.1 + 0.2;
std::cout << total << '\n';
std::cout << std::format("{}", total) << '\n';
std::cout << (total == 0.3) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra frac.cpp -o frac $ ./frac 0.3 0.30000000000000004 0
O cout arredonda e mostra 0.3, mas o valor guardado é 0.30000000000000004, e por isso total == 0.3 dá falso (0). Para aprender POO, o double serve. Um banco de verdade guarda centavos inteiros (long long, com R$ 10,50 guardado como 1050): fica como desafio no fim da página. Mais em float e double.
Missão (etapa 1): crie uma segunda conta, bruno, do titular "Bruno". Deposite 300 e depois 25.50, e mostre a frase dele igual à da Ana. A saída deve terminar com:
Bruno tem R$ 325.5
#include <iostream>
#include <string>
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
void depositar(double valor) {
saldo_ += valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(150.0);
ana.depositar(49.90);
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
#include <iostream>
#include <string>
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
void depositar(double valor) {
saldo_ += valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(150.0);
ana.depositar(49.90);
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
Conta bruno{"Bruno"};
bruno.depositar(300.0);
bruno.depositar(25.50);
std::cout << bruno.titular() << " tem R$ " << bruno.saldo() << '\n';
}
Etapa 2 · Validar depósito e saque
A conta da etapa 1 aceita ana.depositar(-500), e isso é, na prática, um saque escondido. Também falta o saque. As regras do banco:
- depósito e saque só com valor positivo;
- saque só se houver saldo.
Primeira forma de fazer: cada método devolve bool (true = deu certo, false = recusado) e não mexe no saldo quando recusa.
#include <iostream>
#include <string>
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
bool depositar(double valor) {
if (valor <= 0) {
return false; // recusado: nada muda
}
saldo_ += valor;
return true;
}
bool sacar(double valor) {
if (valor <= 0 || valor > saldo_) {
return false;
}
saldo_ -= valor;
return true;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(1000.0);
if (ana.sacar(250.0)) {
std::cout << "Saque de 250 feito.\n";
}
if (!ana.sacar(5000.0)) {
std::cout << "Saque de 5000 recusado.\n";
}
if (!ana.depositar(-50.0)) {
std::cout << "Depósito de -50 recusado.\n";
}
ana.sacar(800.0); // ninguém conferiu se deu certo...
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
| Trecho | O que faz |
|---|---|
bool depositar(double valor) | Agora o método responde se deu certo. |
if (valor <= 0) { return false; } | Recusa antes de mexer em qualquer coisa. O return sai do método na hora. |
valor <= 0 || valor > saldo_ | No saque, duas condições de recusa ligadas por "ou" (operadores lógicos). |
if (!ana.sacar(5000.0)) | Quem chama confere a resposta. O ! lê-se "se não conseguiu sacar". |
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Saque de 250 feito. Saque de 5000 recusado. Depósito de -50 recusado. Ana tem R$ 750
Funciona, mas olhe a penúltima linha do main: ana.sacar(800.0);. O saque foi recusado (só havia 750) e ninguém percebeu: o programa seguiu como se o dinheiro tivesse saído. Com bool, conferir é opcional, e o que é opcional acaba esquecido.
O C++ tem um remédio parcial: o atributo [[nodiscard]] ("não descarte") C++17. Escreva-o antes de bool nos dois métodos ([[nodiscard]] bool depositar(…)) e compile de novo:
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco
banco.cpp: In function ‘int main()’:
banco.cpp:34:18: warning: ignoring return value of ‘bool Conta::depositar(double)’, declared with attribute ‘nodiscard’ [-Wunused-result]
34 | ana.depositar(1000.0);
| ~~~~~~~~~~~~~^~~~~~~~
banco.cpp:8:24: note: declared here
8 | [[nodiscard]] bool depositar(double valor) {
| ^~~~~~~~~
banco.cpp:45:14: warning: ignoring return value of ‘bool Conta::sacar(double)’, declared with attribute ‘nodiscard’ [-Wunused-result]
45 | ana.sacar(800.0); // ninguém conferiu se deu certo...
| ~~~~~~~~~^~~~~~~
banco.cpp:16:24: note: declared here
16 | [[nodiscard]] bool sacar(double valor) {
| ^~~~~
Dois avisos: ignoring return value … declared with attribute ‘nodiscard’ ("ignorando o valor de retorno"). O segundo pega justamente o saque esquecido. O primeiro pega o depósito inicial, que também não foi conferido. É uma ajuda, mas ainda é só um aviso: o programa compila e roda do mesmo jeito. Na próxima etapa, um saque recusado vai interromper o caminho normal do programa, e não dá para ignorar.
Missão (etapa 2): o método sacar está vazio: sempre recusa. Escreva o corpo dele: devolva false se o valor for zero, negativo ou maior que o saldo; senão, tire o valor do saldo e devolva true. (O aviso unused parameter ‘valor’ some quando você usar o parâmetro.) A saída deve ser:
Saque de 250 feito. Saque de 5000 recusado. Depósito de -50 recusado. Ana tem R$ 750
#include <iostream>
#include <string>
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
bool depositar(double valor) {
if (valor <= 0) {
return false; // recusado: nada muda
}
saldo_ += valor;
return true;
}
bool sacar(double valor) {
// Missão: recuse (return false) se o valor for zero, negativo
// ou maior que o saldo; senão, tire do saldo e devolva true.
return false;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(1000.0);
if (ana.sacar(250.0)) {
std::cout << "Saque de 250 feito.\n";
}
if (!ana.sacar(5000.0)) {
std::cout << "Saque de 5000 recusado.\n";
}
if (!ana.depositar(-50.0)) {
std::cout << "Depósito de -50 recusado.\n";
}
ana.sacar(800.0); // ninguém conferiu se deu certo...
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
#include <iostream>
#include <string>
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
bool depositar(double valor) {
if (valor <= 0) {
return false; // recusado: nada muda
}
saldo_ += valor;
return true;
}
bool sacar(double valor) {
if (valor <= 0 || valor > saldo_) {
return false;
}
saldo_ -= valor;
return true;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(1000.0);
if (ana.sacar(250.0)) {
std::cout << "Saque de 250 feito.\n";
}
if (!ana.sacar(5000.0)) {
std::cout << "Saque de 5000 recusado.\n";
}
if (!ana.depositar(-50.0)) {
std::cout << "Depósito de -50 recusado.\n";
}
ana.sacar(800.0); // ninguém conferiu se deu certo...
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
Etapa 3 · A exceção SaldoInsuficiente
Uma exceção é um aviso de "deu errado" que não pode ser ignorado: quando um método faz throw (lança), o programa para o que estava fazendo e pula para o catch (captura) mais próximo. Se não houver nenhum, o programa termina.
Para o saque sem saldo, vamos criar a nossa própria exceção, SaldoInsuficiente. Para valor zero ou negativo, usamos uma que já vem pronta na biblioteca: std::invalid_argument ("argumento inválido").
#include <format>
#include <iostream>
#include <stdexcept>
#include <string>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
void depositar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(1000.0);
try {
ana.sacar(250.0);
std::cout << "Saque de 250 feito.\n";
ana.sacar(5000.0);
std::cout << "Esta linha não aparece.\n";
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
}
try {
ana.depositar(-50.0);
} catch (const std::invalid_argument& e) {
std::cout << "Erro: " << e.what() << '\n';
}
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
| Trecho | O que faz |
|---|---|
#include <stdexcept> | Traz as exceções prontas: std::runtime_error, std::invalid_argument, std::out_of_range… |
class SaldoInsuficiente : public std::runtime_error | A nossa exceção é um runtime_error (herança, que a etapa 4 explica). Com isso ela ganha de graça o método what(), que devolve a mensagem. |
: std::runtime_error{std::format(…)} | O construtor monta a mensagem com o saldo e o pedido e entrega ao runtime_error. O {:.2f} mostra o número com 2 casas (detalhes na etapa 6). |
throw std::invalid_argument{"…"}; | Lança a exceção. Nada depois dela, no método, é executado: o saldo não muda. |
throw SaldoInsuficiente{saldo_, valor}; | Lança a nossa, com os dois números. |
void sacar(…) | Voltou a ser void: não precisa mais responder true/false. Se voltou do método, deu certo. |
try { … } | "Tente": o trecho que pode lançar. |
catch (const SaldoInsuficiente& e) | Captura só esse tipo de exceção. O const & evita copiar o objeto da exceção; use sempre assim. |
e.what() | A mensagem montada no construtor. |
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Saque de 250 feito. Recusado: saldo insuficiente (saldo 750.00, pedido 5000.00) Erro: o valor precisa ser positivo Ana tem R$ 750
Repare que Esta linha não aparece. de fato não aparece: o throw do segundo saque pulou direto para o catch. E o saldo continua 750: nenhuma operação recusada mexeu nele.
Coloque ana.sacar(800.0); fora de qualquer try, logo antes da última linha do main:
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Saque de 250 feito. Recusado: saldo insuficiente (saldo 750.00, pedido 5000.00) Erro: o valor precisa ser positivo terminate called after throwing an instance of 'SaldoInsuficiente' what(): saldo insuficiente (saldo 750.00, pedido 800.00) Abortado (imagem do núcleo gravada) ./banco $ echo $? 134
terminate called after throwing an instance of 'SaldoInsuficiente': "terminar, chamado depois de lançar umSaldoInsuficiente". Como ninguém capturou, o C++ chamastd::terminate, que encerra o programa.what():mostra a nossa mensagem. Ainda bem que ela diz o saldo e o pedido.Abortado (imagem do núcleo gravada)é o Bash avisando que o programa foi abortado; o código de saída 134 é 128 + 6, o número do sinal de "abortar".- A frase final
Ana tem R$ 750nunca foi impressa. Diferente doboolesquecido, o erro não passou despercebido.
Como consertar: toda operação que pode lançar fica dentro de um try com o catch certo, como as outras do main.
-std=c++23 o std::format não existe
O g++ 15 usa C++17 se você não pedir outro padrão. Compilando com g++ -Wall -Wextra banco.cpp -o banco, a primeira mensagem é error: ‘format’ is not a member of ‘std’, seguida da dica note: ‘std::format’ is only available from C++20 onwards. Use sempre o comando padrão do guia.
Missão (etapa 3): o sacar já lança as exceções, mas o depositar aceita qualquer valor, e o depósito de −50 tirou dinheiro da Ana (ela ficou com 700). Faça o depositar lançar std::invalid_argument com a mensagem "o valor precisa ser positivo" quando o valor não for positivo. O main já tem o catch. A saída deve ser:
Saque de 250 feito. Recusado: saldo insuficiente (saldo 750.00, pedido 5000.00) Erro: o valor precisa ser positivo Ana tem R$ 750
#include <format>
#include <iostream>
#include <stdexcept>
#include <string>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
void depositar(double valor) {
// Missão: lance std::invalid_argument se o valor não for positivo.
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(1000.0);
try {
ana.sacar(250.0);
std::cout << "Saque de 250 feito.\n";
ana.sacar(5000.0);
std::cout << "Esta linha não aparece.\n";
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
}
try {
ana.depositar(-50.0);
} catch (const std::invalid_argument& e) {
std::cout << "Erro: " << e.what() << '\n';
}
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
#include <format>
#include <iostream>
#include <stdexcept>
#include <string>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
void depositar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
int main() {
Conta ana{"Ana"};
ana.depositar(1000.0);
try {
ana.sacar(250.0);
std::cout << "Saque de 250 feito.\n";
ana.sacar(5000.0);
std::cout << "Esta linha não aparece.\n";
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
}
try {
ana.depositar(-50.0);
} catch (const std::invalid_argument& e) {
std::cout << "Erro: " << e.what() << '\n';
}
std::cout << ana.titular() << " tem R$ " << ana.saldo() << '\n';
}
Etapa 4 · A conta poupança
A poupança é uma conta com tudo o que a conta comum tem (titular, saldo, depósito, saque) e uma diferença: no fim do mês, ela rende juros. Em vez de copiar a classe inteira, dizemos que ContaPoupanca herda de Conta (herança) e escrevemos só o que muda.
Para o "fim do mês" funcionar com qualquer conta, a classe base ganha dois métodos virtual: tipo() (o nome do tipo de conta) e fecharMes(). virtual quer dizer "uma classe filha pode trocar este método pelo dela, e o C++ vai chamar o da filha mesmo quando só souber que tem uma Conta nas mãos". Isso é o polimorfismo.
Os #include, a SaldoInsuficiente, o depositar e o sacar continuam iguais à etapa 3. O resto do banco.cpp fica assim (o arquivo inteiro está na missão desta etapa):
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
// depositar, sacar, titular() e saldo(): iguais à etapa 3
private:
std::string titular_;
double saldo_ = 0.0;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento);
}
}
private:
double taxa_;
};
void mostrar(const Conta& conta) {
std::cout << conta.titular() << " (" << conta.tipo() << "): R$ "
<< conta.saldo() << '\n';
}
int main() {
Conta ana{"Ana"};
ContaPoupanca bruno{"Bruno", 0.005}; // rende 0,5% ao mês
ana.depositar(1000.0);
bruno.depositar(1000.0); // depositar veio de Conta
mostrar(ana);
mostrar(bruno);
ana.fecharMes();
bruno.fecharMes();
std::cout << "--- fim do mês ---\n";
mostrar(ana);
mostrar(bruno);
}
| Trecho | O que faz |
|---|---|
virtual ~Conta() = default; | Destrutor virtual. Regra: classe com métodos virtual tem destrutor virtual. Sem ele, apagar uma poupança por um ponteiro de Conta (etapa 5) não chamaria a parte da poupança. = default = "o padrão serve". |
virtual std::string tipo() const { return "Corrente"; } | A versão da conta comum. As filhas podem trocar. |
virtual void fecharMes() {} | A conta comum não faz nada no fim do mês: corpo vazio. |
class ContaPoupanca : public Conta | "Poupança é uma Conta". Herda tudo; o private da Conta continua fechado até para a filha, que mexe no saldo só pelos métodos públicos. |
: Conta{titular}, taxa_{taxa} | O construtor da filha chama primeiro o da mãe (que guarda o titular) e depois guarda a taxa. |
std::string tipo() const override | override ("sobrescreve") diz ao compilador: "esta função substitui uma virtual da mãe". Se não substituir nada (nome errado, const a mais…), é erro de compilação. |
double rendimento = saldo() * taxa_; | Com taxa 0.005 (0,5%), mil reais rendem 5. |
depositar(rendimento); | O rendimento entra pelo depositar herdado: a poupança não precisa (nem consegue) mexer em saldo_ direto. |
void mostrar(const Conta& conta) | Uma função que recebe qualquer conta por referência. Graças ao virtual, conta.tipo() chama a versão certa. |
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Ana (Corrente): R$ 1000 Bruno (Poupança): R$ 1000 --- fim do mês --- Ana (Corrente): R$ 1000 Bruno (Poupança): R$ 1005
As duas contas receberam o mesmo depósito e o mesmo fecharMes(), mas só a do Bruno rendeu. E a função mostrar, que só conhece Conta, escreveu "Poupança" para o Bruno.
virtual
Tire as palavras virtual e override de tipo() e fecharMes(). Compila sem nenhum aviso, mas veja:
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Ana (Corrente): R$ 1000 Bruno (Corrente): R$ 1000 --- fim do mês --- Ana (Corrente): R$ 1000 Bruno (Corrente): R$ 1005
O Bruno virou "Corrente" na tela. Sem virtual, a função mostrar recebe uma Conta& e chama o tipo() da Conta, sem olhar que o objeto é uma poupança. O rendimento ainda entrou só porque o main chamou bruno.fecharMes() direto numa variável do tipo ContaPoupanca; numa lista de contas (etapa 5) ele também se perderia. Como consertar: virtual na mãe e override na filha, sempre os dois.
override que não sobrescreve nada
Um erro de digitação, fecharmes com m minúsculo:
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco
banco.cpp:53:10: error: ‘void ContaPoupanca::fecharmes()’ marked ‘override’, but does not override
53 | void fecharmes() override {
| ^~~~~~~~~
marked ‘override’, but does not override: "marcado como override, mas não sobrescreve". Não existe fecharmes virtual na Conta. Sem o override, esse erro passaria em silêncio: a poupança ganharia um método novo que ninguém chama, e nunca renderia. É por isso que o override vale a pena.
Missão (etapa 4): crie a conta que cobra tarifa. A classe ContaTarifada já herda de Conta; escreva nela tipo(), devolvendo "Tarifada", e fecharMes(), que saca a tarifa de 12.90 (use o sacar herdado). Não esqueça o override. A saída deve ser:
Carla (Tarifada): R$ 87.1
#include <format>
#include <iostream>
#include <stdexcept>
#include <string>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento);
}
}
private:
double taxa_;
};
class ContaTarifada : public Conta {
public:
explicit ContaTarifada(std::string titular) : Conta{titular} {}
// Missão: tipo() devolve "Tarifada" e fecharMes() saca a tarifa de 12.90.
};
void mostrar(const Conta& conta) {
std::cout << conta.titular() << " (" << conta.tipo() << "): R$ "
<< conta.saldo() << '\n';
}
int main() {
ContaTarifada carla{"Carla"};
carla.depositar(100.0);
carla.fecharMes();
mostrar(carla);
}
#include <format>
#include <iostream>
#include <stdexcept>
#include <string>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento);
}
}
private:
double taxa_;
};
class ContaTarifada : public Conta {
public:
explicit ContaTarifada(std::string titular) : Conta{titular} {}
std::string tipo() const override { return "Tarifada"; }
void fecharMes() override {
sacar(12.90);
}
};
void mostrar(const Conta& conta) {
std::cout << conta.titular() << " (" << conta.tipo() << "): R$ "
<< conta.saldo() << '\n';
}
int main() {
ContaTarifada carla{"Carla"};
carla.depositar(100.0);
carla.fecharMes();
mostrar(carla);
}
Etapa 5 · Uma lista de contas
Um banco tem muitas contas, de tipos misturados. O natural seria um std::vector<Conta>. Veja o que acontece:
int main() {
std::vector<Conta> contas;
contas.push_back(Conta{"Ana"});
contas.push_back(ContaPoupanca{"Bruno", 0.005});
for (auto& conta : contas) {
conta.depositar(1000.0);
conta.fecharMes();
std::cout << conta.titular() << " (" << conta.tipo() << "): R$ "
<< conta.saldo() << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Ana (Corrente): R$ 1000 Bruno (Corrente): R$ 1000
Compilou sem aviso e o Bruno virou "Corrente" e não rendeu. Cada lugar de um vector<Conta> tem o tamanho exato de uma Conta. Ao guardar a poupança ali, o C++ copia só a parte Conta e joga fora o resto (a taxa e o "jeito poupança" de ser). Isso tem nome: fatiamento (slicing).
A solução é guardar no vetor não as contas, mas ponteiros para elas: todo ponteiro tem o mesmo tamanho, aponte ele para uma conta comum ou para uma poupança. E, para não ter de lembrar de apagar cada conta com delete, usamos o ponteiro inteligente std::unique_ptr (ponteiros inteligentes): quando o vetor some, ele apaga as contas sozinho. As classes ficam iguais à etapa 4; acrescente #include <memory> e #include <vector> no topo e troque o main:
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas.push_back(std::make_unique<ContaPoupanca>("Carla", 0.01));
for (const auto& conta : contas) {
conta->depositar(1000.0);
}
for (const auto& conta : contas) {
conta->fecharMes(); // cada uma do seu jeito
}
for (const auto& conta : contas) {
std::cout << conta->titular() << " (" << conta->tipo() << "): R$ "
<< conta->saldo() << '\n';
}
}
| Trecho | O que faz |
|---|---|
std::vector<std::unique_ptr<Conta>> | "Um vetor de ponteiros únicos para Conta". Cada ponteiro é o único dono da sua conta. |
std::make_unique<Conta>("Ana") | Cria uma Conta na memória dinâmica e devolve o unique_ptr para ela. Os parênteses levam os argumentos do construtor. |
std::make_unique<ContaPoupanca>("Bruno", 0.005) | Cria uma poupança. O ponteiro para ContaPoupanca vira, sozinho, ponteiro para Conta, porque toda poupança é uma conta. |
conta->depositar(1000.0) | Com ponteiro, usa-se -> em vez de .. |
const auto& conta : contas | Percorre o vetor sem copiar os ponteiros (um unique_ptr não pode ser copiado; veja abaixo). |
conta->fecharMes(); | Polimorfismo de verdade: cada conta faz o seu próprio fechamento. |
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Ana (Corrente): R$ 1000 Bruno (Poupança): R$ 1005 Carla (Poupança): R$ 1010
Agora cada conta se comporta como o que ela é: Bruno rende 0,5%, Carla rende 1%, Ana não rende. E nenhum delete no programa: quando o main termina, o vetor é destruído, cada unique_ptr apaga a sua conta, e o destrutor virtual da etapa 4 garante que a poupança seja apagada inteira.
unique_ptr
No segundo laço, troque const auto& conta por auto conta (sem o &):
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco
banco.cpp: In function ‘int main()’:
banco.cpp:75:23: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Conta; _Dp = std::default_delete<Conta>]’
75 | for (auto conta : contas) {
| ^~~~~~
In file included from /usr/include/c++/15/memory:80,
from banco.cpp:3:
/usr/include/c++/15/bits/unique_ptr.h:524:7: note: declared here
524 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
banco.cpp:75:23: note: use ‘-fdiagnostics-all-candidates’ to display considered candidates
75 | for (auto conta : contas) {
| ^~~~~~
A mensagem é comprida, mas o começo diz tudo: use of deleted function ‘std::unique_ptr<…>::unique_ptr(const std::unique_ptr<…>&)’, "uso de função apagada: o construtor de cópia do unique_ptr". O auto conta tenta fazer uma cópia de cada ponteiro, e o unique_ptr proíbe cópias (= delete, como mostra a note do arquivo unique_ptr.h): se houvesse duas cópias, as duas tentariam apagar a mesma conta. Como consertar: percorra com auto& ou const auto&.
Missão (etapa 5): abra uma quarta conta, uma poupança do "Diego" com taxa 0.02. Depois do último laço, some o saldo de todas as contas numa variável total (com mais um for) e mostre o total. A saída deve terminar com:
Diego (Poupança): R$ 1020 Total no banco: R$ 4035
#include <format>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento);
}
}
private:
double taxa_;
};
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas.push_back(std::make_unique<ContaPoupanca>("Carla", 0.01));
for (const auto& conta : contas) {
conta->depositar(1000.0);
}
for (const auto& conta : contas) {
conta->fecharMes(); // cada uma do seu jeito
}
for (const auto& conta : contas) {
std::cout << conta->titular() << " (" << conta->tipo() << "): R$ "
<< conta->saldo() << '\n';
}
}
#include <format>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento);
}
}
private:
double taxa_;
};
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas.push_back(std::make_unique<ContaPoupanca>("Carla", 0.01));
contas.push_back(std::make_unique<ContaPoupanca>("Diego", 0.02));
for (const auto& conta : contas) {
conta->depositar(1000.0);
}
for (const auto& conta : contas) {
conta->fecharMes(); // cada uma do seu jeito
}
for (const auto& conta : contas) {
std::cout << conta->titular() << " (" << conta->tipo() << "): R$ "
<< conta->saldo() << '\n';
}
double total = 0.0;
for (const auto& conta : contas) {
total += conta->saldo();
}
std::cout << "Total no banco: R$ " << total << '\n';
}
Etapa 6 · O extrato com std::format
Um banco precisa lembrar cada movimento, não só o saldo. Cada movimento tem uma descrição ("Depósito", "Saque", "Rendimento") e um valor: positivo quando entra, negativo quando sai. Isso é uma struct, e a conta guarda um std::vector delas. Para mostrar o extrato em colunas alinhadas, usamos std::format.
#include <format>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
struct Movimento {
std::string descricao;
double valor; // positivo entra, negativo sai
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor, std::string descricao = "Depósito") {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
private:
double taxa_;
};
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas[0]->depositar(1000.0);
contas[0]->sacar(250.0);
contas[0]->depositar(49.9, "Pix da Carla");
contas[1]->depositar(1000.0);
for (const auto& conta : contas) {
conta->fecharMes();
std::cout << conta->extrato() << '\n';
}
}
O que mudou em relação à etapa 5:
| Trecho | O que faz |
|---|---|
struct Movimento { … }; | Um movimento: descrição + valor. |
std::vector<Movimento> movimentos_; | O histórico, private como o saldo. |
std::string descricao = "Depósito" | Um valor padrão: depositar(1000.0) registra "Depósito"; depositar(49.9, "Pix da Carla") registra outra descrição. A poupança usa isso para registrar "Rendimento". |
movimentos_.push_back({descricao, valor}); | Acrescenta um Movimento no fim. As chaves montam a struct na hora. |
movimentos_.push_back({"Saque", -valor}); | No saque, o valor vai negativo. |
std::string extrato() const | Monta o extrato inteiro num texto e devolve. Quem chama decide onde mostrar (tela, arquivo…). |
texto += std::format(…) | Cada linha é formatada e grudada no fim do texto. |
contas[0]-> | A primeira conta do vetor (a da Ana). |
As "máscaras" dentro das chaves dizem como cada valor aparece:
| Máscara | Significado | Exemplo de resultado |
|---|---|---|
{} | O valor do jeito padrão. | Ana |
{:<12} | Ocupa 12 colunas, alinhado à esquerda (<), completando com espaços. | Saque |
{:>10.2f} | 10 colunas, alinhado à direita (>), número com 2 casas decimais (.2f). | 799.90 |
{:>+10.2f} | O mesmo, mas sempre com sinal (+), também nos positivos. | +1000.00 |
$ g++ -std=c++23 -Wall -Wextra banco.cpp -o banco $ ./banco Extrato de Ana (Corrente) Depósito +1000.00 Saque -250.00 Pix da Carla +49.90 Saldo 799.90 Extrato de Bruno (Poupança) Depósito +1000.00 Rendimento +5.00 Saldo 1005.00
Os números ficaram alinhados à direita, com duas casas e sinal; e o rendimento da poupança apareceu como um movimento. Repare em "Depósito": tem acento, ocupa 9 bytes em UTF-8 mas 8 colunas na tela, e o std::format do C++23 alinha pelas colunas, por isso a coluna saiu certa (veja Acentos e UTF-8). Com std::setw, do iomanip, que conta bytes, essa linha ficaria um espaço mais curta.
Missão (etapa 6): no fim do main, em vez dos extratos, mostre um resumo com uma linha por conta, usando std::format("{:<6}{:<10}{:>8.2f}\n", …) com o titular, o tipo e o saldo. A saída deve ser:
Ana Corrente 799.90 Bruno Poupança 1005.00
#include <format>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
struct Movimento {
std::string descricao;
double valor; // positivo entra, negativo sai
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor, std::string descricao = "Depósito") {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
private:
double taxa_;
};
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas[0]->depositar(1000.0);
contas[0]->sacar(250.0);
contas[0]->depositar(49.9, "Pix da Carla");
contas[1]->depositar(1000.0);
for (const auto& conta : contas) {
conta->fecharMes();
}
// Missão: uma linha por conta com
// std::format("{:<6}{:<10}{:>8.2f}\n", titular, tipo, saldo)
}
#include <format>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})",
saldo, pedido)} {}
};
struct Movimento {
std::string descricao;
double valor; // positivo entra, negativo sai
};
class Conta {
public:
explicit Conta(std::string titular) : titular_{titular} {}
virtual ~Conta() = default;
virtual std::string tipo() const { return "Corrente"; }
virtual void fecharMes() {} // a conta corrente não rende
void depositar(double valor, std::string descricao = "Depósito") {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
std::string titular() const { return titular_; }
double saldo() const { return saldo_; }
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string tipo() const override { return "Poupança"; }
void fecharMes() override {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
private:
double taxa_;
};
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas[0]->depositar(1000.0);
contas[0]->sacar(250.0);
contas[0]->depositar(49.9, "Pix da Carla");
contas[1]->depositar(1000.0);
for (const auto& conta : contas) {
conta->fecharMes();
}
for (const auto& conta : contas) {
std::cout << std::format("{:<6}{:<10}{:>8.2f}\n",
conta->titular(), conta->tipo(), conta->saldo());
}
}
Etapa 7 · Separar em conta.h, conta.cpp e main.cpp
O banco.cpp já passou de 90 linhas, e tudo nele está misturado: as classes do banco e o programa que as usa. Programas de verdade separam (veja Arquivos .h e .cpp):
| Arquivo | O que tem | Comparação |
|---|---|---|
conta.h | As declarações: os nomes das classes, dos métodos e dos dados. O "o quê". | O cardápio. |
conta.cpp | As definições: o corpo de cada método. O "como". | A cozinha. |
main.cpp | O programa, que inclui conta.h e usa as contas. | O cliente, que só lê o cardápio. |
#pragma once
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido);
};
struct Movimento {
std::string descricao;
double valor;
};
class Conta {
public:
explicit Conta(std::string titular);
virtual ~Conta() = default;
void depositar(double valor, std::string descricao = "Depósito");
void sacar(double valor);
virtual std::string tipo() const;
virtual void fecharMes();
std::string titular() const;
double saldo() const;
std::string extrato() const;
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa);
std::string tipo() const override;
void fecharMes() override;
private:
double taxa_;
};
#include "conta.h"
#include <format>
SaldoInsuficiente::SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})", saldo, pedido)} {}
Conta::Conta(std::string titular) : titular_{titular} {}
void Conta::depositar(double valor, std::string descricao) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void Conta::sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string Conta::tipo() const { return "Corrente"; }
void Conta::fecharMes() {} // a conta corrente não rende
std::string Conta::titular() const { return titular_; }
double Conta::saldo() const { return saldo_; }
std::string Conta::extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
ContaPoupanca::ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string ContaPoupanca::tipo() const { return "Poupança"; }
void ContaPoupanca::fecharMes() {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
#include <iostream>
#include <memory>
#include <vector>
#include "conta.h"
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas[0]->depositar(1000.0);
contas[0]->sacar(250.0);
contas[0]->depositar(49.9, "Pix da Carla");
contas[1]->depositar(1000.0);
for (const auto& conta : contas) {
conta->fecharMes();
std::cout << conta->extrato() << '\n';
}
}
| Trecho | O que faz |
|---|---|
#pragma once | "Inclua este arquivo uma vez só", mesmo que dois arquivos façam #include "conta.h". Sem isso, as classes seriam declaradas duas vezes, e isso é erro (veja o pré-processador). |
void depositar(double valor, std::string descricao = "Depósito"); | No .h, só a "assinatura", terminando em ;. O valor padrão fica só aqui, não se repete no .cpp. |
#include "conta.h" | Com aspas: um arquivo do projeto, procurado primeiro na pasta atual. Com < >: um da biblioteca padrão. |
void Conta::depositar(…) { … } | No .cpp, o nome completo: Conta:: diz "o depositar da classe Conta". |
override, virtual, explicit | Aparecem só no .h. No .cpp não se repetem. |
#include <format> no conta.cpp | Só quem usa o std::format precisa dele. O main.cpp nem sabe que ele existe. |
Para compilar, passe os dois arquivos .cpp para o g++ (o .h não entra no comando: ele é copiado para dentro dos .cpp pelo #include):
$ ls conta.cpp conta.h main.cpp $ g++ -std=c++23 -Wall -Wextra main.cpp conta.cpp -o banco $ ./banco Extrato de Ana (Corrente) Depósito +1000.00 Saque -250.00 Pix da Carla +49.90 Saldo 799.90 Extrato de Bruno (Poupança) Depósito +1000.00 Rendimento +5.00 Saldo 1005.00
A mesma saída da etapa 6: o programa é o mesmo, só mudou de organização. Veja os detalhes do comando em Compilar Vários Arquivos.
$ g++ -std=c++23 -Wall -Wextra main.cpp -o banco /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccqdte6y.o: na função "main": main.cpp:(.text+0x147): referência não definida para "Conta::depositar(double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)" /usr/bin/x86_64-linux-gnu-ld.bfd: main.cpp:(.text+0x184): referência não definida para "Conta::sacar(double)" /usr/bin/x86_64-linux-gnu-ld.bfd: main.cpp:(.text+0x1e5): referência não definida para "Conta::depositar(double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)" /usr/bin/x86_64-linux-gnu-ld.bfd: main.cpp:(.text+0x253): referência não definida para "Conta::depositar(double, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)" /usr/bin/x86_64-linux-gnu-ld.bfd: main.cpp:(.text+0x2e9): referência não definida para "Conta::extrato[abi:cxx11]() const" /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccqdte6y.o: na função "std::__detail::_MakeUniq<Conta>::__single_object std::make_unique<Conta, char const (&) [4]>(char const (&) [4])": main.cpp:(.text._ZSt11make_uniqueI5ContaJRA4_KcEENSt8__detail9_MakeUniqIT_E15__single_objectEDpOT0_[_ZSt11make_uniqueI5ContaJRA4_KcEENSt8__detail9_MakeUniqIT_E15__single_objectEDpOT0_]+0x6f): referência não definida para "Conta::Conta(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)" /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccqdte6y.o: na função "std::__detail::_MakeUniq<ContaPoupanca>::__single_object std::make_unique<ContaPoupanca, char const (&) [6], double>(char const (&) [6], double&&)": main.cpp:(.text._ZSt11make_uniqueI13ContaPoupancaJRA6_KcdEENSt8__detail9_MakeUniqIT_E15__single_objectEDpOT0_[_ZSt11make_uniqueI13ContaPoupancaJRA6_KcdEENSt8__detail9_MakeUniqIT_E15__single_objectEDpOT0_]+0x8d): referência não definida para "ContaPoupanca::ContaPoupanca(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, double)" collect2: error: ld returned 1 exit status
Este erro não é do compilador, é do ligador (linker, o ld), a etapa que junta os pedaços no fim (por isso essas linhas saem em português: o ld do Ubuntu tem tradução, o g++ não). O main.cpp compilou sem problemas, porque o conta.h prometeu que Conta::depositar existe. Na hora de juntar, o ligador procurou o corpo do método e não achou: referência não definida para "Conta::depositar(…)" ("undefined reference", na versão em inglês). O std::__cxx11::basic_string<…> é o nome completo de std::string. O nome do arquivo em /tmp muda a cada vez. Como consertar: ponha conta.cpp no comando. Quando o projeto tiver muitos arquivos, quem lembra disso por você é o Make ou o CMake.
Missão (etapa 7): o main.cpp já chama contas[0]->transferir(*contas[1], 100.0), mas o método não existe, e a compilação falha. Crie-o: declare void transferir(Conta& destino, double valor); no conta.h e escreva o corpo no conta.cpp (saque da própria conta e depósito no destino com a descrição "Transferência"). O *contas[1] é a conta do Bruno: o * pega a conta para a qual o ponteiro aponta. Na saída devem aparecer:
Saldo 699.90 Transferência +100.00
#include <iostream>
#include <memory>
#include <vector>
#include "conta.h"
int main() {
std::vector<std::unique_ptr<Conta>> contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
contas[0]->depositar(1000.0);
contas[0]->sacar(250.0);
contas[0]->depositar(49.9, "Pix da Carla");
contas[1]->depositar(1000.0);
contas[0]->transferir(*contas[1], 100.0); // Ana manda 100 para o Bruno
for (const auto& conta : contas) {
std::cout << conta->extrato() << '\n';
}
}
#pragma once
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido);
};
struct Movimento {
std::string descricao;
double valor;
};
class Conta {
public:
explicit Conta(std::string titular);
virtual ~Conta() = default;
void depositar(double valor, std::string descricao = "Depósito");
void sacar(double valor);
virtual std::string tipo() const;
virtual void fecharMes();
std::string titular() const;
double saldo() const;
std::string extrato() const;
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa);
std::string tipo() const override;
void fecharMes() override;
private:
double taxa_;
};
#include "conta.h"
#include <format>
SaldoInsuficiente::SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})", saldo, pedido)} {}
Conta::Conta(std::string titular) : titular_{titular} {}
void Conta::depositar(double valor, std::string descricao) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void Conta::sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string Conta::tipo() const { return "Corrente"; }
void Conta::fecharMes() {} // a conta corrente não rende
std::string Conta::titular() const { return titular_; }
double Conta::saldo() const { return saldo_; }
std::string Conta::extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
ContaPoupanca::ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string ContaPoupanca::tipo() const { return "Poupança"; }
void ContaPoupanca::fecharMes() {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
#pragma once
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido);
};
struct Movimento {
std::string descricao;
double valor;
};
class Conta {
public:
explicit Conta(std::string titular);
virtual ~Conta() = default;
void depositar(double valor, std::string descricao = "Depósito");
void sacar(double valor);
void transferir(Conta& destino, double valor);
virtual std::string tipo() const;
virtual void fecharMes();
std::string titular() const;
double saldo() const;
std::string extrato() const;
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa);
std::string tipo() const override;
void fecharMes() override;
private:
double taxa_;
};
#include "conta.h"
#include <format>
SaldoInsuficiente::SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})", saldo, pedido)} {}
Conta::Conta(std::string titular) : titular_{titular} {}
void Conta::depositar(double valor, std::string descricao) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void Conta::sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
void Conta::transferir(Conta& destino, double valor) {
sacar(valor); // se faltar saldo, para aqui
destino.depositar(valor, "Transferência");
}
std::string Conta::tipo() const { return "Corrente"; }
void Conta::fecharMes() {} // a conta corrente não rende
std::string Conta::titular() const { return titular_; }
double Conta::saldo() const { return saldo_; }
std::string Conta::extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
ContaPoupanca::ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string ContaPoupanca::tipo() const { return "Poupança"; }
void ContaPoupanca::fecharMes() {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
Etapa 8 · O menu
Até aqui, as operações estavam escritas no main. Agora quem decide é o usuário, digitando comandos. O conta.h e o conta.cpp não mudam nada: essa é a vantagem de ter separado. Só o main.cpp é novo:
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "conta.h"
using Contas = std::vector<std::unique_ptr<Conta>>;
Conta& escolher(Contas& contas, int numero) {
if (numero < 1 || numero > static_cast<int>(contas.size())) {
throw std::out_of_range{"não existe a conta " + std::to_string(numero)};
}
return *contas[numero - 1];
}
int main() {
Contas contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
std::cout << "Banco da Ana. Comandos: l, d N VALOR, s N VALOR, e N, m, q\n";
std::string linha;
while (std::cout << "banco> " && std::getline(std::cin, linha)) {
std::istringstream pedaco{linha};
char comando = ' ';
int numero = 0;
double valor = 0.0;
pedaco >> comando >> numero >> valor;
try {
if (comando == 'q') {
break;
} else if (comando == 'l') {
for (std::size_t i = 0; i < contas.size(); ++i) {
std::cout << i + 1 << ". " << contas[i]->titular() << " ("
<< contas[i]->tipo() << ")\n";
}
} else if (comando == 'd') {
escolher(contas, numero).depositar(valor);
std::cout << "Depósito feito.\n";
} else if (comando == 's') {
escolher(contas, numero).sacar(valor);
std::cout << "Saque feito.\n";
} else if (comando == 'e') {
std::cout << escolher(contas, numero).extrato();
} else if (comando == 'm') {
for (auto& conta : contas) {
conta->fecharMes();
}
std::cout << "Mês fechado.\n";
} else {
std::cout << "Comando desconhecido: " << linha << '\n';
}
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
} catch (const std::exception& e) {
std::cout << "Erro: " << e.what() << '\n';
}
}
std::cout << "\nAté logo!\n";
}
| Trecho | O que faz |
|---|---|
using Contas = std::vector<std::unique_ptr<Conta>>; | Um apelido para o tipo comprido (using). |
Conta& escolher(Contas& contas, int numero) | Recebe o número que o usuário digitou (que começa em 1) e devolve a conta (que no vetor começa em 0). Número fora da lista: lança std::out_of_range. |
static_cast<int>(contas.size()) | size() devolve um número sem sinal; converter para int evita comparar um número com sinal e outro sem sinal, que o -Wall avisa: warning: comparison of integer expressions of different signedness. Veja Conversão de Tipos. |
while (std::cout << "banco> " && std::getline(std::cin, linha)) | Mostra o convite e lê uma linha inteira. O laço para quando a entrada acaba (getline devolve "falso"). |
std::istringstream pedaco{linha}; | Transforma a linha num "fluxo de entrada", como um cin particular. Daí, pedaco >> comando >> numero >> valor separa d 1 1000 em 'd', 1 e 1000.0. O que faltar fica com o valor inicial (0). |
char comando = ' '; | Inicializado: numa linha vazia, cai no "Comando desconhecido". |
catch (const SaldoInsuficiente& e) antes de catch (const std::exception& e) | O mais específico vem primeiro. std::exception é a mãe de todas as exceções da biblioteca (e da nossa), e captura o resto: invalid_argument, out_of_range. |
$ g++ -std=c++23 -Wall -Wextra main.cpp conta.cpp -o banco $ ./banco Banco da Ana. Comandos: l, d N VALOR, s N VALOR, e N, m, q banco> l 1. Ana (Corrente) 2. Bruno (Poupança) banco> d 1 1000 Depósito feito. banco> d 2 1000 Depósito feito. banco> s 1 250 Saque feito. banco> s 2 5000 Recusado: saldo insuficiente (saldo 1000.00, pedido 5000.00) banco> d 3 10 Erro: não existe a conta 3 banco> d 1 -5 Erro: o valor precisa ser positivo banco> m Mês fechado. banco> e 1 Extrato de Ana (Corrente) Depósito +1000.00 Saque -250.00 Saldo 750.00 banco> e 2 Extrato de Bruno (Poupança) Depósito +1000.00 Rendimento +5.00 Saldo 1005.00 banco> q Até logo!
Repare que nenhum erro derruba o programa: conta inexistente, valor negativo e saldo insuficiente viram mensagens, e o menu continua. Esse é o papel do try/catch dentro do laço. Para mais formas de tratar o que o usuário digita, veja Validar a Entrada.
Missão (etapa 8): acrescente ao menu o comando v N, que mostra só o saldo da conta N, com duas casas: Saldo de Ana: R$ 750.00. Use escolher e std::format (lembre do #include <format>). A caixa já digita para você: d 1 1000, s 1 250, v 1 e q.
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "conta.h"
using Contas = std::vector<std::unique_ptr<Conta>>;
Conta& escolher(Contas& contas, int numero) {
if (numero < 1 || numero > static_cast<int>(contas.size())) {
throw std::out_of_range{"não existe a conta " + std::to_string(numero)};
}
return *contas[numero - 1];
}
int main() {
Contas contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
std::cout << "Banco da Ana. Comandos: l, d N VALOR, s N VALOR, e N, m, q\n";
std::string linha;
while (std::cout << "banco> " && std::getline(std::cin, linha)) {
std::istringstream pedaco{linha};
char comando = ' ';
int numero = 0;
double valor = 0.0;
pedaco >> comando >> numero >> valor;
try {
if (comando == 'q') {
break;
} else if (comando == 'l') {
for (std::size_t i = 0; i < contas.size(); ++i) {
std::cout << i + 1 << ". " << contas[i]->titular() << " ("
<< contas[i]->tipo() << ")\n";
}
} else if (comando == 'd') {
escolher(contas, numero).depositar(valor);
std::cout << "Depósito feito.\n";
} else if (comando == 's') {
escolher(contas, numero).sacar(valor);
std::cout << "Saque feito.\n";
} else if (comando == 'e') {
std::cout << escolher(contas, numero).extrato();
} else if (comando == 'm') {
for (auto& conta : contas) {
conta->fecharMes();
}
std::cout << "Mês fechado.\n";
} else {
std::cout << "Comando desconhecido: " << linha << '\n';
}
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
} catch (const std::exception& e) {
std::cout << "Erro: " << e.what() << '\n';
}
}
std::cout << "\nAté logo!\n";
}
#pragma once
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido);
};
struct Movimento {
std::string descricao;
double valor;
};
class Conta {
public:
explicit Conta(std::string titular);
virtual ~Conta() = default;
void depositar(double valor, std::string descricao = "Depósito");
void sacar(double valor);
virtual std::string tipo() const;
virtual void fecharMes();
std::string titular() const;
double saldo() const;
std::string extrato() const;
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa);
std::string tipo() const override;
void fecharMes() override;
private:
double taxa_;
};
#include "conta.h"
#include <format>
SaldoInsuficiente::SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})", saldo, pedido)} {}
Conta::Conta(std::string titular) : titular_{titular} {}
void Conta::depositar(double valor, std::string descricao) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void Conta::sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string Conta::tipo() const { return "Corrente"; }
void Conta::fecharMes() {} // a conta corrente não rende
std::string Conta::titular() const { return titular_; }
double Conta::saldo() const { return saldo_; }
std::string Conta::extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
ContaPoupanca::ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string ContaPoupanca::tipo() const { return "Poupança"; }
void ContaPoupanca::fecharMes() {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
#include <format>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "conta.h"
using Contas = std::vector<std::unique_ptr<Conta>>;
Conta& escolher(Contas& contas, int numero) {
if (numero < 1 || numero > static_cast<int>(contas.size())) {
throw std::out_of_range{"não existe a conta " + std::to_string(numero)};
}
return *contas[numero - 1];
}
int main() {
Contas contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
std::cout << "Banco da Ana. Comandos: l, d N VALOR, s N VALOR, e N, m, q\n";
std::string linha;
while (std::cout << "banco> " && std::getline(std::cin, linha)) {
std::istringstream pedaco{linha};
char comando = ' ';
int numero = 0;
double valor = 0.0;
pedaco >> comando >> numero >> valor;
try {
if (comando == 'q') {
break;
} else if (comando == 'l') {
for (std::size_t i = 0; i < contas.size(); ++i) {
std::cout << i + 1 << ". " << contas[i]->titular() << " ("
<< contas[i]->tipo() << ")\n";
}
} else if (comando == 'd') {
escolher(contas, numero).depositar(valor);
std::cout << "Depósito feito.\n";
} else if (comando == 's') {
escolher(contas, numero).sacar(valor);
std::cout << "Saque feito.\n";
} else if (comando == 'v') {
Conta& conta = escolher(contas, numero);
std::cout << std::format("Saldo de {}: R$ {:.2f}\n", conta.titular(), conta.saldo());
} else if (comando == 'e') {
std::cout << escolher(contas, numero).extrato();
} else if (comando == 'm') {
for (auto& conta : contas) {
conta->fecharMes();
}
std::cout << "Mês fechado.\n";
} else {
std::cout << "Comando desconhecido: " << linha << '\n';
}
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
} catch (const std::exception& e) {
std::cout << "Erro: " << e.what() << '\n';
}
}
std::cout << "\nAté logo!\n";
}
O código completo
Os três arquivos finais, prontos para copiar. Compile com g++ -std=c++23 -Wall -Wextra main.cpp conta.cpp -o banco.
#pragma once
#include <stdexcept>
#include <string>
#include <vector>
class SaldoInsuficiente : public std::runtime_error {
public:
SaldoInsuficiente(double saldo, double pedido);
};
struct Movimento {
std::string descricao;
double valor;
};
class Conta {
public:
explicit Conta(std::string titular);
virtual ~Conta() = default;
void depositar(double valor, std::string descricao = "Depósito");
void sacar(double valor);
virtual std::string tipo() const;
virtual void fecharMes();
std::string titular() const;
double saldo() const;
std::string extrato() const;
private:
std::string titular_;
double saldo_ = 0.0;
std::vector<Movimento> movimentos_;
};
class ContaPoupanca : public Conta {
public:
ContaPoupanca(std::string titular, double taxa);
std::string tipo() const override;
void fecharMes() override;
private:
double taxa_;
};
#include "conta.h"
#include <format>
SaldoInsuficiente::SaldoInsuficiente(double saldo, double pedido)
: std::runtime_error{std::format("saldo insuficiente (saldo {:.2f}, pedido {:.2f})", saldo, pedido)} {}
Conta::Conta(std::string titular) : titular_{titular} {}
void Conta::depositar(double valor, std::string descricao) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
saldo_ += valor;
movimentos_.push_back({descricao, valor});
}
void Conta::sacar(double valor) {
if (valor <= 0) {
throw std::invalid_argument{"o valor precisa ser positivo"};
}
if (valor > saldo_) {
throw SaldoInsuficiente{saldo_, valor};
}
saldo_ -= valor;
movimentos_.push_back({"Saque", -valor});
}
std::string Conta::tipo() const { return "Corrente"; }
void Conta::fecharMes() {} // a conta corrente não rende
std::string Conta::titular() const { return titular_; }
double Conta::saldo() const { return saldo_; }
std::string Conta::extrato() const {
std::string texto = std::format("Extrato de {} ({})\n", titular_, tipo());
for (const auto& m : movimentos_) {
texto += std::format(" {:<12}{:>+10.2f}\n", m.descricao, m.valor);
}
texto += std::format(" {:<12}{:>10.2f}\n", "Saldo", saldo_);
return texto;
}
ContaPoupanca::ContaPoupanca(std::string titular, double taxa)
: Conta{titular}, taxa_{taxa} {}
std::string ContaPoupanca::tipo() const { return "Poupança"; }
void ContaPoupanca::fecharMes() {
double rendimento = saldo() * taxa_;
if (rendimento > 0) {
depositar(rendimento, "Rendimento");
}
}
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "conta.h"
using Contas = std::vector<std::unique_ptr<Conta>>;
Conta& escolher(Contas& contas, int numero) {
if (numero < 1 || numero > static_cast<int>(contas.size())) {
throw std::out_of_range{"não existe a conta " + std::to_string(numero)};
}
return *contas[numero - 1];
}
int main() {
Contas contas;
contas.push_back(std::make_unique<Conta>("Ana"));
contas.push_back(std::make_unique<ContaPoupanca>("Bruno", 0.005));
std::cout << "Banco da Ana. Comandos: l, d N VALOR, s N VALOR, e N, m, q\n";
std::string linha;
while (std::cout << "banco> " && std::getline(std::cin, linha)) {
std::istringstream pedaco{linha};
char comando = ' ';
int numero = 0;
double valor = 0.0;
pedaco >> comando >> numero >> valor;
try {
if (comando == 'q') {
break;
} else if (comando == 'l') {
for (std::size_t i = 0; i < contas.size(); ++i) {
std::cout << i + 1 << ". " << contas[i]->titular() << " ("
<< contas[i]->tipo() << ")\n";
}
} else if (comando == 'd') {
escolher(contas, numero).depositar(valor);
std::cout << "Depósito feito.\n";
} else if (comando == 's') {
escolher(contas, numero).sacar(valor);
std::cout << "Saque feito.\n";
} else if (comando == 'e') {
std::cout << escolher(contas, numero).extrato();
} else if (comando == 'm') {
for (auto& conta : contas) {
conta->fecharMes();
}
std::cout << "Mês fechado.\n";
} else {
std::cout << "Comando desconhecido: " << linha << '\n';
}
} catch (const SaldoInsuficiente& e) {
std::cout << "Recusado: " << e.what() << '\n';
} catch (const std::exception& e) {
std::cout << "Erro: " << e.what() << '\n';
}
}
std::cout << "\nAté logo!\n";
}
Desafios extras
Sem solução pronta: use o editor da etapa 8, que tem o banco completo, ou o seu computador.
- Centavos inteiros. Troque o
double saldo_porlong long centavos_(R$ 10,50 = 1050). No extrato, mostre comstd::format("{}.{:02}", c / 100, c % 100). Cuidado com os valores negativos. - Vírgula brasileira. Faça o extrato mostrar
1.000,00em vez de1000.00. Dica: formate e depois troque os caracteres no texto (Funções de String). - Transferência no menu. Com o
transferirda missão 7, crie o comandot ORIGEM DESTINO VALOR. Se faltar saldo, nenhuma das duas contas pode mudar. - Cheque especial. Crie
ContaEspecial, que pode ficar negativa até um limite. Dica: a mãe precisa de um métodovirtualpara perguntar "quanto posso sacar?". - Salvar as contas. No
q, grave titular, tipo e saldo num arquivocontas.csv; ao abrir, leia de volta (Ler e Escrever Arquivos). O Controle de Estoque faz isso com um CSV. - Classe abstrata. Faça
tipo()virarvirtual std::string tipo() const = 0;e crieContaCorrente. O que acontece comstd::make_unique<Conta>("Ana")? (Classes Abstratas)
Resumo
- Dados
private+ métodospublic: o saldo só muda pordepositaresacar, e o compilador barraana.saldo_ = …. - Validar com
boolfunciona, mas a resposta pode ser ignorada ([[nodiscard]]só avisa). Uma exceção não passa despercebida: semcatch, o programa termina comterminate called after throwing…. - Exceção própria:
class SaldoInsuficiente : public std::runtime_error, com a mensagem montada no construtor e lida comwhat(). Capture comcatch (const Tipo& e), do mais específico para o mais geral. - Herança:
class ContaPoupanca : public Conta. Na mãe,virtual(e destrutor virtual); na filha,override. Semvirtual, a função errada é chamada em silêncio. - Contas de tipos diferentes num vetor:
std::vector<std::unique_ptr<Conta>>comstd::make_unique. Umvector<Conta>fatia a poupança;unique_ptrnão se copia, então percorra comauto&. std::format("{:<12}{:>+10.2f}", …)alinha colunas, fixa as casas decimais e mostra o sinal..hcom as declarações e#pragma once,.cppcom os corpos (Conta::metodo); compile todos os.cppjuntos, senão vemreferência não definidado ligador.