im following this free corse
also stuff is misspelled cuz im lazy
A computer program is a sequence of instructions that tell the computer what to do. A statement is a type of instruction that causes the program to perform some action.
so a statement is a command for a computer to follow.
syntax is like grammer if you misspell a word or statement you get a syntax error.
using a LSP (lanugae server protocol) will help you solve errors while you are coding not at compile time.
comments have nothing to do with the code its just text in the coding file its self. why you compile a file with comments they are ignored complealy.
//i am a regular comment foot
/*im a j uu
multi
line
comment
*/
you can also use comments to "comment out" code for debuging and for fixing problems
int x{}; // you can also put them here too
//int y{};
computer what what is called ram (random access memory) its just abunch of computer chips that can hold alot ones and zeros we can store the value of varibles there
when we declare a varible the computer will make space for it in the ram the type of the varible will dictate how large the space is and how it is interptied.
there are many ways we can define and declare varibles
define = saying somthing exsist and explicting saying what it is
declare = saying somthing exsist but not saying what it is
int a; //(default initialization) this declares a ininterger called a and sets it with no value.
int a = 5;//(copy initialization)
int a(5);//(direct initialization)
int a = 5, b = 6; // copy initialization
int c( 7 ), d( 8 ); // direct initialization
int e { 9 }, f { 10 }; // direct brace initialization
int g = { 9 }, h = { 10 }; // copy brace initialization
int i {}, j {}; // value initialization
//if you dont use a var the compiler might error
[[mabye_unused]] int x { 5 };
iosteam is a cpp standand libary with abunch of helpful functions however it can cause bloat naming concliioins and complexity. include iostream and you get a few functions
#include <iostream>
std::cout << "words";//prints text or the value of var to the console
// std::cout is also buffered
std::cin >> x;// reads a line from the dafasdfaskljfklsjdklonsole and saves it to a varible
std::endl// the same as \n but it flushe the buffer
int x; // un-init-ed int called x
printf("%i",x); // then we prnt x with could be any thing
whitespace is:
tabs
&
s p a c e s
you need whitespace in:
int x;
other that stuff like that it doesnt matter
int
x
=
5
;// valid
a literal is something explicit like the number 5 or the string "Hello"
In mathematics, an operation is a process involving zero or more input values (called operands) that produces a new value (called an output value). The specific operation to be performed is denoted by a symbol called an operator.
operators
In mathematics, an operation is a process involving zero or more input values (called operands) that produces a new value (called an output value). The specific operation to be performed is denoted by a symbol called an operators eg.. + - * / == <= >= != new delete throw
functions are used so you dont have to repeat code over and over it eaier to write it once eg..
// return-type name parmaters
void sayhello(){
printf("hello");
}
// ...
sayhello();
the return type says what the function is going to return like int would return and int a string would return a string and void would return nothing. eg ..
int add(int x{}, int y{}){
return x + y;
}
return is the keyword to end the function and return the givin value.
functions have to be defined before the main function so the program knows what it is.
or you could declare it in the beggening of the program and define it later.
eg..
int add(int x{}, int y{});
int main(){
total = add(5, 5);
return 0;
}
int add(int x{}, int y{}){
return x + y;
}
local scope talks about how depending where a varible or function in declared if it avabile eg ..
void yes(){
int x{}; // x is created here
} // x dies here
x = 99; // wont work because x is only avabile in the function yes
namespaces are used to avoid naming concliioins. they used the (::) operator. iostream is an name space so the var and functions in it dont interfear with your program (name)::(the function) eg.. std::cout
to be revisied in future ^
there are two types of constant
- one is a name constant which means the value of the named constant will not and cannot change durring run time
- another is a literal constant like pi (3.14) pi will never change
types of named constants
- constant varibles
- object like macros like #define this "that"
- enumerated constants in lesssion 13.2
to make a constant varible is is your prefix varible defeination with const eg
const int {89};
the do nots:
- int const this{};
- void voo(const int){}
- use #define as const unless programing on arduiono / imbeded to save on binay space
the do's
- prefer const over macros
const is a type qualifer as so is volatile which is rarely used.
the as-if rule say the the compiler may change your program in order to product more optimized code.
there are 3 differnt stages of constsants
- constant expressions
- compile time constants
- run time constants
consider the following
#include <iostream>
int main()
{
int x { 3 + 4 };
std::cout << x << '\n';
return 0;
}
x is equal to the constant expression 4 + 3. no matter what 4 + 3 is always equal to 7. so instead of spending cpu cycles on solving 4 + 3 every run time the compiler can just solve it then replace 4 + 3 with 7 eg
#include <iostream>
int main()
{
int x { 7 };
std::cout << x << '\n';
return 0;
}
there is still one more optimation that the compiler could make. since int x is only used once we could replace every accurance of x with its value to save on having to create space in ram and cpu cycles to read form ram eg
#include <iostream>
int main()
{
std::cout << 7 << '\n';
return 0;
}
constexpr int x{};
constexpr has to be defined when its declared.
constexpr is flexable and can be runtime and compile time varible.
type | value | data type |
---|---|---|
integer value | 5, 0, -3 | int |
boolean value | true, false | bool |
floating point value | 1.2, 0.0, 3.4 | double (not float!) |
character | ‘a’, ‘\n’ | char |
C-style string | “Hello, world!” | const char[14] |
Literal suffixes
Data type | Suffix | Meaning |
---|---|---|
integral | u or U | unsigned int |
integral | l or L | long |
integral | ul, uL, Ul, UL, lu, lU, Lu, LU | unsigned long |
integral | ll or LL | long long |
integral | ull, uLL, Ull, ULL, llu, llU, LLu, LLU | unsigned long long |
integral | z or Z | The signed version of std::size_t (C++23) |
integral | uz, uZ, Uz, UZ, zu, zU, Zu, ZU | std::size_t (C++23) |
floating point | f or F | float |
floating point | l or L | long double |
string | s | std::string |
string | sv | std::string_view |
best practice
- prefer upper case suffixes
- dont use magic numbers use constexpr varibles instead
we use the deciaml number system but there are others like:
- octal 1 2 3 4 5 6 7 (skip any 8 or 9) 10 11 12 13 14 15 16 17 (skip any 8 or 9) 20
- hexadecimal 1 2 3 4 5 6 7 8 9 10 A B C D E F 11 12 13 14 15 16 17 18 19 10 A B C D E F
octals:
int octal{012};// = 10 in decimal prefix any octal with 0
hexadecimal:
int hexideciamal(0x28fb); // = something
there is also binary which you prefix with 0b
and you could use ' to sepreate large numbers or binary
2'132'645'785
std::cout by default outputs decimal but you can chage that
int x { 12 };
std::cout << x << '\n'; // decimal (by default)
std::cout << std::hex << x << '\n'; // hexadecimal
std::cout << x << '\n'; // now hexadecimal
std::cout << std::oct << x << '\n'; // octal
std::cout << std::dec << x << '\n'; // return to decimal
std::cout << x << '\n'; // decimal
Outputting values in binary is a little harder, as std::cout doesn’t come with this capability built-in. Fortunately, the C++ standard library includes a type called std::bitset that will do this for us (in the header).
To use std::bitset, we can define a std::bitset variable and tell std::bitset how many bits we want to store. The number of bits must be a compile-time constant. std::bitset can be initialized with an integral value (in any format, including decimal, octal, hex, or binary).
#include <bitset> // for std::bitset
#include <iostream>
int main()
{
// std::bitset<8> means we want to store 8 bits
std::bitset<8> bin1{ 0b1100'0101 }; // binary literal for binary 1100 0101
std::bitset<8> bin2{ 0xC5 }; // hexadecimal literal for binary 1100 0101
std::cout << bin1 << '\n' << bin2 << '\n';
std::cout << std::bitset<4>{ 0b1010 } << '\n'; // create a temporary std::bitset and print it
return 0;
}
so you can bacicly do if else statemnts in arithmetic
constexpr int x{4};
constexpr int y{6};
constexpr int b{ (x > y) ? x : y};
// b is = to x if x is larger or y if y is larger
these are good for conditions in varible inits. there is no if else replacement for this.
note
cpp prioritizes the evaluation of most operators above the evaluation of the conditional operator, it’s quite easy to write expressions using the conditional operator that don’t evaluate as expected.
best practice:
- dont use conditional operator in complex conditions.
when ever a function is called there is a prefomacne overhaed
int min(int x, int y){
return (x > y) ? x : y;
}
since min is a simple function the overhead is not optimal
stultion
inline expansion happens at compile time and if the compiler decides to do so
inline expansion is when the content of the function defination replaces the functions calles along with their varibles.
the only down side is that is the function being expanded takes more instructions thatn the function call beding replaced thatn each inline expannsion will cause the exectable to grow larger.
the inline keyword prefixes functions and tell the compiler that this function would prob benfit from inline expansion but now compilers dont need that now becuase they are better than they used to be and they can totaly just ignore the inline keyword
the inline keyword can also cause problems so its best not to use it.
constexpr can also prefix varibles
constexpr int x {10}; // the value of x is known at init.
remeber constexpr cant be declared they have to be defined.
constexpr functions must have a constexpr return and constexpr praamters and no call any non-constexpr functions for it to optimised.
constexpr functions and varibles have the benifit of being runtime and compile-time.
constexpr int min(int x, int y){
return (x>y?y:x);
}
int main(){
constexpr int x{6};
constexpr int y{10}; // if i were to remove constexpr the function would run at run time not comile time
constexpr int g{min(x,y)};
std::cout << g << "is greater";
return 0;
}
if you want min() to be run at compile time set it equal to a constexpr varible eg ^
if min() is set to a non constexpr varible it will run at runtime.
pass min() to somthing like std::cout << min(5,6); it could run at compile time or runtime.
in #include type_traits> std::is_constant_evaluated() returns a bool if a the current function is ran in a const context
consteval mean that a function must be evaluated at compile time and will error if not.
consteval min(int x, int x){
return (x > y ? y : x );
}
consteval auto comileTime(auto value){
return value;
}
constexpr and consteval can be defined in many file but that fails the one-def rule, but if all the funcitons are defined the same it will run.
best practice use constexpr functions when possible
so any string like this "hello" is a c type string they have a fixed size
std::stirng and std::string_view are safer but more complex string types.
std::string can very in legnth and can be set to differnt size string after its been defined.
each charter in a strings 1 a byte.
you can sufix a string 'hello's like this so its type is a std::string
std::cin is used to get input form the user. its in iostream std::cin will only get input up to the first while space.
std::getline is like but its std::getline(std::cin,var); var is the varible to save the input to
when using std::getline use std::gitline(std::cin >> std::ws, varible); this filters any whitespace at the start of the line.
you can get the length of a std::string name.length() is unsigned int int length { static_cast(name.length()) };
c++20 gives std::ssize() to get the length of a string as a signed int
appearntly std::strings are expensive to init and copy so copy std::strings by refference also returning a std::string from a function is also expensive.
** need to proper format ^**
this topic is complex will revisit.
std::bitset in in
it lets us make varibles
#include <bitset>
#include <iostream>
int main(){
// <8> is the numb of bits to be useable in the var foo
//the bits order is..7654 3210
// you can use ' to seperate the bits every 4 bits to make it more readable
std::bitset<8> foo{0b0000'1111};
std::cout << foo.test(7) << "\n";
foo.set(7) // sets the bit on if off and if on nothing
std::cout << foo.test(7) << "\n";
foo.reset(6) // sets the bit off
foo.flip(5) //if on sets off. if off sets on.
return 0;
}
size() returns the number of bits in the bitset.
count() returns the number of bits in the bitset that are set to true. This can be used to determine if all bits are 0 or any bits are 1.
all() returns a Boolean indicating whether all bits are set to true.
any() returns a Boolean indicating whether any bits are set to true.
none() returns a Boolean indicating whether no bits are set to true.
operator | sysmbol | form | operation |
---|---|---|---|
left shift | << | x << y | all bits in x shifted left y bits |
right shift | >> | x >> y | all bits in x shifted right y bits |
bitwise NOT | ~ | ~x | all bits in x flipped |
bitwise AND | & | x & y | each bit in x AND each bit in y |
bitwise OR | x | y | each bit in x OR each bit in y |
bitwise XOR | ^ | x ^ y | each bit in x XOR each bit in y |
the bits section is hard and long will come back to it
you can used {} for block statements such as for functions. if statements by default execute only one statement but if we want more we can use block statements for any many statements as we want
to make a namespace to avoid naming confilicts
namespace my_space
{
// namespace content
}
the scope resolution operator ( :: ) is used to go into a namespace and get a var or func
#include <iostream>
namespace goo
{
int x{69};
void y(int x)
{
std::cout << x << "\n";
}
}
namespace foo
{
int x{54};
void y(int x)
{
std::cout << x << "\n";
}
}
int main(){
foo::y(goo::x);
}
if we define x in the global namespace and you wnat to use the global namespace instead of the current one just use ::x
global varibles can be defined outside of the main function
const g_x{67};
int main()
{
return 0;
}
#include <iostream>
int value{0};
int main(){
{
std::cout << value; // prints global value
int value{5} // shadows var value
std::cout << value; // prints local value
std::cout << ::value; // prints global value
++(::value)
std::cout << ::value; // prints global value
}
return 0;
}
Global variables with internal linkage are sometimes called internal variables.
To make a non-constant global variable internal, we use the static keyword.
// Internal global variables definitions:
static int g_x; // defines non-initialized internal global variable (zero initialized by default)
static int g_x{ 1 }; // defines initialized internal global variable
const int g_y { 2 }; // defines initialized internal global const variable
constexpr int g_y { 3 }; // defines initialized internal global constexpr variable
// Internal function definitions:
static int foo() {}; // defines internal function
its best to use an unnamed namedspace for statics
add external keyword to amke global virailbes external
// External global variable definitions:
int g_x; // defines non-initialized external global variable (zero initialized by default)
extern const int g_x{ 1 }; // defines initialized const external global variable
extern constexpr int g_x{ 2 }; // defines initialized constexpr external global variable
// Forward declarations
extern int g_y; // forward declaration for non-constant global variable
extern const int g_y; // forward declaration for const global variable
extern constexpr int g_y; // not allowed: constexpr variables can't be forward declared
is best not to use non const global varibles because they can be changed easly which can effect your program.
poir to C++17 you would define all the globals in a headerfile example
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants
{
// constants have internal linkage by default
constexpr double pi { 3.14159 };
constexpr double avogadro { 6.0221413e23 };
constexpr double myGravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
and include this headerfile in every file that needed these constants but there is an issue. you would have to recompile every file that included this file if you were to change it
after c++17 when inline was added example
#ifndef CONSTANTS_H
#define CONSTANTS_H
// define your own namespace to hold constants
namespace constants
{
inline constexpr double pi { 3.14159 }; // note: now inline constexpr
inline constexpr double avogadro { 6.0221413e23 };
inline constexpr double myGravity { 9.2 }; // m/s^2 -- gravity is light on this planet
// ... other related constants
}
#endif
#include "constants.h"
#include <iostream>
int main()
{
std::cout << "Enter a radius: ";
double radius{};
std::cin >> radius;
std::cout << "The circumference is: " << 2 * radius * constants::pi << '\n';
return 0;
}
this still leves us with haveing to recompile everything that includes this header tho.
the keyword static is used differntly accross cpp
for varibles when non-static they have automatic duration ( start at def and end at block end)
static varibles are created at program start and end at its end
example
#include <iostream>
void incrementAndPrint()
{
int value{ 1 }; // automatic duration by default
++value;
std::cout << value << '\n';
} // value is destroyed here
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}```
```cpp
#include <iostream>
void incrementAndPrint()
{
static int s_value{ 1 }; // static duration via static keyword. This initializer is only executed once.
++s_value;
std::cout << s_value << '\n';
} // s_value is not destroyed here, but becomes inaccessible because it goes out of scope
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}```
avoid static local varibles in a fucntion unless the value need to be reset everytime
to refresh a static global varible give it interal linkage which means it cant be used in other files.
## unnamed inline namespaces
you can call methods in a unnamed space without a prefix
anything in an un namedspace basicly gives it interal namespace
you can use inline name spaces to version functions
the inline name space acts a the default namespace
```cpp
#include <iostream>
namespace V1 // declare a normal namespace named V1
{
void doSomething()
{
std::cout << "V1\n";
}
}
inline namespace V2 // declare an inline namespace named V2
{
void doSomething()
{
std::cout << "V2\n";
}
}
int main()
{
V1::doSomething(); // calls the V1 version of doSomething()
V2::doSomething(); // calls the V2 version of doSomething()
doSomething(); // calls the inline version of doSomething() (which is V2)
return 0;
}```
V1
V2
V2
## 8.5 constexpr if statements
in a constextr if statment the condition conditionals must be know at compile time which means they too much be constexpr
## 8.5 switch statemetns
```cpp
#include <iostream>
void printDigitName(int x)
{
switch (x) // x evaluates to 3
{
case 1:
std::cout << "One";
break;
case 2:
std::cout << "Two";
break;
case 3:
std::cout << "Three"; // execution starts here
break; // jump to the end of the switch block
default:
std::cout << "Unknown";
break;
}
// execution continues here
std::cout << " Ah-Ah-Ah!";
}
int main()
{
printDigitName(3);
std::cout << '\n';
return 0;
}
here is a switch statements its best to use these when youre using lots of elseif statmests
in switch statements if you dont add break; or return; it will fall through to other cases
sometimes fall throught is wanted and with c++17 compilers give [[fallthrough]] attribute which can be add to a null statement ; eg [[fallthrough]]; before the new case which to fall throught
example of wanted fall throught
bool isVowel(char c)
{
switch (c)
{
case 'a': // if c is 'a'
case 'e': // or if c is 'e'
case 'i': // or if c is 'i'
case 'o': // or if c is 'o'
case 'u': // or if c is 'u'
case 'A': // or if c is 'A'
case 'E': // or if c is 'E'
case 'I': // or if c is 'I'
case 'O': // or if c is 'O'
case 'U': // or if c is 'U'
return true;
default:
return false;
}
}
the goto statement is an unconditonal statement which means unlike if or switch it will always preform its action.
with goto you can jump to functions or a labeled statement exapmple:
int main()
{
goto skip; // error: this jump is illegal because...
int x { 5 }; // this initialized variable is still in scope at statement label 'skip'
skip:
x += 3; // what would this even evaluate to if x wasn't initialized?
return 0;
}
#include <iostream>
#include <cmath> // for sqrt() function
int main()
{
double x{};
tryAgain: // this is a statement label
std::cout << "Enter a non-negative number: ";
std::cin >> x;
if (x < 0.0)
goto tryAgain; // this is the goto statement
std::cout << "The square root of " << x << " is " << std::sqrt(x) << '\n';
return 0;
}
loops for while do while contuniue halts
while (condition) {
}
do {
} while ( condition )
for (init; condition; interator){
}
return
contuine - exit current loop iteration
break
halts
std::exit()
implicit type conversion is when the compiled has to convert one type to another type like
int x = 3.5;
// convert double 3.5 to an int
int x{3.5};
// this wont work because brace-init disallows convertions that result in data loss
some data types like floats can be promoted to doubles
char bool and other data type smaller than int can be numeric promoted to type int implicitly
turning ints to doubles and floats is ok but to int is will result in data remove
also signed to unsigned is also promoblmatic
brace init disallow narring
int x {3.5};
some times you need to convert types and to not get a waring use static_cast(varible);
c sytle cast (double)x / y
cpp static_cast(value);
using speed = int; speed mason = 5;
speed and mason are the same type they are basicly the same just differnt names.
typedef is from c and is complex
typedef Distance double; // incorrect (typedef name first) typedef double Distance; // correct (aliased type name first)
and looks weird
typedef int (FcnType)(double, char); // FcnType hard to find using FcnType = int()(double, char); // FcnType easier to find
prefer aliases over typedef
type deduction the auto keyword can auto detect the varilbe type if its init is provided
auto add(int x, int y) -> int;
int add(int x, int y){ return x + y; }
this function returns an int but what if we wanted to add 2 doubles
then:
int add(int x, int y){ return x + y; }
double add(double x, bouble y){ return x + y; }
these functions have the same name but differnt pramaters so the compiler will be able to tell a difference
add(5,6); add(3.5,8.2);
the compilers tries to find a matching function first then it will move to numeric promotion:
void print(int){}
print('a');
a will be converted to an int
step 3 is numeric conversion:
void print(double){}
print('a')
a will be promoted to int then to a double
step 4 user defined conversion are used which are apart of classes.
step 5 will use a ellipsis?
step 6 compiler gives up and dies lol.
if the compiler happens to find more than one option in one step it will yell at you ambiguous function call then die lol.
1.Often, the best way is simply to define a new overloaded function that takes parameters of exactly the type you are trying to call the function with. Then C++ will be able to find an exact match for the function call.
2.Alternatively, explicitly cast the ambiguous argument(s) to match the type of the function you want to call. For example, to have print(0) match print(unsigned int) in the above example, you would do this:
int x{ 0 }; print(static_cast(x)); // will call print(unsigned int)
void printInt(int x) { std::cout << x << '\n'; } printInt(97); void printInt(int) = delete; printInt(97); --error
void print(int x, int y=4) // 4 is the default argument {}
void foo(int x = 5); // ok void goo(int x ( 5 )); // compile error void boo(int x { 5 }); // compile error
void print(int x=10, int y); // not allowed
void print(int x, int y=4); // forward declaration
void print(int x, int y=4) // error: redefinition of default argument { std::cout << "x: " << x << '\n'; std::cout << "y: " << y << '\n'; }
too complex to make notes of here for now
#include <iostream>
template <typename X>
X dou(X H) { //A
return H * 2;
}
int dou(int h){ //B
return h * 2;
}
int main()
{
std::cout << dou(7) << "\n"; //calls B
std::cout << dou<>(7) << "\n"; //calls A as type int
std::cout << dou<double>(46.545487) << "\n"; // calles A as type double
return 0;
}
forward declerations can be problematic for templetes its best to put the in header files and incude them.
non-type telplate
#include <iostream>
template <int N> // declare a non-type template parameter of type int named N
void print()
{
std::cout << N << '\n'; // use value of N here
}
int main()
{
print<5>(); // 5 is our non-type template argument
return 0;
}
C++ supports the following compound types:
Functions
Arrays
Pointer types:
Pointer to object
Pointer to function
Pointer to member types:
Pointer to data member
Pointer to member function
Reference types:
L-value references
R-value references
Enumerated types:
Unscoped enumerations
Scoped enumerations
Class types:
Structs
Classes
Unions
int x {};
int y {x};// modifiable lvalue
// constexpr or const
constexpr int d {};
const int v {d}; // non-modifale lvalue
L value references
int x {5};
int& ref {x};
x = 6; // x and ref are = 6
ref = 7; // x and ref are = 7
references have to be the same type as they are referencing.
references also cant be declared they have to be inited
refeernces cant change what varible they are referencing.
const refs cant change value becuae that would change the value of the varible that the ref is refing.
referneces and referentes have independent lifetimes
there are also dangling referneces.
a const L value Reference can be equal to const rvalue
const int& ref{5};
Pass by reference can only accept modifiable lvalue arguments.
unless the refernence is const
Best practice
Favor passing by const reference over passing by non-const reference unless you have a specific reason to do otherwise (e.g. the function needs to change the value of an argument).
void sup(const int& x)
sup(5); //ok because sup has const refernece
int y{10}
sup(y); //ok will work anyway if x is const or not
use std::stringview as a function pramater instead of std::string&
POINTERS yay!
what are pointers
- they are like references
- they are more flexable than references
- they inherintly dangerous
- unlike references they are an object
pointers hold the memory address of another varible
to get the address of any varible put & in frount of it
std::cout << &x;
references and pointers use & * syntax
int& x; // this is a reference
int* y; // this is a pointer
the dereference operator * is used to get the vaulue at an adress
// this works
int x{5};
std::cout << *(&x);
pointer example
int x{60};
int* ptr {&x};
std::cout << ptr; // prints the address of x
std::cout << *ptr; // prints the value of x
change the value of a varible through a pointer
int x{60};
int* ptr {&x};
std::cout << x; // prints the value of x (60)
*ptr = 30;
std::cout << x; // prints the value of x (30)
std::cout << *ptr; // prints the value of *ptr (30)
change where a pointer is pointing
int x{8};
int y{4};
int* ptr {&x};
std::cout << *ptr; // prints 8
ptr = &y;
std::cout << *ptr; // prints 4
all pointers are the same size on memeory depending on the archatucher like 64bit they are 8 byte long on 32bit they are 4byts
dangerling pointers can lead to undefined behavior becuase they are pointing to a memeory adress thats not valid and could result in undefined behavior
Null pointers
int* x {};
int* x {nullptr};
int* x {0}; // this should be used
int* x {NULL}; // shouldnt be used its from C <cstddef>
// dont deref null pointes otherwise program will crash
std::cout << *x; // this would crash because x is null pointer
pointers can be const too. they are called pointer to a const value.
const int x {5};
int* y {&x}; // this wont compile because you will be to chage the value of the varilbe even tho its const
the correct way
const int x {5};
const int* y {&x}; // this is ok you just cant change the value
y = &other; // this is also ok because you only changing where the pointer is pointing not the value of varibles.
you can also have a pointer point to a non-const varible
int x;
const int* ptr {&x};
you only wont be able to change the value of the varible pointing to.
ok so here is something confusing a const pointer
its just a pointer that the place its looking at cant change.
you cant init an empty const pointer
int j {10};
int * const ptr {&j}
// but you can still change the value of it
*ptr = 20;
now for the finaly pointer i think
its a pointer that you cant change the address of or the value of is varible
const int * const ptr {&x};
thats a lot of consts