eifueo/docs/ce1/ece150.md
2022-11-02 22:25:00 -04:00

343 lines
10 KiB
Markdown

# ECE 150: C++
## Non-decimal numbers
Binary numbers are prefixed with `0b`.
!!! example
The following two snippets are equivalent:
```cpp
int a{0b110001};
```
```cpp
int a{25};
```
To convert from **binary to decimal**, each digit should be treated as a power of two much like in the base 10 system.
!!! example
$$0
\text{0b1011}=1\times2^3 + 0\times2^2+1\times2^1+1\times2^0=11
$$
Binary addition is the same as decimal addition except $1+1=10$ and $1+1+1=11$.
To convert from **decimal to binary**, the number should be repeatedly divided by 2 and the binary number taken from the remainders from bottom to top.
!!! example
$$
\begin{align*}
13 &= 2\times6 + 1 \\
6 &= 2\times3 + 0 \\
3 &= 2\times1 + 1 \\
1 &= 2\times0 + 1
\\
&\therefore 13 = \text{0b1101}
\end{align*}
$$
To convert from **binary to hexadecimal**, each group of four digits beginning from the right should be converted to their hexadecimal representation.
To convert from **hexadecimal to binary**, each hexadecimal digit should be expanded into its four-digit binary representation.
To convert from **decimal to hexadecimal**, the number should be repeatedly divided by 16 and the hex number taken from the remainders from bottom to top.
!!! example
$$
\begin{align*}
37 &= 16\times2 + 5 \\
2 &= 16\times0 + 2
\\
&\therefore 37 = \text{0x25}
\end{align*}
$$
## Numbers
### Integers
!!! definition
- A **carry** occurs if an overflow or underflow happens in an unsigned number.
The $k$th bit of a number is as known as its **coefficient** because it can be expressed in the form $n\times 2^k$ in binary or $n\times 16^k$ in hexadecimal.
| Type | Bits | Can store |
| --- | --- | --- |
| `short` | 16 | $\pm2^{15}-1$ |
| `int` | 32 | $\pm2^{31}-1$ |
| `long` | 64 | $\pm2^{63}-1$ |
| `char` | 8 | N/A |
| `unsigned short` | 16 | $2^{16}-1$ |
| `unsigned int` | 32 | $2^{32}-1$ |
| `unsigned long` | 64 | $2^{64}-1$ |
| `unsigned char` | 8 | N/A |
The `sizeof()` operator evaluates the size the type takes in memory at compile time.
Signed numbers use the first bit to represent positive or negative numbers. A negative number is equal to the **two's complement** of its positive form. This allows subtraction to be done by taking the two's complement of the subtracter.
!!! definition
The two's complement form of a number flips all bits **but the rightmost digit equal to one**.
### Floating point numbers
| Type | Bits | Digits of precision |
| --- | --- | --- |
| `float` | 32 | ~7 |
| `double` | 64 | ~16 |
Floating point numbers let a computer work with numbers of arbitrary precision. However, the limited digits of precision mean that a small number added to a large number can result in the number not changing. This results in odd scenarios such as:
$$
x+(y+z)\neq(x+y)+z
$$
## References
The ampersand (&) represents a reference variable and an argument passed into a parameter with an ampersand must be a valid lvalue.
Effectively, it is a pointer, letting you do weird shit such as:
```cpp
void inc(int &n) {
n++;
}
```
where the variable passed into `inc` will actually increase in the caller function.
This can also be used in variable declarations to not create a second local variable:
```cpp
#include <climits>
double const &pi{M_PI}; // pi links back to M_PI
```
## Arrays
```cpp
// typename identifier[n]{};
int array[5]{};
int partial[3]{2};
int filled[3]{1, 2, 3};
```
Arrays are contiguous in memory and default to 0 when initialised. If field initialised with values, the array will fill the first values as those values and set the rest to 0.
Because arrays do not check bounds, `array[n+10]` or `array[-5]` will go to the memory address directed without complaint and ruin your day.
### Local arrays
Local arrays cannot be assigned to nor returned from a function. If an array is marked `const`, its entries cannot be modified.
Arrays can be passed to functions by reference (via pointer to the first entry).
## Memory
!!! definition
- **Volatile** memory is erased after the memory is powered off.
- **Byte-addressable** memory is memory that has an address for each byte, such that to change a single bit the whole byte must be rewritten.
Main memory (random access memory, RAM) is volatile and any location in the memory has the same access speed.
An **address bus** with $n$ lines allows the CPU to update $n/8$ bytes at once (one address bit per line). The number of total memory addresses is limited by the number of lanes in the address bus.
When a program is run, the operating system (OS) allocates a block of memory for it such that the largest address is at the bottom of the memory block for the program.
- Instructions (the **code segment**) are stored at the **top** of the block
- Constants (the **data segment**, including string literals) are stored **after** the instructions
- Local variables (the **call stack**) are stored beginning from the **bottom** of the block
Dynamically allocated variables and static variables are stored between the call stack and the data segment.
### Call stack
The call stack represents memory and variables are allocated space from bottom to top.
At the moment a function is run, its parameters are allocated space at the bottom, followed by all local variables that **may or may not** be defined.
The return value of the function overwrites whatever is at the bottom of the function-allocated block such that the caller can simply reach up to get return data.
!!! warning
Arrays are allocated **top-down** such that indexing is made easy.
## C-style strings
C-style strings are char arrays that end with a **null terminator** (`\0`). By default, char arrays are initialised with this character.
If there is not a null terminator, attempting to access a string continues to go down the call stack until a zero byte is found.
## Dynamic allocation
Compared to static memory allocation, which is done by the compiler, dynamic memory is managed by the developer, and is stored between the call stack and data segment in the **heap**.
The `new` operator attempts to allocate its type operand, optionally initialising the variable and returning its memory address.
```cpp
char *c{new char{'i'}};
```
If the operating system cannot allocate that much memory, `std::bad_alloc` is raised, but passing in `nothrow` can return a `nullptr` instead if allocation fails.
```cpp
char *c{new(nothrow) char{`i`}};
if (c == nullptr) {
}
```
The `delete` operator tells the OS that the memory address passed is no longer needed. Generally, it is a good idea to set the deleted pointer afterward to a null pointer.
```cpp
delete c;
c = nullptr;
```
If deleting arrays, `delete[]` should be used instead.
!!! warning
Statically allocated memory **cannot be deallocated** manually as it is done so by the compiler, so differentiating the two is generally a good idea.
### Vectors at home
Dynamic allocation can be used to mimick an `std::vector` by creating a new array whenever an element would be full and doubling its size, copying all elements over.
!!! example
Sample implementation:
```cpp
std::size_t capacity{10};
double *data{new double[capacity]};
std::size_t els{0};
while (true) {
double x{};
std::cin >> x;
++els;
if (els == capacity) {
std::size_t old_capacity{capacity};
double *old_data{data};
capacity *= 2;
data = new double[capacity];
for (int i{}; i < old_capacity; ++i) {
data[i] = old_data[i];
}
delete[] old_data;
old_data = nullptr;
}
}
```
### Wild pointers
A wild pointer is any uninitialised pointer. Its behaviour is undefined. Accessing unallocated memory results in a **segmentation fault**, causing the OS to terminate the program.
!!! warning
Occasionally, the OS does not prevent program access to deallocated memory for some time, which may allow the program to reuse garbage pointers, allowing the pointer to work. This causes inconsistent crashing.
To avoid wild pointers, pointers should always be initialised and set to `nullptr` if not needed even if they would go out of scope.
### Dangling pointers
A dangling pointer is one that has been deallocated, which has the same issues as a wild pointer, especially if two pointers have the same address.
!!! example
`p_2` is dangling.
```cpp
int* p_1{};
int* p_2{p_1};
delete p_1;
```
To avoid dangling pointers, pointers should be immediately set to `nullptr` after deleting them. Deleting a `nullptr` is **guaranteed to be safe**.
### Memory leaks
A memory leak occurs when a pointer is not freed, such as via an early return or setting a pointer to another pointer. This causes memory usage to grow until the program is terminated.
!!! example
The `new int[20]` has leaked and is no longer accessible to the program but remains allocated memory.
```cpp
int* p{new int[20]};
p = new int[10];
```
## Pointers
!!! definition
- A **pointer** is a variable that stores a memory address.
The asterisk `*` indicates that the variable is a pointer address and can be **dereferenced**. `&` can convert an identifier to a pointer.
```cpp
int array[10];
int *p_array{array};
int num{2};
int *p_num{&num};
```
!!! warning
Only **addresses** should be passed when assigning pointer variables this means that primitive types must be converted first to a reference with `&`.
The default size of a pointer (the address size) can be found by taking the `sizeof` of any pointer.
```cpp
sizeof(int*);`
```
The memory at the location of the pointer can be accessed by setting the pointer as the lvalue:
```cpp
*var = 100;
```
The `const` modifier only makes constant the value immediately after `const`, meaning that the expression after it cannot be used as an lvalue.
!!! example
```cpp
int* const p_x{&x};
p_x = &y; // not allowed
*p_x = y; // allowed
```
```cpp
int const *p_x{&x};
p_x = &y; // allowed
*p_x = y; // not allowed
```
```cpp
int const * const p_x{&x};
p_x = &y; // not allowed
*p_x = y; // not allowed
```
Pointers to `const` values must also be `const`.
!!! example
BAD:
```cpp
const int x = 2;
int *p_x{&x};
```
GOOD:
```cpp
const int x = 2;
int const *p_x{&x};
```