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.

Você vai aprender
  • 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 void quer dizer antes do nome e dentro dos parênteses.
  • A diferença entre void f() e void 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, redefinition e 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 receitasEm CNome técnico
Escrever, uma vez, como se faz a massa básicaEscrever o código da função, com nome e corpodefinir 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 continuarO programa volta para a linha seguinte à chamadaretornar

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.

  1. O main começa e mostra Abrindo a loja....
  2. A linha cumprimentar(); faz o programa pular para dentro da função e rodar o corpo: aparece a frase de boas-vindas.
  3. Ao chegar no } da função, o programa volta para o main, logo depois da chamada.
  4. A segunda chamada repete a viagem: pula, mostra a frase, volta.
  5. O main mostra Fim. e termina com return 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:

ParteEm void linha(void)Pergunta que respondeAula
Tipo de retornovoidO que a função devolve quando termina?Valor de Retorno
NomelinhaComo 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çalhoAté o C17 significaNo 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").

Regra do guia: escreva (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:

JeitoComo ficaVantagemQuando usar
Funções primeiro, main no fimlinha, depois cabecalho, depois mainSimples: nada a mais para escreverProgramas de um arquivo, como os desta aula
Protótipos no topo, main logo depois, funções no fimvoid linha(void); no topo; o main; as definições embaixoQuem abre o arquivo vê o main (o "resumo" do programa) primeiro; a ordem das funções deixa de importarProgramas 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

EscritaO 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 nomeA função não devolve valor
(void) no cabeçalhoA função não recebe valores (em qualquer versão do C)
() vazio no cabeçalhoC23: 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:

aviso(void) {

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:

void menu() {

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: chama linha, mostra LOJINHA DA ANA (com dois espaços na frente) e chama linha de novo;
  • rodape: chama linha e mostra Obrigada 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.
  • void antes 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 ()).