I am trying to pull characters from a specific column (in this case, 0) of a text file, and load them into a vector. The code seems to work ok, until it reaches the end, when I get a "string subscript out of range" error, and I do not know how to fix this. Does anyone know what I can do? Here is the relevant code.
class DTree
{
private:
fstream newList;
vector<string> classes;
public:
DTree();
~DTree();
void loadAttributes();
};
void DTree::loadAttributes()
{
string line = "";
newList.open("newList.txt");
string attribute = "";
while(newList.good())
{
getline(newList, line);
attribute = line[0];
classes.push_back(attribute);
}
}
Please try 'while(getline(newList, line)'
Refer here
You can also try something like
ifstream ifs("filename",ios::in);
string temp;
getline(ifs,temp)// Called as prime read
while(ifs)
{
//Do the operations
// ....
temp.clear();
getline(ifs,temp);
}
ifs.clear();
ifs.close();
This works for almost all kinds of files.You can replace getline(ifs,temp) by get() function or by >> operator based on your requirements.
Related
I am trying to write up a config parser class in c++.
I'll first give a snippet of my class:
class foo{
private:
struct st{
std::vector<pair<std::string,std::string>> dvec;
std::string dname;
}
std::vector<st> svec;
public:
//methods of the class
}
int main(){
foo f;
//populate foo
}
I will populate the vectors with data from a file. The file has some text with delimiters. I'll break up the text into strings using the delimiters. I know the exact size of the file and since I am keeping all data as character string, it's safe to assume the vector svec will take the same memory space as the file size. However, I don't know how many strings there will be. e.g., I know the file size is 100 bytes but I don't know if it's 1 string of 100 characters or 10 strings of 10 characters each or whatever.
I would like to avoid reallocation as much as possible. But std::vector.reserve() and std::vector.resize() both allocate memory in terms of number of elements. And this is my problem, I don't know how many elements there will be. I just know how many bytes it will need. I dug around a bit but couldn't find anything.
I am guessing I will be cursed if I try this- std::vector<st> *svec = (std::vector<st> *) malloc(filesize);
Is there any way to reserve memory for vector in terms of bytes instead of number of elements? Or some other workaround?
Thank you for your time.
Edit: I have already written the entire code and it's working. I am just looking for ways to optimize it. The entire code is too long so I will give the repository link for anyone interested- https://github.com/Rakib1503052/Config_parser
For the relevant part of the code:
class Config {
private:
struct section {
std::string sec_name;
std::vector<std::pair<std::string, std::string>> sec_data;
section(std::string name)
{
this->sec_name = name;
//this->sec_data = data;
}
};
//std::string path;
std::vector<section> m_config;
std::map<std::string, size_t> section_map;
public:
void parse_config(std::string);
//other methods
};
void Config::parse_config(string path)
{
struct stat buffer;
bool file_exists = (stat(path.c_str(), &buffer) == 0);
if (!file_exists) { throw exception("File does not exist in the given path."); }
else {
ifstream FILE;
FILE.open(path);
if (!FILE.is_open()) { throw exception("File could not be opened."); }
string line_buffer, key, value;;
char ignore_char;
size_t current_pos = 0;
//getline(FILE, line_buffer);
while (getline(FILE, line_buffer))
{
if (line_buffer == "") { continue; }
else{
if ((line_buffer.front() == '[') && (line_buffer.back() == ']'))
{
line_buffer.erase(0, 1);
line_buffer.erase(line_buffer.size() - 1);
this->m_config.push_back(section(line_buffer));
current_pos = m_config.size() - 1;
section_map[line_buffer] = current_pos;
}
else
{
stringstream buffer_stream(line_buffer);
buffer_stream >> key >> ignore_char >> value;
this->m_config[current_pos].sec_data.push_back(field(key, value));
}
}
}
FILE.close();
}
}
It reads an INI file of the format
[section1]
key1 = value1
key2 = value2
[section2]
key1 = value1
key2 = value2
.
.
.
However, after some more digging I found out that std::string works differently than I thought. Simply put, the strings themselves are not in the vector. The vector only holds pointers to the strings. This makes my case moot.
I'll keep the question here for anyone interested. Especially, if the data is changed to unary types like int or double, the question stands and it has a great answer here- https://stackoverflow.com/a/18991810/11673912
Feel free to share other opinions.
I am trying to work with ifstream and istringstream using only on variable. I know that both of them are children of istream. So, I am trying to make only one variable of type istream and the intialize depending on some input.
The real problem is that I asked the user to input file path or content of file. Then, I will read it line by line. I tried to do like this.
istream * stream;
if(isFile){
ifstream a("fileOrContent");
stream = &a;
} else {
istringstream a("fileOrContent");
stream = &a;
}
getline(stream,line)
// do something with line
I also tried this
ifstream stream;
if(isFile){
ifstream stream("fileOrContent");
} else {
istringstream stream("fileOrContent");
}
getline(stream,line)
// do something with line
Currently, I using two full copies of my code for each one. Any suggestions of how I might do it?
Thank you
How about refactoring your code like this:
void process(std::istream & is)
{
// ....
}
int main()
{
if (isFile)
{
std::ifstream is("foo.txt");
process(is);
}
else
{
std::istringstream is(str);
process(is);
}
}
What you are trying to do is something like this:
istream * stream;
if(isFile){
stream = new ifstream("fileOrContent");
} else {
stream = new istringstream("fileOrContent");
}
getline(*stream,line)
That said you should use a smart pointer to hold the istream pointer to avoid memory leaks, as pointed out by #πάντα ῥεῖ.
i just did something similar to this. you are almost there
if(isFile) {
stream = new ifstream("whatever");
} else {
stream = new istringstream("whatever");
}
getline(*stream, line);
make sure to delete it though
If you don't want to manage the memory yourself, you can use an unique_ptr which will automatically free the memory when it goes out of scope:
#include <memory>
std::unique_ptr<std::istream> stream;
if(isFile){
stream = std::unique_ptr<std::istream>(new ifstream("fileOrContent"));
} else {
stream = std::unique_ptr<std::istream>(new istringstream("fileOrContent"));
}
getline(*stream,line)
Put the getline and the "do something with line" in a function which takes a std::istream & argument.
Then create either an ifstream or an istringstream, and place the function call into the if/else branches.
void DoSomethingWithLine(std::istream &stream)
{
getline(stream,line);
// do something with line
}
if (isFile){
ifstream a("fileOrContent");
DoSomethingWithLine(a);
} else {
istringstream a("fileOrContent");
DoSomethingWithLine(a);
}
It won't get much simpler than this.
I am making a file reading class. It should, when constructed open the file with the given string and depending on which constructor is called use the second string supplied to skip through the file to the line after the string given.
Here is my code as it stands:
SnakeFileReader::SnakeFileReader(string filePath)
{
fileToRead_.open(filePath.c_str(), ios::in);
}
SnakeFileReader::SnakeFileReader(string filePath, string startString)
{
fileToRead_.open(filePath.c_str(), ios::in);
string toFind;
while (toFind != startString && !fileToRead_.eof())
{
fileToRead_ >> toFind;
}
}
string SnakeFileReader::ReadLine()
{
string fileLine;
if (!fileToRead_.fail() && !fileToRead_.eof())
fileToRead_ >> fileLine;
return fileLine;
}
int SnakeFileReader::ReadInt()
{
string fileLine = "";
if (!fileToRead_.fail() && !fileToRead_.eof())
fileToRead_ >> fileLine;
int ret;
istringstream(fileLine) >> ret;
return ret;
}
SnakeFileReader::~SnakeFileReader()
{
fileToRead_.close();
}
The problem I have is that in the second constructor I get a segmentation fault. I get another segmentation fault in the read line function as soon as I declare a string.
[Edit] Here is the extra code requested. I am making a "Snake Game" as a part of the first year of my degree. I want the game to read and save files rather than hard code variable values. I will finally be using this class a lot to setup a level in the game. However here are a few lines that should demonstrate how i intend to use this class:
//Level.cpp
std::string fileToRead = "resources/files/level1.txt";
SnakeFileReader sfr(fileToRead);
std::string mapFilePath = sfr.ReadLine();
ImageFile(mapFilePath).load(map_layout);
mapWidth_ = sfr.ReadInt();
mapHeight_ = sfr.ReadInt();
level_cell_size_ = sfr.ReadInt();
map_ = new TileData*[mapWidth_];
for (int i = 0; i < mapWidth_; i++)
{
map_[i] = new TileData[mapHeight_];
}
Layout of the file:
resources/images/Map1_Layout.bmp
40
30
20
Class declaration:
#ifndef SNAKE_FILE_READER_HPP
#define SNAKE_FILE_READER_HPP
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
class SnakeFileReader
{
public:
SnakeFileReader(string filePath);
SnakeFileReader(string filePath, string startString);
~SnakeFileReader();
string ReadLine();
int ReadInt();
private:
ifstream fileToRead_;
};
#endif // SNAKE_FILE_READER_HPP
in the ReadLine function, you return a reference to a variable allocated on the functions stack. you are corrupting the stack, crazy things can happen. your compiler should have warned you about that.
I'm not sure about your constructor, but the problem with ReadLine() is that you're trying to return the memory address of an automatic variable, which is destroyed when you exit the function.
The simplest fix would be to remove the '&' on the return value, and just return a string. But if you're determined to return a memory address, try this instead:
string *SnakeFileReader::ReadLine()
{
string *fileLine = new string;
if (!fileToRead_.fail() && !fileToRead_.eof())
fileToRead_ >> *fileLine;
return fileLine;
}
This will dynamically allocate the string and pass back the pointer. The difference is that dynamic variables are not automatically destroyed when you leave their scope. The string will still exist on the heap until you delete it yourself (which you must remember to do when you're done with it).
I want to create a struct containing a double and a vector of strings. I tried this
int main ()
{
struct List
{
double price;
vector<string> items;
};
List list;
ifstream infile ("Aap.txt");
double p;
infile>>p;
list.price=p;
cout<<list.price<<endl;
int i=0;
string name;
getline(infile,name);
while(infile)
{
list.items.push_back(name);
cout<<list.items[i]<<endl;
i++;
getline(infile,name);
}
infile.close();
if (!infile)
{
cout<<"File closed."<<endl;
}
return 0;
This is not filling my vector, because it is not by reference in the struct I suppose?
But when I define the vector in the struct as:
vector<string>& items;
I get an error saying:
error: structure `list' with uninitialized reference members.
How can I fix this?
Thank you for helping!
Your code works perfectly fine.
There's a little issue of the first read: if you have a newline character in your input file after the price value, that newline character will remain unread. In this situation the first call to getline will read an empty string, meaning that the first name in your items array will end up empty.
I'm writing a program for an exercise that will read data from a file and format it to be readable. So far, I have a bit of code that will separate a header from the data that goes under it. Here it is:
int main() {
ifstream in("records.txt");
ofstream out("formatted_records.txt");
vector<string> temp;
vector<string> headers;
for (int i = 0; getline(in,temp[i]); ++i) {
static int k = -1;
if (str_isalpha(temp[i])) {
headers[++k] = temp[i];
temp.erase(temp.begin() + i);
}
else {
temp[i] += "," + headers[k];
}
}
}
(str_isalpha() is just a function that applies isalpha() to every character in a string.) Now, the for-loop in this program doesn't execute, and I can't figure out why. Does anybody know?
EDIT: As suggested, I changed it to
string line;
for (int i = 0; getline(in,line); ++i) {
temp.push_back(line);
Still skips the for-loop altogether.
vector<string> temp; makes an empty vector. When you then try to read into temp[0], that is undefined behavior. You should pass as getline's second argument a separate string variable, say string foo; before the loop, then temp.push_back(foo); as the first instruction in the loop's body.
If the loop still doesn't run after ensuring that you're reading into a valid string reference, then you should check that the stream you're reading from is valid. The stream will be invalid if the file doesn't exist or if you lack permission to read it, for instance. When the stream isn't valid, getline won't read anything. Its return value is the same stream, and when converted to bool, it evaluates as false. Check the stream's status before proceeding.
ifstream in("records.txt");
if (!in.is_open()) {
std::cerr << "Uh-oh.\n";
return EXIT_FAILURE;
}