O conceito mais crítico de C — e por que usamos LISTA* em vez de LISTA em todas as funções
Esta aula aprofunda um conceito já introduzido na Aula 14. Antes de estudar ponteiros com structs e listas, é importante fixar a terminologia padrão da literatura — você a encontrará em qualquer livro de Estruturas de Dados ou Sistemas Operacionais.
& na chamada e * no parâmetro.
Passagem por valor copia o argumento inteiro. Um int ocupa 4 bytes — cópia barata. Uma LISTA com 50 REGISTROs pode ocupar centenas de bytes — cópia cara. Mas o problema real não é desempenho: é que a função não consegue modificar a lista original se receber uma cópia.
/* ❌ CALL BY VALUE — recebe cópia da struct */ void inicializar_errado(LISTA l) { l.nroElem = 0; // zera só a cópia local } // a lista original continua com lixo /* ✅ CALL BY REFERENCE — recebe endereço da struct */ void inicializar_correto(LISTA *l) { l->nroElem = 0; // zera através do endereço } // a lista original é modificada int main() { LISTA lista; inicializar_errado(lista); // lista.nroElem ainda contém lixo inicializar_correto(&lista); // lista.nroElem agora é 0 return 0; }
| Call by Value | Call by Reference (ponteiro) | |
|---|---|---|
| O que é passado | Cópia do valor/struct | Endereço de memória |
| Pode modificar o original? | ❌ Não | ✅ Sim |
| Custo de memória | Copia tudo (caro para structs grandes) | Copia apenas o endereço (8 bytes) |
| Sintaxe na chamada | f(lista) |
f(&lista) |
| Sintaxe no parâmetro | void f(LISTA l) |
void f(LISTA *l) |
| Acessa campos com | l.campo |
l->campo (equivale a (*l).campo) |
LISTA *l). Se a função só precisa ler — use ponteiro com const (const LISTA *l) para eficiência sem risco de modificação. Nunca passe structs grandes por valor.
Imagine que você quer escrever uma função inicializar que zera o contador de elementos da lista. Observe a armadilha:
// ❌ VERSÃO ERRADA — compila, mas não inicializa a lista original void inicializarLista(LISTA l) { l.nroElem = 0; // zera uma CÓPIA local, não a original } int main() { LISTA lista; inicializarLista(lista); printf("%d\n", lista.nroElem); // imprime lixo — não foi inicializada! return 0; }
LISTA l para uma função, o C copia a struct inteira para um espaço novo na memória. A função trabalha com a cópia — a original permanece intocada.
Um ponteiro é uma variável que armazena o endereço de memória de outra variável — em vez de armazenar o valor em si.
int x = 42; // variável comum: armazena o valor 42 int *p = &x; // ponteiro: armazena o ENDEREÇO de x // &x significa "endereço de x" printf("%d\n", x); // imprime 42 (o valor) printf("%p\n", p); // imprime algo como 0x7ffd2a1b (o endereço) printf("%d\n", *p); // imprime 42 (o valor no endereço — "desreferência") *p = 100; // modifica x através do ponteiro printf("%d\n", x); // agora imprime 100!
| Operador | Nome | O que faz |
|---|---|---|
& | Endereço-de | Obtém o endereço de uma variável |
* (na declaração) | Declaração de ponteiro | Indica que a variável é um ponteiro |
* (no uso) | Desreferência | Acessa o valor no endereço apontado |
-> | Seta | Acessa campo de uma struct via ponteiro (equivale a (*p).campo) |
Para que uma função possa modificar a variável original, passamos o endereço da variável — não a variável em si.
#include <stdio.h> // ✅ VERSÃO CORRETA — recebe o endereço da lista void inicializarLista(LISTA *l) { l->nroElem = 0; // l-> acessa o campo do original através do ponteiro } int main() { LISTA lista; inicializarLista(&lista); // passa o ENDEREÇO de lista printf("%d\n", lista.nroElem); // imprime 0 — inicializado corretamente! return 0; }
->: Quando temos um ponteiro para struct, usar (*l).nroElem é válido mas verboso. O operador -> é um atalho: l->nroElem significa exatamente o mesmo — "acesse o campo nroElem da struct apontada por l".
| Endereço | Variável | Valor |
|---|---|---|
0x1000 |
lista (em main) |
nroElem = ? (lixo antes) |
0x2000 |
l (em inicializarLista) |
0x1000 ← armazena o endereço de lista |
l->nroElem = 0 escreve em 0x1000, alterando lista.nroElem diretamente |
||
0x1000 |
lista (em main) |
nroElem = 0 ✅ |
LISTA* em Todas as Funções da Lista?Agora que você entende passagem por referência, fica claro por que todas as funções da Lista Linear Sequencial recebem LISTA *l:
| Função | Por que precisa de LISTA* |
|---|---|
inicializarLista(LISTA *l) |
Modifica l->nroElem |
inserirElemLista(LISTA *l, ...) |
Modifica l->A[] e incrementa l->nroElem |
excluirElemLista(LISTA *l, ...) |
Modifica l->A[] e decrementa l->nroElem |
exibirLista(LISTA *l) |
Só lê — mas ponteiro evita copiar 200+ bytes desnecessariamente |
tamanho(LISTA *l) |
Só lê — mesma razão: eficiência |
buscaSequencial(LISTA *l, ...) |
Usa const LISTA* — lê sem modificar, mas sem copiar |
const LISTA *l: assim o compilador impede modificações acidentais, mas a eficiência de não copiar é mantida. Quando a aula de estruturas de dados apresentar o código completo, você verá esse padrão em ação.
.) vs. Seta (->)Um erro muito comum ao começar com structs e ponteiros é confundir os dois operadores de acesso:
| Situação | Operador | Exemplo |
|---|---|---|
| Variável é a própria struct | . (ponto) |
lista.nroElem |
| Variável é um ponteiro para struct | -> (seta) |
l->nroElem |
LISTA lista; // variável struct LISTA *p = &lista; // ponteiro para struct // As quatro formas abaixo são equivalentes: lista.nroElem = 0; // ✅ ponto — acesso direto (*p).nroElem = 0; // ✅ desreferência + ponto (verboso) p->nroElem = 0; // ✅ seta — atalho para (*p).nroElem // ❌ Erros comuns: // p.nroElem = 0; — p é ponteiro, não struct — use -> // lista->nroElem; — lista é struct, não ponteiro — use .
* na declaração, use ->. Se não tem *, use ..
Um exemplo autossuficiente que demonstra tudo que vimos: struct, ponteiro para struct, passagem por referência, operador seta e eficiência de memória.
#include <stdio.h>
#include <string.h>
typedef struct {
int matricula;
char nome[50];
float nota;
} Aluno;
/* Inicializa via ponteiro — modifica o original */
void inicializarAluno(Aluno *a, int mat, const char *nome, float nota) {
a->matricula = mat;
strncpy(a->nome, nome, 49);
a->nota = nota;
}
/* Só lê — const protege de modificação acidental */
void exibirAluno(const Aluno *a) {
printf("Mat: %d | Nome: %-20s | Nota: %.1f | %s\n",
a->matricula, a->nome, a->nota,
a->nota >= 5.0f ? "Aprovado" : "Reprovado");
}
/* Aplica bônus — modifica via ponteiro */
void aplicarBonus(Aluno *a, float bonus) {
a->nota += bonus;
if (a->nota > 10.0f) a->nota = 10.0f;
}
int main() {
Aluno a1, a2;
inicializarAluno(&a1, 1001, "Ana Souza", 7.5f);
inicializarAluno(&a2, 1002, "Bruno Lima", 4.0f);
printf("=== Antes do bônus ===\n");
exibirAluno(&a1);
exibirAluno(&a2);
aplicarBonus(&a2, 1.5f);
printf("\n=== Após bônus de 1.5 para Bruno ===\n");
exibirAluno(&a1);
exibirAluno(&a2);
return 0;
}
| Aspecto | C | Java |
|---|---|---|
| Passagem de struct/objeto | Por valor (cópia) ou por ponteiro (explícito) | Objetos sempre por referência automática |
| Modificar struct em função | Obrigatório usar LISTA *l e l->campo |
Automático — qualquer método pode modificar o objeto |
| Acesso a campos | variavel.campo ou ponteiro->campo |
Sempre objeto.campo |
| Proteção somente leitura | const LISTA *l |
Sem equivalente direto para campos individuais |
| Custo de cópia de struct grande | ⚠️ Você controla — use ponteiro para evitar | Automático — JVM gerencia cópias via GC |