Read formatted data from stream

Hi to everybody.

I'm a beginner c++ programmer. Hard job!

I'm tring to read data from a text file. This file contain some text and some number. My file is like:

Name: pippo, Surname: pluto, age: 23

Name: tizio, Surname: caio, age: 22

and so on. I would like to select just the meaningfull data and insert it into a list. My code is the following:

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

struct Dati{
char nome[20];
char cognome[20];
int anni;
struct Dati* pointer;
};


struct Dati *importa(){

	struct Dati *lista, *primoelemento;

	fstream file;
	file.open("nuovo.txt");
	if(file.is_open()){

		primoelemento= new struct Dati;

		fscanf(file,"\nNome: %s,\tCognome: %s,\tAnni: %d\n",primoelemento->nome, primoelemento->cognome, primoelemento->anni);
		lista=primoelemento;

		while(!file.eof()){
			lista->pointer=new struct Dati;
			lista=lista->pointer;
			fscanf(file,"\nNome: %s,\tCognome: %s,\tAnni: %d\n",lista->nome, lista->cognome, lista->anni);
		}

		lista->pointer=NULL;

		return primoelemento->pointer;
	}
	else{
		cout<<"unable to read file"<<endl;
		return NULL;
	}
}


When I compile this code it gives me this error;


error C2664: 'fscanf' : cannot convert parameter 1 from 'std::fstream' to 'FILE *'
1> No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called


It seems to me the problen is I can not give an fstream as first argoment to fscanf. What can I do?
Is there a better function written for c++?

Another question: how can I pass as an argoment to my function importa() the name of the file I want to open?

Thanks in advance.

Michele
Yeah, don't use fscanf()...

It is incompatible with iostreams anyway

The fstream can do what you want for you. Just use the >> operator.
1
2
3
4
int i;
string s;
double d;
file >> i >> s >> d;

etc.

Here is a handy template function to read the key and validate it, and read and return the associated value. (Notice that for the string version we had to be explicit that we are interested in a string and not a (char*)!)
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
template <typename TipoRisultado>
TipoRisultado scan( istream& ins, const string& chiave, const TipoRisultado& difetto = TipoRisultado() )
  {
  TipoRisultado valore;
  string cconf;
  ins >> cconf >> valore;
  if (cconf != chiave)
    {
    ins.setstate( ios::badbit );
    valore = difetto;
    }
  return valore;
  }

int main()
  {
  ...

  string nome, cognome;
  int anni;

  nome    = scan<string>( file, "Nome:" );
  cognome = scan<string>( file, "Cognome:" );
  anni    = scan( file, "Anni:", 0 );

  ...
  }


To learn more about using the standard strings, I suggest you spend some time reading the tutorial on this site. It is really very good.
http://www.cplusplus.com/doc/tutorial/basic_io.html

Hope this helps.

PS I hope I didn't mince Italian too badly... I'm only fluent in Spanish and English...
Last edited on
Well, first of all thanks for you quick and kind answer. It is god to know I'm not alone out there! I also appreciated your attempt of italian: spanish is really near so i could understand your terms.

Unfortunately i must admit I didn't really get your code.
You define a template function called scan, parametrized by TipoRisultato.

First question: what the use of typename? I found many example in the net but I couldn't understand its use.

You pass to the function the file to be reed, a key and another paramether which kind is TipoRisultato.

Second question: what does const TipoRisultado& difetto = TipoRisultado() means? Tiporisultato is not a function. Are you initialising by default the value of difetto?
I miss something.

In the function the file is read and you extract two element: a string cconf for comparison and a value of type TipoRisultato.
If the key and cconf coincide the function return valore. Otherwhise it return difetto.

Third question: the statement ins.setstate( ios::badbit ); return an error message?

Finally, in the main program you call the scan function. My last question: why don't you pass any parameter to scan when searching for an int?
And wht th meaning of zero in the function argoments?

Sorry if I did so many (stupid) question, but probably this is the better way to understand to problem.
Thank you again, waiting hopeful.
Sorry, template functions look scarier than they really are.

1. and 3.
Basically it is a function that looks like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
string scan( istream& ins, const string& chiave, const string& difetto = "" )
  {
  // This is the value we want to get from the stream
  string valore;

  // This is the key name we want to compare against the argument key name
  string cconf;

  // Read the stream's key and value (for example, "Nome:" and "Mara")
  ins >> cconf >> valore;

  // If the keys didn't match, then there was an error reading the stream.
  if (cconf != chiave)
    {
    // Put the stream into "there was an error" mode
    ins.setstate( ios::badbit );
    // Return the default value (instead of whatever we _may_ have read)
    valore = difetto;
    }

  return valore;
  }


