How to use length indicator in a C++ program - c++

I want to make a program in C++ that reads a file where each field will have a number before it that indicates how long it is.
The problem is I read every record in object of a class; how do I make the attributes of the class dynamic?
For example if the field is "john" it will read it in a 4 char array.
I don't want to make an array of 1000 elements as minimum memory usage is very important.

Use std::string, which will resize to be large enough to hold the contents you assign to it.

If you just want to read in word by word from the file, you can do:
vector<string> words;
ifstream fin("words.txt");
string s;
while( fin >> s ) {
words.push_back(s);
}
This will put all the words in the file into the vector words, though you will lose the whitespace.

In order to do this, you need to use dynamic allocation (either directly or indirectly).
If directly, you need new[] and delete[]:
char *buffer = new char[length + 1]; // +1 if you want a terminating NUL byte
// and later
delete[] buffer;
If you are allowed to use boost, you can simplify that a bit by using boost::shared_array<>. With a shared_array, you don't have to manually delete the memory as the array wrapper will take care of that for you:
boost::shared_array<char> buffer(new char[length + 1]);
Finally, you can do dynamic allocation indirectly via classes like std::string or std::vector<char>.

I suppose there is no whitespace between records, or you would just write file >> record in a loop.
size_t cnt;
while ( in >> cnt ) { // parse number, needs not be followed by whitespace
string data( cnt, 0 ); // perform just one malloc
in.get( data[0], cnt ); // typically perform just one memcpy
// do something with data
}

Related

Is there any way to make a char array without knowing what size it will be

char* a = new char[50];
This is for a school assignment. I am not allowed to use strings or vectors or anything else. Just char array.
Lets say I want to do cin >> a; and I don't know the size of the input. How should I put it in char array? The above only works for a small size of input.
Should I do this? char* a = new char[some_large_number]; or is there a better way?
I can only use (dynamic) char arrays.
EDIT: The input can be any string like
abcd or even somelongrandomsentecewithoutspsomelongrandomsentecewithoutspacessomelongrandomsentecewithoutspaces
This is a little tricky with character arrays: what you need to do is tell cin that you do not want to receive more than a certain number of characters (49 for a 50-character buffer, because you need space for null terminator). When the read is finished, check the length of the string. If it is 49, allocate a new, larger, string, copy the old string into it, and continue reading. If it is less than 49, the end of string has been reached.
You can use istream::get to read the data into your buffer:
cin.get(a, 50); // You can specify an optional delimiter as a third parameter
Note that you pass 50 for the length, and get will subtract 1 automatically, because it knows about the space needed for null terminator.

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);
}

C++ Garbage at the end of file

I have a problem and I dont know how to solve it.
The issue is:
char * ary = new Char[];
ifstream fle;
fle.open(1.txt, ios_base::binary);
fle.seekg(fle.end);
long count = fle.tellg();
fle.seek(fle.beg);
here is the problem:
File 1.txt contains: Hello world!.
when I execute:
ary = new char(count);
fle.read(ary, count);
the ary filled like this: Hello world! #T#^#$#FF(garbage)
The file is ookay nothing inside it only what is above.
Platform: Win 7, VS 2012
Any idea how to solve this issue. (Solved)
(Problem 2)
Now I am facing another problem, the fle.read sometimes read more than the size i gave. For Example if i passed like fle.read(buffer, 1000) it ends in some cases (strlen(buffer) = 1500. How can i solve this?
Regards,
char[]-strings in C are usually null-terminated. They are one byte longer than necessary, and the last byte is set to 0x00. That's necessary because C has no way to tell the length of an array.
When you read binary data from a file, no terminating null-character is read into the string. That means a function like printf which operates on char-arrays of unknown length will output the array and any data which happens to come after it in memory until it encounters a null-character.
Solution: allocate the char[]-buffer one byte longer than the length of the data and set the last byte to 0 manually.
Better solution: Don't use C-style char-arrays. Do it the object-oriented way and use the class std::string to represent strings.
I think your problem is not that your array contains garbage, but that you forgot to put the null-terminator character at the end and your print statement doesn't know when to stop.
Also, you wrote new char(count) instead of new char[count]. In the first case, you only instantiate one char with value count while in the second case you create a buffer of count characters.
Try this:
ary = new char[count+1];
fle.read(ary, count);
ary[count] = '\0';
Most of the other answers miss a very important point:
When you do ary = new char(count); you allocate A SINGLE CHARACTER initialized with a symbol with ASCII code count.
You should write this: ary = new char[count + 1];
Well, the most obvious problem is that you are allocating using
new char(count), which allocates a single char, initialized
with count. What you were probably trying to do would be new
char[count]. What you really need is:
std::vector<char> arr( count );
fle.read( &arr[0], count );
Or maybe count + 1 in the allocation, if you want a trailing
'\0' in the buffer.
EDIT:
Since you're still having problems: fle.read will never read
more than requested. What does fle.gcount() return after the
read?
If you do:
std::vector<char> arr( count );
fle.read( &arr[0], count );
arr.resize( fle.gcount() );
you should have a vector with exactly the number of char that
you have read. If you want them as a string, you can construct
one from arr.begin(), arr.end(), or even use std::string
instead of std::vector<char> to begin with.
If you need a '\0' terminated string (for interface with
legacy software), then just create your vector with a size of
count + 1, instead of count, and &arr[0] will be your
'\0' string.
Do not try to use new char[count] here. It's very difficult
to do so correctly. (For example, it will require a try block
and a catch.)
We have to guess a little here, but most likely this comes down to an issue with your debugging. The buffer is filled correctly, but you inspect its contents incorrectly.
Now, ary is declared as char* and I suspect that when you attempt to inspect the contents of ary you use some printing method that expects a null-terminated array. But you did not null-terminate the array. And so you have a buffer overrun.
If you had only printed count characters, then you would not have overrun. Nor would you if you had null-terminated the array, not forgetting to allocate an extra character for the null terminator.
Instead of using raw arrays and new, it would make much more sense to read the buffer into std::string. You should be trying to avoid null-terminated strings as much as possible. You use those when performing interop with non-C++ libraries.
You're reading count characters for a file, you have to allocate one extra character to provide for the string terminator (\0).
ary = new char[count + 1];
ary[count] = '\0';
Try this
ary = new char[count + 1];
fle.read(ary,count);
ary[count] = '\0';
The terminating null character was missing - its not in the file, you have to add it afterwards

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.

Strings to binary files

My problem goes like this: I have a class called 'Register'. It has a string attribute called 'trainName' and its setter:
class Register {
private:
string trainName;
public:
string getTrainName();
};
As a matter of fact, it is longer but I want to make this simpler.
In other class, I copy several Register objects into a binary file, previously setting trainName.
Register auxRegister = Register();
auxRegister.setName("name");
for(int i = 0; i < 10; i++) {
file.write(reinterpret_cast<char*>(&auxRegister),sizeof(Register));
}
Later on, I try to retrieve the register from the binary file:
Register auxRegister = Register();
while(!file.eof()) { //I kwnow this is not right. Which is the right way?
file.read(reinterpret_cast<char*>(&auxRegister), sizeof(Register));
}
It occurs it does not work. Register does, in fact, have more attributes (they are int) and I retrieve them OK, but it's not the case with the string.
Am I doing something wrong? Should I take something into consideration when working with binary files and strings?
Thank you very much.
The std::string class contains a pointer to a buffer where the string is stored (along with other member variables). The string buffer itself is not a part of the class. So writing out the contents of an instance of the class is not going to work, since the string will never be part of what you dump into the file, if you do it that way. You need to get a pointer to the string and write that.
Register auxRegister = Register();
auxRegister.setName("name");
auto length = auxRegister.size();
for(int i = 0; i < 10; i++) {
file.write( auxRegister.c_str(), length );
// You'll need to multiply length by sizeof(CharType) if you
// use a wstring instead of string
}
Later on, to read the string, you'll have to keep track of the number of bytes that were written to the file; or maybe fetch that information from the file itself, depending on the file format.
std::unique_ptr<char[]> buffer( new char[length + 1] );
file.read( buffer, length );
buffer[length] = '\0'; // NULL terminate the string
Register auxRegister = Register();
auxRegister.setName( buffer );
You cannot write string this way, as it almost certainly contains pointers to some structs and other binary stuff that cannot be serialized at all.
You need to write your own serializing function, and write the string length + bytes (for example) or use complete library, for example, protobuf, which can solve serializing problem for you.
edit: see praetorian's answer. much better than mine (even with lower score at time of this edit).