Ajude nosso projeto a se manter online.

Função Recursiva em C++

Neste tutorial de C++, vamos aprender a famosa e importante arte da recursividade, e você vai entender a piadinha de programadores: "Para saber recursão, tem que saber recursão"

Função Recursiva

Nos programadores que fizemos no decorrer de nossos tutoriais, fizemos com que, várias vezes, uma função fosse chamada dentro da outra. Recursão tem a ver com invocar uma função.

Mas ela chama uma função em especial. A função recursiva nada mais é a função que chama ela mesma.

Vamos criar uma função que exibe uma mensagem na tela e depois invoca ela mesma:
#include <iostream>
using namespace std;

void recur()
{
    cout<<"Progressive C++ course"<<endl;
    recur();
}

int main()
{
    recur();
    return 0;
}
Acontece o seguinte:
Recursividade em C++

E é bem simples de entender...invocamos a recur(), ela exibe uma mensagem na tela e ... chama ela novamente, que ao ser chamada, exibe uma mensagem na tela...depois chama ela novamente, que exibe uma mensagem...e assim vai, indefinidamente, para o infinito e além.

Pra parar, tive que dar um Control+C, senão ficava rodando pra sempre.

Como usar recursão com funções

Desse jeito, as funções recursivas não são tão úteis.
Para aprender como usar elas da maneira correta, precisamos de uma espécie de contador, assim como fizemos com os loopings.

Vamos fazer com que a recur() receba um inteiro, e só vai chamar ela mesmo se esse inteiro for maior que 0:
#include <iostream>
using namespace std;

void recur(int counter)
{
    if(counter>0){
        cout<<"Curso C++ Progressivo"<<endl;
        recur(counter-1);
    }
}

int main()
{
    recur(5);
    return 0;
}
Se o número recebido for maior que 0, exibimos a mensagem e chamamos novamente a recur(), porém, vamos passar um argumento menor, subtraído de 1, pois essa função já executou uma vez.

Assim, se você fizer recur(5), ela vai rodar a função 5 vezes apenas:

  1. Primeiro chamamos recur(5), exibiu a mensagem e chamou recur(4).
  2. recur(4) exibe a mensagem e chama recur(3).
  3. recur(3) exibe a mensagem e chama recur(2).
  4. recur(2) exibe a mensagem e chama recur(1)
  5. recur(1) exibe a mensagem e chama recur(0).
Quando chamamos recur(0), nada é feito e nenhuma função é mais invocada, encerrando a recursão.

Recursividade com funções em C++

Vamos praticar e ver como aplicar a técnica da recursividade com funções, em C++?

Somatório com recursão

Vamos chamar de sum(n) o somatório do valor n.

Por exemplo:
sum(5) =  5 + 4 + 3 + 2 + 1

Mas sum(4) = 4 + 3 + 2 + 1

Ou seja: sum(5) = 5 + sum(4)

Podemos generalizar fazendo:
sum(n) = n + sum(n-1)

Veja que tem uma recursão aí. A sum() está chamando a sum(), com um argumento decrementado em 1, mas está. Quando o argumento for 1, ele deve retornar 1 (pois somatório de 1 é 1) e parar de invocar a função de somatório.

Nosso código fica:
#include <iostream>
using namespace std;

int sum(int num)
{
    if(num==1)
        return 1;
    else
        return num+sum(num-1);
}

int main()
{
    int num;

    cout<<"Somatório de: ";
    cin>> num;

    cout<<"Igual a: "<<sum(num)<<endl;
}
Bacana. né?

Fatorial com recursão
Vamos chamar de fat(n) o fatorial do valor n.
fat(n) = n * (n-1) * (n-2) * ... 3 * 2 * 1

Ou seja:
fat(n) = n * fat(n-1)

Concorda?
Temos aí uma lógica com recursividade. A função fat() chamando a fat().
Ela deve chamar até chegar no argumento 1, e fatorial de 1 é 1.

