我的目标:
我正在尝试用C++编写一个应用程序,用户可以在其中请求特定日期范围内的某个天气参数,该程序将从互联网上找到该信息并将其写入文本文件。因此,用户可以在 2009 年 8 月 2 日至 2009 年 8 月 10 日期间每天要求高温。然后,应用程序将吐出一个文本文件,如下所示:
Month, Date, Year, High
8 2 2009 80.3
8 3 2009 76.9
...
8 10 2009 68.4
我已经获取了网页,将 HTML 解析为有意义的值,并将这些值写入数据库(txt 文件(中。我还写了一个函数
insert(std::iostream& database, Day day); //Day is a class I defined that contains all the weather information
这将找到这一天属于哪里以保持秩序,并将其插入中间。我已经测试了这个函数,它的工作方式完全符合预期。
我的问题:
我现在正在尝试编写一个执行此操作的函数:
void updateDatabase(std::iostream& database, Day start, Day end)
{
Day currentDay = start;
while (currentDay.comesBefore(end))
{
if (currentDay.notInDatabase(database))
insert(database, currentDay);
currentDay = currentDay.nextDay();
}
}
但不幸的是,insert(( 函数只有在我每个程序调用一次时才可以正常工作。如果我尝试连续调用 insert(( 两次(或三次、四次或五次(,只有最后一天会出现在我的文本文件中。
这是重现我的问题但仍在运行的代码量的最小可能代码。
#include <iostream>
#include <fstream>
#include <string>
const std::string FOLDER = "/Users/Jimmy/Desktop/WeatherApp/";
const std::string DATABASE_NAME = FOLDER + "database.txt";
class day
{
public:
int date;
int month;
int year;
bool comesBefore(int month, int date, int year);
day(int month, int date, int year)
{
this->month = month;
this->date = date;
this->year = year;
}
};
void writeToDatabase(std::iostream& file, day today, bool end = true);
void insertDay(std::iostream& file, day today);
int main()
{
std::fstream database;
database.open(DATABASE_NAME);
if (database.fail())
{
std::cout << "Cannot find database.n";
exit(1);
}
day second(1, 2, 2000);
insertDay(database, second);
std::cout << "First day inserted. Press enter to insert second day.n";
std::cin.get();
day third(1, 3, 2000);
insertDay(database, third);
std::cout << "Done!n";
return 0;
}
bool day::comesBefore(int month, int day, int year)
{
if (this->year < year)
return true;
if (this->year > year)
return false;
//We can assume this->year == year.
if (this->month < month)
return true;
if (this->month > month)
return false;
//We can also assume this->month == month
return (this->date < day);
}
void writeToDatabase(std::iostream& file, day today, bool end)
{
if (end) //Are we writing at the current cursor position or the end of the file?
file.seekg(0, std::ios::end);
file << today.month << 't' << today.date << 't' << today.year << 'n';
return;
}
void insertDay(std::iostream& file, day today)
{
//Clear flags, and set cursor at beggining
file.clear();
file.seekg(0, std::ios::beg);
int date, month, year;
long long positionToInsert = 0;
while (!file.eof())
{
file >> month >> date >> year;
//std::cout << month << date << year << 'n';
if (today.comesBefore(month, date, year))
{
//We found the first day that comes after the day we are inserting
//Now read backwards until we hit a newline character
file.unget();
char c = ' ';
while (c != 'n')
{
file.unget();
c = file.get();
file.unget();
}
positionToInsert = file.tellg();
break;
}
}
if (file.eof())
{
//We hit the end of the file. The day we are inserting is after every day we have. Write at the end.
file.clear();
writeToDatabase(file, today);
return;
}
file.clear();
file.seekg(0, std::ios::beg);
std::fstream tempFile;
std::string tempFileName = FOLDER + "tempfile.txt";
std::string terminalCommand = "> " + tempFileName;
//Send the command "> /Users/Jimmy/Desktop/WeatherApp/tempfile.txt" to the terminal.
//This will empty the file if it exists, and create it if it does not.
system(terminalCommand.c_str());
tempFile.open(tempFileName);
if (tempFile.fail())
{
std::cout << "Failure!n";
exit(1);
}
int cursorPos = 0;
while (cursorPos++ < positionToInsert)
{
char c = file.get();
tempFile.put(c);
}
tempFile.put('n'); //To keep the alignment right.
writeToDatabase(tempFile, today, false);
file.get();
char c = file.get();
while (!file.eof())
{
tempFile.put(c);
c = file.get();
}
terminalCommand = "mv " + tempFileName + " " + DATABASE_NAME;
//Sends the command "mv <tempFileName> <databaseName>" to the terminal.
//This command will move the contents of the first file (tempfile) into the second file (database)
//and then delete the old first file (tempfile)
system(terminalCommand.c_str());
return;
}
我在 main 中添加了 cin.get(( 部分,这样我就可以在每次 insert(( 调用之前和之后查看我的数据库。以下是编译/运行之前的数据库:
1 1 2000
1 4 2000
这是在按 enter/move over cin.get(( 之前的数据库:
1 1 2000
1 2 2000
1 4 2000
这是我继续使用 cin.get(( 并且我的程序退出后的数据库:
1 1 2000
1 3 2000
1 4 2000
在运行程序之前,我已经更改了要插入的日期,要插入的日期数,两个日期相距的距离以及数据库的初始大小,但是我总是得到相同的结果。每次调用 insert(( 后,数据库的行为就好像这是有史以来唯一一次插入调用一样。但是,如果我多次运行该程序,文本文件会继续增长。只有当我尝试在每次编译/运行时多次调用插入时,我才会遇到这个问题。因此,如果我运行该程序5次:
int main()
{
std::fstream database;
database.open(DATABASE_NAME);
if (database.fail())
{
std::cout << "Cannot find database.n";
exit(1);
}
day today(1, 2, 2000);
insertDay(database, today);
std::cout << "Done!n";
return 0;
}
我的数据库最终看起来像这样:
1 1 2000
1 2 2000
1 2 2000
1 2 2000
1 2 2000
1 2 2000
1 4 2000
我怀疑这是fstream.clear((,fstream.seekg((和fstream.eof((的问题,或者可能是关闭/重新打开文件的问题。但是我为修复它所做的一切都没有帮助。
另外,值得注意的是,这不会在Windows计算机上运行。在 Linux 上应该没问题,但我只在 Mac 上测试过它,所以我可能是错的。它使用 bash 来创建/删除/重命名/移动文件。
任何帮助(即使只是朝着正确的方向推动(都非常感谢。我已经把头发拉到这个身上有一段时间了。另外,我知道SO不喜欢代码转储,所以我大大简化了这个问题。我的完整程序是 700+ 行和 10 个不同的文件,这大约是我尽可能短的,同时仍然能传达这个想法。
您在这里遇到的问题与您处理文件的方式有关:当您mv
文件时,旧文件本身不会被覆盖;相反,它被取消链接("已删除"(并在 is 中创建一个新文件。
在类Unix操作系统上,您仍然可以保留未链接文件的句柄:只是无法使用路径访问它。 这就是为什么在Unix上完全可以删除仍然打开的文件,这与Windows不同:在取消链接后,该文件仍然存在,至少在所有文件描述符关闭之前。 这意味着database
根本没有改变:它仍然指向您的旧文件并包含相同的内容。
一个简单的解决方法是关闭并重新打开该文件。 (从实际的角度来看,只使用现成的解决方案(如 Sqlite(可能会好得多。