Nesse tipo de passagem são feitas cópias de valores fornecidos para os parâmetros do procedimento.

Nesse tipo de passagem são feitas cópias de valores fornecidos para os parâmetros do procedimento.

Nesta aula vamos aprender sobre passagem de parâmetros por valor e por referência em portugol.

Até o momento, toda vez que passamos algum parâmetro para nossas funções e procedimentos, fizemos uma passagem de parâmetro por valor. Isso significa que o computador literalmente faz uma cópia da variável passada como parâmetro.

Contudo, há outra forma bem interessante conhecida como passagem de parâmetro por referência, que iremos aprender nesta aula.

Passando uma variável como parâmetro por referência, o computador cria uma referência para a variável original, uma espécie de ponteiro. Dessa forma, qualquer alteração feita dentro da função / procedimento, irá alterar também a variável original.

No código a seguir observe o procedimento imprimir. Ele recebe dois parâmetros, um inteiro i e um caracter l. Perceba que à esquerda de l há um e comercial (&). Este & comercial indica que será feito aí uma passagem de parâmetro por referência, ou seja, não será feito uma cópia da variável original, mas uma referencia para ela. Qualquer alteração na variável l irá alterar também nossa variável original.

A variável i, por outro lado, não possui o & à esquerda. Isso significa que temos uma passagem de parâmetro por valor, ou seja, será feita uma cópia da variável original e, qualquer alteração feita, será feita nesta cópia, não alterando nossa variável original.

programa{

/*
	         Passagem de parâmetros por VALOR e por REFERÊNCIA

                 Código escrito por Wagner Gaspar
                 Abril de 2021
*/

	funcao imprimir(inteiro i, caracter &l){
		escreva(i, "\t", l, "\n")
		i = 50 // altera a variável i, que é uma cópia de idade
		l = 'W' // altera a variável letra, pois l é uma referência para letra
	}
	
	funcao inicio()
{

		inteiro idade = 35
		caracter letra = 'A'

		imprimir(idade, letra)

		escreva("Idade: ", idade, "\n") // idade não foi alterada
		escreva("letra: ", letra, "\n") // letra foi alterada
		
	}
}

Wagner Gaspar

Capixaba de São Gabriel da Palha, Espírito Santo. Bacharel em Ciência da Computação pela Universidade Federal do Amazonas e mestre em informática pela Universidade Federal do Espírito Santo.

Avançar para o conteúdo principal

Não há mais suporte para esse navegador.

Atualize o Microsoft Edge para aproveitar os recursos, o suporte técnico e as atualizações de segurança mais recentes.

Funções (C++)

  • Artigo
  • 09/29/2022
  • 14 minutos para o fim da leitura

Neste artigo

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á limite prático para o comprimento da função, mas um bom design visa funções que executam uma só 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). Essas funções são chamadas de funções livres ou funções não de membro; 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 mais informações, confira 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, no nome da função e na lista de parâmetros (que pode estar vazia), além de palavras-chave opcionais que dão 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 à direita nos nomes das funções da Biblioteca Padrão indicam funções de membro privado ou funções não membros que não se destinam ao uso pelo 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 gerará uma exceção se a expressão is_pod 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 classe derivada adicional. Para mais informações, confira Funções virtuais.

  7. (somente funções de membro) static aplicado a uma função 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 mais informações, confira Sobrecarga de funções.

A figura a seguir mostra as partes de uma definição de função. A área sombreada é o corpo da função.

Nesse tipo de passagem são feitas cópias de valores fornecidos para os parâmetros do procedimento.

Partes de uma definição de função

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 const para 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 parâmetros de tipo ou valor adicionais. 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 esse argumento, qualifique o parâmetro como const&:

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

C++ 11: para lidar explicitamente com argumentos que são passados por rvalue-reference ou lvalue-reference, use um e comercial duplo 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 ilícito especificar um argumento void, exceto conforme descrito aqui, os tipos derivados do tipo void (como ponteiros para void e matrizes de void) podem aparecer em qualquer lugar na lista de declarações de argumentos.

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 direito 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 quer que a expressão decltype produza e, por si só, 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. Locais não estáticos só ficam visíveis dentro do corpo da função e, se forem declarados na pilha, sairão do escopo quando a função for 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 programa do controle ignorou a declaração dele, nenhuma tentativa de destruição de objeto será feita.

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
}

Observe que auto não preserva o carácter const do tipo que ele 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 retornados são inicializadas ao mesmo tempo em que são declaradas, o que, em alguns casos, pode ser muito 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 typedef seja usado para declarar um alias para o tipo de ponteiro de função se for 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 adequada 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 a lista de argumentos, como segue:

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

A declaração anterior é equivalente à declaração que usa typedef acima.

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

Quais são os tipos de passagem de parâmetros utilizados?

A passagem de parâmetro pode ser feita de duas formas: Passagem de Parâmetro por Valor e Passagem de Parâmetro por Referência.

O que é passagem de parâmetros por valor e por referência?

Outro tipo de passagem de parâmetros para uma função ocorre quando alterações nos parâmetros formais, dentro da função, alteram os valores dos parâmetros que foram passados para a função. Este tipo de chamada de função tem o nome de "chamada por referência".

O que é passagem de parâmetros e quando ela acontece?

Até o momento, toda vez que passamos algum parâmetro para nossas funções e procedimentos, fizemos uma passagem de parâmetro por valor. Isso significa que o computador literalmente faz uma cópia da variável passada como parâmetro.

O que é passagem por valor?

Existem dois métodos de passagem de parâmetros para funções: Passagem por valor – permite usar dentro de uma função uma cópia do valor de uma variável, porém não permite alterar o valor da variável original (somente a cópia pode ser alterada).