C++ Retorno

As funções das aulas anteriores faziam coisas: mostravam uma frase, um item do recibo. Mas muitas vezes você quer que a função calcule algo e entregue o resultado para quem a chamou, que decide o que fazer com ele: guardar numa variável, somar com outro, testar num if. Isso é o retorno. Nesta aula você vai usar o return, escolher o tipo de retorno, ver o que acontece (de verdade) quando esquece o return, criar funções que respondem "sim ou não" e conhecer o auto no retorno.

Você vai aprender
  • O que o return faz: devolve um valor e encerra a função.
  • O tipo de retorno, escrito antes do nome da função, e o que acontece quando o valor devolvido é de outro tipo.
  • Como usar o valor devolvido: numa variável, numa conta, dentro de outra chamada.
  • O aviso -Wreturn-type quando você esquece o return, e por que isso derruba o programa.
  • Funções que devolvem bool, perfeitas para usar em if.
  • A ideia de devolver vários valores (com struct ou std::pair).
  • O auto como tipo de retorno C++14.

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: a máquina de suco

Você coloca laranjas na máquina de suco (os argumentos, aula Parâmetros), ela trabalha lá dentro e devolve um copo de suco. O que você faz com o suco é problema seu: beber agora, guardar na geladeira, misturar com outro. A máquina não bebe o suco por você; ela só entrega.

Uma função com retorno é assim: recebe valores, calcula e entrega o resultado no lugar onde foi chamada. Compare com o que você já viu:

FunçãoO que fazQuem chama recebe
void mostrarDobro(int x)Calcula e mostra o dobro na telaNada. O número foi para a tela e acabou.
int dobro(int x)Calcula e devolve o dobroUm int, para usar como quiser.

1. return: devolver um valor

#include <iostream>

int dobro(int x) {
    return x * 2;
}

int main() {
    int resultado = dobro(5);
    std::cout << resultado << "\n";
    std::cout << dobro(21) << "\n";
    std::cout << dobro(3) + 1 << "\n";
    std::cout << dobro(dobro(2)) << "\n";
}
int dobro(int x)
O int do começo é o tipo de retorno: esta função promete devolver um número inteiro. (Nas aulas anteriores ele era void, "nada".)
return x * 2;
return ("devolver", "retornar"): calcula x * 2 e entrega o resultado a quem chamou. O valor tem de ser do tipo prometido, aqui int.
int resultado = dobro(5);
A chamada dobro(5) "vira" o valor devolvido, 10, e ele é guardado na variável.
dobro(3) + 1
A chamada pode estar no meio de uma conta: dobro(3) vira 6, e 6 + 1 dá 7.
dobro(dobro(2))
O resultado de uma chamada pode ser o argumento de outra. O C++ resolve de dentro para fora: dobro(2) = 4, depois dobro(4) = 8.
$ g++ -std=c++23 -Wall -Wextra dobro.cpp -o dobro
$ ./dobro
10
42
7
8

Pense assim: depois que a função termina, a chamada é substituída pelo valor que ela devolveu. std::cout << dobro(21) vira std::cout << 42.

Um exemplo da lojinha

A Ana quer somar o total de dois itens e dar um brinde se a compra passar de R$ 30. Com uma função que devolve o total de cada item, o main pode somar e comparar:

#include <iostream>

double total(int quantidade, double preco) {
    return quantidade * preco;
}

int main() {
    double compra = total(3, 10.0) + total(2, 2.5);
    std::cout << "Total da compra: R$ " << compra << "\n";
    if (compra > 30) {
        std::cout << "Ganhou um brinde!\n";
    }
}
$ g++ -std=c++23 -Wall -Wextra total.cpp -o total
$ ./total
Total da compra: R$ 35
Ganhou um brinde!

O tipo de retorno é double porque preço tem centavos. Se a função só mostrasse o total (como a item da aula de parâmetros), o main não teria como somar os dois nem comparar com 30. É por isso que funções que calculam costumam devolver em vez de mostrar: quem chama decide o que fazer.

Experimente: crie uma função int triplo(int x) ao lado da dobro e mostre triplo(4). Depois tente dobro(triplo(2)): quanto dá? Faça a conta de cabeça antes de rodar.

