Commit f3fb9168 authored by Praetorius, Simon's avatar Praetorius, Simon
Browse files

section about includes and libraries added

parent d6b09aa6
......@@ -10,8 +10,8 @@ Often you do not always want to care about the actual datatype used in your prog
do this via `typedef`/`using`:
```c++
typedef <type> <alias_name>;
using <alias_name> = <type>; //< suggested
typedef <type> <alias_name>; // or...
using <alias_name> = <type>; // (suggested)
```
Afterwards, `<alias_name>` can be used like any other datatype:
......@@ -137,7 +137,7 @@ The individual variables in a struct are accessed via `.` operator, i.e.:
```c++
Vector x;
x.size = 10;
x.size = 10;
x.data = new real_t[ x.size ];
```
......@@ -229,9 +229,10 @@ Here, only a single pointer is supplied to the function instead of 200
---
# Advanced Datatypes
## Arrays of Struct
## Array of Structs
Like any other datatype, structs can also be allocated in the form of an array:
.pure-g[.pure-u-2-5[.padding-5[
```c++
struct Coord {
real_t x, y, z;
......@@ -239,38 +240,91 @@ struct Coord {
Coord coordinates[ 10 ];
```
]].pure-u-3-5[.padding-5[
```c++
#include <cmath>
...
for (std::size_t i = 0; i < 10; ++i)
{
coordinates[i].x = std::cos(i * 36*M_PI / 180.0);
coordinates[i].y = std::sin(i * 36*M_PI / 180.0);
coordinates[i].z = i / 10.0;
}
```
]]]
for fixed sized array or
for fixed sized array or...
---
# Advanced Datatypes
## Array of Structs
Like any other datatype, structs can also be allocated in the form of an array:
.pure-g[.pure-u-2-5[.padding-5[
```c++
Coord* coordinates = new Coord[ 10 ];
struct Coord {
real_t x, y, z;
};
Coord* coordinates = new Coord[10];
```
]].pure-u-3-5[.padding-5[
```c++
#include <cmath>
...
for (std::size_t i = 0; i < 10; ++i)
{
coordinates[i].x = std::cos(i * 36*M_PI / 180.0);
coordinates[i].y = std::sin(i * 36*M_PI / 180.0);
coordinates[i].z = i / 10.0;
}
```
]]]
using dynamic memory management.
... using dynamic memory management.
---
# Advanced Datatypes
## Arrays of Structs
The access to struct members then comes after addressing the array entry:
## Struct of Arrays
The storage can also be inverted. Instead of an array of structures (AoS) one can store a structure
of arrays (SoA):
.pure-g[.pure-u-2-5[.padding-5[
```c++
struct Coordinates {
real_t x[ 10 ];
real_t y[ 10 ];
real_t z[ 10 ];
};
Coordinates coordinates;
```
]].pure-u-3-5[.padding-5[
```c++
#include <cmath>
...
for (std::size_t i = 0; i < 10; ++i)
{
coordinates[i].x = std::cos( real_t(i) * 36.0 * M_PI / 180.0 );
coordinates[i].y = std::sin( real_t(i) * 36.0 * M_PI / 180.0 );
coordinates[i].z = real_t(i) / 10.0;
coordinates.x[i] = std::cos(i * 36*M_PI / 180.0);
coordinates.y[i] = std::sin(i * 36*M_PI / 180.0);
coordinates.z[i] = i / 10.0;
}
```
]]]
For different applications and usage/access pattern either the one or the other representation might be
more efficient or convenient.
See also https://en.wikipedia.org/wiki/AoS_and_SoA
---
# Advanced Datatypes
Structures can be nested, i.e., a struct can be a member of another struct:
## Example: Sparse Matrix in Coordinate Format
## Example1: Sparse Matrix in Coordinate Format
```c++
struct Triple {
std::size_t row;
......@@ -293,30 +347,30 @@ struct SparseMatrix {
---
# Advanced Datatypes
## Example: Sparse Matrix in Coordinate Format
Laplace 5-point stencil
## Example1: Sparse Matrix in Coordinate Format
1d Laplacian 3-point stencil
\[
A = \begin{pmatrix}
4 & -1 & & & & \\
-1 & 4 & -1 & & & \\
2 & -1 & & & & \\
-1 & 2 & -1 & & & \\
& -1 & \ddots & & & \\
& & -1 & 4 & -1 \\
& & &-1 & 4
& & -1 & 2 & -1 \\
& & &-1 & 2
\end{pmatrix}
\]
---
# Advanced Datatypes
## Example: Sparse Matrix in Coordinate Format
## Example1: Sparse Matrix in Coordinate Format
The nested structures can be initialized directly using nested curly braces:
```c++
SparseMatrix mat{10,10, {28, new Triple[28]}};
std::size_t idx = 0;
mat.nonzeros.data[idx++] = {0,0, 4.0};
mat.nonzeros.data[idx++] = {0,0, 2.0};
for (std::size_t i = 1; i < 10; ++i) {
mat.nonzeros.data[idx++] = {i,i, 4.0};
mat.nonzeros.data[idx++] = {i,i, 2.0};
mat.nonzeros.data[idx++] = {i-1,i, -1.0};
mat.nonzeros.data[idx++] = {i,i-1, -1.0};
}
......@@ -326,7 +380,7 @@ assert(idx == 28);
---
# Advanced Datatypes
## Example: Sparse Matrix in Coordinate Format
## Example1: Sparse Matrix in Coordinate Format
### Matrix-Vector product
```c++
void mat_vec(real_t const alpha,
......@@ -335,9 +389,9 @@ void mat_vec(real_t const alpha,
Vector& y)
{
for (std::size_t i = 0; i < A.nonzeros.size; ++i) {
auto const& entry = A.nonzeros[i];
auto const& [row,col,value] = A.nonzeros[i]; // structured binding also for structs
y[entry.row] += alpha * entry.value * x[entry.col];
y[ row ] += alpha * value * x[ col ];
}
}
```
......@@ -346,12 +400,12 @@ void mat_vec(real_t const alpha,
# Advanced Datatypes
## Example2: Sparse Matrix in Compressed Format
We can store sparse matrices even more memory efficient, with more locality. Three arrays:
We can store sparse matrices even with more locality, using three arrays:
- `indices`: stores column indices for all entries, sorted by row,
- `values`: stores all coefficients in same order as in `colind` and
- `values`: stores all coefficients in same order as in `indices` and
- `offset`: stores at `offset[i]` the position of the first value corresponding to row `i` in the arrays
`indices` and `values`. The last field, contains the number of nonzeros.
`indices` and `values`. The last field contains the number of nonzeros.
This format is known as the *compressed row storage* format.
......@@ -364,6 +418,8 @@ struct CRSMatrix {
};
```
See, e.g., Y. Saad: Iterative methods for sparse linear systems, Section 2.3 Storage Schemes
---
# Advanced Datatypes
......@@ -381,9 +437,9 @@ For the matrix
the corresponding source code is:
```c++
std::size_t offset[] = { 0, 2, 4, 7, 9 }; // accumulated nr of entries per row
std::size_t indices[] = { 0, 2, 1, 3, 0, 1, 2, 0, 3 };
real_t values[] = { 1, 3, 2, -1, -4, -1, 1, 1, 3 };
std::size_t offset[] = { 0, 2, 4, 7, 9 }; // accumulated num of entries per row
std::size_t indices[] = { 0, 2, 1, 3, 0, 1, 2, 0, 3 };
real_t values[] = { 1, 3, 2, -1, -4, -1, 1, 1, 3 };
CRSMatrix mat{4, 4, offset, indices, values};
```
......@@ -401,7 +457,7 @@ void mat_vec(real_t const alpha,
{
for (std::size_t i = 0; i < A.nrows; ++i)
{
real_t f = 0.0;
real_t f = 0.0;
std::size_t const lb = A.offset[ i ];
std::size_t const ub = A.offset[ i+1 ];
......@@ -411,4 +467,121 @@ void mat_vec(real_t const alpha,
y[ i ] += alpha * f;
}
}
```
\ No newline at end of file
```
---
# Advanced Datatypes
## Enumerations
A special datatype is available to define enumerations:
```c++
enum <enum_name> {
<name_1>, <name_2>, <name_3>,...
};
```
### Example:
```c++
enum Symmetry { unsymmetric, symmetric, hermitian };
Symmetry s;
if (s == symmetric) { ... }
```
Enumerations are handled as integer datatypes by C++. By default, the
members of an enumeration are numbered from `0` to `n-1`, e.g., `<name_1> = 0, <name_2> = 1`, etc..
---
# Advanced Datatypes
## Enumerations
One can also define the value of the enumeration members
explicitly:
```c++
enum Symmetry { unsymmetric = 4, symmetric = 17, hermitian = 42 };
```
Since enumerations are equivalent to integer types, they can also be
used in `switch` statements:
```c++
switch (s)
{
case symmetric: ...; break;
case hermitian: ...; break;
case unsymmetric:
default: ...;
}
```
---
# Advanced Datatypes
## Enumerations
Sometimes it is useful to specify the integer of enums explicitly
```c++
enum <enum_name> : <type> {
<name_1>, <name_2>, <name_3>,...
};
```
### Example:
```c++
enum Symmetry : unsigned short { unsymmetric, symmetric, hermitian };
```
---
# Advanced Datatypes
## Enumerations
By default, the enum values are introduced in the scope where the enum is defined. If there are
other variables/functions/... with the same name it could clash. It is thus recommended to introduce
a corresponding *named scope* for the enum values:
```c++
enum class <enum_name> [: <type>] {
<name_1>, <name_2>, ...
}
```
### Example:
```c++
enum class Symmetry { unsymmetric, symmetric, hermitian };
Symmetry s;
switch (s) {
case Symmetry::unsymmetric: ...;
}
```
---
# Advanced Datatypes
## Enumerations
By default, the enum values are introduced in the scope where the enum is defined. If there are
other variables/functions/... with the same name it could clash. It is thus recommended to introduce
a corresponding *named scope* for the enum values:
```c++
enum class <enum_name> [: <type>] {
<name_1>, <name_2>, ...
}
```
### Example:
```c++
enum class Symmetry { unsymmetric, symmetric, hermitian };
Symmetry s;
switch (s) {
using enum Symmetry; // [c++20]
case unsymmetric: ...;
}
```
......@@ -265,6 +265,22 @@ std::cout << p[4] << std::endl; // yields n[4]
The index operator `[i]` of a pointer `p` gives access to the `i`'th element of the array
starting at address `p`.
---
# Arrays and Dynamic Memory
## Pointer Arithmetic
Pointers can be "moved" in memory, i.e., they can point to the "next" or "previous" memory region.
Moving a pointer can be done by simple arithmetic operations with integers:
Let `<type>* p` be a pointer addressing data of type `<type>` and `i` and integer, then
- `p + i`, `i + p` points to the address `<p> + i*sizeof(<type>)`
- `p - i` points to the address `<p> - i*sizeof(<type>)`
- `p++`, `++p` is equivalent to `p = p+1`
- `p[i]` is equivalent to `*(p + i)`
---
# Arrays and Dynamic Memory
......
---
class: center, middle
# Modules and Namespaces
---
# Modules and Namespaces
## Header Files
Up to now, all source code was placed into one file. For
reasonable programs, this is not desirable, especially if functions are
reused by different programs.
Until C++20, we had no real module system in C++ like, e.g., Python or
Java, to group similar functions or types. Instead, **header**
files are used to make C++ entities known to different source files.
As you remember, functions can be used if they were previously declared
or implemented. By separating declaration and implementation into header
and source file:
.pure-g[.pure-u-1-2[.padding-5[
```c++
// header file: f.hh
void f (int n, double f);
```
]].pure-u-1-2[.padding-5[
```c++
// source file: f.cc
void f (int const n, double f)
{ ... }
```
]]]
the function can be reused by just **including** the header file.
---
# Modules and Namespaces
## Header Files
Including another file into the current source code is
performed by the `#include` directive:
```c++
#include "filename" // or
#include <filename>
```
The first version is usually used for files in the same project, whereas
the second version is for files from other projects, e.g., the c++ standard library
or an external library.
```c++
#include "f.hh" // contains declaration of "f"
int main ()
{
f(42, 3.1415926);
}
```
---
# Modules and Namespaces
## Header Files
In order to compile a source file that includes another header file, we have to tell the compiler
where to find these files:
1. With `#include <filename>` the compiler searches in all default system locations, internal compiler
locations, and all explicitly specified locations, see below.
2. With `#include "filename"` the compiler searches additionally in the same directory as the source
file that includes `filename`.
3. Search directories can be explicitly specified by the compiler option `-I<dir>`
---
# Modules and Namespaces
## Header Files
### Example
- The source file `mat_vec.cc` from the introductory example contains the line `#include <boost/numeric/mtl/mtl.hpp>`
- Thus, the compiler needs to know the directory `<dir>` that contains <br>`<dir>/boost/numeric/mtl/mtl.hpp`
- The corresponding library that provides this file is [MTL4](https://www.simunova.com/de/mtl4/)
- Download the source by using [subversion](https://subversion.apache.org/) into the directory `<libs>`
```bash
cd <libs>
svn checkout https://svn.simunova.com/svn/mtl4/trunk mtl4
```
- Compile the source file by
```bash
c++ -I<libs>/mtl4 mat_vec.cc -o mat_vec
```
---
# Modules and Namespaces
## Header Files
By convention, the filename suffix of the header file should be either
`.h` (like in C), `.hh`, `.hpp`, or `.hxx`.
The filename suffix for source files is, correspondingly
`.cc`, `.cpp`, or `.cxx`.
> .h3[Note:] C++ standard libraries are provided in header files without suffix. The functions
> imported from *C* are in files with the `c` prefix: `#include <cmath>` vs. `#include <math.h>`.
---
# Modules and Namespaces
## C++ Library
The C++ compiler comes with a set of standard include
files containing declarations of many functions:
[`<cstdlib>`](https://en.cppreference.com/w/cpp/header/cstdlib)
: standard C functions, e.g.,
.font-md[
- `std::exit`: stop program,
- `std::atoi` and `std::atof`: string to `int` and `double` conversion,
- `std::qsort`: sort arrays,
- `std::malloc` and `std::free`: C-style dynamic memory management,
]
[`<cmath>`](https://en.cppreference.com/w/cpp/header/cmath)
: mathematical functions, e.g.,
.font-md[
- `std::sqrt`: square root,
- `std::abs`: absolute value,
- `std::sin` and `std::cos`,
- `std::log`: natural logarithm
]
---
# Modules and Namespaces
## C++ Library
[`<cstdio>`](https://en.cppreference.com/w/cpp/header/cstdio)
: C-style IO functions, e.g.,
.font-md[
- `std::printf`: print variables to standard output,
- `std::fopen`, `std::fclose`, `std::fread` and `std::fwrite`: file IO
]
[`<cstring>`](https://en.cppreference.com/w/cpp/header/cstring)
: string functions, e.g.,
.font-md[
- `std::strlen`: string length,
- `std::strcat`: string concatenation,
- `std::strcmp`: string comparison,
- `std::strcpy`: string copy
]
[`<cctype>`](https://en.cppreference.com/w/cpp/header/cctype)
: character tests, e.g.,
.font-md[
- `std::isdigit`: test for digit,
- `std::islower`: test for lower case,
- `std::isspace`: test for white-space
]
etc.
: [`<cassert>`](https://en.cppreference.com/w/cpp/header/cassert),
[`<cerrno>`](https://en.cppreference.com/w/cpp/header/cerrno),
[`<cinttypes>`](https://en.cppreference.com/w/cpp/header/cinttypes),
[`<climits>`](https://en.cppreference.com/w/cpp/header/climits),
[`<ctime>`](https://en.cppreference.com/w/cpp/header/ctime).
---
# Modules and Namespaces
## C++ Library
Specific C++ functionality usually comes in the form of
the "*standard template library*". It is implemented via `class`es
(see later) and provided by the following header files:
`<iostream>`
: file input/output functions and classes,
`<vector>`
: dynamic containers similar to arrays,
`<valarray>`
: similar to `vector` but better suited for numerics,
`<limits>`
: functions for determining type limits, e.g. minimal or maximal
values,
`<map>`
: associative array, e.g. indices are arbitrary types,
`<list>`
: provides standard list and iterators,
`<complex>`
: provides complex datatype,
etc.
: The specific classes and their usage will be discussed later.
---
# Modules and Namespaces
## Libraries without Headers
BLAS or LAPACK are written in Fortran and no header files exist for C++. Therefore, we will have to
write them ourselves. Consider
```fortran
SUBROUTINE DGESVD( JOBU, JOBVT, M, N, A, LDA, S, U, LDU, VT, LDVT,
$ WORK, LWORK, INFO )
CHARACTER JOBU, JOBVT
INTEGER INFO, LDA, LDU, LDVT, LWORK, M, N
DOUBLE PRECISION A( LDA, * ), S( * ), U( LDU, * ),
$ VT( LDVT, * ), WORK( * )
```
To define a C++ function corresponding to the above Fortran function
the datatypes have to be mapped to C++ types. In Fortran, every
variable is provided as a **pointer**, hence:
<table>
<tr><td><code class="remark-inline-code">CHARACTER</td><td> \(\to\) <code class="remark-inline-code">char *</code></td></tr>
<tr><td><code class="remark-inline-code">INTEGER</td><td> \(\to\) <code