Funções e Vetores em C

MCTA028-15 - PROGRAMAÇÃO ESTRUTURADA

Victor S. Portella

UFABC

2026-09-25

Questões Logísticas

Funções

O que são funções?

Usadas para modularizar o códgio;

Uma função:

  • Executa um bloco de código;
  • (Possívelmente) retorna um valor;

Sintaxe de como definir funções:

tipo_retorno nome_funcao(lista_d_parametros) {
// Corpo da função
}

Sintaxe da lista de parâmetros:

tipo_var1 var1, tipo_var2 var2, ...

Exemplo de uma função


int calcula_quadrado(int num){
    return num * num;
}

Chamando uma função

#include<stdio.h>

int calcula_quadrado(int num) {
    return num * num;
}

int main() {
    int n1;
    scanf("%d", &n1);
    
    int quad = calcula_quadrado(n1);
    
    printf("%d\n", quad);
    
    return 0;
}

Procedimento - Função sem retorno

Um procedimento é uma função que não retorna um valor

Usamos o tipo void para indicar isso

void print_table(int start, int stop) {
    int i;

    for (i = start; i <= stop; i++) {
        printf("%d\t", i*i);
    }
    printf("\n");
}

Declaração de funções

Definimos as funções antes de suas chamadas

Ex: código da função calcula_quadrado está antes da função main.

#include <stdio.h>

int calcula_quadrado(int num) {
    return num * num;
}

int main() {
    int n1;
    scanf("%d", &n1);

    int quad = calcula_quadrado(n1);
    printf("%d\n", quad);
    return 0;
}

Declaração de funções

Alternativamente, podemos só declarar o protótipo da função antes dela ser chamada

Código pode então ser declarado depois da função ser chamada

O protótipo contem o tipo de retorno, o nome da função, e os tipos dos argumentos

int calcula_quadrado(int); // Protótipo 

int main() {
    //...
    int quad = calcula_quadrado(n1);
    //...
}
int calcula_quadrado(int num) {
    return num * num;
}

Passagem de parâmetros

Em C, todo parâmetro de função é passado por valor

Passagem por referência vai ser feita via ponteiros

#include <stdio.h>

void bla(int x, int y) {
    x = y;
}

int main() {
    int a = 1, b = 2;

    bla(a, b);
    printf("a =  %d, b = %d \n", a, b, res);

    return 0;
} 
a = 1, b = 2

Escopo de variáveis

O escopo de uma variável determina de onde ela pode ser acessada

Em C, existem dois escopos pincipais:

  • Variáveis locais: acessíveis apenas localmente em um bloco de código;
  • Variáveis globais: Acessíveis por qualquer parte do código;

Escopo - Exemplo

#include <stdio.h>

double resultado; // Variável global

void calcula_quadrado(double num) {
    resultado = num * num;
}
double calcula_soma(double n1, double n2) {
    return n1 + n2;
}
int main() {
    int a = 2, b = 3; // Variáveis locais da main()

    resultado = calcula_soma(a, b);
    printf("%.2f\n", resultado);
    calcula_quadrado(resultado);
    printf("%.2f\n", resultado);
    calcula_quadrado(resultado);
    printf("%.2f\n", resultado);

    return 0;
} 

Escopo - Exemplo com Resposta

#include <stdio.h>

float resultado; // Variável global

void calcula_quadrado(double num) {
    resultado = num * num;
}
double calcula_soma(double n1, double n2) {
    return n1 + n2;
}
int main() {
    int a = 2, b = 3; // Variáveis locais da main()

    resultado = calcula_soma(a, b);
    printf("%.2f\n", resultado); // 5.00
    calcula_quadrado(resultado);
    printf("%.2f\n", resultado); // 25.00
    calcula_quadrado(resultado);
    printf("%.2f\n", resultado); // 625.00

    return 0;
} 

Sobre variáveis globais

Variáveis globais deixam o código de mais difícil leitura

Não sabemos quando seu valor pode mudar

Usar algum padrão de nomeação para variáveis globais (por exemplo, nomes começando com g_) é um modo de mitigar alguns dos problemas das variáveis globais.

Escopo em blocos de código

Escopo também vale para blocos de código: variável declarada dentro de um bloco de código só existe dentro daquele mesmo bloco.

