Menu

Parâmetros de função em C++: por valor, por referência e const

Como os argumentos chegam às funções em C++: passagem por valor x por referência, referências const para acesso barato somente leitura, argumentos padrão, ponteiros e as armadilhas de custo de cópia que deixam os programas lentos sem você perceber.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Parâmetros x argumentos

Os parâmetros de uma função são as variáveis nomeadas em sua definição; os argumentos são os valores reais que você passa quando a chama. A página anterior mostrou como definir e chamar funções; esta página é sobre como esses valores de fato entram, porque o C++ oferece várias formas e a escolha afeta tanto a correção quanto a velocidade.

O padrão em C++ é a passagem por valor: a função recebe uma cópia.

Dentro de addTen, n é uma variável separada inicializada a partir de score. Reatribuir n mexe apenas nessa cópia, então score permanece intacto de volta em main. Isso é seguro e previsível - a função não pode atropelar seus dados por acidente - e é exatamente por isso que é o padrão.

Passagem por referência: deixando uma função alterar quem a chama

Às vezes você quer que a função modifique a variável de quem a chama. Adicione um & ao tipo do parâmetro e ele se torna uma referência - um alias do original, não uma cópia:

A única diferença em relação ao primeiro exemplo é o &, mas agora n e score são o mesmo objeto. Essa é a forma padrão de "retornar" mais de um valor ou de atualizar algo no lugar. Um uso clássico é trocar duas variáveis:

Sem o &, swapValues embaralharia duas cópias e main não veria mudança alguma - um erro de iniciante muito comum.

Referências const: acesso barato e somente leitura

A passagem por valor copia o argumento. Para um int isso não custa nada, mas copiar um string ou vector grande a cada chamada é trabalho real e desperdiçado. A solução é uma referência const (const T&): você obtém a velocidade da referência (sem cópia) mais uma promessa garantida pelo compilador de não modificar o argumento.

Uma regra prática útil: passe tipos embutidos pequenos (int, double, char, bool, ponteiros) por valor, e passe objetos grandes que você só precisa ler por referência const. Reserve uma T& simples e não const para os casos em que você realmente pretende modificar o objeto de quem chama.

Uma armadilha sutil: uma int& n simples não pode ligar a um temporário ou a um literal. O addTen(5) do primeiro exemplo não compilaria se o parâmetro fosse int&, porque 5 não é uma variável da qual você possa criar um alias. Uma const int& pode ligar a 5, o que é mais uma razão pela qual as referências const são tão usadas.

Argumentos padrão

Você pode dar a um parâmetro um valor de reserva para que quem chama possa omiti-lo. Se o argumento estiver ausente, o valor padrão é usado:

Duas regras costumam pegar as pessoas. Primeira, os valores padrão devem ser finais - assim que um parâmetro tem um valor padrão, todo parâmetro depois dele também precisa ter. Você não pode escrever void f(int a = 1, int b) porque não haveria como fornecer b pulando a. Segunda, quando uma função é declarada em um cabeçalho e definida em outro lugar, coloque o valor padrão apenas na declaração, nunca o repita na definição - repeti-lo é um erro de compilação.

Passando arrays e vetores

Um array bruto decai para um ponteiro quando passado, então a função perde a noção do tamanho dele - você quase sempre passa o comprimento junto:

Como o array virou um ponteiro, sizeof(arr) dentro de sum daria o tamanho de um ponteiro, não do array - um bug notório. No C++ moderno, prefira um std::vector (ou um std::span no C++20), passado por referência const, que carrega o próprio tamanho:

Repare no const&: tire-o e cada chamada copia o vetor inteiro. Para um vetor de quatro elementos isso é inofensivo, mas para um milhão de elementos é um sumidouro silencioso de desempenho.

Parâmetros do tipo ponteiro

Você também pode passar um ponteiro (T*). Como uma referência, isso permite à função alcançar os dados de quem a chama, mas um ponteiro pode ser reapontado ou ser nulo - então é a ferramenta certa quando "nenhum valor" é uma opção legítima:

Quem chama passa &value para compartilhar seu endereço, e a função escreve através de *out. A diferença chave em relação às referências: um ponteiro pode ser nullptr, então uma função que recebe um deve verificar antes de desreferenciá-lo - pular essa proteção e desreferenciar um ponteiro nulo é comportamento indefinido, geralmente uma falha. Se "nenhum valor" nunca faz sentido, uma referência é mais limpa porque, para começar, ela não pode ser nula.

Próximo: Referências

Os parâmetros são onde as referências ganham seu valor, mas as referências são um recurso por si só - aliases que você pode criar para qualquer variável, não apenas dentro da assinatura de uma função. A próxima página aprofunda como as referências funcionam por conta própria: como declará-las, por que devem ser inicializadas imediatamente, a diferença entre uma referência lvalue e uma referência const, e as formas sutis pelas quais uma referência pode acabar pendente (dangling).

Perguntas frequentes

Qual é a diferença entre passagem por valor e por referência em C++?

A passagem por valor copia o argumento para o parâmetro, então alterações dentro da função não afetam quem a chamou. A passagem por referência (int&) faz o parâmetro ser um alias da variável de quem chama, então as alterações são vistas de fora. Use void f(int x) para copiar e void f(int& x) para modificar o original.

Quando você deve usar um parâmetro de referência const em C++?

Use const T& quando quiser ler um objeto grande sem copiá-lo e sem deixar a função modificá-lo - por exemplo void print(const string& s). Isso dá a você a velocidade da passagem por referência com a segurança da passagem por valor. Para tipos pequenos como int ou char, a passagem por valor simples é igualmente rápida.

O que são argumentos padrão em C++?

Argumentos padrão permitem que um parâmetro assuma um valor de reserva quando quem chama o omite, por exemplo void greet(string name = "there"). Os valores padrão devem ser os parâmetros finais (mais à direita), e você os especifica apenas na declaração, não na definição se as duas forem separadas.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR