C Funções
Até aqui, todo o seu código morava dentro do main. Isso funciona para programas pequenos, mas logo o main vira uma parede de linhas repetidas. Uma função é um pedaço de código com nome: você escreve uma vez e usa quantas vezes quiser, só chamando o nome. Nesta aula você vai criar suas primeiras funções, entender o que significa o void, descobrir por que () e (void) eram coisas diferentes até o C23 e ler os erros que o compilador mostra quando algo sai do lugar.
- O que é uma função e por que ela deixa o programa mais curto e mais fácil de consertar.
- Como definir uma função (escrever) e como chamar (usar).
- Em que ordem as linhas rodam quando o programa entra e sai de uma função.
- O que o
voidquer dizer antes do nome e dentro dos parênteses. - A diferença entre
void f()evoid f(void), e o que mudou no C23. - Onde a função fica no arquivo, e por que função dentro de função não é C padrão.
- Os erros mais comuns:
too many arguments,void value not ignored,redefinitione o parêntese esquecido.
Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa (cada opção é explicada em Compilar com gcc). Você pode repetir cada um no editor do guia, que compila C de verdade no navegador.
A ideia: uma receita com nome
Pense no caderno de receitas da avó da Ana. Lá está escrito, uma vez só, como fazer a massa básica. Depois, a receita de pizza diz apenas "faça a massa básica", e a de pão de queijo também. Ninguém copia o passo a passo da massa em cada receita: basta citar o nome.
Em C é igual:
| No caderno de receitas | Em C | Nome técnico |
|---|---|---|
| Escrever, uma vez, como se faz a massa básica | Escrever o código da função, com nome e corpo | definir a função |
| "Agora, faça a massa básica" | Escrever o nome da função seguido de () | chamar a função |
| Voltar para a receita da pizza e continuar | O programa volta para a linha seguinte à chamada | retornar |
Você já usa funções desde a primeira aula, só que escritas por outras pessoas: printf é uma função da biblioteca do C, e o próprio main é uma função, a que o sistema chama quando o programa começa. Agora você vai escrever as suas.
1. Criar e chamar uma função
A loja da Ana abre e cumprimenta cada cliente com a mesma frase. Vamos colocar a frase numa função chamada cumprimentar:
#include <stdio.h>
void cumprimentar(void) {
printf("Olá! Bem-vindo à lojinha da Ana.\n");
}
int main(void) {
printf("Abrindo a loja...\n");
cumprimentar();
cumprimentar();
printf("Fim.\n");
return 0;
}
- void cumprimentar(void) { … }
- A definição da função: diz o nome e o que ela faz. Fica fora do
main, antes dele. - void (antes do nome)
- "Vazio": a função não devolve nenhum valor para quem a chamou. Ela só faz uma coisa (aqui, escrever na tela) e termina. Funções que devolvem um resultado ficam para Valor de Retorno.
- cumprimentar
- O nome. Segue as mesmas regras dos nomes de variáveis (Nomes de Variáveis): letras, números e
_, sem acento, sem começar com número. Use um verbo que diga o que a função faz. - (void) (dentro dos parênteses)
- A função não recebe nenhuma informação. Funções que recebem valores ficam para Parâmetros.
- { … }
- O corpo: as linhas que rodam cada vez que a função é chamada.
- cumprimentar();
- A chamada: "rode agora o corpo de
cumprimentar". Os parênteses são obrigatórios, mesmo vazios, e o;termina o comando, como qualquer outro.
$ gcc -std=c23 -Wall -Wextra ola.c -o ola $ ./ola Abrindo a loja... Olá! Bem-vindo à lojinha da Ana. Olá! Bem-vindo à lojinha da Ana. Fim.
A frase aparece duas vezes, porque a função foi chamada duas vezes. O texto dela, porém, está escrito uma vez só no código.
Em que ordem as linhas rodam
O programa sempre começa pelo main, mesmo que a função esteja escrita antes dele no arquivo. Definir uma função não a executa: é como escrever a receita no caderno sem cozinhar nada. Ela só roda quando alguém a chama.
- O
maincomeça e mostraAbrindo a loja.... - A linha
cumprimentar();faz o programa pular para dentro da função e rodar o corpo: aparece a frase de boas-vindas. - Ao chegar no
}da função, o programa volta para omain, logo depois da chamada. - A segunda chamada repete a viagem: pula, mostra a frase, volta.
- O
mainmostraFim.e termina comreturn 0;.
Experimente: rode o programa. Depois chame cumprimentar(); uma terceira vez, antes do Fim.. Por último, mude o texto dentro da função e rode de novo: as três frases mudam juntas.
#include <stdio.h>
void cumprimentar(void) {
printf("Olá! Bem-vindo à lojinha da Ana.\n");
}
int main(void) {
printf("Abrindo a loja...\n");
cumprimentar();
cumprimentar();
printf("Fim.\n");
return 0;
}
2. Por que usar funções
A Ana imprime um recibo para cada venda. Sem funções, a linha de = aparece repetida quatro vezes:
#include <stdio.h>
int main(void) {
printf("==============================\n");
printf(" LOJINHA DA ANA\n");
printf("==============================\n");
printf("Caderno ......... R$ 12,50\n");
printf("Caneta .......... R$ 3,00\n");
printf("==============================\n");
printf("Total ........... R$ 15,50\n");
printf("==============================\n");
return 0;
}
Com uma função linha, o desenho da linha fica num lugar só:
#include <stdio.h>
void linha(void) {
printf("==============================\n");
}
int main(void) {
linha();
printf(" LOJINHA DA ANA\n");
linha();
printf("Caderno ......... R$ 12,50\n");
printf("Caneta .......... R$ 3,00\n");
linha();
printf("Total ........... R$ 15,50\n");
linha();
return 0;
}
$ gcc -std=c23 -Wall -Wextra recibo2.c -o recibo2 $ ./recibo2 ============================== LOJINHA DA ANA ============================== Caderno ......... R$ 12,50 Caneta .......... R$ 3,00 ============================== Total ........... R$ 15,50 ==============================
A saída é idêntica à do recibo1.c. Então, o que ganhamos?
- Consertar num lugar só. Se a Ana quiser a linha com
-em vez de=, muda uma linha, e não quatro. Com o código copiado, é fácil esquecer uma das cópias. - Ler como uma frase.
linha();diz o que acontece; quem lê não precisa contar sinais de igual. - Dividir o problema. Um programa grande vira várias funções pequenas, cada uma com uma tarefa. Fica mais fácil pensar, testar e achar erros em cada pedaço.
- Reaproveitar. Uma função boa pode ir para outro programa (em Arquivos .h e .c você vai ver como guardar funções num arquivo separado).
Uma função pode chamar outra
O corpo de uma função pode chamar outras funções. Aqui, cabecalho usa linha:
#include <stdio.h>
void linha(void) {
printf("==============================\n");
}
void cabecalho(void) {
linha();
printf(" LOJINHA DA ANA\n");
linha();
}
int main(void) {
cabecalho();
printf("Caderno ......... R$ 12,50\n");
return 0;
}
$ gcc -std=c23 -Wall -Wextra chama.c -o chama $ ./chama ============================== LOJINHA DA ANA ============================== Caderno ......... R$ 12,50
O caminho foi: main chama cabecalho, que chama linha, que volta para cabecalho, que mostra o nome da loja, chama linha de novo, e só então volta para o main. Cada função lembra de onde foi chamada e volta exatamente para lá. Repare que linha está escrita acima de cabecalho: a seção 4 explica por quê.
3. O void e os parênteses
A primeira linha de uma função (antes da {) se chama cabeçalho ou assinatura. Ela tem três partes:
| Parte | Em void linha(void) | Pergunta que responde | Aula |
|---|---|---|---|
| Tipo de retorno | void | O que a função devolve quando termina? | Valor de Retorno |
| Nome | linha | Como ela é chamada? | esta |
| Lista de parâmetros | (void) | O que ela recebe para trabalhar? | Parâmetros |
Só para você reconhecer quando encontrar (as próximas aulas explicam com calma), uma função que recebe um número e devolve outro tem esta cara:
int dobro(int n) { // recebe um int (n) e devolve um int
return n * 2;
}
Nesta aula, todas as funções são void e (void): não recebem nada e não devolvem nada. Por isso, usar o "resultado" de uma delas é erro:
#include <stdio.h>
void linha(void) {
printf("----------\n");
}
int main(void) {
int x = linha();
printf("%d\n", x);
return 0;
}
$ gcc -std=c23 -Wall -Wextra usarvoid.c -o usarvoid
usarvoid.c: In function ‘main’:
usarvoid.c:8:13: error: void value not ignored as it ought to be
8 | int x = linha();
| ^~~~~
Void value not ignored as it ought to be = "valor vazio não foi ignorado, como deveria ser". A função não devolve nada, então não há o que guardar em x. O Clang, no editor, diz initializing 'int' with an expression of incompatible type 'void' ("inicializando um int com uma expressão de tipo incompatível, void").
() × (void): uma diferença que o C23 apagou
Você vai encontrar muito código (e muitos cursos) escrito assim, com os parênteses vazios:
#include <stdio.h>
void linha() {
printf("----------\n");
}
int main(void) {
linha();
linha(5);
return 0;
}
Repare na linha 9: linha(5) passa um número para uma função que não usa número nenhum. É um engano. O que acontece depende da versão do C:
$ gcc -std=c23 -Wall -Wextra vazio.c -o vazio
vazio.c: In function ‘main’:
vazio.c:9:5: error: too many arguments to function ‘linha’; expected 0, have 1
9 | linha(5);
| ^~~~~ ~
vazio.c:3:6: note: declared here
3 | void linha() {
| ^~~~~
$ gcc -std=c17 -Wall -Wextra vazio.c -o vazio « o mesmo arquivo, no padrão C17
$ ./vazio
----------
---------- « compilou sem um único aviso, e o 5 foi ignorado
- too many arguments to function ‘linha’
- "Argumentos demais para a função
linha". Argumento é o valor que vai entre os parênteses na chamada. - expected 0, have 1
- "Esperava 0, recebeu 1".
- note: declared here
- "Declarada aqui": o compilador mostra o cabeçalho da função, para você comparar.
Por que o C17 aceitou? Por causa de uma regra que vem do C de 1978 (o "K&R C", veja Versões do C):
| Cabeçalho | Até o C17 significa | No C23 significa |
|---|---|---|
void linha() | "não estou dizendo quais parâmetros ela tem": o compilador aceita qualquer chamada, com qualquer número de argumentos, sem conferir | "não recebe nada" (igual a (void)) |
void linha(void) | "não recebe nada": chamar com argumento é erro | "não recebe nada" |
O C23 acabou com a ambiguidade: agora () e (void) querem dizer a mesma coisa. Como o gcc 15 já usa o C23 por padrão, o engano é pego. Mas se o seu código for compilado como C17 ou mais antigo (muitos projetos fazem isso), o () volta a esconder erros. O Clang, compilando como C17, ao menos avisa: passing arguments to 'linha' without a prototype is deprecated in all versions of C and is not supported in C23 ("passar argumentos para linha sem protótipo está obsoleto em todas as versões do C e não é aceito no C23").
(void)
Para uma função que não recebe nada, escreva void linha(void) e int main(void). Funciona igual em todas as versões do C, e quem lê entende na hora. O (void) vale só no cabeçalho; na chamada, os parênteses ficam vazios: linha();.
4. Onde a função fica no arquivo
O compilador lê o arquivo de cima para baixo, uma vez só. Quando ele encontra linha();, precisa já saber que linha existe. Por isso, nesta aula, as funções sempre aparecem acima do main (e acima de quem as chama). Veja o que acontece com a ordem trocada:
#include <stdio.h>
int main(void) {
linha();
return 0;
}
void linha(void) {
printf("----------\n");
}
$ gcc -std=c23 -Wall -Wextra depois.c -o depois
depois.c: In function ‘main’:
depois.c:4:5: error: implicit declaration of function ‘linha’ [-Wimplicit-function-declaration]
4 | linha();
| ^~~~~
depois.c: At top level:
depois.c:8:6: warning: conflicting types for ‘linha’; have ‘void(void)’
8 | void linha(void) {
| ^~~~~
depois.c:4:5: note: previous implicit declaration of ‘linha’ with type ‘void(void)’
4 | linha();
| ^~~~~
Implicit declaration of function ‘linha’ = "declaração implícita da função linha": na linha 4, o compilador ainda não conhecia essa função. O aviso que vem depois (conflicting types, "tipos em conflito") é uma consequência do primeiro erro: ao encontrar a função de verdade na linha 8, o gcc a compara com o que tinha imaginado na linha 4. Conserte o primeiro erro e os dois somem. Há dois consertos: mover a função para cima, ou escrever no topo um protótipo, uma linha que avisa "existe uma função linha, ela vem mais abaixo". Os protótipos têm aula própria: Protótipos (Declarar Antes de Usar).
Função dentro de função: não é C padrão
Cada função é escrita fora das outras, lado a lado no arquivo. Uma curiosidade: o gcc aceita uma função dentro de outra, como uma extensão própria (um "extra" que só ele oferece):
#include <stdio.h>
int main(void) {
void oi(void) {
printf("oi\n");
}
oi();
return 0;
}
$ gcc -std=c23 -Wall -Wextra aninhada.c -o aninhada
$ ./aninhada
oi « o gcc aceitou, sem aviso
$ gcc -std=c23 -Wall -Wextra -pedantic aninhada.c -o aninhada
aninhada.c: In function ‘main’:
aninhada.c:4:5: warning: ISO C forbids nested functions [-Wpedantic]
4 | void oi(void) {
| ^~~~
Com -pedantic ("exigente": avise tudo o que não é do padrão), o gcc confessa: ISO C forbids nested functions, "o C padrão (ISO) proíbe funções aninhadas". O Clang, o compilador do editor, nem compila: error: function definition is not allowed here ("definição de função não é permitida aqui"). Um código assim só funciona num compilador. Não use: escreva oi fora do main, acima dele.
Missão: o programa repete printf("----------\n"); duas vezes. Crie uma função void separador(void), acima do main, que mostra essa linha, e troque os dois printf repetidos por chamadas separador();. A saída tem de continuar a mesma.
Pedido 1 ---------- Pedido 2 ----------
#include <stdio.h>
int main(void) {
printf("Pedido 1\n");
printf("----------\n");
printf("Pedido 2\n");
printf("----------\n");
return 0;
}
#include <stdio.h>
void separador(void) {
printf("----------\n");
}
int main(void) {
printf("Pedido 1\n");
separador();
printf("Pedido 2\n");
separador();
return 0;
}
Formas diferentes de organizar o arquivo
Existem dois jeitos comuns de arrumar as funções num arquivo .c. Os dois compilam e dão o mesmo resultado:
| Jeito | Como fica | Vantagem | Quando usar |
|---|---|---|---|
Funções primeiro, main no fim | linha, depois cabecalho, depois main | Simples: nada a mais para escrever | Programas de um arquivo, como os desta aula |
Protótipos no topo, main logo depois, funções no fim | void linha(void); no topo; o main; as definições embaixo | Quem abre o arquivo vê o main (o "resumo" do programa) primeiro; a ordem das funções deixa de importar | Programas maiores; é o primeiro passo para os arquivos .h (Protótipos) |
O que não existe em C: duas funções com o mesmo nome (mesmo que recebam coisas diferentes) e função dentro de função. Se você vem de outra linguagem que permite isso, a aula Parâmetros mostra o erro.
Quando dá errado
error: implicit declaration of function ‘linha’
O que significa: você chamou a função numa linha em que o compilador ainda não a conhecia. Ou ela está escrita mais abaixo, ou o nome está diferente (Linha, linah), ou faltou um #include (para printf, o <stdio.h>).
Como consertar: confira o nome letra por letra; mova a definição para cima de quem a chama ou escreva um protótipo no topo (Protótipos). No editor, o Clang escreve use of undeclared identifier 'linha'.
error: too many arguments to function ‘linha’; expected 0, have 1
O que significa: a chamada passou valores entre os parênteses, mas a função não recebe nenhum.
Como consertar: tire o valor da chamada (linha();) ou, se a função deveria mesmo receber um valor, acrescente um parâmetro (Parâmetros). No editor: too many arguments to function call, expected 0, have 1.
error: void value not ignored as it ought to be
O que significa: você tentou guardar ou usar o "resultado" de uma função void, que não devolve nada.
Como consertar: chame a função sozinha na linha (linha();). Se você precisa de um resultado, a função tem de devolver um, com outro tipo e return (Valor de Retorno).
warning: statement with no effect [-Wunused-value] na linha linha;
O que significa: faltaram os parênteses. Sem eles, linha; só cita o nome da função e não a chama: o programa compila, mas a linha não aparece. O gcc avisa que o comando "não tem efeito"; o Clang, expression result unused ("resultado da expressão não usado").
Como consertar: escreva linha();. Toda chamada leva parênteses, mesmo vazios.
error: redefinition of ‘linha’
O que significa: existem duas funções com o mesmo nome no arquivo. A note: previous definition of ‘linha’ aponta a primeira.
Como consertar: apague uma delas ou dê outro nome (linha_dupla, por exemplo). Em C, cada nome de função é único no programa inteiro.
error: expected identifier or ‘(’ before ‘{’ token
O que significa: provavelmente sobrou um ; no fim do cabeçalho: void linha(void); seguido de {. Com o ;, o cabeçalho vira um protótipo, e a { da linha de baixo fica sem dono.
Como consertar: na definição, nada de ; entre o ) e a {. O ; vai na chamada (linha();) e no protótipo.
Referência rápida
| Escrita | O que faz |
|---|---|
void nome(void) { … } | Define uma função que não recebe nada e não devolve nada |
nome(); | Chama a função: roda o corpo e volta para a linha seguinte |
void antes do nome | A função não devolve valor |
(void) no cabeçalho | A função não recebe valores (em qualquer versão do C) |
() vazio no cabeçalho | C23: o mesmo que (void). Até o C17: "parâmetros não informados", sem conferência |
void nome(void); | Protótipo: avisa que a função existe e vem depois (Protótipos) |
int main(void) | A função por onde todo programa começa |
Exercícios
1. Chamar a função
Complete a linha do main que chama a função apitar, definida como void apitar(void):
2. O tipo que não devolve nada
Complete o cabeçalho de uma função que só mostra um texto e não devolve valor:
3. Sem parâmetros, do jeito seguro
Complete os parênteses para dizer, em qualquer versão do C, que a função não recebe nada:
4. Um programa define void ola(void) acima do main, mas o main não chama ola. O que acontece com o texto de dentro de ola?
5. Com void linha() e a chamada linha(5);, o que o gcc 15 faz com -std=c17?
6. O que a linha linha; (sem parênteses) faz?
Missão final: o recibo da lojinha
Missão: monte o recibo com três funções, todas void e (void), escritas acima do main:
linha: mostra==========(dez sinais de igual);cabecalho: chamalinha, mostraLOJINHA DA ANA(com dois espaços na frente) e chamalinhade novo;rodape: chamalinhae mostraObrigada e volte sempre!.
O main já está pronto. A saída tem de ser exatamente esta, sem avisos:
========== LOJINHA DA ANA ========== Caderno R$ 12,50 ========== Obrigada e volte sempre!
#include <stdio.h>
// escreva aqui as funções linha, cabecalho e rodape
int main(void) {
cabecalho();
printf("Caderno R$ 12,50\n");
rodape();
return 0;
}
#include <stdio.h>
void linha(void) {
printf("==========\n");
}
void cabecalho(void) {
linha();
printf(" LOJINHA DA ANA\n");
linha();
}
void rodape(void) {
linha();
printf("Obrigada e volte sempre!\n");
}
int main(void) {
cabecalho();
printf("Caderno R$ 12,50\n");
rodape();
return 0;
}
Se você escreveu cabecalho acima de linha, o compilador reclamou com implicit declaration (no editor, use of undeclared identifier): quem é chamado tem de vir antes de quem chama. Na aula Protótipos você aprende a se livrar dessa preocupação.
Resumo
- Função = pedaço de código com nome. Definir escreve a receita; chamar (
nome();) executa. O programa pula para a função e volta para a linha seguinte. - Funções evitam código repetido: conserta-se num lugar só, e o programa fica mais fácil de ler e dividir.
voidantes do nome: não devolve nada (usar o "resultado" dávoid value not ignored).(void)nos parênteses: não recebe nada.- Até o C17,
()vazio queria dizer "parâmetros não informados", e chamadas erradas passavam caladas. No C23,()=(void). Escreva sempre(void). - A função tem de ser conhecida antes de ser chamada: escreva-a acima (ou use um protótipo). Cada função fica fora das outras; função aninhada é extensão do gcc, não é C padrão.
- Erros típicos:
implicit declaration,too many arguments,redefinition,statement with no effect(faltou o()).