Share via


Funções (C++)

Uma função é um bloco de código que executa alguma operação. Opcionalmente, uma função pode definir parâmetros de entrada que permitem que os chamadores passem argumentos para a função. Uma função também pode retornar um valor como saída. As funções são úteis para encapsular operações comuns em um só bloco reutilizável, idealmente com um nome que descreve de modo claro o que a função faz. A função a seguir aceita dois inteiros de um chamador e retorna sua soma; a e b são parâmetros do tipo int.

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

A função pode ser invocada ou chamada de qualquer número de locais no programa. Os valores que são passados para a função são os argumentos, cujos tipos devem ser compatíveis com os tipos de parâmetro na definição de função.

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; // 108
}

Não há um limite prático para o comprimento da função, mas um bom design visa funções que executam uma única tarefa bem definida. Algoritmos complexos devem ser divididos em funções mais simples de fácil compreensão sempre que possível.

As funções definidas no escopo da classe são chamadas de funções de membro. Em C++, ao contrário de outras linguagens, uma função também pode ser definida no escopo do namespace (incluindo o namespace global implícito). Tais funções são chamadas de funções livres ou funções não-membros, elas são usadas extensivamente na Biblioteca Padrão.

As funções podem estar sobrecarregadas, o que significa que diferentes versões de uma função podem compartilhar o mesmo nome se forem diferentes pelo número e/ou tipo de parâmetros formais. Para obter mais informações, consulte Sobrecarga de funções.

Partes de uma declaração de função

Uma declaração de função mínima consiste no tipo de retorno, nome da função e lista de parâmetros (que pode estar vazia), juntamente com palavras-chave opcionais que fornecem mais instruções ao compilador. O seguinte exemplo é uma declaração de função:

int sum(int a, int b);

Uma definição de função consiste em uma declaração, além do corpo, que é todo o código entre as chaves:

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

Uma declaração de função seguida de ponto e vírgula pode aparecer em vários locais em um programa. Ela deve aparecer antes de qualquer chamada para essa função em cada unidade de tradução. A definição de função deve aparecer apenas uma vez no programa, de acordo com a ODR (Regra de Definição de Um).

As partes necessárias de uma declaração de função são:

  1. O tipo de retorno, que especifica o tipo do valor que a função retorna ou void se nenhum valor é retornado. No C++11, auto é um tipo de retorno válido que instrui o compilador a inferir o tipo da instrução return. No C++14, decltype(auto) também é permitido. Para mais informações, confira Dedução de tipo em tipos de retorno abaixo.

  2. O nome da função, que deve começar com uma letra ou sublinhado e não pode conter espaços. Em geral, os sublinhados à esquerda nos nomes de função da Biblioteca Padrão indicam funções de membro privado ou funções de não membro que não se destinam ao uso pelo seu código.

  3. A lista de parâmetros, um conjunto delimitado por chaves, separado por vírgula de zero ou mais parâmetros que especificam o tipo e, opcionalmente, um nome local pelo qual os valores podem ser acessados dentro do corpo da função.

Partes opcionais de uma declaração de função são:

  1. constexpr, que indica que o valor retornado da função é um valor constante pode ser calculado em tempo de compilação.

    constexpr float exp(float x, int n)
    {
        return n == 0 ? 1 :
            n % 2 == 0 ? exp(x * x, n / 2) :
            exp(x * x, (n - 1) / 2) * x;
    };
    
  2. Sua especificação de vínculo, extern ou static.

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    Para mais informações, confira Unidades de tradução e vínculo.

  3. inline, que instrui o compilador a substituir cada chamada à função pelo próprio código de função. Embutir pode ajudar o desempenho em cenários em que uma função é executada com rapidez e é invocada repetidamente em uma seção crítica de desempenho do código.

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    Para mais informações, confira Embutir funções.

  4. Uma expressão noexcept, que especifica se a função pode ou não gerar uma exceção. No exemplo a seguir, a função não lançará uma exceção se a is_pod expressão for avaliada como true.

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    Para obter mais informações, consulte noexcept.

  5. (Somente funções de membro) Os qualificadores cv, que especificam se a função é const ou volatile.

  6. (Somente funções de membro) virtual, override ou final. virtual especifica que uma função pode ser substituída em uma classe derivada. override significa que uma função em uma classe derivada está substituindo uma função virtual. final significa que uma função não pode ser substituída em nenhuma outra classe derivada. Para mais informações, confira Funções virtuais.

  7. (somente funções de membro) static Aplicada a uma função de membro significa que a função não está associada a nenhuma instância de objeto da classe.

  8. (Somente funções de membro não estático) O qualificador de ref, que especifica para o compilador qual sobrecarga de uma função escolher quando o parâmetro de objeto implícito (*this) é uma referência rvalue versus uma referência lvalue. Para obter mais informações, consulte Sobrecarga de funções.

Definições de função

Uma definição de função consiste na declaração e no corpo da função, entre chaves, que contém declarações, instruções e expressões variáveis. O seguinte exemplo mostra uma definição de função completa:

    int foo(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       if(strcmp(s, "default") != 0)
       {
            value = mc.do_something(i);
       }
       return value;
    }

As variáveis declaradas dentro do corpo são chamadas de variáveis locais ou locais. Elas saem do escopo quando a função é encerrada; portanto, uma função nunca deve retornar uma referência a um local.

    MyClass& boom(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       mc.Initialize(i,s);
       return mc;
    }

Funções const e constexpr

Você pode declarar uma função de membro para const especificar que a função não tem permissão para alterar os valores de nenhum membro de dados na classe. Declarando uma função de membro como const, você ajuda o compilador a impor correção de const. Se alguém tentar modificar o objeto por engano usando uma função declarada como const, um erro do compilador será gerado. Para mais informações, confira const.

Declarar uma função como constexpr quando o valor que ela produz pode ser determinado em tempo de compilação. Uma função constexpr geralmente é executada mais rápido do que uma função regular. Para obter mais informações, consulte constexpr.

Modelos de função

Um modelo de função é semelhante a um modelo de classe; gera funções concretas com base nos argumentos de modelo. Em muitos casos, o modelo é capaz de inferir os argumentos de tipo, portanto, não é necessário especificá-los explicitamente.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string

Para mais informações, confira Modelos de Função

Parâmetros e argumentos de função

Uma função tem uma lista de parâmetros separada por vírgulas de zero ou mais tipos, cada um dos quais tem um nome pelo qual pode ser acessado dentro do corpo da função. Um modelo de função pode especificar mais parâmetros de tipo ou valor. O chamador passa argumentos, que são valores concretos cujos tipos são compatíveis com a lista de parâmetros.

Por padrão, os argumentos são passados para a função por valor, o que significa que a função recebe uma cópia do objeto que está sendo passado. Para objetos grandes, fazer uma cópia pode ser caro e nem sempre é necessário. Para passar os argumentos por referência (especificamente, referência lvalue), adicione um quantificador de referência ao parâmetro:

void DoSomething(std::string& input){...}

Quando uma função modifica um argumento passado por referência, ela modifica o objeto original, não uma cópia local. Para impedir que uma função modifique tal argumento, qualifique o parâmetro como const&:

void DoSomething(const std::string& input){...}

C++11: Para manipular explicitamente argumentos que são passados por rvalue-reference ou lvalue-reference, use um double-ampersand no parâmetro para indicar uma referência universal:

void DoSomething(const std::string&& input){...}

Uma função declarada com a única palavra-chave void na lista de declarações de parâmetro não usa nenhum argumento, desde que a palavra-chave void seja o primeiro e único membro da lista de declarações de argumentos. Os argumentos do tipo void em qualquer outro lugar da lista gera erros. Por exemplo:

// OK same as GetTickCount()
long GetTickCount( void );

Embora seja ilegal especificar um void argumento, exceto conforme descrito aqui, os tipos derivados do tipo void (como ponteiros para void e matrizes de ) podem aparecer em qualquer lugar da lista de declaração de voidargumento.

Argumentos padrão

O último parâmetro ou parâmetros em uma assinatura de função pode ser atribuído a um argumento padrão, o que significa que o chamador pode deixar de fora o argumento ao chamar a função, a menos que deseje especificar algum outro valor.

int DoSomething(int num,
    string str,
    Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
    string str = string{ "Working" },
    Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
    string str,
    Allocator& = defaultAllocator)
{...}

Para mais informações, confira Argumentos padrão.

Tipos de retorno de função

Uma função pode não retornar outra função ou uma matriz interna; no entanto, ela pode retornar ponteiros para esses tipos, ou um lambda, que produz um objeto de função. Exceto nesses casos, uma função pode retornar um valor de qualquer tipo que esteja no escopo ou não retornar nenhum valor, nesse caso, o tipo de retorno é void.

Tipo de retorno à direita

Um tipo de retorno "comum" está localizado no lado esquerdo da assinatura de função. Um tipo de retorno à direita está localizado no lado mais à direita da assinatura e é precedido pelo -> operador. Tipos de retorno à direita são especialmente úteis em modelos de função quando o tipo do valor retornado depende de parâmetros de modelo.

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

Quando auto é usado em conjunto com um tipo de retorno à direita, ele serve apenas como um espaço reservado para o que a expressão decltype produz, e não executa a dedução de tipo.

Variáveis locais de função

