C++/File operation
Expert: Ralph McArdell - 10/14/2006
Questionhello
i am new to C++ and need your help in this code.
i am trying to copy a text file in an array of string a[100] with the getline function but same is not working. below is my code.
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
//template<class T>
//struct b {
//char a[255];
//};
void main()
{
string a[100];
//struct b lines[100];
ifstream input;
input.open("c:\file2.txt");
int i=0;
if(!input)
cout<<"file not opened";
else
cout<<"file opened successfully"<<endl;
while(input)
{
input.getline(a[i],255,"\n");
i++;
}
cout<<a[i];
}
i am receiveing the following errors
error C2664: 'class std::basic_istream<char,struct std::char_traits<char> > &__thiscall std::basic_istream<char,struct std::char_traits<char> >::getline(char *,int,char)' : cannot convert parameter 1 from 'class std::basic_
string<char,struct std::char_traits<char>,class std::allocator<char> >' to 'char *
at line
input.getline(a[i],255,"\n");
kindly advice please
regards
AnswerI suggest you go back and read the small print for the std:istream::getline
functions, here are the signatures of these functions:
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
(namespace and class qualifications omitted for brevity)
You will notice in both cases that they take a char * value and not
std::string & value for their first parameter (they also take a single char
value and not a string value for their third parameter). Hence you cannot
use the std::istream class getline functions in the way you are trying to.
This is indicated by the error message, if you follow it through. I know
this is tricky when template type expansions are involved and is a
recognised problem with C++ compilers. So I have broken the error message up
and reduced the template parameter expansions to char, blah, blah to help
with seeing what the compiler is really on about:
error C2664:
'class std::basic_istream<char, blah, blah>::getline(char *,int,char)':
cannot convert parameter 1 from
'class std::basic_string<char, blah, blah>'
to
'char *
at line
input.getline(a[i],255,"\n");
The important part you need to look out for is what the error really is. In
this case it is indicated by the text:
cannot convert parameter 1
followed by the type of the item you provided for the offending parameter
(the from type) and what type it was expecting (the to type), and finally
the compiler displays the line that it is having problems with.
That is the compiler cannot convert the string type object indicated by a[i]
to the expected char * type required by the first parameter to the getline
member function.
The class templates std::basic_istream and std::basic_string are the class
templates that std::istream and std::string are formed from. The only thing
to note about their appearance in the error message is that the template
parameters in the error message mention char as the character type in both
cases.
OK so now you understand what is wrong, what can you do to fix it?
Well first you could use an array of character arrays:
const unsigned int NumberOfStrings(100);
const unsigned int MaxStringLength(254);
const unsigned int CharArraySize(MaxStringLength+1);
char a[NumberOfStrings][CharArraySize];
// ...
input.getline(a[i], CharArraySize,'\n');
Note that I have defined a set of constant values for the sizes involved.
This is because just using literal values like 100 and 255 (so called 'magic
numbers') are fairly meaningless and tend to need repeating. This makes
maintenance more difficult, especially if done by someone else. In your code
for example we now require the value 255 twice: once for the size of each
character array, and again for the limit of the number of characters to be
read by input.getline. If you increase the latter value to handle reading
longer lines and forget to increase the former to make space for them then
your revised program is likely to crash.
Notice that to be accurate the maximum number of characters read is one less
than that passed to input.getline, and the size of a char array used for
C-style strings is one more than the number of characters required to
include space for the terminating zero character present at the end of
C-style strings. Hence the size of a char array for a maximum C-string
character length of n is n+1 and the value passed to input.getline to
specify n as the maximum number of characters to read is also n+1. This is
why I created two constants MaxStringLength, which represents the value of
n, and CharArraySize, which represents the value of n+1. So if you wish to
increase the maximum line length to say 511 you just change the value of
MaxStringLength to 511 and re-build the program.
Also note that you do not need to explicitly state the delimiter as being
the new line character as this is the default so:
input.getline(a[i], CharArraySize);
would do just as well. However if you do specify a delimiter it is a single
character value (e.g. '\n') and _not_ a C-string or string literal value
(e.g. "\n"), as is present in your original code.
The problem with this solution is that you need to work with fixed sized
arrays and upper bounds to the line lengths that can be read. Obviously it
would be better to find a solution that used std::string as the container
for read lines which automatically grow as required and find some getline
functionality which supported this behaviour.
This brings us to the second option which is to use the stand alone
std::getline functions (i.e. they are not a member of any class):
istream& getline( istream& is, string& str);
istream& getline( istream& is, string& str, char delim);
(namespace and class qualifications omitted for brevity)
Again there are two versions: one that takes a delimiter character and
another that does not which defaults to using new line as the delimiter.
These are defined as part of the std::string interface and as such are
declared in the <string> header.
These versions take a std::istream object reference as their first parameter
and a std::string reference as their second. So all you would need to do to
your original code is to replace the line:
input.getline(a[i],255,"\n");
with:
getline( input, a[i], '\n' );
or:
getline( input, a[i] );
Now what happens if your file has more than 100 lines? You have _no_ checks
on the number of lines read, and this is still fixed at 100. Reading the
101st line will cause undefined behaviour as you overwrite the memory after
a[]. If you are lucky you will cause your program to crash, if not then you
may get odd behaviour. The program may finish in this case before you notice
such problems being so short, but in a larger program such errors can cause
odd bugs that can take a while to track down.
Likewise the writing of a[i] to cout _after_ the while loop will also try to
access the 101st element for a file of 100 lines, and will always access the
contents of the next string in the array after the last one read which, if i
is still in range will be an empty string.
I suggest that in the same way as std::string extends automatically as
required, unlike a built in array of char, you use a collection of strings
that does likewise rather than using a built in array of strings. The
obvious choice here is a std::vector of strings:
#include<vector>
typedef std::vector< std::string > StringVector;
using namespace std;
int main()
{
StringVector a;
// ...
I declared a type alias for a vector of strings by using typedef. This is
not necessary but does create type name without all the template trappings
(which also tend to be shorter) and is common practice. Your program will
compile without any further changes, but will most likely crash when run.
This is because when using a vector like this it is created empty with no
elements in it at all. To extend it we have to call push_back to add a new
element to the end of the vector. This means we have to modify your loop
code somewhat:
while (input)
{
string in_str;
getline(input, in_str);
a.push_back( in_str );
cout<<a[i];
i++;
}
You will notice that I have moved the writing of the last added value to a
to cout into the loop before i is incremented. You might wish to add extra
formatting to this statement (such as writing spaces or new lines between
each string written).
In fact it would probably be better to only perform the push_back etc. if
input is still OK:
while (input)
{
string in_str;
getline(input, in_str);
if (input)
{
a.push_back( in_str );
cout<<a[i];
i++;
}
}
Hope this is of use to you.
I suggest you go back and read the small print for the std:istream::getline
functions, here are the signatures of these functions:
istream& getline (char* s, streamsize n );
istream& getline (char* s, streamsize n, char delim );
(namespace and class qualifications omitted for brevity)
You will notice in both cases that they take a char * value and not
std::string & value for their first parameter (they also take a single char
value and not a string value for their third parameter). Hence you cannot
use the std::istream class getline functions in the way you are trying to.
This is indicated by the error message, if you follow it through. I know
this is tricky when template type expansions are involved and is a
recognised problem with C++ compilers. So I have broken the error message up
and reduced the template parameter expansions to char, blah, blah to help
with seeing what the compiler is really on about:
error C2664:
'class std::basic_istream<char, blah, blah>::getline(char *,int,char)':
cannot convert parameter 1 from
'class std::basic_string<char, blah, blah>'
to
'char *
at line
input.getline(a[i],255,"\n");
The important part you need to look out for is what the error really is. In
this case it is indicated by the text:
cannot convert parameter 1
followed by the type of the item you provided for the offending parameter
(the from type) and what type it was expecting (the to type), and finally
the compiler displays the line that it is having problems with.
That is the compiler cannot convert the string type object indicated by a[i]
to the expected char * type required by the first parameter to the getline
member function.
The class templates std::basic_istream and std::basic_string are the class
templates that std::istream and std::string are formed from. The only thing
to note about their appearance in the error message is that the template
parameters in the error message mention char as the character type in both
cases.
OK so now you understand what is wrong, what can you do to fix it?
Well first you could use an array of character arrays:
const unsigned int NumberOfStrings(100);
const unsigned int MaxStringLength(254);
const unsigned int CharArraySize(MaxStringLength+1);
char a[NumberOfStrings][CharArraySize];
// ...
input.getline(a[i], CharArraySize,'\n');
Note that I have defined a set of constant values for the sizes involved.
This is because just using literal values like 100 and 255 (so called 'magic
numbers') are fairly meaningless and tend to need repeating. This makes
maintenance more difficult, especially if done by someone else. In your code
for example we now require the value 255 twice: once for the size of each
character array, and again for the limit of the number of characters to be
read by input.getline. If you increase the latter value to handle reading
longer lines and forget to increase the former to make space for them then
your revised program is likely to crash.
Notice that to be accurate the maximum number of characters read is one less
than that passed to input.getline, and the size of a char array used for
C-style strings is one more than the number of characters required to
include space for the terminating zero character present at the end of
C-style strings. Hence the size of a char array for a maximum C-string
character length of n is n+1 and the value passed to input.getline to
specify n as the maximum number of characters to read is also n+1. This is
why I created two constants MaxStringLength, which represents the value of
n, and CharArraySize, which represents the value of n+1. So if you wish to
increase the maximum line length to say 511 you just change the value of
MaxStringLength to 511 and re-build the program.
Also note that you do not need to explicitly state the delimiter as being
the new line character as this is the default so:
input.getline(a[i], CharArraySize);
would do just as well. However if you do specify a delimiter it is a single
character value (e.g. '\n') and _not_ a C-string or string literal value
(e.g. "\n"), as is present in your original code.
The problem with this solution is that you need to work with fixed sized
arrays and upper bounds to the line lengths that can be read. Obviously it
would be better to find a solution that used std::string as the container
for read lines which automatically grow as required and find some getline
functionality which supported this behaviour.
This brings us to the second option which is to use the stand alone
std::getline functions (i.e. they are not a member of any class):
istream& getline( istream& is, string& str);
istream& getline( istream& is, string& str, char delim);
(namespace and class qualifications omitted for brevity)
Again there are two versions: one that takes a delimiter character and
another that does not which defaults to using new line as the delimiter.
These are defined as part of the std::string interface and as such are
declared in the <string> header.
These versions take a std::istream object reference as their first parameter
and a std::string reference as their second. So all you would need to do to
your original code is to replace the line:
input.getline(a[i],255,"\n");
with:
getline( input, a[i], '\n' );
or:
getline( input, a[i] );
Now what happens if your file has more than 100 lines? You have _no_ checks
on the number of lines read, and this is still fixed at 100. Reading the
101st line will cause undefined behaviour as you overwrite the memory after
a[]. If you are lucky you will cause your program to crash, if not then you
may get odd behaviour. The program may finish in this case before you notice
such problems being so short, but in a larger program such errors can cause
odd bugs that can take a while to track down.
Likewise the writing of a[i] to cout _after_ the while loop will also try to
access the 101st element for a file of 100 lines, and will always access the
contents of the next string in the array after the last one read which, if i
is still in range will be an empty string.
I suggest that in the same way as std::string extends automatically as
required, unlike a built in array of char, you use a collection of strings
that does likewise rather than using a built in array of strings. The
obvious choice here is a std::vector of strings:
#include<vector>
typedef std::vector< std::string > StringVector;
using namespace std;
int main()
{
StringVector a;
// ...
I declared a type alias for a vector of strings by using typedef. This is
not necessary but does create type name without all the template trappings
(which also tend to be shorter) and is common practice. Your program will
compile without any further changes, but will most likely crash when run.
This is because when using a vector like this it is created empty with no
elements in it at all. To extend it we have to call push_back to add a new
element to the end of the vector. This means we have to modify your loop
code somewhat:
while (input)
{
string in_str;
getline(input, in_str);
a.push_back( in_str );
cout<<a[i];
i++;
}
You will notice that I have moved the writing of the last added value to a
to cout into the loop before i is incremented. You might wish to add extra
formatting to this statement (such as writing spaces or new lines between
each string written).
In fact it would probably be better to only perform the push_back etc. if
input is still OK:
while (input)
{
string in_str;
getline(input, in_str);
if (input)
{
a.push_back( in_str );
cout<<a[i];
i++;
}
}
Hope this is of use to you.