#include <iostream>

int dobro(int x) {
    return x * 2;
}

int main() {
    std::cout << dobro(5) << "\n";
    std::cout << dobro(5) + dobro(1) << "\n";
}

2. O return encerra a função na hora

O return faz duas coisas: entrega o valor e sai da função imediatamente. O que estiver depois dele, no mesmo caminho, nunca roda:

#include <iostream>

int dobro(int x) {
    return x * 2;
    std::cout << "esta linha nunca aparece\n";
}

int main() {
    std::cout << dobro(4) << "\n";
}
$ g++ -std=c++23 -Wall -Wextra depois.cpp -o depois
$ ./depois
8                        « a frase da linha 5 não apareceu

Isso é útil: uma função pode ter vários return, um em cada caminho, e sai no primeiro que alcançar. A Ana classifica os alunos do reforço assim:

#include <iostream>
#include <string>

std::string situacao(double nota) {
    if (nota >= 6) {
        return "aprovado";
    }
    if (nota >= 4) {
        return "recuperação";
    }
    return "reprovado";
}

int main() {
    std::cout << "Bruno: " << situacao(7.5) << "\n";
    std::cout << "Carla: " << situacao(5) << "\n";
    std::cout << "Davi: " << situacao(2.5) << "\n";
}
$ g++ -std=c++23 -Wall -Wextra situacao.cpp -o situacao
$ ./situacao
Bruno: aprovado
Carla: recuperação
Davi: reprovado
  1. situacao(7.5): 7.5 >= 6 é verdade, então return "aprovado" e a função acaba ali. Os outros testes nem são feitos.
  2. situacao(5): o primeiro if falha; o segundo (5 >= 4) passa: "recuperação".
  3. situacao(2.5): os dois if falham e a função chega ao último return: "reprovado".

Repare que não precisou de else: se o primeiro return acontece, o resto já não roda. O tipo de retorno aqui é std::string: uma função pode devolver texto, número, bool, um std::vector… qualquer tipo.

return; sozinho, numa função void

Uma função void não devolve nada, mas pode usar return; (sem valor) para sair mais cedo:

#include <iostream>

void dividir(int a, int b) {
    if (b == 0) {
        std::cout << "Não dá para dividir por zero.\n";
        return;
    }
    std::cout << a << " / " << b << " = " << a / b << "\n";
}

int main() {
    dividir(10, 2);
    dividir(7, 0);
    dividir(9, 3);
}
$ g++ -std=c++23 -Wall -Wextra voidret.cpp -o voidret
$ ./voidret
10 / 2 = 5
Não dá para dividir por zero.        « o return; pulou a divisão
9 / 3 = 3

Programadores chamam isso de retorno antecipado (em inglês, early return ou "cláusula de guarda"): trate o caso problemático primeiro e saia; o resto da função fica mais simples, sem um else enorme.


3. O tipo de retorno tem de bater

O tipo antes do nome é uma promessa. O compilador confere cada return contra ela.

CódigoO que acontece
void saudar() { return 5; }Erro: prometeu "nada" e devolveu um número.
int dobro(int x) { return; }Erro: prometeu um int e não devolveu nenhum.
int idade() { return "vinte e nove"; }Erro: texto não vira int.
int media(int a, int b) { return (a + b) / 2.0; }Compila sem aviso, mas o resultado perde as casas decimais.

As três primeiras linhas, no g++:

$ g++ -std=c++23 -Wall -Wextra voidvalor.cpp -o voidvalor
voidvalor.cpp: In function ‘void saudar()’:
voidvalor.cpp:2:12: error: return-statement with a value, in function returning ‘void’ [-fpermissive]
    2 |     return 5;
      |            ^
$ g++ -std=c++23 -Wall -Wextra semvalor.cpp -o semvalor
semvalor.cpp: In function ‘int dobro(int)’:
semvalor.cpp:2:5: error: return-statement with no value, in function returning ‘int’ [-fpermissive]
    2 |     return;
      |     ^~~~~~
semvalor.cpp:1:15: warning: unused parameter ‘x’ [-Wunused-parameter]
    1 | int dobro(int x) {
      |           ~~~~^
$ g++ -std=c++23 -Wall -Wextra texto.cpp -o texto
texto.cpp: In function ‘int idade()’:
texto.cpp:2:12: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive]
    2 |     return "vinte e nove";
      |            ^~~~~~~~~~~~~~
      |            |
      |            const char*
  • Return-statement with a value, in function returning ‘void’ = "return com valor numa função que devolve void".
  • Return-statement with no value, in function returning ‘int’ = "return sem valor numa função que devolve int". (O aviso de parâmetro não usado é consequência: o x ficou sem uso.)
  • Invalid conversion from ‘const char*’ to ‘int’ = "conversão inválida de texto para int", o mesmo erro da aula Parâmetros.

A quarta linha da tabela é a traiçoeira. A média de 7 e 8 é 7.5, mas a função promete um int, então o C++ converte sozinho e corta o .5:

#include <iostream>

int media(int a, int b) {
    return (a + b) / 2.0;
}

int main() {
    std::cout << media(7, 8) << "\n";
}
$ g++ -std=c++23 -Wall -Wextra tipoerrado.cpp -o tipoerrado
$ ./tipoerrado
7                        « 7.5 virou 7, sem erro e sem aviso

A conta dentro da função estava certa; quem estragou foi o tipo de retorno. A correção é prometer o tipo certo: double media(int a, int b). (Como na aula de parâmetros, a opção -Wconversion faria o g++ avisar.)


4. Esquecer o return: aviso e comportamento indefinido

Este é o erro mais perigoso da aula, porque o g++ não trata como erro: ele só avisa e gera o programa. A Ana calculou o dobro, guardou numa variável… e esqueceu de devolver:

#include <iostream>

int dobro(int x) {
    int resultado = x * 2;
}

int main() {
    std::cout << dobro(5) << "\n";
    std::cout << "fim\n";
}
$ g++ -std=c++23 -Wall -Wextra esqueceu.cpp -o esqueceu
esqueceu.cpp: In function ‘int dobro(int)’:
esqueceu.cpp:4:9: warning: unused variable ‘resultado’ [-Wunused-variable]
    4 |     int resultado = x * 2;
      |         ^~~~~~~~~
esqueceu.cpp:5:1: warning: no return statement in function returning non-void [-Wreturn-type]
    5 | }
      | ^
$ ./esqueceu
Instrução ilegal         (imagem do núcleo gravada) ./esqueceu
$ echo $?
132
warning: unused variable ‘resultado’
A primeira pista: a variável foi calculada e ninguém usou.
warning: no return statement in function returning non-void
"Nenhum return numa função que não é void". A função prometeu um int e chega ao } sem entregar nada. O aviso aponta a linha 5, o fecha-chaves, onde a função "cai para fora".
[-Wreturn-type]
O nome do aviso. Ele já vem ligado no g++, mesmo sem -Wall (repare no comando com -O2, mais abaixo).
Instrução ilegal (imagem do núcleo gravada)
Ao rodar, o programa caiu: nem o número nem o fim apareceram. O g++ 15 coloca uma "armadilha" (uma instrução proibida para o processador) no ponto em que a função cai para fora sem return. Em inglês, a mensagem é Illegal instruction (core dumped).
132
O código de saída: 128 + 4, o sinal 4 (SIGILL, instrução ilegal).

O mesmo acontece quando o return existe, mas só em alguns caminhos. A função pontos devolve um valor quando há acertos, e nada quando não há:

#include <iostream>

int pontos(int acertos) {
    if (acertos > 0) {
        return acertos * 10;
    }
}

int main() {
    std::cout << pontos(3) << "\n";
    std::cout << pontos(0) << "\n";
    std::cout << "fim\n";
}
$ g++ -std=c++23 -Wall -Wextra esqueceu2.cpp -o esqueceu2
esqueceu2.cpp: In function ‘int pontos(int)’:
esqueceu2.cpp:7:1: warning: control reaches end of non-void function [-Wreturn-type]
    7 | }
      | ^
