I'm taking a basic OOP in C++ class. One of our assignments is to read lines from a file, run them through a function (parseLine()) that converts a string to a string stream, throw exceptions from that parseLine, catch the exceptions in the readFile function and write the lines that threw exceptions to the console. The lines that did not throw exceptions should be added to a struct array.
The problem: The throws are not being thrown, or not being caught.
I have spent hours playing with the formatting trying to figure out why my try catch statement isn't working. I wish I could ask a more specific question, but I believe the answer will be obvious to a more experienced programmer who sees my code
Note: Formatting style is prescribed by the class.
/**********************************************************************
* struct: Record
* fileName
* user
* time
***********************************************************************/
struct Record
{
string file;
string user;
long time;
};
/**********************************************************************
* function: parseLine
* parse line into struct
***********************************************************************/
void parseLine(const string & line, Record & buffer) throw (string)
{
assert(line.length() > 0);
stringstream ss;
ss.str(line);
// stream string to struct
ss >> buffer.file;
if (ss.fail())
{
ss.clear();
throw string(line);
}
ss >> buffer.user;
if (ss.fail())
{
ss.clear();
throw string(line);
}
ss >> buffer.time;
if (ss.fail() || buffer.time < 1,000,000,000 || buffer.time > 10,000,000,000)
{
ss.clear();
throw string(line);
}
}
/**********************************************************************
* function: readFile
* read from the file name provided by the user
***********************************************************************/
int readFile(const string & fileName, Record record[])
{
// declare fstream variable
ifstream fin(fileName.c_str());
// error check
if (fin.fail())
{
cout << "ERROR: Unable to read file "
<< fileName
<< endl;
return 0;
}
// loop through file and store it
Record buffer;
string line;
int size = 0;
while (getline(fin, line) && size < 500)
{
try
{
parseLine(line, buffer);
record[size] = buffer;
}
catch (string text)
{
cout << "Error parsing line: "
<< text
<< endl;
}
size++;
}
// close the file
fin.close();
return size;
}
Question 1 The file being read purposely contains errors (Empty lines, ints in unexpected places, etc), it seems like the ss.fail() is not being triggered, how could that happen?
Question 2 Is my try/catch block written correctly?
Thank you very much for your help!
Your integer literals should not contain commas.
The comma is actually a C++ operator.
Related
I'm trying to read some text out of a file called "file.dat". The problem is, that the string in the file does not include a zero at the end as for standard C. So I need something that adds the zero, so I can work with the string without getting random symbols after the string when I print it.
void cSpectrum::readSpectrum(const std::string &filename, double
tubeVoltage, double &minEnergy, std::string &spectrumName)
{
//Object with the name "inp" of the class ifstream
ifstream inp(filename, ios::binary);
//Checks if the file is open
if (!inp.is_open()) {
throw runtime_error("File not open!");
}
cout << "I opened the file!" << endl;
//Check the title of the file
string title;
char *buffer = new char[14];
inp.read(buffer, 14);
cout << buffer << endl;
}
At the moment I get the following output, I would like to get it without the ²²²²┘.
I opened the file!
x-ray spectrum²²²²┘
Simply allocate +1 more char for your array, but don't read into that char, just set it to 0:
char buffer[15];
inp.read(buffer, 14);
buffer[14] = '\0';
cout << buffer << endl;
Or, simply don't use a char[] at all, use std::string instead, see:
What is the best way to read an entire file into a std::string in C++?
I did it with the std::string now. If you want you can replace the 14 by an integer variable.
void cSpectrum::readSpectrum(const std::string & filename, double tubeVoltage, double
& minEnergy, std::string const & spectrumName){
ifstream inp(filename, ios::binary);
//Checks if the file is open
if (!inp.is_open()) {
throw runtime_error("ERROR: Could not open the file!");
}
//Reads the title
string title(14, '\0');
inp.read(&title[0], 14);
//If it is not the correct file throw an ERROR
if (title != spectrumName)
throw runtime_error("ERROR: Wrong file title");
readSpectrum(inp, tubeVoltage, minEnergy, spectrumName);
}
I want to programmatically convert a string of characters stored in a file to a string of character codes (encode) by following a code table. The string of binary codes should then go to a file, from which I can revert it back to the string of characters later (decode). The codes in the code table were generated using Huffman algorithm and the code table is stored in a file.
For example, by following a code table where characters and its corresponding codes are single spaced like this:
E 110
H 001
L 11
O 111
encoding "HELLO" should output as "0011101111111"
My C++ code cannot seem to complete the encoded string. Here is my code:
int main
{
string English;
ifstream infile("English.txt");
if (!infile.is_open())
{
cout << "Cannot open file.\n";
exit(1);
}
while (!infile.eof())
{
getline (infile,English);
}
infile.close();
cout<<endl;
cout<<"This is the text in the file:"<<endl<<endl;
cout<<English<<endl<<endl;
ofstream codefile("codefile.txt");
ofstream outfile ("compressed.txt");
ifstream codefile_input("codefile.txt");
char ch;
string st;
for (int i=0; i<English.length();)
{
while(!codefile_input.eof())
{
codefile_input >> ch >> st;
if (English[i] == ch)
{
outfile<<st;
cout<<st;
i++;
}
}
}
return 0;
}
For an input string of "The_Quick_brown_fox_jumps_over_the_lazy_dog", the output string is 011100110, but it should be longer than that!
output image
Please help! Is there anything I have missed?
(n.b. my C++ code has no syntax errors)
Let's take a look at the main loop, you are doing your work in:
for (int i=0; i<English.length();)
{
while(!codefile_input.eof())
{
codefile_input >> ch >> st;
if (English[i] == ch)
{
outfile<<st;
cout<<st;
i++;
}
}
}
Your code, will read through the codefile_input once, and then will get stuck in codefile_input.eof () == true condition, and then, for (int i=0; i<English.length();) will become an infinite loop, due to the fact, that there won't be a code path, in which i is increased, and it will never reach the value equal to English.length ().
As a side note, take a read on Why is iostream::eof inside a loop condition considered wrong?.
To avoid the issue, explained above, consider reading the dictionary file, to a data container (e.g. std::map), and then, use that, while iterating through the string, that you want to encode.
For example:
std::ifstream codefile_input("codefile.txt");
char ch;
std::string str;
std::map<char, std::string> codes;
while (codefile_input >> ch >> str)
{
codes[ch] = str;
}
codefile_input.close ();
for (int i=0; i<English.length(); ++i)
{
auto it = codes.find (English[i]);
if (codes.end () != it)
{
outfile << codes->second;
cout << codes->second;
}
}
Note, you will need to #include <map> to use std::map.
In addition to solving the issue, about which, your question, was actually, about, your loop:
while (!infile.eof())
{
getline (infile,English);
}
only reads the last line of the file, while discarding all other lines, that came prior to it. If you want to process all the lines in a file, consider changing that loop to:
while (std::getline (infile, English))
{
/* Line processing goes here */
}
And, since, your dictionary is unlikely to be different for different lines, you can move that logic, to the front of this loop:
std::ifstream codefile_input("codefile.txt");
char ch;
std::string str;
std::map<char, std::string> codes;
while (codefile_input >> ch >> str)
{
codes[ch] = str;
}
codefile_input.close ();
ifstream infile("English.txt");
if (!infile.is_open())
{
cout << "Cannot open file.\n";
exit(1);
}
ofstream outfile ("compressed.txt");
string English;
while (std::getline (infile, English))
{
for (int i=0; i<English.length(); ++i)
{
auto it = codes.find (English[i]);
if (codes.end () != it)
{
outfile << codes->second;
cout << codes->second;
}
}
}
In addition, consider adding error checking for all of the files that you open. You check if you can open file English.txt, and exit if you can't, but you don't check if you could open any other file.
On unrelated note #2, considering reading Why is “using namespace std” considered bad practice? (that's why you see me using std:: explicitly in the code, that I added).
I have a file that has a number in which is the number of names that follow. For example:
4
bob
jim
bar
ted
im trying to write a program to read these names.
void process_file(ifstream& in, ofstream& out)
{
string i,o;
int tmp1,sp;
char tmp2;
prompt_user(i,o);
in.open (i.c_str());
if (in.fail())
{
cout << "Error opening " << i << endl;
exit(1);
}
out.open(o.c_str());
in >> tmp1;
sp=tmp1;
do
{
in.get(tmp2);
} while (tmp2 != '\n');
in.close();
out.close();
cout<< sp;
}
So far I am able to read the first line and assign int to sp
I need sp to be a counter for how many names. How do I get this to read the names.
The only problem I have left is how to get the names while ignoring the first number.
Until then i cannot implement my loop.
while (in >> tmp1)
sp=tmp1;
This successfuly reads the first int from the and then tries to continue. Since the second line is not an int, extraction fails, so it stops looping. So far so good.
However, the stream is now in fail state, and all subsequent extractions will fail unless you clear the error flags.
Say in.clear() right after the first while loop.
I don't really see why you wrote a loop to extract a single integer, though. You could just write
if (!(in >> sp)) { /* error, no int */ }
To read the names, read in strings. A loop is fine this time:
std::vector<std::string> names;
std::string temp;
while (in >> temp) names.push_back(temp);
You'd might want to add a counter somewhere to make sure that the number of names matches the number you've read from the file.
int lines;
string line;
inputfile.open("names.txt");
lines << inputfile;
for(i=0; i< lines; ++i){
if (std::getline(inputfile, line) != 0){
cout << line << std::endl;
}
}
First of all, assuming that the first loop:
while (in >> tmp1)
sp=tmp1;
Is meant to read the number in the beginning, this code should do:
in >> tmp1;
According to manual operator>>:
The istream object (*this).
The extracted value or sequence is not returned, but directly stored
in the variable passed as argument.
So don't use it in condition, rather use:
in >> tmp1;
if( tmp1 < 1){
exit(5);
}
Second, NEVER rely on assumption that the file is correctly formatted:
do {
in.get(tmp2);
cout << tmp2 << endl;
} while ( (tmp2 != '\n') && !in.eof());
Although whole algorithm seems a bit clumsy to me, this should prevent infinite loop.
Here's a simple example of how to read a specified number of words from a text file in the way you want.
#include <string>
#include <iostream>
#include <fstream>
void process_file() {
// Get file name.
std::string fileName;
std::cin >> fileName;
// Open file for read access.
std::ifstream input(fileName);
// Check if file exists.
if (!input) {
return EXIT_FAILURE;
}
// Get number of names.
int count = 0;
input >> count;
// Get names and print to cout.
std::string token;
for (int i = 0; i < count; ++i) {
input >> token;
std::cout << token;
}
}
If I include the if test in my code the error message is returned and I'm not sure why.
and when it's not used, my program get's stuck in a loop where it never reaches the end of the file. I don't understand what's going wrong.
int countlines()
{
fstream myfile;
myfile.open("questions.txt", ios::in);
string contents;
int linenumber = 0;
//if (myfile.is_open())
// {
while (!myfile.eof())
{
getline( myfile, contents );
if (contents != "")
{
linenumber++;
}
}
cout << "there are " << linenumber << " lines.\n";
//}else {cout<<"Unable to get file.\n";}
myfile.close();
return(linenumber);
}
What's going on is that your file is not being opened. That's why is_open fails.
Then, when you comment out the check, you're breaking your loop because you're iterating incorrectly (see my comment) and not detecting stream failures (.eof() will never be true on that stream).
Make sure that the file is in the right place, and that it is accessible.
The correct idiom for reading a file line-by-line in C++ is using a loop like this:
for (std::string line; std::getline(file,line);)
{
// process line.
}
Inserting this in your example (+fixing indentation and variable names) gives something like this:
int countlines(const std::string& path)
{
// Open the file.
std::ifstream file(path.c_str());
if (!file.is_open()) {
return -1; // or better, throw exception.
}
// Count the lines.
int count = 0;
for (std::string line; std::getline(file,line);)
{
if (!line.empty()) {
++count;
}
}
return count;
}
Note that if you don't intend to process the line contents, you can actually skip processing them using std::streambuf_iterator, which can make your code look like:
int countlines(const std::string& path)
{
// Open the file.
std::ifstream file(path.c_str());
if (!file.is_open()) {
return -1; // or better, throw exception.
}
// Refer to the beginning and end of the file with
// iterators that process the file character by character.
std::istreambuf_iterator<char> current(file);
const std::istreambuf_iterator<char> end;
// Count the number of newline characters.
return std::count(current, end, '\n');
}
The second version will completely bypass copying the file contents and avoid allocating large chunks of memory for long lines.
When using std::istream and std::ostream (whose std::fstream implements), the recommended usage is to directly use the stream in a bool context instead of calling eof() function because it only return true when you managed to read until the last byte of the file. If there was any error before that, the function will still return true.
So, you should have written your code as:
int countlines() {
ifstream myfile;
int linenumber = 0;
string linecontent;
myfile.open("question.txt", ios::in);
while (getline(myfile, linecontent)) {
if (!linecontent.empty()) {
++linenumber;
}
}
return linenumber;
}
Try the following code. It will also (hopefully) give you an idea why the file open is failing...
int countlines()
{
ifstream myfile;
myfile.open("questions.txt");
string contents;
int linenumber = 0;
if (myfile.is_open())
{
while (getline(myfile, contents))
{
if (contents != "")
linenumber++;
}
cout << "there are " << linenumber << " lines." << endl;
myfile.close();
}
else
cout << "Unable to get file (reason: " << strerror(errno) << ")." << endl;
return linenumber;
}
I am making a statistics collector that reads the log of a music player and lets the user show top ten most played etc. As a noob project.
A line from the log looks like: "20:42:03 start E:\ROTATION\A\Håkan Lidbo - Dammlunga.mp3"
I have put this in a string using ifstream and getline.
Then making an array of chars of the string using
const char *charveqtur = newline.c_str();
Then I tried to sort i out with sscanf:
sscanf (charveqtur, "%d:%d:%d\tstart\t%s", &this->hour, &this->minute, &this->second, &this->filename);
The problem is that the filename is cut at the first space. I have also tried using istringstream instead but no breakthrough so far.
Which is the most convinient way of doing this? Thanks.
You can use some input stream to read the first integers and colons, and because the filename is the last entity, you can then use std::getline. However, even if your filename is not the last part, note that std::getline is quite a versatile function that accepts any delimiter.
A more advanced method would be to define your own type for filenames and overload operator>>(std::istream &, T const &) on it.
Here is a complete example using std::getline and stringstream with basic diagnostics and some reformatting:
#include <sstream> // for istringstream
#include <iostream> // for cout and cerr
#include <iomanip> // for setprecision
#include <cmath>
bool read (std::string const &line) {
char c = 0;
double length;
double rating;
std::string title;
std::istringstream ss;
ss.str (line);
ss >> length;
if (!ss.good()) { std::cerr << "invalid length\n"; return false; }
if (ss.get()!=':') { std::cerr << "expected colon\n"; return false; }
ss >> rating;
if (!ss.good()) { std::cerr << "invalid rating\n"; return false; }
if (ss.get()!=':') { std::cerr << "expected colon\n"; return false; }
std::getline (ss, title);
double sink;
std::cout << title << " ("
<< int(length) << ':' << 60*std::modf (length,&sink)
<< " min), your rating: " << rating << '\n';
return true;
}
int main () {
read ("30.25:5:Vivaldi - The four seasons.ogg");
read ("3.5:5:Cannibal Corpse - Evisceration Plague.ogg");
read ("meh");
return 0;
}
Output:
Vivaldi - The four seasons.ogg (30:15 min), your rating: 5
Cannibal Corpse - Evisceration Plague.ogg (3:30 min), your rating: 5
invalid length
Important: When parsing, you are sailing close to the security risks. Always be conscious and sensible and try to use tested and proven libraries where possible. This also implies that you do not use sscanf, which is not typesafe, error-prone and sometimes hard to get right.
Don't use C if you have C++, and used correctly, iostreams are even more convenient than printf/scanf+co.
You could perhaps do something like
int lastpos = 0;
if sscanf (charveqtur, "%d:%d:%d\tstart\t%n", &this->hour,
&this->minute, &this->second,
&lastpos) > 3 && lastpos >0) {
std::string filename = newline.substr(lastpos);
/* do something with filename */
}