void funcao_teste(int param1) {
    int a = param1;
    if (param1 > 0) {
        int b = 0;
        for (int i = 0; i < 10; i++) {
            int c = i * i;
            b += c;
            printf("%d %d %d", a, b, c);
        }
        // Erro: c não existe
        printf("%d %d %d", a, b, c); 
    }
    // Erro: b e c não existem
    printf("%d %d %d", a, b, c); 
} 

Vetores

O que é um vetor?

Um vetor é um conjunto de variáveis do mesmo tipo.

  • Todos os elementos são referenciados por um mesmo identificador.
  • Cada elemento é acessado por meio de um índice.

Exemplo: queremos ler a idade de 10 pessoas e contar quantas estão acima da média.

  • Uma alternativa seria criar 10 variáveis.
  • Outra alternativa, muito melhor, é criar um vetor de comprimento 10.

Declarando vetores

A sintaxe geral para declarar um vetor é:

tipo nome[tamanho]; // tamanho é um inteiro

Exemplos:

int idades[10];
double vetor2[5];
int valores[3] = {10, 20, 30}; // Modo de inicializar

Índices começam em zero

Em C, os índices de um vetor começam em 0.

int vetor[5];

Esse vetor possui 5 posições válidas:

vetor[0], vetor[1], vetor[2], vetor[3], vetor[4]

A posição vetor[5] não existe

Acessando elementos

A expressão vetor[i] acessa o elemento na posição i.

int vetor[5];

vetor[4];        // acessa o elemento de índice 4
vetor[3] = 7;    // atribui valor ao elemento de índice 3

if (vetor[3] == 8) {
    return 0;
}

O compilador geralmente não impede que você escreva um índice inválido. O programa pode compilar, mas ter comportamento imprevisível durante a execução.

Lendo valores para um vetor

Podemos usar um laço para ler vários elementos.

int idade[10];
int i;

for (i = 0; i < 10; i++) {
    scanf("%d", &idade[i]);
}

Por que usamos &idade[i]?

  • idade[i] é o elemento do vetor.
  • &idade[i] é o endereço desse elemento.
  • scanf precisa receber o endereço da variável onde deve guardar o valor.

Trate idade[i] da mesma forma que você trataria uma variável

Percorrendo um vetor

O padrão mais comum é usar um laço for.

int vetor[5];
int i;

for (i = 0; i < 5; i++) {
    vetor[i] = (i + 1) * (i + 1);
}

Depois desse laço:

i = 0  -> vetor[0] = 1
i = 1  -> vetor[1] = 4
i = 2  -> vetor[2] = 9
i = 3  -> vetor[3] = 16
i = 4  -> vetor[4] = 25

Exemplo: idades acima da média

Queremos:

  1. Ler a idade de 10 pessoas.
  2. Calcular a média.
  3. Contar quantas idades estão acima da média.

Precisamos guardar as idades porque vamos percorrer os valores mais de uma vez.

  • Primeiro para calcular a soma.
  • Depois para poder comparar cada idade com a média.

Idades acima da média: código

#include <stdio.h>

int main() {
    int idade[10];
    int i, soma = 0;

    for (i = 0; i < 10; i++) {
        scanf("%d", &idade[i]);
        soma += idade[i];
    }

    double media = soma / 10.0;
    int qtd_acima_media = 0;

    for (i = 0; i < 10; i++) {
        if (idade[i] > media) {
            qtd_acima_media++;
        }
    }

    printf("Ha %d pessoa(s) acima da media.\n", qtd_acima_media);

    return 0;
}

Detalhes importantes do exemplo

int idade[10];

Declara um vetor com 10 elementos.

scanf("%d", &idade[i]);

Lê um inteiro e guarda no elemento de índice i.

double media = soma / 10.0;

Usamos 10.0 para evitar divisão inteira.

Cuidado com os limites do vetor

Este vetor possui índices válidos de 0 a 9:

int idade[10];

O laço correto é:

for (i = 0; i < 10; i++) {
    scanf("%d", &idade[i]);
}

Este laço acessa uma posição inválida:

for (i = 0; i <= 10; i++) {
    scanf("%d", &idade[i]);
}

Comportamento imprevisível

int idade[10];

for (i = 0; i <= 10; i++) {
    scanf("%d", &idade[i]);
}

O acesso idade[10] está fora dos limites do vetor.

Em C, isso pode causar comportamento indefinido:

  • o programa pode parecer funcionar (!!!);
  • pode imprimir valores errados;
  • pode sobrescrever outra variável;
  • pode encerrar com erro (segmentation fault);

Vetores e memória

Vetores são armazenados em posições consecutivas na memória.

Se sizeof(int) == 4, então int vetor[8] ocupa:

8 * sizeof(int) = 8 * 4 = 32 // bytes

De forma geral, o espaço usado por um vetor é

quantidade_de_elementos * sizeof(tipo)

Consultando tamanhos com sizeof

O operador sizeof retorna quantos bytes um tipo ou expressão ocupa.

int main() {
    int a; long int b; long long int c;
    float d; double e; char f;

    printf("%zu %zu %zu %zu %zu %zu\n",
           sizeof(a), sizeof(b),sizeof(c), sizeof(d),
           sizeof(e), sizeof(f));
    return 0;
}

sizeof retorna um valor do tipo size_t, impresso usando %zu. Em Linux x86 ele é definido como unsigned long int pelo gcc, mas pode mudar dependendo da plataforma.

Algoritmos importantes com vetores

Alguns padrões que aparecem o tempo todo:

  • imprimir todos os elementos;
  • imprimir em ordem inversa;
  • inserir elemento no final;
  • inserir elemento no início ou no meio;
  • buscar elemento;
  • remover elemento;
  • deslocar elementos.

Imprimindo um vetor

#include <stdio.h>

int main() {
    int lista[5] = {20, 30, 50, 10, 40};

    int i;
    for (i = 0; i < 5; i++) {
        printf("%d ", lista[i]);
    }
    printf("\n");

    return 0;
}

Saída:

20 30 50 10 40

Imprimindo em ordem inversa

Como imprimir o mesmo vetor na ordem inversa?

int lista[5] = {20, 30, 50, 10, 40};
int i;
for (i = 4; i >= 0; i--) {
    printf("%d ", lista[i]);
}
printf("\n");

Saída:

40 10 50 30 20

Inserindo no final com while

#include <stdio.h>

int main() {
    int lista[5], i = 0;

    while (i < 5) {
        int numero;
        scanf("%d", &numero);
        lista[i++] = numero;
    }

    for (i = 0; i < 5; i++) {
        printf("%d ", lista[i]);
    }
    printf("\n");

    return 0;
}

Inserindo no início

Para inserir no início, precisamos deslocar os elementos para a direita.

Exemplo:

insere 10:  10
insere 32:  32 10
insere 57:  57 32 10

Inserindo no início: primeira versão

int main() {
    int lista[5];

    for (int i = 0; i < 5; i++) {
        int numero;
        scanf("%d", &numero);

        for (int k = 5 - 1; k > 0; k--) {
            lista[k] = lista[k - 1];
        }
        lista[0] = numero;
    }
    // imprime o vetor...
    for (i = 0; i < 5; i++) { printf("%d ", lista[i]); }
    printf("\n");

    return 0;
}

Podemos melhorar?

Na versão anterior, sempre deslocamos até a última posição.

Mas, no início, nem todas as posições do vetor já foram preenchidas.

Podemos deslocar apenas as posições já utilizadas começando com k = i:

for (k = i; k > 0; k--) {
    lista[k] = lista[k - 1];
}

lista[0] = numero;

Inserindo no início: versão melhorada

#include <stdio.h>

int main() {
    int lista[5];

    for (int i = 0; i < 5; i++) {
        int numero;
        scanf("%d", &numero);

        for (int k = i; k > 0; k--) {
            lista[k] = lista[k - 1];
        }

        lista[0] = numero;
    }

    for (i = 0; i < 5; i++) {
        printf("%d ", lista[i]);
    }
    printf("\n");

    return 0;
}

Números fixos no código

E se quisermos trabalhar com um vetor de 10 elementos?

Na versão atual, o valor 5 aparece várias vezes:

int lista[5];

for (i = 0; i < 5; i++) {
    ...
}

for (i = 0; i < 5; i++) {
    printf("%d ", lista[i]);
}

Esse valor está hardcoded no código.

Evitando valores hardcoded

Uma opção simples é usar uma constante simbólica.

#include <stdio.h>

#define TAM 5