$ ./esqueceu2
30                       « pontos(3) passou pelo return: tudo bem
Instrução ilegal         (imagem do núcleo gravada) ./esqueceu2
$ echo $?
132

Control reaches end of non-void function = "a execução pode chegar ao fim de uma função não-void": existe um caminho (aqui, acertos igual a 0) sem return. A chamada pontos(3) funcionou; a pontos(0) derrubou o programa.

Por que "indefinido"?

A regra do C++ diz: se uma função que promete um valor chega ao fim sem return, o comportamento é indefinido. Quer dizer que a linguagem não promete nada sobre o que acontece. Cada compilador, e cada conjunto de opções, faz uma coisa diferente. Veja o mesmo esqueceu2.cpp compilado com otimização (-O2, a opção que deixa o programa mais rápido):

$ g++ -std=c++23 -O2 esqueceu2.cpp -o esqueceu2
esqueceu2.cpp: In function ‘int pontos(int)’:
esqueceu2.cpp:7:1: warning: control reaches end of non-void function [-Wreturn-type]
    7 | }
      | ^
$ ./esqueceu2
30
30
30
30                       « … e assim por mais de 27 mil linhas …
30
Falha de segmentação     (imagem do núcleo gravada) ./esqueceu2
$ echo $?
139

O mesmo código-fonte, outro comportamento: com -O2, o programa ficou repetindo o 30 dezenas de milhares de vezes (o número exato muda de uma execução para outra) e caiu com outra mensagem (Falha de segmentação, código 139). Em outros casos, um programa assim pode até "funcionar" e mostrar um número qualquer. É por isso que o guia insiste: leia os avisos. O -Wreturn-type sempre é um erro de verdade no seu código. A aula Comportamento Indefinido mostra outros casos.

Transforme o aviso em erro

A opção -Werror=return-type faz o g++ recusar o programa em vez de só avisar. Muitos projetos usam -Werror (todos os avisos viram erros); veja Avisos.

E o main?

O main é a única exceção: ele pode terminar sem return e o C++ considera return 0; (aula Funções). Em qualquer outra função não-void, todo caminho precisa de um return com valor.

Missão: rode e veja o aviso (no editor, o Clang escreve non-void function does not return a value in all control paths: "a função não-void não devolve valor em todos os caminhos"). Conserte a função pontos para devolver 0 quando não houver acertos, até o programa compilar sem nenhum aviso.

#include <iostream>

int pontos(int acertos) {
    if (acertos > 0) {
        return acertos * 10;
    }
}

int main() {
    std::cout << "Bruno: " << pontos(3) << " pontos\n";
    std::cout << "Carla: " << pontos(0) << " pontos\n";
}
#include <iostream>

int pontos(int acertos) {
    if (acertos > 0) {
        return acertos * 10;
    }
    return 0;
}

int main() {
    std::cout << "Bruno: " << pontos(3) << " pontos\n";
    std::cout << "Carla: " << pontos(0) << " pontos\n";
}

5. Devolver bool: funções que respondem "sim ou não"

Muitas perguntas do dia a dia têm resposta sim ou não: "o número é par?", "o aluno passou?", "tem estoque?". Uma função que devolve bool (aula Booleanos) responde essas perguntas, e o resultado entra direto num if:

#include <iostream>

bool ehPar(int n) {
    return n % 2 == 0;
}

bool aprovado(double nota) {
    return nota >= 6;
}

int main() {
    std::cout << ehPar(4) << " " << ehPar(7) << "\n";
    std::cout << std::boolalpha << ehPar(4) << " " << ehPar(7) << "\n";
    for (int i = 1; i <= 6; i++) {
        if (ehPar(i)) {
            std::cout << i << " é par\n";
        }
    }
    if (!aprovado(5.5)) {
        std::cout << "Carla vai para a recuperação\n";
    }
}
bool ehPar(int n)
Devolve true (verdadeiro) ou false (falso). Nomes que começam com eh, tem, pode deixam claro que a resposta é sim/não: ehPar, temEstoque, podeVotar.
return n % 2 == 0;
A comparação n % 2 == 0 (o resto da divisão por 2 é zero?) já é um bool. Basta devolvê-la. Não precisa de if (n % 2 == 0) return true; else return false;.
std::boolalpha
Faz o std::cout mostrar true/false em vez de 1/0.
if (ehPar(i))
Lê-se como uma frase: "se i é par". Não precisa escrever == true.
if (!aprovado(5.5))
O ! ("não") inverte a resposta: "se não aprovado" (Operadores Lógicos).
$ g++ -std=c++23 -Wall -Wextra bool.cpp -o bool
$ ./bool
1 0                      « sem boolalpha: true = 1, false = 0
true false
2 é par
4 é par
6 é par
Carla vai para a recuperação

