C++ Funções
Até aqui, todo o seu programa morava dentro do main, de cima a baixo. Isso funciona para programas pequenos, mas logo você se pega copiando e colando as mesmas linhas várias vezes. Uma função é um pedaço de código com nome: você escreve as linhas uma vez, dá um nome a elas e, sempre que precisar, chama esse nome. Nesta aula você vai criar e chamar suas primeiras funções, entender o void, ver o erro que aparece quando a função está "no lugar errado" do arquivo e descobrir que o main também é uma função.
- O que é uma função e por que vale a pena usar funções.
- Como criar (definir) uma função e como chamar uma função.
- Em que ordem o programa executa as linhas quando chama uma função.
- O que significa
void("vazio"). - Por que o g++ diz
‘saudar’ was not declared in this scopequando a função está depois domain, e como resolver com a declaração antecipada (o protótipo). - Que o
mainé uma função como as outras, com uma tarefa especial.
Todos os programas desta aula foram compilados com o g++ 15 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp, com o comando padrão do guia: g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa (cada opção é explicada em Compilar com g++). Você pode repetir cada um no editor do guia, que compila C++ de verdade no navegador.
A ideia: uma receita com nome
Pense num caderno de receitas. A receita do bolo de cenoura está escrita uma vez, na página 12. Quando alguém pede um bolo, você não reescreve a receita: você diz "faça o bolo da página 12". Se um dia descobrir que fica melhor com menos açúcar, corrige só a página 12, e todos os bolos dali em diante saem melhores.
Uma função é exatamente isso:
| No caderno de receitas | Em C++ |
|---|---|
| Escrever a receita uma vez, com um título | Definir a função: dar um nome a um bloco de código |
| "Faça o bolo de cenoura" | Chamar a função: escrever o nome dela seguido de () |
| Corrigir a receita numa página só | Mudar o código da função num lugar só; todas as chamadas mudam juntas |
Você já usou funções prontas sem perceber: std::sqrt (raiz quadrada, em Matemática) e std::getline (em Ler uma Linha) são funções que alguém escreveu e você só chama. Agora você vai escrever as suas.
1. Criar e chamar uma função
A Ana quer que o programa da lojinha cumprimente os clientes. Em vez de escrever a frase dentro do main, ela cria uma função chamada saudar:
#include <iostream>
void saudar() {
std::cout << "Olá! Bem-vinda à lojinha da Ana.\n";
}
int main() {
saudar();
saudar();
}
As linhas 3 a 5 são a definição da função: elas ensinam ao compilador o que saudar faz. Peça por peça:
- void
- O tipo de retorno: o que a função devolve quando termina.
voidquer dizer "vazio": esta função faz o trabalho dela (mostrar uma frase) e não devolve nenhum valor. Funções que devolvem valores ficam para a aula Retorno. - saudar
- O nome da função. Segue as mesmas regras dos nomes de variáveis (Nomes de Variáveis): letras, números e
_, sem começar com número, sem espaço, sem acento. Costuma ser um verbo, porque função faz alguma coisa:saudar,mostrarRecibo,calcular_total. - ()
- Os parênteses guardam os parâmetros, as informações que a função recebe. Aqui estão vazios:
saudarnão precisa de nada para trabalhar. Os parâmetros têm aula própria: Parâmetros e Argumentos. - { … }
- As chaves delimitam o corpo da função: as linhas que rodam cada vez que ela é chamada. Pode ter quantas linhas você quiser.
As linhas 8 e 9 são chamadas:
- saudar();
- "Execute a função
saudaragora." O nome, os parênteses (obrigatórios, mesmo vazios) e o ponto e vírgula, que termina a instrução como em qualquer linha domain.
$ g++ -std=c++23 -Wall -Wextra saudar.cpp -o saudar $ ./saudar Olá! Bem-vinda à lojinha da Ana. Olá! Bem-vinda à lojinha da Ana. « duas chamadas, duas frases
A frase está escrita uma vez no código, mas apareceu duas vezes na tela, porque a função foi chamada duas vezes. Definir uma função sozinho não faz nada aparecer: o código do corpo só roda quando alguém chama a função.
Se você apagar as duas linhas saudar(); do main, o programa compila e não mostra nada. A definição é a receita guardada no caderno; ninguém fez o bolo ainda.
Em que ordem as linhas rodam
Todo programa C++ começa pelo main, não importa onde ele está no arquivo. Quando o main chama uma função, o programa pula para o corpo dela, executa tudo, e volta para a linha seguinte à chamada. Veja com números:
#include <iostream>
void saudar() {
std::cout << "2. dentro da função saudar\n";
}
int main() {
std::cout << "1. começo do main\n";
saudar();
std::cout << "3. de volta ao main\n";
saudar();
std::cout << "4. fim do main\n";
}
$ g++ -std=c++23 -Wall -Wextra ordem.cpp -o ordem $ ./ordem 1. começo do main 2. dentro da função saudar 3. de volta ao main 2. dentro da função saudar 4. fim do main
- O programa começa no
main(linha 7), mesmo com a funçãosaudarescrita antes dele, e mostra1.. - Linha 9: chamada. O programa pula para a linha 4, mostra
2., chega ao}da função e volta. - Continua na linha 10, logo depois da chamada:
3.. - Linha 11: nova chamada, novo pulo,
2.outra vez, volta. - Linha 12:
4., e omaintermina. O programa acabou.
É como ler um livro com notas de rodapé: você para no meio da frase, lê a nota lá embaixo e volta exatamente de onde parou.
Experimente: rode como está. Depois chame saudar() mais uma vez, antes da linha do 1., e veja a ordem mudar. Por último, apague todas as chamadas e rode: a função continua lá, mas nada dela aparece.
#include <iostream>
void saudar() {
std::cout << "2. dentro da função saudar\n";
}
int main() {
std::cout << "1. começo do main\n";
saudar();
std::cout << "3. de volta ao main\n";
}
2. Por que usar funções
A Ana escreveu o recibo da lojinha assim, tudo no main:
#include <iostream>
int main() {
std::cout << "--------------------\n";
std::cout << "Lojinha da Ana\n";
std::cout << "--------------------\n";
std::cout << "3 cadernos R$ 30\n";
std::cout << "--------------------\n";
std::cout << "Obrigada e volte sempre!\n";
std::cout << "--------------------\n";
}
Funciona, mas a linha de tracinhos aparece quatro vezes no código. Se a Ana resolver trocar os tracinhos por ====, terá de mudar quatro lugares, e é fácil esquecer um. Com uma função, a linha de tracinhos fica escrita num lugar só:
#include <iostream>
void linha() {
std::cout << "--------------------\n";
}
int main() {
linha();
std::cout << "Lojinha da Ana\n";
linha();
std::cout << "3 cadernos R$ 30\n";
linha();
std::cout << "Obrigada e volte sempre!\n";
linha();
}
$ g++ -std=c++23 -Wall -Wextra recibo2.cpp -o recibo2 $ ./recibo2 -------------------- Lojinha da Ana -------------------- 3 cadernos R$ 30 -------------------- Obrigada e volte sempre! --------------------
A saída é igual à da primeira versão, letra por letra. O que mudou foi o código, e para melhor:
| Vantagem | No exemplo |
|---|---|
| Não repetir código | Os tracinhos estão escritos uma vez. Programadores chamam isso de DRY (Don't Repeat Yourself, "não se repita"). |
| Mudar num lugar só | Para trocar por ====, a Ana muda a linha 4 e pronto: as quatro chamadas mudam juntas. |
| Ler mais fácil | linha(); diz o que acontece. Quem lê o main entende o recibo sem contar tracinhos. |
| Dividir o problema | Um programa grande vira várias funções pequenas, cada uma com uma tarefa. Fica mais fácil testar e consertar uma parte de cada vez. |
Uma função pode chamar outra
Dentro do corpo de uma função vale tudo o que vale no main, inclusive chamar outras funções. A Ana juntou o cabeçalho do recibo numa função que usa a linha:
#include <iostream>
void linha() {
std::cout << "--------------------\n";
}
void cabecalho() {
linha();
std::cout << "Lojinha da Ana\n";
linha();
}
int main() {
cabecalho();
std::cout << "3 cadernos R$ 30\n";
linha();
}
$ g++ -std=c++23 -Wall -Wextra chamaoutra.cpp -o chamaoutra $ ./chamaoutra -------------------- Lojinha da Ana -------------------- 3 cadernos R$ 30 --------------------
O main chama cabecalho, que chama linha duas vezes; cada uma volta para quem a chamou. Uma função pode até chamar a si mesma; isso se chama recursão e tem aula própria: Recursão.
Onde não pode: função dentro de função
Em C++, toda função é definida fora das outras, lado a lado no arquivo. Escrever uma função dentro do main dá erro:
#include <iostream>
int main() {
void saudar() {
std::cout << "Olá!\n";
}
saudar();
}
$ g++ -std=c++23 -Wall -Wextra dentro.cpp -o dentro
dentro.cpp: In function ‘int main()’:
dentro.cpp:4:19: error: a function-definition is not allowed here before ‘{’ token
4 | void saudar() {
| ^
dentro.cpp:7:5: error: ‘saudar’ was not declared in this scope
7 | saudar();
| ^~~~~~
A function-definition is not allowed here = "uma definição de função não é permitida aqui". Tire a função de dentro do main e coloque-a acima dele. (Existe um jeito de criar uma "funçãozinha" dentro de outra, a lambda, com outra escrita: veja Lambdas.)
3. void: a função que não devolve nada
Toda função em C++ começa dizendo o que ela devolve. Algumas funções servem para calcular e entregar um resultado (a raiz quadrada de 9 é 3); outras servem só para fazer alguma coisa, como mostrar uma frase. Para estas, o tipo de retorno é void.
| Começo da função | O que diz | Aula |
|---|---|---|
void linha() | Faz algo e não devolve nada. | esta |
int dobro(int x) | Recebe um int e devolve um int. | Parâmetros e Retorno |
bool ehPar(int x) | Devolve verdadeiro ou falso. | Retorno |
Como uma função void não devolve nada, não dá para guardar "o resultado" dela numa variável. O compilador recusa:
#include <iostream>
void saudar() {
std::cout << "Olá!\n";
}
int main() {
int x = saudar();
std::cout << x << "\n";
}
$ g++ -std=c++23 -Wall -Wextra voidvalor.cpp -o voidvalor
voidvalor.cpp: In function ‘int main()’:
voidvalor.cpp:8:19: error: void value not ignored as it ought to be
8 | int x = saudar();
| ~~~~~~^~
Void value not ignored as it ought to be = "valor void não ignorado, como deveria ser": você tentou usar um "nada" como se fosse um número. Se precisa de um valor, a função tem de devolver um (aula Retorno); se não precisa, chame a função sozinha na linha: saudar();.
4. A função precisa ser conhecida antes de ser chamada
O compilador lê o arquivo de cima para baixo, uma vez. Quando encontra um nome, ele precisa já saber o que esse nome é. Veja o que acontece se a Ana escrever o main primeiro e a função depois:
#include <iostream>
int main() {
saudar();
}
void saudar() {
std::cout << "Olá!\n";
}
$ g++ -std=c++23 -Wall -Wextra antes.cpp -o antes
antes.cpp: In function ‘int main()’:
antes.cpp:4:5: error: ‘saudar’ was not declared in this scope
4 | saudar();
| ^~~~~~
- In function ‘int main()’
- O problema está dentro do
main. - antes.cpp:4:5
- Arquivo
antes.cpp, linha 4, coluna 5: a chamadasaudar();. (Como ler esse formato com calma: Lendo Erros do Compilador.) - ‘saudar’ was not declared in this scope
- "‘saudar’ não foi declarado neste escopo": na linha 4, o compilador ainda não viu nenhuma função com esse nome. Ela existe, mas só na linha 7, e o compilador não olha para a frente.
Repare: o programa não foi criado. Com um erro, o g++ para e não gera o ./antes. Há dois jeitos de consertar.
Jeito 1: mover a função para cima do main
É o que fizemos até agora: as funções vêm primeiro, o main por último. Simples e muito usado em programas de um arquivo só.
Jeito 2: a declaração antecipada (o protótipo)
Às vezes você prefere o main no topo, para quem abre o arquivo ver logo o "roteiro" do programa. Então basta avisar o compilador, lá em cima, que a função existe e como ela é. Esse aviso se chama declaração ou protótipo:
#include <iostream>
void saudar();
int main() {
saudar();
}
void saudar() {
std::cout << "Olá!\n";
}
- void saudar();
- A declaração (protótipo): o começo da função seguido de
;, sem corpo. Diz ao compilador: "existe uma função chamadasaudar, que não recebe nada e não devolve nada; o corpo vem depois". - saudar();
- Agora, na linha 6, o compilador já conhece o nome e aceita a chamada.
- void saudar() { … }
- A definição: o começo da função de novo, agora com o corpo entre chaves. Tem de bater com a declaração (mesmo nome, mesmo tipo de retorno, mesmos parâmetros).
$ g++ -std=c++23 -Wall -Wextra prototipo.cpp -o prototipo $ ./prototipo Olá!
| Declaração (protótipo) | Definição | |
|---|---|---|
| Como se escreve | void saudar(); | void saudar() { … } |
| Tem corpo? | Não; termina em ; | Sim, entre { } |
| Para que serve | Apresentar o nome ao compilador | Dizer o que a função faz |
| Quantas vezes pode aparecer | Várias (repetir não é erro) | Uma vez só no programa inteiro |
O protótipo parece um detalhe agora, mas é a base para dividir um programa em vários arquivos: os protótipos ficam num arquivo .h e as definições num .cpp (aulas Arquivos .h e .cpp e Compilar Vários Arquivos).
Missão: este programa não compila: rode e leia o erro. Conserte sem mover a função: acrescente o protótipo de saudar acima do main. (No editor, o compilador é o Clang, e a mensagem sai como use of undeclared identifier 'saudar': "uso de identificador não declarado". É o mesmo problema com outras palavras.)
#include <iostream>
int main() {
saudar();
}
void saudar() {
std::cout << "Olá!\n";
}
#include <iostream>
void saudar();
int main() {
saudar();
}
void saudar() {
std::cout << "Olá!\n";
}
5. O main também é uma função
Olhe de novo para o começo do main: int main() { … }. Tipo de retorno, nome, parênteses, corpo: é uma função como as outras. Ela só tem duas coisas especiais:
- Quem chama o
mainé o sistema, quando você roda./programa. Você nunca escrevemain();no seu código. - O
maindevolve umint: o código de saída do programa.0quer dizer "deu tudo certo"; qualquer outro número quer dizer "algo deu errado". O terminal guarda esse número na variável$?.
#include <iostream>
int main() {
std::cout << "Algo deu errado.\n";
return 3;
}
$ g++ -std=c++23 -Wall -Wextra main3.cpp -o main3 $ ./main3 Algo deu errado. $ echo $? 3 « o número do return chegou ao terminal
- return 3;
- Termina o
maine entrega o número 3 a quem chamou (o sistema). Oreturné explicado com calma em Retorno. - echo $?
- No terminal, mostra o código de saída do último programa.
E nos programas sem return? O main é a única função que pode terminar sem return mesmo devolvendo int: nesse caso o C++ considera return 0;. Por isso todos os exemplos desta aula terminaram com código 0. Em qualquer outra função que promete devolver um valor, esquecer o return é um erro sério, mostrado em Retorno.
Scripts e outros programas olham esse número para saber se o seu programa funcionou. O g++ mesmo sai com código 1 quando encontra um erro de compilação. Combine assim: 0 para sucesso, 1 (ou outro número pequeno) para falha.
Formas diferentes de organizar o arquivo
| Forma | Como fica | Quando usar |
|---|---|---|
Funções antes do main | void linha() {…} … int main() {…} | Programas de um arquivo, pequenos. O jeito mais simples; é o que o guia usa na maioria dos exemplos. |
Protótipos no topo, main, definições embaixo | void linha(); … int main() {…} … void linha() {…} | Quando você quer o main logo no começo, ou quando duas funções chamam uma à outra (aí uma delas precisa do protótipo). |
Protótipos num .h, definições num .cpp | #include "loja.h" | Programas maiores, divididos em arquivos (Arquivos .h e .cpp). |
Quando dá errado
error: ‘saudar’ was not declared in this scope
$ g++ -std=c++23 -Wall -Wextra antes.cpp -o antes
antes.cpp: In function ‘int main()’:
antes.cpp:4:5: error: ‘saudar’ was not declared in this scope
4 | saudar();
| ^~~~~~
O que significa: na linha da chamada, o compilador não conhece nenhuma função com esse nome. Ou a função está mais abaixo no arquivo, ou o nome está escrito diferente.
Como consertar: mova a função para cima do main ou acrescente o protótipo (void saudar();) no topo. Se ela já está em cima, compare o nome letra por letra. Quando o nome é parecido com um que existe, o g++ ajuda:
$ g++ -std=c++23 -Wall -Wextra maiusc.cpp -o maiusc
maiusc.cpp: In function ‘int main()’:
maiusc.cpp:8:5: error: ‘Saudar’ was not declared in this scope; did you mean ‘saudar’?
8 | Saudar();
| ^~~~~~
| saudar
Did you mean ‘saudar’? = "você quis dizer ‘saudar’?". Maiúsculas e minúsculas contam: Saudar e saudar são nomes diferentes.
warning: statement is a reference, not call, to function
#include <iostream>
void saudar() {
std::cout << "Olá!\n";
}
int main() {
saudar;
std::cout << "fim\n";
}
$ g++ -std=c++23 -Wall -Wextra semparen.cpp -o semparen
semparen.cpp: In function ‘int main()’:
semparen.cpp:8:5: warning: statement is a reference, not call, to function ‘saudar’ [-Waddress]
8 | saudar;
| ^~~~~~
semparen.cpp:8:5: warning: statement has no effect [-Wunused-value]
$ ./semparen
fim « o "Olá!" não apareceu
O que significa: "a instrução é uma referência à função ‘saudar’, não uma chamada" e "a instrução não tem efeito". Sem os (), você só citou o nome da função, sem mandar executá-la. É só um aviso: o programa foi criado e roda, mas a função nunca é chamada. Sem o -Wall, nem o aviso apareceria (veja Avisos).
Como consertar: saudar();, com os parênteses, mesmo que vazios.
error: void value not ignored as it ought to be
O que significa: você usou o "resultado" de uma função void, por exemplo int x = saudar(); ou std::cout << saudar();. Uma função void não tem resultado.
Como consertar: chame a função sozinha (saudar();), ou mude-a para devolver um valor (aula Retorno).
referência não definida para "saudar()"
#include <iostream>
void saudar();
int main() {
saudar();
}
$ g++ -std=c++23 -Wall -Wextra semdef.cpp -o semdef /usr/bin/x86_64-linux-gnu-ld.bfd: /tmp/ccCGBZ4L.o: na função "main": semdef.cpp:(.text+0x9): referência não definida para "saudar()" collect2: error: ld returned 1 exit status
O que significa: esta mensagem é diferente das outras: não tem número de linha e parte dela está em português. Ela vem do ligador (ld, de linker), a última etapa da compilação, que junta as peças do programa (veja Como o C++ Vira Programa). O compilador aceitou a chamada porque o protótipo prometeu que saudar existe; na hora de juntar tudo, o ligador procurou o corpo da função e não achou. ld returned 1 exit status = "o ld terminou com código 1" (falhou). O nome /tmp/cc….o é um arquivo temporário e muda a cada compilação. Em inglês, a mesma mensagem é undefined reference to `saudar()'.
Como consertar: escreva a definição (void saudar() { … }), ou confira se o nome e os parênteses da definição batem exatamente com os do protótipo. Em projetos com vários arquivos, esse erro costuma significar que você esqueceu de compilar um dos .cpp (Compilar Vários Arquivos).
error: redefinition of ‘void saudar()’
$ g++ -std=c++23 -Wall -Wextra redef.cpp -o redef
redef.cpp:7:6: error: redefinition of ‘void saudar()’
7 | void saudar() {
| ^~~~~~
redef.cpp:3:6: note: ‘void saudar()’ previously defined here
3 | void saudar() {
| ^~~~~~
O que significa: "redefinição de ‘void saudar()’": o arquivo tem duas definições (com corpo) da mesma função, nas linhas 3 e 7. A note ("nota") aponta onde está a primeira.
Como consertar: apague uma das duas, ou dê outro nome a uma delas. (Duas funções com o mesmo nome e parâmetros diferentes são permitidas; isso é a sobrecarga.)
Referência rápida
| Escrita | O que é |
|---|---|
void nome() { … } | Definição de uma função que não recebe nada e não devolve nada |
nome(); | Chamada: executa o corpo da função e volta |
void nome(); | Declaração (protótipo): apresenta a função; o corpo vem depois |
void | Tipo de retorno "vazio": a função não devolve valor |
int main() { … } | A função por onde o programa começa; devolve o código de saída (0 = sucesso) |
return 3; no main | Termina o programa com código de saída 3 |
Exercícios
1. Chamar a função
Complete a linha que chama a função mostrarMenu, que não recebe nada:
2. O tipo que não devolve nada
Complete a definição de uma função que só mostra uma frase e não devolve valor:
3. O protótipo
A função void linha() { … } está definida depois do main. Escreva o protótipo que vai acima do main:
4. Um programa tem a função saudar definida no topo e um main que não a chama. O que aparece ao rodar?
5. O main chama cabecalho(), que mostra A e chama linha(), que mostra B. Depois da chamada, o main mostra C. Qual a saída?
6. O g++ mostra error: ‘calcular’ was not declared in this scope na linha do main, mas a função calcular está no arquivo, lá embaixo. Qual a causa mais provável?
Missão final: o rodapé do recibo
Missão: crie uma função void rodape() que mostre três linhas: a linha de =, depois Obrigada e volte sempre!, depois outra linha de =. Use a função linha() que já existe dentro do seu rodape(). No main, chame rodape() duas vezes (um recibo para cada cliente), depois do texto de cada compra.
Lojinha da Ana 3 cadernos R$ 30 ==================== Obrigada e volte sempre! ==================== Lojinha da Ana 1 caneta R$ 4 ==================== Obrigada e volte sempre! ====================
#include <iostream>
void linha() {
std::cout << "====================\n";
}
// crie a função rodape aqui
int main() {
std::cout << "Lojinha da Ana\n";
std::cout << "3 cadernos R$ 30\n";
// chame rodape aqui
std::cout << "Lojinha da Ana\n";
std::cout << "1 caneta R$ 4\n";
// e aqui
}
#include <iostream>
void linha() {
std::cout << "====================\n";
}
void rodape() {
linha();
std::cout << "Obrigada e volte sempre!\n";
linha();
}
int main() {
std::cout << "Lojinha da Ana\n";
std::cout << "3 cadernos R$ 30\n";
rodape();
std::cout << "Lojinha da Ana\n";
std::cout << "1 caneta R$ 4\n";
rodape();
}
Resumo
- Uma função é um bloco de código com nome. Você define uma vez e chama quantas vezes quiser, com
nome();. - Funções evitam repetição, deixam o
mainfácil de ler e permitem mudar uma coisa num lugar só. - O programa começa no
main. Cada chamada pula para o corpo da função e volta para a linha seguinte. void= a função não devolve nada; não dá para usar o "resultado" dela.- O compilador lê de cima para baixo: a função tem de ser conhecida antes da chamada. Senão:
‘nome’ was not declared in this scope. - Para usar uma função definida mais abaixo, declare o protótipo no topo:
void saudar();. Protótipo sem definição dá erro do ligador (referência não definida). - O
mainé uma função: o sistema a chama, e ointque ela devolve é o código de saída (0 = sucesso). - Próxima aula: Parâmetros e Argumentos, para mandar informações para dentro da função.