C語言中文網 目錄

C語言文件隨機訪問fseek()和ftell()函數

文件隨機訪問是指在某個文件內直接讀寫任何給定位置數據的能力。通過獲取與設定文件位置指示符可以實現這一功能,文件位置指示符指定了文件中的當前訪問位置,該文件與一個給定的流關聯。

獲取當前文件位置

下面的函數返回當前文件的訪問位置。當需要標記文件中的位置,以便以后返回到該位置時,可以使用下面的函數。
long ftell(FILE*fp);
ftell()返回 fp 流的文件位置。對一個二進制流來說,它與該位置之前的字符數量是相同的,也就是當前字符位置距離文件頭部的偏差。當發生錯誤時,ftell()返回 -1。

int fgetpos(FILE*restrict fp,fpos_t*restrict ppos);
fgetpos()將 fp 流的文件位置指示符寫入 ppos 所引用的對象,該對象類型為 fpos_t。如果 fp 是一個寬字符導向流,那么 fgetpos()所存儲的指示符也會包含流當前的轉換狀態。當發生錯誤時,fgetpos()返回非 0 值;當執行成功時,返回 0。

下面的示例記錄文件 messages.txt 中以 # 字符開頭的所有行的位置:
#define ARRAY_LEN 1000
long arrPos[ARRAY_LEN] = { 0L };
FILE *fp = fopen( "messages.txt", "r" );
if ( fp != NULL)
{
  int i = 0, c1 = '\n', c2;
  while ( i < ARRAY_LEN && ( c2 = getc(fp) ) != EOF )
  {
    if ( c1 == '\n' && c2 == '#' )
      arrPos[i++] = ftell( fp ) - 1;
    c1 = c2;
  }
  /* ... */
}

設置文件訪問位置

下面的函數修改文件位置指示符。
int fsetpos(FILE*fp,const fpos_t*ppos);
將文件位置指示符和轉換狀態設置成 ppos 所引用對象中存儲的值。ppos 所引用對象內的這些值必須通過調用函數 fgetpos()才能獲得。如果成功,fsetpos()返回 0,并清除該流的 EOF 標記。如果發生錯誤,則返回非 0 值。

int fseek(FILE*fp,long offset,int origin);
將文件位置指示符設置為以參數 origin 作為參考點,offset 作為偏差。三種可能的參考點均被定義為宏值,參數 offset 指定位置只可能是相對這三種參考點中的一種。

表 1 列出了這些宏,以及在 ANSI C 定義它們之前,曾用于 origin 的傳統取值。這些 offset 值可以是負的,但是,最終結果所獲得的文件位置必須大于等于 0。
表1 fseek中的參數origin
宏名稱 origin的傳統取值 偏差相對于的參考點
SEEK_SET 0 文件開頭
SEEK_CUR 1 當前文件位置
SEEK_END 2 文件結尾

當處理文本流時(在可區分文本流和二進制流的系統上),應該使用通過調用函數 ftell()獲得的值作為 offset 參數,并且讓 origin 的值為 SEEK_SET。

函數 ftell()與 fseek()、fgetpos()與 fsetpos()并非互相兼容的,因為 fgetpos()和 fsetpos()用來指示文件位置的 fpos_t 對象,可以不是算術類型。

如果成功的話,fseek()會清除流的 EOF 標記并返回 0。非 0  的返回值表示發生錯誤。函數 rewind()將文件位置指示符設置成文件開頭,并清除流的 EOF 與錯誤標記:
void rewind( FILE *fp );

如果不考慮對錯誤標記的影響,那么調用 rewind(fp)等同于:
(void)fseek( fp, 0L, SEEK_SET )

如果該文件已被以讀寫模式打開,那么在成功調用 fseek()、fsetpos()或 rewind()之后,就可以進行讀寫操作。

下面的例子使用一個索引表來存儲文件中記錄的位置。這個方法允許直接地訪問需要被更新的記錄。
// setNewName():在索引表中找關鍵字,并且更新文件中關鍵字所對應的記錄
// 包含這些記錄的文件,必須以“讀寫模式”打開;也就是采用模式字符串"r+b"
// 參數:—指向被打開數據文件的指針;—關鍵字;—新名稱
// 返回值:指向更新記錄的指針,當未找到時,返回NULL
// ---------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include "Record.h"     // 定義類型Record_t, IndexEntry_t:
                                // typedef struct { long key; char name[32];
                                //                  /* ... */ } Record_t;
                                // typedef struct { long key, pos; } IndexEntry_t;

extern IndexEntry_t indexTab[];   // 索引表
extern int indexLen;              // 表條目的數量

Record_t *setNewName( FILE *fp, long key, const char *newname )
{
  static Record_t record;
  int i;
  for ( i = 0; i < indexLen; ++i )
  {
    if ( key == indexTab[i].key )
      break;                      // 找到指定的鍵
  }
  if ( i == indexLen )
    return NULL;                          // 沒有找到
  // 將文件位置設定到該記錄:
  if (fseek( fp, indexTab[i].pos, SEEK_SET ) != 0 )
    return NULL;                          // 定位失敗
  // 讀取記錄:
  if ( fread( &record, sizeof(Record_t), 1, fp ) != 1 )
    return NULL;                          // 讀取錯誤

  if ( key != record.key )                // 測試鍵值
    return NULL;
  else
  {                                       // 更新記錄
    size_t size = sizeof(record.name);
    strncpy( record.name, newname, size-1 );
    record.name[size-1] = '\0';

    if ( fseek( fp, indexTab[i].pos, SEEK_SET ) != 0 )
      return NULL;                        // 設定文件位置出錯
    if ( fwrite( &record, sizeof(Record_t), 1, fp ) != 1 )
      return NULL;                        // 寫入文件出錯

    return &record;
  }
}

在寫操作之前的第二個 fseek()調用,可以用下面代碼替換,以相對于之前的位置,移動文件指針:
if (fseek( fp, -(long)sizeof(Record_t), SEEK_CUR ) != 0 )
    return NULL;                          // 設定文件位置出錯

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo