C++ text file writing and reading

Exercise says: Write integer numbers into a text file and then read from them.
I wrote the code below for that.
Any comments?

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
#include <iostream>
#include <fstream>
#include <string>

int main() {

	std::fstream file;
	file.open("C:/Users/Myname/Desktop/test.txt");
	if (file.is_open())
		file << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 10 << 11;
	else std::cout << "Could not open file for writing!\n";

	file.close();

	std::string line;

	file.open("C:/Users/Myname/Desktop/test.txt");
	if (file.is_open()) {
		while (getline(file,line))
			std::cout << line << '\n';
		file.close();
	}
	else std::cout << "Could not open file for reading!\n";

	system("pause");
	return 0;
}
Last edited on
Lines 7-8 can be combined in to one line.
std::fstream file("C:/Users/Myname/Desktop/test.txt");
Line 10: Why not use a for loop ?
Line 11: Why continue ?
Line 19: If the exercise says you should read integer numbers, why reading a string?
Last edited on
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
#include <iostream>
#include <fstream>
#include <string>

int main() {

    const int numbers[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 } ;
    const std::string path_to_file = "C:/Users/Myname/Desktop/test.txt" ;

    // Write integer numbers into a text file
    if( std::ofstream file{path_to_file} ) // if the file is opened for output
    {
        for( int n : numbers ) file << n << ' ' ; // *** add a white space as separator
        /* std::cout */ file << '\n' ; // good idea to end the text file with a new line
    } // file is automagically closed

    else
    {
        std::cerr << "failed to open file for output\n" ;
        return 1 ;
    }

    // and then read from them
    if( std::ifstream file{path_to_file} ) // if the file is opened for input
    {
        int n ;
        while( file >> n ) std::cout << n << ' ' ;
        std::cout << '\n' ;
    } // file is automagically closed

    else
    {
        std::cerr << "failed to open file for input\n" ;
        return 2 ;
    }
}
Last edited on
That's greatttt, JLBorges!

Few things are rather astonishing for me, that:

1- You're using an array in modern C++ code.
2- You've eliminated .open , .is_open() APIs.
3- The file is closed right after exiting the if block, automagically.
Last edited on
@JLBorges,

shouldn't line 14 be writing the new line to the file instead of std::cout? Your comment mentions adding to the file.

@frek,

A std::vector could have been used instead of a regular array, and the rest of the code would have been the same.

The use of an array and how a file was opened for writing and reading shows how versatile C++ can be. :)
@frek,

