I/O Text file handling and dynamic memory allocation

Hi,
I am working on an OOP assignment (text handler) which part of its description is:
Some lines of text as a story which the delimiter between each story is %%%%%
Each text loaded should only occupy the space needed for the text to fit.

It's required to use dynamic variables of type char*. To be more detailed, the text-handler must contain a vector of such char-pointers (i.e. c-strings), and the parameter in the constructor indicates how many pointers (c-strings) to be contained in the vector. Each loaded text will be represented by a number, so that the first text in the file gets number 0 and the next one gets number 1 ... etc. When you want to access a text, you request the text with a certain number, and then get a pointer in return that may be used to output the text on the screen.

My problem is first to allocate a dynamic memory like char** without defining the number of array elements (Each text loaded should only occupy the space needed for the text to fit. )and then store each story from text file (comprise of a few lines of text) into that dynamically located memory(char **)to be able to do some operation on it later.

may somebody help me in this regard.
I would probably read the file line by line into a std::string, use .size() to get its length, and then allocate a c-string of appropriate length. You could also read the file char by char and keep track of how many you're read in before you hit a new line.
may you give a more specific hint for the case of char by char with a code snippet please!?
In fact the problem of allocating dynamic memory for reading character by character of a few lines together is still remained.
Last edited on
closed account (D80DSL3A)
From the directions:
the parameter in the constructor indicates how many pointers (c-strings) to be contained in the vector.

Perhaps something like:
1
2
3
4
5
6
7
8
class TH// text handler
{
    private:
    int numLines;
    char** theLines;
    public:
    TH( int NumLines ): numLines(NumLines), theLines(new char*[NumLines]) {}
}


That gets the pointers allocated but each pointer points to nothing yet.
I would also read the lines from the file in the constructor by passing the file name too. This may violate instructions though. It says ONE parameter for the constructor.
You could have a 2nd function do the file reading. Check the assignment requirements for how you are expected to do this.
The constructor will take two parameters – the first parameter should be the name of the text file and the second parameter tells the maximum number of texts to be hold by the texthandler.

But actually my problem is here: (assume it is inside the constructor of my class, although for ease of testing its functionality I am working on it in a test project, inside the int main() ):

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
29
30
31
32
ifstream infile;
    infile.open("Stories.txt");//open the text file
	 if (!infile)
	{
		cout << "Unable to open file";
		exit(1); // terminate with error
    }
	 int ind = 0;
	 const int MaxNoStories = 40;
	 char **myStories = new char *[MaxNoStories];
	 int StoryNo = 0;
	 char str;
	 int i=0;
	 
	 char a;
            while(!infile.eof())
            {
				infile >> str;
				if( str == '%'){
					ind++;
					if(ind==5){
						cout<<"\n\n";  //separate the stories from each other 
						cout<<"Story Number: "<< ++StoryNo << endl;
						i=0;
					}
						
				}
             	else{
					ind=0;
					myStories[StoryNo][i++] = str; //PROBLEM IS HERE!!
				}
            }


in the line which I marked it as "//PROBLEM IS HERE!!", I have the real problem of my this section. I do not how put the characters which I read in str into my **myStories.

I have to do some manipulation for each story:
(This is the requirement for the constructor)

TextHandler object should be able to handle the different fonts used in Windows and the Console window. Once the content is printed in a Console window, most characters works pretty well, but one problem is that the line-break character, used to separate lines in the text file, not at all match the newline character used in the Con-sole window. To solve this, you can read line by line from the text file (use file.getline (...)). Then you get a line of text from the file in the format we call "string", ie. there is a terminat-ing '\0' after the text itself. Replace this character with '\n' which is the newline character used by Console window. Then, you read the next line and add it to the buffer and repeat this until you hit the line with the 5 percent signs. The final character '\0' should then remain, so that the entire buffer is in "string" format. Now, the entire text is in the buffer and can be saved on the next available location in the array with string-pointers (char*), mentioned above.

Last edited on
closed account (D80DSL3A)
You can read an entire line at once using cin.getline().

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
29
30
31
32
33
34
ifstream infile;
    infile.open("Stories.txt");//open the text file
	 if (!infile)
	{
		cout << "Unable to open file";
		exit(1); // terminate with error
    }
	 int ind = 0;
	 int MaxNoStories = 40;// doesn't need to be constant. This would be one ctor argument
	 char **myStories = new char *[MaxNoStories];
	 int StoryNo = 0;
         char line[1000];// for each line to read into
	 char buff[1000];// local array large enough to contain all lines before the line "%%%%%"
	 int i=0;
         int length = 0;
	 
            while( cin.getline( line, 999 )  )
            {
                 if(  strcmp( line, "%%%%%" ) != 0 )// special line not found yet
                 {
                      strcat( line, buff );// add content of line to buff
                      length = strlen( buff );
                      buff[length] = '\n';// change the '\0' to '\n'
                 }
                 else// copy buff to  myStories[StoryNo]
                 {                      
                      // just change that last '\n' back to '\0'
                      buff[length] = '\0';
                      myStories[StoryNo] = new char[length+1];// exact amount required
                      strcpy( buff, myStories[StoryNo] );
                      StoryNo++;
                 }
                 
            }

Some of the function names may not be quite right (strcmp, strcpy, strlen ), and the argument order may be reversed. It's been years since I've used these methods. Do you mind looking up the details?

Hope this helps.
Dear fun2code,
your help was really useful and helped me a lot. However, I worked on it and tried to optimize it more.
here the code is what I have done.
The biggest change was that the first inputted line array should be copied to buff (strcpy) and after that each reading data into line array should be concatenate to buff (strcat)
But still I have some problems which I am writing it down here after showing the code:

What I have done first is:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
ifstream infile;
    infile.open("Stories.txt");//open the text file
	 if (!infile)
	{
		cout << "Unable to open file";
		exit(1); // terminate with error
    }
	 int ind = 0;
	 int MaxNoStories = 40;// doesn't need to be constant. This would be one ctor argument
	 char **myStories = new char *[MaxNoStories];
	 int StoryNo = 0;
     char line[1000];// for each line to read into
	 char buff[1000];// local array large enough to contain all lines before the line "%%%%%"
	 int i=0;
         int length = 0;
	 bool check = false;
	 strcpy(line," ");
	 strcpy(buff," ");
	
            while(!infile.eof()){
				infile.getline( line, 500);
				
				 if( strcmp(line, "%%%%%") !=0 ){
						line[strlen(line)]='\n'; // PROBLEM 1
						
						if(check== false){
						    strcpy( buff, line );
                                                    check = true;
						}
					
					       else{
						       strcat( buff, line );
						}
				 }
				else{
					 myStories[StoryNo]= new char [strlen(buff)];
					 strcpy(myStories[StoryNo], buff);

					 //cout<< buff<<"\n\n";
					 for(int j=0;j <strlen(buff);j++)
						 cout<<myStories[StoryNo]; //PROBLEM 2
					
					 strcpy(buff," ");
					 check = false;
					 StoryNo++;
				 }

            }


// PROBLEM 1: when I use line[strlen(line)]='\n'; the memory crashes and the \0 null terminator didn't change to newline \n by that line of code, I tried many things, but I cannot understand why it makes the program to crash, although when change it to line[strlen(line)-1]='\n'; it changed the final character of the line and not \0.

//PROBLEM 2: again putting the data from buff to dynamically allocated memory as myStories[StoryNo] does not work.

Shouldn't myStories[StoryNo] as a double pointer (char **myStories = new char *[MaxNoStories];) be defined in the while loop as *myStories[StoryNo] = new char *[length+1]; since myStories is a dynamic double pointer????

Last edited on
ANSWER 1: Your string is not null-terminated. line[strlen(line)-1] is the last character, line[strlen(line)] is one character past the last character.

