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.

Você vai aprender
  • 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 scope quando a função está depois do main, 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 receitasEm C++
Escrever a receita uma vez, com um títuloDefinir 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. void quer 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: saudar nã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 saudar agora." O nome, os parênteses (obrigatórios, mesmo vazios) e o ponto e vírgula, que termina a instrução como em qualquer linha do main.
$ 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.

Definir não é executar

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
  1. O programa começa no main (linha 7), mesmo com a função saudar escrita antes dele, e mostra 1..
  2. Linha 9: chamada. O programa pula para a linha 4, mostra 2., chega ao } da função e volta.
  3. Continua na linha 10, logo depois da chamada: 3..
  4. Linha 11: nova chamada, novo pulo, 2. outra vez, volta.
  5. Linha 12: 4., e o main termina. 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:

VantagemNo exemplo
Não repetir códigoOs 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ácillinha(); diz o que acontece. Quem lê o main entende o recibo sem contar tracinhos.
Dividir o problemaUm 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çãoO que dizAula
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 chamada saudar();. (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 chamada saudar, 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 escrevevoid saudar();void saudar() { … }
Tem corpo?Não; termina em ;Sim, entre { }
Para que serveApresentar o nome ao compiladorDizer o que a função faz
Quantas vezes pode aparecerVá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 escreve main(); no seu código.
  • O main devolve um int: o código de saída do programa. 0 quer 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 main e entrega o número 3 a quem chamou (o sistema). O return é 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.

Para que serve o código de saída?

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

FormaComo ficaQuando usar
Funções antes do mainvoid 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 embaixovoid 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.

Esqueceu os parênteses: 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).

Protótipo sem definição: 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

EscritaO 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
voidTipo 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 mainTermina 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:

despedir() { std::cout << "Tchau!\n"; }

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 main fá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 o int que 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.