6. E para devolver vários valores?

Um return devolve um valor. Mas às vezes o resultado tem duas partes, como uma divisão: o quociente e o resto. A solução é devolver um valor que contém outros. Aqui fica só a ideia; cada forma tem aula própria.

Com uma struct (um tipo com campos que você nomeia)

#include <iostream>

struct Divisao {
    int quociente;
    int resto;
};

Divisao dividir(int a, int b) {
    return {a / b, a % b};
}

int main() {
    Divisao d = dividir(17, 5);
    std::cout << "17 / 5 = " << d.quociente << ", resto " << d.resto << "\n";
}
$ g++ -std=c++23 -Wall -Wextra struct.cpp -o struct
$ ./struct
17 / 5 = 3, resto 2

A struct Divisao é uma "ficha" com dois campos; a função devolve uma ficha preenchida, e o main lê cada campo com o ponto (d.quociente). Tudo sobre structs: Structs.

Com std::pair (um par pronto)

#include <iostream>
#include <utility>

std::pair<int, int> dividir(int a, int b) {
    return {a / b, a % b};
}

int main() {
    auto [quociente, resto] = dividir(17, 5);
    std::cout << "17 / 5 = " << quociente << ", resto " << resto << "\n";
}
$ g++ -std=c++23 -Wall -Wextra pair.cpp -o pair
$ ./pair
17 / 5 = 3, resto 2

std::pair<int, int> é um par de dois int, já pronto na biblioteca (<utility>). A linha auto [quociente, resto] = … abre o par em duas variáveis de uma vez C++17. Detalhes, e o std::tuple para três ou mais valores: pair, tuple e Desestruturação.

FormaQuando usar
structQuando os valores têm significado (quociente, resto): o código fica mais legível. É a escolha mais comum.
std::pair / std::tuplePara algo rápido e local, sem criar um tipo novo.
Parâmetros por referênciaA forma antiga: a função escreve nas variáveis de quem chamou (Por Valor × Por Referência). Hoje, prefira devolver.

7. auto no retorno C++14

Desde o C++14, você pode escrever auto no lugar do tipo de retorno e deixar o compilador deduzir o tipo olhando o return, como o auto das variáveis (aula auto):

#include <iostream>

auto dobro(int x) {
    return x * 2;
}

auto metade(double x) {
    return x / 2;
}

int main() {
    auto a = dobro(5);
    auto b = metade(5);
    std::cout << a << " " << b << "\n";
}
$ g++ -std=c++23 -Wall -Wextra auto.cpp -o auto
$ ./auto
10 2.5

x * 2 com x inteiro dá int, então dobro devolve int; x / 2 com x double dá double. Duas regras do auto no retorno:

Todos os return precisam dar o mesmo tipo.

auto desconto(bool cliente_fiel) {
    if (cliente_fiel) {
        return 10;
    }
    return 2.5;
}
$ g++ -std=c++23 -Wall -Wextra autoerro.cpp -o autoerro
autoerro.cpp: In function ‘auto desconto(bool)’:
autoerro.cpp:5:12: error: inconsistent deduction for auto return type: ‘int’ and then ‘double’
    5 |     return 2.5;
      |            ^~~

"Dedução inconsistente para o tipo de retorno auto: int e depois double". O primeiro return diz int, o segundo diz double. Escreva 10.0, ou declare o tipo: double desconto(bool cliente_fiel).