To see using a std::vector change line 7 to const std::vector<int> numbers { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

AND make sure you add #include <vector> !

Compile and test, it runs the same.

If'n you compiled as C++17 (or later) creating a std::vector could be done as const std::vector numbers { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; The type (int) is deduced by the initializer list.

Deduction guides are so cool! :Þ

https://en.cppreference.com/w/cpp/container/vector/deduction_guides
> You're using an array in modern C++ code.

As I see it, there is nothing wrong in using a classical array in modern C++ code, provided that it has a fixed constant size, and it is used only within the local scope where it is defined.


> You've eliminated .open , .is_open() APIs.

They are implicit within the condition of the if statement: the constructor of the object calls open() and operator bool verifies that the stream is ready for i/o.

> The file is closed right after exiting the if block, automagically.

Yes, the destructor of the object closes the file.

> shouldn't line 14 be writing the new line to the file instead of std::cout?

Yes, thanks! Correcting it now.

That's greatttt, JLBorges!

Few things are rather astonishing for me, that:

1- You're using an array in modern C++ code.

It's ok to use older constructs, and the size of the array is known within the scope of its declaration. If it's passed around, only a pointer is passed, so be careful.

This is also more efficient than using a std::vector, as that requires an allocation and release from the heap. If you don't care about efficiency, why put yourself thru the grief of learning C++? You may as well use Python or Ruby.

2- You've eliminated .open , .is_open() APIs.

fstreams aren't just files, they're streams. Streams have a state (good/bad/eof/fail), you only need to check if it's good if all you want to know is that all's ok. That's why that construct is in an if statement.

3- The file is closed right after exiting the if block, automagically.

Yes, the file is closed. No, it's not magic. It's a feature of objects having constructors and destructors. Constructors initialize the object before use (this is why you shouldn't call open()), and destructors release resources when you're done with the object (this is why you shouldn't call close()). Think stream, not file.
A c -style array can be passed by ref using a template to obtain it's size in the function without having another param as the size. Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>

template<size_t N>
void myfunc(const int (&array)[N]) {
	for (const auto& v : array)
		std::cout << v << ' ';
}

int main() {
	const int a[] {1, 2, 3, 4, 5};

	myfunc(a);
}



1 2 3 4 5


Also an important idea in C++ is RAII - Resource Acquisition Is Initialisation
See https://en.cppreference.com/w/cpp/language/raii

C++ allows constructs like this:

1
2
3
4
5
6
#include <iostream>

int main() {
	if (int a {6})
		std::cout << a << " != 0";
}


where the if condition test is the result of defining a and assigning 6 to it (ie. 6) and where the scope of a is within the if statement. This is similar to the use of std::ofstream within an if statement condition to open a file.

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

int main() {
	const int numbers[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
	const std::string path_to_file {"test.txt"};

	if (std::ofstream file {path_to_file})
		for (const auto n : numbers)
			file << n << ' ';
	else
		return (std::cerr << "Failed to open file for output\n"), 1;

	if (std::ifstream file {path_to_file}) {
		for (int n {}; file >> n; std::cout << n << ' ');
		std::cout << '\n';
	} else
		return (std::cerr << "failed to open file for input\n"), 2;
}

Last edited on
> if (int a {6})
> if condition test is the result of defining a and assigning 6 to it

There is no assignment: this is initialisation. The condition is evaluated in a bool context.
Try this: if ( const int a = 6 ) std::cout << a << " != 0\n";

6 != 0


as expected. Sorry for my in-exact wording. I tend to interchange assignment/initialisation when defining a variable. In my over 25 years of using C++ I've never read a c++ standard. Oh dear... :) :)

There used to be a difference between myclass mc = 6; and myclass mc (6); - the first called assignment after a default constructor and the second called a constructor. Now they both call the constructor.
> There used to be a difference between myclass mc = 6; and myclass mc (6);

There still is an important difference.
myclass mc = 6; - copy initialisation: https://en.cppreference.com/w/cpp/language/copy_initialization
myclass mc (6); - direct initialisation: https://en.cppreference.com/w/cpp/language/direct_initialization

For instance:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
    struct myclass
    {
        explicit myclass(int) {}
    };

    const myclass mc1(6) ; // fine: direct initialisation

    const myclass mc2 = 6 ; // *** error *** : attempted copy initialisation
                             //                there is no viable conversion from int to myclass
}

http://coliru.stacked-crooked.com/a/dc3134b43767e0cf


> the first called assignment after a default constructor

No. It has always been (even before C++11) copy initialisation.
@George P
Thank you, yeah that was cool! :Þ

JLBorges
Yes, the destructor of the object closes the file.
Where is that destructor called in our code, please?

Thanks kbw
Last edited on
> Where is that destructor called

When the life-time of the object ends.
For an object in the condition of an if statement, life-time begins at the point of its declaration
and ends at the end of the if statement.

1
2
3
4
if( std::ofstream file{path_to_file} ) // if the file is opened for output
{
    // ... 
} // destructor is called here; the if statement ends here and the end of life-time of file is reached 
So it does not necessarily hinges upon the curly brackets of the if-statement and even if we write that this way, it works:

1
2
3
4
5
if( std::ofstream file{path_to_file} ) // if the file is opened for output
        for( int n : numbers )
          file << n << ' ' ; // *** add a white space as separator
           // Here when the for-loop ends, the if-statements, too, ends 
           // so the destructor for the file object is called 

Right?
Last edited on
Yes. That is a 1 statement invoked if the condition is true. {} is just a compound statement used in place of a single statement. See https://en.cppreference.com/w/cpp/language/statements

The scope as said above by Borges is from the point it's defined to the end of the if statement - which includes any optional else.

Also note that with C++17 you can write it like this:

1
2
3
if (std::ofstream file(Path_to_file); file)
    for (int n : number)
        file << n << ' ';


which is how I now prefer to code it. See https://en.cppreference.com/w/cpp/language/if
Right. Curly braces are required only if the statement-true part (or the statement-false part)
of the if-statement is a compound statement.

For example:
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 <iostream>
#include <string>

int main()
{
    struct A
    {
        std::string tag ;
        A( std::string name ) : tag( std::move(name) ) { std::cout << "A(" << tag << ")::constructor\n" ; }
        ~A() { std::cout << "A(" << tag << ")::destructor\n" ; }
        explicit operator bool() const noexcept
        {
            std::cout << "A(" << tag << ")::operator bool()\n" ;
            return !tag.empty() ;
        }
    };

    if( const A one{"condition"} ) // condition of if-statement: A(condition)::constructor, A(condition)::operator bool()
            const A two{"statement-true"} ; // statement-true part of if-statement: A(statement-true)::constructor
            // end of statement-true part: destroy A(statement-true)
            // A(statement-true)::destructor
    // end of if-statement: destroy A(condition)
    // A(condition)::destructor
}

http://coliru.stacked-crooked.com/a/e1452fd12d37e4f9
What makes you define the class inside main(), please? Classes/structs are normally defined outside the main function if I'm not mistaken.
Last edited on
> What makes you define the class inside main()?
> Classes/structs are normally defined outside the main function

The class is required only within the function main(); so make it local to the function main().
Many classes (types) are designed to be used in more than one function; ergo, they would be declared at namespace scope.

A class declaration can appear inside the body of a function, in which case it defines a local class. The name of such a class only exists within the function scope, and is not accessible outside.
https://en.cppreference.com/w/cpp/language/class#Local_classes


This is somewhat similar to variable declarations; a variable that is required only within a block would be declared at block scope.
Topic archived. No new replies allowed.