I don't know about your second problem. I avoid manual dynamic allocation as much as I can.
What do you mean exactly with my string is not null terminated.
To the best of my knowledge when I use infile.getline (line, 1000) for example I create a null terminated string as a C-string. and as you said the last character would be line[strlen(line)]. but it should have a null-termination character in the line array.
Although in this program when I try to change the null-termination \0 with \n (newline) the program crashes.
closed account (D80DSL3A)
OK. I tried this myself and found several problems with the code in my previous post.

Had to wrap my head around exactly what's going on with the '\0' characters.
It's been a while.
We have to be careful about over writing it because it marks end of string.
Your PROBLEM 1 results from trying to copy a string lacking a '\0' character (so it goes forever).

I think PROBLEM 2 results from the mess created by PROBLEM 1, and being 1 char short in allocation.

Also, it's OK to strcat to an empty string (so long as buff[0] == '\0'), so the check isn't needed.

I have the code working.
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int main()
{
    ifstream infile;
    infile.open("Stories.txt");//open the text file
    if (!infile)
    {
	cout << "Unable to open file";
	return 1; // terminate with error
    }

    unsigned int MaxNoStories = 40;// doesn't need to be constant. This would be one ctor argument
    char **myStories = new char *[MaxNoStories];
    unsigned int StoryNo = 0;
    char line[1000] = {0};// for each line to read into
    char buff[1000] = {0};// local array large enough to contain all lines before the line "%%%%%"
    unsigned int length = 0;

    while(!infile.eof() && StoryNo < MaxNoStories)// watch for end of array too!
//    while( infile.getline( line, 500) )
    {
	infile.getline( line, 500);

	 if( strcmp(line, "%%%%%") !=0 )
	{
	    length = strlen(line);
	    line[length] = '\n';// over write the '\0' with '\n'
	    line[length+1] = '\0';// put '\0' at the end so strcat will work
	    strcat( buff, line );// OK to strcat to an empty buff. No need to strcpy 1st time
       }
        else
	{
	     length = strlen(buff);
	     myStories[StoryNo]= new char [length + 1];// +1 for the '\0'
	     strcpy(myStories[StoryNo], buff);
	     StoryNo++;
	     buff[0] = '\0';// reset for the next story. Makes buff appear empty
	}
    }// end while

    for( unsigned i = 0; i < StoryNo; ++i )
        cout << myStories[i] << "\n\n";

    return 0;
}

I puzzled why the 1st line starts with a space for a while before I finally saw this line:
strcpy(buff," ");
It isn't needed.
I decided to post my complete code because there are several local variables and some code removed.
I also changed a few variables from type int to unsigned int, to get rid of those pesky "comparison of signed to unsigned" warnings.

Shouldn't myStories[StoryNo] as a double pointer (char **myStories = new char *[MaxNoStories];) be defined in the while loop as *myStories[StoryNo] = new char *[length+1]; since myStories is a dynamic double pointer????

No. The use of [] does 2 things. It advances the pointer and dereferences it.
You would have to use the * this way:
*(myStories + StoryNo) = new char[length+1];// not char*
Last edited on
Dear fun2code,

Thanks a lot. it was perfect. I understand the problems I made and now it can be compiled and run without an error. Now I can try to tailor it to my class for other functionality.
Thank you so much :)
closed account (D80DSL3A)
You're welcome. Thanks for the archaic methods review!
In the class declaration, I got some errors.

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include<fstream>
#include <cstring>
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
using std::ifstream;


class Texthandler
{
	int TotStoriesinFile;
    int MaxNoStories;// doesn't need to be constant. This would be one ctor argument
    char **myStories;
    unsigned int StoryNo;
    char line[1000];// for each line to read into
    char buff[1000];// local array large enough to contain all lines before the line "%%%%%"
    unsigned int length;
	char FileName[100];
public:
	Texthandler(char *str, int Nr);		// Constructor
	~Texthandler();		// Destructor
	int nrOfTexts();
	int maxNrOfTexts();
	const char* text(int txtnr);
};

