Read undefined number of variables from file line - c++

I am making a simple database system in C++.
The table data is stored in a file, where each line represents a table row, where all data is separated by spaces.
I want to read ncols elements in same line, where ncols is not always the same, and store each read value in data[x].
data variable declaration is char** data.
void Table::LoadTableRows(Table::TableStruct *table,char *dbname) {
ifstream fp;
Table::RowStruct *p = (Table::RowStruct*) malloc(sizeof(Table::RowStruct));
char *filename;
int x;
filename = (char*) malloc((strlen(table->tablename)+strlen(dbname)+strlen("Data"))*sizeof(char));
strcpy(filename,dbname);
strcat(filename,table->tablename);
strcat(filename,"Data");
fp.open(filename);
while(!fp.eof()) { //goes through all file lines
Table::RowStruct *newrow = (Table::RowStruct*) malloc(sizeof(Table::RowStruct)); //allocates space for a new row
//initializes element
newrow->prev = NULL;
newrow->next = NULL;
newrow->data = (char**) malloc(table->ncols*30*sizeof(char)); //allocates space to store the row data
for(x=0;x<table->ncols;x++) {
newrow->data[x] = (char*) malloc(30*sizeof(char)); //allocates space for individual data element
fp >> newrow->data[x];
}
for(p=table->rows;p->next!=NULL;p=p->next) {}
newrow->prev = p;
p->next = newrow;
}
fp.close();
}
I've tried this code, but it crashed as I expected.