O compilador precisa ver o corpo antes da chamada. Com auto, um protótipo sozinho (auto dobro(int x);) não basta, porque sem o corpo ninguém sabe o tipo:

$ g++ -std=c++23 -Wall -Wextra autoantes.cpp -o autoantes
autoantes.cpp: In function ‘int main()’:
autoantes.cpp:6:23: error: use of ‘auto dobro(int)’ before deduction of ‘auto’
    6 |     std::cout << dobro(5) << "\n";
      |                  ~~~~~^~~

"Uso de auto dobro(int) antes da dedução do auto". Defina a função antes do main.

EscritaQuando usar
int dobro(int x)O padrão. Quem lê vê o tipo na hora. Use na maioria das funções.
auto dobro(int x)Funções curtas em que o tipo é óbvio, ou quando o tipo é longo e chato de escrever. Muito usado com templates e lambdas.
auto dobro(int x) -> int"Tipo de retorno no fim" C++11: é o mesmo que int dobro(int x), escrito de outro jeito. Aparece em código moderno e em templates; você vai reconhecê-lo.

Quando dá errado

warning: no return statement in function returning non-void ou control reaches end of non-void function

O que significa: a função promete devolver um valor, mas pelo menos um caminho chega ao } sem return. O programa é gerado mesmo assim e, ao passar por esse caminho, tem comportamento indefinido: no g++ 15 sem otimização, ele cai com Instrução ilegal (código 132).

Como consertar: garanta um return com valor em todos os caminhos. Um truque: termine a função com um return "padrão" depois dos if. Trate esse aviso como erro (-Werror=return-type).

Instrução ilegal (imagem do núcleo gravada) ao rodar
$ ./esqueceu
Instrução ilegal         (imagem do núcleo gravada) ./esqueceu
$ echo $?
132

O que significa: o processador encontrou uma instrução proibida. Em programas seus, compilados pelo g++, a causa mais comum é exatamente esta: uma função não-void que terminou sem return. Volte à compilação e procure o aviso -Wreturn-type.

error: return-statement with a value, in function returning ‘void’

O que significa: a função é void e tem return algo;.

Como consertar: se a função deve devolver o valor, troque o void pelo tipo certo (int, double…). Se não deve, use return; sem valor, ou tire o return.

error: return-statement with no value, in function returning ‘int’

O que significa: a função promete um int e tem um return; vazio.

Como consertar: devolva um valor (return 0;, return resultado;) ou, se a função não precisa devolver nada, mude o tipo para void.

O resultado sai sem as casas decimais

O que significa: a conta é com casas decimais, mas o tipo de retorno é int. O valor é convertido e a parte decimal some, sem aviso.

Como consertar: use double como tipo de retorno quando o resultado pode ter casas decimais.

O valor devolvido "sumiu"
#include <iostream>

int dobro(int x) {
    return x * 2;
}

int main() {
    dobro(5);
    std::cout << "fim\n";
}
$ g++ -std=c++23 -Wall -Wextra ignorar.cpp -o ignorar
$ ./ignorar
fim                      « o 10 foi calculado e jogado fora

O que significa: chamar uma função que devolve um valor e não usar o resultado é permitido, e o g++ não avisa: o 10 é calculado e descartado. Para quem vem da aula anterior, é um engano comum achar que a função "mostra" o valor.

Como consertar: guarde (int r = dobro(5);) ou mostre (std::cout << dobro(5);). Se você escreve uma função cujo resultado nunca deveria ser ignorado, marque-a com [[nodiscard]] C++17 antes do tipo de retorno, e o compilador passa a avisar:

#include <iostream>

[[nodiscard]] int dobro(int x) {
    return x * 2;
}

int main() {
    dobro(5);
    std::cout << "fim\n";
}
$ g++ -std=c++23 -Wall -Wextra nodiscard.cpp -o nodiscard
nodiscard.cpp: In function ‘int main()’:
nodiscard.cpp:8:10: warning: ignoring return value of ‘int dobro(int)’, declared with attribute ‘nodiscard’ [-Wunused-result]
    8 |     dobro(5);
      |     ~~~~~^~~
