Projeto Boletim de Notas

A Ana dá aula de reforço para uma turma pequena e quer um programa que faça o boletim: ela digita o nome e a nota de cada aluno, e o programa mostra a média da turma, quem tirou a maior e a menor nota, quantos passaram e uma tabela bem alinhada. É o projeto para treinar arrays (muitos valores numa variável só) e structs (vários dados juntos de uma pessoa).

Você vai praticar
  • Guardar várias notas num array e percorrê-lo com for.
  • Somar e tirar a média (e o cuidado com o índice que passa do fim).
  • Juntar nome e nota numa struct e fazer um array de structs.
  • Ler texto com %29s sem estourar o array (Strings).
  • Achar o maior, o menor e contar quem passou, num laço só.
  • Alinhar uma tabela com %-10s e %5.1f (Formatos do printf) e o problema dos acentos.

O que vamos construir

O boletim pronto, na máquina da Ana, com cinco alunos:

$ ./boletim
=== Boletim da Turma ===
Quantos alunos (1 a 30)? 5
Aluno 1 (nome e nota): Bruno 7.5
Aluno 2 (nome e nota): Carla 9
Aluno 3 (nome e nota): Diego 5.5
Aluno 4 (nome e nota): Elisa 8
Aluno 5 (nome e nota): Gabriel 3.5

Aluno       Nota  Situação
---------- -----  -----------
Bruno        7.5  Aprovado
Carla        9.0  Aprovado
Diego        5.5  Recuperação
Elisa        8.0  Aprovado
Gabriel      3.5  Reprovado
---------- -----  -----------
Média        6.7

Maior nota: 9.0 (Carla)
Menor nota: 3.5 (Gabriel)
Aprovados: 3 de 5
  1. As notas num array e a média.
  2. Nome e nota juntos: um array de structs.
  3. Maior, menor e aprovados.
  4. A tabela alinhada (%-10s %5.1f, uma função e a quantidade de alunos).

Antes de começar

Você precisa ter visto Arrays, for e scanf; para a versão 2, Structs. O arquivo vai se chamar boletim.c:

$ mkdir -p c/notas
$ cd c/notas

Versão 1 · As notas num array e a média

Cinco alunos, cinco notas. Daria para criar nota1, nota2… nota5, mas e se a turma tiver 30? Um array guarda todas numa variável só: notas[0] é a primeira, notas[4] a quinta. E um for passa por todas, sem repetir código.

#include <stdio.h>

#define QTD 5

int main(void) {
    double notas[QTD] = {0};
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Nota do aluno %d: ", i + 1);
        if (scanf("%lf", &notas[i]) != 1) {
            printf("\nNota inválida.\n");
            return 1;
        }
        soma += notas[i];
    }

    printf("Notas: ");
    for (int i = 0; i < QTD; i++) {
        printf("%.1f ", notas[i]);
    }
    printf("\nMédia da turma: %.2f\n", soma / QTD);
    return 0;
}
LinhaO que faz
#define QTD 5O tamanho da turma, com nome. Usado no array e nos laços: para mudar a turma, você muda um número só.
double notas[QTD] = {0};Um array de 5 double, todos começando em zero. O {0} zera o array inteiro.
for (int i = 0; i < QTD; i++)i vale 0, 1, 2, 3 e 4: as posições do array. Repare no <: a posição 5 não existe (veja o cenário abaixo).
printf("Nota do aluno %d: ", i + 1);Para a Ana, o primeiro aluno é o 1, não o 0. Por isso mostramos i + 1.
scanf("%lf", &notas[i])Lê a nota direto na posição i do array. O & pega o endereço daquela posição.
soma += notas[i];Acumula: soma = soma + notas[i] (veja Operadores de Atribuição).
printf("%.1f ", notas[i]);Mostra cada nota com uma casa decimal.
soma / QTDA média. Como soma é double, a divisão tem casas decimais (com dois int, 33 / 5 daria 6; veja Conversão de Tipos).
$ gcc -std=c23 -Wall -Wextra boletim.c -o boletim
$ ./boletim
=== Boletim da Turma ===
Nota do aluno 1: 7.5
Nota do aluno 2: 9
Nota do aluno 3: 5.5
Nota do aluno 4: 8
Nota do aluno 5: 3.5
Notas: 7.5 9.0 5.5 8.0 3.5 
Média da turma: 6.70
i <= QTD: o laço passa do fim do array

Este código tem um erro de propósito: for (int i = 0; i <= QTD; i++) no laço que lê as notas.

$ gcc -std=c23 -Wall -Wextra boletim.c -o boletim
$ ./boletim
=== Boletim da Turma ===
Nota do aluno 1: 7.5
Nota do aluno 2: 9
Nota do aluno 3: 5.5
Nota do aluno 4: 8
Nota do aluno 5: 3.5
Nota do aluno 6: 10                       « pediu uma nota a mais
Notas: 7.5 9.0 5.5 8.0 3.5
Média da turma: 8.70                      « e a média ficou errada
*** stack smashing detected ***: terminated
Abortado                   (imagem do núcleo gravada) ./boletim

O que aconteceu: com <=, o laço também roda com i igual a 5, e o scanf escreveu a sexta nota em notas[5], uma posição que não pertence ao array. O C não confere: ele escreveu por cima do que estava na memória logo depois. A média ficou errada porque a sexta nota também entrou na soma (43.5 / 5 = 8.70). E a escrita fora do array estragou uma "trava" que o gcc coloca na pilha; no fim do main, o programa viu a trava quebrada ("stack smashing detected", pilha esmagada) e se abortou. Em outro programa, poderia não aparecer nada e só dar um resultado errado.

Para achar a linha exata, compile com -g -fsanitize=address (veja Sanitizers):

$ gcc -std=c23 -Wall -Wextra -g -fsanitize=address boletim.c -o boletim
$ ./boletim
=== Boletim da Turma ===
Nota do aluno 1: 7.5
Nota do aluno 2: 9
Nota do aluno 3: 5.5
Nota do aluno 4: 8
Nota do aluno 5: 3.5
Nota do aluno 6: 10
=================================================================
==74118==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x79bf1ddf0058 at pc 0x7dbf202b05dc bp 0x7ffdac6bde10 sp 0x7ffdac6bd598
WRITE of size 8 at 0x79bf1ddf0058 thread T0
    #0 0x7dbf202b05db in scanf_common ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_format.inc:346
    #1 0x7dbf202f0e4e in __isoc23_vscanf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1586
    #2 0x7dbf202f1634 in __isoc23_scanf ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:1616
    #3 0x60d26256341d in main /home/ana/c/notas/boletim.c:12
…                                         « o relatório continua; cortamos aqui
  This frame has 1 object(s):
    [48, 88) 'notas' (line 6) <== Memory access at offset 88 overflows this variable

"Estouro de buffer na pilha": uma escrita (WRITE) de 8 bytes (um double) feita pelo scanf chamado em boletim.c:12, logo depois do fim de notas (linha 6).

Como consertar: um array de QTD posições vai de 0 a QTD - 1. O laço é i < QTD. No editor do guia esse erro pode passar calado (a memória do WebAssembly é organizada de outro jeito), por isso o exemplo é do terminal.

Missão (versão 1): o programa lê as notas, mas a média sai 0.00. Dentro do primeiro laço, some cada nota em soma com soma += notas[i];. A entrada da missão é 7.5, 9, 5.5, 8 e 3.5.

#include <stdio.h>

#define QTD 5

int main(void) {
    double notas[QTD] = {0};
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Nota do aluno %d: ", i + 1);
        if (scanf("%lf", &notas[i]) != 1) {
            printf("\nNota inválida.\n");
            return 1;
        }
        // some a nota em soma
    }

    printf("Notas: ");
    for (int i = 0; i < QTD; i++) {
        printf("%.1f ", notas[i]);
    }
    printf("\nMédia da turma: %.2f\n", soma / QTD);
    return 0;
}
#include <stdio.h>

#define QTD 5

int main(void) {
    double notas[QTD] = {0};
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Nota do aluno %d: ", i + 1);
        if (scanf("%lf", &notas[i]) != 1) {
            printf("\nNota inválida.\n");
            return 1;
        }
        soma += notas[i];
    }

    printf("Notas: ");
    for (int i = 0; i < QTD; i++) {
        printf("%.1f ", notas[i]);
    }
    printf("\nMédia da turma: %.2f\n", soma / QTD);
    return 0;
}

Versão 2 · Nome e nota juntos: um array de structs

Notas sem nome não servem para um boletim. Poderíamos ter dois arrays, um de nomes e um de notas, e cuidar para a posição 2 de um combinar com a posição 2 do outro. Mais simples: uma struct, uma "ficha" com os dados de um aluno, e um array de fichas.

#include <stdio.h>

#define QTD 5

struct Aluno {
    char nome[30];
    double nota;
};

int main(void) {
    struct Aluno turma[QTD];
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    printf("\n");
    for (int i = 0; i < QTD; i++) {
        printf("%s: %.1f\n", turma[i].nome, turma[i].nota);
    }
    printf("Média da turma: %.2f\n", soma / QTD);
    return 0;
}
struct Aluno { char nome[30]; double nota; };
Cria um tipo novo: cada struct Aluno tem um nome (texto de até 29 letras mais o '\0', veja Strings) e uma nota. O ; depois da } é obrigatório.
struct Aluno turma[QTD];
Um array de 5 fichas. turma[0] é a ficha do primeiro aluno.
turma[i].nome e turma[i].nota
O ponto entra na ficha: "o nome da ficha i".
scanf("%29s %lf", turma[i].nome, &turma[i].nota)
Lê uma palavra e um número. %29s lê no máximo 29 caracteres: sobra lugar para o '\0' e o nome nunca passa do fim do array. O nome não leva &: um array já é o endereço do seu começo (veja Ponteiros e Arrays).
!= 2
Agora o scanf tem de ler dois valores.
$ gcc -std=c23 -Wall -Wextra boletim.c -o boletim
$ ./boletim
=== Boletim da Turma ===
Aluno 1 (nome e nota): Bruno 7.5
Aluno 2 (nome e nota): Carla 9
Aluno 3 (nome e nota): Diego 5.5
Aluno 4 (nome e nota): Elisa 8
Aluno 5 (nome e nota): Gabriel 3.5

Bruno: 7.5
Carla: 9.0
Diego: 5.5
Elisa: 8.0
Gabriel: 3.5
Média da turma: 6.70
$ ./boletim
=== Boletim da Turma ===
Aluno 1 (nome e nota): Bruno 7.5
Aluno 2 (nome e nota): Ana Paula 8

Entrada inválida.

O último teste mostra um limite do %s: ele lê uma palavra, até o primeiro espaço. Em Ana Paula 8, o nome ficou Ana, e o %lf tentou ler Paula como número e falhou. Para nomes com espaço, seria preciso ler a linha com fgets e separar nome e nota (é um dos desafios). Por enquanto, um nome por aluno.

Nunca use %s sem o tamanho

Com scanf("%s", nome), alguém que digite um nome de 100 letras escreve 100 bytes num array de 30, e o programa estraga a memória ao lado (como no cenário da versão 1). É o famoso buffer overflow (veja Segurança). Sempre um a menos que o tamanho do array: char nome[30] → %29s.

Missão (versão 2): o programa lê os cinco alunos, mas só mostra a média. Antes dela, escreva um for que mostra cada aluno no formato Bruno: 7.5, usando turma[i].nome e turma[i].nota com %.1f.

#include <stdio.h>

#define QTD 5

struct Aluno {
    char nome[30];
    double nota;
};

int main(void) {
    struct Aluno turma[QTD];
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    printf("\n");
    // mostre cada aluno no formato "Bruno: 7.5"
    printf("Média da turma: %.2f\n", soma / QTD);
    return 0;
}
#include <stdio.h>

#define QTD 5

struct Aluno {
    char nome[30];
    double nota;
};

int main(void) {
    struct Aluno turma[QTD];
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    printf("\n");
    for (int i = 0; i < QTD; i++) {
        printf("%s: %.1f\n", turma[i].nome, turma[i].nota);
    }
    printf("Média da turma: %.2f\n", soma / QTD);
    return 0;
}

Versão 3 · Maior, menor e aprovados

Agora as estatísticas. Para achar a maior nota, o jeito clássico é: "a maior até agora é a do primeiro aluno; para cada um dos outros, se a nota dele for maior, ele passa a ser o maior". Guardamos a posição (não a nota), porque assim sabemos também o nome. A menor nota é igual, com <. E os aprovados são um contador que sobe quando a nota é pelo menos 6.

#include <stdio.h>

#define QTD 5
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

