Commits on Source (77)
-
GILLES Sebastien authored
#101 Add a sentence to indicate no default value for simple initialization: with cppyy we might get 0 for doubles which might give a false impression.
-
GILLES Sebastien authored
In 3-Types, modify an example which could introduce confusion (two equivalent syntaxes were shown one with `float` and the other with `double`, which could draw false inferences...)
-
GILLES Sebastien authored
-
GILLES Sebastien authored
#101 Notebook about variables: remove a Xeus cling reference, and use the possibility to call directly clang to illustrate warnings.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Replace a link to Coliru by an example run by the local compiler.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
#101 Notebook intro to operator: replace Coliru and comments that Xeus cling doesn't work by working cells.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
#101 Separate declarations / definitions in the two last notebooks (wasn't possible with Xeus-cling).
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
#101 Template specialization: leverage more powerful Cppyy kernel to provide a more interactive example of failure of function template specialization.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
It is the first notebook that required to be complexified a bit due to the change - some very specific stuff (using an `int` as an exception for instance) were not properly supported and needed to be worked around.
-
GILLES Sebastien authored
These ones were very light to adapt; the most important changes are in the one about move semantics in which definitions could be moved outside the class declaration.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Only the one with WeakPtr requires meaningful changes.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
environment.yml has been removed.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Other images and the if rule have been temporarily commented out.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
#101 Use native magic pointed out by Christian instead of home written magics in CppyyKernel (that will be removed anyway due to the duplication). Explanation has also been updated as gibberish might not appear when executable is run.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
- The one related to Xeus cling is no longer useful as Xeus cling is dismissed. - There is no need to provide a dedicated cppyy_kernel one for the training session: the basic Docker image built for CppyyKernel project is good enough. Reactivate properly the CI, minus the jobs no longer required to build aforementioned Docker images.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
#101 Binder: copy the content of the folder from https://gitlab.inria.fr/sgilles/clang-tidy-feedback project which works with exact same dependency.
-
GILLES Sebastien authored
#101 Installation: no need to clone the cppyy_kernel repo and then invoke pip: pip may be invoked directly.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Use CppyyKernel to run the notebooks instead of Xeus-cling. Closes #101 See merge request !114
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Rephrase differently the naming of parameters argument - why same name could be used in two different scopes.
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Concept should anyway be more developed!
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
Very few corrections noted in day 3 See merge request !119
-
GILLES Sebastien authored
Introduce maybe_unused. Closes #137 See merge request !115
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
GILLES Sebastien authored
-
ROUVREAU Vincent authored
Fixes from my notes of the first day of March 2024 session See merge request !117
-
ROUVREAU Vincent authored
Fixes from my notes of the last day of March 2024 session See merge request !120
Showing
- .gitlab-ci.yml 1 addition, 5 deletions.gitlab-ci.yml
- 0b-getting_started_with_tutorial.ipynb 47 additions, 18 deletions0b-getting_started_with_tutorial.ipynb
- 1-ProceduralProgramming/0-main.ipynb 5 additions, 6 deletions1-ProceduralProgramming/0-main.ipynb
- 1-ProceduralProgramming/1-Variables.ipynb 82 additions, 45 deletions1-ProceduralProgramming/1-Variables.ipynb
- 1-ProceduralProgramming/2-Conditions-and-loops.ipynb 30 additions, 30 deletions1-ProceduralProgramming/2-Conditions-and-loops.ipynb
- 1-ProceduralProgramming/2b-hands-on.ipynb 5 additions, 6 deletions1-ProceduralProgramming/2b-hands-on.ipynb
- 1-ProceduralProgramming/3-Types.ipynb 146 additions, 73 deletions1-ProceduralProgramming/3-Types.ipynb
- 1-ProceduralProgramming/4-Functions.ipynb 274 additions, 84 deletions1-ProceduralProgramming/4-Functions.ipynb
- 1-ProceduralProgramming/4b-hands-on.ipynb 4 additions, 4 deletions1-ProceduralProgramming/4b-hands-on.ipynb
- 1-ProceduralProgramming/5-DynamicAllocation.ipynb 12 additions, 12 deletions1-ProceduralProgramming/5-DynamicAllocation.ipynb
- 1-ProceduralProgramming/6-Streams.ipynb 68 additions, 23 deletions1-ProceduralProgramming/6-Streams.ipynb
- 1-ProceduralProgramming/6b-hands-on.ipynb 4 additions, 4 deletions1-ProceduralProgramming/6b-hands-on.ipynb
- 1-ProceduralProgramming/7-StaticAndConstexpr.ipynb 11 additions, 11 deletions1-ProceduralProgramming/7-StaticAndConstexpr.ipynb
- 2-ObjectProgramming/0-main.ipynb 5 additions, 6 deletions2-ObjectProgramming/0-main.ipynb
- 2-ObjectProgramming/1-Introduction.ipynb 29 additions, 21 deletions2-ObjectProgramming/1-Introduction.ipynb
- 2-ObjectProgramming/1b-hands-on.ipynb 5 additions, 6 deletions2-ObjectProgramming/1b-hands-on.ipynb
- 2-ObjectProgramming/2-Member-functions.ipynb 20 additions, 20 deletions2-ObjectProgramming/2-Member-functions.ipynb
- 2-ObjectProgramming/2b-hands-on.ipynb 5 additions, 6 deletions2-ObjectProgramming/2b-hands-on.ipynb
- 2-ObjectProgramming/3-constructors-destructor.ipynb 79 additions, 50 deletions2-ObjectProgramming/3-constructors-destructor.ipynb
- 2-ObjectProgramming/3b-hands-on.ipynb 5 additions, 6 deletions2-ObjectProgramming/3b-hands-on.ipynb
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Conditions and loops](./2-Conditions-and-loops.ipynb) | ||
%% Cell type:markdown id: tags: | ||
## Conditions | ||
%% Cell type:markdown id: tags: | ||
### `if` condition followed by a single statement | ||
%% Cell type:markdown id: tags: | ||
In C++, a condition is the `if` command followed by a condition in parenthesis `()`. The single instruction (that ends at the next `;`) _or_ the block (in braces `{}`) that follows is then executed only if the condition is true. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a > 0) | ||
std::cout << a << " is greater than 0" << std::endl; | ||
if (a < 0) | ||
std::cout << "This line won't be executed so nothing will be printed." << std::endl; | ||
std::cout << "But this line will be printed: without braces `{}` only the first instruction depends " | ||
"on the condition!" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Of course, the precedent code is embarrassing but is due to the poor indenting used: you have to remember that in C++ indenting is just for the programmer: it is much easier to read if a program is properly indented, but it doesn't matter from the compiler standpoint! | ||
Our case above would be much clearer however with a more logical indenting and spacing: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a < 0) | ||
std::cout << "This line won't be executed so nothing will be printed." << std::endl; | ||
std::cout << "It's much clearer now that this line is not encompassed by the condition!" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `if` condition followed by a block | ||
And if you need several lines to be encompassed by the condition, just use braces! | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a < 0) | ||
{ | ||
std::cout << "Should not be printed: the condition is false!" << std::endl; | ||
++a; // won't be executed: the condition is false | ||
} | ||
std::cout << "a was not modified and is still 2: " << a << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### No semicolon at the end of the `if` condition | ||
__BEWARE__: do not put a `;` at the end of an `if` statement! If you do so, the statement executed if the condition is true is the empty statement `;` which does nothing... The risk is rather mitigated: any compiler worth its salt will warn you if you do this mistake. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int a { 2 }; | ||
if (a == 0); | ||
std::cout << "Will be printed: the statement after the condition is ';', which does nothing..." << std::endl; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int a { 2 }; | ||
if (a == 2) | ||
; // putting the semicolon in a different line silences the warning; there are legitimate cases in | ||
// which it's useful to do so and the risk is | ||
// slim that this was written by mistake. | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### if ... else if ... else | ||
It is rather usual to foreseen two (or more...) courses of action depending on the results of one or several conditions. The syntax in this case is the following: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a < 0) | ||
std::cout << a << " is negative." << std::endl; | ||
else if (a == 0) | ||
{ | ||
std::cout << a << " is zero." << std::endl; | ||
} | ||
else if (a < 10) | ||
std::cout << a << " is positive but lower than 10." << std::endl; | ||
else if (a < 20) | ||
std::cout << a << " is in the interval [10, 20[." << std::endl; | ||
else | ||
{ | ||
std::cout << a << "is greater than 20." << std::endl; | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice that: | ||
* `else if` and `else` syntax is the same as the one for `if` and you may choose a single statement or a block in each branch of the condition. | ||
* As soon as one condition is fulfilled, the execution of the condition blocks ends. I therefore didn't have to spell out in the (`a < 20`) case that `a` had to be greater than 10. | ||
%% Cell type:markdown id: tags: | ||
### The ternary operator | ||
%% Cell type:markdown id: tags: | ||
C++ has inherited from C the so-called ternary operator, which is basically an if/else case packed in one line: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 5 }; | ||
std::cout << "Is " << i << " even? -> " << (i % 2 == 0 ? "true" : "false") << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
It is really the same as: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 5 }; | ||
std::cout << "Is " << i << " even? -> "; | ||
if (i % 2 == 0) | ||
std::cout << "true" << std::endl; | ||
else | ||
std::cout << "false" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
There are nonetheless some cases in which a ternary operator performs a task that is not otherwise reachable by an if/else statement; please consider for instance the initialisation of a `const` variable: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int i { 5 }; | ||
const int is_strictly_positive = (i > 0 ? 1 : 0); | ||
std::cout << is_strictly_positive << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
With if/else this doesn't work: both ways to do so that might come to mind are flawed: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int i { 5 }; | ||
const int is_strictly_positive; | ||
if (i > 0) | ||
is_strictly_positive = 1; // COMPILATION ERROR: can't assign to const value! | ||
// ... | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int i { 5 }; | ||
if (i > 0) | ||
const int is_strictly_positive = 1; | ||
else | ||
const int is_strictly_positive = 0; | ||
std::cout << is_strictly_positive << std::endl; // COMPILATION ERROR: is_strictly_positive not | ||
// in the current scope. | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `switch` statement | ||
Very briefly: there is also a `switch` statement that can be used when: | ||
* The variable is an integer, an enum (see [next notebook](./3-Types.ipynb#Enumerations)) or might be convertible into one of those. | ||
* The relationship considered is an equality. | ||
I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the vastly more powerful `switch` present in other languages...) | ||
%% Cell type:markdown id: tags: | ||
## Logical operators | ||
A condition might be an amalgation of several conditions. The way to glue it is to use logical operators. | ||
The operators are: | ||
* `&&` for the **and** operator. | ||
* `||` for the **or** operator. | ||
* `!` for the **not** operator. | ||
Actually there are so-called __alternative representations__ using the english name directly for each of them but their usage is not widely spread and not advised from a stylistic standpoint (many StackOverflow threads debate on this topic...). | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
int a { 2 }; | ||
int b { 3 }; | ||
if (a == 2 || b == 5) | ||
std::cout << "Ok: first condition is true." << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (a == 2 or b == 5) | ||
std::cout << "Same as above illustrating the alternative representation." << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (a == 2 && b == 5) | ||
std::cout << "Not printed: one condition is false." << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (!(a < 0)) | ||
std::cout << "Ok: a < 0 is false so the `not` operator returns true." << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
You may combine several of them in a more complex condition. `and` takes precedence over `or`, but anyway it's usually better to disambiguate using parenthesis: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (a == 5 || a == 2 && b == 3) | ||
std::cout << "(a == 5) and (a == 2 && b == 3) are evaluated separately and the latter is true so this text is printed" << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if ((a == 5) || (a == 2 && b == 3)) | ||
std::cout << "Same but easier to grasp." << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first. | ||
Please notice this means these operators are **not commutative**! | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
int a { 2 }; | ||
int b { 3 }; | ||
if (a < 0 && b++ == 3) | ||
; | ||
std::cout << "b was not incremented!: " << b << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
The story is different if the ordering of the conditions to test is inverted: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (b++ == 3 && a < 0) | ||
; | ||
std::cout << "b was incremented: " << b << | ||
" (as first operation is true second one is evaluated)." << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice it's true **only for built-in operators**: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands. | ||
%% Cell type:markdown id: tags: | ||
## Loops | ||
%% Cell type:markdown id: tags: | ||
### `while` loop | ||
%% Cell type:markdown id: tags: | ||
The `while` instruction allows you to execute a block of instructions in a loop | ||
as long as a condition is true. The condition is checked **before** each | ||
iteration. | ||
The rules about the instructions belonging to the loop are exactly the same as the ones described for `if` statements. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { }; | ||
while (a++ < 5) | ||
std::cout << a << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
If the condition is never true in the first place, we never go inside the loop: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { }; | ||
while (a-- < 0) | ||
std::cout << a << std::endl; | ||
std::cout << "Done!" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `do`...`while` loop | ||
%% Cell type:markdown id: tags: | ||
The `do` instruction allows you to execute a block of instructions in a loop as long as | ||
that a condition is true. The condition is verified **after** each | ||
iteration. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { }; | ||
do | ||
{ | ||
std::cout << a << std::endl; | ||
} while (a++ < 0); // there is a semicolon here. | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `for` loop | ||
%% Cell type:markdown id: tags: | ||
#### Historical `for` loop | ||
%% Cell type:markdown id: tags: | ||
The historical `for` instruction allows you to execute a block of instructions as long as a | ||
condition is true; the difference with the `while` loop is that there are fields explicitly detailing: | ||
* The initial situation. | ||
* The condition to check at the end of each loop. | ||
* What should be changed in next loop if one is called for. | ||
Syntax is: | ||
for (_initial situation_ ; _end loop condition_ ; *evolution for next loop*) | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
for (int i = 0; i < 5; ++i) | ||
std::cout << i << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Any of these fields might be left empty: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { }; // i might also be declared and initialized outside the block | ||
for ( ; i < 5; ) // completely equivalent to `while(i < 5)` | ||
{ | ||
++i; // i might also be modified within the block | ||
std::cout << i << std::endl; | ||
} | ||
std::cout << "`for` loop stopped for i = " << i << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
#### New `for` loop | ||
%% Cell type:markdown id: tags: | ||
The `for`syntax we just saw is still very useful, but C++ 11 introduced a new syntax when for instance you want to iterate through all the items in a container, clearly taking inspiration from syntax present in languages such as Python: | ||
for (_type_ _element_ : *container*) | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
#include <vector> // we'll present this one more in detail later | ||
{ | ||
std::vector<int> v { 2, 3, 5, 7 }; | ||
for (int item : v) | ||
std::cout << item << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
It might not seem much, but just for the record doing the same before C++ 11 was not for the faint of heart...: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
#include <vector> // we'll present this one more in detail later | ||
{ | ||
std::vector<int> v { 2, 3, 5, 7 }; // and we're cheating here: C++ 03 syntax was here much worse as well... | ||
for (std::vector<int>::const_iterator it = v.cbegin(), | ||
end = v.cend(); | ||
it != end; | ||
++it) | ||
{ | ||
std::cout << *it << std::endl; | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### continue, break and infinite loop | ||
%% Cell type:markdown id: tags: | ||
A danger with a loop is to make it infinite: you have to make sure an exit way is foreseen: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
// WARNING: if you run this you will have to restart your kernel! (in Kernel menu) | ||
{ | ||
int i { 2 }; | ||
while (i > 0) // condition that is true for quite a long time... | ||
std::cout << ++i << " "; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
The best is to write a palatable condition to end it, but when some loops become increasingly complex you may have to resort to `break`. Be aware it is slightly frowned upon by some programmers, for the very same reasons [goto](https://en.wikipedia.org/wiki/Goto) instructions are avoided. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 2 }; | ||
while (i > 0) | ||
{ | ||
std::cout << i++ << " "; | ||
if (i > 20) | ||
break; | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
In this trivial case writing the condition more properly would be of course much better: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 2 }; | ||
while (i > 0 && i <= 20) | ||
std::cout << i++ << " "; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
but honestly in more complex cases `break` can help keep the code more readable. | ||
`continue` is related: it is useful when in some conditions you want to skip the rest of the current loop iteration to go directly to the next: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
for (int i = 2; i < 20; ++i) | ||
{ | ||
if (i == 2) | ||
{ | ||
std::cout << i << " is even and prime (hello 2!)." << std::endl; | ||
continue; | ||
} | ||
if (i % 2 == 0) | ||
{ | ||
std::cout << i << " is even." << std::endl; | ||
continue; // goes directly at the condition checking step in the loop, | ||
// skipping the remaining code below. | ||
} | ||
std::cout << i << " is odd"; | ||
bool is_prime = true; | ||
for (int j = 2; j < i / 2; ++j) | ||
{ | ||
if (i % j == 0) // % returns the remainder of the division | ||
{ | ||
is_prime = false; | ||
break; // this break cuts the inner loop 'for (int j = 1; j < i / 2; ++j)' | ||
} | ||
} | ||
std::cout << (is_prime ? " and prime." : ".") << std::endl; // the ternary operator | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Of course, in a trivial example like this one we could have written it much more cleanly without any `continue`, but in more complex cases it is really handful to use it: not using it could lead to code much more complicated to understand, and you really should always strive for code that is the most expressive for a reader. | ||
%% Cell type:markdown id: tags: | ||
### So which loop should I use? | ||
%% Cell type:markdown id: tags: | ||
Whichever you want in fact! | ||
They are mostly interchangeable: | ||
* `while` and (historical) `for` are completely interchangeable, as: | ||
- A `while` loop is exactly like a `for ` loop with only the middle term. | ||
- You can transform a `for` loop into a `while` one: putting the first term before the loop and the third one inside the loop to do so. | ||
* `do..while` behaves slightly differently, but you can always mimic the behaviour with another type of loop. | ||
Lots of programming language define these guys (at least `for` and `while`) so it's useful to know about them, but you can choose one and stick with it as well. | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Conditions and loops](./2-Conditions-and-loops.ipynb) | ||
%% Cell type:markdown id: tags: | ||
## Conditions | ||
%% Cell type:markdown id: tags: | ||
### `if` condition followed by a single statement | ||
%% Cell type:markdown id: tags: | ||
In C++, a condition is the `if` command followed by a condition in parenthesis `()`. The single instruction (that ends at the next `;`) _or_ the block (in braces `{}`) that follows is then executed only if the condition is true. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a > 0) | ||
std::cout << a << " is greater than 0" << std::endl; | ||
if (a < 0) | ||
std::cout << "This line won't be executed so nothing will be printed." << std::endl; | ||
std::cout << "But this line will be printed: without braces `{}` only the first instruction depends " | ||
"on the condition!" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Of course, the precedent code is embarrassing but is due to the poor indenting used: you have to remember that in C++ indenting is just for the programmer: it is much easier to read if a program is properly indented, but it doesn't matter from the compiler standpoint! | ||
Our case above would be much clearer however with a more logical indenting and spacing: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a < 0) | ||
std::cout << "This line won't be executed so nothing will be printed." << std::endl; | ||
std::cout << "It's much clearer now that this line is not encompassed by the condition!" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `if` condition followed by a block | ||
And if you need several lines to be encompassed by the condition, just use braces! | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a < 0) | ||
{ | ||
std::cout << "Should not be printed: the condition is false!" << std::endl; | ||
++a; // won't be executed: the condition is false | ||
} | ||
std::cout << "a was not modified and is still 2: " << a << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### No semicolon at the end of the `if` condition | ||
__BEWARE__: do not put a `;` at the end of an `if` statement! If you do so, the statement executed if the condition is true is the empty statement `;` which does nothing... The risk is rather mitigated: any compiler worth its salt will warn you if you do this mistake. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int a { 2 }; | ||
if (a == 0); | ||
std::cout << "Will be printed: the statement after the condition is ';', which does nothing..." << std::endl; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int a { 2 }; | ||
if (a == 2) | ||
; // putting the semicolon in a different line silences the warning; there are legitimate cases in | ||
// which it's useful to do so and the risk is | ||
// slim that this was written by mistake. | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### if ... else if ... else | ||
It is rather usual to foreseen two (or more...) courses of action depending on the results of one or several conditions. The syntax in this case is the following: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { 2 }; | ||
if (a < 0) | ||
std::cout << a << " is negative." << std::endl; | ||
else if (a == 0) | ||
{ | ||
std::cout << a << " is zero." << std::endl; | ||
} | ||
else if (a < 10) | ||
std::cout << a << " is positive but lower than 10." << std::endl; | ||
else if (a < 20) | ||
std::cout << a << " is in the interval [10, 20[." << std::endl; | ||
else | ||
{ | ||
std::cout << a << "is greater than 20." << std::endl; | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice that: | ||
* `else if` and `else` syntax is the same as the one for `if` and you may choose a single statement or a block in each branch of the condition. | ||
* As soon as one condition is fulfilled, the execution of the condition blocks ends. I therefore didn't have to spell out in the (`a < 20`) case that `a` had to be greater than 10. | ||
%% Cell type:markdown id: tags: | ||
### The ternary operator | ||
%% Cell type:markdown id: tags: | ||
C++ has inherited from C the so-called ternary operator, which is basically an if/else case packed in one line: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 5 }; | ||
std::cout << "Is " << i << " even? -> " << (i % 2 == 0 ? "true" : "false") << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
It is really the same as: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 5 }; | ||
std::cout << "Is " << i << " even? -> "; | ||
if (i % 2 == 0) | ||
std::cout << "true" << std::endl; | ||
else | ||
std::cout << "false" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
There are nonetheless some cases in which a ternary operator performs a task that is not otherwise reachable by an if/else statement; please consider for instance the initialisation of a `const` variable: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int i { 5 }; | ||
const int is_strictly_positive = (i > 0 ? 1 : 0); | ||
std::cout << is_strictly_positive << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
With if/else this doesn't work: both ways to do so that might come to mind are flawed: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int i { 5 }; | ||
const int is_strictly_positive; | ||
if (i > 0) | ||
is_strictly_positive = 1; // COMPILATION ERROR: can't assign to const value! | ||
// ... | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
int i { 5 }; | ||
if (i > 0) | ||
const int is_strictly_positive = 1; | ||
else | ||
const int is_strictly_positive = 0; | ||
std::cout << is_strictly_positive << std::endl; // COMPILATION ERROR: is_strictly_positive not | ||
// in the current scope. | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `switch` statement | ||
Very briefly: there is also a `switch` statement that can be used when: | ||
* The variable is an integer, an enum (see [next notebook](./3-Types.ipynb#Enumerations)) or might be convertible into one of those. | ||
* The relationship considered is an equality. | ||
I present it quickly in [appendix](../7-Appendix/Switch.ipynb) but we do not have yet seen all the elements needed to explain its interest (which remains fairly limited compared to the vastly more powerful `switch` present in other languages...) | ||
%% Cell type:markdown id: tags: | ||
## Logical operators | ||
A condition might be an amalgation of several conditions. The way to glue it is to use logical operators. | ||
The operators are: | ||
* `&&` for the **and** operator. | ||
* `||` for the **or** operator. | ||
* `!` for the **not** operator. | ||
Actually there are so-called __alternative representations__ using the english name directly for each of them but their usage is not widely spread and not advised from a stylistic standpoint (many StackOverflow threads debate on this topic...). | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
int a { 2 }; | ||
int b { 3 }; | ||
if (a == 2 || b == 5) | ||
std::cout << "Ok: first condition is true." << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (a == 2 or b == 5) | ||
std::cout << "Same as above illustrating the alternative representation." << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (a == 2 && b == 5) | ||
std::cout << "Not printed: one condition is false." << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (!(a < 0)) | ||
std::cout << "Ok: a < 0 is false so the `not` operator returns true." << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
You may combine several of them in a more complex condition. `and` takes precedence over `or`, but anyway it's usually better to disambiguate using parenthesis: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (a == 5 || a == 2 && b == 3) | ||
std::cout << "(a == 5) and (a == 2 && b == 3) are evaluated separately and the latter is true so this text is printed" << std::endl; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if ((a == 5) || (a == 2 && b == 3)) | ||
std::cout << "Same but easier to grasp." << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Builtin operators `&&` and `||` perform short-circuit evaluation: they do not evaluate the second operand if the result is known after evaluating the first. | ||
Please notice this means these operators are **not commutative**! | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
int a { 2 }; | ||
int b { 3 }; | ||
if (a < 0 && b++ == 3) | ||
; | ||
std::cout << "b was not incremented!: " << b << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
The story is different if the ordering of the conditions to test is inverted: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
if (b++ == 3 && a < 0) | ||
; | ||
std::cout << "b was incremented: " << b << | ||
" (as first operation is true second one is evaluated)." << std::endl; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice it's true **only for built-in operators**: later in this tutorial we will deal with operators overloading, and such operators always evaluate both operands. | ||
%% Cell type:markdown id: tags: | ||
## Loops | ||
%% Cell type:markdown id: tags: | ||
### `while` loop | ||
%% Cell type:markdown id: tags: | ||
The `while` instruction allows you to execute a block of instructions in a loop | ||
as long as a condition is true. The condition is checked **before** each | ||
iteration. | ||
The rules about the instructions belonging to the loop are exactly the same as the ones described for `if` statements. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { }; | ||
while (a++ < 5) | ||
std::cout << a << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
If the condition is never true in the first place, we never go inside the loop: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { }; | ||
while (a-- < 0) | ||
std::cout << a << std::endl; | ||
std::cout << "Done!" << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `do`...`while` loop | ||
%% Cell type:markdown id: tags: | ||
The `do` instruction allows you to execute a block of instructions in a loop as long as | ||
that a condition is true. The condition is verified **after** each | ||
iteration. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int a { }; | ||
do | ||
{ | ||
std::cout << a << std::endl; | ||
} while (a++ < 0); // there is a semicolon here. | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `for` loop | ||
%% Cell type:markdown id: tags: | ||
#### Historical `for` loop | ||
%% Cell type:markdown id: tags: | ||
The historical `for` instruction allows you to execute a block of instructions as long as a | ||
condition is true; the difference with the `while` loop is that there are fields explicitly detailing: | ||
* The initial situation. | ||
* The condition to check at the end of each loop. | ||
* What should be changed in next loop if one is called for. | ||
Syntax is: | ||
for (_initial situation_ ; _end loop condition_ ; *evolution for next loop*) | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
for (int i = 0; i < 5; ++i) | ||
std::cout << i << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Any of these fields might be left empty: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { }; // i might also be declared and initialized outside the block | ||
for ( ; i < 5; ) // completely equivalent to `while(i < 5)` | ||
{ | ||
++i; // i might also be modified within the block | ||
std::cout << i << std::endl; | ||
} | ||
std::cout << "`for` loop stopped for i = " << i << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
#### New `for` loop | ||
%% Cell type:markdown id: tags: | ||
The `for`syntax we just saw is still very useful, but C++ 11 introduced a new syntax when for instance you want to iterate through all the items in a container, clearly taking inspiration from syntax present in languages such as Python: | ||
for (_type_ _element_ : *container*) | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
#include <vector> // we'll present this one more in detail later | ||
{ | ||
std::vector<int> v { 2, 3, 5, 7 }; | ||
for (int item : v) | ||
std::cout << item << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
It might not seem much, but just for the record doing the same before C++ 11 was not for the faint of heart...: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
#include <vector> // we'll present this one more in detail later | ||
{ | ||
std::vector<int> v { 2, 3, 5, 7 }; // and we're cheating here: C++ 03 syntax was here much worse as well... | ||
for (std::vector<int>::const_iterator it = v.cbegin(), | ||
end = v.cend(); | ||
it != end; | ||
++it) | ||
{ | ||
std::cout << *it << std::endl; | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### continue, break and infinite loop | ||
%% Cell type:markdown id: tags: | ||
A danger with a loop is to make it infinite: you have to make sure an exit way is foreseen: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
// WARNING: if you run this you will have to restart your kernel! (in Kernel menu) | ||
{ | ||
int i { 2 }; | ||
while (i > 0) // condition that is true for quite a long time... | ||
std::cout << ++i << " "; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
The best is to write a palatable condition to end it, but when some loops become increasingly complex you may have to resort to `break`. Be aware it is slightly frowned upon by some programmers, for the very same reasons [goto](https://en.wikipedia.org/wiki/Goto) instructions are avoided. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 2 }; | ||
while (i > 0) | ||
{ | ||
std::cout << i++ << " "; | ||
if (i > 20) | ||
break; | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
In this trivial case writing the condition more properly would be of course much better: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
int i { 2 }; | ||
while (i > 0 && i <= 20) | ||
std::cout << i++ << " "; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
but honestly in more complex cases `break` can help keep the code more readable. | ||
`continue` is related: it is useful when in some conditions you want to skip the rest of the current loop iteration to go directly to the next: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
for (int i = 2; i < 20; ++i) | ||
{ | ||
if (i == 2) | ||
{ | ||
std::cout << i << " is even and prime (hello 2!)." << std::endl; | ||
continue; | ||
} | ||
if (i % 2 == 0) | ||
{ | ||
std::cout << i << " is even." << std::endl; | ||
continue; // goes directly at the condition checking step in the loop, | ||
// skipping the remaining code below. | ||
} | ||
std::cout << i << " is odd"; | ||
bool is_prime = true; | ||
for (int j = 2; j < i / 2; ++j) | ||
{ | ||
if (i % j == 0) // % returns the remainder of the division | ||
{ | ||
is_prime = false; | ||
break; // this break cuts the inner loop 'for (int j = 1; j < i / 2; ++j)' | ||
} | ||
} | ||
std::cout << (is_prime ? " and prime." : ".") << std::endl; // the ternary operator | ||
} | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Of course, in a trivial example like this one we could have written it much more cleanly without any `continue`, but in more complex cases it is really handful to use it: not using it could lead to code much more complicated to understand, and you really should always strive for code that is the most expressive for a reader. | ||
%% Cell type:markdown id: tags: | ||
### So which loop should I use? | ||
%% Cell type:markdown id: tags: | ||
Whichever you want in fact! | ||
They are mostly interchangeable: | ||
* `while` and (historical) `for` are completely interchangeable, as: | ||
- A `while` loop is exactly like a `for ` loop with only the middle term. | ||
- You can transform a `for` loop into a `while` one: putting the first term before the loop and the third one inside the loop to do so. | ||
* `do..while` behaves slightly differently, but you can always mimic the behaviour with another type of loop. | ||
Lots of programming language define these guys (at least `for` and `while`) so it's useful to know about them, but you can choose one and stick with it as well. | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 2](./4b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 2: Adding a function__ | ||
Introduce in the previous program a function called `DisplayPowerOf2Approx()` which takes as argument the actual value to be approximated. Modify the main program to call this function with values 0.65 and 0.35. The main program must be: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
DisplayPowerOf2Approx(.65); | ||
DisplayPowerOf2Approx(.35); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
0.65 ~ 1 / 2^1 | ||
0.65 ~ 3 / 2^2 | ||
0.65 ~ 5 / 2^3 | ||
0.65 ~ 10 / 2^4 | ||
0.65 ~ 21 / 2^5 | ||
0.65 ~ 42 / 2^6 | ||
0.65 ~ 83 / 2^7 | ||
0.65 ~ 166 / 2^8 | ||
0.35 ~ 1 / 2^1 | ||
0.35 ~ 1 / 2^2 | ||
0.35 ~ 3 / 2^3 | ||
0.35 ~ 6 / 2^4 | ||
0.35 ~ 11 / 2^5 | ||
0.35 ~ 22 / 2^6 | ||
0.35 ~ 45 / 2^7 | ||
0.35 ~ 90 / 2^8 | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 3: Compute the approximation__ | ||
Add in the `DisplayPowerOf2Approx()` function the display of the approximate value, which is calculated by dividing each numerator by the power of two associated. Be careful, dividing an integer by an integer returns an integer: for instance 3 / 4 is 0. A division returns a real if one of the terms is real: 3. / 4 or `static_cast<double>`(3) / 4 is 0.75. | ||
We will modify the display slightly so that the output looks like: | ||
``` | ||
0.65 ~ 0.5 (1 / 2^1) | ||
0.65 ~ 0.75 (3 / 2^2) | ||
0.65 ~ 0.625 (5 / 2^3) | ||
0.65 ~ 0.625 (10 / 2^4) | ||
0.65 ~ 0.65625 (21 / 2^5) | ||
0.65 ~ 0.65625 (42 / 2^6) | ||
0.65 ~ 0.648438 (83 / 2^7) | ||
0.65 ~ 0.648438 (166 / 2^8) | ||
0.35 ~ 0.5 (1 / 2^1) | ||
0.35 ~ 0.25 (1 / 2^2) | ||
0.35 ~ 0.375 (3 / 2^3) | ||
0.35 ~ 0.375 (6 / 2^4) | ||
0.35 ~ 0.34375 (11 / 2^5) | ||
0.35 ~ 0.34375 (22 / 2^6) | ||
0.35 ~ 0.351562 (45 / 2^7) | ||
0.35 ~ 0.351562 (90 / 2^8) | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 4: Search for the best approximation for a given maximum numerator.__ | ||
The larger the numerator and the exponent to the denominator, the more accurate the approximation. | ||
In `DisplayPowerOf2Approx()`, modify the loop so that it looks for the best numerator / exponent pair, without the numerator exceeding a certain maximum value, passed as an argument to the function. | ||
Keep the display only for this best solution, and add in this display the value of the maximum allowed numerator. | ||
Use the following `main()` function to check it works: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
DisplayPowerOf2Approx(15, 0.65); | ||
DisplayPowerOf2Approx(255, 0.65); | ||
DisplayPowerOf2Approx(15, 0.35); | ||
DisplayPowerOf2Approx(255, 0.35); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Expected result is something like: | ||
``` | ||
[With numerator < 15]: 0.65 ~ 0.625 (10 / 2^4) | ||
[With numerator < 255]: 0.65 ~ 0.648438 (166 / 2^8) | ||
[With numerator < 15]: 0.35 ~ 0.34375 (11 / 2^5) | ||
[With numerator < 255]: 0.35 ~ 0.349609 (179 / 2^9) | ||
``` | ||
%% Cell type:markdown id: tags: | ||
%% Cell type:markdown id: tags: | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 5: Computation of the maximum numerator as a function of the number of bits__ | ||
The highest usable value for the numerator depends on the number of bits used to represent this integer. | ||
In `DisplayPowerOf2Approx()` arguments, replace the argument designating the maximum numerator with an argument designating the maximum number of bits and correct the body of the function accordingly, using the `MaxInt()` function given below. | ||
On display, replace the maximum numerator with the number of bits. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
// Declaration | ||
/*! | ||
* \brief Maximum integer that might be represented with `nbits` bits. | ||
* | ||
* \param[in] nbits Number of bits available. | ||
* | ||
* \return Biggest integer that may be represented. | ||
*/ | ||
int MaxInt(int nbits); | ||
// Definition | ||
int MaxInt(int nbits) | ||
{ | ||
return (TimesPowerOf2(1, nbits) - 1); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
**WARNING:** If you do not separate declaration and definition explicitly, `MaxInt()` must be located _before_ `DisplayPowerOf2Approx()`. | ||
Use the following `main()` function: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.65); | ||
std::cout << std::endl; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.35); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 6: moving display to a new intermediate function__ | ||
Currently, `DisplayPowerOf2Approx()` is in charge of two different operations: | ||
* Computing the values. | ||
* Displaying them on screen. | ||
It is often advisable to give one main functionality to a given function; we will therefore separate here the computation from the display. | ||
Write a new function named `ComputePowerOf2Approx()` that will be called in `DisplayPowerOf2Approx()`. | ||
This new function should: | ||
* Return the floating point approximation. | ||
* Return (with reference parameters) the numerator and the exponent (that are displayed on screen). | ||
Output should remain the same! | ||
**WARNING:** If you do not separate declaration and definition explicitly, `ComputePowerOf2Approx()` must be located _before_ `DisplayPowerOf2Approx()`. | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 7: adding error in display__ | ||
Add in `DisplayPowerOf2Approx()` the relative error of the approximation, computed by | ||
``` | ||
|number - approx| / number | ||
``` | ||
presented as a percentage and rounded to the nearest integer. | ||
Absolute value may be computed with `std::fabs`, which is in header file `cmath`. | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 2](./4b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 2: Adding a function__ | ||
Introduce in the previous program a function called `DisplayPowerOf2Approx()` which takes as argument the actual value to be approximated. Modify the main program to call this function with values 0.65 and 0.35. The main program must be: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
DisplayPowerOf2Approx(.65); | ||
DisplayPowerOf2Approx(.35); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
0.65 ~ 1 / 2^1 | ||
0.65 ~ 3 / 2^2 | ||
0.65 ~ 5 / 2^3 | ||
0.65 ~ 10 / 2^4 | ||
0.65 ~ 21 / 2^5 | ||
0.65 ~ 42 / 2^6 | ||
0.65 ~ 83 / 2^7 | ||
0.65 ~ 166 / 2^8 | ||
0.35 ~ 1 / 2^1 | ||
0.35 ~ 1 / 2^2 | ||
0.35 ~ 3 / 2^3 | ||
0.35 ~ 6 / 2^4 | ||
0.35 ~ 11 / 2^5 | ||
0.35 ~ 22 / 2^6 | ||
0.35 ~ 45 / 2^7 | ||
0.35 ~ 90 / 2^8 | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 3: Compute the approximation__ | ||
Add in the `DisplayPowerOf2Approx()` function the display of the approximate value, which is calculated by dividing each numerator by the power of two associated. Be careful, dividing an integer by an integer returns an integer: for instance 3 / 4 is 0. A division returns a real if one of the terms is real: 3. / 4 or `static_cast<double>`(3) / 4 is 0.75. | ||
We will modify the display slightly so that the output looks like: | ||
``` | ||
0.65 ~ 0.5 (1 / 2^1) | ||
0.65 ~ 0.75 (3 / 2^2) | ||
0.65 ~ 0.625 (5 / 2^3) | ||
0.65 ~ 0.625 (10 / 2^4) | ||
0.65 ~ 0.65625 (21 / 2^5) | ||
0.65 ~ 0.65625 (42 / 2^6) | ||
0.65 ~ 0.648438 (83 / 2^7) | ||
0.65 ~ 0.648438 (166 / 2^8) | ||
0.35 ~ 0.5 (1 / 2^1) | ||
0.35 ~ 0.25 (1 / 2^2) | ||
0.35 ~ 0.375 (3 / 2^3) | ||
0.35 ~ 0.375 (6 / 2^4) | ||
0.35 ~ 0.34375 (11 / 2^5) | ||
0.35 ~ 0.34375 (22 / 2^6) | ||
0.35 ~ 0.351562 (45 / 2^7) | ||
0.35 ~ 0.351562 (90 / 2^8) | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 4: Search for the best approximation for a given maximum numerator.__ | ||
The larger the numerator and the exponent to the denominator, the more accurate the approximation. | ||
In `DisplayPowerOf2Approx()`, modify the loop so that it looks for the best numerator / exponent pair, without the numerator exceeding a certain maximum value, passed as an argument to the function. | ||
Keep the display only for this best solution, and add in this display the value of the maximum allowed numerator. | ||
Use the following `main()` function to check it works: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
DisplayPowerOf2Approx(15, 0.65); | ||
DisplayPowerOf2Approx(255, 0.65); | ||
DisplayPowerOf2Approx(15, 0.35); | ||
DisplayPowerOf2Approx(255, 0.35); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Expected result is something like: | ||
``` | ||
[With numerator < 15]: 0.65 ~ 0.625 (10 / 2^4) | ||
[With numerator < 255]: 0.65 ~ 0.648438 (166 / 2^8) | ||
[With numerator < 15]: 0.35 ~ 0.34375 (11 / 2^5) | ||
[With numerator < 255]: 0.35 ~ 0.349609 (179 / 2^9) | ||
``` | ||
%% Cell type:markdown id: tags: | ||
%% Cell type:markdown id: tags: | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 5: Computation of the maximum numerator as a function of the number of bits__ | ||
The highest usable value for the numerator depends on the number of bits used to represent this integer. | ||
In `DisplayPowerOf2Approx()` arguments, replace the argument designating the maximum numerator with an argument designating the maximum number of bits and correct the body of the function accordingly, using the `MaxInt()` function given below. | ||
On display, replace the maximum numerator with the number of bits. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
// Declaration | ||
/*! | ||
* \brief Maximum integer that might be represented with `nbits` bits. | ||
* | ||
* \param[in] nbits Number of bits available. | ||
* | ||
* \return Biggest integer that may be represented. | ||
*/ | ||
int MaxInt(int nbits); | ||
// Definition | ||
int MaxInt(int nbits) | ||
{ | ||
return (TimesPowerOf2(1, nbits) - 1); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
**WARNING:** If you do not separate declaration and definition explicitly, `MaxInt()` must be located _before_ `DisplayPowerOf2Approx()`. | ||
Use the following `main()` function: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.65); | ||
std::cout << std::endl; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.35); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 6: moving display to a new intermediate function__ | ||
Currently, `DisplayPowerOf2Approx()` is in charge of two different operations: | ||
* Computing the values. | ||
* Displaying them on screen. | ||
It is often advisable to give one main functionality to a given function; we will therefore separate here the computation from the display. | ||
Write a new function named `ComputePowerOf2Approx()` that will be called in `DisplayPowerOf2Approx()`. | ||
This new function should: | ||
* Return the floating point approximation. | ||
* Return (with reference parameters) the numerator and the exponent (that are displayed on screen). | ||
Output should remain the same! | ||
**WARNING:** If you do not separate declaration and definition explicitly, `ComputePowerOf2Approx()` must be located _before_ `DisplayPowerOf2Approx()`. | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 7: adding error in display__ | ||
Add in `DisplayPowerOf2Approx()` the relative error of the approximation, computed by | ||
``` | ||
|number - approx| / number | ||
``` | ||
presented as a percentage and rounded to the nearest integer. | ||
Absolute value may be computed with `std::fabs`, which is in header file `cmath`. | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 3](./7b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 8: Multiplication by an integer__ | ||
Write the `Multiply()` function that calculates the product of an approximated real by an integer coefficient and returns an integer. | ||
*Reminder*: a real is approximated by `numerator / 2^exponent`; your function should rely upon `ComputePowerOf2Approx()` and `TimesPowerOf2()` functions. | ||
The arguments of `Multiply()` are the maximum number of bits for approximation, the real and the integer coefficient. | ||
The new main will be: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.65) ; | ||
std::cout << std::endl; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.35) ; | ||
std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | ||
{ | ||
double exact = 0.65 * 3515; | ||
int rounded = RoundAsInt(exact); | ||
int approx = Multiply(nbits, 0.65, 3515); | ||
std::cout << "[With " << nbits << " bits]: 0.65 * 3515 = " | ||
<< rounded << " ~ " << approx << std::endl; | ||
} | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
_Expected result:_ | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 = 2285 ~ 1757 | ||
[With 2 bits]: 0.65 * 3515 = 2285 ~ 2636 | ||
[With 3 bits]: 0.65 * 3515 = 2285 ~ 2196 | ||
[With 4 bits]: 0.65 * 3515 = 2285 ~ 2196 | ||
[With 5 bits]: 0.65 * 3515 = 2285 ~ 2306 | ||
[With 6 bits]: 0.65 * 3515 = 2285 ~ 2306 | ||
[With 7 bits]: 0.65 * 3515 = 2285 ~ 2279 | ||
[With 8 bits]: 0.65 * 3515 = 2285 ~ 2279 | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 9: display sum of two Multiply__ | ||
Write a `DisplaySumOfMultiply` function which will write the computation of the sum of two approximated real numbers that have each been multiplied by a different integer coefficient. | ||
The function is expected to compute and display the following: | ||
$$ | ||
\displaystyle r_1 \cdot c_1 + r_2 \cdot c_2 \simeq (a_1 \cdot 2^{-b_1}) \cdot c_1 + (a_2 \cdot 2^{-b_2}) \cdot c_2 | ||
$$ | ||
This function will take 5 arguments: | ||
* The number of bits to use. | ||
* Two real values. | ||
* Their associated coefficients. | ||
New main will look like: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.65); | ||
std::cout << std::endl; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.35); | ||
std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | ||
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832 | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 10: print error in `DisplaySumOfMultiply()`__ | ||
Modify slightly the function defined above to add display of the error; we will express it over 1000 (see exercise 7 which was roughly the same!) | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### [optional] __EXERCISE 11: write in output file__ | ||
Modify the program so that the `DisplayPowerOf2Approx` and `DisplaySumOfMultiply` functions take an additional argument: the output stream to which the content should be written. | ||
The following `main()` which writes part of the outputs in a file should work: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
std::ofstream out("/tmp/approx_0.65.txt"); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(out, nbits, 0.65); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(std::cout, nbits, 0.35); | ||
std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | ||
DisplaySumOfMultiply(std::cout, nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832 | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
and `cat /tmp/approx_0.65.txt` should return: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### [optional] __EXERCISE 12: function pointers__ | ||
Create a `Loop()` function that takes as an argument : | ||
* The output stream | ||
* An initial number of bits | ||
* A final number of bits | ||
* An increment to be applied to the number of bits | ||
* A pointer to a function to be executed for each number of bits | ||
You will need the following intermediate function to be able to use them in `Loop()` (as a specific signature is expected): | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
// Declarations | ||
void Display_065(std::ostream& out, int nbits); | ||
void Display_035(std::ostream& out, int nbits); | ||
void Display_065_3515_035_4832(std::ostream& out, int nbits); | ||
// Definitions | ||
void Display_065(std::ostream& out, int nbits) | ||
{ | ||
DisplayPowerOf2Approx(out, nbits, 0.65); | ||
} | ||
void Display_035(std::ostream& out, int nbits) | ||
{ | ||
DisplayPowerOf2Approx(out, nbits, 0.35); | ||
} | ||
void Display_065_3515_035_4832(std::ostream& out, int nbits) | ||
{ | ||
DisplaySumOfMultiply(out, nbits, 0.65, 3515, 0.35, 4832); | ||
} | ||
// Main | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
std::ofstream out("/tmp/approx_0.65.txt"); | ||
Loop(out, 2, 8, 2, Display_065); | ||
Loop(std::cout, 2, 8, 2, Display_035); | ||
std::cout << std::endl; | ||
Loop(std::cout, 1, 8, 1, Display_065_3515_035_4832); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Procedural programming](./0-main.ipynb) - [Hands-on 3](./7b-hands-on.ipynb) | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 8: Multiplication by an integer__ | ||
Write the `Multiply()` function that calculates the product of an approximated real by an integer coefficient and returns an integer. | ||
*Reminder*: a real is approximated by `numerator / 2^exponent`; your function should rely upon `ComputePowerOf2Approx()` and `TimesPowerOf2()` functions. | ||
The arguments of `Multiply()` are the maximum number of bits for approximation, the real and the integer coefficient. | ||
The new main will be: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.65) ; | ||
std::cout << std::endl; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.35) ; | ||
std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | ||
{ | ||
double exact = 0.65 * 3515; | ||
int rounded = RoundAsInt(exact); | ||
int approx = Multiply(nbits, 0.65, 3515); | ||
std::cout << "[With " << nbits << " bits]: 0.65 * 3515 = " | ||
<< rounded << " ~ " << approx << std::endl; | ||
} | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
_Expected result:_ | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 = 2285 ~ 1757 | ||
[With 2 bits]: 0.65 * 3515 = 2285 ~ 2636 | ||
[With 3 bits]: 0.65 * 3515 = 2285 ~ 2196 | ||
[With 4 bits]: 0.65 * 3515 = 2285 ~ 2196 | ||
[With 5 bits]: 0.65 * 3515 = 2285 ~ 2306 | ||
[With 6 bits]: 0.65 * 3515 = 2285 ~ 2306 | ||
[With 7 bits]: 0.65 * 3515 = 2285 ~ 2279 | ||
[With 8 bits]: 0.65 * 3515 = 2285 ~ 2279 | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 9: display sum of two Multiply__ | ||
Write a `DisplaySumOfMultiply` function which will write the computation of the sum of two approximated real numbers that have each been multiplied by a different integer coefficient. | ||
The function is expected to compute and display the following: | ||
$$ | ||
\displaystyle r_1 \cdot c_1 + r_2 \cdot c_2 \simeq (a_1 \cdot 2^{-b_1}) \cdot c_1 + (a_2 \cdot 2^{-b_2}) \cdot c_2 | ||
$$ | ||
This function will take 5 arguments: | ||
* The number of bits to use. | ||
* Two real values. | ||
* Their associated coefficients. | ||
New main will look like: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.65); | ||
std::cout << std::endl; | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(nbits, 0.35); | ||
std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | ||
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832 | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 | ||
%% Cell type:markdown id: tags: | ||
### __EXERCISE 10: print error in `DisplaySumOfMultiply()`__ | ||
Modify slightly the function defined above to add display of the error; we will express it over 1000 (see exercise 7 which was roughly the same!) | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] | ||
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### [optional] __EXERCISE 11: write in output file__ | ||
Modify the program so that the `DisplayPowerOf2Approx` and `DisplaySumOfMultiply` functions take an additional argument: the output stream to which the content should be written. | ||
The following `main()` which writes part of the outputs in a file should work: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
std::ofstream out("/tmp/approx_0.65.txt"); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(out, nbits, 0.65); | ||
for (int nbits = 2; nbits <= 8; nbits += 2) | ||
DisplayPowerOf2Approx(std::cout, nbits, 0.35); | ||
std::cout << std::endl; | ||
for (int nbits = 1; nbits <= 8; ++nbits) | ||
DisplaySumOfMultiply(std::cout, nbits, 0.65, 3515, 0.35, 4832); // to compute 0.65 * 3515 + 0.35 * 4832 | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
*Expected result*: | ||
``` | ||
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] | ||
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] | ||
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] | ||
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] | ||
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] | ||
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] | ||
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] | ||
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] | ||
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] | ||
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] | ||
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] | ||
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
and `cat /tmp/approx_0.65.txt` should return: | ||
``` | ||
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] | ||
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] | ||
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] | ||
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### [optional] __EXERCISE 12: function pointers__ | ||
Create a `Loop()` function that takes as an argument : | ||
* The output stream | ||
* An initial number of bits | ||
* A final number of bits | ||
* An increment to be applied to the number of bits | ||
* A pointer to a function to be executed for each number of bits | ||
You will need the following intermediate function to be able to use them in `Loop()` (as a specific signature is expected): | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
// Declarations | ||
void Display_065(std::ostream& out, int nbits); | ||
void Display_035(std::ostream& out, int nbits); | ||
void Display_065_3515_035_4832(std::ostream& out, int nbits); | ||
// Definitions | ||
void Display_065(std::ostream& out, int nbits) | ||
{ | ||
DisplayPowerOf2Approx(out, nbits, 0.65); | ||
} | ||
void Display_035(std::ostream& out, int nbits) | ||
{ | ||
DisplayPowerOf2Approx(out, nbits, 0.35); | ||
} | ||
void Display_065_3515_035_4832(std::ostream& out, int nbits) | ||
{ | ||
DisplaySumOfMultiply(out, nbits, 0.65, 3515, 0.35, 4832); | ||
} | ||
// Main | ||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) | ||
{ | ||
std::ofstream out("/tmp/approx_0.65.txt"); | ||
Loop(out, 2, 8, 2, Display_065); | ||
Loop(std::cout, 2, 8, 2, Display_035); | ||
std::cout << std::endl; | ||
Loop(std::cout, 1, 8, 1, Display_065_3515_035_4832); | ||
return EXIT_SUCCESS; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Member functions](./2-Member-functions.ipynb) | ||
%% Cell type:markdown id: tags: | ||
## Member functions | ||
The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers). | ||
But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...) | ||
One of the idea that was missing with original C `struct` was the possibility to add as well member functions: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <cmath> | ||
struct Vector | ||
{ | ||
double x; | ||
double y; | ||
double z; | ||
void Init(double x, double y, double z) | ||
{ | ||
this->x = x; | ||
this->y = y; | ||
this->z = z; | ||
} | ||
double Norm() | ||
{ | ||
return std::sqrt(x * x + y * y + z * z); | ||
} | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector v; | ||
v.Init(5., 6., -4.2); | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Let's do a bit of taxonomy here: | ||
- `Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here. | ||
- **Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method. | ||
- **Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes. | ||
**WARNING**: In C++ you can't complete a class after the fact as you could for instance in Python. So all the methods and data attributes have to be declared within the `struct` brackets here; if you need to add something you will have to recompile the class. This means especially you can't add directly a member function to a class provided by a third party library; we'll see shortly the mechanism you may use instead to do your bidding. | ||
%% Cell type:markdown id: tags: | ||
## The `this` keyword | ||
The `this->` may have puzzled you: it is a keyword to refer to the current object (very akin to Python `self`). | ||
In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely. | ||
An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <cmath> | ||
struct Vector2 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
void Init(double x, double y, double z) | ||
{ | ||
x_ = x; // no need to `this` here as there is no ambiguity between data attribute name and parameter name | ||
y_ = y; | ||
z_ = z; | ||
} | ||
double Norm() | ||
{ | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector2 v; | ||
v.Init(5., 6., -4.2); | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
That is not to say you should forget altogether the `this` keyword: it might be necessary in some contexts (for templates for instance - see [later](../4-Templates/3-Syntax.ipynb)...) | ||
**Note:** another popular convention to name data attributes is to prefix with `m_` (e.g. `double m_x;` instead of `double x_` above). As most conventions, you should either use the one defined for the code on which you work or choose the one the more appropriate for you and stick with it if you're the decision maker on it. | ||
%% Cell type:markdown id: tags: | ||
## Separating declaration and definition | ||
We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have: | ||
- On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)): | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
struct Vector3 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
void Init(double x, double y, double z); | ||
double Norm(); | ||
}; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
- On another side the definition, usually in a source file which includes the header file: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector3::Init(double x, double y, double z) | ||
{ | ||
x_ = x; | ||
y_ = y; | ||
z_ = z; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
double Vector3::Norm() | ||
{ | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector3 v; | ||
v.Init(5., 6., -4.2); | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice the `::` syntax which specifies the class for which the implementation is provided. | ||
%% Cell type:markdown id: tags: | ||
## Const methods | ||
Are we happy here with what we have so far? Unfortunately, not quite... | ||
If we define a simple free function that print the norm of a `Vector3`: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
void PrintNorm(const Vector3& v) | ||
{ | ||
std::cout << v.Norm() << std::endl; // COMPILATION ERROR | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
... we see that doesn't compile. So what is happening? | ||
The issue here is that the function `PrintNorm` takes as argument a constant reference to a `Vector` object, and has to guarantee the underlying object is not modified in the process. A "patch" would be to define it without the const: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
void PrintNormNoConst(Vector3& v) // BAD IDEA! | ||
{ | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
Vector3 v; | ||
v.Init(5., 6., -4.2); | ||
PrintNormNoConst(v); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong. | ||
The sketchy "patch" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned. | ||
The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
struct Vector4 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
void Init(double x, double y, double z); | ||
double Norm() const; // notice the additional keyword! | ||
void DontPutConstEverywhere() const; | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector4::Init(double x, double y, double z) | ||
{ | ||
x_ = x; | ||
y_ = y; | ||
z_ = z; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
double Vector4::Norm() const | ||
{ | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice `const` needs to be specified both on declaration and on definition: if not provided in definition the signature of the method won't match and the compiler will yell. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
void PrintNorm(const Vector4& v) | ||
{ | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
{ | ||
Vector4 v; | ||
v.Init(5., 6., -4.2); | ||
PrintNorm(v); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Obviously, if we try to ignore a `const` keyword, the compiler will also yell (it is and **SHOULD BE** very good at this!): | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector4::DontPutConstEverywhere() const | ||
{ | ||
x_ = 0.; // ERROR! | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `mutable` keyword | ||
Tread with extreme caution here! Sometimes, you might want for a method to be mostly unable to modify the state of the class but you still need to modify one or more specific attribute. You may in this case use the `mutable` keyword when defining this attribute: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
struct Vector5 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
mutable unsigned int Nnorm_calls_; | ||
void Init(double x, double y, double z); | ||
double Norm() const; | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector5::Init(double x, double y, double z) | ||
{ | ||
x_ = x; | ||
y_ = y; | ||
z_ = z; | ||
Nnorm_calls_ = 0u; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
double Vector5::Norm() const | ||
{ | ||
++Nnorm_calls_; | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector5 v; | ||
v.Init(5., 6., -4.2); | ||
for (int i = 0; i < 5; ++i) | ||
v.Norm(); | ||
std::cout << "Method 'Norm()' was called " << v.Nnorm_calls_ << " times." << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
I must stress again that you should use this in a **last resort**! | ||
For my part, I have used this in only two contexts: | ||
* Using a work variable that would have been costly to reallocate at each call. This variable was always reset and used within the method that calls it and was not used to share a state between methods. | ||
* For mutexes when using shared memory parallelism. This is way out of the scope of this tutorial. | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |
%% Cell type:markdown id: tags: | ||
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Member functions](./2-Member-functions.ipynb) | ||
%% Cell type:markdown id: tags: | ||
## Member functions | ||
The struct we used previously would work the same in C code (with the exceptions of references: with a C compiler you would have to stick with pointers). | ||
But when Bjarne Stroustrup created the C++, its main idea was to extend these structs into full-fledged **classes** (to the extent that the working name of his language was *C with classes*...) | ||
One of the idea that was missing with original C `struct` was the possibility to add as well member functions: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <cmath> | ||
struct Vector | ||
{ | ||
double x; | ||
double y; | ||
double z; | ||
void Init(double x, double y, double z) | ||
{ | ||
this->x = x; | ||
this->y = y; | ||
this->z = z; | ||
} | ||
double Norm() | ||
{ | ||
return std::sqrt(x * x + y * y + z * z); | ||
} | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector v; | ||
v.Init(5., 6., -4.2); | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Let's do a bit of taxonomy here: | ||
- `Init()` and `Norm()` are called **member functions** or **methods**. The same remark concerning C++ purist I did for member variables may be applied here. | ||
- **Method** is used in other programming languages, but for some reason Julia creators used this exact term for an entirely different concept. So to put in a nutshell a C++ method is akin to a Python one but not to what Julia calls a method. | ||
- **Attributes** are in fact the data attributes AND the methods. It is however often used only to designate the data attributes. | ||
**WARNING**: In C++ you can't complete a class after the fact as you could for instance in Python. So all the methods and data attributes have to be declared within the `struct` brackets here; if you need to add something you will have to recompile the class. This means especially you can't add directly a member function to a class provided by a third party library; we'll see shortly the mechanism you may use instead to do your bidding. | ||
%% Cell type:markdown id: tags: | ||
## The `this` keyword | ||
The `this->` may have puzzled you: it is a keyword to refer to the current object (very akin to Python `self`). | ||
In most cases, it might be altogether removed; we have to put it explicitly here solely because we named the `Init` parameters with the same name as the data attribute. If not, we could have avoided to mention it completely. | ||
An usual convention is to suffix data attributes with a `_` (**be careful**, attributes prefixed with a `_` is reserved by the C++ standard); doing so remove the need to the explicit `this`: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <cmath> | ||
struct Vector2 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
void Init(double x, double y, double z) | ||
{ | ||
x_ = x; // no need to `this` here as there is no ambiguity between data attribute name and parameter name | ||
y_ = y; | ||
z_ = z; | ||
} | ||
double Norm() | ||
{ | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector2 v; | ||
v.Init(5., 6., -4.2); | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
That is not to say you should forget altogether the `this` keyword: it might be necessary in some contexts (for templates for instance - see [later](../4-Templates/3-Syntax.ipynb)...) | ||
**Note:** another popular convention to name data attributes is to prefix with `m_` (e.g. `double m_x;` instead of `double x_` above). As most conventions, you should either use the one defined for the code on which you work or choose the one the more appropriate for you and stick with it if you're the decision maker on it. | ||
%% Cell type:markdown id: tags: | ||
## Separating declaration and definition | ||
We have defined so far the method directly in the class declaration; which is not very clean. It is acceptable for a very short method as here, but for a more complex class and method it is better to separate explicitly both. In this case you will have: | ||
- On one side, usually in a header file (we'll see the file structure in real C++ code [later on](../6-InRealEnvironment/2-FileStructure.ipynb)): | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
struct Vector3 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
void Init(double x, double y, double z); | ||
double Norm(); | ||
}; | ||
``` | ||
%% Cell type:markdown id: tags: | ||
- On another side the definition, usually in a source file which includes the header file: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector3::Init(double x, double y, double z) | ||
{ | ||
x_ = x; | ||
y_ = y; | ||
z_ = z; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
double Vector3::Norm() | ||
{ | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector3 v; | ||
v.Init(5., 6., -4.2); | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice the `::` syntax which specifies the class for which the implementation is provided. | ||
%% Cell type:markdown id: tags: | ||
## Const methods | ||
Are we happy here with what we have so far? Unfortunately, not quite... | ||
If we define a simple free function that print the norm of a `Vector3`: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
void PrintNorm(const Vector3& v) | ||
{ | ||
std::cout << v.Norm() << std::endl; // COMPILATION ERROR | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
... we see that doesn't compile. So what is happening? | ||
The issue here is that the function `PrintNorm` takes as argument a constant reference to a `Vector` object, and has to guarantee the underlying object is not modified in the process. A "patch" would be to define it without the const: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
void PrintNormNoConst(Vector3& v) // BAD IDEA! | ||
{ | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
{ | ||
Vector3 v; | ||
v.Init(5., 6., -4.2); | ||
PrintNormNoConst(v); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Why is it such a poor idea? C++ is a compiled language, and this has its (many) pros and (many) cons. One of the advantages is to be able to leverage the compilation to detect at early time something is amiss. Here the compilation error is a good way to see we might be doing something wrong. | ||
The sketchy "patch" I provided would be akin to ignoring the `const` feature almost entirely whenever objects are concerned. | ||
The proper way to solve the issue is in fact quite the opposite: we may specify when writing a method that it is not allowed to modify the state of the object: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
struct Vector4 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
void Init(double x, double y, double z); | ||
double Norm() const; // notice the additional keyword! | ||
void DontPutConstEverywhere() const; | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector4::Init(double x, double y, double z) | ||
{ | ||
x_ = x; | ||
y_ = y; | ||
z_ = z; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
double Vector4::Norm() const | ||
{ | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Please notice `const` needs to be specified both on declaration and on definition: if not provided in definition the signature of the method won't match and the compiler will yell. | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
void PrintNorm(const Vector4& v) | ||
{ | ||
std::cout << v.Norm() << std::endl; | ||
} | ||
{ | ||
Vector4 v; | ||
v.Init(5., 6., -4.2); | ||
PrintNorm(v); | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
Obviously, if we try to ignore a `const` keyword, the compiler will also yell (it is and **SHOULD BE** very good at this!): | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector4::DontPutConstEverywhere() const | ||
{ | ||
x_ = 0.; // ERROR! | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
### `mutable` keyword | ||
Tread with extreme caution here! Sometimes, you might want for a method to be mostly unable to modify the state of the class but you still need to modify one or more specific attribute. You may in this case use the `mutable` keyword when defining this attribute: | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
struct Vector5 | ||
{ | ||
double x_; | ||
double y_; | ||
double z_; | ||
mutable unsigned int Nnorm_calls_; | ||
void Init(double x, double y, double z); | ||
double Norm() const; | ||
}; | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
void Vector5::Init(double x, double y, double z) | ||
{ | ||
x_ = x; | ||
y_ = y; | ||
z_ = z; | ||
Nnorm_calls_ = 0u; | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
double Vector5::Norm() const | ||
{ | ||
++Nnorm_calls_; | ||
return std::sqrt(x_ * x_ + y_ * y_ + z_ * z_); | ||
} | ||
``` | ||
%% Cell type:code id: tags: | ||
``` C++17 | ||
``` c++ | ||
#include <iostream> | ||
{ | ||
Vector5 v; | ||
v.Init(5., 6., -4.2); | ||
for (int i = 0; i < 5; ++i) | ||
v.Norm(); | ||
std::cout << "Method 'Norm()' was called " << v.Nnorm_calls_ << " times." << std::endl; | ||
} | ||
``` | ||
%% Cell type:markdown id: tags: | ||
I must stress again that you should use this in a **last resort**! | ||
For my part, I have used this in only two contexts: | ||
* Using a work variable that would have been costly to reallocate at each call. This variable was always reset and used within the method that calls it and was not used to share a state between methods. | ||
* For mutexes when using shared memory parallelism. This is way out of the scope of this tutorial. | ||
%% Cell type:markdown id: tags: | ||
[© Copyright](../COPYRIGHT.md) | ||
... | ... |