Veja como fica nosso código:
#include <iostream>
using namespace std;

int fat(int num)
{
    if(num==1)
        return 1;
    else
        return num*fat(num-1);
}

int main()
{
    int num;

    cout<<"Fatorial de: ";
    cin>> num;

    cout<<"Igual a: "<<fat(num)<<endl;
}
Sequência de Fibonacci com recursividade

Sobrecarga de Funções: Parâmetros e Argumentos diferentes

Neste tutorial de nossa apostila de C++, vamos aprender o que é sobrecarga de funções, para que servem e como usar esta importante técnica de programação.

Tamanhos de Parâmetros e Argumentos em Funções no C++

No tutorial sobre Argumentos Padrão, vimos que é possível enviar uma quantidade variada de argumentos para uma função, desde que ela esteja usando parâmetros de referência.

Por exemplo, o código abaixo é de uma função que calcula uma média aritmética:
#include <iostream>
using namespace std;

float average(float a, float b, float c, float d = 0.0)
{
    return (a+b+c+d)/4;
}

int main()
{
    cout<<"Media de 3 numeros: "<<average(10, 9, 7)<<endl;
    cout<<"Media de 4 numeros: "<<average(10, 9, 7, 6)<<endl;

    return 0;
}
Ela pode receber 3 ou 4 argumentos. Se enviar apenas 3, o 4 argumento é o padrão, de valor 0.
Porém, note um erro.

Mesmo que enviemos apenas 3 argumentos, a média aritmética é calculada como se houvessem 4 notas.

Seria interessante se, caso eu enviasse 3 argumentos, ela retornasse:
(a+b+c)/3

E caso eu enviasse 4 argumentos, ela retornasse:
(a+b+c+d)/4

E isso é possível, com sobrecarga de funções.

Sobrecarga de Funções em C++

Sobrecarga, ou overloading, é a técnica que permite que tenhamos funções com mesmo nome, desde que seus parâmetros sejam diferentes.

Veja como ficaria o exemplo do programa que calcula as médias de 3 ou 4 notas:
#include <iostream>
using namespace std;

float average(float a, float b, float c)
{
    return (a+b+c)/3;
}

float average(float a, float b, float c, float d)
{
    return (a+b+c+d)/4;
}

int main()
{
    cout<<"Media de 3 numeros: "<<average(10, 9, 7)<<endl;
    cout<<"Media de 4 numeros: "<<average(10, 9, 7, 6)<<endl;

    return 0;
}
Note que invocamos a função average(), a única diferença é que na primeira chamada passamos 3 argumentos, e na segunda chamada da função passamos 4 argumentos.

Como o C++ é maroto e inteligente, ele sabe qual função rodar, corretamente.
Ele é tão esperto que, mesmo que exista o mesmo número de parâmetros/argumentos, ele consegue diferenciar através do tipo de dado que estamos usando.

Veja:
#include <iostream>
using namespace std;

double average(double a, double b, double c)
{
    cout<<"Media de 3 double   : ";
    return (a+b+c)/3;
}

int average(int a, int b, int c)
{
    cout<<"Media de 3 inteiros : ";
    return (a+b+c)/3;
}

int main()
{
    cout<<average(10.0, 9.0, 7.0)<<endl;
    cout<<average(10, 9, 7)<<endl;

    return 0;
}
Quando passamos variáveis do tipo double, ele chama a double average()
Quando passamos variáveis do tipo int, ele chama a int average()

O C++ consegue diferenciar pois as assinaturas de cada função são diferentes (seja no número de parâmetros ou no tipo que vai trabalhar).

Aliás, até se as funções tiverem o mesmo nome, o mesmo número de parâmetros e os mesmos tipos de dados, podemos fazer sobrecarga de funções, desde que a ordem dos parâmetros seja diferente:
#include <iostream>
using namespace std;

void func(int a, double b)
{
    cout<<"Primeiro é int  : "<<a<<endl;
    cout<<"Segundo é double: "<<b<<endl;
}

void func(double b, int a)
{
    cout<<"Primeiro é double: "<<b<<endl;
    cout<<"Segundo é int    : "<<a<<endl;
}

int main()
{
    func(1, 2.5);
    cout<<endl;
    func(2.5, 1);

    return 0;
}
No exemplo assim temos: func(int, double)
E também: func(double, int)

Basta que a assinatura de uma função para outra seja diferente, para podermos usar sobrecarga, onde assinatura é um conjunto de dados: nome da função, tipo de dados, número de dados e ordem das informações.

No que se refere as boas práticas de programação, você deve usar sobrecarga de funções sempre que precisar usar funções com mesmo propósito e mesma lógica, mas para número e/ou tipos e/ou ordem de dados diferentes.

Parâmetros e Variáveis de Referência de Funções em C++

Neste tutorial de nosso curso de C++, vamos aprender a passagem de argumentos por referência, em funções.

Passagem por Valor

Já aprendemos como enviar informações para funções, através dos argumentos e parâmetros.
E vimos que esta passagem é chamada passagem por valor, pois só passamos o valor para a função.

Se enviamos uma variável pra uma função, e dentro dessa função essa variável é alterada, ela não é alterada fora da função. Teste o seguinte código, que eleva um número ao quadrado:
#include <iostream>
using namespace std;

void square(int num);

int main()
{
    int number = 6;

    cout<<"Antes  : num = "<<number<<endl;
    square(number);
    cout<<"Depois : num = "<<number<<endl;

    return 0;
}

void square(int num)
{
    num = num*num;
    cout<<"Durante: num = "<<num<<endl;
}
Quando invocamos a função: square(number), estamos na verdade, passando uma cópia do valor da variável number.

Dentro da função, a variável num vai receber uma cópia do valor de number. Ou seja, ela não vai ter acesso a variável original number, somente ao seu valor! Por isso, passagem por valor.

Parâmetro de Referência: &

Seja a declaração de variável:

  • int num = 6;


O que estamos fazendo aí é alocando, reservando, um espaço na memória do seu computador.
Assim, quando usamos 'num', o C++ entende que devemos ir no endereço para onde essa variável aponta e pegar o número que está dentro daquele local da memória.

Como vimos, quando passamos essa variável normal para uma função que espera uma variável normal, ela pega apenas uma cópia de seu valor, e não mexe no conteúdo original.

Porém, existe outro tipo especial de variável, a variável de referência.
Ela é especial, pois se passarmos uma variável para uma função e a função quiser obter a variável de referência, através de um parâmetro de referência, ela vai ter acesso ao endereço da memória (a referência), onde esta variável está originalmente armazenada.

Ou seja: vamos ter acesso, de verdade, a variável, e não somente a sua cópia.

Para pegarmos a referência de uma variável, basta usarmos o operador & antes do nome da variável, no parâmetro da função! É o parâmetro de referência. Tanto no protótipo quanto na declaração da função.

Assim, o exemplo de código anterior, fica:
#include <iostream>
using namespace std;

void square(int &);

int main()
{
    int number = 6;

    cout<<"Antes  : num = "<<number<<endl;
    square(number);
    cout<<"Depois : num = "<<number<<endl;

    return 0;
}

void square(int &num)
{
    num = num*num;
    cout<<"Durante: num = "<<num<<endl;
}
Veja agora o resultado:
Variável de referência em C++ : &

De fato, a função conseguiu mudar o valor da variável, pois essa passagem de argumento para o parâmetro foi via parâmetro de referência.

O que acontece aqui é que o parâmetro não vai capturar o valor do argumento, mas sim sua referência, para onde ele está apontando na memória. Falando em apontar para um endereço, estudaremos mais sobre esse assunto nas seções de ponteiros e arrays (vetores) em C++, onde estudaremos sobre passagem por referência, usando ponteiros.

Mais sobre parâmetros de referência em C++

Alguns programadores também preferem declarar o protótipo assim:

  • square (int&);

Você também pode usar assim no protótipo:

  • square(int &num);
  • square(int& num);


Também pode, o importante é que o & esteja tanto no protótipo quanto na declaração da função, ok?

Lembrando que se seu parâmetro é de referência, ele só pode trabalhar com variável de referência.
Seria um erro se você usar um argumento que não é uma variável, como um literal, uma expressão ou uma constante, por exemplo:

  • square(10); // tem que usar square(number);
  • square(number+1); // é uma expressão, evite
Quando fazemos: square(int &num)
Leia-se: "num é uma referência para um inteiro", ou seja, ele está referenciando, apontando, indicando um local da memória em que está armazenado um inteiro. Como você tem o local da variável original, você consegue mudar esse valor.

Exercício de passagem de valor e referência

Crie um programa que pede um inteiro ao usuário. Em seguida, ele deve transformar esse valor em seu cubo. Faça isso usando uma função que recebe passagem por valor e outra que usa parâmetro e variável de referência. Deixe bem claro que uma altera o valor somente dentro da função (valor) e a outra altera o valor original da variável (referência). 

Cole nos comentários, sua solução.

Argumentos Padrão e Omissão de Argumentos

Neste tutorial de C++, vamos aprender o que é um argumento padrão, para que serve e como usar, bem como omitir um argumento numa chamada de uma função.

Argumento Padrão em C++

Já aprendemos como enviar informações para funções, através do uso de parâmetros e argumentos.
Vamos supor que queiramos somar dois números, a e b, a função seria:
float sum2(float a, float b)
{
    return a+b;
}
Agora vamos supor que queiramos calcular a soma de três variáveis, teríamos que fazer uma função assim:
float sum3(float a, float b, float c)
{
    return a+b+c;
}
Note que teríamos que usar outra função, com outro nome, pro mesmo propósito: somar os argumentos. Não é algo muito inteligente, concorda?
Seria legal se uma mesma função somasse 2 ou 3 argumentos, o tanto que o usuário quiser.

Para somar dois números, seria interessante fazer: sum(1,2);
Para somar três números, faríamos: sum(1,2,3);

É aí que entra o conceito de argumento padrão.
Basta declararmos o protótipo da função assima:

  • float sum(float a, float b, float c = 0.0);

E seu escopo:
float sum(float a, float b, float c = 0.0)
{
    return a+b+c;
}
O que ocorre é o seguinte:
O valor padrão de c é 0.0

Caso você faça: sum(1,2, 3), o valor de c será 3.
Caso faça: suma (1,2), você não estará definindo valor para c, logo ele vai assumir o valor padrão, que é 0. Entendeu? Argumento com valor padrão, caso você não forneça esse valor.

Teste:
#include <iostream>
using namespace std;

float sum(float a, float b, float c = 0.0)
{
    return a+b+c;
}

int main()
{
    cout<<sum(1,2)<<endl;
    cout<<sum(1,2,3)<<endl;

    return 0;
}
Funciona pra 2 ou 3 variáveis, a gosto do freguês!

Omissão de argumentos

O código anterior funciona para somar 2 ou 3 números.
Para somarmos 2, 3 ou 4 números, poderíamos fazer:
float sum(float a, float b, float c = 0.0, float d = 0.0)
{
    return a+b+c+d;
}
Agora você pode fazer:
sum(1,2);
sum(1,2,3);
sum(1,2,3,4);

No primeiro caso, omitimos o argumento c e o d.
No segundo exemplo, omitimos o argumento d.
No último exemplo, não omitimos nenhum argumento.

Ou seja, argumentos padrões são automaticamente substituídos, quando informamos os argumentos.

Regras no uso de argumentos padrão

Quando informamos e passamos argumentos para uma função, eles são copiados da esquerda pra direita.

