diff --git a/1-ProceduralProgramming/3-Types.ipynb b/1-ProceduralProgramming/3-Types.ipynb index 7534c528867631c352b38cde16260a6c43622faa..5253a6ca21fff3a69b5c70e06be33f7f2160d78d 100644 --- a/1-ProceduralProgramming/3-Types.ipynb +++ b/1-ProceduralProgramming/3-Types.ipynb @@ -422,7 +422,7 @@ "source": [ "#### Conversions between digital types\n", "\n", - "[Earlier](/notebooks/1-ProceduralProgramming/1-Variables.ipynb#Initialisation) I indicated there were small differences between the three initialization methods, that could be ignored most of the time. \n", + "[Earlier](/notebooks/1-ProceduralProgramming/1-Variables.ipynb#Initialisation) we indicated there were small differences between the three initialization methods, that could be ignored most of the time. \n", "\n", "The difference is related to implicit conversion: both historical initialization methods are ok with implicit conversion __with accuracy loss__:" ] @@ -436,15 +436,14 @@ "#include <iostream>\n", "#include <iomanip>\n", "{\n", - " float f = 1.1234567890123;\n", - " double d = 2.1234567890123;\n", - " float f_d(d);\n", - " float f_dd = d;\n", + " float f = 1.123456789;\n", + " double d = 2.123456789;\n", + " float f_d(d); // Could also have been written: float f_d = d;\n", "\n", " std::cout << \"A double may print around 15 significant digits, d = \" << std::setprecision(16) << d << std::endl;\n", " std::cout << \"A float may print around 7 significant digits, f_d = \" << std::setprecision(16) << f_d << \" so we see here the value was altered from the initial double value.\" << std::endl;\n", "\n", - " std::cout << \"Even without conversion involved there is an accuracy loss: f = \" << std::setprecision(16) << f << std::endl;\n", + " std::cout << \"Even without conversion involved there is an accuracy loss if we require too high a precision: f = \" << std::setprecision(16) << f << std::endl;\n", " \n", "}" ] @@ -453,7 +452,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "whereas C++ 11 introduced initialization with braces isn't:" + "whereas C++ 11 introduced initialization with braces isn't, even if the initial number could fit in the target conversion type:" ] }, { @@ -463,7 +462,7 @@ "outputs": [], "source": [ "{\n", - " double d = 2.12345678901234567890;\n", + " double d = 2.12345;\n", " float f_d{d}; // COMPILATION ERROR\n", "}" ] @@ -472,7 +471,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This is really related to **accuracy loss**: initialization with braces is ok if there are none:" + "This is really related to **accuracy loss**: initialization with braces is ok only if there is no such loss:" ] }, { @@ -482,7 +481,7 @@ "outputs": [], "source": [ "{\n", - " float f = 1.12345678901234567890;\n", + " float f = 1.123456;\n", " double d_f { f }; // OK\n", "}" ] @@ -626,7 +625,66 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Please note that the best would be to explicitly cast both values, to avoid useless implicit conversion." + "Please note that the best would be to **explicitly cast both values**, to avoid useless implicit conversion." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "{\n", + " double a = 2. / 3.;\n", + " std::cout << a << std::endl;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Underlying types of `enum class`\n", + "\n", + "In fact, under the hood `enum class` are storing `int` values by default:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "enum class arbitrary_enum_class { yes, no }; // with scoped enum I can choose any value without fearing naming conflict!\n", + "\n", + "// Don't bother if you're lost with `std::is_same_v<int, std::underlying_type_t<int_enum>>` - we'll cover that later in part 5.\n", + "// You just have to know here it checks whether underlying type behind enum is an `int` or not.\n", + "std::cout << \"Check underlying type behind `arbitrary_enum_class` is `int`: \" << std::boolalpha << std::is_same_v<int, std::underlying_type_t<arbitrary_enum_class>> << '\\n';" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However it is possible to choose another type by specifying it explicitly after a colon:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "enum class altitude : char // telling here explicitly to use `char` as underlying type\n", + "{\n", + " high = 'h',\n", + " low = 'l'\n", + "}; \n", + "\n", + "std::cout << \"Check underlying type behind `altitude` is not `int`: \" << std::boolalpha << std::is_same_v<int, std::underlying_type_t<altitude>> << '\\n';\n", + "std::cout << \"Check underlying type behind `altitude` is `char`: \" << std::boolalpha << std::is_same_v<char, std::underlying_type_t<altitude>> << '\\n';" ] }, { @@ -679,7 +737,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There are several other functions related to historical strings; for more information, do not hesitate to consult [this reference page](http://www.cplusplus.com/reference/cstring/)." + "There are several other functions related to historical strings; for more information, do not hesitate to consult [this reference page](http://www.cplusplus.com/reference/cstring/).\n", + "\n", + "#### Drawbacks of historical strings\n", + "\n", + "- Keeping track properly and not forgetting `\\0` gets old really fast and the outcome is usually not user-friendly...\n", + "- Assignment doesn't work as you would expect:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <iostream>\n", + "\n", + "// Define hello, and copy it\n", + "char hello[] = {'h','e','l','l','o', '\\0'};\n", + "const char* copy = hello;\n", + "\n", + "// Modify hello\n", + "hello[0] = '!';\n", + "\n", + "// `copy` is modified too!\n", + "std::cout << \"Value of 'copy' is not the 'hello' you probably wanted: \" << copy << std::endl;" ] }, { @@ -715,7 +797,8 @@ "metadata": {}, "outputs": [], "source": [ - "copy = hello; // please notice assignment is much more straightforward\n", + "copy = hello; // please notice assignment is much more straightforward!\n", + "hello = \"hi\";\n", "std::cout << \"String '\" << copy << \"' is \" << copy.length() << \" characters long.\" << std::endl;" ] }, @@ -725,8 +808,11 @@ "metadata": {}, "outputs": [], "source": [ - "const char* copy_str = copy.data(); // Returns a classic C-string (from C++11 onward)\n", - "std::cout << \"String '\" << copy_str << \"' is \" << strlen(copy_str) << \" characters long.\" << std::endl;" + "std::string dynamic {\"dynamic\"};\n", + "std::cout << \"String '\" << dynamic << \"' is \" << dynamic.length() << \" characters long.\" << std::endl;\n", + "\n", + "dynamic += \" string\";\n", + "std::cout << \"String '\" << dynamic << \"' is \" << dynamic.length() << \" characters long.\" << std::endl;" ] }, { @@ -735,18 +821,15 @@ "metadata": {}, "outputs": [], "source": [ - "const char* old_copy_str = ©[0]; // Same before C++11... \n", - "std::cout << \"String '\" << old_copy_str << \"' is \" << strlen(old_copy_str) << \" characters long.\" << std::endl;" + "dynamic = \"std::string is dynamical and flexible\";\n", + "std::cout << \"String '\" << dynamic << \"' is \" << dynamic.length() << \" characters long.\" << std::endl;" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "std::string dynamic {\"dynamic std::string\"};\n", - "std::cout << \"String '\" << dynamic << \"' is \" << dynamic.length() << \" characters long.\" << std::endl;" + "If needed (for instance to interact with a C library) you may access to the underlying table with `c_str()` or `data()`:" ] }, { @@ -755,15 +838,24 @@ "metadata": {}, "outputs": [], "source": [ - "dynamic = \"std::string is dynamical and flexible\";\n", - "std::cout << \"String '\" << dynamic << \"' is \" << dynamic.length() << \" characters long.\" << std::endl;" + "#include <string>\n", + "{\n", + " std::string cplusplus_string(\"C++ string!\");\n", + " \n", + " const char* c_string = cplusplus_string.c_str();\n", + " const char* c_string_2 = cplusplus_string.data();\n", + "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "If needed (for instance to interact with a C library) you may access to the underlying table with `c_str()` or `data()` (both are interchangeable):" + "The `const` here is important: you may access the content but should not modify it; this functionality is provided for read-only access.\n", + "\n", + "Both are interchangeable; `data()` was introduced in C++ 11 but is more generic as other containers also define it - we'll cover that in part 5.\n", + "\n", + "As the principle to \"convert\" to C string is to return the address of the first character, you may also meet in some legacy codes:" ] }, { @@ -776,8 +868,7 @@ "{\n", " std::string cplusplus_string(\"C++ string!\");\n", " \n", - " const char* c_string = cplusplus_string.c_str();\n", - " const char* c_string_2 = cplusplus_string.data();\n", + " const char* c_string = &cplusplus_string[0]; \n", "}" ] }, @@ -785,14 +876,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `const` here is important: you may access the content but should not modify it; this functionality is provided for read-only access." + "which tells to litteraly take address of the first element." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "FYI, C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some operations (it is presented [in appendix](../7-Appendix/StringView.ipynb) but if it's your first reading it's a bit early to tackle it now)." + "FYI, C++17 introduced [std::string_view](https://en.cppreference.com/w/cpp/header/string_view) which is more efficient than `std::string` for some read-only operations (it is presented [in appendix](../7-Appendix/StringView.ipynb) but if it's your first reading it's a bit early to tackle it now)." ] }, { diff --git a/1-ProceduralProgramming/4-Functions.ipynb b/1-ProceduralProgramming/4-Functions.ipynb index 18386df91dff21174f3625fead51d30599a2fa2a..1e37bde9ea39d540e4beefb183adaa23a9a3b2cd 100644 --- a/1-ProceduralProgramming/4-Functions.ipynb +++ b/1-ProceduralProgramming/4-Functions.ipynb @@ -77,6 +77,7 @@ "- No semicolon after the prototype\n", "- A block follows the prototype instead; inside this block the implementation is written.\n", "- Parameter may not be named, but if they are used (which should be the case most of the time hopefully...) you will need such a name in the implementation.\n", + "- No semicolon after the closing brace.\n", "\n", "For instance:" ] @@ -99,7 +100,7 @@ " division = numerator / denominator ;\n", " std::cout << numerator << \" / \" << denominator << \" = \" << division << std::endl ; \n", " }\n", - "}" + "} // no semicolon here as well!" ] }, { @@ -148,7 +149,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Functions cannot be nested in C++, contrary to some other langages such as Python:" + "Functions cannot be nested in C++, contrary to some other languages such as Python:" ] }, { @@ -192,14 +193,14 @@ "source": [ "#include <iostream>\n", "\n", - "void IncrementAndPrint(int i)\n", + "void IncrementAndPrint(int value)\n", "{\n", - " ++i;\n", - " std::cout << \"Inside the function: i = \" << i << std::endl;\n", + " ++value;\n", + " std::cout << \"Inside the function: value = \" << value << std::endl;\n", "}\n", "\n", "{\n", - " int i = 5; // I could have named it differently - it doesn't matter as the scope is different!\n", + " int i { 5 };\n", "\n", " IncrementAndPrint(i);\n", "\n", @@ -211,7 +212,16 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `i` in the block body and in the function definition are not the same: one or the other could have been named differently and the result would have been the same:" + "#### Naming and scope" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the above example, the lonely parameter of the function `IncrementAndPrint` was named `value`, whereas at call site the variable was simply named `i`.\n", + "\n", + "We could as well have used the same name in both places - there are no ambiguity whatsoever in doing so." ] }, { @@ -220,23 +230,33 @@ "metadata": {}, "outputs": [], "source": [ - "#include <iostream>\n", - "\n", - "void IncrementAndPrint2(int local_argument)\n", + "void IncrementAndPrintUseI(int i)\n", "{\n", - " ++local_argument;\n", - " std::cout << \"Inside the function: local_argument = \" << local_argument << std::endl;\n", + " ++i;\n", + " std::cout << \"Inside the function: i = \" << i << std::endl;\n", "}\n", "\n", "{\n", - " int i = 5; // I could have named it differently - it doesn't matter as the scope is different!\n", + " int i { 5 };\n", "\n", - " IncrementAndPrint2(i);\n", + " IncrementAndPrintUseI(i);\n", "\n", " std::cout << \"Outside the function: i = \" << i << std::endl; \n", "}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This might seem idiotic (and sure enough giving a name as simple as a one letter character is misguided in most cases!), but on\n", + "the other hand when you're considering a mathematical or physical quantity (`jacobian`, `displacement`, etc... ) you do not want \n", + "to force yourself to find another name (naming is already hard enough as it is).\n", + "\n", + "The reason there are no ambiguity at all is that in each scope (remember [first notebook](../1-ProceduralProgramming/1-Variables.ipynb#Scope-and-blocks)) \n", + "there is at most one `i` defined." + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -346,7 +366,7 @@ "}\n", "\n", "PrintDivision(8, 5);\n", - "PrintDivision(8, 0); // bug!\n" + "PrintDivision(8, 0); // prints garbage values...\n" ] }, { @@ -358,6 +378,62 @@ "* This is something completely out of control: quotient and remainder do not get a default value that would help to see if something is askew. The behaviour is undefined: you have no guarantee on the values the program will print (currently I see the same values as in the previous function call, but another compiler/architecture/etc... might yield another wrong value." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### [[nodiscard]] \n", + "\n", + "C++ 17 introduced a keyword named `[[nodiscard]]` that mitigates the issue mentioned above; this keyword tells the return value must be checked and not doing so is a **compilation warning**.\n", + "\n", + "It doesn't prevent a developer to use improperly the `ComputeDivision` function, but at least they are warned that they're doing something wrong." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%cppmagics --print_command clang\n", + "\n", + "#include <cstdlib>\n", + "#include <iostream>\n", + "\n", + "// Declarations\n", + "int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder);\n", + "void PrintDivision(int arg1, int arg2);\n", + "\n", + "\n", + "// Definitions \n", + "[[nodiscard]] int ComputeDivision(int arg1, int arg2, int& quotient, int& remainder)\n", + "{\n", + " if (arg2 == 0)\n", + " return -1; // error code.\n", + " \n", + " quotient = arg1 / arg2;\n", + " remainder = arg1 % arg2;\n", + " return 0; // code when everything is alright.\n", + "}\n", + "\n", + "void PrintDivision(int arg1, int arg2)\n", + "{\n", + " int quotient, remainder;\n", + " \n", + " ComputeDivision(arg1, arg2, quotient, remainder); // the dev 'forgot' to check the error code.\n", + " \n", + " std::cout << \"Euclidean division of \" << arg1 << \" by \" << arg2 << \" yields a quotient of \" \n", + " << quotient << \" and a remainder of \" << remainder << std::endl; \n", + "}\n", + "\n", + "int main([[maybe_unused]] int argc, char** argv)\n", + "{\n", + " PrintDivision(8, 0); // bug!\n", + "\n", + " return EXIT_SUCCESS;\n", + "}" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1400,7 +1476,7 @@ "\n", "Sometimes, in old programs you may see __void main()__; this is not correct and is now refused by most modern compilers.\n", "\n", - "The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes.\n", + "The return value of the main function is an integer, __EXIT_SUCCESS__ should be returned when the program succeeds and __EXIT_FAILURE__ if it fails. You will often see a numerical value instead of these: __EXIT_SUCCESS__ is just a macro which value is 0. I recommend its use as you should strive to avoid any magic number in your codes. These two macros are defined inside `cstdlib` header.\n", "\n", "We will deal with main functions later when we will work in a true C++ environment." ] diff --git a/1-ProceduralProgramming/5-DynamicAllocation.ipynb b/1-ProceduralProgramming/5-DynamicAllocation.ipynb index 7c51ceda37ef931309396a484d3b5b9406882766..80bbb97602100247d55b3ff0a32bb81a4bf59c77 100644 --- a/1-ProceduralProgramming/5-DynamicAllocation.ipynb +++ b/1-ProceduralProgramming/5-DynamicAllocation.ipynb @@ -158,12 +158,12 @@ "\n", "auto Ndice = 5ul;\n", "\n", - "int* throw_5_dices = throw_dice(Ndice);\n", + "int* throw_5_dice = throw_dice(Ndice);\n", "\n", "for (std::size_t i = 0; i < Ndice; ++i)\n", - " std::cout << throw_5_dices[i] << std::endl;\n", + " std::cout << throw_5_dice[i] << std::endl;\n", "\n", - "delete[] throw_5_dices;" + "delete[] throw_5_dice;" ] }, { @@ -176,12 +176,12 @@ "\n", "Ndice = 3;\n", "\n", - "int* throw_7_dices = throw_dice(Ndice);\n", + "int* throw_7_dice = throw_dice(Ndice);\n", "\n", "for (std::size_t i = 0; i < Ndice; ++i)\n", - " std::cout << throw_7_dices[i] << std::endl;\n", + " std::cout << throw_7_dice[i] << std::endl;\n", "\n", - "delete[] throw_7_dices;" + "delete[] throw_7_dice;" ] }, { diff --git a/1-ProceduralProgramming/6-Streams.ipynb b/1-ProceduralProgramming/6-Streams.ipynb index 3ba2bc5d653229944491c3a3bd8fad92aff0c1c1..aafca4ca2b242563344fe6c44d5f605f2a5592a5 100644 --- a/1-ProceduralProgramming/6-Streams.ipynb +++ b/1-ProceduralProgramming/6-Streams.ipynb @@ -309,6 +309,47 @@ "}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Binary files\n", + "\n", + "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`. \n", + "\n", + "It may be combined with others with `|` operator (here `std::ios::app` which tells to append the new content at the end the file if it already exists - without this cue the default behaviour is to overwrite pre-existing `test_stream.binary` file)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#include <fstream>\n", + "\n", + "{\n", + " std::ofstream out(\"test_stream.binary\", std::ios::binary | std::ios::app); \n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using binary is *much* faster than ascii; if you want to write lots of data (typically in HPC) you should probably consider it.\n", + "\n", + "Drawbacks are:\n", + "\n", + "- Files written this way are less portable.\n", + "- Files can't be read directly by a human.\n", + "- Writing and reading such files requires more handiwork by the developer.\n", + "\n", + "Still, it is an option that should be on the table as I/O might really be a bottleneck in runtime.\n", + "\n", + "For more details see [the documentation on base class for all I/O stream classes](https://en.cppreference.com/w/cpp/io/ios_base) - fmtflags are also interesting, but a bit to deep for the training." + ] + }, { "cell_type": "markdown", "metadata": {},