nodiscard.cpp:3:19: note: declared here
    3 | [[nodiscard]] int dobro(int x) {
      |                   ^~~~~

"Ignorando o valor de retorno de int dobro(int), declarada com o atributo nodiscard" (no discard = "não descarte").


Referência rápida

EscritaO que faz
int f(…) { return valor; }Devolve um int e encerra a função
return;Sai de uma função void antes do fim
int r = f(5);Guarda o valor devolvido
f(g(2))O retorno de g vira argumento de f
bool ehPar(int n) { return n % 2 == 0; }Função que responde sim/não, para usar em if
struct / std::pair<A, B>Devolver vários valores juntos
auto f(int x) { return x * 2; }Tipo de retorno deduzido pelo compilador (C++14)
auto f(int x) -> intTipo de retorno escrito no fim (C++11)
[[nodiscard]]Avisa se alguém ignorar o valor devolvido (C++17)
-Wreturn-typeAviso: função não-void sem return em algum caminho

Exercícios

1. Devolver

Complete a função que devolve o quadrado de um número:

int quadrado(int x) { x * x; }

2. O tipo certo

Complete o tipo de retorno de uma função que devolve o preço com 10% de desconto (o preço tem centavos):

comDesconto(double preco) { return preco * 0.9; }

3. De dentro para fora

Com int dobro(int x) { return x * 2; }, quanto vale dobro(dobro(3)) + 1?

4. O que o return faz?

5. A função int pontos(int a) { if (a > 0) { return a * 10; } } compila com o aviso control reaches end of non-void function. O que acontece em pontos(0)?

6. Qual é o jeito mais simples de escrever uma função que diz se alguém pode votar (16 anos ou mais)?

Missão final: média e aprovação

Missão: complete as duas funções. media recebe três notas e devolve a média delas. aprovado recebe uma média e devolve true se ela for 6 ou mais. O main já está pronto e não deve mudar. Saída esperada:

Bruno: média 7 - aprovado
Carla: média 5 - reprovado
#include <iostream>
#include <string>

double media(double n1, double n2, double n3) {
    // devolva a média das três notas
}

bool aprovado(double m) {
    // devolva true se m for 6 ou mais
}

void boletim(std::string aluno, double n1, double n2, double n3) {
    double m = media(n1, n2, n3);
    std::cout << aluno << ": média " << m << " - ";
    if (aprovado(m)) {
        std::cout << "aprovado\n";
    } else {
        std::cout << "reprovado\n";
    }
}

int main() {
    boletim("Bruno", 6, 7, 8);
    boletim("Carla", 4, 5.5, 5.5);
}
#include <iostream>
#include <string>

double media(double n1, double n2, double n3) {
    return (n1 + n2 + n3) / 3;
}

bool aprovado(double m) {
    return m >= 6;
}

void boletim(std::string aluno, double n1, double n2, double n3) {
    double m = media(n1, n2, n3);
    std::cout << aluno << ": média " << m << " - ";
    if (aprovado(m)) {
        std::cout << "aprovado\n";
    } else {
        std::cout << "reprovado\n";
    }
}

int main() {
    boletim("Bruno", 6, 7, 8);
    boletim("Carla", 4, 5.5, 5.5);
}

Resumo

  • O tipo de retorno vem antes do nome: int dobro(…) promete devolver um int; void promete nada.
  • return valor; entrega o valor a quem chamou e encerra a função. A chamada "vira" o valor devolvido.
  • Uma função pode ter vários return; sai no primeiro que alcançar. Em void, return; sai mais cedo.
  • O valor devolvido é convertido para o tipo de retorno: int corta casas decimais em silêncio.
  • Esquecer o return numa função não-void dá só um aviso (-Wreturn-type) e cria comportamento indefinido: no g++ 15, Instrução ilegal. Nunca ignore esse aviso.
  • Funções bool respondem sim/não: return n % 2 == 0;, e cabem direto num if.
  • Para vários valores, devolva uma struct ou um std::pair.
  • auto no retorno deixa o compilador deduzir o tipo; todos os return precisam concordar.
  • Próxima aula: Valores Padrão, para parâmetros opcionais.