This works fine for Name and Surname, but when it comes to Age we want to get an integer from the file, not a string. So the function would have to look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int scan( istream& ins, const string& chiave, const int& difetto = 0 )
  {
  // This is the value we want to get from the stream
  int valore;

  // This is the key name we want to compare against the argument key name
  string cconf;

  // Read the stream's key and value (for example, "Nome:" and "Mara")
  ins >> cconf >> valore;

  // If the keys didn't match, then there was an error reading the stream.
  if (cconf != chiave)
    {
    // Put the stream into "there was an error" mode
    ins.setstate( ios::badbit );
    // Return the default value (instead of whatever we _may_ have read)
    valore = difetto;
    }

  return valore;
  }

You will notice that the only difference between the two functions is the type of valore -- which is the type of the value we want the function to return. And when functions only differ in the type of certain parameters, they become great candidates for templatization.

The C++ keyword 'typename' just indicates that TipoRisultado is a stand-in for some other type: int, string, double, vector<int>, etc.

You can read more about template functions from the C++ language tutorial on this site:
http://www.cplusplus.com/doc/tutorial/templates.html

2.
You are exactly right. TipoRisultado() is the same as int() or string() or whatever the type may be, which is just the default value for that type. That way the default value is an optional argument. (You've missed nothing. ;-)

4.
C++ is pretty bright about deducing the type of things, so when scan() had an explicit int argument, I didn't need to tell it what kind of template function to instantiate. Had I left off that I would have had to write:

anni = scan<int>( file, "Anni:" );

For the name and surname version, I would have had to write:

nome = scan( file, "Nome:", string() );

Notice how I did not write

nome = scan( file, "Nome:", "" );

because the "" is a const char*, not a string, and the result type had to be a string.

Whew. Hope this helps.
Here I am again!
thank you very much for your last answer, really helful.

Anyway i wrote the following code:

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include<iostream>
#include<fstream>
#include<string>
using namespace std;

//declaration of sturctures and functions
//
struct Dati{
string nome;
string cognome;
int anni;
struct Dati* pointer;
};

struct Dati *creaLista();
void stampaLista(struct Dati *);
void salva(struct Dati *);
struct Dati *importa();

//it seems to me that the template functions follow other declaration rules. I must define here the function, was not able to declare it (sigh!) 
//
template <typename TipoDato>
TipoDato scan(istream& file, const string& chiave, const TipoDato errore=TipoDato())
{
	TipoDato letto;
	string confronto;
	file >> confronto >> letto;
	if(confronto!=chiave)
	{
		file.setstate(ios::badbit);
		letto=errore;
	}
	return letto;
}




//main file
//
void main(){
struct Dati *lista;

lista=new struct Dati;
lista=creaLista();
stampaLista(lista);
salva(lista);

lista=importa();
stampaLista(lista);

return;
}




//definitions of all declared functions
//


struct Dati *creaLista()
{
	struct Dati *primoelemento, *elemento;
	int n;

	cout<<"Quanti elementi contiene la lista?"<<endl;
	cin>>n;

	if(n!=0){
	primoelemento= new struct Dati;
	cout<<"Nome:"<<endl;
	cin>>primoelemento->nome;
	cout<<"Cognome:"<<endl;
	cin>>primoelemento->cognome;
	cout<<"Anni:"<<endl;
	cin>>primoelemento->anni;

	elemento=primoelemento;
	}

	for(int i=2;i<=n;i++){
	elemento->pointer=new struct Dati;
	elemento = elemento->pointer;

	cout<<"Nome:"<<endl;
	cin>>elemento->nome;
	cout<<"Cognome:"<<endl;
	cin>>elemento->cognome;
	cout<<"Anni:"<<endl;
	cin>>elemento->anni;
	}

	elemento->pointer=NULL;
	return primoelemento;
}


void stampaLista(struct Dati *lista)
{
	while(lista!=NULL)
	{
		cout<<"\nNome: "<<lista->nome<<"\tCognome: "<<lista->cognome<<"\tAnni: "<<  lista->anni<<endl;
		lista=lista->pointer;
	}
return;
}


void salva(struct Dati *lista)
{
	fstream file;
	file.open("nuovo.txt",ios::trunc);
	file.close();
	file.open("nuovo.txt",ios::app);

	while(lista!=0)
	{
		file<<"Nome: "<<lista->nome<<"\tCognome: "<<lista->cognome<<"\tAnni: "<<lista->anni<<endl;
		lista=lista->pointer;
	}
	file.close();
}


struct Dati *importa()
{
	struct Dati *lista, *primoelemento;
	fstream file;

	file.open("nuovo.txt");

	if(file.is_open())
		{
		primoelemento= new struct Dati;
		primoelemento->nome=scan<string>(file,"Nome: ");
		primoelemento->cognome=scan<string>(file,"Cognome: ");
		primoelemento->anni=scan<int>(file,"Anni: ",0);
		lista=primoelemento;

		while(!file.eof())
		{
			lista->pointer=new struct Dati;
			lista=lista->pointer;

			lista= new struct Dati;
			lista->nome=scan<string>(file,"Nome:","che mi hai chiesto?");
			lista->cognome=scan<string>(file,"Cognome:");
			lista->anni=scan<int>(file,"Anni:",0);
		}

		lista->pointer=NULL;
		return primoelemento->pointer;
	}
	else{
		cout<<"unable to read file"<<endl;
		return NULL;
	}
}


When I compile it give no errors. Anyway it can store my data into the txt file and the program breaks after the operation stampaLista.

Any clue?
If you will come to Italy I promise I'll offer to you a beer!
Heh, thanks!

Line 20
Yes, template functions are really just instructions to the compiler on how to make a function. No function actually exists until you use it, but you still must declare it before using it.

You could have declared it before your import function (lines 125..126) and it would work.

Lines 15, 16, 17, etc
You don't actually have to keep saying 'struct' in C++. The definition on line 8 is enough. Elsewhere you can just say:
1
2
3
4
5
Dati *creaLista();
void stampaLista(Dati *);
...
foo = new Dati;
...


Line 44, 45
I think you are still a little fuzzy on pointers. Again I recommend you to the tutorials on this site.

On line 44, you create a new Dati on the heap and put its address in the variable lista.

Then, on line 45 you use creaLista() to create new Datis on the heap and put the first one's address in the variable lista.

What has happened is that the address of the Dati you allocated on line 44 is forgotten, so you can never access it again. This is a memory leak.

Since you don't need the Dati on line 44, just get rid of line 44 altogether.

(BTW, "lista" is a terrible name for a list. You should name things based on what they contain, not on what they are. Better names might be "dati_della_gente" or "dati_degli_amici" or etc)

Line 79
This is why it breaks. You have a malformed list.

I could tell you exactly how to fix it, but I think you would benefit more by figuring it out yourself. Personally, I find that getting out a piece of paper and drawing little boxes labeled "first element", "second element", etc. and drawing little arrows between the boxes helps me a lot.

Again, "pointer" is a terrible name. A better name would be "dati_prossimo" or even just "prossimo".

Other commentary
Your print list function looks great!

In your save function you don't need to close and reopen the file. The ios::trunc does everything you need. (So you can delete lines 114 and 115.)

The import function has the same problem as the create list function.
Also, don't put spaces in the keyname to look for. In other words, say "Nome:" instead of "Nome: ". The >> operator stops reading at whitespace, so the second version can never match.

You seem to have a very good understanding of things. Keep up the great work!


I'd love to visit Italy, but I probably wouldn't get past airport security because I'd mangle your language like a Mexican. :-S :-)
Hi Duoas, here I am again.

I did it!

I thought a lot about your world, and I agree with you, it is more usful to lern how to fix problem tham get solution from some one else.
I read the tutorials, then i tried many different experiment.

Finally I fixed all the problem and now my code works. Anyway I still have some problem with storing and reading data in/from files.

1) If I save the file like in row 73, the last line of my saved file is an empy line. For this reason, when importing data with impotra(), my while cycle does a roun more than necessary without reading data. This because of the condition while(!file.eof()). I fixed it saving files in this format:

1
2
3
4
5
6
7
8
9
10
11
12
void salva(Dati *database)
{
	fstream file;
	file.open("nuovo.txt");

	while(database!=NULL)
	{
		file<<"\nNome: "<<database->nome<<"\tCognome: "<<database->cognome<<"\tAnni: "<<database->anni;
		database=database->next;
	}
	file.close();
}

but now I have an empty line at the file beginning. It works but I don't like it. any suggestion?
The same problem also condition the while cycle int the importa() function (see line 112)

2)the main problem I had was how to clear the file .txt before to store data in it. infact, at the very moment my function salva() just overwrite data on the previous .txt. As result, if I write 3 data on a file previously filled with 5 data, i still have 2 of the old data. the ios::trunc od not works like i would.

3)and4)
could you show me how to declare, not define, a template function? I'm not sure about the parameters and theri order.
Also, how can I pass the name of the file to save in or to read from by user choice? I don't want a solution, but some clue. I loved to fight with my problem!

I did so many different mistake that was a miralce if finally could run my program, thanks for your patience and your kindness.

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include<iostream>
#include<fstream>
#include<string>
using namespace std;



struct Dati{
string nome;
string cognome;
int anni;
Dati* next;
};
//
Dati *creaLista()
{
	Dati *primoelemento, *elemento;
	int n;

	cout<<"Quanti elementi contiene il database?"<<endl;
	cin>>n;

	if(n!=0)
	{
	elemento= new Dati;
	cout<<"Nome:"<<endl;
	cin>>elemento->nome;
	cout<<"Cognome:"<<endl;
	cin>>elemento->cognome;
	cout<<"Anni:"<<endl;
	cin>>elemento->anni;
	elemento->next=NULL;

	primoelemento=elemento;
	}
	if(n>1)
	{
		for(int i=2;i<=n;i++)
		{
			elemento->next=new Dati;
			elemento = elemento->next;

			cout<<"Nome:"<<endl;
			cin>>elemento->nome;
			cout<<"Cognome:"<<endl;
			cin>>elemento->cognome;
			cout<<"Anni:"<<endl;
			cin>>elemento->anni;

			elemento->next=NULL;
		}
	}
	return primoelemento;
};
//
void stampaLista(Dati *database)
{
	while(database!=NULL)
	{
		cout<<"Nome: "<<database->nome<<"\tCognome: "<<database->cognome<<"\tAnni: "<<  database->anni<<endl;
		database=database->next;
	}
return;
};
//
void salva(Dati *database)
{
	fstream file;
	file.open("nuovo.txt");

	while(database!=NULL)
	{
		file<<"Nome: "<<database->nome<<"\tCognome: "<<database->cognome<<"\tAnni: "<<database->anni<<endl;
		database=database->next;
	}
	file.close();
}
//
template <typename TipoDato>
TipoDato scan(istream& file, const string& chiave, const TipoDato errore=TipoDato())
{
	TipoDato letto;
	string confronto;
	file >> confronto >> letto;
	if(confronto!=chiave)
	{
		file.setstate(ios::badbit);
		letto=errore;
	}
	return letto;
}




//
Dati *importa()
{
	Dati *elemento, *primoelemento;
	fstream file;

	file.open("nuovo.txt");

	
		elemento= new Dati;
		elemento->nome=scan<string>(file,"Nome:");
		elemento->cognome=scan<string>(file,"Cognome:");
		elemento->anni=scan<int>(file,"Anni:");
		elemento->next=NULL;
		primoelemento=elemento;

		while(!file.eof())
		{
			elemento->next=new Dati;
			elemento=elemento->next;

			elemento->nome=scan<string>(file,"Nome:");
			elemento->cognome=scan<string>(file,"Cognome:");
			elemento->anni=scan<int>(file,"Anni:");
			elemento->next=NULL;
		}

		return primoelemento;		
}
//
//
void main(){
Dati *database, *database2;

database=creaLista();
stampaLista(database);
salva(database);

database2=importa();
stampaLista(database2);

return;
}



Once again, thank you.
Last edited on
I haven't actually looked through all your code yet... but to answer your questions:

1) Don't worry about having an extra line. An extra line is a good thing, and spinning one extra round at the end when reading isn't a cause for concern.

2) Change line 69 to

file.open("nuovo.txt", ios::trunc);

The 'trunc' flag instructs the open() function to first erase the old file then open for writing. Before it didn't do that.

3) Templates are a bit of an advanced subject, but they are not too difficult to understand. Keep in mind that this site has very good C++ tutorials:
http://www.cplusplus.com/doc/tutorial/templates.html

Don't get confundled by the words declare vs. define. Typical template functions do both at the same time.

4) I'll have to respond a little later.

Hope this helps.
Thanks once again. The problem with ios::trunc was I should declare file as ofstream, then it works.

I'll study better template and i'll make some try with passing name of files.

Hope hear you soon again.

Michele
Topic archived. No new replies allowed.