首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

CArchive 與 CFile 文件指針位置的關係詳解

     編輯:程式設計綜合問答

問題背景

在 MFC 開發中,許多開發者會遇到 CArchive 和 CFile 文件指針位置不一致的問題。本文通過一個具體的學生信息管理系統代碼示例,深入分析這兩個類的文件指針關係,並解決讀寫循環只執行一次、寫入換行符導致讀取異常等常見問題。

核心代碼分析

void Add::OnClickedIdbAdd() {
    UpdateData();
    // ... 數據驗證代碼 ...

    CFile cf;
    if (!cf.Open(_T("D:\\SIMS.txt"), CFile::modeCreate | CFile::modeNoTruncate | CFile::modeReadWrite)) {
        #ifdef _DEBUG
        afxDump << "unable to open file" << "\n";
        exit(1);
        #endif
    }

    CArchive cas(&cf, CArchive::store);  // 存檔對象(寫入)
    CArchive cal(&cf, CArchive::load);   // 加載對象(讀取)

    CString strSno, strtemp;
    // 嘗試用 CFile 的指針判斷文件結尾
    while (cf.GetPosition() != cf.GetLength()) {
        cal >> strSno;
        if (cf.GetPosition() == cf.GetLength()) {
            strtemp.Format(_T("等於:pos:%lu,len:%lu"), cf.GetPosition(), cf.GetLength());
            MessageBox(strtemp);
        }
        MessageBox(strSno);
        if (m_aSno == strSno) {
            MessageBox(_T("該學號已存在,請重新輸入。"), _T("錯誤"));
            m_aSno = ""; UpdateData(FALSE); return;
        }
    }

    cf.SeekToEnd();  // 移動到文件末尾準備寫入
    cas << m_aSno << m_aName << m_aMajor << m_aComp << m_aMath << m_aEng << m_aGym;
    strtemp.Format(_T("\r\n"));
    cas << strtemp;  // 寫入換行符
    cas.Flush();

    cf.SeekToBegin();  // 回到文件開頭驗證
    cal >> m_aSno >> m_aName;
    MessageBox(m_aSno); MessageBox(m_aName);

    // 清空界面並提示成功
    m_aSno = ""; m_aName = ""; m_aMajor = "";
    m_aComp = m_aEng = m_aGym = m_aMath = 0;
    UpdateData(FALSE);
    MessageBox(_T("添加成功"), _T("成功"));

    cal.Close(); cas.Close(); cf.Close();
}

關鍵問題解析

1. CArchive 與 CFile 的文件指針是否共享?

答案是肯定的。 CArchive 內部沒有獨立的文件指針,它直接操作關聯的 CFile 對象的指針。因此 cf.GetPosition() 和 cf.GetLength() 反映的就是 CArchive 讀寫後的實際位置。

2. 為什麼讀取循環只執行一次?

代碼中同時創建了兩個 CArchive 對象:cas(存檔/寫入)和 cal(加載/讀取),它們共享同一個 CFile 對象 cf。這會導致嚴重的緩衝區衝突:

  • cal 讀取時會從文件預讀數據到內部緩衝區,導致 cf 的指針向前移動。
  • cas 寫入時也會使用緩衝區,兩個緩衝區互相干擾,造成文件指針位置錯亂。
  • 首次循環時 cal >> strSno 讀取了一筆數據,緩衝區可能已讀取了後續內容,導致 cf.GetPosition() 直接跳到文件末尾,循環條件 cf.GetPosition() != cf.GetLength() 變為假,循環結束。

3. 寫入 \r\n 導致後續讀取異常的原因

CArchive::ReadString 遇到換行符會停止讀取。代碼中寫入 \r\n 作為記錄分隔符,但讀取時使用的是 operator>>(針對 CString 會調用 ReadString)。第一次讀取正常,第二次讀取時遇到殘留的換行符或緩衝區數據錯亂,導致讀取到空字符串或亂碼,第三次關閉 CArchive 時因緩衝區不一致拋出異常。

正確的解決方案

方案一:分離讀寫操作,避免同時存在兩個 CArchive

// 1. 先純讀取檢查學號是否存在
CFile cfRead;
if (cfRead.Open(_T("D:\\SIMS.txt"), CFile::modeRead | CFile::shareDenyWrite)) {
    CArchive cal(&cfRead, CArchive::load);
    CString strSno;
    while (cal.ReadString(strSno)) {  // 使用 ReadString 按行讀取
        // 解析每行數據(需約定格式,如字段用制表符分隔)
        // 這裡簡單演示:假設每行第一個字段是學號
        int pos = strSno.Find(_T('\t'));
        CString curSno = (pos != -1) ? strSno.Left(pos) : strSno;
        if (m_aSno == curSno) {
            MessageBox(_T("學號已存在"));
            cal.Close(); cfRead.Close(); return;
        }
    }
    cal.Close(); cfRead.Close();
}

// 2. 追加寫入新記錄
CFile cfWrite;
cfWrite.Open(_T("D:\\SIMS.txt"), CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite);
cfWrite.SeekToEnd();
CArchive cas(&cfWrite, CArchive::store);
cas << m_aSno << _T('\t') << m_aName << _T('\t') << m_aMajor << _T('\t') 
    << m_aComp << _T('\t') << m_aMath << _T('\t') << m_aEng << _T('\t') << m_aGym << _T('\n');
cas.Flush(); cas.Close(); cfWrite.Close();

方案二:使用 CFile 直接讀寫,不使用 CArchive

對於簡單的文本文件操作,直接用 CFile::Read/Write 或 CStdioFile 更直觀,避免序列化緩衝區問題。

關鍵最佳實踐

  • 同一時刻只能有一個 CArchive 操作同一 CFile,否則緩衝區會相互破壞。
  • 讀取文本文件優先用 CStdioFile::ReadString,按行處理,避免二進制序列化的複雜性。
  • 寫入前先關閉讀取句柄,確保文件狀態一致。
  • 約定清晰的數據格式(如 CSV、JSON、制表符分隔),便於解析和擴展。

總結

CArchive 和 CFile 共享文件指針,但 CArchive 的內部緩衝機制使得同時進行讀寫操作極其危險。正確做法是將讀取和寫入分離到不同的時間點、不同的文件句柄中進行,並根據數據性質選擇合適的讀寫方式(文本用 CStdioFile,二進制結構化用 CArchive)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved