2013-03-21 68 views
7

所以我遇到了一個無法正確讀取二進制文件到我的結構中的問題。其結構如下:將二進制文件讀入結構(C++)

struct Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 

它是37個字節(來自char數組的25個字節,每個整數4個字節)。我的.dat文件是185字節。這是5名學生的3個整數等級。所以每個學生需要37個字節(37 * 5 = 185)。

它看起來以純文本格式是這樣的:

Bart Simpson   75 65 70 
Ralph Wiggum   35 60 44 
Lisa Simpson   100 98 91 
Martin Prince   99 98 99 
Milhouse Van Houten 80 87 79 

我能單獨使用此代碼來讀取每個記錄:

Student stud; 

fstream file; 
file.open("quizzes.dat", ios::in | ios::out | ios::binary); 

if (file.fail()) 
{ 
    cout << "ERROR: Cannot open the file..." << endl; 
    exit(0); 
} 

file.read(stud.name, sizeof(stud.name)); 
file.read(reinterpret_cast<char *>(&stud.quiz1), sizeof(stud.quiz1)); 
file.read(reinterpret_cast<char *>(&stud.quiz2), sizeof(stud.quiz2)); 
file.read(reinterpret_cast<char *>(&stud.quiz3), sizeof(stud.quiz3)); 

while(!file.eof()) 
{ 
    cout << left 
     << setw(25) << stud.name 
     << setw(5) << stud.quiz1 
     << setw(5) << stud.quiz2 
     << setw(5) << stud.quiz3 
     << endl; 

    // Reading the next record 
    file.read(stud.name, sizeof(stud.name)); 
    file.read(reinterpret_cast<char *>(&stud.quiz1), sizeof(stud.quiz1)); 
    file.read(reinterpret_cast<char *>(&stud.quiz2), sizeof(stud.quiz2)); 
    file.read(reinterpret_cast<char *>(&stud.quiz3), sizeof(stud.quiz3)); 
} 

我也得到一個好看的輸出,但我希望能夠一次讀入一個整體結構,而不是一次只讀取每個結構的各個成員。這個代碼是我相信完成任務所需要的,但是...它不起作用(我會在後面顯示輸出):

*不包括文件和結構開放的相似部分聲明等。

file.read(reinterpret_cast<char *>(&stud), sizeof(stud)); 

while(!file.eof()) 
{ 
    cout << left 
     << setw(25) << stud.name 
     << setw(5) << stud.quiz1 
     << setw(5) << stud.quiz2 
     << setw(5) << stud.quiz3 
     << endl; 

    file.read(reinterpret_cast<char *>(&stud), sizeof(stud)); 
} 

OUTPUT:

Bart Simpson    16640179201818317312 
ph Wiggum    288358417665884161394631027 
impson     129184563217692391371917853806 
ince      175193530917020655191851872800 

只有一部分它不弄亂是第一個名字,之後,它的下了山。我已經試過一切,我我不知道什麼是錯的。我甚至搜遍了我有的書,但找不到任何東西。那裏的東西看起來像我擁有的​​和他們的工作,但由於某些奇怪的原因,我的不是。我在第25個字節做了file.get(ch)(ch是一個char),它返回了K,這是75的ASCII碼。這是第一個測試分數,所以,一切都應該在這裏。這只是沒有正確閱讀我的結構。

任何幫助將不勝感激,我只是堅持這一個。

編輯:在收到你們大量意想不到的好消息後,我決定接受你的建議,並堅持一次一個人的閱讀。我通過使用函數使事情變得更清潔和更小。 再次感謝您提供如此快速和啓發性的輸入。非常感謝。

如果你在那不建議大多數解決方法感興趣,滾動向底部,由user1654209第三的答案。該解決方案完美無瑕地工作,但閱讀所有評論,看看爲什麼不受歡迎。

+1

你能說明你是如何寫這個文件的嗎? – 2013-03-21 08:36:19

+2

如果您打印'sizeof(Student)',您將看到它不是37個字節。它可能是40或56. – 2013-03-21 08:36:51

+0

@RetiredNinja我沒有包括所有的代碼,以避免太長時間的帖子,對不起。這裏是我的整個代碼@ codepad:http://codepad.org/331LdCYY – 2013-03-21 08:38:34

回答

8

您的結構幾乎可以肯定被填充以保持其內容的對齊。這意味着它不會是37個字節,而且這種不匹配會導致讀數不同步。看看每個字符串丟失3個字符的方式,看起來它已被填充到40個字節。

由於填充可能位於字符串和整數之間,所以即使第一條記錄不能正確讀取。

在這種情況下,我會建議不要嘗試讀取您的數據作爲二進制blob,並堅持閱讀個別領域。它更強大,特別是如果你甚至想改變你的結構。

+0

是的,它是40字節...不幸的。任何方式來解決這個問題? .dat文件是185字節。如果可以的話,我想盡量整體閱讀。除非沒有辦法。 – 2013-03-21 08:45:42

+0

這不是問題,它是一個功能。你可能會破解它,但結果會非常脆弱。我建議反對它。 – JasonD 2013-03-21 08:46:19

+0

「脆弱」是什麼意思? – 2013-03-21 08:55:35

4

沒有看到代碼寫入的數據,我猜你寫數據的方式,你在第一個例子中,每個元素讀取它的方式一個接一個。然後文件中的每個記錄確實將是37個字節。

但是,由於編譯器爲了優化原因而填充結構以將成員放在漂亮的邊界上,所以您的結構爲40個字節。所以當你在一次調用中讀完整個結構時,你實際上一次只能讀取40個字節,這意味着你的讀數將與文件中的實際記錄不同步。

您必須重新執行寫入操作才能一次寫入完整結構,或者使用第一種方法讀取您一次讀取一個成員字段的位置。

+0

是的,聽起來就像每個人都在同一個頁面上...原始文件可能一次寫入一個成員,因此沒有產生填充(?)。 – 2013-03-21 08:52:55

+0

寫數據的方法與它有什麼關係?唯一重要的是這些數據在二進制文件中的外觀如何,無關緊要。他們甚至可能不會被任何代碼寫在那裏,而是手工製作或其他東西。數據的外觀(它們在文件中的佈局)和讀取代碼必須與之相匹配(即對內存中的結構使用相同的佈局,或者對於搜索使用相同的偏移量),重要的是什麼。 – SasQ 2015-06-10 18:38:08

3

簡單的解決方法是將您的結構收拾使用1個字節

GCC

struct __attribute__((packed)) Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 

使用MSVC

#pragma pack(push, 1) //set padding to 1 byte, saves previous value 
struct Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 
#pragma pack(pop) //restore previous pack value 

編輯:由於用戶ahans指出:編譯包是由GCC支持自從2.7.2.3版本(1997年發佈)以來,如果您的目標是msvc和gcc,那麼使用編譯指示包作爲唯一的打包符號似乎是安全的

+0

非常感謝! :)它實際上完美地加載了整個結構。雖然我很擔心使用它,但是,在閱讀完所有評論之後,heh – 2013-03-21 09:11:18

+1

實際上並不需要特殊的GCC版本,它也理解'#pragma pack'是一樣的方式MSVC做。 – ahans 2013-03-21 09:24:24

+0

@ahans當然,雖然我不記得哪個gcc版本引入了pragma pack支持。如果有人獲得了這些信息,請評論,我將編輯答案 – 2013-03-21 09:29:00

2

正如你已經發現的那樣,填充是這裏的問題。另外,正如其他人所建議的那樣,解決這個問題的正確方法是按照您的例子逐個讀取每個成員。我不認爲這會花費太多的成本,而是一次性讀取整個事情。但是,如果你仍然想繼續前進,因爲一次讀它,你可以告訴編譯器做不同的填充:

#pragma pack(push, 1) 
struct Student 
{ 
    char name[25]; 
    int quiz1; 
    int quiz2; 
    int quiz3; 
}; 
#pragma pack(pop) 

隨着#pragma pack(push, 1)你告訴編譯器保存到內部棧當前包值和之後使用1的包值。這意味着你得到一個1字節的對齊,這意味着在這種情況下根本沒有填充。用#pragma pack(pop)告訴編譯器從堆棧中獲取最後一個值,然後使用它,從而恢復編譯器在定義struct之前使用的行爲。

雖然#pragma通常表示非可移植,依賴於編譯器的功能,但這個功能至少可以在GCC和Microsoft VC++中使用。

+0

啊,謝謝你進一步解釋這個實現的效果。我並不完全確定每個詞的含義,你的解釋比我在其他網站上閱讀的更容易理解。 – 2013-03-21 09:43:33

0

