- Build Configurations - For debugging - `-ggdb` flag gcc - For release builds - `-O2 -DNDEBUG` - Difference b/w both is that using the debugging flag, the output executable is of larger size as compared to building with release build configurations. - A program with takes input from user and display the inputted data is of size: - 20k with release build configuration - 68k with debug build configuration -------------- - Compiler extensions - Theses extensions allow the user to write and compile code which dont follow the C++ standards, leading to errors like: - Not being able to compile the same program in other compiler or even if it compiles, it might not run correctly. - Disable compiler extensions in gcc: - `-pedantic-errors` flag ==================================================== 1.1 Statements and structure of a program ========================================= 1.1.1 Statements ---------------- A statement is a type of instruction which causes the program to perform some action. Computer program is a sequence of instructions that tell the computer what to do. - Types of statements - Declaration statements - Jump statements - Expression statements - Compound statements - Selection statements (conditional) - Iteration statements (loops) - try blocks 1.1.2 Functions and the main function ------------------------------------- Functions are unit of statements. The statements inside a function are executed from top to bottom in sequence. Every C++ program must have a `main()` function or the program will fail to link. Program terminates after the last line/statement has been executed. ``` int main() { instructions-here; return 0; } ``` `main()` function has `int` before the word main as it indicates what datatype will be the return value, so the return datatype of this function is integer. `return 0` tells the Operating system that everything went fine. 1.2 Comments ============ - Single Line Comments // This a single line comment - Multi-Line Comment /* This is multi line comment */ 1.3 Objects and Variables ========================= C++ discourages direct memory access, this is why objects are used. An object is a region in the memory that can store a value. An object with a name is called variable. 1.3.1 Variable instantiation ---------------------------- Creating an object and assigning it an address is called variable instantiation. At runtime, the variable is instantiated. An instantiated object is called instance. In variable definition, an object is JUST given a name and data type. eg. int x; 1.3.2 Data type --------------- Data type just tells the compiler what type of value the variable will store. `data-type variable-name;` eg. int x; double width; 1.4 variable assignment and initialization ========================================== 1.4.1 Variable assignment ------------------------- Assignment is a process of defining a variable and then providing it a value using = operator in two different statements. eg. int x; x = 1; Here, the process is called operator and the "=" sign is called assignment operator. By default, assignment copies the value on right side to the variable on left. This is called `copy assignment` 1.4.2 Initialization -------------------- Initialization is the process of defining a variable and providing it a value in the same statement. The syntax used to intialize is called initalizer. eg. int width {5}; {5} is the initializer 1.4.2.1 Types of intialization ------------------------------ int a; - no intialization int a = 5; - copy intializaiton int a(5); - direct intializaiton These are List intializaiton methods introduced in C++11: int a {5}; - direct list initialization int a = {5}; - copy list intializaiton int a {}; - value intializaiton 1.4.2.1.1 Default intializaiton ------------------------------- When no intializer is provided, it is called default intializer. eg. int a; 1.4.2.1.2 Copy intialization ---------------------------- Copies value from right to variable on the right. Copy intialization was inherited from C. eg. int a = 56; Copy intialization is not favoured anymore due to being less efficient for complex types. However, C++17 fixed the bulk of these issues. 1.4.2.1.3 Direct intializaiton ------------------------------ Direct intialization was introduced to allow for more efficient intialization of complex objects, but it fell out of favour due to being superseded by list initialization and it having similar syntax to defining funciton. eg. int a(5); 1.4.2.1.3 List intializaiton ---------------------------- The modern way to intialize objects. also called uniform or brace initialization. eg. int width { 5 }; // direct list initialization int height = { 6 }; // copy list initialization int depth {}; // value initialization 1.4.2.1.4 Why list initialization is preffered ---------------------------------------------- List initialization has an added benefit: “narrowing conversions” in list initialization are ill-formed. This means that if you try to brace initialize a variable using a value that the variable can not safely hold, the compiler is required to produce a diagnostic (usually an error) eg. If I try to give a decimal to a int object, other intialization process would drop the decial part and only keep the integer without the compiler complaining. But, in the case of list intialization, compiler will throw error. int a = 4.5; // Output : 4 (no error) int a (4.5); // Output : 4 (no error) int a {4.5}; // Output : Error Unsafe implict conversions will generate an error when using brace initialization. eg. double d {5}; // okay, int to double safe int x {5.5}; // error: double to int not safe because converting double to int will result in data loss as it will drop the fractional part from doulble More about brace initialization and implict conversion on 4.12 1.4.2.1.5 Value/Zero intialization ---------------------------------- Value intialization sets the value of variablee to zero or empty depending on data type. 1.4.2.2 Unused intialized variable warnings ------------------------------------------- Compilers generate warnings when a variable is defined but not used anywhere. To overcome this, `[[maybe_unused]]` attribute was introduced in C++17 eg. Consider the case where we have a bunch of math/physics values that we use in many different programs int main() { [[maybe_unused]] double pi { 3.14159 }; [[maybe_unused]] double gravity { 9.8 }; [[maybe_unused]] double phi { 1.61803 }; return 0; } 1.5 Introduction to iostream: cout, cin, endl ============================================= iostream (input/output library) is the part of C++ standard library that handles basic input and output. 1.5.1 std::cout --------------- cout (character out) is used to print text in the console. eg. ``` #include int main() { std::cout << "hello"; return 0 } ``` "<<" is called insertion operator. 1.5.2 std::endl --------------- endl (end line) is used to move cursor to the next line as well as flush the buffer. eg. std::cout << "hello"; std::cout << "world"; // Output : hello world std::cout << "hello" << endl; std::cout << "world" << endl; // Output : hello // world 1.5.3 std::cout is buffered --------------------------- The statment in progrm requests the output to be sent to the console. However, the output is typically not sent to the console immediately. Instead, the requested output “gets in line”, and is stored in a region of memory set aside to collect such requests (called a buffer). Periodically, the buffer is flushed, meaning all of the data collected in the buffer is transferred to its destination (in this case, the console). 1.5.4 std::endl vs \n --------------------- endl is inefficient since it prints newline and flushes the buffer (which is slow). C++ output system self-flush buffer periodically, it is both simpler and more efficient to let it flush itself. \n (inside either single or double quotes) only prints next line. \n should be single quoted if it is not added with the text itself. This is because in C++, single quote is used to represent single character and double quote for text. eg. std::cout << "hello\n"; std::cout << "world\n"; 1.5.5 std::cin -------------- cin (character input) is used to read input from keyboard. eg. int x; std::cout << "Enter a number : " std::cin >> x; std::cout << "Number" << x; 1.6 Unintialized variabls and undefined behaviour ================================================= 1.6.1 Unintialized variables ---------------------------- Unlike some programming languages, C/C++ doesn't automatically intialize most variables (ie no value given to the variable but address is provided in the memory). The uninitialized variable is assigned whatever garbage was at the assigned memory address. eg. int x; // uninitialized variable cout << x; One would assume the output would throw error or 0 but it will print whatever value was stored on that memory address before. Here my output was: 32767 - Initialized : The object is given a known value at the point of definition. - Assignment : The object is given a known value beyond the point of definition. - Uninitialized : The object has not been given a known value yet. 1.6.2 Undefined behaviour ------------------------- Using the value from an uninitialized variable is our first example of undefined behaviour. Undefined behaviour (often abbreviated UB) is the result of executing code whose behaviour is not well-defined by the C++ language 1.7 Keywords and Naming Identifiers =================================== 1.7.1 Keywords -------------- C++ has 92 words reserved, each of which have special meaning. These words are called keywords. 1.7.2 Naming Identifiers ------------------------ Name of a variable, function, or any other kind of item is called identifier Rules for naming: - Cant be a keyword - Can only contain numbers, letters, underscore - Must begin with a letter - Naming is case sensitive. fValue, fvalue, FVALUE are different Best Practices: - Should begin with lowercase - When working on a exsisting program, use the convention of that program - Avoid abbreviations - Names shouldnt start with underscore as they are reserved for OS or compiler 1.9 Introduction to Literals and Operators ========================================== 1.8.1 Literals -------------- Literal (Literal Constant) is a fixed that are inserted directly into the source code. While Objects and Variables represent memory locations that hold values. These values can be fetched on demand. eg. std::cout << "Hello world!"; int x { 5 }; // Hello world and 5 are literals. 1.8.2 Operators --------------- Operation is a process that takes zero or more input and produces a new value. - The specific operation to be performed is denoted by symbols called operators. - The input values are called operands. - The ouput value of operation is called return value. eg. std::cout << 1 + 2 << '\n'; // Literals : 1, 2 Operators : <<, + eg of operators: +, -, /, *, <<, >>, ==, new, delete, throw The number of operands that operator takes in are called arity. Types of arity: 1. Unary : Only one operands required - eg. -5 2. Binary : Two operands required - eg. +, -, <<, >> 3. Ternary : 3 operands required 4. Nullary : 0 operands required 2.1 Intro to Functions ====================== A function - is a reusable sequence of statements designed to do a particular job. - provide a way to split our programs into small, modular chunks that are easy to organise and maintain. The function intiating the function call is "caller", and the function being called is "calee" 2.1.1 User-defined Function --------------------------- eg. returnType funtionName() { // statements here } 1st line : function header - Used to define the return type of function, function name, pass parameters. 2nd line : function body - This is where statements go > Nested function are not supported 2.2 Function return values ========================== 2.2.1 Return Values ------------------- We define the return type value in the function header. Inside the function body, we will use return statement to return the specific value to the function caller. When return statement is executed, it copies return value from function back to the caller. This process is called "return by value". > return values would not be printed unless sent to console using std::cout 2.2.2 main() Function ===================== When a program is executed, the OS makes a function call to main(). Then the statements are executed sequentially and finally returns integer value (usually 0). The return value in main() function is called status/exit/return code. 0 means program executed successfully. When return type is defined the header but no return value is return using return statement then it will lead to undefined behaviour. > void return type is an exception, since it is supposed to return nothing > main() function returns 0 even if no return statement is used. > Functions can only return one value. 2.2.3 Reusing Functions ----------------------- Follow DRY: “don’t repeat yourself”. If you need to do something more than once, consider how to modify your code to remove as much redundancy as possible. Variables can be used to store the results of calculations that need to be used more than once (so we don’t have to repeat the calculation). Functions can be used to define a sequence of statements we want to execute more than once. And loops (which we’ll cover in a later chapter) can be used to execute a statement more than once. 2.3 Void Functions ================== 2.3.1 Void return values ------------------------ Void functions are not required to return any value. So, They dont need return statement as well. We can add return statement like the follwing to tell the compiler to return to caller statement, But it is better not write a return statemennt since it will go back to caller function anyways. void add(){ cout << "Addition"; return; } eg. void hello() { cout << "hello"; } 2.4 Introduction to functions and parameters ============================================ 2.4.1 Function paramters ------------------------ A function parameter is a variable used to in the function header. Its value is intialized with the value provided by caller of the function. eg. int add(int x, int y) { // this is a function header with x and y as parameters. return x + y; } 2.4.2 Function arguments ------------------------ An argument is the value that is passed from the caller to the function. eg. add(2, 4); // this a function call with 2 and 4 passed as an argument to the function add. 2.4.3 How parameters and argument work together ----------------------------------------------- When a function is called, the parameters of the function are created as variables, and the value of arguments are copied to the corresponding parameters which then can be used inside the function body. This process is called "pass by value" eg. int add(int x, int y) { // 3, 4 are copied to x, y respectively when add() is called inside main function return x + y; } int main() { add(3, 4); // when add function is called, x and y parameters are created as variables. } 2.4.4 Unreferenced parameters ----------------------------- Unreferenced parameters are parameters which are not used inside the function body. The compiler which warn about this2.4.4 Unreferenced parameters ----------------------------- Unreferenced parameters are parameters which are not used inside the function body. The compiler which warn about this. 2.5 Introduction to local scope =============================== 2.5.1 Local Variables --------------------- Varibales defined inside the function body is called local scope. It can't be used outside that specific function body unless returned. 2.5.2 Local Variables Lifetime ------------------------------ Local variables are created at the variable definition and destroyed where curly brackets end. eg. int add(int x, int y) { int z { x + y}; // z is created here return z; } // x, y, z are destroyed here 2.5.3 Local Scope ----------------- "Scope" determines where the identifier can be seen and used within the source code. When an identifier can be seen and used, it is called "in scope" When an cant be seen or used, it is called "out of scope" A local variable's scope begins at the point of varibable definition and it ends where curly bracket ends. So, we say that variable is in local scope. 2.5.4 Out of Scope ------------------ An identifier is out of scope when it can't be accessed within the code 2.6 Why functions are useful and how to use them effectively ============================================================ When a function becomes too long, too complicated, or hard to understand, it can be split into sub functions. This method is called "refactoring" 2.7 Forward Declaration and Defintions ====================================== ``` #include int main() { std::cout << "The sum of 3 and 4 is: " << add(3, 4) << '\n'; return 0; } int add(int x, int y) { return x + y; } ``` The linker will throw an error since we are called add() functin before defining the add() function. There are two ways to deal with this: 1. Reorder the add() function so it is defined before main() 2. Use Forward Declaration 2.7.1 Forward Declaration ------------------------- It allows us to tell the compiler about existence of an identifier before actually defining it. To write a forward declaration, we use a "funcion declaration" statement (also called function prototype). The function declaration consists of function return type, name, parameters types and/or name and ends with a semicolon. ``` int add(int x, int y); // this a function declaration ``` It is optionally to include the parameter names but it is a best practice to include both parameter names and types as it will help us understanding the function just by looking at the declaration. 2.7.2 Why Forward Declaration ----------------------------- - Most of the time, forward declaration is are used to tell the compiler about some function defined in a different file. 2.7.3 Declarations vs Definition -------------------------------- - Declaration : It tells the compiler about the existence of an identifier, with its information like data type, name of identifier, name of parameters. - Defintion : It is declaration that actually implements the identifier. i.e. it contains declaration + its main content - Pure Declaration : A declaration that isnt a definition 2.7.4 One Defintion Rule (ODR) ------------------------------ 1. Within a "file", each function, variable, type or template in a given scope can only have one definition. Defintions in different scope dont violate this rule. 2. With a "program", each function, variable, type or template in a given scope can only have one definition. 3. Types, templates, inline funcitons, and variables are allowed to have duplicate definition in a different files, so long as definition is identifical. ``` int add(int x, int y) { return x + y; } int add(int x, int y) // violation of ODR, we've already defined function add(int, int) { return x + y; } int main() { int x{}; int x{ 5 }; // violation of ODR, we've already defined x } ``` 2.9 Naming Collisions and Introduction to Namespaces ==================================================== If two identical identifier are in the same program such that compiler or linker cant tell them apart, the compiler or linker will produce an error called "Naming Collision" or "Naming conflict". > If the colliding identifier are in same file, it will be compiler error. > If the colliding identifier are in separate file, it will be linker error. 2.9.1 Scope Regions ------------------- It is an area of source code in which all declared identifiers are distinct from names declared in other scopes. Within the scope, all identifiers must be unqiue. 2.9.2 Namespaces ---------------- Namespaces provides another type of scope region that can be used to declare names inside it so that the name wont be mistaken by for an identical name declard in another scope. Only declarations and definitions can appear in namespaces, No executable statement allowed. However, We can define a function in the namespace and then define executable statments inside the funtion. 2.9.2.1 Global Namespaces ------------------------- Any name that is not defined in a class, function, or a namespace is considered ot be a part of global namespace. This is why executable statment in not allowed outside of functions, since everything is inside global namespace. > Identifiers declared inside the global scope are in scope from the point of declaration to the end of the file. ``` #include // imports the declaration of std::cout into the global scope // All of the following statements are part of the global namespace void foo(); int x; // compiles but strongly discouraged: non-const global variable definition (without initializer) int y { 5 }; // compiles but strongly discouraged: non-const global variable definition (with initializer) x = 5; // compile error: executable statements are not allowed in namespaces int main() // okay: function definition { return 0; } ``` 2.9.2.2 The std Namespace ------------------------- C++ originally didnt have the standard namespace, every identifier inside the standard library was accessible without the std:: prefix (they were part of global namespace) We should use/access std namespace using "scope resolution operator" - "::" The operand on the left is the namespace we are calling. If no operand is used on the left, the namespace defaults to global namespace. eg. std::cout << "Hello"; // we can interpret it something like this: cout of the std namespace Another way of accessing std namespace is by "using directive". using directive allows us to access the names without using the std prefix. eg. using namespace std; // this is "using directive" cout << "Hello"; > "using directive" should be avoided, any identifier we define may conflict with any identically named identifier in the std namespace. 2.10 Introduction to Preprocessor --------------------------------- Prior to compilation, the source file goes through preprocessing phasse. The preprocessor makes changes to the source files and store in memory or temp files. What is changes: - strips out comments, ensures each source file ends in a new line. - processes #include directives 2.10.1 Preprocessor Directives ------------------------------ Preprocessor directives are instructions that start with a "#" symbol and end with a newline. (Not semicolon). These directives tell the preprocessor to perform certain text manipulation tasks. The directives have their own synatax which is very similar to C++ syntax. When preprocessor has completed processing, it result is called "translation unit" 2.10.1.1 #include ----------------- The preprocessor replaces the #include statement with the content of the file which is suffix of #include word. eg. #include // preprocessor will replace this statement with the contents of iostream file. The included content is then preprocessed again, and same cycle repeats recursively. 2.10.1.2 Macro Defines ---------------------- A Macro is a fragment of code which has been given a name. Whenever the name is used, it is replaced by the contents of the macro by the preprocessor. There are two types of macros: 1. Function-Like Macro 2. Object-Like Macro 2.10.1.2.1 Function-Like Macro ------------------------------ Function-like Macros act like fucntions, and serve a similar purpose. They are generally considered unsafe, and almost anything they can do be done by a normal function. 2.10.1.2.2 Object-Like Macro ---------------------------- Object-Like Macros can be defined in 2 ways: #define IDENTIFIER #define IDENTIFIER some-text The top definition has no subsitution text but bottom one has one. This is because, they are directives and not statements. 2.10.1.2.2.1 Object-like macros with substitution text ------------------------------------------------------ Preprocessor replaces the identifier with the substitution text. Idenitfier is typed in all CAPS and _ for spaces. Object-like macros with substitution text were used in C as a way to assign names to literals. There are better ways to do this in C++. This is only used in legacy code now. 2.10.1.2.2.2 Object-like macros without substitution text --------------------------------------------------------- ~~~~~~~~~~~~~~~~~~~~~~SKIPPING THIS CHAPTER 2.10~~~~~~~~~~~~~~~~~~~~~~ 2.11 Header Files ================= Header Files allow us to put declaraions in one file ending with .h extension and then import them wherever we need them. When we #include a file, the pre-processor copies content of the #include file into the file file doing #include. Visual of how header files work: https://www.learncpp.com/images/CppTutorial/Section1/IncludeHeader.png > Best Practice: If a header file is paired with a code file (e.g. add.h with add.cpp), they should both have the same base name (add). > Best Practice: Do not put function or variable definition in header files. It voilates ODR (One Definition Rule) and avoid including .cpp files 2.11.1 Angled Bracket vs Double Quotes -------------------------------------- Angled Brackets tell the pre-processor that we didnt write this file. It will only look into directories specified by the "include directories". Which are set by the compiler. The default "include directories" set by gcc can be viewed using: echo | gcc -xc++ -E -v - Double Quotes tell the pre-processor that we wrote the file. It will look into the current directory, if it can't find it there, it will then look into the "include directories" 2.11.2 Including header from other directories ---------------------------------------------- We can include headers from other directories using the "I" flag: gcc -I header-location Another hacky way to put relative location of the header file in #include line. #include "../../foo/bar.h" #include "test/test2.h" 2.11.3 Header may include other headers --------------------------------------- It is possible to put a #include line in a header. First pre-processor will the include the 1st header and then any other headers inside the previous headers. These additional header files are called "transitive headers" 2.12 Header Guards ================== Headers Guards are used to prevent duplicate definition by telling the pre-processor to not copy header file more than once into a single file. > Duplicate declarations are fine -- but even if your header file is composed of all declarations (no definitions) it’s still a best practice to include header guards. eg. #ifndef identifier-name #define identifier-name // declaration(s) here int add(int x, int y); #endif 2.12.1 pragma once ------------------ Modern compiler support "pragma once". It serves the same purpose as header guards but it is not defined in the C++ standard. Compilers and support for pragma once: https://en.wikipedia.org/wiki/Pragma_once#Portability Only SDCC doesnt have pragma once support, others support it. eg. #pragma once // declaration(s) here int add(int x, int y); 2.13 How to design programs =========================== https://www.learncpp.com/cpp-tutorial/how-to-design-your-first-programs/ 3.1 Syntax Error and Semantic Error (Logical Error) =================================================== 3.1.1 Syntax Error ------------------- Syntax Error occurs when we write a statement which is not valid according to the grammar of C++. 3.1.2 Semantic Error (Logical Error) ------------------------------------ Semantic Error occurs when the program doesnt do what the programmer had intended to. eg. #include int main() { int x; // no initializer provided std::cout << x << '\n'; // Use of uninitialized variable leads to undefined result return 0; } 3.2 The Debugging Process ========================= https://www.learncpp.com/cpp-tutorial/the-debugging-process/ 3.4 Basic Debugging Tactics =========================== 3.4.1 Commenting out your code ------------------------------ First tactic to deal with error is to comment out statement/function and see if the error persist. If it still does, then the commented out code wasnt responsible. > Dont forget to comment out the portion of code you commented after debugging it might cause more problems. An alternate apporach is to use 3rd party library for repeatedly adding/removing and uncommenting/commenting debug statements. This libray will leave the statements in the code and let you compile them out in release mode via a pre-processor macro. "https://github.com/sharkdp/dbg-macro/blob/master/dbg.h" is a header only library which does that. 3.4.2 Validating your code flow ------------------------------- Another problem can that a function is being called too many or too few times or may not even be called. In such cases, it can be helpful to to place statements on top the function to print the function name. So, we can see the function name which are being called. > When printing information for debugging purposes, use std::cerr instead of std:cout. This is because, std:cout is buffered, which means there maybe be a pause between when you ask std:cout to output information and when it actually does. Practical example of this is that, if you output using std::cout but the program crashes immediately afterward, std::cout may or may not have output yet. std::cerr is not buffered, so the it outputs immediately. This is done with a little cost of performance. It is also reccomended to not indent the std::cerr statement so it is easier to point out those debug statement in the code. 3.4.3 Printing Values --------------------- With some type of bug, the program might be calculating or passing wrong value. So, We can output the variables or statements to ensure that those are correct. 3.4.3.1 Why using printing statements to debug isnt great --------------------------------------------------------- - Debugging code clutters your code. - Debugging code clutters your program output. - Debugging requires adding and removing code so it might introduce new bugs. - Debug statements must be removed from the code after debugging so it makes them non-reusable. We can remove some of these issue. We'll explore more about it in the next section 3.5 More Debugging Tactics ========================== 3.5.1 Conditionalizing your debugging code ------------------------------------------ Using conditional pre-processor directives, we can enable and disable through out the program. eg. #include #define ENABLE_DEBUG int main() { #ifdef ENABLE_DEBUG std::cerr << "main() called\n"; // debug statement #endif std::cout << "Enter a number : "; int x{}; std::cin >> x; std::cout << "Entered number : " << x; return 0; } Now we can uncomment/comment the #define ENABLE_DEBUG statement to disable/enable debugging for the program. If this were a multi-file program, we could put the #define ENABLE_DEBUG statment in a header file thats included in all code files. This process removes the requirement of removing or adding debug statement repeatedly. Hence, makes the debug statements reusable. 3.5.2 Using a logger -------------------- We can send to debug information to a log. Log files have a few advantages as all the debug information is written in a file so we can read or send that file to others for diagonosis. Hence, removing the clutter from the output. C++ has output stream named std::clog which can be used for logging, but by default it writes to standard error stream (same as std::cerr). It is easier to use a 3rd party library "https://github.com/SergiusTheBest/plog". Plog is lightweight and supports all platforms and compilers. eg. #include // Step 1: include the logger headers #include #include int getUserInput() { PLOGD << "getUserInput() called"; // PLOGD is defined by the plog library std::cout << "Enter a number: "; int x{}; std::cin >> x; return x; } int main() { plog::init(plog::debug, "Logfile.txt"); // Step 2: initialize the logger PLOGD << "main() called"; // Step 3: Output to the log as if you were writing to the console int x{ getUserInput() }; std::cout << "You entered: " << x << '\n'; return 0; } Logging can be disabled using : plog::init(plog::none , "Logfile.txt"); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~CHAPTER 3 SKIP~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4.1 Introduction to fundamental data types ========================================== 1 byte = 8 bits (standard) 4.1.1 Fundaental Data Types --------------------------- List of fundamental data types are here: https://www.learncpp.com/cpp-tutorial/introduction-to-fundamental-data-types/ 4.2 void ======== void means no type. It is a imcomplete data type. Incomplete data type are those that are declared but not defined. Compiler knows about the exsistence of the data type but doesnt have enough information to determine how much memory is allocate for its object. Incomplete types can not be instantiated. Most common use of void is in function that do not return anything. 4.3 Object sizes and size of operator ===================================== A single object can take more than 1 byte of memory depending upon the type used for the variable. An object with n bits can have 2^n unqiue values. eg. 8-bit byte = 2^8 unqiue values 2 byte = 2^16 unqiue values 4.3.1 Fundaental Data Types --------------------------- C++ standard doesnt define the exact size in bits for any type. char must be 1 byte but it is not standardized whether that 1 byte is 8-bit or not. Size of my machine: bool: 1 bytes char: 1 bytes short: 2 bytes int: 4 bytes long: 8 bytes long long: 8 bytes float: 4 bytes double: 8 bytes 4.3.2 sizeof() operator ----------------------- sizeof() is used to determine the size of data types on particular machine. 4.4 Signed Integers =================== 4 primary fundamental integer types: Type Minimum Size - short int 16 bits - int 16 bits - long int 32 bits - long long int 64 bits > Technically, bool and char are integral types as they store their values as integer values. 4.4.1 Signed Integers --------------------- In signed integers, the sign is stored as a part of the number. Therefore, it signed integers can hold -ve, +ve and 0 as values. 4.4.2 Defining Signed Integers ----------------------------- Preffered way of defining signed integers: short num0; // prefer short instead of additional int int num1; long num2; // prefer long instead of adding an additional int long long num3; // prefer long long instead of adding an additional int We can optionally add "signed" as prefix to the signed int statements. eg. signed int s; 4.4.3 Signed integers range --------------------------- The range of values for signed integers are: Size/Type Range 8-bit Signed -128 to 127 16-bit Signed -32,768 to 32,767 32 bit signed -2,147,483,648 to 2,147,483,647 64 bit signed -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 Mathematical expression for the range: -(2^(n-1)) to 2^(n-1) 4.5 Unsigned Integers ===================== Usigned integers are the type which can only hold +ve and 0 values. eg. unsigned short us; unsigned int ui; unsigned long ul; unsigned long long ull; 4.5.1 Usigned Integer Range --------------------------- Size/Type Range 8-bit unsigned 0 to 255 16-bit unsigned 0 to 65,535 32-bit unsigned 0 to 4,294,967,295 64-bit unsigned 0 to 18,446,744,073,709,551,615 Mathematical expression for range: 0 to (2^n)-1 > It is better to avoid using un-signed type for storing data as it is easy to overflow. since the bottom range is 0. 4.8 Floating point numbers ========================== A floating type can store a number with fractional component (43.12, -2.23,..). The term "floating point" means that it can support a variable number of digits before and after decimal point. There are 3 standard floating point data type: - float - double - long double These types dont have any actual size defined. > Note: floating point literals default to type double. An `f` is used as a suffix to denote a float type. eg. double a {5.0}; // 5.0 is a floating point literal. No "f" suffix so it defaults to double. float b {5.0f}; // 5.0 is a floating point literal. "f" suffix means it is float type. 4.8.1 Printing floating point numbers ------------------------------------- cout << 5.0; // output: 5 cout << 5.0f; // output: 5.0 cout << 5.1; // output: 5.1 By default, std:cout strips out the decimal part if the number after decimal is/are 0. Hence, 1st line prints 5 instead of 5.0 2nd line prints 5.0 because it has a "f" suffix. 3rd line prints 5.1 because number after decimal is non-zero. 4.8.2 Floating point precision ------------------------------ "precision" means how many significant numbers it can represent without information loss. The number of digits of precision depends on both size of type it is being stored in and the particular value being stored (some values can be represted more precisely than others). eg. a float has 6-9 digits of precision, that means it can exactly represent any number with up to 6 significant digits and 7-9 digits may or may not be represted exactly. > float precision : 6-9 double precision: 15-18 eg. cout << 9.87654321f; // 9.87654 cout << 9876543.21f; // 987654 cout << 987.654; // 987.654 4.8.3 Output Manipulators ------------------------- Output mainpulators alter how data is output. Using std::setprecision(n) from "iomanip" header, we can override std::cout default precision. eg. cout << std::setprecision(17); cout << 3.33333333333333333333333333333333333333f; // 3.3333332538604736 cout << 3.33333333333333333333333333333333333333; // 3.3333333333333335 Because we set precision to 17 digits, the above numbers are printed with 17 digits. But, the numbers arent precision to 17 digits because float type are less precision than doubles. Hence, more error. 4.8.4 Rounding errors --------------------- Floating numbers are tricky to work with difference between how binary numbers are stored and decimal numbers. 1/10 can be represted as 0.1 in decimal. In binary, 0.1 is represted as by infinite sequence of 0.0001100...Because of this, we run into precision problems. eg. double d{0.1}; cout << d; // 0.1 cout << setprecision(17); cout << d; // 0.10000000000000001 the second line, the output is wrong because double type truncate the approximation due to its limited memory. Rounding errors may make a number either slightly smaller or larger, depending on where truncation happens. 4.8.5 NaN and Inf ----------------- NaN and Inf are two special types of floating point numbers. Inf: Infinity. Can be +ve and -ve. NaN: Not a number. eg. double zero {0.0}; double positiveInf { 5.0 / zero }; // positive infinity std::cout << posinf; // inf double negativeInf { -5.0 / zero }; // negative infinity std::cout << neginf; // -inf double nan { zero / zero }; // not a number (mathematically invalid) std::cout << nan; // -nan > Nan and Inf are only avaiable in compiler using IEEE 754 format for floating point numbers. > Output may differ from compiler to compiler. 4.9 Boolean Values ================== Boolean are data types which can have only 2 values, true or false. Boolean is a integral type since it store the value as: true = 1 false = 0 4.9.1 Boolean Variables ----------------------- Boolean are evaluated in terms of numbers as well. eg. bool b1; // decalring a boolean bool b2 {true}; // intializing a boolean bool b3 {}; // default initalize to false bool b1 {!true}; // initialized to false bool b2 {!false}; // initialized to true 4.9.2 Printing Boolean Values ----------------------------- When boolean values are printed, std::cout prints "0" for false and "1" for true. eg. cout << true; // 1 cout << false; // 0 It is possible to print the words "true" and "false" instead of number, using "std::boolalpha" manipulator. eg. cout << true; // 1 cout << false; // 0 cout << std::boolalpha; // this wont print anything cout << true; // true cout << false; // false cout << std::noboolalpha; // for turning this feature off 4.9.3 Integer to Boolean Conversion ----------------------------------- 4.9.3.1 Conversion using uniform intialization ---------------------------------------------- Using uniform intialization, it is possible to intialize a variable using integral literals 0 and 1. Other integers throw compilation error. eg. bool a {0}; // intializes okay bool b {1}; // intializes okay bool c {2}; // compilation error 4.9.3.2 Conversion using copy intialization ------------------------------------------- Copy intialization allows implict conversion, which means, we can use 0 and 1 to intialize a bool variable just like before and non-zero integers are converted into true. eg. bool a = 0; // intializes just fine bool b = 1; // intializes just fine bool c = 4; // intializes to true/1 bool d = -1; // intializes to true/1 4.9.4 Inputting Boolean Values ------------------------------ It is not possible to input "true" to a bool variable and expect it to store 1 in it. std::cout only accepts 0 or 1 not false or true. eg. bool a{}; cout << "enter: "; cin >> b; cout << b; This program will print 0 (false) as the value of variable b. Since std::cin only accepts 0 or 1, other input will cause std::cin to fail and assign 0 to the variable. To fix this, we can use std::boolalpha. Enabling std::boolalpha for input will only allow lowercase true and false (case sensitive). 0 and 1 will also no longer be accepted. eg. cin >> boolalpha; cin >> b; cout << boolalpha; cout << b; > Note: boolalpha in cin and cout need to be disabled/enabled seperately. 4.10 Introduction to if statements ================================== eg. if (condition) { // do something if condition is true } else if (condition-2) { // do something if condition-2 is true } else { // do something if none of the above if/else-if statments conditions are true } 4.10.1 Non-Boolean Conditionals ------------------------------- If condition expression is something that doesnt evaluate to a boolean value. In such case, conditional expression is converted to boolean "true" and zero to boolean "false". eg. if (4) { // 4 here doesnt evaluate to a boolean value so, it gets converted to one. in this case, to "true". cout << "hi"; } else { cout << "bye"; } "hi" gets printed since 4 is a non-zero value. 4.11 Chars ========== Char data type stores a single character. A single character can be a letter, symbol, or whitespace. It is a integral data type, means it stores the value in integers. Integers stored by char are interpreted as ASCII character. ASCII codes are b/w 0 - 127. - 0 - 31 : are unprintable characters, these were supposed to control peripheral devices like printer. But now are obselute. - 32 - 126: are printable characters, they represent basic english characters. 4.11.1 Intializing chars ------------------------ eg. char c1 {'a'}; // stored as integer 97 (ascii code for a) char c2 {97}; // it is possible to intialize chars with ascii codes but should be usually avoided 4.11.2 Printing chars --------------------- std::cout prints the char variable as an ASCII character. eg. char c1 {'a'}; // prints a char c2 {98}; // prints b 4.11.3 Escape sequences ----------------------- Escape sequences are special characters which start with a backslash (\) and then followed by a character. These escape sequences have a special meaning. eg. cout << a << "\n" << b; // prints a then on next line, prints b cout << a << "\t" << b; // prints a then tab and then b 4.11.4 Difference between single and double quotes ------------------------------------------------ Text between single quotes are treated as a char literal, which represents a single character. eg. 'a' represents a, '+' is +, and so on. Text between single quotes are treated as a C-style string lietral, which can contain multiple characters. > Best Practice: Single characters should be single quoted, not double. When a is in single quotes, it is a single character literal. But when a is in double quotes, it a string literal containing 'a' and null terminator (2 char array). 4.11.5 Multi-Character Literal ------------------------------ For backwards compatibility reasons, many C++ compilers support multi-character literals. If supported, values of these varies depending upon compiler. Since they are not part of C++ standard, and their value is not strictly defined. eg. cout << '/n'; // prints 12142 on g++. it has a forward slash instead of backward, hence it is treated as a multi-character literal. 4.12 Type Conversion & static_cast ================================== 4.12.1 Type Conversion ---------------------- The process of converting a value from one data type to another is called "type conversion" C++ in most cases will allow to convert from one fundamental type to another. i.e, convert from number datatype to another number data type. (int -> float; int -> double; and vice versa). 4.12.1.1 Implicit Type Conversion --------------------------------- When the compiler does type conversion on our behalf without us explicitly asking, we call it "implict type conversion" There are few cases where implict conversion will throw erorr/warning or is not preffered. Unsafe implict conversions will generate an error when using brace initialization. eg. double d {5}; // okay, int to double safe int x {5.5}; // error: double to int not safe because converting double to int will result in data loss as it will drop the fractional part from doulble 4.12.2 Explicit Type Conversion via static_cast operator -------------------------------------------------------- Explicit type conversion allows us to convert a value from one type to another with the programmer taking full responsibility to the value loss of the data. To use explicit type conversion, we use static_cast eg. static_cast (expression) 4.12.2.1 static_cast to convert char to int ------------------------------------------- It is possible to convert a char ascii code to int as well. eg. char ch{ 97 }; // 97 is ASCII code for 'a' // print value of variable ch as an int std::cout << ch << " has value " << static_cast(ch) << '\n'; 4.12.2.2 Sign Conversions using static_cast ------------------------------------------- Signed integrals can be to unsigned_int and vice versa. eg. int a {5}; unsigned int {static_cast a}; 5. Constants and Strings ======================== 5.1 Constant Variables (Named Constants) ========================================