Uma variável declarada dentro de um corpo de função é chamada de variável local ou simplesmente de local. Os locais não estáticos só são visíveis dentro do corpo da função e, se forem declarados na pilha, saem do escopo quando a função é encerrada. Quando você constrói uma variável local e a retorna por valor, o compilador geralmente pode executar a otimização de valor retornado nomeada para evitar operações de cópia desnecessárias. Se você retornar uma variável local por referência, o compilador emitirá um aviso porque qualquer tentativa do chamador de usar essa referência ocorrerá depois que o local tiver sido destruído.

No C++, uma variável local pode ser declarada como estática. A variável só é visível dentro do corpo da função, mas existe uma só cópia da variável para todas as instâncias da função. Os objetos estáticos locais são destruídos durante o término especificado por atexit. Se um objeto estático não foi construído porque o fluxo de controle do programa ignorou sua declaração, nenhuma tentativa será feita para destruir esse objeto.

Dedução de tipo em tipos de retorno (C++14)

No C++14, você pode usar auto para instruir o compilador a inferir o tipo de retorno do corpo da função sem precisar fornecer um tipo de retorno à direita. Observe que auto sempre deduz a um retorno por valor. Use auto&& para instruir o compilador a deduzir uma referência.

Neste exemplo, auto será deduzido como uma cópia de valor não const da soma de lhs e rhs.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs; //returns a non-const object by value
}

Note que isso não preserva a const-ness do tipo que auto deduz. Para encaminhar funções cujo valor retornado precisa preservar o caráter const ou ref de seus argumentos, use a palavra-chave decltype(auto), que usa as regras de inferência de tipos decltype e preserva todas as informações de tipos. decltype(auto) pode ser usado como um valor retornado comum no lado esquerdo ou como um valor retornado à direita.

O exemplo a seguir (com base no código de N3493), mostra decltype(auto) sendo usado para habilitar o encaminhamento perfeito de argumentos de função em um tipo de retorno que não é conhecido até que o modelo seja instanciado.

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
    typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
   decltype( auto)
    apply(F&& f, Tuple&& args)
{
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

Como retornar diversos valores de uma função

Há várias maneiras de retornar mais de um valor de uma função:

  1. Encapsular os valores em uma classe nomeada ou objeto struct. Exige que a definição de classe ou struct seja visível para o chamador:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        S s = g();
        cout << s.name << " " << s.num << endl;
        return 0;
    }
    
  2. Retornar um objeto std::tuple ou std::pair:

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    
    int main()
    {
        auto t = f();
        cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
    
        // --or--
    
        int myval;
        string myname;
        double mydecimal;
        tie(myval, myname, mydecimal) = f();
        cout << myval << " " << myname << " " << mydecimal << endl;
    
        return 0;
    }
    
  3. Visual Studio 2017 versão 15.3 e posterior (disponível no modo /std:c++17 e posteriores): use associações estruturadas. A vantagem das associações estruturadas é que as variáveis que armazenam os valores de retorno são inicializadas ao mesmo tempo em que são declaradas, o que, em alguns casos, pode ser significativamente mais eficiente. Na instrução auto[x, y, z] = f();, os colchetes introduzem e inicializam nomes que estão no escopo de todo o bloco de funções.

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        auto[x, y, z] = f(); // init from tuple
        cout << x << " " << y << " " << z << endl;
    
        auto[a, b] = g(); // init from POD struct
        cout << a << " " << b << endl;
        return 0;
    }
    
  4. Além de usar o valor retornado em si, você pode "retornar" valores definindo qualquer número de parâmetros para usar a opção de passar por referência para que a função possa modificar ou inicializar os valores dos objetos fornecidos pelo chamador. Para mais informações, confira Argumentos de função de tipo de referência.

Ponteiros de função

O C++ dá suporte a ponteiros de função da mesma maneira que a linguagem C. No entanto, uma alternativa mais fortemente tipada geralmente é usar um objeto de função.

É recomendável que você use typedef para declarar um alias para o tipo de ponteiro de função se declarar uma função que retorna um tipo de ponteiro de função. Por exemplo

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

Se isso não for feito, a sintaxe apropriada para a declaração de função pode ser deduzida da sintaxe do declarador para o ponteiro de função, substituindo o identificador (fp no exemplo acima) pelo nome das funções e pela lista de argumentos, da seguinte maneira:

int (*myFunction(char* s))(int);

A declaração anterior é equivalente à declaração usada typedef anteriormente.

Confira também

Sobrecarga de função
Funções com listas de argumentos variáveis
Funções explicitamente usadas como padrão e excluídas
Pesquisa de nome dependente de argumento (Koenig) em funções
Argumentos padrão
Funções Embutidas