int main() {
    int lista[TAM];
    int i;

    for (i = 0; i < TAM; i++) {
        int numero;
        scanf("%d", &numero);
        lista[i] = numero;
    }

    for (i = 0; i < TAM; i++) {
        printf("%d ", lista[i]);
    }
    printf("\n");

    return 0;
}

Buscando um elemento

Queremos verificar se um número aparece no vetor.

#include <stdio.h>

int main() {
    int lista[5] = {20, 30, 50, 10, 40};

    int numero;
    scanf("%d", &numero);

    int i, encontrou = 0;
    for (i = 0; i < 5; i++) {
        if (lista[i] == numero) {
            encontrou = 1;
        }
    }

    if (encontrou) {
        printf("Encontrou!\n");
    } else {
        printf("NAO encontrou!\n");
    }

    return 0;
}

Podemos melhorar a busca?

Depois de encontrar o elemento, não precisamos continuar percorrendo o vetor.

int i, encontrou = 0;

for (i = 0; i < 5; i++) {
    if (lista[i] == numero) {
        encontrou = 1;
        break;
    }
}

Outra possibilidade:

int i, encontrou = 0;

for (i = 0; i < 5 && !encontrou; i++) {
    if (lista[i] == numero) {
        encontrou = 1;
    }
}

Vetores como argumentos

Variáveis são passadas por valor

void muda_valor(int parametro) {
    parametro = 507;
    printf("%d\n", parametro);
}

int main() {
    int n = 1000;

    muda_valor(n);
    printf("%d\n", n);

    return 0;
}

Qual é a saída?

507
1000

Variáveis são passadas por valor: resposta

507
1000

A função recebe uma cópia do valor de n.

Alterar parametro não altera n.

E com vetores?

#include <stdio.h>

void muda_valor(int vetor[]) {
    vetor[0] = 90;
    printf("%d\n", vetor[0]);
}

int main() {
    int v[3] = {200, 500, 300};

    muda_valor(v);
    printf("%d %d %d\n", v[0], v[1], v[2]);

    return 0;
}

Qual é a saída?

90
90 500 300

Por que isso acontece?

O identificador de uma variável comum representa o valor da variável.

int matricula = 123;

printf("%d\n", matricula);
printf("%p\n", &matricula);
  • matricula acessa o valor.
  • &matricula acessa o endereço.

O nome do vetor

Já o identificador de um vetor representa o endereço do primeiro elemento.

int vetor[3] = {20, 500, 7};

printf("%p\n", vetor);
printf("%p\n", &vetor[0]);

Essas duas expressões imprimem o mesmo endereço.

Quando passamos vetor para uma função, estamos passando uma cópia do endereço de onde esse vetor está armazeado.

  • Em outras palavras, vetor é (essencialmente) ponteiro para um espaço da memória
  • Veremos ponteiros em mais detalhes no futuro

Exemplo para praticar

Criar uma função que troca todos os números primos de um vetor pelo número -1.

Exemplo:

entrada:  10 7 8 11 20
saída:    10 -1 8 -1 20

Podemos separar o problema em duas funções:

int eh_primo(int n);
void troca_primos(int vetor[], int n);

Nota: int v[n] não é alocação estática

Em C, é possível escrever:

void f(int n) {
    int v[n];
}

Isso é um VLA (variable-length array): um vetor cujo tamanho é decidido durante a execução.

Mas ele tem armazenamento automático, não estático:

  • existe apenas enquanto a função/bloco está sendo executado;
  • normalmente usa a stack (mais detalhes no futuro);

Alocação estática seria algo como int v[100]; ou uma variável global, com o tamanho conhecido em tempo de compilação.

Por que não usar VLAs como padrão?

Embora VLAs sejam aceitos pelo GCC, eles têm limitações importantes:

  • O suporte a VLAs não é igualmente forte em todos os compiladores/padrões de C.

  • O tamanho pode vir da entrada do usuário e ser grande demais.

  • Não há uma forma simples de verificar se a alocação falhou.

Por isso, nesta disciplina, vamos preferir: int v[MAX]; quando houver um limite fixo, e alocação dinâmica manual no futuro.

Exercícios

  1. Implemente os algoritmos de inserção vistos nesta aula em forma de função.

  2. Implemente uma função para remover o elemento de índice i de um vetor.

    • A função deve deslocar para a esquerda os elementos que estão depois de i.
    • Pense também no que deve acontecer com o tamanho lógico da lista.