Texthandler::Texthandler(char *str, int Nr){ 
	
	int TotStoriesinFile = 0;
    int MaxNoStories = Nr;// doesn't need to be constant. This would be one ctor argument
    char **myStories = new char *[MaxNoStories];
    unsigned int StoryNo = 0;
    char line[1000] = {0};// for each line to read into
    char buff[1000] = {0};// local array large enough to contain all lines before the line "%%%%%"
    unsigned int length = 0;
	
	ifstream infile;
    infile.open(str);//open the text file

    while(!infile.eof()){
		infile.getline( line, 1000);

		 if( strcmp(line, "%%%%%") !=0 ){
			length = strlen(line);
			line[length] = '\n';// over write the '\0' with '\n'
			line[length+1] = '\0';// put '\0' at the end so strcat will work
			strcat( buff, line );// OK to strcat to an empty buff. No need to strcpy 1st time
		   }
		else if ((strcmp(line, "%%%%%") == 0) && (strlen(buff)!=0) ){
			 length = strlen(buff);
			 myStories[StoryNo]= new char [length + 1];// +1 for the '\0'
			 strcpy(myStories[StoryNo], buff);
			 StoryNo++;
			 buff[0] = '\0';// reset for the next story. Makes buff appear empty
		}
    }// end while
	TotStoriesinFile = StoryNo;

	//for( unsigned i = 0; i < StoryNo; ++i ){
	//	cout<<"Text nr "<< i+1<<" contains "<< strlen(myStories[i])<<" characters.\n";
	//	cout<<"-------------------------------------------------------------------\n";
 //       cout << myStories[i] << "\n\n";
	//}
}


int Texthandler::nrOfTexts(){

	return TotStoriesinFile;
}

int Texthandler::maxNrOfTexts(){
	return MaxNoStories;
}

const char* Texthandler::text(int txtnr){
	char * str= new char [strlen(myStories[txtnr])];

	if((txtnr>0) && (txtnr<MaxNoStories)){
		str = myStories[txtnr];
		return str;
	}
	else
		return NULL;
}

Texthandler::~Texthandler(){
	
	for(int i=0;i<MaxNoStories;i++)
		delete [] myStories[i];
	delete [] myStories;
}



int main(){
	int maximalNumberOfTexts = 20;
	Texthandler th("Stories.txt", maximalNumberOfTexts);
	cout<< th.text(4);
	cout<< "\n\n\ntest to pass constructor\n\n\n";
	cout<<th.maxNrOfTexts();
	th.~Texthandler();
}


When I try to return the value of two private variable which has got its value from the constructor from two member functions:
int Texthandler::nrOfTexts(){

return TotStoriesinFile;
}

int Texthandler::maxNrOfTexts(){
return MaxNoStories;
}

I get garbage on the screen, and I cannot understand why?

after calling the member function:
const char* Texthandler::text(int txtnr){
char * str= new char [strlen(myStories[txtnr])];

if((txtnr>0) && (txtnr<MaxNoStories)){
str = myStories[txtnr];
return str;
}
else
return NULL;
}

why does the program crash??
closed account (D80DSL3A)
You've declared several variables as both members of the class and as local variables within the ctor (constructor). Probably the local vars are getting set in the ctor, not the data members.

I think the data members should be:
1
2
3
4
5
int MaxNoStories;// can this be negative? Declare as unsigned int
char **myStories;
unsigned int StoryNo;// maybe ActualNoStories would be a better name.
// NO OTHER VARIABLES as data members
// DO NOT declare these as local vars in the ctor 

The rest are local variables in the ctor. Their sole purpose is helping to get the Texthandler constructed.

EDIT: The filename should be passed as a const char*
Texthandler::Texthandler(const char *str, int Nr)
Last edited on
Yeah, You were right. The problem was for double declaration. and since I update the variable for the second declaration it couldn't be return as the value for the member variable and then it returned just garbage as the value inside uninitialized member variable of the class (Although it cannot be initialized basically).
The problem was solved :)
closed account (D80DSL3A)
I'm glad that solved the problem.
Maybe re read the chapter on classes.
Last edited on
Topic archived. No new replies allowed.