|
1 |
| -# Files and Formatting |
| 1 | +# Files and Formatting |
| 2 | + |
| 3 | +## Formatting values and variables for output |
| 4 | + |
| 5 | +We have seen how values and variables can be put to output streams using `<<`, and how `print()` and `println()` can be used to output subsequent parameters using curly braces in the format string. For further control over the way these are output, such as field width, accuracy etc. we need to specify this using stream manipulators (when outputting to streams) or extra information in the *format string* (when using `print()`/`println()`). Manipulators are covered later in this Chapter, what follows is a discussion of how to use *format specifiers* with `print()`, `println()` and `format()`/`format_to()`. |
| 6 | + |
| 7 | +The following program demonstrates use of format specifiers for some common types: |
| 8 | + |
| 9 | +```cpp |
| 10 | +// 08-format1.cpp : Basic usage of format string |
| 11 | + |
| 12 | +#include <print> |
| 13 | +#include <string> |
| 14 | +using namespace std; |
| 15 | + |
| 16 | +int main() { |
| 17 | + string s{ "Formatted" }; |
| 18 | + auto d{ 10.0 / 3.0 }; |
| 19 | + auto i{ 20000 }; |
| 20 | + println("{0:20}:{2:8}, {1:12.11}", s, d, i); |
| 21 | +} |
| 22 | +``` |
| 23 | + |
| 24 | +This program outputs the text `Formatted` followed by sufficient spaces to pad up to a width of 20 characters, then a colon present in the format string, then the value `20000` right-aligned to a width of 8 characters, then the comma and space present in the format string, and finally the value 3.3333333333 at a "precision" of 11 figures (plus decimal point) padded to a width of 12 characters (no padding is necessary for this particular value. |
| 25 | + |
| 26 | +**Experiment**: |
| 27 | + |
| 28 | +* Try printing the three parameters in a different order, by changing the numbers before the colon within the curly braces. |
| 29 | + |
| 30 | +* Is it possible to achieve the same results when removing these numbers altogether? |
| 31 | + |
| 32 | +* What happens if you repeat one of `s`, `d`, or `i` in the parameter list? Or take one away? |
| 33 | + |
| 34 | +The format string, and its associated format specifier(s), are evaluated at compile-time for maximum performance. It must therefore be a string literal, not a string-type variable except for one that is `constexpr`. The values of the subsequent parameters referenced by the format specifiers can (and probably will) change during the run of the program. |
| 35 | + |
| 36 | +## Format specifiers |
| 37 | + |
| 38 | +As well as describing the field width and precision for all of the built-in types (plus several Standard Library types), format specifiers offer fine-grained control over the output. In fact, all format specifiers are made up of eight optional parts, all of which (if used) appear in order after the colon in the format string. These are listed in the table below: |
| 39 | + |
| 40 | +| Field | Description | Example | Result | |
| 41 | +|----------------|-----------------------------------------------------|---------|--------------------------| |
| 42 | +| Fill-and-align | Optional fill character then: <, >, or ^ | {:@>10} | @@@@1233456 | |
| 43 | +| Sign | One of: +, - (default), or space | {:+} | +1.23 | |
| 44 | +| # | Use alternate form | {:#} | 0x12a, 3.0 | |
| 45 | +| 0 | Pad integers with leading zeros | {:06} | 000123 | |
| 46 | +| Width | Minimum field width | {:10} | "abc " | |
| 47 | +| Precision | FP-precision, maximum field width | {:.7} | 3.333333, "Formatt" | |
| 48 | +| L | Use locale-specific setting | {L} | 12,345, 1.234,56, "faux" | |
| 49 | +| Type | One of: b, B, d, o, x, X, a, A, e, E, f, F, g, G, ? | {:8.7a} | 1.aaaaaabp+1 | |
| 50 | + |
| 51 | +It is also possible to write custom formatters which operate on arbitrary format specifiers and user-defined classes. An alternative method would be to create a public `toString()` method in the class and simply invoke this as a parameter after the format string. |
| 52 | + |
| 53 | +The format specifiers listed above work with `print()` and `println()` as well as other functions from the `<format>` header (which include wide-character variants). Here is a complete list: |
| 54 | + |
| 55 | +| Function | Description | Parameters | Return value | |
| 56 | +|---------------|--------------------------------------------|--------------------------------|---------------------------| |
| 57 | +| `print()` | Output to `stdout`, `FILE*` or `std::ostream` | [dest, ] fmt, ... | None | |
| 58 | +| `println()` | As for `print()` with trailing newline | [dest, ] fmt, ... | None | |
| 59 | +| `format()` | Create a string from (wide-) format string | [locale, ] fmt, ... | `std::string`, `std::wstring` | |
| 60 | +| `format_to()` | Write to a (wide-) output iterator | iter, [locale, ] fmt, ... | `out` member is `std::iterator` | |
| 61 | +| `format_to_n()` | As for `format_to()` with size limit | iter, max, [locale, ] fmt, ... | `out` member is `std::iterator` | |
| 62 | + |
| 63 | +In choosing between the above functions, the aim would be to choose the most performant for the task. The following program outputs different format strings and parameters utilizing a variety of these functions: |
| 64 | + |
| 65 | +```cpp |
| 66 | +// 08-format2.cpp : Various format string-using functions |
| 67 | + |
| 68 | +#include <print> |
| 69 | +#include <format> |
| 70 | +#include <string> |
| 71 | +#include <iostream> |
| 72 | +#include <iterator> |
| 73 | +#include <array> |
| 74 | +#include <cmath> |
| 75 | +using namespace std; |
| 76 | + |
| 77 | +int main() { |
| 78 | + string world{ "World" }; |
| 79 | + print(cout, "Hello, {}!\n", world); |
| 80 | + println("{1} or {0}", false, true); |
| 81 | + |
| 82 | + constexpr const char *fmt = "Approximation of π = {:.12g}"; |
| 83 | + string s = format(fmt, asin(1.0) * 2); |
| 84 | + cout << s << '\n'; |
| 85 | + |
| 86 | + constexpr const wchar_t *wfmt = L"Approximation of pi = {:.12g}"; |
| 87 | + wstring ws = format(wfmt, asin(1.0) * 2); |
| 88 | + wcout << ws << L'\n'; |
| 89 | + |
| 90 | + format_to(ostream_iterator<char>(cout), "Hello, {}!\n", world); |
| 91 | + wstring ww{ L"World" }; |
| 92 | + array<wchar_t,9> wa; |
| 93 | + auto iter = format_to_n(wa.begin(), 8, L"Hello, {}!\n", ww); |
| 94 | + *(iter.out) = L'\0'; |
| 95 | + wcout << wa.data() << L'\n'; |
| 96 | +} |
| 97 | +``` |
| 98 | + |
| 99 | +A few things to note about this program: |
| 100 | + |
| 101 | +* The use of `print()` is straightforward and simply outputs `Hello, World!` on a single line, using the variant that prints to a `std::ostream`, in this case `cout`. |
| 102 | + |
| 103 | +* The call to `println()` reverses the order of its subsequent parameters and outputs them textually: `true or false`. You should be aware that this prints to the C standard output (`stdout`) as mixing stream and C output can sometimes cause issues. |
| 104 | + |
| 105 | +* The uses of `format()`, first with a 8-bit and second with a wide-character format string, create a temporary (wide-) string and then put this to the (wide-) character output stream. |
| 106 | + |
| 107 | +* The function `format_to()` is called with `ostream_iterator<char>(cout)` which is boilerplate for creating a suitable output iterator from a stream object. |
| 108 | + |
| 109 | +* The use for `format_to_n()` is more involved as it uses a fixed size `std::array` to hold the wide-character output string. The first parameter is the (writable) iterator pointing to the start of the array, and the second is the maximum number of characters to write. The return value has an `out` data member which is the iterator pointing to the next character in the array, which needs to be set to NUL in order to allow putting `data()` to `wcout`. |
| 110 | + |
| 111 | +**Experiment:** |
| 112 | + |
| 113 | +* Modify this program to use different field widths. Do they work with wide characters? |
| 114 | + |
| 115 | +* Try some of the different format specifiers from the table above, together with different built-in types such as `long long` and `double`. |
2 | 116 |
|
3 | 117 | ## Simple file access
|
4 | 118 |
|
5 | 119 | All of the programs we have seen so far lose their internal state, together with any user input, when they exit. A program which can save and/or restore its state makes use of *persistence*. The way this is usually achieved, of course, is to enable saving to and loading from a disk file, stored on a hard-drive, memory card or network server.
|
6 | 120 |
|
| 121 | +C++ file access using the Standard Library header `<fstream>` is designed to be analogous to use of `cin` and `cout`, using the stream extraction (`>>`) and insertion (`<<`) operators. File access using the C Library's `<cstdio>` header is also possible, and a suitable `FILE *` pointer can be passed as the first parameter to `print()` and `println()` to switch output to that file. |
| 122 | + |
7 | 123 | The following program reads from a previously created file and echoes the content to the console. (The filename is provided at run-time as the first environment parameter after the executable name.) This program is only safe to use with text files, so fire up your favorite editor and create a test file to use, including some whitespace such as spaces, tabs and newlines.
|
8 | 124 |
|
9 | 125 | ```cpp
|
@@ -42,7 +158,7 @@ A few things to note about this program:
|
42 | 158 |
|
43 | 159 | **Experiment:**
|
44 | 160 |
|
45 |
| -* Try removing `static_cast<char>` and see what happens. Consider if this could ever be desirable. |
| 161 | +* Try removing `static_cast<char>` and see what happens. Consider whether this could ever be desirable. |
46 | 162 |
|
47 | 163 | * What happens if you change the same line to `cout.put(c);`?
|
48 | 164 |
|
@@ -608,4 +724,4 @@ int main() {
|
608 | 724 |
|
609 | 725 | * Modify this program to read `Pixel`s.
|
610 | 726 |
|
611 |
| -*All text and program code ©2019-2022 Richard Spencer, all rights reserved.* |
| 727 | +*All text and program code ©2019-2024 Richard Spencer, all rights reserved.* |
0 commit comments