Por exemplo: sum(1,2,3)
O 1 vai pro 'a', o 2 pro 'b' e o valor 3 vai pro parâmetro 'c'. O valor do argumento 'd', então, é o argumento padrão, que definimos como 0.

Outra regra é que, uma vez que usemos um argumento padrão em um parâmetro, todos os outros parâmetros subsequentes deverão também ter argumentos padrão também. Por exemplo, o protótipo de função a seguir é válido:

  • float volume(float base, float height=1, float width=1);


'height' é um argumento padrão, e o seguinte também.

Já o seguinte protótipo de função é inválido:

  • float volume(float height=1, float base, float width=1);


Como o primeiro argumento é padrão, todos os outros devem ser, e o 'base' é um parâmetro normal, que deve ser necessariamente fornecido pela chamada da função.

Outro ponto é que os parâmetros com argumentos padrão devem ser declarados na primeira ocorrência da declaração da função. Ou seja, se você usar um protótipo de uma função e depois em outro lugar vai definir o escopo da sua função, os argumentos padrão devem ser definidos já no protótipo, que vai vir antes. Por exemplo, você pode inclusive abreviar assim:

Protótipo: double area( double = 1.0, float 2.0);
Definição da função: double area (double length, float witdh) { return length*width; }

Assim, recapitulando, seja a função:
double area (double length = 1.0, float witdh = 2.0) 
{ 
   return length*width; 
} 
Se fizermos as seguintes chamadas de função:

  1. area() - serão usados os valores 1.0 e 2.0 para calcular a área, respectivamente, ou seja, estamos usando os dois argumentos padrão.
  2. area(3) - serão usados os valores 3 e 2.0 para calcular a área, ou seja, o primeiro argumento padrão foi substituído por 3
  3. area(3,6) - serão usados os valores 3 e 6 para calcular a área, ou seja, os dois argumentos padrão foram substituídos.
Vamos ver isso na prática? Teste o seguinte código:

#include <iostream>
using namespace std;

void area(float = 1.0, float = 1.0);

int main()
{
    cout << "Nenhum argumento passado:"<<endl;
    area();

    cout << "\nPrimeiro argumento passado:"<<endl;
    area(2.0);

    cout << "\nAmbos argumentos passados:"<<endl;
    area(2.0, 3.0);

    return 0;
}

void area(float length, float width)
{
    cout << "Area: " << length * width << endl;
}

Deu tudo certo? Sim? Então vamos seguir em nossos tutoriais de C++.

Variável Local, Global, Constante Global e Variável estática

Neste tutorial de nossa apostila de C++, vamos aprender um pouco mais da relação entre funções e variáveis, conhecendo os tipos locais, global, constantes e estáticas.

Variável Local

Quando declaramos uma variável dentro de uma função, dizemos que ela é local.
Isso se deve ao fato dela 'existir' apenas pra quem está dentro da função, ou seja, só ali dentro podem enxergar ela e seu valor.

Outros comandos, em outras funções, não podem acessar ela normalmente.

Para ilustrar isso, vamos declarar a variável myVar com valor 1, dentro da main().
Em seguida, vamos chamar a função imprime(), que vai imprimir o valor de myVar:
#include <iostream>
using namespace std;

void imprime()
{
    cout<<myVar;
}

int main()
{
    int myVar=1;
    imprime();
    return 0;
}
Esse código nem compilado é, aparece o erro:
" ‘myVar’ was not declared in this scope|"

Ou seja, a variável myVar não está dentro do escopo da função imprime(), é como se ela não existisse.
Vale o contrário também:
#include <iostream>
using namespace std;

void imprime()
{
    int myVar=1;
}

int main()
{
    cout<<myVar;
    return 0;
}
Ou seja, nem mesmo a função main() consegue visualizar o conteúdo interno da função imprime().

Agora vamos declarar a myVar tanto dentro da imprime() quanto dentro da main(), e vamos imprimir os dois valores:
#include <iostream>
using namespace std;

