I'm very new to c++ (and the question is a part of my homework).
I need to store an array of structs in a binary file and read them back. The problem is that my struct contains std::string field. On the other side I can read the string, but nothing after it. Here is my structure definition:
struct Organization {
int id;
string name;
float paidTaxes;
};
and here is the code where I write array of this struct to a file:
void writeToFile(Organization *orgs, int n) {
ofstream file("orgs.bin", ios::out | ios::binary);
if (!file) { return; }
for (int i = 0; i < n; i++) {
// determine the size of the string
string::size_type sz = orgs[i].name.size();
file.write(reinterpret_cast<char*>(&orgs[i].id), sizeof(int));
// write string size
file.write(reinterpret_cast<char*>(&sz), sizeof(string::size_type));
// and actual string
file.write(orgs[i].name.data(), sizeof(sz));
file.write(reinterpret_cast<char*>(&orgs[i].paidTaxes), sizeof(float));
}
file.close();
}
and here is the code part where I'm reading this file back:
count = 0;
Organization org;
ifstream file;
file.open("orgs.bin", ios::binary);
while (file.good()) {
string::size_type sz;
file.read(reinterpret_cast<char*>(&org.id), sizeof(int));
file.read(reinterpret_cast<char*>(&sz), sizeof(string::size_type));
org.name.resize(sz);
file.read(&org.name[0], sz);
file.read(reinterpret_cast<char*>(&org.paidTaxes), sizeof(float));
count++;
}
As far as I understand, I need to run this loop to determine how many structs are stored in a file. When I run debugger, I successfully read id field, string size and actual string. However, I never get proper paidTaxes field (type of float) and subsequent loops return me junk.
thanks in advance!
The problem is in this line
file.write(orgs[i].name.data(), sizeof(sz));
Here is a typo, insted use
file.write(orgs[i].name.data(), sz);
You shoud write sz bytes, but not sizeof(sz). There is also one more problem with your code. You will read the last record twice, because file.good() will return true after the last record. You can read like that:
while (file.good()) {
string::size_type sz;
if( !file.read(reinterpret_cast<char*>(&org.id), sizeof(int)) )
break;
file.read(reinterpret_cast<char*>(&sz), sizeof(string::size_type));
org.name.resize(sz);
file.read(&org.name[0], sz);
file.read(reinterpret_cast<char*>(&org.paidTaxes), sizeof(float));
count++;
std::cout << org.id << " " << org.name << " " << org.paidTaxes << std::endl;
}
Related
Newb here... taking a C++ class on data structures. I am making a program that takes a list of chores from a text file and stores them in a dynamic array.
//In header/ In class:
private:
/* var to keep len of list */
int len = 99; // Not sure what to set this to or if I need to even set it.
/* add appropriate data structure to store list */
string *arr = new string[len];
//In .cpp:
ListOfChores::ListOfChores(string fileName) {
ifstream file(fileName, ifstream::in);
string line;
if (file.is_open()) //Checking if the file can be opened
{
while (!file.eof()) // To get all the lines.
{
getline(file, line); // Gets a single line
arr[len] = line; // Store a line in the array
len++; // Increases the array size by one
}
file.close(); // Closes file
}
else cout << "Unable to open file" << endl; // Gives error if the file can't be opened
}
But I am getting an error for storing a line in the array. It says "Access violation reading location." There is another function executed in the main.cpp for printing the lines.
You overrun your array buffer at once because len is already 99. You should have a notion of capacity and length. Capacity is the maximum you can store without reallocating, and length is the actual number of data lines.
Please avoid this C-style array in C++ code. Use vector, which has been around for at least 20 years (STL) if I'm not mistaken.
(you're not a lost cause, you are already using std::string :))
Check this:
#include <vector>
//In header/ In class:
private:
/* add appropriate data structure to store list */
std::vector<string> arr; // define a vector
//In .cpp:
ListOfChores::ListOfChores(string fileName) {
ifstream file(fileName, ifstream::in);
string line;
if (file.is_open()) //Checking if the file can be opened
{
while (getline(file, line))
{
arr.push_back(line);
}
file.close(); // Closes file
}
else cout << "Unable to open file" << endl; // Gives error if the file can't be opened
}
Now arr.size() holds the number of lines, it is no longer limited to 99 lines but to the max. program memory capacity. You can still access line number 13 by arr[12] or arr.at(12) for boundary checked access.
proper way to iterate through it (C++11) for instance to print all lines:
for (auto s : arr)
{
std::cout << s << std::endl;
}
Now, if you REALLY have to use an array, you can emulate/mimic what vector does (well, not as performant I'm sure, but does the job):
private:
int len=0;
int capacity=100;
string *arr = new string[capacity];
now in the code, just before inserting (untested, but the idea is right):
if (len>=capacity)
{
string *narr = new string[capacity+100];
for (int i = 0; i < capacity; i++)
{
narr[i] = arr[i];
}
delete [] arr;
arr = narr;
capacity += 100; // growth
}
(you cannot use realloc or memcpy because you're handling objects in the arrays)
I am working with images. I would like to extract width and height of it from a header.
The width is represented at location
size
4B width
4B height
they are represented at specific index.
I tried parse it and extract it with code
ifstream f(name, ios::binary | ios:: in ); // reading a file in binary
ostringstream ob;
if ( f.fail()){ // fail test
return false;
}
f.seekg (0, f.end);
int length = f.tellg(); // length of the file
memory = new char[length]; // allocate array of chars
f.read (memory, length); // read the content of the file into an array
f.seekg (0, f.beg); // point back at the beginning of the file.
Each of them has 4B , so using for loop
for ( int i = index ; i <4 ;i++){
cout << hex<< memory[i];
}
or i even tried it converting it into number using
string a;
for ( int i = index; i < 4 ;i++{
a+=memory[i];
}
cout << atoi( a.c_str() ) << endl;
Should output a number , but it output some unreadable format.
You're seeking to the beginning of the file after reading it, rather than before. You need to switch round the read() and the seekg():
f.seekg (0, f.beg); // point back at the beginning of the file.
f.read (memory, length); // read the content of the file into an array
You don't give very much information to go on so a little guesswork here but this is how I might approach reading the file:
The main features being, don't use manual memory allocation, use a std::vector (its what its for). Also copy the data from the char array into variables cast from the correct type. This ensures alignment is correct (never cast into the char array). Another approach might be to read directly from the file into the correctly typed variables cast into char*.
int main(int, char* argv[])
{
// first parameter needs to be file name
std::string name = argv[1] ? argv[1]:"";
std::ifstream ifs(name, std::ios::binary|std::ios::ate); // open at end
if(!ifs)
{
std::cerr << std::strerror(errno) << '\n';
return EXIT_FAILURE;
}
if(ifs.tellg() < 8) // too small
{
std::cerr << "Bad image file, too short" << '\n';
return EXIT_FAILURE;
}
// Don't allocate memory manually, use a container
std::vector<char> image(ifs.tellg()); // big enough for whole file
ifs.seekg(0); // back to beginning
if(!ifs.read(image.data(), image.size()))
{
std::cerr << std::strerror(errno) << '\n';
return EXIT_FAILURE;
}
// copy raw data into variables
std::uint32_t width; // 4 bytes wide integer
std::uint32_t height; // 4 bytes wide integer
std::copy(&image[4], &image[ 8], (char*)&width); // offset 4 bytes
std::copy(&image[8], &image[12], (char*)&height); // offset 8 bytes
// at this point a lot depends on the system architecture and
// how the number is stored in the file. The documentation
// should tell you if it is little-endian or big-endian
// you may have to do manual jiggery-pokery
// to change endienness
std::cout << "width : " << width << '\n';
std::cout << "height: " << height << '\n';
}
For a quick answer of the problem stated in the title, and by looking at the input file you specify, you could simply read three lines, skip the first and then extract one integer each from the following two lines.
So something like
int width, height;
std::string input;
std::istringstream is;
std::getline(stream, input); // One line for the "size" string
std::getline(stream, input); // One line for the width
is.str(input)
is >> width;
std::getline(stream, input); // One line for the height
is.str(input)
is >> height;
If you have multiple entries like this in the file, then do the above in a loop.
If the file doesn't actually contain the texts you show, just the numbers, then it's even simpler:
int width, height;
stream >> width >> height;
Or if you have multiple entries
while (stream >> width >> height) { ... }
std::cout will print data passed as characters if the data is char, so try casting before printing.
for ( int i = 0 ; i < 4 ; i++) {
cout << hex<< (int)(unsigned char)memory[index + i];
}
If the number in the file is little endian, it can be converted to integer like this:
// Use #include <cstdint> for using type uint32_t and uint8_t
uint32_t num = 0; // the converted number will be here
for ( int i = 0 ; i < 4 ; i++) {
num |= (uint8_t)memory[index + i] << (uint32_t)(8 * i);
}
I tried making a program earlier that tells the user then number of char, words, and lines in a text file. I made functions to determine the numbers of each, yet I was passing them by value. This resulted in an error since after reading the number of char it would be at the end of the file and then output zero for the other two. Now I cant seem to rewrite my functions so that the file is open and closed each time its checked for char, words, and lines. Any one see where my errors are?? Thanks! (just copied and pasted one of my functions for now).
int num_of_lines(ifstream file)
{
string myfile;
myfile = argv[1];
ifstream l;
l.open(myfile);
int cnt3 = 0;
string str;
while(getline(file, str))cnt3++;
l.close();
return(cnt3);
}
int main(int argc, char **argv)
{
int num_of_char(ifstream file);
string file;
file = argv[1];
if(argc == 1)die("usage: mywc your_file");
ifstream ifs;
ifs.open(file);
if(ifs.is_open())
{
int a, b, c;
a = num_of_lines(ifs);
cout <<"Lines: " << a << endl;
}
else
{
cerr <<"Could not open: " << file << endl;
exit(1);
}
ifs.close();
return(0);
}
There is no way to "reopen" a file other than knowing the name and creating a new ifstream, but you can use the seekg member function to set your read position in the file, and setting it to 0 will have the next read operation start from the beginning of the file.
A stream is not possible to copy, so you can't pass it "by value", but must pass it by reference.
int num_of_lines(ifstream &file)
{
int count = 0;
string str;
while (getline(file, str)) {
count++;
}
file.seekg(0);
return count;
}
For the full problem, I agree with Mats Petersson, though. Counting both characters, lines and words in one pass will be much more efficient than reading through the file three times.
This seems like such an easy task, but everything I've tried hasn't worked so far.
I have a file foo.txt:
3
3 4 2
Now I want to read this file, read the first line and instantiate an int array with the size of the number it read on the first line.
Then it should populate that array with the elements in the second line, which has the exact same amount of elements and noted in line one.
If we're going to give you example code, might as well show you the best way to do it:
std::ifstream datafile("foo.txt");
if (!datafile) {
std::cerr << "Could not open \'foo.txt\', make sure it is in the correct directory." << std::endl;
exit(-1);
}
int num_entries;
// this tests whether the number was gotten successfully
if (!(datafile >> num_entries)) {
std::cerr << "The first item in the file must be the number of entries." << std::endl;
exit(-1);
}
// here we range check the input... never trust that information from the user is reasonable!
if (num_entries < 0) {
std::cerr << "Number of entries cannot be negative." << std::endl;
exit(-2);
}
// here we allocate an array of the requested size.
// vector will take care of freeing the memory when we're done with it (the vector goes out of scope)
std::vector<int> ints(num_entries);
for( int i = 0; i < num_entries; ++i )
// again, we'll check if there was any problem reading the numbers
if (!(datafile >> ints[i])) {
std::cerr << "Error reading entry #" << i << std::endl;
exit(-3);
}
}
Demo (with small changes because I can't provide a file with the right name on ideone): http://ideone.com/0vzPPN
You need to use ifstream object just like you use cin
ifstream fin("foo.txt"); //open the file
if(!fin.fail()){
int count;
fin>>count; //read the count
int *Arr = new int[count];
for(int i=0;i<count;i++){ //read numbers
fin>>Arr[i];
}
//... do what you need ...
//... and finally ...
delete [] Arr;
}
If you open a file using input filestream you can simply do that:
std::ifstream file_txt("file.txt");
int number_count = 0;
file_txt >> number_count; // read '3' from first line
for (int number, i = 0; i < number_count; ++i) {
file_txt >> number; // read other numbers
// process number
}
Filestreams just like other standard streams (std::cin, std::cout) can apply formatting depending on type supplied to operator>> (in this case an int).
This apply to both input and output.
Alternatively, you could avoid the entire need to read in the size beforehand by simply loading it into a std::vector:
std::ifstream fin("myfile.txt");
std::vector<int> vec{std::istream_iterator<int>(fin), std::istream_iterator<int>()};
fin.close();
or, if you cannot use C++11 syntax:
std::ifstream fin("myfile.txt");
std::vector<int> vec;
std::copy(std::istream_iterator<int>(fin), std::istream_iterator<int>(), std::back_inserter(vec));
fin.close();
I am quite new to C++ and am trying to work out how to write a record in the format of this structure below to a text file:
struct user {
int id;
char username [20];
char password [20];
char name [20];
char email [30];
int telephone;
char address [70];
int level;
};
So far, I'm able to write to it fine but without an incremented id number as I don't know how to work out the number of records so the file looks something like this after I've written the data to the file.
1 Nick pass Nick email tele address 1
1 user pass name email tele address 1
1 test test test test test test 1
1 user pass Nick email tele addy 1
1 nbao pass Nick email tele 207 1
Using the following code:
ofstream outFile;
outFile.open("users.dat", ios::app);
// User input of data here
outFile << "\n" << 1 << " " << username << " " << password << " " << name << " "
<< email << " " << telephone << " " << address << " " << 1;
cout << "\nUser added successfully\n\n";
outFile.close();
So, how can I increment the value for each record on insertion and how then target a specific record in the file?
EDIT: I've got as far as being able to display each line:
if (inFile.is_open())
{
while(!inFile.eof())
{
cout<<endl;
getline(inFile,line);
cout<<line<<endl;
}
inFile.close();
}
What you have so far is not bad, except that it cannot handle cases where there is space in your strings (for example in address!)
What you are trying to do is write a very basic data base. You require three operations that need to be implemented separately (although intertwining them may give you better performance in certain cases, but I'm sure that's not your concern here).
Insert: You already have this implemented. Only thing you might want to change is the " " to "\n". This way, every field of the struct is in a new line and your problem with spaces are resolved. Later when reading, you need to read line by line
Search: To search, you need to open the file, read struct by struct (which itself consists of reading many lines corresponding to your struct fields) and identifying the entities of your interest. What to do with them is another issue, but simplest case would be to return the list of matching entities in an array (or vector).
Delete: This is similar to search, except you have to rewrite the file. What you do is basically, again read struct by struct, see which ones match your criteria of deletion. You ignore those that match, and write (like the insert part) the rest to another file. Afterwards, you can replace the original file with the new file.
Here is a pseudo-code:
Write-entity(user &u, ofstream &fout)
fout << u.id << endl
<< u.username << endl
<< u.password << endl
<< ...
Read-entity(user &u, ifstream &fin)
char ignore_new_line
fin >> u.id >> ignore_new_line
fin.getline(u.username, 20);
fin.getline(u.password, 20);
...
if end of file
return fail
Insert(user &u)
ofstream fout("db.dat");
Write-entity(u, fout);
fout.close();
Search(char *username) /* for example */
ifstream fin("db.dat");
user u;
vector<user> results;
while (Read-entity(u))
if (strcmp(username, u.username) == 0)
results.push_back(u);
fin.close();
return results;
Delete(int level) /* for example */
ifstream fin("db.dat");
ofstream fout("db_temp.dat");
user u;
while (Read-entity(u))
if (level != u.level)
Write-entity(u, fout);
fin.close();
fout.close();
copy "db_temp.dat" to "db.dat"
Side note: It's a good idea to place the \n after data has been written (so that your text file would end in a new line)
Using typical methods at least you will need to use fix size records if you want to have random access when reading the file so say you have 5 characters for name it will be stored as
bob\0\0
or whatever else you use to pad, this way you can index with record number * record size.
To increment the index you in the way you are doing you will need to the read the file to find the high existing index and increment it. Or you can load the file into memory and append the new record and write the file back
std::vector<user> users=read_dat("file.dat");
user user_=get_from_input();
users.push_back(user_);
then write the file back
std::ofstream file("file.dat");
for(size_t i=0; i!=users.size(); ++i) {
file << users.at(i);
//you will need to implement the stream extractor to do this easily
}
I suggest to wrap the file handler into a Class, and then overload the operator >> and << for your struct, with this was you will control the in and out.
For instance
struct User{
...
};
typedef std::vector<User> UserConT;
struct MyDataFile
{
ofstream outFile;
UserConT User_container;
MyDataFile(std::string const&); //
MyDataFile& operator<< (User const& user); // Implement and/or process the record before to write
MyDataFile& operator>> (UserConT & user); // Implement the extraction/parse and insert into container
MyDataFile& operator<< (UserConT const & user); //Implement extraction/parse and insert into ofstream
};
MyDataFile& MyDataFile::operator<< (User const& user)
{
static unsigned myIdRecord=User_container.size();
myIdRecord++;
outFile << user.id+myIdRecord << ....;
return *this;
}
int main()
{
MydataFile file("data.dat");
UserConT myUser;
User a;
//... you could manage a single record
a.name="pepe";
...
file<<a;
..//
}
A .Dat file is normally a simple text file itself that can be opened with notepad . So , you can simply read the Last Line of the file , read it , extract the first character , convert it into integer . THen increment the value and be done .
Some sample code here :
#include <iostream.h>
#include <fstream.h>
using namespace std;
int main(int argc, char *argv[])
{
ifstream in("test.txt");
if(!in) {
cout << "Cannot open input file.\n";
return 1;
}
char str[255];
while(in) {
in.getline(str, 255); // delim defaults to '\n'
//if(in) cout << str << endl;
}
// Now str contains the last line ,
if ((str[0] >=48) || ( str[0] <=57))
{
int i = atoi(str[0]);
i++;
}
//i contains the latest value , do your operation now
in.close();
return 0;
}
Assuming your file format doesn't not need to be human readable.
You can write the struct out to file such as.
outFile.open("users.dat", ios::app | ios::binary);
user someValue = {};
outFile.write( (char*)&someValue, sizeof(user) );
int nIndex = 0;
user fetchValue = {};
ifstream inputFile.open("user.data", ios::binary);
inputFile.seekg (0, ios::end);
int itemCount = inputFile.tellg() / sizeof(user);
inputFile.seekg (0, ios::beg);
if( nIndex > -1 && nIndex < itemCount){
inputFile.seekg ( sizeof(user) * nIndex , ios::beg);
inputFile.read( (char*)&fetchValue, sizeof(user) );
}
The code that writes to the file is a member function of the user struct?
Otherwise I see no connection with between the output and the struct.
Possible things to do:
write the id member instead of 1
use a counter for id and increment it at each write
don't write the id and when reading use the line number as id