有多種方法可以解決此線程的問題。這是基於使用結構和炭BUF的聯合解決方案:)

#include <fstream> 
#include <sstream> 
#include <iomanip> 
#include <string> 

/* 
This is the main idea of the technique: Put the struct 
inside a union. And then put a char array that is the 
number of chars needed for the array. 

union causes sStudent and buf to be at the exact same 
place in memory. They overlap each other! 
*/ 
union uStudent 
{ 
    struct sStudent 
    { 
     char name[25]; 
     int quiz1; 
     int quiz2; 
     int quiz3; 
    } field; 

    char buf[ sizeof(sStudent) ]; // sizeof calcs the number of chars needed 
}; 

void create_data_file(fstream& file, uStudent* oStudent, int idx) 
{ 
    if (idx < 0) 
    { 
     // index passed beginning of oStudent array. Return to start processing. 
     return; 
    } 

    // have not yet reached idx = -1. Tail recurse 
    create_data_file(file, oStudent, idx - 1); 

    // write a record 
    file.write(oStudent[idx].buf, sizeof(uStudent)); 

    // return to write another record or to finish 
    return; 
} 


std::string read_in_data_file(std::fstream& file, std::stringstream& strm_buf) 
{ 
    // allocate a buffer of the correct size 
    uStudent temp_student; 

    // read in to buffer 
    file.read(temp_student.buf, sizeof(uStudent)); 

    // at end of file? 
    if (file.eof()) 
    { 
     // finished 
     return strm_buf.str(); 
    } 

    // not at end of file. Stuff buf for display 
    strm_buf << std::setw(25) << std::left << temp_student.field.name; 
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz1; 
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz2; 
    strm_buf << std::setw(5) << std::right << temp_student.field.quiz3; 
    strm_buf << std::endl; 

    // head recurse and see whether at end of file 
    return read_in_data_file(file, strm_buf); 
} 



std::string quiz(void) 
{ 

    /* 
    declare and initialize array of uStudent to facilitate 
    writing out the data file and then demonstrating 
    reading it back in. 
    */ 
    uStudent oStudent[] = 
    { 
     {"Bart Simpson",   75, 65, 70}, 
     {"Ralph Wiggum",   35, 60, 44}, 
     {"Lisa Simpson",   100, 98, 91}, 
     {"Martin Prince",   99, 98, 99}, 
     {"Milhouse Van Houten", 80, 87, 79} 

    }; 




    fstream file; 

    // ios::trunc causes the file to be created if it does not already exist. 
    // ios::trunc also causes the file to be empty if it does already exist. 
    file.open("quizzes.dat", ios::in | ios::out | ios::binary | ios::trunc); 

    if (! file.is_open()) 
    { 
     ShowMessage("File did not open"); 
     exit(1); 
    } 


    // create the data file 
    int num_elements = sizeof(oStudent)/sizeof(uStudent); 
    create_data_file(file, oStudent, num_elements - 1); 

    // Don't forget 
    file.flush(); 

    /* 
    We wrote actual integers. So, you cannot check the file so 
    easily by just using a common text editor such as Windows Notepad. 

    You would need an editor that shows hex values or something similar. 
    And integrated development invironment (IDE) is likely to have such 
    an editor. Of course, not always so. 
    */ 


    /* 
    Now, read the file back in for display. Reading into a string buffer 
    for display all at once. Can modify code to display the string buffer 
    wherever you want. 
    */ 

    // make sure at beginning of file 
    file.seekg(0, ios::beg); 

    std::stringstream strm_buf; 
    strm_buf.str(read_in_data_file(file, strm_buf)); 

    file.close(); 

    return strm_buf.str(); 
} 

呼叫測驗(和接收格式顯示到std字符串::法院,寫入文件,或什麼的。

主要思想是聯合中的所有項目在內存中的相同地址處開始。所以你可以有一個char或wchar_t buf,它的大小與你要寫入或讀取文件的結構相同。並注意到零點需要。代碼中沒有投射。

我也不用擔心填充。

對於那些不喜歡遞歸的人,抱歉。用遞歸解決問題對我來說更容易,也更不容易出錯。也許對其他人更容易?遞歸可以轉換爲循環。他們需要轉換爲非常大的文件循環。

對於那些喜歡遞歸的人來說,這是另一個使用遞歸的例子。

我不認爲使用聯合是最好的解決方案。似乎它是一個解決方案。也許你喜歡它?