Erros muito estranhos... Linguagem C e arquivos

Com a ajuda do pessoal do GUJ, consegui fazer um programa para calcular o índice de massa corpórea e gravar os dados. Porém quando tento colocar em ordem alfabética acontecem algumas coisas estranhas.
Ao gravar o arquivo imc2.txt ele perde o imc e um dos nomes (estou testando então são apenas quatro pessoas).

veja como ele calcula corretamente o imc e lista na tela.

Agora veja o arquivo que deveria conter os nomes das quatro pessoas em ordem alfabética, mas tem erros e o imc se perdeu, o que aconteceu?

Nome: Airton Senna
IMC: 25.319
Nome: Airton Senna
IMC: 0.000
Nome: Juan Pablo Montoia
IMC: 0.000
Nome: Lewwis Hamilton
IMC: 0.000

Veja também o primeiro arquivo imc.txt, com os dados gravados corretamente:

Ronaldo Rodrigues Godoi
48 82.500 1.700
Juan Pablo Montoia
45 80.321 1.720
Lewwis Hamilton
32 78.394 1.740
Airton Senna
57 68.932 1.650

O programa grava em uma linha idade, peso e altura e na primeira linha o nome. O código do programa está abaixo:

/*

   Programa de manipulação de arquivo
   Ronaldo Rodrigues Godoi
   
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_LISTA 4

void limpa(FILE *fp) {
	int c;
	while((c=fgetc(fp)) != EOF && c != '\n') ;	
}

struct registro {
	char nome[81];
	int idade;
	double peso;
	double altura;
	double imc;
};

struct registro pessoas[NUM_LISTA];

void ordena_nome() {
	
	char aux_nome[81];
	int aux_idade = 0;
	double aux_peso = 0;
	double aux_altura = 0;
	double aux_imc = 0;
	
	for(int i = 0; i < NUM_LISTA; i++) {
		printf("\n\nNome: %s\n i: %d", pessoas[i].nome, i);
	}
	
	scanf("%d", &aux_idade);
	
	for(int i = 0; i < NUM_LISTA; i++) {
		for(int j = i + 1; j < NUM_LISTA; j++) {
			int r = strcmp(pessoas[i].nome, pessoas[j].nome);
			
			printf("\n\nAAAA Nomes: \n %-80s\n %-80s\n %d %d", pessoas[i].nome, pessoas[j].nome, i, j);
			
			if(r > 0) {
				
				printf("\n\nNome: %-80s\nIMC: %.3f", pessoas[i].nome, pessoas[i].imc);
				
				strcpy(aux_nome, pessoas[i].nome);
				strcpy(pessoas[i].nome, pessoas[j].nome);
				strcpy(pessoas[j].nome, aux_nome);
				
				aux_idade = pessoas[i].idade;
				pessoas[i].idade = pessoas[j].idade;
				pessoas[j].idade = aux_idade;
				
				aux_peso = pessoas[i].peso;
				pessoas[i].peso = pessoas[j].peso;
				pessoas[j].peso = aux_peso;
				
				aux_altura = pessoas[i].altura;
				pessoas[i].altura = pessoas[j].altura;
				pessoas[j].altura = aux_altura;
				
				aux_imc = pessoas[i].imc;
				pessoas[i].imc = pessoas[j].imc;
				pessoas[j].imc = aux_imc;
			}
		}
	}
}

main() {
	
	printf("\n\nCalculo do IMC");
	char entrada;
	do {
		printf("\n\nFaz entrada de dados? (S/N)");
		scanf("%c", &entrada);
	} while(entrada != 'S' && entrada != 's' && entrada != 'N' && entrada != 'n');
	
	if(entrada == 's' || entrada == 'S') {
		printf("Dados para o arquivo: \n");
		for(int i = 0; i < NUM_LISTA; i++) {
			limpa(stdin);
			printf("\n\n Nome: ");
			scanf("%80[^\n]", pessoas[i].nome);
			limpa(stdin);
			printf("\n Idade: ");
			scanf("%d", &pessoas[i].idade);
			limpa(stdin);
			printf("\n Peso: ");
			scanf("%lf", &pessoas[i].peso);
			limpa(stdin);
			printf("\n Altura: ");
			scanf("%lf", &pessoas[i].altura);
		}
	}
	
	FILE *ponteiro;
	ponteiro = fopen("imc.txt", "a");
	if(!ponteiro) {
		printf("\nErro ao abrir arquivo!\n");
		fclose(ponteiro);
		exit(0);
	}
	else
		printf("\nArquivo aberto com sucesso\n");
		
	if(entrada == 's' || entrada == 'S') {
		for(int i = 0; i < NUM_LISTA; i++) {
			fprintf(ponteiro, "%s\n%d %.3f %.3f\n", pessoas[i].nome, pessoas[i].idade, pessoas[i].peso, pessoas[i].altura);
		}
	}
	
	int registrosLidos = 0;
	char buffer[81];
	fclose(ponteiro);
	ponteiro = fopen("imc.txt", "r");
	if(!ponteiro) {
		printf("\nErro ao abrir arquivo!\n");
		fclose(ponteiro);
		exit(0);
	}
	else
		printf("\nArquivo aberto com sucesso, segunda vez.\n");
	
	while (1) {
		
		if(! fgets(buffer, 80, ponteiro)) {
			printf("\n\nNão consegui ler o nome...2");
			break;
		}
		
		buffer[strcspn(buffer, "\n")] = '\0';
		strcpy(pessoas[registrosLidos].nome,buffer);
		
		if(! fgets(buffer, 80, ponteiro)) {
			printf("\n\nNão consegui ler os numeros...3");
			break;
		}
		
		if(sscanf(buffer, "%d %lf %lf", &pessoas[registrosLidos].idade,
									 	&pessoas[registrosLidos].peso,
									 	&pessoas[registrosLidos].altura) !=3) {
	 		break;
											 }
											 
		pessoas[registrosLidos].imc = pessoas[registrosLidos].peso / (pessoas[registrosLidos].altura * pessoas[registrosLidos].altura);
		
		printf("\n\nNome: %-80s\nIdade: %d\nPeso: %.3f\nAltura: %.3f\n\nIMC: %.3f\n", 
		pessoas[registrosLidos].nome,
		pessoas[registrosLidos].idade,
		pessoas[registrosLidos].peso,
		pessoas[registrosLidos].altura,
		pessoas[registrosLidos].imc);
		
	}
	
	ordena_nome();
	
	FILE *ponteiro2;
	ponteiro2 = fopen("imc2.txt", "w");
	if(!ponteiro2) {
		printf("\nErro ao abrir/criar segundo arquivo!\n");
		fclose(ponteiro);
		exit(0);
	}
	else
		printf("\nSegundo Arquivo aberto/criado com sucesso\n");
	for(int i = 0; i < NUM_LISTA; i++) {
		fprintf(ponteiro2, "Nome: %s\nIMC: %.3f\n", pessoas[i].nome, pessoas[i].imc);		
	}
	
	fclose(ponteiro);
	fclose(ponteiro2);
	return(0);
	
}

Bom, não entendo porque os dados ficam embaralhados. Pode ser erro em alguma máscara ou de ordenação. Se alguém puder me ajudar eu agradeço.

Atenciosamente,
Ronaldo

Ao ler do arquivo, faltou incrementar a variável registrosLidos:

while (1) {
    // fgets, sscanf, print, etc...

    // no final, faltou isso aqui:
    registrosLidos++;
}

Sem isso, todos os registros eram colocados na posição zero.


Dito isso, tem um jeito mais simples de ordenar, que é usar a função nativa qsort:

int compararPessoasPorNome(const void *a, const void *b) {
    return strcmp(((struct registro *) a)->nome, ((struct registro *) b)->nome);
}

void ordena_nome(struct registro *pessoas) {
    qsort(pessoas, NUM_LISTA, sizeof(struct registro), compararPessoasPorNome);
}

Para isso você precisa criar outra função que faz a comparação entre dois registros. No caso, é a função compararPessoasPorNome, que compara duas pessoas pelo nome (usando a já existente strcmp), que deve retornar -1 se o primeiro valor é “menor” (ou seja, se deve vir antes na ordenação), 1 se o primeiro valor é “maior” (se deve vir depois) ou zero se tanto faz. Como strcmp já faz isso para strings, aproveitei ela, passando os nomes como parâmetros.

A sintaxe é meio chata porque os argumentos devem ser void *, então precisa do cast para a sua struct.

E repare que agora eu passo o array como parâmetro (melhor, porque senão a função só funciona para o struct global que você declarou fora do main - já passando como parâmetro ela funciona para qualquer outro array). Para chamar a função, basta passar o array para ela, assim:

ordena_nome(pessoas);
1 curtida

Muito obrigado!!! Está funcionando.

Atenciosamente,
Ronaldo

Outro detalhe: usar qsort como sugerido acima é mais eficiente porque ela trabalha com ponteiros em vez da struct em si. Ou seja, em vez de copiar todos os dados da struct (o que é bem ineficiente, ainda mais se você precisa fazer várias vezes), ela só troca os ponteiros (os endereços de memória), muito mais eficiente.


Claro que se quiser implementar na mão a função de ordenação, pode. Mas em vez de copiar todos os dados da struct na mão, pode ser assim:

void ordena_nome(struct registro *pessoas) {
    for (int i = 0; i < NUM_LISTA - 1; i++)
        for (int j = 0; j < NUM_LISTA - i - 1; j++)
            if (strcmp(pessoas[j].nome, pessoas[j + 1].nome) > 0) {
                struct registro tmp = pessoas[j];
                pessoas[j] = pessoas[j + 1];
                pessoas[j + 1] = tmp;
            }
}
1 curtida