C++ Valores Padrão
Na aula Parâmetros e Argumentos, cada chamada tinha de mandar todos os argumentos, senão vinha o erro too few arguments. Mas muitas vezes um parâmetro quase sempre tem o mesmo valor: a quantidade quase sempre é 1, a linha do recibo quase sempre tem 20 tracinhos. Para esses casos, o C++ deixa você dar ao parâmetro um valor padrão: se a chamada mandar o argumento, ele é usado; se não mandar, entra o padrão. Nesta aula você vai criar parâmetros opcionais e conhecer as duas regras que o compilador cobra: padrões só no fim, e padrão escrito num lugar só.
- Como dar um valor padrão a um parâmetro:
void saudar(std::string nome = "cliente"). - Como o C++ decide qual argumento vai para qual parâmetro quando alguns faltam.
- Por que os parâmetros com padrão precisam ficar no fim (e o erro
default argument missing for parameter). - Por que não dá para "pular" um argumento no meio, e a armadilha de
linha('='). - Onde escrever o padrão quando a função tem protótipo (e o erro
default argument given for parameter … [-fpermissive]com a notaprevious specification).
Todos os programas desta aula foram compilados com o g++ 15 na máquina fictícia da Ana, na pasta ~/cpp, com o comando padrão do guia, g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: o pedido "do jeito de sempre"
Na padaria, a Ana pede "um café". O atendente não pergunta o tamanho nem se é com açúcar: ele faz o café de sempre, o médio sem açúcar. Se um dia ela quiser diferente, diz: "um café grande". O que ela não disser fica no padrão.
Um parâmetro com valor padrão funciona assim: a função já sabe o que usar quando a chamada não disser nada. Quem chama só precisa falar o que for diferente do de sempre.
1. Um parâmetro opcional
#include <iostream>
#include <string>
void saudar(std::string nome = "cliente") {
std::cout << "Olá, " << nome << "!\n";
}
int main() {
saudar("Ana");
saudar();
}
- std::string nome
- O parâmetro, como nas aulas anteriores.
- = "cliente"
- O valor padrão (em inglês, default argument, "argumento padrão"): o valor que
nomerecebe quando a chamada não mandar nenhum. Escreve-se com=, como ao criar uma variável. - saudar("Ana");
- A chamada mandou o argumento:
nomevale"Ana". O padrão é ignorado. - saudar();
- A chamada não mandou nada:
nomevale o padrão,"cliente". Sem o padrão, esta linha dariatoo few arguments to function.
$ g++ -std=c++23 -Wall -Wextra saudar.cpp -o saudar $ ./saudar Olá, Ana! Olá, cliente!
Por dentro, é como se o compilador reescrevesse a segunda chamada como saudar("cliente"). A função em si não sabe se o valor veio da chamada ou do padrão.
2. Vários parâmetros com padrão
A linha do recibo da aula Funções sempre tinha 20 tracinhos. A Ana quer poder mudar o tamanho e o símbolo de vez em quando, sem ter de dizer os dois toda vez:
#include <iostream>
void linha(int tamanho = 20, char simbolo = '-') {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
int main() {
linha();
linha(10);
linha(10, '=');
linha(5, '*');
}
$ g++ -std=c++23 -Wall -Wextra linha.cpp -o linha $ ./linha -------------------- ---------- ========== *****
| Chamada | tamanho | simbolo | Por quê |
|---|---|---|---|
linha() | 20 (padrão) | '-' (padrão) | Nenhum argumento: os dois padrões. |
linha(10) | 10 | '-' (padrão) | Um argumento: vai para o primeiro parâmetro. |
linha(10, '=') | 10 | '=' | Os dois argumentos: nenhum padrão é usado. |
A regra é a da aula de parâmetros: os argumentos vão para os parâmetros pela posição, da esquerda para a direita. Os padrões só preenchem o que sobrou no fim.
Mais um exemplo, com três parâmetros e todas as combinações possíveis:
#include <iostream>
#include <string>
void cartao(std::string nome = "cliente", std::string loja = "Lojinha da Ana", int desconto = 5) {
std::cout << loja << ": " << nome << " ganhou " << desconto << "% de desconto\n";
}
int main() {
cartao();
cartao("Bruno");
cartao("Carla", "Papelaria Central");
cartao("Davi", "Lojinha da Ana", 10);
}
$ g++ -std=c++23 -Wall -Wextra todos.cpp -o todos $ ./todos Lojinha da Ana: cliente ganhou 5% de desconto Lojinha da Ana: Bruno ganhou 5% de desconto Papelaria Central: Carla ganhou 5% de desconto Lojinha da Ana: Davi ganhou 10% de desconto
Repare na última chamada: para dar 10% ao Davi, a Ana teve de escrever também a loja, "Lojinha da Ana", mesmo sendo o padrão. É a próxima regra.
Missão: o main chama linha(), linha(5) e linha(3, '*'), mas a função exige os dois argumentos e o programa não compila. Sem mexer no main, dê valores padrão aos parâmetros (20 tracinhos '-') para a saída ser:
-------------------- ----- ***
#include <iostream>
void linha(int tamanho, char simbolo) {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
int main() {
linha();
linha(5);
linha(3, '*');
}
#include <iostream>
void linha(int tamanho = 20, char simbolo = '-') {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
int main() {
linha();
linha(5);
linha(3, '*');
}
3. Não dá para pular um argumento
E se a Ana quiser o tamanho padrão, mas com =? Em algumas linguagens dá para dizer "pule o primeiro". Em C++, não. A primeira tentativa é deixar o lugar vazio:
#include <iostream>
void linha(int tamanho = 20, char simbolo = '-') {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
int main() {
linha(, '=');
}
$ g++ -std=c++23 -Wall -Wextra pular2.cpp -o pular2
pular2.cpp: In function ‘int main()’:
pular2.cpp:11:11: error: expected primary-expression before ‘,’ token
11 | linha(, '=');
| ^
Expected primary-expression before ‘,’ token = "esperava uma expressão antes da vírgula": um argumento vazio não existe em C++.
A segunda tentativa é pior, porque compila:
#include <iostream>
void linha(int tamanho = 20, char simbolo = '-') {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
int main() {
linha('=');
}
$ g++ -std=c++23 -Wall -Wextra pular.cpp -o pular « nenhum aviso $ ./pular -------------------------------------------------------------
Saíram 61 tracinhos! O argumento '=' foi para o primeiro parâmetro, tamanho, que é int. Um char é um número por dentro (o código do = é 61, veja Caracteres), e a conversão implícita fez o resto. O simbolo ficou com o padrão '-'.
A única forma é escrever também o primeiro: linha(20, '=');. Por isso, ao criar a função, ponha por último o parâmetro que menos muda: quem chama vai querer trocar os primeiros sem precisar repetir os outros.
4. Os padrões só podem ficar no fim
Como os argumentos são distribuídos da esquerda para a direita, um parâmetro com padrão não pode ter, depois dele, um parâmetro sem padrão. A Ana tentou deixar a quantidade opcional no meio:
#include <iostream>
#include <string>
void item(std::string produto, int quantidade = 1, double preco) {
std::cout << quantidade << " x " << produto
<< " = R$ " << quantidade * preco << "\n";
}
int main() {
item("caneta", 2.5);
}
$ g++ -std=c++23 -Wall -Wextra meio.cpp -o meio
meio.cpp:4:59: error: default argument missing for parameter 3 of ‘void item(std::string, int, double)’
4 | void item(std::string produto, int quantidade = 1, double preco) {
| ~~~~~~~^~~~~
meio.cpp:4:36: note: ...following parameter 2 which has a default argument
4 | void item(std::string produto, int quantidade = 1, double preco) {
| ~~~~^~~~~~~~~~~~~~
- default argument missing for parameter 3
- "Falta o argumento padrão do parâmetro 3" (o
preco). - note: ...following parameter 2 which has a default argument
- "…que vem depois do parâmetro 2, que tem argumento padrão". Como a
quantidadetem padrão, todo parâmetro depois dela também precisaria ter.
Pense na chamada item("caneta", 2.5): o 2.5 deveria ir para a quantidade ou para o preço? Pela posição, iria para a quantidade, e o preço ficaria sem nada. O compilador corta o problema pela raiz. A correção é mudar a ordem, deixando o opcional no fim:
#include <iostream>
#include <string>
void item(std::string produto, double preco, int quantidade = 1) {
std::cout << quantidade << " x " << produto
<< " = R$ " << quantidade * preco << "\n";
}
int main() {
item("caneta", 2.5);
item("caderno", 10.0, 3);
}
$ g++ -std=c++23 -Wall -Wextra fim.cpp -o fim $ ./fim 1 x caneta = R$ 2.5 3 x caderno = R$ 30
| Cabeçalho | Vale? |
|---|---|
void f(int a, int b = 2, int c = 3) | Sim: os padrões estão no fim. |
void f(int a = 1, int b = 2, int c = 3) | Sim: todos têm padrão; f() funciona. |
void f(int a = 1, int b, int c) | Não: b e c vêm depois de um padrão. |
void f(int a, int b = 2, int c) | Não: c vem depois de um padrão. |
5. Com protótipo: o padrão vai num lugar só
Quando a função tem declaração antecipada (o protótipo, da aula Funções), o padrão aparece duas vezes? Não. A Ana tentou escrever nos dois lugares:
#include <iostream>
void linha(int tamanho = 20, char simbolo = '-');
int main() {
linha();
}
void linha(int tamanho = 20, char simbolo = '-') {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
$ g++ -std=c++23 -Wall -Wextra duas.cpp -o duas
duas.cpp:9:6: error: default argument given for parameter 1 of ‘void linha(int, char)’ [-fpermissive]
9 | void linha(int tamanho = 20, char simbolo = '-') {
| ^~~~~
duas.cpp:3:6: note: previous specification in ‘void linha(int, char)’ here
3 | void linha(int tamanho = 20, char simbolo = '-');
| ^~~~~
duas.cpp:9:6: error: default argument given for parameter 2 of ‘void linha(int, char)’ [-fpermissive]
9 | void linha(int tamanho = 20, char simbolo = '-') {
| ^~~~~
duas.cpp:3:6: note: previous specification in ‘void linha(int, char)’ here
3 | void linha(int tamanho = 20, char simbolo = '-');
| ^~~~~
- default argument given for parameter 1
- "Argumento padrão dado para o parâmetro 1" na linha 9, a definição. Um erro para cada parâmetro repetido (1 e 2).
- note: previous specification … here
- "Especificação anterior aqui": o padrão já tinha sido dado na linha 3, o protótipo. Mesmo sendo o mesmo valor, repetir é proibido.
- [-fpermissive]
- Ignore: é uma opção antiga que relaxaria a regra. Não use.
Então, deixar o padrão só na definição? Também não funciona, porque o main vem antes dela:
#include <iostream>
void linha(int tamanho, char simbolo);
int main() {
linha();
}
void linha(int tamanho = 20, char simbolo = '-') {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
$ g++ -std=c++23 -Wall -Wextra sodef.cpp -o sodef
sodef.cpp: In function ‘int main()’:
sodef.cpp:6:10: error: too few arguments to function ‘void linha(int, char)’
6 | linha();
| ~~~~~^~
sodef.cpp:3:6: note: declared here
3 | void linha(int tamanho, char simbolo);
| ^~~~~
Lembre: o compilador lê de cima para baixo. Na linha 6, ele só conhece o protótipo da linha 3, que não tem padrão nenhum; o padrão da linha 9 ainda não foi lido. O certo é o padrão no protótipo, que é o que quem chama enxerga, e a definição sem ele:
#include <iostream>
void linha(int tamanho = 20, char simbolo = '-');
int main() {
linha();
linha(8, '~');
}
void linha(int tamanho, char simbolo) {
for (int i = 0; i < tamanho; i++) {
std::cout << simbolo;
}
std::cout << "\n";
}
$ g++ -std=c++23 -Wall -Wextra certo.cpp -o certo $ ./certo -------------------- ~~~~~~~~
| Situação | Onde escrever o padrão |
|---|---|
Só a definição, acima do main (sem protótipo) | Na definição. |
| Protótipo no topo e definição embaixo | No protótipo. A definição fica sem. |
Protótipo num .h e definição num .cpp | No .h, que é o que os outros arquivos incluem (Arquivos .h e .cpp). |
Muitos programadores deixam o padrão como comentário na definição, só para quem lê: void linha(int tamanho /* = 20 */, char simbolo /* = '-' */) { … }. Para o compilador, comentário não conta (aula Comentários).
6. O que pode ser um valor padrão
Qualquer valor do tipo do parâmetro: um número, um texto, uma constante com nome. Uma constante deixa claro de onde veio o número e permite mudá-lo num lugar só (aula Constantes):
#include <iostream>
const double TAXA_PADRAO = 0.05;
double comTaxa(double valor, double taxa = TAXA_PADRAO) {
return valor + valor * taxa;
}
int main() {
std::cout << comTaxa(100) << "\n";
std::cout << comTaxa(100, 0.10) << "\n";
}
$ g++ -std=c++23 -Wall -Wextra constante.cpp -o constante $ ./constante 105 « taxa padrão de 5% 110 « taxa de 10%, mandada na chamada
Repare que os valores padrão funcionam igual em funções que devolvem um resultado, como esta.
Valor padrão ou outra forma?
Existe outro jeito de deixar uma função ser chamada com números diferentes de argumentos: escrever duas funções com o mesmo nome, a sobrecarga. Quando usar cada uma:
| Use… | Quando | Exemplo |
|---|---|---|
| Valor padrão | O comportamento é o mesmo; só um valor costuma se repetir. | linha(int tamanho = 20) |
| Sobrecarga | O trabalho é diferente conforme o que chega (outro tipo, outro cálculo). | mostrar(int) e mostrar(std::string) |
| Nenhum dos dois | O argumento é importante e quem chama sempre deve pensar nele. | O preço de um item: sem padrão. |
Cuidado para não exagerar: uma função com cinco parâmetros opcionais fica difícil de chamar (lembre do Davi, que teve de repetir a loja). Se isso acontecer, talvez seja hora de agrupar as opções numa struct.
Quando dá errado
error: default argument missing for parameter 3 of …
O que significa: um parâmetro sem padrão aparece depois de um parâmetro com padrão. A nota ...following parameter 2 which has a default argument mostra qual tem o padrão.
Como consertar: mude a ordem, deixando os opcionais no fim (e ajuste as chamadas), ou dê padrão também aos que vêm depois.
error: default argument given for parameter 1 of … [-fpermissive] e note: previous specification … here
O que significa: o padrão foi escrito duas vezes: no protótipo e na definição.
Como consertar: deixe o padrão só no protótipo (no .h, se houver) e apague-o da definição. No editor do guia, o Clang diz redefinition of default argument ("redefinição do argumento padrão").
too few arguments to function, mas a função tem padrão
O que significa: o padrão está só na definição, que fica depois da chamada; o compilador, na linha da chamada, só conhece o protótipo sem padrão.
Como consertar: mova o padrão para o protótipo.
linha('=') e saiu uma linha enorme de tracinhos
O que significa: o argumento foi para o primeiro parâmetro (o tamanho), não para o símbolo. Um char virou o número 61. Não houve erro nem aviso.
Como consertar: mande também os argumentos anteriores: linha(20, '='). Se isso for comum, repense a ordem dos parâmetros.
Referência rápida
| Escrita | O que faz |
|---|---|
void f(int x = 5) | f() usa 5; f(9) usa 9 |
void f(int a, int b = 2, int c = 3) | f(1), f(1, 7) e f(1, 7, 8) valem |
f(1, , 8) | Não existe: não dá para pular um argumento |
void f(int x = 5); + void f(int x) { … } | Com protótipo: padrão só no protótipo |
void f(int x /* = 5 */) { … } | Lembrete opcional na definição, como comentário |
Exercícios
1. Tornar opcional
Complete para que repetir() sem argumento repita 3 vezes:
2. Quanto vale?
Com void linha(int tamanho = 20, char simbolo = '-'), quantos caracteres a chamada linha(7) mostra (sem contar a quebra de linha)?
3. O protótipo
A definição é void avisar(std::string msg, int vezes) { … }, abaixo do main. Complete o protótipo para que vezes seja 1 por padrão:
4. Qual destes cabeçalhos é válido?
5. Com void cartao(std::string nome = "cliente", int desconto = 5), o que acontece em cartao(10)?
6. A função tem protótipo no topo e definição embaixo. Onde vai o valor padrão?
Missão final: etiquetas de preço
Missão: a Ana quer o main no topo do arquivo. Escreva o protótipo de etiqueta acima do main, com parcelas valendo 1 por padrão, e a definição embaixo, sem repetir o padrão. A função mostra produto: R$ preco à vista se parcelas for 1, e produto: Nx de R$ valor se for mais (o valor de cada parcela é preco / parcelas). Saída esperada:
caderno: R$ 30 à vista mochila: 3x de R$ 30 estojo: R$ 12.5 à vista
#include <iostream>
#include <string>
// protótipo aqui
int main() {
etiqueta("caderno", 30);
etiqueta("mochila", 90, 3);
etiqueta("estojo", 12.5);
}
// definição aqui
#include <iostream>
#include <string>
void etiqueta(std::string produto, double preco, int parcelas = 1);
int main() {
etiqueta("caderno", 30);
etiqueta("mochila", 90, 3);
etiqueta("estojo", 12.5);
}
void etiqueta(std::string produto, double preco, int parcelas) {
if (parcelas == 1) {
std::cout << produto << ": R$ " << preco << " à vista\n";
} else {
std::cout << produto << ": " << parcelas << "x de R$ " << preco / parcelas << "\n";
}
}
Resumo
tipo nome = valorna lista de parâmetros dá um valor padrão: se a chamada não mandar o argumento, ele é usado.- Os argumentos vão para os parâmetros da esquerda para a direita; os padrões preenchem o que faltou no fim.
- Parâmetros com padrão ficam no fim da lista. Senão:
default argument missing for parameter N. - Não dá para pular um argumento do meio. E cuidado:
linha('=')manda o'='(61) para o primeiro parâmetro. - Com protótipo, o padrão vai só no protótipo. Nos dois lugares:
default argument given for parameter … previous specification. Só na definição:too few argumentsna chamada. - Próxima aula: Por Valor × Por Referência, para a função poder mudar a variável de quem chama.