2. Funções: valor, ponteiro e referência¶
Este é o capítulo mais importante da Unidade 1. Em C você já domina duas formas de passar parâmetro: por valor e por ponteiro. O C++ acrescenta uma terceira — referência — que resolve o motivo mais comum pelo qual você usava ponteiro em C: "quero que a função altere a variável do chamador" ou "quero evitar copiar um valor grande, como um texto".
Por valor: o padrão que você já conhece¶
void dobra(int x) {
x = x * 2; // altera so a copia local
}
int main() {
int n = 5;
dobra(n);
// n continua 5 aqui
}
Igualzinho a C: x é uma cópia. Alterar x dentro da função não afeta n no chamador.
Por ponteiro: como você resolvia isso em C¶
void dobra_ponteiro(int* x) {
*x = *x * 2;
}
int main() {
int n = 5;
dobra_ponteiro(&n);
// agora n e 10
}
Funciona, mas tem um custo de legibilidade: no ponto de chamada (dobra_ponteiro(&n)) fica visível que é passagem por endereço, mas dentro da função você precisa desreferenciar (*x) toda hora, e o ponteiro pode ser nulo — a função precisaria decidir o que fazer com dobra_ponteiro(nullptr).
Por referência: a novidade do C++¶
Uma referência é um "apelido" para uma variável já existente. Sintaxe: & no tipo do parâmetro, não na chamada.
void dobra_referencia(int& x) {
x = x * 2; // x AQUI e a variavel do chamador, nao uma copia
}
int main() {
int n = 5;
dobra_referencia(n); // sem & na chamada
// agora n e 10
}
Compare as três chamadas:
dobra(n); // por valor: n nao muda
dobra_ponteiro(&n); // por ponteiro: precisa de &, dentro usa *
dobra_referencia(n); // por referencia: chamada limpa, sem * dentro
Diferenças que importam na prática:
- Referência não pode ser nula. Uma vez ligada a uma variável,
int& x = n;sempre aponta pran— não existe "referência vazia" (ao contrário de ponteiro, que pode sernullptr). Isso elimina uma categoria inteira de bug. - Referência não pode ser "religada". Depois de inicializada,
xcontinua sendo um apelido para a mesma variável. - Por isso, sempre que a intenção é "alterar o valor do chamador, e ele com certeza vai existir", referência é a ferramenta certa. Ponteiro continua fazendo sentido quando o "nada" é uma resposta válida (por exemplo, "procure X e retorne o endereço, ou nulo se não achar" — você vai ver uma alternativa mais moderna para esse caso, o padrão do capítulo 3:
boolde retorno e o resultado por referência).
const T&: a combinação que você vai usar o tempo todo¶
Agora o motivo pelo qual referência importa mesmo quando você não quer alterar nada.
Passar um std::string (ou, mais adiante, um std::vector) por valor faz uma cópia inteira toda vez que a função é chamada:
double calcula_total(std::string produto, double preco) {
// "produto" foi copiado inteiro so para a funcao ler o valor
return preco * 1.1;
}
Passar por referência evita a cópia — mas uma referência comum (std::string&) permite que a função altere o argumento, o que aqui não é a intenção. A solução é referência constante:
double calcula_total(const std::string& produto, double preco) {
// sem copia, e o compilador GARANTE que "produto" nao e alterado aqui
return preco * 1.1;
}
const T& virou o padrão em C++ para "quero ler um valor potencialmente grande sem copiar e sem correr risco de alterá-lo sem querer". Você já viu isso nos exercícios da Lista 01 (const std::string& nome) — agora sabe por quê. No capítulo 3 você vai criar seus próprios tipos com struct, e a mesma regra vale para eles: const MinhaStruct& é o padrão para recebê-los.
Regra prática para decidir a assinatura de um parâmetro:
| Situação | Assinatura |
|---|---|
Tipo pequeno (int, double, char, bool), só leitura |
por valor: int n |
| Tipo qualquer, só leitura, quer evitar cópia | const T& |
| Precisa alterar o argumento do chamador | T& |
| "Pode não existir" | bool de retorno + parâmetro por referência (capítulo 3) |
Retorno por referência (cuidado)¶
Você pode retornar uma referência, mas nunca para uma variável local — ela deixa de existir quando a função termina:
int& perigoso() {
int local = 10;
return local; // ERRO logico: referencia para memoria que nao existe mais
}
O compilador frequentemente avisa (warning: reference to local variable returned). Leve esse warning a sério — é um dos poucos casos em que C++ tenta te salvar de um bug sério e você não deve ignorar.
Sobrecarga de funções (overload)¶
Em C, cada função precisa de um nome único — por isso bibliotecas C têm abs, labs, fabs para int, long, double. C++ permite várias funções com o mesmo nome, desde que os parâmetros sejam diferentes:
int soma(int a, int b) { return a + b; }
double soma(double a, double b) { return a + b; }
std::string soma(const std::string& a, const std::string& b) { return a + b; }
O compilador escolhe a versão certa pelo tipo dos argumentos na chamada:
soma(2, 3); // chama a versao int
soma(2.5, 1.5); // chama a versao double
soma("a", "b"); // erro: const char* nao converte direto para a versao string sem ambiguidade em alguns casos — prefira soma(std::string("a"), "b")
Argumentos padrão (default arguments)¶
Um parâmetro pode ter um valor usado quando o chamador não o fornece:
double calcula_total(double preco, double taxa = 1.1) {
return preco * taxa;
}
calcula_total(100.0); // usa taxa = 1.1
calcula_total(100.0, 1.05); // usa taxa = 1.05
Argumentos com valor padrão precisam vir depois dos que não têm (senão o compilador não sabe qual argumento você está pulando).
Erros comuns¶
"error: invalid initialization of non-const reference from an rvalue" — você tentou passar um valor temporário (ex.: 2 + 3) para um parâmetro int& (referência não-const). Referência não-const precisa de uma variável de verdade para se ligar; use const int& se só for ler.
"error: call of overloaded ‘soma(const char [2], const char [2])’ is ambiguous" — literais de string ("a") são const char* em C++, não std::string. Quando isso causa ambiguidade entre sobrecargas, converta explicitamente: soma(std::string("a"), "b").
warning "reference to local variable ‘x’ returned" — veja a seção acima. Não ignore.
Resumo¶
- Por valor: cópia, seguro, mas caro para tipos grandes.
- Por ponteiro: como em C, permite alterar o chamador, mas pode ser nulo e exige
*/&manuais. - Por referência (
T&): "apelido" da variável do chamador, nunca nula, sem*no corpo da função. const T&: o padrão para parâmetros grandes que só serão lidos — sem cópia, sem risco de alteração.- Nunca retorne referência para variável local.
- Sobrecarga: mesmo nome, parâmetros diferentes, o compilador escolhe pela chamada.
- Argumento padrão: valor usado quando o chamador omite o argumento.