int main(void) {
    struct Aluno turma[QTD];
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;  // posição do aluno com a maior nota
    int menor = 0;  // posição do aluno com a menor nota
    int aprovados = 0;
    for (int i = 0; i < QTD; i++) {
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        if (turma[i].nota < turma[menor].nota) {
            menor = i;
        }
        if (turma[i].nota >= MEDIA_APROVACAO) {
            aprovados++;
        }
    }

    printf("\nMédia da turma: %.2f\n", soma / QTD);
    printf("Maior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, QTD);
    return 0;
}
#define MEDIA_APROVACAO 6.0
A nota mínima para passar, com nome. Ninguém precisa adivinhar o que é aquele 6.0 no meio do código.
int maior = 0;
A posição do aluno com a maior nota. Começa no primeiro (posição 0).
if (turma[i].nota > turma[maior].nota) maior = i;
Compara com a maior até agora; se ganhou, troca a posição.
if (turma[i].nota >= MEDIA_APROVACAO) aprovados++;
Conta quem passou. >=: quem tirou exatamente 6 também passa.
turma[maior].nome
Com a posição, pegamos qualquer dado da ficha.
$ gcc -std=c23 -Wall -Wextra boletim.c -o boletim
$ ./boletim
=== Boletim da Turma ===
Aluno 1 (nome e nota): Bruno 7.5
Aluno 2 (nome e nota): Carla 9
Aluno 3 (nome e nota): Diego 5.5
Aluno 4 (nome e nota): Elisa 8
Aluno 5 (nome e nota): Gabriel 3.5

Média da turma: 6.70
Maior nota: 9.0 (Carla)
Menor nota: 3.5 (Gabriel)
Aprovados: 3 de 5
E se houver empate?

Com >, o primeiro aluno com a maior nota fica com o título; com >=, ficaria o último. Nenhum dos dois está errado, mas é bom saber qual você escolheu.

Missão (versão 3): o programa já acha a maior nota, mas a menor sai sempre a do Bruno e os aprovados dão 0. No laço, atualize menor (como foi feito com maior, mas com <) e conte os aprovados (nota >= MEDIA_APROVACAO).

#include <stdio.h>

#define QTD 5
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

int main(void) {
    struct Aluno turma[QTD];
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;  // posição do aluno com a maior nota
    int menor = 0;  // posição do aluno com a menor nota
    int aprovados = 0;
    for (int i = 0; i < QTD; i++) {
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        // atualize "menor" e conte os aprovados (nota >= MEDIA_APROVACAO)
    }

    printf("\nMédia da turma: %.2f\n", soma / QTD);
    printf("Maior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, QTD);
    return 0;
}
#include <stdio.h>

#define QTD 5
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

int main(void) {
    struct Aluno turma[QTD];
    double soma = 0.0;

    printf("=== Boletim da Turma ===\n");
    for (int i = 0; i < QTD; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;  // posição do aluno com a maior nota
    int menor = 0;  // posição do aluno com a menor nota
    int aprovados = 0;
    for (int i = 0; i < QTD; i++) {
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        if (turma[i].nota < turma[menor].nota) {
            menor = i;
        }
        if (turma[i].nota >= MEDIA_APROVACAO) {
            aprovados++;
        }
    }

    printf("\nMédia da turma: %.2f\n", soma / QTD);
    printf("Maior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, QTD);
    return 0;
}

Versão 4 · A tabela alinhada

A versão final tem três novidades:

  • A quantidade de alunos é perguntada no começo (de 1 a 30). O array tem 30 posições, mas o programa só usa as qtd primeiras.
  • Uma função situacao devolve o texto "Aprovado", "Recuperação" (de 4 a 5.9) ou "Reprovado".
  • Uma tabela, com as colunas alinhadas pela largura dos formatos do printf.
#include <stdio.h>

#define MAX_ALUNOS 30
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

const char *situacao(double nota) {
    if (nota >= MEDIA_APROVACAO) {
        return "Aprovado";
    } else if (nota >= 4.0) {
        return "Recuperação";
    }
    return "Reprovado";
}

int main(void) {
    struct Aluno turma[MAX_ALUNOS];
    int qtd = 0;

    printf("=== Boletim da Turma ===\n");
    printf("Quantos alunos (1 a %d)? ", MAX_ALUNOS);
    if (scanf("%d", &qtd) != 1 || qtd < 1 || qtd > MAX_ALUNOS) {
        printf("\nQuantidade inválida.\n");
        return 1;
    }

    double soma = 0.0;
    for (int i = 0; i < qtd; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;
    int menor = 0;
    int aprovados = 0;
    printf("\n%-10s %5s  %s\n", "Aluno", "Nota", "Situação");
    printf("---------- -----  -----------\n");
    for (int i = 0; i < qtd; i++) {
        printf("%-10s %5.1f  %s\n", turma[i].nome, turma[i].nota, situacao(turma[i].nota));
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        if (turma[i].nota < turma[menor].nota) {
            menor = i;
        }
        if (turma[i].nota >= MEDIA_APROVACAO) {
            aprovados++;
        }
    }
    printf("---------- -----  -----------\n");
    printf("%-11s %5.1f\n", "Média", soma / qtd);  // 11: o "é" ocupa 2 bytes
    printf("\nMaior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, qtd);
    return 0;
}
FormatoO que fazExemplo
%-10sTexto numa coluna de 10 posições, encostado à esquerda (o -); o resto é preenchido com espaços.[Bruno ]
%10sO mesmo, encostado à direita.[ Bruno]
%5.1fNúmero com 1 casa decimal numa coluna de 5 posições, à direita: os pontos decimais ficam um embaixo do outro.[ 7.5]
%5sO título Nota com a mesma largura dos números.[ Nota]
const char *situacao(double nota)
A função devolve um texto fixo: um ponteiro para o primeiro caractere de "Aprovado", por exemplo (veja Ponteiros e Strings). O const avisa que ninguém deve mudar esse texto.
scanf("%d", &qtd) != 1 || qtd < 1 || qtd > MAX_ALUNOS
Recusa a quantidade se não for número, ou se for menor que 1 ou maior que 30. Sem isso, qtd = 50 faria o laço escrever além do array.
"%-11s %5.1f\n", "Média"
Por que 11 e não 10? Veja o cenário logo abaixo.
$ gcc -std=c23 -Wall -Wextra boletim.c -o boletim
$ ./boletim
=== Boletim da Turma ===
Quantos alunos (1 a 30)? 5
Aluno 1 (nome e nota): Bruno 7.5
Aluno 2 (nome e nota): Carla 9
Aluno 3 (nome e nota): Diego 5.5
Aluno 4 (nome e nota): Elisa 8
Aluno 5 (nome e nota): Gabriel 3.5

Aluno       Nota  Situação
---------- -----  -----------
Bruno        7.5  Aprovado
Carla        9.0  Aprovado
Diego        5.5  Recuperação
Elisa        8.0  Aprovado
Gabriel      3.5  Reprovado
---------- -----  -----------
Média        6.7

Maior nota: 9.0 (Carla)
Menor nota: 3.5 (Gabriel)
Aprovados: 3 de 5
Acentos desalinham a tabela

Com %-10s na linha da média, a Ana viu isto:

Gabriel      3.5  Reprovado
---------- -----  -----------
Média       6.7

E com alunos de nome acentuado, mesmo com a correção da média:

$ ./boletim
=== Boletim da Turma ===
Quantos alunos (1 a 30)? 3
Aluno 1 (nome e nota): Bruno 7.5
Aluno 2 (nome e nota): Fábio 6
Aluno 3 (nome e nota): Lúcia 8

Aluno       Nota  Situação
---------- -----  -----------
Bruno        7.5  Aprovado
Fábio       6.0  Aprovado
Lúcia       8.0  Aprovado
---------- -----  -----------
Média        7.2

Maior nota: 8.0 (Lúcia)
Menor nota: 6.0 (Fábio)
Aprovados: 3 de 3

O que aconteceu: o printf conta bytes, não letras. No UTF-8, letras acentuadas como é, á e ú ocupam 2 bytes. "Fábio" tem 5 letras, mas 6 bytes; o %-10s acha que já usou 6 posições e completa com só 4 espaços. Na tela, a coluna fica um passo para a esquerda.

Como consertar: para um texto fixo, como "Média", aumente a largura no número de letras acentuadas: %-11s. Para nomes digitados, conte as letras de verdade e complete com espaços você mesmo (é o desafio 3). A aula Acentos e UTF-8 explica os bytes.

Missão (versão 4): a tabela sai toda torta: Bruno 7.5 Aprovado. Use larguras nos dois printf da tabela: "\n%-10s %5s %s\n" no título e "%-10s %5.1f %s\n" nas linhas dos alunos (repare nos dois espaços antes da situação). A entrada da missão é a turma da Ana.

#include <stdio.h>

#define MAX_ALUNOS 30
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

const char *situacao(double nota) {
    if (nota >= MEDIA_APROVACAO) {
        return "Aprovado";
    } else if (nota >= 4.0) {
        return "Recuperação";
    }
    return "Reprovado";
}

int main(void) {
    struct Aluno turma[MAX_ALUNOS];
    int qtd = 0;

    printf("=== Boletim da Turma ===\n");
    printf("Quantos alunos (1 a %d)? ", MAX_ALUNOS);
    if (scanf("%d", &qtd) != 1 || qtd < 1 || qtd > MAX_ALUNOS) {
        printf("\nQuantidade inválida.\n");
        return 1;
    }

    double soma = 0.0;
    for (int i = 0; i < qtd; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;
    int menor = 0;
    int aprovados = 0;
    printf("\n%s %s %s\n", "Aluno", "Nota", "Situação");
    printf("---------- -----  -----------\n");
    for (int i = 0; i < qtd; i++) {
        printf("%s %.1f %s\n", turma[i].nome, turma[i].nota, situacao(turma[i].nota));
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        if (turma[i].nota < turma[menor].nota) {
            menor = i;
        }
        if (turma[i].nota >= MEDIA_APROVACAO) {
            aprovados++;
        }
    }
    printf("---------- -----  -----------\n");
    printf("%-11s %5.1f\n", "Média", soma / qtd);  // 11: o "é" ocupa 2 bytes
    printf("\nMaior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, qtd);
    return 0;
}
#include <stdio.h>

#define MAX_ALUNOS 30
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

const char *situacao(double nota) {
    if (nota >= MEDIA_APROVACAO) {
        return "Aprovado";
    } else if (nota >= 4.0) {
        return "Recuperação";
    }
    return "Reprovado";
}

int main(void) {
    struct Aluno turma[MAX_ALUNOS];
    int qtd = 0;

    printf("=== Boletim da Turma ===\n");
    printf("Quantos alunos (1 a %d)? ", MAX_ALUNOS);
    if (scanf("%d", &qtd) != 1 || qtd < 1 || qtd > MAX_ALUNOS) {
        printf("\nQuantidade inválida.\n");
        return 1;
    }

    double soma = 0.0;
    for (int i = 0; i < qtd; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;
    int menor = 0;
    int aprovados = 0;
    printf("\n%-10s %5s  %s\n", "Aluno", "Nota", "Situação");
    printf("---------- -----  -----------\n");
    for (int i = 0; i < qtd; i++) {
        printf("%-10s %5.1f  %s\n", turma[i].nome, turma[i].nota, situacao(turma[i].nota));
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        if (turma[i].nota < turma[menor].nota) {
            menor = i;
        }
        if (turma[i].nota >= MEDIA_APROVACAO) {
            aprovados++;
        }
    }
    printf("---------- -----  -----------\n");
    printf("%-11s %5.1f\n", "Média", soma / qtd);  // 11: o "é" ocupa 2 bytes
    printf("\nMaior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, qtd);
    return 0;
}

O programa completo

A versão final, igual à versão 4. Compile com gcc -std=c23 -Wall -Wextra boletim.c -o boletim:

#include <stdio.h>

#define MAX_ALUNOS 30
#define MEDIA_APROVACAO 6.0

struct Aluno {
    char nome[30];
    double nota;
};

const char *situacao(double nota) {
    if (nota >= MEDIA_APROVACAO) {
        return "Aprovado";
    } else if (nota >= 4.0) {
        return "Recuperação";
    }
    return "Reprovado";
}

int main(void) {
    struct Aluno turma[MAX_ALUNOS];
    int qtd = 0;

    printf("=== Boletim da Turma ===\n");
    printf("Quantos alunos (1 a %d)? ", MAX_ALUNOS);
    if (scanf("%d", &qtd) != 1 || qtd < 1 || qtd > MAX_ALUNOS) {
        printf("\nQuantidade inválida.\n");
        return 1;
    }

    double soma = 0.0;
    for (int i = 0; i < qtd; i++) {
        printf("Aluno %d (nome e nota): ", i + 1);
        if (scanf("%29s %lf", turma[i].nome, &turma[i].nota) != 2) {
            printf("\nEntrada inválida.\n");
            return 1;
        }
        soma += turma[i].nota;
    }

    int maior = 0;
    int menor = 0;
    int aprovados = 0;
    printf("\n%-10s %5s  %s\n", "Aluno", "Nota", "Situação");
    printf("---------- -----  -----------\n");
    for (int i = 0; i < qtd; i++) {
        printf("%-10s %5.1f  %s\n", turma[i].nome, turma[i].nota, situacao(turma[i].nota));
        if (turma[i].nota > turma[maior].nota) {
            maior = i;
        }
        if (turma[i].nota < turma[menor].nota) {
            menor = i;
        }
        if (turma[i].nota >= MEDIA_APROVACAO) {
            aprovados++;
        }
    }
    printf("---------- -----  -----------\n");
    printf("%-11s %5.1f\n", "Média", soma / qtd);  // 11: o "é" ocupa 2 bytes
    printf("\nMaior nota: %.1f (%s)\n", turma[maior].nota, turma[maior].nome);
    printf("Menor nota: %.1f (%s)\n", turma[menor].nota, turma[menor].nome);
    printf("Aprovados: %d de %d\n", aprovados, qtd);
    return 0;
}

O que o projeto juntou: arrays, for, structs, scanf com largura, #define, uma função que devolve texto e os formatos do printf.

Rodando no laboratório

Em vez de digitar os alunos toda vez, a Ana guardou a turma num arquivo de texto e mandou o arquivo para a entrada do programa com < (veja stdin, stdout e stderr). Como ninguém digitou nada, as respostas não aparecem depois das perguntas:

$ cat turma.txt
4
Bruno 7.5
Carla 9
Diego 5.5
Elisa 8
$ ./boletim < turma.txt
=== Boletim da Turma ===
Quantos alunos (1 a 30)? Aluno 1 (nome e nota): Aluno 2 (nome e nota): Aluno 3 (nome e nota): Aluno 4 (nome e nota): 
Aluno       Nota  Situação
---------- -----  -----------
Bruno        7.5  Aprovado
Carla        9.0  Aprovado
Diego        5.5  Recuperação
Elisa        8.0  Aprovado
---------- -----  -----------
Média        7.5

Maior nota: 9.0 (Carla)
Menor nota: 5.5 (Diego)
Aprovados: 3 de 4

Desafios extras

  1. Duas notas por aluno. Troque double nota por double notas[2] (prova e trabalho) e mostre a média de cada aluno numa coluna nova.
  2. Nomes com espaço. Leia cada aluno com fgets, ache o último espaço da linha com strrchr, converta o que vem depois com strtod e copie o que vem antes como nome (veja Funções de String).
  3. Alinhar com acentos. Escreva uma função que conta as letras de uma string UTF-8 (conte só os bytes em que (c & 0xC0) != 0x80) e use-a para completar a coluna do nome com espaços.
  4. Em ordem de nota. Mostre a tabela da maior para a menor nota com qsort (veja Ordenação).
  5. Turma de qualquer tamanho. Troque o array fixo de 30 por um array criado com malloc do tamanho exato (veja malloc e free).

Exercícios

1. Um array declarado como double notas[5] tem quais posições válidas?

2. Por que o scanf usa %29s para um char nome[30]?

3. O que printf("[%-6s]", "Ana") mostra?

4. Por que o programa guarda a posição da maior nota em vez da nota?

5. Complete o formato que mostra uma nota com uma casa decimal numa coluna de 5 posições:

printf("", 7.5);

6. Complete o acesso à nota do terceiro aluno do array turma:

turma[].nota

Resumo

  • Um array guarda muitos valores do mesmo tipo; as posições vão de 0 a tamanho - 1, e o laço é i < tamanho.
  • Passar do fim do array não dá erro de compilação: estraga a memória ao lado. O -fsanitize=address mostra a linha.
  • Uma struct junta os dados de uma coisa (nome e nota); um array de structs guarda a turma.
  • %29s lê uma palavra sem passar de 29 letras; %s para no primeiro espaço.
  • Maior e menor: comece pelo primeiro e troque quando achar um melhor; guarde a posição.
  • %-10s alinha texto à esquerda, %5.1f alinha números; o printf conta bytes, então acentos desalinham.

Próximo projeto: Agenda de Contatos.