Vorlesungsseite: https://lec.inf.ethz.ch/math/informatik_cse/2024/
Vorlesungssckript: (provisorisch) [[infln.pdf]]
Prüfungsstoff: Vorlesungsinhalte, Handout, Übungen + Übungsstunden
- Skript: https://lec.inf.ethz.ch/math/informatik_cse/2024/lecture_notes/infln.pdf
- Spickinspiration: [[spick2.pdf]]
Übungen
- ab Montag morgen online, nächsten Montag bis 18.00 abgeben
- 3 Bonusuebungen, jeweils +0.125 -> zwei reichen für 0.25 Bonus
Prüfung
-
drei Orte:
- Oerlikon -> E25
maschinenbauHG
-
Orte, Umfrage für Tastatur ca. 1 Woche bevor über Email
-
findet an ETH-PC statt
- IBM schweizerdeutsche Tastatur (IBM-PC-US-Tastatur möglich)
- IBM schweizerdeutsche Tastatur (IBM-PC-US-Tastatur möglich)
-
4 Blätter (beidseitig) erlaubt
-
25P Theorie-, 55P Programmieraufgaben
- proportional nach bestandenen Testfällen bewertet, muss kompilieren
- Tipp: nach jedem bestandenen Test submitten
- Tipp: möglichst kein std::cout zum testen verwenden
- nach der Prüfung werden alle Testfälle ersetzt -> normalerweise keine neuen Edge-cases
- Warnungen in der Prüfung erlaubt (im Gegensatz zu Übungen)
- grundsätzlich erlaubt, nicht behandelten Strukturen/Funktionen verwenden
- proportional nach bestandenen Testfällen bewertet, muss kompilieren
-
Übung
- 120min
- Alte Prüfungen
- https://lec.inf.ethz.ch/past_exams/
- Abschnitt 252-0856 auswählen
- moodle, codeExpert
- Rechtschreibfehler werden in der echten Prüfung als richtig bewertet
- EBN-Aufgaben kommen nicht mehr vor
- https://lec.inf.ethz.ch/past_exams/
Todo
Lab
- Tools, Hintergründe, Best Practices
- Nicht prüfungsrelevant, aber für weiteres Studium
Zusammenfassung
Algorithm
Algorithmus: Handlungsweise zur schrittweisen Loesung eines Problems
3 Abstraktionsstufen:
- Kernidee (abstrakt)
- Pseudocode (semi-detailliert)
- Implementierung (sehr detailliert)
- Syntax: Zusammenguegungsregeln fuer elementare eichen -> kann vom Computer ueberprueft werden
- Semantik: Interpretationsregeln fuer zusammengefuegte Zeichen -> implementierter code, braucht menschliches Verstaendnis
links vom Zuweisungoperatur is immer ein Ausdruck mit Adresse
r- & l-values
- Präzendenz:
- Assoziativität: arithmetische Operatoren sind linksassoziativ
- Stelligkeit: unaeren Operatoren (+, -) vor binaeren Operatoren
Auswertungsreihenfolge -> anzuwendende gueltige Reihenfolge in C++ nicht spezifiziert
-
jeder knoten wird nach seinen Kindern behandelt
-
Variablen veraenderung vermeiden, wenn sie im gleichen Ausdruck nochmals verwendet werden (z.b.
a*(a=2
) -
verlustbehaftete Operation möglich spät ausführen, um "Fehlereskalation" zu vermeiden
a = (a/b) * b * a%b
increment
++expr
-> pre-increment, returns new value
++expr
-> post-increment, returns old value
binary numbers
binary -> prefix 0b
hexadecimal -> prefix 0x
octal -> prefix 0
unsigned int -> prefix 1u
/`17u
int: B
unsigned int:
overflow, underflow is undefined behaviour (for int)
overflow , underflow is modulo
in mixed operations (uint, int), int gets converted to unit -> may return wrong results -> avoid uint
Conditionals
Vollständigkeitsbeweis
- binaere Boolsche Funktionen koennen mit ihrem charakteristischen Vektor identifiziert werden
xor
(entweder .. oder)
y = (x||y) && !(x && y)
y !=x
charakteristischer Vektor:
können "verordert" werden
z.B.:
int-bool conversion
-> schlechter Stil, nicht benutzen
Kurzschlussauswertung
- logische Operatoren
&&
und||
werten linken Operanden zuerst aus - falls Ergebnis schon feststeht, wird der rechte Operand nicht mehr ausgewertet
z.B. x!= && z/x > y
-> verhindert Division durch 0
Loops
for vs while
- in
for
, the expression is often repsonsible for the progres
redundanz ist schlecht
-
continue spring ans ende des blocks -> danach wird die schleife wieder durchgeführt
-
richtige Iterationsanweisung
- wenige Anweisungen
- wenigen Zeilen code
- möglichst einfach
goto
- you could use only
if
andgoto
instead of loops, etc.- machine language and assembler is working like this
- do not use this for programming tasks -> complicated, error prone
Float, Double
- float: 7 nachkommastellen
- double: 15 nachkommastellen
literale:
- dezimalkomma
1.0
: double, wert 1
- exponent
1e3
: double, wert 1000
#timestamp 2024-10-14
A Floating-point number system is defined by the four natural numbers:
- β ≥ 2, the base,
- p ≥ 1, the precision (number of places),
, the smallest possible exponent, , the largest possible exponent.
Notation:
IEE Standard 754
- float:
(32 bit) - 8 bit for exponent, 1 bit for
, 23 bit precision (24 bit, since first bit is always 1) - double :
(64bit)
- Do not test rounded floating-point numbers for equality.
- Do not add two numbers of very different orders of magnitude.
- Avoid the subtraction of numbers of similiar sizes
when calculating a sum, always start with the smaller numbers -> when starting with the large numbers, the smaller dissapear when rounding float/doubles
#timestamp 2024-10-21
code structuring
forward declaration, scope
function scope: the union of all scopes of its declarations (there can be more than one)
#include <iostream>
int f(int i); // Scope from here
int main() {
std::cout << f(1);
return 0;
}
int f(int i) {
return i;
}
header files
- use header file instead of including, since a included
.cpp
file gets recompiled for every inclusion
namespaces
header file:
namespace informatik {
double pow(double b, int e);
}
can be called like this:
#include <cmath>
#include "mymath.h"
int main() {
double x = std::pow(2.0, -2); // <cmath>
double y = informatik::pow(2.0, -2); // mymath.h
}
- avoids name conflicts
- better structuring
#timestamp 2024-10-28
Vectors
- Vector Initialization
vector<int> fivezeros = vector<int>(5);
//The five elements of fivezeros are intialized with 0)
vector<int> fivetwos = vector<int>(5, 2);
//the five elements of fivetwos are initialized with 2
vector<int> countdown = {5, 4, 3, 2, 1};
//the vector is initialized with a list of values directly
vector<int> empty;
//Declares and initialises an empty vector
Accessing elements outside the valid bounds of a vector leads to undefined
behavior.
- vector access
std::vector<int> a {1,2,3,4,5,6,7,8,9};
// append
a.push_back(10);
// read value
a.at(2)
a[2]
// check size of vector (unsigned int!!!!!!!)
a.size()
int(a.size()) // better
std::ssize(a) // cpp >= 20
// matrix
std::vector<std::vector<int>> m = {
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1}
};
// matrix init with rows, cols
int rows = ...;
int cols = ...;
std::vector<std::vector<int>> m;
m = std::vector<std::vector<int>>(rows, std::vector<int>(cols, 1));
// matrices do not have to be rectangular !!!
std::vector<std::vector<std::string>> cantons = {
{"ZH", "BE", "SG", "GE", "LU", "SO", "NE", "ZG"},
{"FR", "VD", "VS", "JU"},
{"AI", "OW", "NW", "AR", "BS", "BL"}
};
// type alias
using imatrix = std::vector<std::vector<int>>;
// auto init
auto all_ones_matrix = std::vector<std::vector<int>>(rows, std::vector<int>(cols, 1));
Reference types
A reference is an alias, an alternate name for an object. It is not an object itself (and in that way is not a pointer, even if some of their uses overlap with uses of pointers).
References have certain limitations to their handling, related to their non-objectness. For example, you can't create an array of references. They have to be initialized (bound, seated) as soon as they are declared, since they can't possibly exist without an object to alias.
You can however store them, and they obey the rules of automatic variables or member variables. One of their uses is to poke through C++'s pass-by-value function calls.
// T-reference (T being the type of reference)
T&
// initalization
int& darth_vader = anakin_skywalker;
darth_vader = 22; // effect: anakin_skywalker = 22
// must be initalised with an L-value
int& k = 5; // Error: literal 5 has no address
//
When a reference is created, the object referred to must “stay alive” at
least as long as the reference.
pass by reference vs pass by value
Say I want to share a web page with you. If I tell you the URL, I'm passing by reference. You can use that URL to see the same web page I can see. If that page is changed, we both see the changes. If you delete the URL, all you're doing is destroying your reference to that page - you're not deleting the actual page itself.
If I print out the page and give you the printout, I'm passing by value. Your page is a disconnected copy of the original. You won't see any subsequent changes, and any changes that you make (e.g. scribbling on your printout) will not show up on the original page. If you destroy the printout, you have destroyed your copy of the object - but the original web page remains intact.
- pass by reference
- vector doesn't get copied, more efficient
- v gets updated outside function if it updates inside function
void egvector<int>& vec {
...
}
std::vector<int> v = {0, 1, 2, 3};
eg(v)
- return by reference
- avoid copying
- return an L-value
int& egvector<int>& vec, int index {
return vec[index];
}
std::vector<int> v = {0, 1, 2, 3};
std::cout << eg(v, 2);
const references
const T& r = lvalue;
// with r-value (compiler generates a temporary object with value of rvalue)
const T& r = rvalue;
#timestamp 2024-11-04
chars and strings
Character: Value of the fundamental type char
- Represents printable characters and control characters (e.g.
a
,\n
) - formally int (8-bit), convertible to int
- all int arithmetic operations supported (+,*,\,-,%,...)
- domain:
{−128,...,127}
or{0,...,255}
Zeichen -> {0, . . . , 127}
’A’, ’B’, ... , ’Z’ -> 65, 66, ..., 90
’a’, ’b’, ... , ’z’ -> 97, 98, ..., 122
’0’, ’1’, ... , ’9’ -> 48, 49, ..., 57
Text: std::string ≈ vector of char elements
- theoretically
vector<string>
, but use#include <string>
- operations:
#Declaration, and initialisation with a literal:
std::string text = "Essen ist fertig!"
#Initialise with variable length:
std::string text = std::string(n, ’a’)
# Comparing texts:
if (text1 == text2) ...
# Querying size:
int length = text.size();
# Size not equal to text length if multi-byte encoding is used, e.g. UTF-8
# Reading single characters:
if (text[0] == 'a') ... // or text.at(0)
# text[0] does not check index bounds, whereas text.at(0) does
# Writing single characters:
text[0] = 'b'; // or text.at(0)
# Iterating over characters
for (int i = 0; i < text.size(); ++i) std::cout << text[i];
# concatenate strings
text += ")";
'
for char, "
for stringrecursion
#timestamp 2024-11-11
Recursive strategies
decrease and conquer | divide and conquer |
---|---|
remove one element, one recursive call on remainder | halving the elements, one recursive call per half |
limitedly parallelizable | oftentimes well-parallelizable |
large recursion depth | smaller recursion depth |
Recursion vs Iteration |
- Recursion can be always simulated with loops, explicit "call stack" (e.g. vectors)
- recursive formulations are often more simple, but also less efficient
- overhead due to function calls
- often repeated calculations
- stack overflow because of large recursion depth
custom data types
struct
- defines new data type
struct extended_int {
// represents value if is_positive==true
// and -value otherwise
unsigned int value;
bool is_positive;
};
# initalise
extended_int t; // member values uninitalised
extended_int t = {200, false}; // member-wise initalization
extended_int s = t; // member-wise copy
s = t; // member-wise copy
operator/function overloading
- function overloading: function is defined by name, number, argument type
- compile automatically chooses best-fitting function
- thus, this is possible:
double sq(double x) { ... } // f1
int sq(int x) { ... } // f2
std::cout << sq(3); // compiler chooses f2
std::cout << sq(1.414); // compiler chooses f1
- operators
- special functions that can be overloaded
- syntax
operatorOP
extended_int operator+ (extended_int a, extended_int b) {
...
}
// minus
extended_int operator-
// increase
extended_int operator+=
// compare
bool operator==
// print
std::ostream& operator<<
// input
std::istream& operator>>
Information hiding
Encapsulation
idea:
- a type is uniquely defined by its value range and functionality
- representation should not be visible, only functionality offered
Classes
-
structs, where everything is hidden by default
-
scope of members in a class is whole class, independent of declaration order
-
const items can only call const member functions
-
default constructor
- is automatically generated if it doesn't exist
- unique constructor with empty argument list
- can be deleted with
rational () = delete;
class rational {
public:
# in-class member function
# const member function (n cannot be changed via this function)
int numerator() const {
return n
}
# member function (n can be changed via the function)
void set_numerator (int N) {
n = N;
}
# user-defined conversion !!!
operator double () {
return double (n)/d;
}
# constructor
rational (int num, int den)
: n (num), d (den) {
assert (den != 0);
}
# constructor with only one variable
rational (int num)
: n (num), d (1)
{}
# default constructor
rational ()
: n (0), d (1)
{}
private:
int n;
int d;
}
# out-of-class member function
# needs scope operator
int rational::numerator2 () const {
return n;
}
# call cuonstructor directly...
rational r (1,2);
# or indirectly
rational r = rational (1,2);
# call empty constructor
rational r;
Containers, Iterators
- vector
- collection of elements
- operations on the collection
=> container ("collection" in e.g. Java)
- properties
- efficient index-based access
- efficient memory-usage
- inserting/removing from arbitrary index can be difficult
- looking for specific element potentially inefficient
- can contain same element more than once
- elements are in insertion order
- examples
std::list<T>
-> less efficient than vector, but more efficient inserting/removing at arbitrary positionstd::unordered_set<T>
, mathematical set, not duplicates, unorderedstd::set<T>
, lookup/insertion/removal efficiency: vector < set < unordered_set- implemented as red-black-tree
Iterators
- each
container implements theoir own iterator - allows accessing different containers in uniform way
- functionality
- essentially "pimped" pointers
- std includes over 100 iterator-defined interval algorithms (e.g.
find
,fill
,sort
)
# pointing to first element
it = c.begin()
# pointing __behind__ the last element
it = c.end()
# dereference operator (access to current element)
it*
# advance iterator by one element
++it
- example
std::vector<int> v = {1, 2, 3};
for iterator it = v.begin(); it != v.end(); ++it {
*it = -*it;
}
- range-based for loop
std::list<int> l = {0, 0, 0};
# sequential iteration
for iterator it = l.begin(); it != l.end(); ++it
std::cout << *it; // prints 000
# also possible
for (int& e : l)
e += 3; // modifies
for (int e : l)
std::cout << e; // prints 333
- const-iterator
- read-only access to container
- const containers can only use const iterator
llvec::const_iterator llvec::cbegin() const;
llvec::const_iterator llvec::cend() const;
const int& llvec::const_iterator::operator*() const;
...
#timestamp 2024-11-25
#timestamp 2024-12-02
Pointers
A
- The address-of operator (
&
) is used to get the memory address of a variable. - The dereference operator (
*
) is used to access or modify the value stored at the memory location pointed to by a pointer.
int a = 42;
int* p = &a; // Pointer 'p' stores the address of 'a'
cout << *p; // Outputs the value of 'a' (42)
*p = 100; // Changes the value of 'a' to 100
- The
new
expression is used to allocate memory dynamically on the heap. It returns a pointer to the allocated memory. Use thedelete
operator to free the memory.does not automatically deallocate memory allocated with new
-> always usedelete
to prevent memory leaks
- The
delete
expression is used to deconstruct an object constructed withnew
- does not set the pointer of released object to
nullptr
-> dangling pointer!
- does not set the pointer of released object to
int* p = new int(42); // Allocates memory for an int initialized to 42
delete p; // Deallocates memory
- Always initialize pointers to prevent them from being wild pointers.
- Use
nullptr
- Use
- For memory allocated with
new
, always usedelete
to prevent memory leaks.
pointer types
- Null Pointer (
nullptr
): A pointer that does not point to any valid memory address.
int* p = nullptr;
- Void Pointer (
void*
): A generic pointer that can point to any type but cannot be dereferenced directly.
void* ptr;
int a = 10;
ptr = &a; // Allowed
- Dangling Pointer: A pointer that points to a memory location that has been freed or deleted. dangerous, leads to undefined behaviour, since the memory might contain totally different data=
int* p = new int(5);
delete p; // 'p' becomes dangling
- Wild Pointer: A pointer that is declared but not initialized. dangerous, leads to undefined behaviour, since the pointer points to a random memory address
int* p; // Uninitialized, points to a garbage location
- Constant Pointer: A pointer whose value (memory address it points to) cannot be changed.
int a = 10, b = 20;
int* const p = &a; // 'p' always points to 'a'
- Pointer to Constant: A pointer to a variable whose value cannot be modified through the pointer.
const int a = 10;
const int* p = &a;
const pointer
int const p1;
//p1 is a constant integer
int const* p2;
// p2 is pointer to constant integer (read-only pointer to integer)
int* const p3;
// p3 is constant pointer to integer (non-changeable pointer to integer)
int const* const p4;
// p4 is constant pointer to constant integer (non-changeable read-only pointer)
smart pointer
alternative to [[#deconstructor, copy, assignment operator]]
using <memory>
std::shared_ptr<T> sp1 = std::make_shared<T>(...);
std::shared_ptr<T> sp2 = sp1;
std::cout << sp1.use_count(); // 2
sp1 = nullptr; // explicitly remove pointer sp1 to object
std::cout << sp2.use_count(); // 1
// if the number goes down to 0, the object gets deleted
cpp|using <memory>
std::shared_ptr<T>
- Counts the number of pointers referring to an object and deletes the object only when that number goes down to 0.
std::unique_ptr<T>
- Makes sure that not more than one pointer can point to an object.
dynamic data structures
idea: elements in list, which contain pointers pointing to the next element
- pros: insertion and deletion of arbitrary elements is simple
- cons: No contiguous area of memory and no random access
-> idea behind linked list
deconstructor, copy constructor , assignment operator
alternative to [[#smart pointer]]
- the
~T ( );
deconstructor is called when the class gets deleted- if no one exists, automatically created at compile
- the
T (const T& x);
copy constructor is a unique constructor- automatically called when values of type T are initialized with values of type T
- generated automatically; then initalises member-wise
- also overload
=
assignment operator needed
If a type (that manages dynamic memory) defines one of these structures, it needs to implement all three
- Deconstructor
- Copy Constructor
- Assignment operator
GC cons
Garbage Collector (GC): in other programming languages, the GC takes care
of cleaning up old data
- Problem 1: unpredictable latency when started
- Problem 2: Memory not released right away, only after next GC run
Data structures with shared objects need management
- Example: one node is used in two stacks – changing the node value changes both stacks
#timestamp 2024-12-09
dynamic memory
- new for arrays
- new contiguous chunk of memory n elements of type T is allocated
- also called
array
- value: pointer T* to the starting address of the memory chunk
- delete arrays
- array is deleted, memory released
- calls deconstructor for every element in array
- for primitive types (no deconstructor, e.g. int), the memory is simply released
- calls deconstructor for every element in array
- pointer is not set to nullptr!
- array is deleted, memory released
T* p = new T[expr] // new contiguous chunk of memory n elements of type T is allocated
delete[] expr // delete array, release memory
pointer arithmetic
// yields the address of the element
p
// yields the value of the element
*p
- increment
ptr++
,++ptr
- Moves the pointer to the next element of the type it points to
- decrement
ptr--
,--ptr
- moves the pointer to the previous element
- Addition/Subtraction (
ptr + n
orptr - n
):- Moves the pointer forward (
+
) or backward (-
) byn
elements.
- Moves the pointer forward (
- Difference (
ptr2 - ptr1
):- Computes the number of elements between two pointers (of the same type).
- pointer arithmetic operates in units of the size of the type pointed to. For example, if
int
is 4 bytes, incrementing a pointer increases its value by 4. - Pointer arithmetic is valid only within the bounds of an allocated memory block.
pointer iteration
- sequential pointer iteration
char* p = new char[3]{'x', 'y', 'z'};
for (char* it = p; it != p + 3; ++it) {
std::cout << *it << ' '; // x y z
}
- random access
for (int i = 0; i < 3; ++i)
std::cout << p[i] << " ";
-> less efficient
- Iteration via random access (
p[0],p[1],...
) costs one addition and one
multiplication per access - Iteration via sequential access (
++p, ++p,...
) costs only one addition per
access
CPP convention
begin
: pointer to first elementend
: pointer after last element
#timestamp 2024-12-16