Mentions légales du service

Skip to content
Snippets Groups Projects
Commit 4a83c0ba authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Add small section about binary writing.

parent 0e2cfb99
No related branches found
No related tags found
1 merge request!117Fixes from my notes of the first day of March 2024 session
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Input and output streams](./6-Streams.ipynb) # [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Input and output streams](./6-Streams.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Predefined streams ## Predefined streams
The standard C++ includes an input/output library that specifies a common interface for all data exchanges with the outside world, based in particular on the insertion `<<` and extraction `>>` operators. The standard C++ includes an input/output library that specifies a common interface for all data exchanges with the outside world, based in particular on the insertion `<<` and extraction `>>` operators.
### `std::cout` ### `std::cout`
We have already dealt profusely with `std::cout` which provide the link to the Unix channel `stdout`: We have already dealt profusely with `std::cout` which provide the link to the Unix channel `stdout`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << "Hello world!" << std::endl; std::cout << "Hello world!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `std::cerr` ### `std::cerr`
There is also `std::cerr`, which is related to Unix `stderr`: There is also `std::cerr`, which is related to Unix `stderr`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
int n = -4; int n = -4;
if (n < 0) if (n < 0)
std::cerr << "Positive or null value expected!" << std::endl; std::cerr << "Positive or null value expected!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `std:cin` ### `std:cin`
And finally `std::cin`, related to Unix channel `stdin`. Line crossings are ignored (assimilated to spaces and tabs). And finally `std::cin`, related to Unix channel `stdin`. Line crossings are ignored (assimilated to spaces and tabs).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cppmagics std::cin/clang %%cppmagics std::cin/clang
// Standard input is **very** tricky to use in notebook environment; we're therefore using // Standard input is **very** tricky to use in notebook environment; we're therefore using
// a hack that calls the underlying clang compiler on your system. // a hack that calls the underlying clang compiler on your system.
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <random> #include <random>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::random_device rd; // Will be used to obtain a seed for the random number engine std::random_device rd; // Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd() std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(0, 100); std::uniform_int_distribution<> dis(0, 100);
auto hidden = dis(gen); auto hidden = dis(gen);
int guess = -1; int guess = -1;
while (guess != hidden) while (guess != hidden)
{ {
std::cout << "Find the value between 0 and 100: "; std::cout << "Find the value between 0 and 100: ";
std::cin >> guess; std::cin >> guess;
if (guess > hidden) if (guess > hidden)
std::cout << " Too high!" << std::endl; std::cout << " Too high!" << std::endl;
else if (guess < hidden) else if (guess < hidden)
std::cout << " Too low!" << std::endl; std::cout << " Too low!" << std::endl;
} }
std::cout << "Congratulations! You have found the hidden number!" << std::endl; std::cout << "Congratulations! You have found the hidden number!" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`std::cin` is a bit more tricky to use than the others, as the risk the operation fails is really higher. For instance, if you give a string in the code above it will become crazy and keep printing the same message "Too high!" or "Too low!" (be ready to restart the kernel...). The following code fixes this: `std::cin` is a bit more tricky to use than the others, as the risk the operation fails is really higher. For instance, if you give a string in the code above it will become crazy and keep printing the same message "Too high!" or "Too low!" (be ready to restart the kernel...). The following code fixes this:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
%%cppmagics std::cin/clang %%cppmagics std::cin/clang
// Standard input is **very** tricky to use in notebook environment; we're therefore using // Standard input is **very** tricky to use in notebook environment; we're therefore using
// a hack that calls the underlying clang compiler on your system. // a hack that calls the underlying clang compiler on your system.
#include <cstddef> #include <cstddef>
#include <iostream> #include <iostream>
#include <random> #include <random>
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
std::random_device rd; //Will be used to obtain a seed for the random number engine std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<> dis(0, 100); std::uniform_int_distribution<> dis(0, 100);
auto hidden = dis(gen); auto hidden = dis(gen);
int guess = -1; int guess = -1;
while (guess != hidden) while (guess != hidden)
{ {
do do
{ {
if (!std::cin) if (!std::cin)
{ {
std::cin.clear(); // clear the states of std::cin, putting it back to `goodbit`. std::cin.clear(); // clear the states of std::cin, putting it back to `goodbit`.
std::cin.ignore(10000, '\n'); // clean-up what might remain in std::cin before using it again. std::cin.ignore(10000, '\n'); // clean-up what might remain in std::cin before using it again.
} }
std::cout << "Find the value between 0 and 100: "; std::cout << "Find the value between 0 and 100: ";
std::cin >> guess; std::cin >> guess;
} while (!std::cin); } while (!std::cin);
if (guess > hidden) if (guess > hidden)
std::cout << " Too high!" << std::endl; std::cout << " Too high!" << std::endl;
else if (guess < hidden) else if (guess < hidden)
std::cout << " Too low!" << std::endl; std::cout << " Too low!" << std::endl;
} }
std::cout << "Congratulations! You have found the hidden number!" << std::endl; std::cout << "Congratulations! You have found the hidden number!" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you want to learn more about `std::cin`, you might want to look at [this post](https://stackoverflow.com/questions/5131647/why-would-we-call-cin-clear-and-cin-ignore-after-reading-input) on StackOverflow. If you want to learn more about `std::cin`, you might want to look at [this post](https://stackoverflow.com/questions/5131647/why-would-we-call-cin-clear-and-cin-ignore-after-reading-input) on StackOverflow.
If you need to use it extensively, you should look more deeply the behaviour of the bit flags (`goodbit`, `badbit`, `failbit`, `eofbit`). If you need to use it extensively, you should look more deeply the behaviour of the bit flags (`goodbit`, `badbit`, `failbit`, `eofbit`).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Input/output with files ## Input/output with files
The same syntax with operators `<<` and `>>` may be used to interact with files; the streams are built with `std::ofstream` for an output stream and `std::ifstream` for an input stream. The same syntax with operators `<<` and `>>` may be used to interact with files; the streams are built with `std::ofstream` for an output stream and `std::ifstream` for an input stream.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <fstream> // for std::ifstream and std::ofstream #include <fstream> // for std::ifstream and std::ofstream
#include <iostream> #include <iostream>
{ {
std::ofstream out("File.tmp"); std::ofstream out("File.tmp");
out << 5 << std::endl; out << 5 << std::endl;
out << -7 << std::endl; out << -7 << std::endl;
out << 9 << std::endl; out << 9 << std::endl;
out.close(); // file is written on disk when closed; automatically done when `out` gets out of scope otherwise out.close(); // file is written on disk when closed; automatically done when `out` gets out of scope otherwise
std::ifstream in("File.tmp"); std::ifstream in("File.tmp");
int value; int value;
while (in >> value) while (in >> value)
std::cout << value << std::endl; std::cout << value << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `getline()` ### `getline()`
When reading a file, if you want to interpret it line by line you should also consider `getline()`; this function may get a third argument to choose which separator to use (`\n` by default). When reading a file, if you want to interpret it line by line you should also consider `getline()`; this function may get a third argument to choose which separator to use (`\n` by default).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <string> #include <string>
{ {
std::ifstream in("File.tmp"); // assumes previous cell has been played recently! std::ifstream in("File.tmp"); // assumes previous cell has been played recently!
std::string line; std::string line;
while (getline(in, line)) while (getline(in, line))
std::cout << line << std::endl; std::cout << line << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `ostream` and `istream` ## `ostream` and `istream`
If you want to devise a function that may take as argument either a `std::cout` or a `std::ofstream`, you should use a `std::ostream` (we'll study [later](../2-ObjectProgramming/6-inheritance.ipynb) why this works but just take my word for now): If you want to devise a function that may take as argument either a `std::cout` or a `std::ofstream`, you should use a `std::ostream` (we'll study [later](../2-ObjectProgramming/6-inheritance.ipynb) why this works but just take my word for now):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
void PrintOnStream(std::ostream& out) void PrintOnStream(std::ostream& out)
{ {
out << "Printed on the chosen stream!" << std::endl; out << "Printed on the chosen stream!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
PrintOnStream(std::cout); PrintOnStream(std::cout);
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <string> #include <string>
#include <fstream> #include <fstream>
{ {
std::ofstream out("test_stream.txt"); std::ofstream out("test_stream.txt");
PrintOnStream(out); PrintOnStream(out);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
{ {
// Read the content of the line previously written. // Read the content of the line previously written.
std::ifstream in("test_stream.txt"); std::ifstream in("test_stream.txt");
std::string line; std::string line;
getline(in, line); getline(in, line);
std::cout << line << std::endl; std::cout << line << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Binary files
Exactly as in C, you may want to write directly your output in binary format rather than in ascii format by specifying `std::ios::binary` as second argument of `std::ofstream` or `std::ifstream`.
It may be combined with others with `|` operator:
%% Cell type:code id: tags:
``` c++
#include <fstream>
{
std::ofstream out("test_stream.binary", std::ios::binary | std::ios::app);
}
```
%% Cell type:markdown id: tags:
Using binary is *much* faster than ascii; if you want to write lots of data (typically in HPC) you should probably consider it.
Drawbacks are:
- Files written this way are less portable.
- Files can't be read directly by a human.
- Writing and reading such files requires more handiwork by the developer.
Still, it is an option that should be on the table as I/O might really be a bottleneck in runtime.
%% Cell type:markdown id: tags:
## Conversion ## Conversion
Stream syntax was until C++ 11 the only way to convert: Stream syntax was until C++ 11 the only way to convert:
- A string into a number with `std::istringstream` - A string into a number with `std::istringstream`
- A number into a string with `std::ostringstream`; the `str()` method returns the content as a `std::string`. - A number into a string with `std::ostringstream`; the `str()` method returns the content as a `std::string`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
std::string number_as_string = "657"; std::string number_as_string = "657";
int number; int number;
std::istringstream iconv(number_as_string); std::istringstream iconv(number_as_string);
iconv >> number; iconv >> number;
std::cout << "Number + 1 = " << number + 1 << std::endl; std::cout << "Number + 1 = " << number + 1 << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
int number = 657; int number = 657;
std::ostringstream oconv; std::ostringstream oconv;
oconv << "The number is " << number; oconv << "The number is " << number;
std::cout << oconv.str() << std::endl; std::cout << oconv.str() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To reuse a `std::ostringstream`, you must set its content to an empty string with an overloaded `str()`: To reuse a `std::ostringstream`, you must set its content to an empty string with an overloaded `str()`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
int number = 657; int number = 657;
std::ostringstream oconv; std::ostringstream oconv;
oconv << "The number is " << number; oconv << "The number is " << number;
std::cout << oconv.str() << std::endl; std::cout << oconv.str() << std::endl;
oconv.str(""); // reset oconv oconv.str(""); // reset oconv
oconv << "My new content is now there!"; oconv << "My new content is now there!";
std::cout << oconv.str() << std::endl; std::cout << oconv.str() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course as for `std::cin` you may check the state of the object is still valid - if conversion is incorrect it won't!: Of course as for `std::cin` you may check the state of the object is still valid - if conversion is incorrect it won't!:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <sstream> // for std::ostringstream and std::istringstream #include <sstream> // for std::ostringstream and std::istringstream
#include <string> #include <string>
{ {
std::string number_as_string = "abd"; std::string number_as_string = "abd";
int number; int number;
std::istringstream iconv(number_as_string); std::istringstream iconv(number_as_string);
iconv >> number; // invalid conversion! iconv >> number; // invalid conversion!
if (!iconv) if (!iconv)
std::cerr << "Invalid string!" << std::endl; std::cerr << "Invalid string!" << std::endl;
else else
std::cout << "Number + 1 = " << number + 1 << std::endl; std::cout << "Number + 1 = " << number + 1 << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In C++ 11, `std::to_string()` and the [`stoi` (and similar functions for long)](https://en.cppreference.com/w/cpp/string/basic_string/stol) were introduced to provide similar functionality with a more direct syntax: In C++ 11, `std::to_string()` and the [`stoi` (and similar functions for long)](https://en.cppreference.com/w/cpp/string/basic_string/stol) were introduced to provide similar functionality with a more direct syntax:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
{ {
int number = 657; int number = 657;
std::string int_to_string("Number is "); std::string int_to_string("Number is ");
int_to_string += std::to_string(number); int_to_string += std::to_string(number);
std::cout << int_to_string << std::endl; std::cout << int_to_string << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
{ {
std::string number_as_string = "4557"; std::string number_as_string = "4557";
int number = std::stoi(number_as_string); int number = std::stoi(number_as_string);
std::cout << "Number is " << number << std::endl; std::cout << "Number is " << number << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is however useful to be aware of the pre-C++ 11 syntax, especially for the number to string conversion: 'arithmetic' operations between strings (as `+`) incur copies that are avoided with the `std::ostringstream` syntax... but the construction of such a `std::ostringstream` object is costly as well... It is however useful to be aware of the pre-C++ 11 syntax, especially for the number to string conversion: 'arithmetic' operations between strings (as `+`) incur copies that are avoided with the `std::ostringstream` syntax... but the construction of such a `std::ostringstream` object is costly as well...
C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details)... but unfortunately current support by compilers is [not great](https://en.cppreference.com/w/cpp/compiler_support) (still true in 2024...) C++ 20 should provide a better looking and more efficient syntax with `std::format` (see [this page](https://en.cppreference.com/w/cpp/utility/format/format) for more details)... but unfortunately current support by compilers is [not great](https://en.cppreference.com/w/cpp/compiler_support) (still true in 2024...)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Formatting and manipulators ## Formatting and manipulators
You may act upon the exact formatting of the output. You may act upon the exact formatting of the output.
I'll be honest: it's not what is the most refined tool in the C++ library, and you may long for the simplicity and power of something like Python (or even C `printf`, which is much simpler to use while being a mess under the hood...). I'll be honest: it's not what is the most refined tool in the C++ library, and you may long for the simplicity and power of something like Python (or even C `printf`, which is much simpler to use while being a mess under the hood...).
Once again `std::format` from C++ 20 should be a game changer here! Once again `std::format` from C++ 20 should be a game changer here!
The difficulty is that some settings apply only to the next entry onto the stream (`width` here), while others change the behaviour permanently (until told otherwise of course, e.g. `precision` here). Here are few examples of these syntaxes: The difficulty is that some settings apply only to the next entry onto the stream (`width` here), while others change the behaviour permanently (until told otherwise of course, e.g. `precision` here). Here are few examples of these syntaxes:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
{ {
std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly
std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only
std::cout.precision(2); // number of decimal digits std::cout.precision(2); // number of decimal digits
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 1.237 ; std::cout << 1.237 ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 100.1245 ; std::cout << 100.1245 ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << '\n' ; std::cout << '\n' ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 1.5774e-2 ; std::cout << 1.5774e-2 ;
std::cout.width(8) ; std::cout.width(8) ;
std::cout << 12. << '\n' ; std::cout << 12. << '\n' ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Manipulators** provide a shorter syntax to add some of the properties as the `width` or the `precision`: **Manipulators** provide a shorter syntax to add some of the properties as the `width` or the `precision`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` c++ ``` c++
#include <iostream> #include <iostream>
#include <iomanip> // for std::setprecision #include <iomanip> // for std::setprecision
{ {
std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly std::cout.setf(std::ios::showpos); // Add the `+` sign explicitly
std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only std::cout.setf(std::ios::fixed, std::ios::floatfield); // use decimal notation only
std::cout << std::setprecision(2) << std::setw(8) << 1.237; std::cout << std::setprecision(2) << std::setw(8) << 1.237;
std::cout << std::setw(8) << 100.1245; std::cout << std::setw(8) << 100.1245;
std::cout << '\n'; std::cout << '\n';
std::cout << std::setw(8) << 1.5774e-2; std::cout << std::setw(8) << 1.5774e-2;
std::cout << std::setw(8) << 12. << '\n'; std::cout << std::setw(8) << 12. << '\n';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment