C++ Construtores
Até aqui, a lojinha da Ana criava produtos de dois jeitos: Produto p;, que deixava lixo nos números, ou Produto p{"caneta", 3, 50};, que só funcionava porque a classe era simples e toda pública. Nesta aula, a classe passa a decidir como um produto nasce. Quem faz isso é o construtor: uma função especial que o C++ chama sozinho no momento em que o objeto é criado.
- O que é um construtor e quando ele roda.
- Construtores com parâmetros e as três formas de chamá-los.
- A lista de inicialização (
: nome(n), preco(p)) e por que preferi-la. - O construtor padrão, e por que ele some quando você escreve outro (o erro
no matching function for call to). - Como ter vários construtores, e o que fazem
= defaulte os valores iniciais na declaração. - A ideia do
explicit.
Os programas desta aula foram compilados e executados na máquina fictícia da Ana, na pasta ~/cpp/construtores, com o g++ 15.2 e o comando padrão g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: a ficha já sai preenchida
Imagine que a Ana tem um carimbo na lojinha. Toda ficha nova passa pelo carimbo assim que é tirada do bloco, e o carimbo já escreve "sem nome, R$ 0, estoque 0". Não existe ficha com campo em branco: ou ela sai carimbada, ou alguém preenche na hora, no balcão. Nunca fica uma ficha "pela metade" na gaveta.
O construtor é o carimbo. Ele roda uma vez, automaticamente, no nascimento de cada objeto, e deixa o objeto pronto para uso. Você não chama o construtor como um método comum: criar o objeto é chamar o construtor.
1. O primeiro construtor
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco;
int estoque;
Produto() {
nome = "sem nome";
preco = 0;
estoque = 0;
std::cout << "Nasceu um produto!\n";
}
void mostrar() {
std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
}
};
int main() {
std::cout << "Início do main\n";
Produto a;
Produto b;
a.mostrar();
b.nome = "borracha";
b.mostrar();
std::cout << "Fim do main\n";
}
$ g++ -std=c++23 -Wall -Wextra nasce.cpp -o nasce $ ./nasce Início do main Nasceu um produto! « Produto a; Nasceu um produto! « Produto b; sem nome: R$ 0 (0 no estoque) borracha: R$ 0 (0 no estoque) Fim do main
- Produto()
- O construtor. Duas regras o diferenciam de um método: ele tem o mesmo nome da classe e não tem tipo de retorno, nem
void. - nome = "sem nome"; preco = 0; estoque = 0;
- O corpo deixa o objeto num estado válido. Acabou o lixo: a borracha agora nasce com R$ 0, e não com um número qualquer como na aula Classes e Objetos.
- Produto a;
- Criar o objeto chama o construtor. A mensagem "Nasceu um produto!" apareceu duas vezes, uma para cada objeto, exatamente na linha em que cada um foi criado.
Um construtor sem parâmetros, como este, se chama construtor padrão (default constructor): é o que roda quando você cria o objeto sem dar nenhum valor. A mensagem na tela foi só para você ver o momento em que ele roda; construtores de verdade normalmente não escrevem nada.
2. Construtor com parâmetros
"Sem nome, R$ 0" é melhor que lixo, mas a Ana quer cadastrar a caneta já com nome, preço e estoque. Um construtor pode receber parâmetros, como qualquer função:
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco;
int estoque;
Produto(std::string n, int p, int e) {
nome = n;
preco = p;
estoque = e;
}
void mostrar() {
std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
}
};
int main() {
Produto caneta("caneta", 3, 50);
Produto caderno{"caderno", 12, 8};
Produto regua = {"régua", 5, 20};
caneta.mostrar();
caderno.mostrar();
regua.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra parametros.cpp -o parametros $ ./parametros caneta: R$ 3 (50 no estoque) caderno: R$ 12 (8 no estoque) régua: R$ 5 (20 no estoque)
- Produto(std::string n, int p, int e)
- Construtor com três parâmetros. Os nomes curtos
n,p,eevitam confusão com os atributosnome,preco,estoque. (Usar os mesmos nomes é possível, e a aula this mostra como.) - Produto caneta("caneta", 3, 50);
- Forma com parênteses: os valores vão para o construtor, na ordem dos parâmetros.
- Produto caderno{"caderno", 12, 8};
- Forma com chaves. Parece a da aula passada, mas agora quem recebe os valores é o construtor, não os atributos direto.
- Produto regua = {"régua", 5, 20};
- Forma com igual e chaves. Também chama o construtor.
Produto caderno{"caderno", 12, 8};): elas servem para tudo, recusam conversões que perdem informação (como pôr 3.9 num int; veja Conversão de Tipos) e evitam a armadilha dos parênteses vazios, que você vai ver em Quando dá errado.3. A lista de inicialização
O construtor acima funciona, mas faz as coisas em duas etapas. Antes de entrar nas chaves do construtor, o C++ já criou todos os atributos (o nome nasceu como string vazia; os int, com lixo). Só então o corpo roda e troca os valores. É como tirar a ficha do bloco, carimbar "sem nome" e depois apagar e escrever "caneta".
A lista de inicialização cria cada atributo já com o valor certo, de uma vez:
class Produto {
public:
std::string nome;
int preco;
int estoque;
Produto(std::string n, int p, int e) : nome(n), preco(p), estoque(e) {
}
void mostrar() {
std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
}
};
$ g++ -std=c++23 -Wall -Wextra lista.cpp -o lista $ ./lista caneta: R$ 3 (50 no estoque)
- :
- Os dois-pontos depois dos parênteses abrem a lista de inicialização.
- nome(n)
- "Crie o atributo
nomecom o valor den." Os itens são separados por vírgula. Também vale com chaves:nome{n}. - { }
- O corpo continua obrigatório, mesmo vazio. Ele roda depois da lista; use-o para o que não é só guardar valores (conferir regras, mostrar algo).
Por que preferir a lista
- Faz uma vez só. Cada atributo nasce com o valor certo, em vez de nascer vazio e ser trocado depois. Para um
int, a diferença é pequena; para uma string longa ou um vetor, é trabalho jogado fora. - Algumas coisas só funcionam com ela. Um atributo
const(um código de produto que nunca muda, por exemplo) não pode receber valor depois de criado. Veja o que acontece se você tentar no corpo:
#include <iostream>
#include <string>
class Produto {
public:
const int codigo;
std::string nome;
Produto(int c, std::string n) {
codigo = c;
nome = n;
}
};
int main() {
Produto caneta{101, "caneta"};
std::cout << caneta.codigo << " " << caneta.nome << "\n";
}
$ g++ -std=c++23 -Wall -Wextra constante.cpp -o constante
constante.cpp: In constructor ‘Produto::Produto(int, std::string)’:
constante.cpp:9:5: error: uninitialized const member in ‘const int’ [-fpermissive]
9 | Produto(int c, std::string n) {
| ^~~~~~~
constante.cpp:6:15: note: ‘const int Produto::codigo’ should be initialized
6 | const int codigo;
| ^~~~~~
constante.cpp:10:16: error: assignment of read-only member ‘Produto::codigo’
10 | codigo = c;
| ~~~~~~~^~~
Dois erros que contam a história das "duas etapas": o primeiro diz que o membro const ficou sem valor na hora de nascer (should be initialized, "deveria ser inicializado"); o segundo, que a linha 10 tentou atribuir a um membro só de leitura (read-only). Com a lista de inicialização, o código nasce com o valor e pronto:
Produto(int c, std::string n) : codigo(c), nome(n) {
}
$ g++ -std=c++23 -Wall -Wextra constante2.cpp -o constante2 $ ./constante2 101 caneta
O mesmo vale para atributos que são referências e para atributos de classes que não têm construtor padrão: só a lista consegue criá-los.
-Wall avisa (-Wreorder). E, se um item usa outro, o resultado pode ser lixo:#include <iostream>
#include <string>
class Produto {
public:
int preco;
int estoque;
Produto(int e) : estoque(e), preco(estoque * 2) {
}
};
int main() {
Produto x{10};
std::cout << "estoque " << x.estoque << ", preço " << x.preco << "\n";
}
$ g++ -std=c++23 -Wall -Wextra ordem.cpp -o ordem
ordem.cpp: In constructor ‘Produto::Produto(int)’:
ordem.cpp:7:9: warning: ‘Produto::estoque’ will be initialized after [-Wreorder]
7 | int estoque;
| ^~~~~~~
ordem.cpp:6:9: warning: ‘int Produto::preco’ [-Wreorder]
6 | int preco;
| ^~~~~
ordem.cpp:9:5: warning: when initialized here [-Wreorder]
9 | Produto(int e) : estoque(e), preco(estoque * 2) {
| ^~~~~~~
ordem.cpp:9:40: warning: member ‘Produto::estoque’ is used uninitialized [-Wuninitialized]
9 | Produto(int e) : estoque(e), preco(estoque * 2) {
| ^~~~~~~
ordem.cpp: In constructor ‘Produto::Produto(int)’:
ordem.cpp:9:40: warning: ‘*this.Produto::estoque’ is used uninitialized [-Wuninitialized]
9 | Produto(int e) : estoque(e), preco(estoque * 2) {
| ^~~~~~~
$ ./ordem
estoque 10, preço 0 « deveria ser 20
Os três primeiros avisos formam uma frase só: "estoque será inicializado depois de preco, quando inicializado aqui". Como preco vem primeiro na classe, ele foi criado primeiro, usando um estoque que ainda não existia (os avisos is used uninitialized). O "0" da saída é lixo que, desta vez, deu zero. Regra prática: escreva a lista sempre na mesma ordem da declaração, e não leve avisos na brincadeira.
4. O construtor padrão e quando ele some
Se você não escreve nenhum construtor, o C++ cria um construtor padrão invisível, que não faz quase nada (por isso Produto p; funcionava nas aulas anteriores, com lixo nos int). Mas, assim que você escreve qualquer construtor, o C++ entende que a classe agora tem regras de nascimento, e deixa de criar o padrão. Veja:
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco;
int estoque;
Produto(std::string n, int p, int e) : nome(n), preco(p), estoque(e) {
}
};
int main() {
Produto caneta{"caneta", 3, 50};
Produto vazio;
}
$ g++ -std=c++23 -Wall -Wextra sumiu.cpp -o sumiu
sumiu.cpp: In function ‘int main()’:
sumiu.cpp:16:13: error: no matching function for call to ‘Produto::Produto()’
16 | Produto vazio;
| ^~~~~
sumiu.cpp:16:13: note: there are 3 candidates
sumiu.cpp:10:5: note: candidate 1: ‘Produto::Produto(std::string, int, int)’
10 | Produto(std::string n, int p, int e) : nome(n), preco(p), estoque(e) {
| ^~~~~~~
sumiu.cpp:10:5: note: candidate expects 3 arguments, 0 provided
sumiu.cpp:4:7: note: candidate 2: ‘constexpr Produto::Produto(const Produto&)’
4 | class Produto {
| ^~~~~~~
sumiu.cpp:4:7: note: candidate expects 1 argument, 0 provided
sumiu.cpp:4:7: note: candidate 3: ‘constexpr Produto::Produto(Produto&&)’
sumiu.cpp:4:7: note: candidate expects 1 argument, 0 provided
Leia a mensagem por partes:
| Trecho | Significado |
|---|---|
no matching function for call to ‘Produto::Produto()’ | "Nenhuma função serve para a chamada Produto::Produto()": você pediu um construtor sem argumentos, e ele não existe. |
there are 3 candidates | "Há 3 candidatos": os construtores que existem. O g++ lista cada um e diz por que não serve. |
candidate 1: ‘Produto::Produto(std::string, int, int)’ … expects 3 arguments, 0 provided | O seu construtor: espera 3 argumentos, e você deu 0. |
candidate 2 e candidate 3 | Construtores de cópia (const Produto&) e de movimento (Produto&&), que o C++ cria sozinho para toda classe. Eles servem para criar um produto a partir de outro (aula Cópia e Movimento) e também precisam de 1 argumento. |
No editor do guia, o Clang resume tudo em no matching constructor for initialization of 'Produto' ("nenhum construtor serve para inicializar Produto"), seguido das notas com os candidatos.
Isso não é um defeito: é proteção. Se a classe diz "todo produto nasce com nome, preço e estoque", um produto sem nada não deveria existir. Mas, se você quer permitir os dois jeitos, precisa escrever os dois construtores, como na próxima seção.
Produto p;, Produto p{};, std::vector<Produto> v(10); (dez produtos vazios) e std::array<Produto, 3> a;. Se o construtor padrão sumiu, todas essas linhas dão o mesmo erro.5. Vários construtores
Uma classe pode ter quantos construtores quiser, desde que os parâmetros sejam diferentes: é a mesma regra da sobrecarga de funções. O C++ escolhe o construtor pelos argumentos que você passa:
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco;
int estoque;
Produto() : nome("sem nome"), preco(0), estoque(0) {
}
Produto(std::string n, int p) : nome(n), preco(p), estoque(0) {
}
Produto(std::string n, int p, int e) : nome(n), preco(p), estoque(e) {
}
void mostrar() {
std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
}
};
int main() {
Produto a;
Produto b{"apontador", 2};
Produto c{"caneta", 3, 50};
a.mostrar();
b.mostrar();
c.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra varios.cpp -o varios $ ./varios sem nome: R$ 0 (0 no estoque) apontador: R$ 2 (0 no estoque) caneta: R$ 3 (50 no estoque)
| Criação | Construtor escolhido |
|---|---|
Produto a; | Produto(), o padrão |
Produto b{"apontador", 2}; | Produto(std::string, int): produto que ainda não chegou ao estoque |
Produto c{"caneta", 3, 50}; | Produto(std::string, int, int) |
Funciona, mas repare na repetição: "estoque(0)" aparece em dois construtores, "preco(0)" em outro. Se um dia o estoque inicial mudar, você precisa lembrar de mudar em vários lugares. A próxima seção resolve isso.
Produto(std::string n, int p) : Produto(n, p, 0) {} (o "construtor delegante", desde o C++11). E parâmetros com valor padrão também valem em construtores: Produto(std::string n, int p, int e = 0) serve para 2 ou 3 argumentos.Experimente: apague o primeiro construtor, Produto(), e rode: qual linha do main deixa de compilar? Depois coloque-o de volta e acrescente um quarto produto, Produto d{"cola", 6, 15};.
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco;
int estoque;
Produto() : nome("sem nome"), preco(0), estoque(0) {
}
Produto(std::string n, int p) : nome(n), preco(p), estoque(0) {
}
Produto(std::string n, int p, int e) : nome(n), preco(p), estoque(e) {
}
void mostrar() {
std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
}
};
int main() {
Produto a;
Produto b{"apontador", 2};
Produto c{"caneta", 3, 50};
a.mostrar();
b.mostrar();
c.mostrar();
}
6. Valores iniciais na declaração e = default
Desde o C++11, cada atributo pode ter um valor inicial escrito na própria declaração. Ele vale para todo construtor que não disser outra coisa na lista de inicialização:
class Produto {
public:
std::string nome = "sem nome";
int preco = 0;
int estoque = 0;
Produto() = default;
Produto(std::string n, int p) : nome(n), preco(p) {
}
Produto(std::string n, int p, int e) : nome(n), preco(p), estoque(e) {
}
void mostrar() {
std::cout << nome << ": R$ " << preco << " (" << estoque << " no estoque)\n";
}
};
$ g++ -std=c++23 -Wall -Wextra padrao.cpp -o padrao $ ./padrao sem nome: R$ 0 (0 no estoque) apontador: R$ 2 (0 no estoque) caneta: R$ 3 (50 no estoque)
Mesma saída da seção anterior, com menos repetição (o main é o mesmo):
- int estoque = 0;
- Valor inicial na declaração (default member initializer). O construtor de 2 parâmetros não menciona
estoquena lista, então ele nasce com 0. O de 3 parâmetros menciona, e aí vale o que a lista diz: o valor da lista sempre ganha. - Produto() = default;
- "Quero o construtor padrão que o compilador sabe fazer." Ele traz de volta o construtor padrão que tinha sumido (seção 4). Como os atributos têm valores na declaração, o produto vazio nasce com "sem nome", 0 e 0, sem lixo.
int, double, bool, ponteiro) direto na declaração. Assim, nenhum construtor, nem um que você escreva no futuro, deixa lixo para trás. Também vale chaves: int preco{0}; ou só int preco{}; (zero).= default × escrever Produto() {} à mão: com valores na declaração, os dois dão o mesmo resultado. Prefira = default: deixa claro, para quem lê, que o construtor não faz nada de especial além do que as declarações já dizem.
7. explicit (só a ideia)
Um construtor que recebe um argumento só tem um efeito colateral: o C++ passa a usá-lo para converter sozinho. Veja um cupom de desconto da lojinha:
#include <iostream>
#include <string>
class Cupom {
public:
int percentual;
Cupom(int p) : percentual(p) {
}
};
void cobrar(int valor, Cupom c) {
int desconto = valor * c.percentual / 100;
std::cout << "Total: R$ " << valor - desconto << "\n";
}
int main() {
cobrar(200, Cupom{10});
cobrar(200, 10); // 10 o quê? reais? por cento?
}
$ g++ -std=c++23 -Wall -Wextra cupom.cpp -o cupom $ ./cupom Total: R$ 180 Total: R$ 180
A segunda chamada passou um int onde se esperava um Cupom, e o C++ criou um cupom escondido com Cupom(10). Funcionou, mas quem lê cobrar(200, 10) pode achar que são R$ 10 de desconto, e não 10%. Conversões silenciosas assim escondem erros.
A palavra explicit na frente do construtor proíbe essa conversão automática. No arquivo explicito.cpp, só esta linha mudou em relação a cupom.cpp:
explicit Cupom(int p) : percentual(p) {
}
$ g++ -std=c++23 -Wall -Wextra explicito.cpp -o explicito
explicito.cpp: In function ‘int main()’:
explicito.cpp:19:17: error: could not convert ‘10’ from ‘int’ to ‘Cupom’
19 | cobrar(200, 10);
| ^~
| |
| int
"Não foi possível converter 10 de int para Cupom": agora quem chama é obrigado a escrever Cupom{10}, e o código fica claro. Regra prática: marque com explicit todo construtor de um argumento só, a não ser que a conversão automática faça sentido de verdade (como de texto para std::string). Construtores de dois ou mais argumentos raramente precisam dele.
Formas de criar um objeto: qual usar
| Forma | Exemplo | Comentário |
|---|---|---|
| Chaves | Produto c{"caneta", 3, 50}; | Recomendada. Funciona sempre e recusa conversões que perdem dados. |
| Chaves vazias | Produto c{}; | Usa o construtor padrão. Nunca confunde o compilador. |
| Parênteses | Produto c("caneta", 3, 50); | Comum em código antigo. Funciona com argumentos; vazio, não (veja abaixo). |
| Sem nada | Produto c; | Usa o construtor padrão. Seguro quando a classe dá valores iniciais a tudo. |
| Igual + chaves | Produto c = {"caneta", 3, 50}; | Funciona, mas não com construtores explicit. |
Quando dá errado
#include <iostream>
#include <string>
class Produto {
public:
std::string nome = "sem nome";
int preco = 0;
int estoque = 0;
void mostrar() {
std::cout << nome << ": R$ " << preco << "\n";
}
};
int main() {
Produto p();
p.mostrar();
}
$ g++ -std=c++23 -Wall -Wextra vexing.cpp -o vexing
vexing.cpp: In function ‘int main()’:
vexing.cpp:16:14: warning: empty parentheses were disambiguated as a function declaration [-Wvexing-parse]
16 | Produto p();
| ^~
vexing.cpp:16:14: note: remove parentheses to default-initialize a variable
16 | Produto p();
| ^~
| --
vexing.cpp:16:14: note: or replace parentheses with braces to aggregate-initialize a variable
vexing.cpp:17:7: error: request for member ‘mostrar’ in ‘p’, which is of non-class type ‘Produto()’
17 | p.mostrar();
| ^~~~~~~
O que aconteceu: Produto p(); parece "crie p sem argumentos", mas, para o C++, é a declaração de uma função chamada p, que não recebe nada e devolve um Produto (igual a int f();). O aviso diz exatamente isso: "parênteses vazios foram interpretados como declaração de função". Essa armadilha é tão famosa que tem nome: most vexing parse, "a análise mais irritante". Por isso o erro da linha 17: p é do tipo Produto() (uma função), não uma classe, e não tem mostrar.
Como resolver: as próprias notas do g++ dão as duas saídas: tire os parênteses (Produto p;) ou troque por chaves (Produto p{};). É mais um motivo para preferir chaves.
#include <iostream>
#include <string>
class Produto {
public:
std::string nome;
int preco;
void Produto(std::string n, int p) : nome(n), preco(p) {
}
};
int main() {
Produto caneta{"caneta", 3};
}
$ g++ -std=c++23 -Wall -Wextra retorno.cpp -o retorno
retorno.cpp:9:5: error: return type specification for constructor invalid
9 | void Produto(std::string n, int p) : nome(n), preco(p) {
| ^~~~
retorno.cpp:9:10: error: bit-field ‘Produto::Produto(std::string, int)’ has non-integral type ‘void (Produto::)(std::string, int)’ {aka ‘void (Produto::)(std::__cxx11::basic_string<char>, int)’}
9 | void Produto(std::string n, int p) : nome(n), preco(p) {
| ^~~~~~~
O que aconteceu: o primeiro erro é o que importa: "especificar tipo de retorno para construtor é inválido". Construtor não devolve nada, nem void. O segundo erro é o g++ se confundindo com o resto da linha (ele tentou ler os : como outra construção da linguagem, um bit-field); ignore-o, ele some quando você conserta o primeiro. Lembre da aula Lendo Erros do Compilador: conserte sempre o primeiro erro primeiro. O Clang diz só constructor cannot have a return type.
Como resolver: apague o void: Produto(std::string n, int p) : nome(n), preco(p) {}.
É o erro no matching function for call to ‘Produto::Produto()’ da seção 4. Como resolver, conforme o que você quer: se um produto vazio faz sentido, acrescente Produto() = default; (e dê valores iniciais aos atributos); se não faz, o erro está certo, e quem precisa mudar é a linha que cria o objeto, passando os valores.
Referência rápida
| Escrita | O que faz |
|---|---|
Produto() { … } | Construtor padrão (sem parâmetros). Mesmo nome da classe, sem tipo de retorno. |
Produto(std::string n, int p) { … } | Construtor com parâmetros. |
: nome(n), preco(p) | Lista de inicialização: cria os atributos já com o valor. Use a ordem da declaração. |
int estoque = 0; | Valor inicial na declaração; vale quando a lista não diz outra coisa. |
Produto() = default; | Pede o construtor padrão do compilador (traz de volta o que sumiu). |
explicit Cupom(int p) | Proíbe a conversão automática de int para Cupom. |
Produto(std::string n, int p) : Produto(n, p, 0) {} | Construtor que delega para outro. |
Produto c{"caneta", 3, 50}; | Cria o objeto chamando o construtor (forma recomendada). |
Exercícios
1. A lista de inicialização
Complete o construtor de class Cliente { std::string nome; int pontos; … }; usando lista de inicialização:
2. Trazer de volta
Complete para pedir ao compilador o construtor padrão:
3. Sem conversão escondida
Que palavra se escreve antes de Cupom(int p) para proibir cobrar(200, 10)?
4. A classe tem só o construtor Produto(std::string n, int p). O que acontece com Produto x;?
5. Por que Produto p(); não cria um produto?
6. Na classe, preco é declarado antes de estoque. A lista é : estoque(e), preco(p). Em que ordem os atributos são criados?
Missão final: o construtor do Cliente
Missão: complete a classe Cliente. (1) Dê aos atributos os valores iniciais "visitante" e 0 na declaração. (2) Escreva o construtor padrão com = default. (3) Escreva o construtor Cliente(std::string n, int p) usando lista de inicialização. A saída deve ser:
Bia: 10 pontos visitante: 0 pontos
#include <iostream>
#include <string>
class Cliente {
public:
std::string nome;
int pontos;
// escreva os dois construtores aqui
void mostrar() {
std::cout << nome << ": " << pontos << " pontos\n";
}
};
int main() {
Cliente bia{"Bia", 10};
Cliente alguem;
bia.mostrar();
alguem.mostrar();
}
#include <iostream>
#include <string>
class Cliente {
public:
std::string nome = "visitante";
int pontos = 0;
Cliente() = default;
Cliente(std::string n, int p) : nome(n), pontos(p) {
}
void mostrar() {
std::cout << nome << ": " << pontos << " pontos\n";
}
};
int main() {
Cliente bia{"Bia", 10};
Cliente alguem;
bia.mostrar();
alguem.mostrar();
}
Resumo
- Construtor: função com o nome da classe, sem tipo de retorno, chamada automaticamente quando o objeto nasce.
- Construtor padrão = sem parâmetros. O C++ cria um invisível só se você não escrever nenhum construtor; escreveu outro, ele some (
no matching function for call to ‘Produto::Produto()’). - Prefira a lista de inicialização (
: nome(n), preco(p)): cria cada atributo já com o valor e é obrigatória para membrosconste referências. Escreva-a na ordem da declaração. - Vários construtores podem conviver, com parâmetros diferentes. Valores iniciais na declaração (
int estoque = 0;) eProduto() = default;evitam repetição e lixo. explicitem construtores de um argumento evita conversões escondidas.- Crie objetos com chaves:
Produto p{};.Produto p();declara uma função. - Próxima aula: proteger os dados para que ninguém crie um produto inválido depois do nascimento, em public e private.