void imprime()
{
    int myVar=1;
    cout << "Na imprime() = "<<myVar<<endl;
}

int main()
{
    int myVar=2;
    cout << "Na main() = "<<myVar<<endl;
    imprime();
    return 0;
}

O resultado é:
Curso de C++ online grátis

Ou seja, podemos declarar variáveis de nomes iguais, mas em funções diferentes.
E as funções só 'enxergam' aquilo que está dentro da função, ok?

Variável Global

Existe uma maneira de fazer com que uma mesma variável seja vista e acessada por diversas funções, basta fazê-la ser uma variável global.

Para isso, basta declarar ela fora do escopo das funções.
Por exemplo, vamos declarar a variável pi, e armazenar o valor do pi.

Em seguida, vamos pedir o valor do raio ao usuário, e através de outras duas funções, calculamos o perímetro e a área do círculo:
#include <iostream>
using namespace std;
float pi = 3.14;

float perimeter(float r)
{
    return 2*pi*r;
}

float area(float r)
{
    return pi*r*r;
}

int main()
{
    float rad;

    cout<<"Raio: ";
    cin>>rad;

    cout<<"Perímetro: " << perimeter(rad)<<endl;
    cout<<"Área     : " << area(rad)<<endl;

    return 0;
}
Veja que usamos a variável pi nas funções, sem ter declarado dentro do escopo delas.
Se fossemos usar variáveis locais, teríamos que declarar a pi mais de uma vez, o que não seria muito eficiente.

Use variáveis globais quando elas forem necessárias em vários trechos diferentes de seu código.
E você pode usar o mesmo nome para uma variável local e global. Nesse caso, a variável local vai ter prioridade.

Constantes Globais

Muitíssimo cuidado ao usar variáveis globais. Em programas complexos, é fácil 'perder o controle' de variáveis globais, visto que elas podem ser alteradas em qualquer lugar do código.

Na maioria dos casos, dê prioridade para o uso de argumentos mesmo.
Porém, caso queira usar variáveis globais que não devam ser alteradas, use a palavra-chave const antes declarar a variável:
  • const float pi = 3.14

Vamos supor que um hacker invadiu seu sistema, e inseriu uma função chamada muda(), que vai alterar o valor do pi para 4, sabotando seu projeto:
#include <iostream>
using namespace std;
const float pi = 3.14;

void muda()
{
    pi = 4;
}

int main()
{
    muda();

    return 0;
}
Como a variável foi declarada com a keyword const, vai dar o erro:
"|7|error: assignment of read-only variable ‘pi’| "

Ou seja, a variável é somente para leitura, não pode alterar o 'pi'. E, de fato, em nenhum programa se deve alterar o pi, logo, faz sentido ele ser global e constante, concorda?

Vamos supor que você vai criar um sistema para uma grande rede de supermercados, e vai definir o preço do desconto em 10%, faça:
  • const float desconto = 0.1;

Pronto. Agora, milhares de funções podem acessar o valor do desconto.
E caso você queira aumentar o desconto pra 20%?
Moleza, só fazer:
  • const float desconto = 0.2;

Veja que você alterou só uma coisinha, só uma variável. Mas, automaticamente, modificou diretamente os cálculos das milhares de funções que usam essa variável.

Altera só uma vez, e a alteração ocorre em vários cantos.
Se tivesse feito isso de maneira local, teria que ir em cada função e alterar variável por variável...ia levar horas ou dias.

Mas com variável global constante, não. Altera só uma vez. E tudo muda.
Sacou a utilidade de variáveis globais e constantes?

Variável estática local

Quando declaramos uma variável dentro de uma função, ela é criada e reservada na memória do computador no início da função e é destruída, ao término da execução da função.

No exemplo de código abaixo, inicializamos a variável myVar com valor 1, imprimimos, incrementamos em uma unidade e terminamos a função.
#include <iostream>
using namespace std;

void test()
{
    int myVar=1;

    cout<<myVar<<endl;

    myVar++;
}

int main()
{
    test();
    test();

    return 0;
}
Chamamos a função test() duas vezes, e o que aparece na tela é sempre o mesmo: o valor 1.
Cada vez que a função roda, a variável é criada, inicializada e o valor 1 é exibido. Ela é incrementada, mas depois a função termina e a variável morre, simplesmente.

Dizemos então que as variáveis locais são não-persistentes. Ou seja, elas não 'persistem', elas são criadas e destruídas, junto com a função.

Existe uma maneira de fazer com que a variável seja persistente, ou seja, que ela seja criada de início e não seja destruída, ou seja, seu endereço e valor na memória se mantém. São as variáveis estáticas.

Para declarar uma variável como sendo estática, basta usarmos a keyword static antes da declaração:

  • static int myVar;


Veja:
#include <iostream>
using namespace std;

void test()
{
    static int myVar=1;

    cout<<myVar<<endl;

    myVar++;
}

int main()
{
    test();
    test();
    test();
    test();

    return 0;
}
Pronto. Não importa quantas vezes você chama a função test(), a variável myVar que você vai usar é declarada e inicializada apenas uma vez. Quando a função terminar, ela ainda vai persistir e quando chamar novamente a função, ela já vai pegar o valor anterior da variável, pra imprimir e incrementar.

Assim como as variáveis globais, as estáticas locais sempre são inicializadas com valor 0, caso você não inicialize explicitamente. E caso inicialize, essa inicialização vai ocorrer somente uma vez, como você viu no exemplo de código anterior, ok?

Protótipo de Função - Como programar uma calculadora completa em C++

Neste tutorial de nosso curso de C++, vamos aprender como criar uma calculadora completa em C++, bem como entender o que são e para que servem os protótipos de funções.

Programando uma Calculadora em C++

Vamos agora programar uma calculadora, bem funcional e útil, usando os conhecimentos que aprendemos de C++:


As funções sum(), sub(), mult() e divis() fazem as operações de soma, subtração, multiplicação e divisão, respectivamente. Todas recebem dois dados, do tipo float, e retornam um resultado em float também.

Exceto pela função divis(), pois ela precisa testar se o denominador é diferente de 0.
Se for, retorna a divisão corretamente.
Se não for, retorna uma mensagem dizendo que não é possível dividir por 0, que é o correto a se fazer.

A função menu() é a responsável por mostrar as possíveis operações matemáticas que ele pode fazer.
O usuário digita um valor como opção.

Se essa opção for 0, o programa sai do looping DO WHILE da menu() e se encerra o programa.
Se digitar de qualquer outro número, pedimos os dois valores que ele vai querer calcular a operação e em seguida vai pro SWITCH, onde selecionamos a operação corretamente.

Passamos os números digitados pelo usuário pra respectiva função, ela retorna o resultado e o exibimos.

Por fim, o menu de opções é exibido novamente, para caso o usuário queira fazer outro cálculo.
Veja como fica nosso código:
#include <iostream>
using namespace std;

float sum(float a, float b)
{
    return a+b;
}

float sub(float a, float b)
{
    return a-b;
}

float mult(float a, float b)
{
    return a*b;
}

float divis(float a, float b)
{
    if(b!=0)
        return a/b;
    else
        cout<<"Não pode dividir por 0\n";
}

void menu()
{
    int op;
    float a, b;
    do{
        cout<<"0. Sair\n";
        cout<<"1. Somar\n";
        cout<<"2. Subtrair\n";
        cout<<"3. Multiplicar\n";
        cout<<"4. Dividir\n";
        cin >> op;

        if(op){
            cout<<"\nPrimeiro numero: ";
            cin >> a;

            cout<<"Segundo numero: ";
            cin >> b;

            switch(op){
                case 1:
                    cout<<"Soma: " << sum(a,b) << endl;
                    break;
                case 2:
                    cout<<"Diferença: " << sub(a,b) << endl;
                    break;
                case 3:
                    cout<<"Produto: " << mult(a,b) << endl;
                    break;
                case 4:
                    if(b)
                        cout<<"Divisão: " << divis(a,b) << endl;
                    else
                        divis(a,b);
                    break;
                default:
                    cout<<"Opção inválida\n";
            }
        }else
            cout<<"Saindo...\n";
        cout<<endl;
    }while(op);
}

