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.