I do not fully understand what you want to do. There is missing information. Anyway. I will try to help.
I guess that you are new to C++. You are using a lot of C functions. And your program looks completely like C, with some additional C++ features. That you should not do. You are especially using malloc and raw pointers. This you must not do at all.
Try to learn C++ step by step.
Let me first show you what I mean with C-Style programming. I took your program and added comments with hints.
// Do not pass arguments by pointer, pass by reference
// For invariants, pass as const T&
// Do not use "char *". Should be at least const. But do not use at all
// Use std::string (so pass "const std::string& dbname") as argument
void Table::LoadTableRows(Table::TableStruct *table,char *dbname) {
// Always initialize varibles. Use universal initialization, with {}
ifstream fp;
// Never use malloc. Use new.
// Do not use raw ptr. use std::unique_ptr. Initialize with std::make_unique
// Do not use C-Style cast. Use static_cast
Table::RowStruct *p = (Table::RowStruct*) malloc(sizeof(Table::RowStruct));
// Use std::string
char *filename;
int x;
// Again. No malloc, no C-Style cast
// Do not use C-Sytle string functions
filename = (char*) malloc((strlen(table->tablename)+strlen(dbname)+strlen("Data"))*sizeof(char));
// Do not use C-Sytle string functions
strcpy(filename,dbname);
// Do not use C-Sytle string functions
strcat(filename,table->tablename);
// Do not use C-Sytle string functions
strcat(filename,"Data");
// Check, if open works, Open file through constructor, then it will be closed by destructor
fp.open(filename);
while(!fp.eof()) { //goes through all file lines
// Do not use malloc and C-Style cast
Table::RowStruct *newrow = (Table::RowStruct*) malloc(sizeof(Table::RowStruct)); //allocates space for a new row
//initializes element
// Do not use NULL, but nullptr
newrow->prev = NULL;
newrow->next = NULL;
// Do not use malloc and C-Style cast
newrow->data = (char**) malloc(table->ncols*30*sizeof(char)); //allocates space to store the row data
// Do not use x++ but ++x
for(x=0;x<table->ncols;x++) {
// Do not use malloc and C-Style cast
newrow->data[x] = (char*) malloc(30*sizeof(char)); //allocates space for individual data element
// Check for out of bounds
fp >> newrow->data[x];
}
// Do not use selfmade linked list. Use STL container
for(p=table->rows;p->next!=NULL;p=p->next) {}
newrow->prev = p;
p->next = newrow;
}
fp.close();
}
You see, there is a lot of C in it and not so much C++.
The modern C++ makes much use of containers and algorithms.
A full fledged example for C++ is below. It is hard to understand for beginners. But try to analyze and you will get a hang of it.
#include <vector>
#include <string>
#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <sstream>
using AllWordsInOneLine = std::vector<std::string>;
using AllLines =std::vector<AllWordsInOneLine>;
struct Line // ! This is a proxy for the input_iterator !
{ // Input function. Read on line of text file and split it in words
friend std::istream& operator>>(std::istream& is, Line& line) {
std::string wholeLine; std::getline(is, wholeLine); std::istringstream iss{ wholeLine }; line.allWordsInOneLine.clear();
std::copy(std::istream_iterator<std::string>(iss), std::istream_iterator<std::string>(), std::back_inserter(line.allWordsInOneLine));
return is;
}
operator AllWordsInOneLine() const { return allWordsInOneLine; } // cast to needed result
AllWordsInOneLine allWordsInOneLine{}; // Local storage for all words in line
};
int main()
{
std::ifstream inFileStream{ "r:\\input.txt" }; // Open input file. Will be closed by destructor
if (!inFileStream) { // ! operator is overloaded
std::cerr << "Could not open input file\n";
}
else {
// Read complete input file into memory and organize it in words by lines
AllLines allLines{ std::istream_iterator<Line>(inFileStream), std::istream_iterator<Line>() };
// Make exact ncols entries.
const size_t ncols = 6; // whatever ncols may be. Empty cols will be filled with ___ (or whatever you like)
std::for_each(allLines.begin(), allLines.end(), [ncols](AllWordsInOneLine& awil) {awil.resize(ncols, "___"); });
// copy result to std::cout
std::for_each(allLines.begin(), allLines.end(), [](AllWordsInOneLine & awil) {std::copy(awil.begin(), awil.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << '\n'; });
}
return 0;
}
Please see especially that the whole file, with all lines split into words, will be read in one line of code in function main.
An additional one-liner converts this into a vector with exactly ncols elements (words). This regardless if there were more or less than ncols words per line in the source file.
Hope I could help at least a little bit.

char *filename;
filename = (char*) malloc((strlen(table->tablename)+strlen(dbname)+strlen("Data"))*sizeof(char));
strcpy(filename,dbname);
strcat(filename,table->tablename);
strcat(filename,"Data");
Here's your first problem. You haven't allocated space for the terminating nul byte at the end of the string. I'm not sure why you're using C-style strings instead of std::string, but C-style strings use a zero byte at the end to mark the end of the string.
fp.open(filename);
while(!fp.eof()) { //goes through all file lines
You are misusing eof. It can't predict that a future read will succeed, it's not a future-predicting function but a past reporting function.
newrow->data = (char**) malloc(table->ncols*30*sizeof(char)); //allocates space to store the row data
This is puzzling. The type is char **, which means you're allocating a pointer to a pointer. Yet you allocate space for 30 characters. Why allocate 30 characters for a pointer?
fp >> newrow->data[x];
You don't check if this read succeeds. That's never a good thing and makes your program impossible to debug.
These are the major issues that immediately stand out.

Related

Read binary bytes beginning at position N within ifstream?

I am writing an unknown number of structs to a binary file and then reinterpret_cast-ing the bytes back to the struct. I know how to write the bytes.
I am unsure how to iterate over the binary file. I would like to use std::ifstream. At some point I must need to increment a file pointer/index by sizeof(struct) bytes, but the only examples (of reading binary in to structs) I could find online were writing N structs and then reading N structs, they were not looping over the file, incrementing any file index.
Pseudo code of what I would like to achieve is:
std::ifstream file("test.txt", std::ifstream::binary);
const size_t fileLength = file.size();
size_t pos = 0;
while(pos < fileLength)
{
MyStruct* ms = &(reinterpret_cast<MyStruct&>(&file[pos]));
// Do whatever with my struct
pos += sizeof(MyStruct);
}
UPDATE:
My struct is POD
#include <fstream>
struct MyStruct{};
int main()
{
std::ifstream file("test.txt", std::ifstream::binary);
MyStruct ms;
//Evaluates to false if anything wrong happened.
while(file.read(reinterpret_cast<char*>(&ms),sizeof ms))
{
// Do whatever with my struct
}
if(file.eof())
;//Successfully iterated over the whole file
}
Please be sure not to do something like this:
char buffer[sizeof(MyStruct)];
file.read(buffer,sizeof(MyStruct));
//...
MyStruct* myStruct = reinterpret_cast<MyStruct*>(buffer);
It will likely work, but it breaks the aliasing rule and is undefined behaviour. If you truly need a buffer ( e.g. for small files it might be faster to read the whole file into the memory first and then iterate over that buffer) then the correct way is:
char buffer[sizeof(MyStruct)];
file.read(buffer,sizeof(MyStruct));
//...
MyStruct myStruct;
std::memcpy(&myStruct,buffer,sizeof myStruct);

C++ cin.getline to char array

I have declared an array: names[1000];
and another array, data[1000];, to store the data temporarily
and later used an ifstream to read data from an XML file.
then later, I used cin.getline(data, 300) to put the data into data[] array.
but when I assign data[] array to names[] array, an error occurs:
invalid operands of types char[1000] and char[1000] to binary operator>>
code:
char data[1000];
char names[1000];
ifstream openFile("myfile.xml");
if(!openFile)
{
cout<<"File not found! please re-enter filename"<<endl;
}
while (openFile.getline (data, 300))
{
if (data[0] == '<' && data[1] == 'n') // to only check the <name> xml tag
{
cout<<data<<endl;
data >> names;
}
}
Any idea why I cant assign data array to names array?
Thanks!
">>" operator is usually defined for streams, but data is just an array.
if you want to copy the content, use strncpy from string.h:
strncpy(names, data, 1000);
if you want to treat your string/array as stream, try stringstream.
BTW, you may want to use C++ string instead of character arrays -- it's more convenient (but not so efficient).
The operator >> is not used for assignment ! You could assign using strncpy like this
strncpy(names, data, 1000);
and add the include
#include <string.h>
Because you can't assign arrays. At all.
You may want to take a look at std::vector or std::string, which can be assigned among other cool things.
If you want to stick to char arrays, you can do the following :
std::copy(std::begin(data), std::end(data), std::begin(names));
Or (to avoid copying trash after the 300th element) :
std::copy(std::begin(data), std::begin(data) + 301, std::begin(names));

Writing struct of vector to a binary file in c++

I have a struct and I would like to write it to a binary file (c++ / visual studio 2008).
The struct is:
struct DataItem
{
std::string tag;
std::vector<int> data_block;
DataItem(): data_block(1024 * 1024){}
};
I am filling tha data_block vector with random values:
DataItem createSampleData ()
{
DataItem data;
std::srand(std::time(NULL));
std::generate(data.data_block.begin(), data.data_block.end(), std::rand);
data.tag = "test";
return data;
}
And trying to write the struct to file:
void writeData (DataItem data, long fileName)
{
ostringstream ss;
ss << fileName;
string s(ss.str());
s += ".bin";
char szPathedFileName[MAX_PATH] = {0};
strcat(szPathedFileName,ROOT_DIR);
strcat(szPathedFileName,s.c_str());
ofstream f(szPathedFileName, ios::out | ios::binary | ios::app);
// ******* first I tried to write this way then one by one
//f.write(reinterpret_cast<char *>(&data), sizeof(data));
// *******************************************************
f.write(reinterpret_cast<const char *>(&data.tag), sizeof(data.tag));
f.write(reinterpret_cast<const char *>(&data.data_block), sizeof(data.data_block));
f.close();
}
And the main is:
int main()
{
DataItem data = createSampleData();
for (int i=0; i<5; i++) {
writeData(data,i);
}
}
So I expect a file size at least (1024 * 1024) * 4 (for vector)+ 48 (for tag) but it just writes the tag to the file and creates 1KB file to hard drive.
I can see the contents in while I'm debugging but it doesn't write it to file...
What's wrong with this code, why can't I write the strcut to vector to file? Is there a better/faster or probably efficient way to write it?
Do I have to serialize the data?
Thanks...
Casting a std::string to char * will not produce the result you expect. Neither will using sizeof on it. The same for a std::vector.
For the vector you need to use either the std::vector::data method, or using e.g. &data.data_block[0]. As for the size, use data.data_block.size() * sizeof(int).
Writing the string is another matter though, especially if it can be of variable length. You either have to write it as a fixed-length string, or write the length (in a fixed-size format) followed by the actual string, or write a terminator at the end of the string. To get a C-style pointer to the string use std::string::c_str.
Welcome to the merry world of C++ std::
Basically, vectors are meant to be used as opaque containers.
You can forget about reinterpret_cast right away.
Trying to shut the compiler up will allow you to create an executable, but it will produce silly results.
Basically, you can forget about most of the std::vector syntactic sugar that has to do with iterators, since your fstream will not access binary data through them (it would output a textual representation of your data).
But all is not lost.
You can access the vector underlying array using the newly (C++11) introduced .data() method, though that defeats the point of using an opaque type.
const int * raw_ptr = data.data_block.data();
that will gain you 100 points of cool factor instead of using the puny
const int * raw_ptr = &data.data_block.data[0];
You could also use the even more cryptic &data.data_block.front() for a cool factor bonus of 50 points.
You can then write your glob of ints in one go:
f.write (raw_ptr, sizeof (raw_ptr[0])*data.data_block.size());
Now if you want to do something really too simple, try this:
for (int i = 0 ; i != data.data_block.size() ; i++)
f.write (&data.data_block[i], sizeof (data.data_block[i]));
This will consume a few more microseconds, which will be lost in background noise since the disk I/O will take much more time to complete the write.
Totally not cool, though.

C++ multidimension array storing strings

I wish to store for example 10 words into a multi-d array. This is my code.
char array[10][80]; //store 10 words, each 80 chars in length, get from file
int count = 0;
while ( ifs >> word ){ //while loop get from file input stream <ifstream>
array[count++][0] = word;
}
when i compile, there's error. "invalid conversion from ‘char*’ to ‘char’ ". ifs return a char pointer. How can i succesffuly store into array?
As this is C++, I would use the STL containers to avoid some char* limitations. word would have type std::string, array would have type std::vector<std::string> and you would push_back instead of assigning. The code looks like this:
#include <string>
#include <vector>
std::string word;
std::vector<std::string> array;
while(ifs >> word) {
array.push_back(word);
}
This is better than char* for a few reasons: you hide the dynamic allocation, you have words with real variable size(up to memory size), and you don't have any issues if you need more than 10 words.
Edit: as mentioned in the comments, if you have a compiler that supports C++11, you can use emplace_back and std::move instead, which will move the string instead of copying it (emplace_back alone will construct the string inplace.)
You should define a pointer to array I think, that can access each value of array blocks one by one (or the way you want). You can also try dynamic allocation. Those are pointer things so then it'll be comparable easily.
word is char*(string), but array[count++][0] is store a char, you can change "array[count++][0] = word;" to "strcpy(array[count++], word);"
char array[10][80]; //store 10 words, each 80 chars in length, get from file
int count = 0;
while ( ifs >> word ){ //while loop get from file input stream <ifstream>
strcpy(array[count++], word);
}

Use C++ strings in file handling

How to use C++ strings in file handling? I created a class that had C++ string as one of its private data members but that gave an error while reading from the file even if I am not manipulating with it at the moment and was initialised with default value in constructor. There is no problem while writing to the file. It works fine if I use C string instead but I don't want to. Is there a way to solve this?
class budget
{
float balance;
string due_name,loan_name; //string objects
int year,month;
float due_pay,loan_given;
public:
budget()
{
balance=0;
month=1;
due_name="NO BODY"; //default values
loan_name="SAFE";
year=0;
balance = 0;
due_pay=0;
loan_given=0;
}
.
.
.
};
void read_balance() //PROBLEM AFTER ENTERING THIS FUNCTION
{
system("cls");
budget b;
ifstream f1;
f1.open("balance.dat",ios::in|ios::binary);
while(f1.read((char*)&b,sizeof(b)))
{ b.show_data();
}
system("cls");
cout<<"No More Records To Display!!";
getch();
f1.close();
}
String is non-POD data-type. You cannot read/write from/in string by read/write functions.
basic_istream<charT,traits>& read(char_type* s, streamsize n);
30 Effects: Behaves as an unformatted input function (as described in
27.7.2.3, paragraph 1). After constructing a sentry object, if !good() calls setstate(failbit) which may throw an exception, and return.
Otherwise extracts characters and stores them into successive
locations of an array whose first element is designated by s.323
Characters are extracted and stored until either of the following
occurs: — n characters are stored; — end-of-file occurs on the input
sequence (in which case the function calls setstate(failbit | eofbit),
which may throw ios_base::failure (27.5.5.4)). 31 Returns: *this.
There is nothing about, how members of std::string placed. Look at, or use boost::serialiation. http://www.boost.org/doc/libs/1_50_0/libs/serialization/doc/index.html And of course you can write size of string and then write data and when read - read size, allocate array of this size, read data in this array and then create string. But use boost is better.
While reading the string members (due_name,loan_name) of your class budget your code literally fills them byte by byte. While it makes sense for floats and ints it won't work for strings.
Strings are designed to keep 'unlimited' amount of text, therefore their constructors, copy constructors, concatenations and so on must ensure to allocate the actual piece of memory to store the text and expand it if necessary (and delete upon destruction). Filling strings this way from disk will result in invalid pointers inside your string objects (not pointing to the actual memory which contains the text), actually no text will be actually read this way at all.
The easiest way to solve this is to not use C++ strings in that class. Work out the maximum length for each of the strings you will be storing, and make a char array that is one byte longer (to allow for the 0-terminator). Now you can read and write that class as binary without worrying about serialization etc.
If you don't want to do that, you cannot use iostream::read() on your class. You will need member functions that read/write to a stream. This is what serialization is about... But you don't need the complexity of boost. In basic terms, you'd do something like:
// Read with no error checking :-S
istream& budget::read( istream& s )
{
s.read( (char*)&balance, sizeof(balance) );
s.read( (char*)&year, sizeof(year) );
s.read( (char*)&month, sizeof(month) );
s.read( (char*)&due_pay, sizeof(due_pay) );
s.read( (char*)&loan_given, sizeof(loan_given) );
size_t length;
char *tempstr;
// Read due_name
s.read( (char*)&length, sizeof(length) );
tempstr = new char[length];
s.read( tempstr, length );
due_name.assign(tempstr, length);
delete [] tempstr;
// Read loan_name
s.read( (char*)&length, sizeof(length) );
tempstr = new char[length];
s.read( tempstr, length );
loan_name.assign(tempstr, length);
delete [] tempstr;
return s;
}
ostream& budget::write( ostream& s )
{
// etc...
}
Notice above that we've serialized the strings by writing a size value first, and then that many characters after.