2013-11-22 56 views
-1

我有用於輸入文件並將內容存儲在字符串中的函數。(C++)遞歸調用文件輸入函數

下面是代碼

std::string inputFile(); 

    int main() 
    { 
     std::string fileContents = inputFile(); 
    } 

    std::string inputFile() 
     { 
      std::string fileName; 
      std::cout << "\nEnter file name, including path:\n"; 
      std::getline(std::cin, fileName); 

      std::ifstream input(fileName.c_str()); 
      std::string buffer; 
      std::string result; 

      if (!input.fail()) // if input does not fail 
      { 
       while (!input.eof()) 
       { 
        std::getline(input, buffer); 
        result.append(buffer); 
       } 
       input.close(); 

       return result; 
      } 
      else 
      { 
       std::cout << "\nInvalid file name or path"; 
       inputFile(); // recursive call to inputFile 
      } 
     } 

它工作正常,如果文件名和路徑是正確輸入。

但是,如果文件名或路徑輸入不正確,則會執行對inputFile的遞歸調用,併爲用戶提供另一個輸入文件的機會。

:那麼,如果輸入正確的文件名錯誤在Visual Studio 2013扔「在0x77F7A9E8(msvcr120d.dll)在Assignment4.exe未處理的異常:0000005:訪問衝突讀取位置0xCCCCCCC0。」

感謝您

+0

你有什麼話對現在調試?任何信息? –

+1

「while」或「do/while」循環可能比遞歸更合適。 – crashmstr

+0

......我可以保證:while(!input.eof())'是不對的。 – WhozCraig

回答

1

什麼我要說的話在技​​術上是一個答案,因爲如果你這樣做,你將避免導致你發佈錯誤的問題。但是因爲它說「重寫所有的假設」,所以作爲評論會更好。由於我無法讓它看起來很漂亮,所以在這裏。

使用遞歸這裏是錯誤

,直到達到一個確定好的終點遞歸函數連續調用本身進行處理。有些情況下(樹或圖走是那些最直接想到的),它是理想的解決方案。還有其他一些情況(斐波那契序列中的典型課程,例如)在哪裏是可接受的解決方案,但實際上並沒有任何好處。有很多情況下使用它是錯誤的。

這是後者之一。

遞歸的問題在於,對遞歸函數的調用沒有任何返回,直到您遇到終止條件,並且所有內容都折回到自身中。因此,假設您點擊input.fail個案,然後遞歸到inputFile。您的調用堆棧中現在有兩個inputFile實例。第一個遞歸併等待響應,第二個將檢查input.fail。如果第二個輸入失敗,那麼現在你將在內存中有三個實例,等等。 在任何系統上,在堆棧上有太多功能會有一些限制。當你到達這一點時,程序將耗盡內存,並且實際上會得到一個稱爲堆棧溢出的異常。您不一定能夠達到這一點,但通過使用遞歸,您將爲您的程序發生此漏洞。

什麼是正確的方法?

正確的方法是始終讓inputFile函數返回,因此您不會爲堆棧添加可能無限制數量的調用。

這裏是做你的代碼以正確的方式(我會用你的代碼逐字地,沒有固定的其他問題,如由約阿希姆Pileborg提到的那些):

std::string inputFile(); 

int main() 
{ 
    std::string fileContents; 
    while (fileContents.empty()) { 
     fileContents = inputFile(); 
    } 
} 

std::string inputFile() 
    { 
     std::string fileName; 
     std::cout << "\nEnter file name, including path:\n"; 
     std::getline(std::cin, fileName); 

     std::ifstream input(fileName.c_str()); 
     std::string buffer; 
     std::string result; 

     if (!input.fail()) // if input does not fail 
     { 
      while (!input.eof()) 
      { 
       std::getline(input, buffer); 
       result.append(buffer); 
      } 
      input.close(); 
     } 
     else 
     { 
      std::cout << "\nInvalid file name or path"; 
     } 

     return result; 
    } 

不同的是,我得到了擺脫遞歸。 main現在檢查inputFile的響應,而inputFile不會自行調用並始終返回一個值。一次只能執行inputFile以上的實例,並且您的調用堆棧中永遠不會有2個以上的函數。

它也是更乾淨的代碼:每個函數都應該負責完成一項任務。 inputFile讀取文件並返回結果。這不是inputFileRepeatedlyUntilSuccessful

擁有一個控制功能(在這種情況下,main)更有意義,它檢查函數是否成功,而不是讓函數永久調用自己。

遞歸有它的位置,但可能無限循環需要用戶反饋是不是。

5

你不確定的行爲,如else情況下,你不返回任何任何建議。

此外,這可能更好地處理與循環比遞歸。


順便說一句,你不應該這樣做while (!input.eof()) ...,它不會像你期望的那樣工作。原因是EOF標誌沒有設置,直到之後一個輸入操作失敗,所以你將有一個失敗的輸入操作,在你注意到你已經到達文件末尾之前你沒有檢查。

這個解決方案是使用std::getline返回流的事實,和流對象可以作爲一個布爾值,檢查一切正常:

while (std::getline(...)) { ... } 
+0

感謝您的建議,但我該如何寫 - 省略橢圓的地方是什麼? – navig8tr

+0

@ navig8tr你現在擁有的東西。 –

1

使用循環比遞歸更好,因爲你的代碼導致了未定義的行爲。

它可以這樣做:

#include <iostream> 
#include <fstream> 
#include <string> 


std::string inputFile() 
{ 
    std::string fileName; 
    std::ifstream input; 
    std::string buffer; 
    std::string result; 

    while(true) 
    { 
     std::cout << "\nEnter file name, including path:\n"; 
     std::getline(std::cin, fileName); 
     input.open(fileName); 
     if (input.fail()) // if input fails ask for another filename 
     { 
      std::cout << "\nInvalid file name or path"; 
     } 
     else break;// if the file exists break the loop 
    } 
    //read the contents 
    while (getline(input, buffer)) 
    { 
     result.append(buffer); 
    } 
    input.close(); 
    return result; 
} 

int main() 
{ 
    std::string fileContents = inputFile(); 
    std::cout << fileContents << std::endl; 
    return 0; 
} 
+0

遞歸本身不是未定義的行爲。即使函數遞歸正確,並且不會導致未定義的行爲,遞歸仍然是這個問題的一個不好的解決方案。 –