Summary

This document covers the basics of C++ programming. Topics include pointers, memory allocation, static and dynamic linking, classes, function pointers, and operator overloading. There are code examples illustrating these concepts.

Full Transcript

C++ Code notes: https://github.com/seandixit/C-Basics -​ Great platform support: As long as there's a compiler, you can run C++ on platform -​ Compiler compiles.cpp directly into machine code (.exe -> script or.lib -> library), so its fast af -​ When you ru...

C++ Code notes: https://github.com/seandixit/C-Basics -​ Great platform support: As long as there's a compiler, you can run C++ on platform -​ Compiler compiles.cpp directly into machine code (.exe -> script or.lib -> library), so its fast af -​ When you run.exe, CPU directly runs the machine code in the.exe -​ Other languages (like Java) run on virtual machine (Java VM: JVM) -​ Compiled into bytecode -​ Then when you run the program, VM converts it into machine code at runtime -​ Low level: -​ Allows you to control things like allocating memory through pointers -​ So its used for when you want to minimize memory / time -​ Errors: -​ Compile time errors: errors you get when you compile the cpp (into obj file) -​ Link errors: errors you get while linker links obj files. Starts with LNK -​ Runtime error: error after compiling (code compiles just fine, but crashes or misbehaves while running) -​ Pointer -​ Integer that stores a memory address -​ Memory is a linear, 1d line of memory addresses -​ * symbolizes pointer -​ & used to get address of a var -​ Pointer / memory address size: -​ 4 bytes (in 32-bit system) (=32 bits) -​ Because there are 2^32 possible memory addresses -​ So 32 bits to represent all possible addresses -​ 8 bytes (in 64-bit system) -​ Bytes represented as 2 hexadecimals [1 hex = 4 bits] -​ -​ Variables -​ When we create a variable, its going to be placed in memory in either the stack or heap -​ Primitive data types: -​ They define how many bytes is occupied by the variable in memory -​ Code reads data typed vars according to the data type -​ If I have char a = 65; std::cout initialized only once before program starts -> if we go to static int x = 0; again, it doesn’t re-initialize it -​ If inside function, belongs to the function (but its a nonlocal var) -​ If inside class, belongs to class (not instance) -​ If global var, belongs to cpp file -​ -​ Static local variable: -​ When you create a static variable, they are assigned a memory location once and remain there throughout program -​ Static variables are initialized only once before the program starts running (below is an example) -​ -​ Above, will return 1 2 3 4 5 (instead of 1 1 1 1 1) -​ Count declaration is skipped (since initialized before program starts) -​ Static class member: -​ Static member variable inside class means it will remain in memory until program is done, and initialized before program starts (have to initialize as global var) -​ Shared amongst all instances of the class – belongs to class, not instances -​ Aren’t able to access other member functions/variables (because members variables belong to instance, not class) -​ UNLESS its a static member. Static members can access other statics (since they belong to class) -​ -​ Static member functions: -​ Belongs to class, can only access static vars, can be called using class name -​ Global static variable: -​ When global variable is static, it means it is only visible to the cpp file it is in #include // CPP first looks at all # commands, which are called "preprocessor statements" // looks for iostream file, takes all content from file and pastes into this file #include // required for std::to_string #include // required for memset (clears memory) #include // required for std::array #include // required for std::vector #include "Log.h" // quotes used for files not in "include path" (like iostream) // Header file: —--------------------------------------------------------------- #pragma once // only include this file once // if you include this file twice (or another file // that declares one of the same functions below), // you wont get duplicate declaration error // Header files are used to declare functions so you don't need to // explicitly declare in each of the cpp files // Just gotta #include "Log.h" in the cpps // --> Don't need to compile.h files (when you compile.cpp files, compiler pulls code from headers via #include) // -------------—--------------------------------------------------------------- void Log(const char* message); #include #include #include #include //#define struct class // replaces all instances of struct with class (wherever there is struct, is replaces it with class) // struct is a thing because of backwards compatibility with C lol (no real point in C++) //void Log(const char* message); // have to declare (as opposed to define) the function first // linker links this function to the Log function in log.cpp // when you compile log.cpp // std: standard template library, contains templates (ie classes that take a type as a parameter, ex std::vector) static int Multiply(int a, int b) // static -> cannot be declared by other cpp files { // (WITH static): we say multiply can ONLY be called here. so if multiply // hasnt been called in main, linker doesnt link declaration inside to definitions // NOTE: Compiler does throw out compile time error if declaration not found Log("Multiply"); return a * b; } // time overhead with functions: // - compiler pushes things like params to stack // - jumps to different part of memory to where function code is // - then jump back with return val // similarly, theres a time overhead with if statements (must jump to different part of memory) void Increment(int& value) { // PASS BY REFERENCE: value is a reference to the original value, // allowing the function to modify the original value // value is a reference to the original value (not a copy of it) // so if you pass in a variable, it will modify the original variable // (as opposed to a copy of it) value++; } class Player // struct vs class: struct is public by default, class is private by default { public: int x, y; int speed; static int shared_var; // static inside function: across all instances of the class, there is only one copy of this function // aka it belongs to the class, not the instance of the class // must declare it (outside the class) in cpp file as int Player::shared_var; // static OUTSIDE of a class: the function will only be visible/usable in this file (not other cpp files) (its like marking it as private) // ^ use static so linker doesnt link it to other cpp files if its only being used here Player() // init (constructor) { x=0; y=0; } Player(int X, int Y) // init (constructor) { x=X; y=Y; } ~Player() // destructor, called when instance is descoped (for example, Player p is initialized inside function, and function was completed) { std::cout InitalizerExample example = std::string("Cherno"); // ^ (creates InitializerExample object with name = "Cherno") // if dont want this, use explicit keyword in constructor (ex below) class InitializerExample { private: std::string name; public: explicit InitializerExample(std::string n) // explicit: prevents implicit type conversion ^ : name(n) // member initializer list // instead of doing name = "Default Name"; inside the constructor body { // name is initialized to "Default Name" here } }; int main() // entry point (ran by default), if you dont return anything, assumes it returns 0 (only for main though33) { Example varr = A; bool comparison = (varr == 0); // time voerhead with if: // - compiler jumps (assembly jump equals 'je' call) to different part of memory to where else or after if (machine) code is // - only if condition is false (je = jump if equal (..to false)) if (comparison) { Log("Hello World!"); } else { Log("Goodbye World!"); } for (int i = 0; i < 10; i++) { Log("Looping!"); } bool condition = false; do { } while (condition); // do while loop (executes once, then checks condition) // Pointers: int var = 8; int* varPtr = &var; // varPtr is memory adderss of var (where 8 is stored), & = get address operator // double* = double pointer (points to double value), int* = int pointer (points to int value) *varPtr = 10; // *ptr: * is dereference operator, dereferences the pointer (points to the VALUE at the address of var, NOT the address itself) // var = 10 now void* ptr = nullptr; // void: we dont care what type of data is held in the memory address referenced by the pointer char* buffer = new char; // new: allocates memory on heap (dynamic memory allocation) // char* buffer: buffer points to the first byte of the array of 8 chars memset(buffer, 0, 8); // memset: sets the first 8 bytes of buffer to 0 (clears the buffer) char** ptrr = &buffer; // pointer to pointer (points to the address of buffer) // ptr is 4 bytes. if you go to ptr, you get address of buffer delete[] buffer; // delete: deallocates memory on heap (dynamic memory deallocation) // REFERENCES: not really a variable, but a reference to a variable (aka alias) int vaar = 8; int& ref = vaar; // ref is a reference to vaar (ref is an alias for vaar) ref = 2; // ref is now 2, vaar is now 2 (ref is NOT a pointer, it is an alias for vaar) Increment(vaar); // = Increment(&vaar) where Increment: Increment(int* value) { *(value)++; } (we dereference the pointer) // Note: you cannot change reference to point to something else int a = 5; int b = 10; int& refer = a; // ref is a reference to a refer = b; // ref is now 10, but a is still 5 (ref is NOT a pointer, it is an alias for a) // a = 10, b = 10, ref = 10 Log((std::to_string(a) + " " + std::to_string(b) + " " + std::to_string(refer)).c_str()); // prints 10 10 // Note: you CAN, however, change pointer to point to something eles // since we must dereference the pointer to change the value of what it points to) //int* ptr = &a; //ptr = &b; // ref is now pointing to b // *ptr = 20; // b is now 20, a is still 5 (dereference the pointer to change the value of b) // Classes: Player player; player.x = 5; // x must be public (default is private) player.move(1, 0); player.shared_var = 10; // same as: //Player::shared_var = 10; // Arrays: int example; // example is of pointer type std::cout : to access members of the object pointed to by the unique pointer // y + other.y); //} // this: // POINTER to the current instance of the class // so its this->x = x; in constructor // STACKS: // when we enter a scope, you can think of a book being pushed onto stack // when we create new local variables inside the scope, we are writing onto the book // when we exit the scope, the book is popped off the stack (all of the variables / objects in scope are destroyed) // Smart Pointers: // when you call smart pointer, it will automatically call delete when the smart pointer goes out of scope // useful for heap allocated objects so you dont have to manually call delete // raw pointers problem: // - when a raw pointer in a scope is destroyed (because we are out of scope), the object it points to is NOT destroyed (IT SHOULD BE if nothing points to it!) // - if you forget to call delete, you have a memory leak // - if exception occurs before delete, we have a memory leak // Types of smart pointers: // unique pointer: scoped pointer, when it exits scope, it gets destroyed (delete called). It's stored in the STACK. // -- you cannot copy a unique pointer (only move it), since if content of it is destroyed, the copy will be dangling std::unique_ptr playerPtr = std::make_unique(1, 2); // same as std::unique_ptr uniquePlayer(new Player(1,2));, but make_unique is safer //std::unique_ptr playerPtr2 = playerPtr; // ERROR: cannot copy unique pointer playerPtr->move(1, 0); // shared pointer: allows us to have multiple pointers pointing to same object // -- when the last shared pointer pointing to the object is destroyed, the object is deleted // -- has to allocate memory for a control block which has: // ---> REFERENCE COUNT (= how many pointers pointing to the same object)-- when 0, deleted (extra OVERHEAD) std::shared_ptr sharedPlayer = std::make_shared(1, 2); std::shared_ptr sharedPlayer2 = sharedPlayer; // both point to same object sharedPlayer->move(1, 0); // weak pointers: pointer that doesnt affect the reference count of the shared pointer std::weak_ptr weakPlayer = sharedPlayer; // wont keep the object alive // NOTE: the pointers you create in current scope are gonna be in stack, but the objects they point to ARE ON HEAP // Copying and Copy Constructors: // copying an object fully takes time, even if you just want to copy to read object data/vars int a = 2; int b = a; // copies the value of a into b (2) b=3; // a still 2 Player player1(1, 2); Player player2 = player1; // copies the values of player1 into player2 (x=1, y=2) player2.x = 3; // player1.x still 1 // problem: when copying a string, string class holds a pointer to the string data in memory // and when the string is copied, both objects point to same memory address // BUT when they are destroyed, the destructor calls "delete" on the memory address // so we have a double delete error (memory corruption) // so we have to do a DEEP COPY, not just a shallow copy // SHALLOW COPY: copy instance variables of the object (like pointers) to the new object // DEEP COPY: copy the actual data pointed to by the pointers to the new object // do deep copy by using copy constructor: (constructor called by the compiler when copying an object) /** BEWARE: say you have: void PrintString(String string) { std::cout