int main()
{
    menu();
    return 0;
}
Na função main() nós simplesmente invocamos a função responsável por exibir o menu.
E só.

Veja, usamos funções, operações matemáticas, IF e ELSE, SWITCH e DO WHILE.
Basicamente, todo conhecimento que estudamos até aqui, em nosso curso de C++.

Agora faça um exercício.

Protótipos de funções em C++

Pegue a função main(), recorte ela do código e cole lá em cima, antes das funções das operações matemáticas. Agora rode seu código.

Deve dar algum erro do tipo 'sum() was not declared', dizendo que a função sum() não foi declarada.
Isso ocorre pois o compilador lê o código de cima pra baixo.

Quando ele entra na menu() e esta invoca a sum(), ele não sabe o que fazer, pois esta função ainda não foi declarada, ela está abaixo da menu(). Por isso, o correto é declarar primeiro as funções de operações matemáticas, e só depois invocar elas na menu().

Agora imagine num programa mais complexo e bem maior, como isso ficaria complicado.
Teríamos que ter cuidado com o que declarar antes de que, e o código antes da função principal main() ficaria gigantesco.

A solução pra isso é, lá em cima, declarar apenas os protótipos das funções.
O protótipo nada mais é que o cabeçalho da função, com os tipos de dados que recebe e o return correto, seguido de ponto-e-vírgula, sem o par de chaves e o código da função.

Veja agora como fica o código de nossa calculadora, usando os protótipos:
#include <iostream>
using namespace std;

void menu();
float sum(float a, float b);
float sub(float a, float b);
float mult(float a, float b);
float divis(float a, float b);

int main()
{
    menu();
    return 0;
}

void menu()
{
    int op;
    float a, b;
    do{
        cout<<"0. Sair\n";
        cout<<"1. Somar\n";
        cout<<"2. Subtrair\n";
        cout<<"3. Multiplicar\n";
        cout<<"4. Dividir\n";
        cin >> op;

        if(op){
            cout<<"\nPrimeiro numero: ";
            cin >> a;

            cout<<"Segundo numero: ";
            cin >> b;

            switch(op){
                case 1:
                    cout<<"Soma: " << sum(a,b) << endl;
                    break;
                case 2:
                    cout<<"Diferença: " << sub(a,b) << endl;
                    break;
                case 3:
                    cout<<"Produto: " << mult(a,b) << endl;
                    break;
                case 4:
                    if(b)
                        cout<<"Divisão: " << divis(a,b) << endl;
                    else
                        divis(a,b);
                    break;
                default:
                    cout<<"Opção inválida\n";
            }
        }else
            cout<<"Saindo...\n";
        cout<<endl;
    }while(op);
}

float sum(float a, float b)
{
    return a+b;
}

float sub(float a, float b)
{
    return a-b;
}

float mult(float a, float b)
{
    return a*b;
}

float divis(float a, float b)
{
    if(b!=0)
        return a/b;
    else
        cout<<"Não pode dividir por 0\n";
}
Bem mais bonito e organizado, não acha?
Aliás, o compilador só precisa saber o returno, o nome da função, quantos e que tipos de parâmetros a função tem, precisa nem do nome deles, você pode declarar assim também:
void menu();
float sum(float, float);
float sub(float, float);
float mult(float, float);
float divis(float, float);
Recomendamos sempre usar protótipos de funções, deixa seu código mais organizado e menos suscetível a erros e problemas