You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用fgets读取文件结构体时最后一条数据重复问题求助

问题:读取员工文件时最后一条数据重复显示

我用C语言编写了一个员工CRUD(西班牙语称ABM)程序,在使用fgets读取empleados.txt文件中的Empleado结构体数据并展示时,出现最后一条结构体数据重复显示的问题。以下是程序代码、文件内容及异常情况:

程序代码

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

// ==== Estructura Empleado ====
typedef struct
{
  int legajo; // Numero de 6 digitos
  char nombreApellido[30];
  float sueldo; // Aleatorio entre 80k-200k
} Empleado;

// Carga un empleado por teclado; Devuelve un struct Empleado
Empleado cargarEmpleadoPorTeclado();

// Carga un vector de Empleado
void cargarEmpleadosPorTeclado(Empleado empleados[], int cantidadEmpleados);

// Recibe un vector de chars y devuelve un struct Empleado
Empleado parsearEmpleado(char datosSinParsear[50], int largoDatos);

// Carga los empleados desde el archivo al vector empleados[]
void cargarEmpleadosEnVector(Empleado empleados[], int cantidadEmpleados);

// Guarda los empleados en el archivo 'empleados.txt'
void guardarEmpleados(Empleado empleados[], int cantidadEmpleados);

// Muestra los empleados cargados en el vector empleados[]
void mostrarEmpleadosDesdeArchivo(int cantidadEmpleados, int eliminados);

// Busca un empleado por legajo en el archivo 'empleados.txt' y lo 'elimina'
void eliminarEmpleadoPorLegajo(Empleado empleados[], int legajo, int cantidadEmpleados, int * eliminados);

void eliminarEmpleadoVectorPorLegajo(Empleado empleados[], int legajo, int cantidadEmpleados, int *eliminados);

void menu(Empleado empleados[], int cantidadEmpleados);

/* ==== Funcion main ==== */
int main()
{
  int cantidadEmpleados = 100;
  Empleado empleados[cantidadEmpleados];

  menu(empleados, cantidadEmpleados);

  return 0;
}

/* ==== Implementaciones ==== */

Empleado cargarEmpleadoPorTeclado()
{
  Empleado empleado;

  printf("\nIngrese el Nombre y Apellido del empleado: ");
  fflush(stdin);
  gets(empleado.nombreApellido);

  printf("\nIngrese el numero de Legajo del empleado: ");
  fflush(stdin);
  scanf("%d", &empleado.legajo);

  empleado.sueldo = 80000 + rand() % (200000-80000);

  return empleado;
}

void cargarEmpleadosPorTeclado(Empleado empleados[], int cantidadEmpleados)
{
  for (int i = 0; i < cantidadEmpleados; i++)
  {
    empleados[i] = cargarEmpleadoPorTeclado();
  }
  guardarEmpleados(empleados, cantidadEmpleados);
}

Empleado parsearEmpleado(char datosSinParsear[], int largoDatos)
{
  Empleado empleado;

  int delimitador1 = -1;
  int delimitador2 = -1;

  char auxiliarLegajo[10] = " ";
  char auxiliarNombreApellido[30] = " "; 
  char auxiliarSueldo[10] = " ";

  for(int i = 0; i < largoDatos; i++)
  {
    // Recorro el string hasta encontrar la primera ocurrencia
    // Notese que i = largoDatos es para forzar la salida del for y no sobreescribir el valor de delimitador1
    // con la segunda ocurrencia
    if(datosSinParsear[i] == ';')
    {
      delimitador1 = i;
      i = largoDatos;
    }
  }
  // Recorro el string a partir de la posicion siguiente de la primera ocurrencia
  for (int i = delimitador1 + 1; i < largoDatos; i++)
  {
    if (datosSinParsear[i] == ';')
    {
      delimitador2 = i;
    }
  }

  if ((delimitador1 != -1) && (delimitador2 != -1))
  {
    // Desde 0 hasta delimitador1 tenemos el legajo
    for (int i = 0; i < delimitador1; i++)
    {
      auxiliarLegajo[i] = datosSinParsear[i];
    }

    // Desde delimitador1+1 hasta delimitador2 tenemos el nombre y apellido
    for (int i = delimitador1 + 1; i < delimitador2; i++)
    {
      auxiliarNombreApellido[i - delimitador1 - 1] = datosSinParsear[i];
    }

    // Desde delimitador2+1 hasta largoDatos tenemos el sueldo en float
    for (int i = delimitador2 + 1; i < strlen(datosSinParsear); i++)
    {
      auxiliarSueldo[i - delimitador2 - 1] = datosSinParsear[i];
    }

    empleado.legajo = atoi(auxiliarLegajo);
    strcpy(empleado.nombreApellido, auxiliarNombreApellido);
    empleado.sueldo = atof(auxiliarSueldo);

    return empleado;
  }
}

void cargarEmpleadosEnVector(Empleado empleados[], int cantidadEmpleados)
{
  // Intentamos abrir el archivo y verificamos si no es un puntero nulo
  FILE * archivoEmpleados = fopen("empleados.txt", "r");

  if(archivoEmpleados != NULL)
  {
    int i = 0;

    while (!feof(archivoEmpleados) && (i < cantidadEmpleados))
    {
      char datosSinParsear[50] = "";
      Empleado empleadoActual;

      // Leemos los datos y lo parseamos
      fgets(datosSinParsear, 50, archivoEmpleados);
      empleadoActual = parsearEmpleado(datosSinParsear, 50);

      // Lo asignamos a la posicion actual del vector 'empleados[]'
      empleados[i] = empleadoActual;

      i++;
    }
    fclose(archivoEmpleados);
  }

  // Si el archivo no existe o no lo encuentra
  else 
  {
    printf("\nImposible leer los empleados\nEl archivo \"empleados.txt\" no existe o no se encuentra en la ruta actual");
    exit(-1);
  }
}

void guardarEmpleados(Empleado empleados[], int cantidadEmpleados)
{
    // Intentamos abrir el archivo y verificamos si no es un puntero nulo
  FILE * archivoEmpleados = fopen("empleados.txt", "w");

  if(archivoEmpleados != NULL)
  {
    for (int i = 0; i < cantidadEmpleados; i++)
    {
      // Grabamos los datos linea por linea con el formato 'legajo;nombreapellido;sueldo'
      fprintf(archivoEmpleados, "%d;%s;%.2f\n", empleados[i].legajo, empleados[i].nombreApellido, empleados[i].sueldo);
    }
    printf("\nEmpleados guardados exitosamente en \"empleados.txt\"");
    fclose(archivoEmpleados);
  }

  // Si el archivo no existe o no lo encuentra
  else 
  {
    printf("\nImposible guardar los empleados\nEl archivo \"empleados.txt\" no existe o no se encuentra en la ruta actual");
    exit(-1);
  }
}

void mostrarEmpleadosDesdeArchivo(int cantidadEmpleados, int eliminados)
{
  FILE * archivoEmpleados = fopen("empleados.txt", "r");

  int i = 0;

  while(!feof(archivoEmpleados) && i < (cantidadEmpleados-eliminados))
  {
    char datosSinParsear[50] = "";
    fgets(datosSinParsear, 50, archivoEmpleados);

    Empleado empleadoActual;

    empleadoActual = parsearEmpleado(datosSinParsear, 50);
    
    printf("\nEmpleado");
    printf("\n\t* Numero de legajo: %d", empleadoActual.legajo);
    printf("\n\t* Nombre y Apellido: %s", empleadoActual.nombreApellido);
    printf("\n\t* Sueldo (en pesos): %.2f", empleadoActual.sueldo);
    
    i++;
  }
  fclose(archivoEmpleados);
}

/*
Esta funcion no elimina en si la linea que pertenezca al empleado
Sino mas bien, la omitiria cuando la lea, escribiendo un archivo nuevo con el mismo nombre
De esa forma, estariamos haciendo lo mismo
*/
void eliminarEmpleadoPorLegajo(Empleado empleados[], int legajo, int cantidadEmpleados, int * eliminados)
{
  // Intentamos abrir el archivo y verificamos si no es un puntero nulo
  FILE * archivoEmpleados = fopen("empleados.txt", "r");

  // Creamos un nuevo archivo para escribir los datos
  FILE * nuevoArchivoEmpleados = fopen("empleados2.txt", "w");

  if(archivoEmpleados != NULL)
  {
    int i = 0;
    while (!feof(archivoEmpleados) && i < cantidadEmpleados)
    {
      char datosSinParsear[50] = "";
      Empleado empleadoActual;

      // Recibimos los datos y los parseamos
      fgets(datosSinParsear, 50, archivoEmpleados);
      empleadoActual = parsearEmpleado(datosSinParsear, 50);

      /*
      Cuando no coincida el legajo, escribe
      Si es que llega a coincidir (justamente ese debemos eliminar), no lo escribimos,
      simplemente, hacemos una iteración y continuamos el bucle
      */      
      if (empleadoActual.legajo != legajo)
      {
        fprintf(nuevoArchivoEmpleados, "%d;%s;%.2f\n", empleadoActual.legajo, empleadoActual.nombreApellido, empleadoActual.sueldo);
        i++;
      }
      else
      {
        i++;
        continue;
      };
    }
    fclose(archivoEmpleados);
    fclose(nuevoArchivoEmpleados);

    remove("empleados.txt");
    rename("empleados2.txt", "empleados.txt");

    eliminarEmpleadoVectorPorLegajo(empleados, legajo, cantidadEmpleados, &eliminados);

    *eliminados = *eliminados + 1;

    printf("\nEmpleado con legajo numero %d eliminado exitosamente", legajo);
  }

  // Si el archivo no existe o no lo encuentra
  else 
  {
    printf("\nImposible eliminar al empleado\nEl archivo \"empleados.txt\" no existe o no se encuentra en la ruta actual");
    exit(-1);
  }
}

void eliminarEmpleadoVectorPorLegajo(Empleado empleados[], int legajo, int cantidadEmpleados, int *eliminados)
{
  int posicionDelElemento = -1;

  for (int i = 0; i < cantidadEmpleados; i++)
  {
    if(empleados[i].legajo == legajo)
    {
      posicionDelElemento = i;
      i = cantidadEmpleados;
    }
  }

  for(int i = posicionDelElemento - 1; i<cantidadEmpleados-1; i++)
  {
    empleados[i] = empleados[i + 1];
  }
  cantidadEmpleados--;
}

void menu(Empleado empleados[], int cantidadEmpleados)
{
  int sigue = 1;
  int eliminados = 0;
  int cantidadEmpleadosVar = 0;

  printf("\nMenu Empleados\n");
  printf("\nOpciones:\n");
  printf("\n\t1. Listar los empleados");
  printf("\n\t2. Agregar un empleado");
  printf("\n\t3. Eliminar un empleado");
  printf("\n\t4. Salir");

  do
  {
    int opcion;
    int legajo;

    printf("\nIngrese opcion > ");
    scanf("%d", &opcion);

    switch (opcion)
    {
    case 1:
      printf("\nMostrando empleados cargados");
      mostrarEmpleadosDesdeArchivo(cantidadEmpleados, eliminados);
      break;

    case 2:
      printf("\nAgregar un empleado:");
      printf("\nCuantos empleados desea cargar? > ");
      
      scanf("%d", &cantidadEmpleadosVar);
      cargarEmpleadosPorTeclado(empleados, cantidadEmpleadosVar);
      break;
    
    case 3:
      printf("\nEliminando un empleado:");
      printf("\nIngrese el numero de legajo del empleado a eliminar > ");
      scanf("%d", &legajo);
      eliminarEmpleadoPorLegajo(empleados, legajo, cantidadEmpleados, &eliminados);
      break;

    case 4:
      sigue = 0;
      break;

    default:
      printf("\nLa opcion \"%d\" no es valida, ingrese otra vez\n\n", opcion);
      continue;
      break;
    }
  } while (sigue);
}

empleados.txt文件内容

555666;Leandro;80041.00
232433;Juan;80945.00
234443;Antonio;90485.00
119304;Manuel;102943.00

错误原因分析

核心问题是错误使用feof()作为循环终止条件。feof()的工作逻辑是:只有当程序尝试读取超过文件末尾之后,它才会返回true;而当程序刚好到达文件末尾时,feof()仍然返回false。

以mostrarEmpleadosDesdeArchivo函数的循环为例:
当读取完最后一行数据后,feof()还是false,循环会继续执行一次。这次fgets()读取失败,返回NULL,但datosSinParsear数组里还保留着上一次读取的最后一行数据,解析后就会重复打印最后一条员工记录。

此外,其他涉及文件读取的函数(如cargarEmpleadosEnVector、eliminarEmpleadoPorLegajo)也存在同样的feof()使用错误,可能会引发类似的重复读取或数据异常问题。

另外,parsearEmpleado函数没有处理解析失败的场景(比如读取到空字符串时),会返回未初始化的结构体,但在这个案例中因为数组保留了上一次的数据,所以表现为重复最后一条记录。

解决方案

正确的做法是先执行读取操作,再根据读取结果判断是否终止循环,把fgets()的返回值作为循环条件:

修改mostrarEmpleadosDesdeArchivo函数的循环部分:

void mostrarEmpleadosDesdeArchivo(int cantidadEmpleados, int eliminados)
{
  FILE * archivoEmpleados = fopen("empleados.txt", "r");
  if (archivoEmpleados == NULL) {
      printf("\nImposible leer los empleados\nEl archivo \"empleados.txt\" no existe o no se encuentra en la ruta actual");
      return;
  }

  int i = 0;
  char datosSinParsear[50] = "";
  while(fgets(datosSinParsear, 50, archivoEmpleados) != NULL && i < (cantidadEmpleados-eliminados))
  {
    Empleado empleadoActual = parsearEmpleado(datosSinParsear, 50);
    
    printf("\nEmpleado");
    printf("\n\t* Numero de legajo: %d", empleadoActual.legajo);
    printf("\n\t* Nombre y Apellido: %s", empleadoActual.nombreApellido);
    printf("\n\t* Sueldo (en pesos): %.2f", empleadoActual.sueldo);
    
    i++;
  }
  fclose(archivoEmpleados);
}

同样的修改逻辑需要应用到其他使用feof()的文件读取循环中。

额外建议:给parsearEmpleado函数添加错误处理逻辑,比如当找不到分隔符时,返回一个标识错误的结构体(例如将`

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 09:55:21