Read a text file into dynamically allocated array and return a pointer to array

So I have searched my issue quite a bit and found a few helpful tips, but I haven't quite found an answer similar enough to my question to help me get over my hump. As a side note my instructor never responded to my request for an appointment for assistance and I am unable to make it to recitation due to my work schedule. Help me cplusplus, you are my only hope!
I need to create a function that reads a text file into a dynamically allocated array using pointers. I am to return a pointer to the array along with a pointer to the size of the array. I didn't realize I could return two pointers? The following function definition was given: string* getFile(string filename,const int maxUsers,int*size);

Here is what I have come up with so far but I feel like I have overthought so much that my brain has become discombobulated!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 #include <iostream>
#include <fstream>
#include <string>
#include <consoleFunctions.h>

using namespace std;

string* getFile(string filename, const int maxUsers, int*size)
{
	int size = 0;
	filename = new string[maxUsers];
	ifstream inputFile;
	inputFile.open("UserAccounts.txt");
	if (inputFile.is_open())
	{ 
	while (!inputFile.eof())
		getline(inputFile, filename[*size]);	


He wants us to use the getline function to read from the file but I am confused on that as well. And I don't even know where to begin with a value for maxUsers...hell if I know. I honestly thought I was starting to grasp C++ until this programming assignment...I have a long way to go. Any and all help will be so greatly appreciated. Thanks!

Marcus
1
2
3
4
std::string line;
std::vector<std::string> array;
while(std::getline(input, line))
   array.push_back(line);
While I thank you immensely for your response I must admit we have not covered vectors. This seems to be a common issue with all of these threads. I have seen people ask similar questions to mine and get answers that involve vectors. Man I can't wait to learn vectors. Do you have any alternative suggestions without vectors?
Unless you know the number of lines in the file, you'll probably could not guess a good value for the dimension of the array. Eventuallly you'll have to redimension your array.
1
2
3
4
5
aux = new T[size*K]; //create new array
copy(array, aux, size); //loop, copy each element to the new array
delete[] array; //deallocate the memory that is no longer necessary (the old array)
array = aux; //now you are working with the new array
size *= K;


That means that you need to keep track of the capacity (how many elements can it hold) and the size (the number of used spaces). This is quite error-prone, so I suggest you to encapsulate that behaviour.
You'll end up creating a class similar to std::vector.


An alternative is to know the number of lines. By instance the first line in the file is a number with that information. Or you could read the file twice, first to count its lines, and then again to fill the array.
Thank you for the tips...I am working them into my program now.
That's an odd function definition. Since it returns a pointer to string and string contains a size() method. there's no reason to return the size also, but if that's the prototype you're given then that's what you'll have to use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <fstream>
using namespace std;

// Read filename into a string allocated on the heap. Return a pointer
// to the string and set *size to the size of the string.  The caller
// is responsible for deleting the string.
string* getFile(string filename,const int maxUsers,int*size)
{
    string *result = new string;
    string line;
    ifstream inputFile(filename.c_str());
    while (getline(inputFile, line)) {
	if (!inputFile.eof()) line += '\n';
	*result += line;
    }
    *size = result->size();
    return result;
}

int main()
{
    int size;
    string *data = getFile("foo.cpp", 22, &size);
    cout << "file is " << size << " bytes\n";
    cout << *data;
    delete data;
    return 0;
}


FWIW, this is a good example of things not to do:
- don't return two bits of data if you can return one instead (you can get size from the string)
- don't return heap objects if you can help it since they just burden the caller with deleting them.
- the goal of functions and methods isn't to "do the work." It's to "make doing the work easy." If you think this way, you'll write much more flexible code. Compare this version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <fstream>
using namespace std;

// Read the stream and return its contents
string getFile(istream &is)
{
    string result;
    string line;
    while (getline(is, line)) {
	if (!is.eof()) line += '\n';
	result += line;
    }
    return result;
}

int main()
{
    int size;
    ifstream inputStream("foo.cpp");
    string data = getFile(inputStream);
    cout << "file is " << data.size() << " bytes\n";
    cout << data;
    return 0;
}


By passing an istream into getFile instead of a filename, it can read any stream at all. The cost is just one line for the caller to create the ifstream. Also by returning a string insteadof a pointer to a string, the caller doesn't have to worry about deleting it. With older versions of C++ this might have required copying the string contents, but I'm pretty sure that with move semantics, no copy is needed.
We could also argue that std::string is (quite likely) a pointer to dynamically allocated array of characters. Not 'type' pointer, but implemented with a pointer. Semantic argument. Doesn't satisfy a teacher, who demands explicit foo.
Just want to say that I sincerely am thankful for your help here. I am reading through all the suggestions and processing them to the best of my novice ability. Still working on the program, but I hope to have specific questions on all of the advice submitted here shortly.
I have added some comments to the code you provided. Can you tell me if I am understanding each line correctly?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
string* getFile(string filename,const int maxUsers,int*size)
{
    string *result = new string; // Declares a pointer variable to dynamically allocated string 
                                              // array

    string line;   // Declares string variable to hold contents of file one line at a time?

    ifstream inputFile(filename.c_str()); // Open file with function name inputFile

    while (getline(inputFile, line)) {   // Use getline to feed each line of file into string variable 
                                                      // line

	if (!inputFile.eof()) line += '\n'; // If not at end of file add escape to go to next line to be
                                                       // read

	*result += line; // add each line to allocated array. Result is dereferenced so the string
                                 //value is added. I did not know that we could place the string contents
                                 // of line into an allocated array just by using "+".
    }
    *size = result->size(); // We have not covered the object pointer you used here but I
                                      //assume the size function is able to get the size of result? Then
                                     // we place this calculated value into the dereferenced pointer size?
    return result; //result is now our pointer to the allocated array full of the files contents?
Last edited on
> string *result = new string;
is not an array, just one string.

> if (!inputFile.eof()) line += '\n';
You are reading line by line, lines are terminated by a line break character which getline() discards and so now it is restored.
The last line is an special case, as the file may not end with a line break.

> *result += line;
again, `result' is not an array, there the strings are concatenated in one big string.


You cannot do result[2] to get the third line of the file.
1
2
3
 while (getline(inputFile, line)) {
	if (!inputFile.eof()) line += '\n';
	*result += line;


What if I used a for loop here to feed each line into an array instead of:
1
2
 line += '\n'; // I imagine I would need to declare line an array as well??? Or maybe just 
                   // eliminate the line variable all together? 


So the variable result would end up being declared like this:
 
string result = new string[];  // Is this right? 


My instructor (who is still MIA) wants us to get the size of the array by counting it as we feed it into the array. Eventually we are to use this array, which will consist of 3 usernames and passwords each on one line, to compare to username and password entered by user.

By making it one long concatenated string I am not sure how to separate for the comparison. Hope that made sense.
Last edited on
So the variable result would end up being declared like this:
string result = new string[]; // Is this right?

No. That line does not say how many elements the array must have. Every array allocation requires the count.

(Even an int arr[] {4,2,7}; has implicit '3' in the brackets due to the initializer.)


There are concepts capacity -- how many elements the array contains -- and size --
how many elements are actually used. The size cannot exceed the capacity. If you have to store more than capacity elements, then you have to resize the array:
1
2
3
4
5
6
7
8
9
void expand( Foo* & arr, size_t size, size_t & capacity, size_t by ) {
  if ( arr && 0 < by ) {
    Foo* bigger = new Foo [capacity + by];
    std::copy_n( arr, size, bigger );
    delete [] arr;
    arr = bigger;
    capacity += by;
  }
}

Both the std::string and std::vector do their own resize operation automatically (if needed) when you append characters/elements to them. You, however, apparently have to have a plain array whose memory management is yours to implement.
By making it one long concatenated string I am not sure how to separate for the comparison. Hope that made sense.

I'm glad you recognized this conflict. It shows a knack for understanding programming problems. Can you post the entire problem? That might help us understand what the prof wants.

Eventually we are to use this array, which will consist of 3 usernames and passwords each on one line

Okay that's very important. So the maxUsers parameter tells us the maximum number of items in the file. That means that we can allocate space for maxUsers users:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <fstream>
using namespace std;

// Read the stream and return its contents
string *getFile(string filename, const int maxUsers, int *size)
{
    string *result = new string[maxUsers];
    ifstream is(filename.c_str());
    *size = 0;
    while (*size < maxUsers && getline(is, result[*size])) {
	++ *size;
    }
    return result;
}

int main()
{
    int size;
    string *lines = getFile("foo.cpp", 20, &size);
    for (int i=0; i<size; ++i) {
	cout << lines[i] << endl;
    }
    delete[] lines;
    return 0;
}


Line 8 allocates an array of maxUsers strings and points result to it.
Lines 10-13 read up to maxUsers strings and store them into sequential strings in the results array. Since size is a pointer to an int, we have to use *size to access the integer that it points to. Rather than use a local counter read the lines, I'm just using *size.

Line 12 increments the size.

Line 14 returns result to the caller.

At line 20 we call getFile(), passing it the address of the local variable "size". (&size). At the same line, the program declares a local variable called lines that points to a string. This variable is assigned the return value from getFile().
Topic archived. No new replies allowed.