| Lecture Notes for CSE 701: Foundations of Modern Scientific Programming McMaster University, Fall 2025(Last Updated: 2025-09-25)
 | 
  | Table of contents| 1 | Introduction |  |  | 1.1 | A short overview of programming languages |  |  |  | 1.1.1 | Machine code and assembly language |  |  |  | 1.1.2 | C and C++ |  |  |  | 1.1.3 | Higher-level languages |  |  | 1.2 | Installing the IDE and compiler |  |  |  | 1.2.1 | Installing Visual Studio Code |  |  |  | 1.2.2 | Installing linters |  |  |  | 1.2.3 | Installing a compiler |  |  |  | 1.2.4 | Creating and configuring your first program |  |  |  | 1.2.5 | The JSON configuration files |  |  |  | 1.2.6 | Learning more about Visual Studio Code |  | 2 | Fundamentals of C programming |  |  | 2.1 | Basic C syntax and analysis of the "Hello, World!" program |  |  |  | 2.1.1 | Line-by-line analysis |  |  |  | 2.1.2 | Comments and semicolons |  |  |  | 2.1.3 | Indentation and code formatting |  |  | 2.2 | Variables and integer data types |  |  |  | 2.2.1 | Integers and bit width |  |  |  | 2.2.2 | The integer types |  |  |  | 2.2.3 | Line-by-line analysis and printf format placeholders |  |  |  | 2.2.4 | Width modifiers |  |  |  | 2.2.5 | Declaring and naming variables |  |  |  | 2.2.6 | Initializing variables |  |  |  | 2.2.7 | Constant variables |  |  |  | 2.2.8 | Arithmetic operators on integers |  |  |  | 2.2.9 | Integer overflows |  |  |  | 2.2.10 | Fixed-width integer types |  |  | 2.3 | Selection statements |  |  |  | 2.3.1 | If statements and comparison operators |  |  |  | 2.3.2 | If subtleties and Boolean values |  |  |  | 2.3.3 | The (ternary) conditional operator |  |  |  | 2.3.4 | Switch statements |  |  |  | 2.3.5 | Enumerations |  |  | 2.4 | Iteration statements (loops) |  |  |  | 2.4.1 | While and do-while loops |  |  |  | 2.4.2 | Controlling loop evaluation |  |  |  | 2.4.3 | For loops |  |  |  | 2.4.4 | Nested loops |  |  |  | 2.4.5 | Variable scope |  |  | 2.5 | Arrays |  |  |  | 2.5.1 | Declaring arrays |  |  |  | 2.5.2 | Initializing arrays to zeros |  |  |  | 2.5.3 | Accessing array elements out of range |  |  |  | 2.5.4 | Multi-dimensional arrays |  |  |  | 2.5.5 | Characters and strings |  |  | 2.6 | Functions |  |  |  | 2.6.1 | Defining functions |  |  |  | 2.6.2 | Constant vs. non-constant arguments |  |  |  | 2.6.3 | Recursion |  |  |  | 2.6.4 | Forward declarations |  |  |  | 2.6.5 | Mutual recursion |  |  |  | 2.6.6 | Global variables |  |  |  | 2.6.7 | Static variables |  | 3 | Advanced topics in C programming |  |  | 3.1 | Debugging with Visual Studio Code |  |  |  | 3.1.1 | Getting ready for debugging |  |  |  | 3.1.2 | Breakpoints and the debug toolbar |  |  |  | 3.1.3 | Conditional breakpoints and logpoints |  |  |  | 3.1.4 | Variables, watches, and the call stack |  |  |  | 3.1.5 | Using the debug console |  |  |  | 3.1.6 | Further reading about debugging |  |  | 3.2 | Floating-point data types |  |  |  | 3.2.1 | Floating-point numbers and bit width |  |  |  | 3.2.2 | Single, double, and extended precision |  |  |  | 3.2.3 | Entering and printing floating-point numbers |  |  |  | 3.2.4 | Rounding errors |  |  |  | 3.2.5 | Commutativity, associativity, and distributivity |  |  |  | 3.2.6 | Special values |  |  |  | 3.2.7 | Diving deeper into the floating point representation |  |  |  | 3.2.8 | Common mathematical functions |  |  |  | 3.2.9 | Complex numbers |  |  | 3.3 | Type conversion |  |  |  | 3.3.1 | Implicit conversion |  |  |  | 3.3.2 | Explicit conversion: type casting |  |  | 3.4 | Pointers |  |  |  | 3.4.1 | Memory addresses and the stack |  |  |  | 3.4.2 | Using pointers |  |  |  | 3.4.3 | Constant pointers vs. pointers to constant variables |  |  |  | 3.4.4 | Arrays and pointers |  |  |  | 3.4.5 | Functions and pointers |  |  |  | 3.4.6 | Passing arrays to functions |  |  |  | 3.4.7 | Passing multi-dimensional arrays to functions |  |  |  | 3.4.8 | Jagged arrays |  |  | 3.5 | Dynamic memory allocation |  |  |  | 3.5.1 | Allocating memory in the heap |  |  |  | 3.5.2 | Proper use of malloc and calloc |  |  |  | 3.5.3 | Proper use of realloc |  |  | 3.6 | Input and output |  |  |  | 3.6.1 | Input from the command line |  |  |  | 3.6.2 | Input from the terminal during run time |  |  |  | 3.6.3 | Input from a file |  |  |  | 3.6.4 | Output to a file |  |  | 3.7 | Structs and typedefs |  |  |  | 3.7.1 | Structs |  |  |  | 3.7.2 | Arrays of structs |  |  |  | 3.7.3 | Structures as pointers |  |  |  | 3.7.4 | Typedef |  |  | 3.8 | Further reading |  | 4 | Introduction to C++ |  |  | 4.1 | Some new features of C++ |  |  |  | 4.1.1 | The "Hello, World!" program |  |  |  | 4.1.2 | Using namespaces |  |  |  | 4.1.3 | Reserved keywords |  |  |  | 4.1.4 | Function overloading |  |  |  | 4.1.5 | Constant expressions |  |  |  | 4.1.6 | The cin object |  |  |  | 4.1.7 | Namespaces revisited |  |  |  | 4.1.8 | Default function arguments |  |  | 4.2 | Pointers and references |  |  |  | 4.2.1 | References |  |  |  | 4.2.2 | Functions and references |  |  |  | 4.2.3 | Improving performance with (constant) references |  |  |  | 4.2.4 | Range-based for loops and references |  |  |  | 4.2.5 | Null pointers |  |  | 4.3 | Vectors and strings |  |  |  | 4.3.1 | Vectors |  |  |  | 4.3.2 | Strings |  | 5 | Classes and object-oriented programming |  |  | 5.1 | Classes |  |  |  | 5.1.1 | Introduction to classes |  |  |  | 5.1.2 | Classes and structures |  |  |  | 5.1.3 | Member functions |  |  |  | 5.1.4 | Constructors |  |  |  | 5.1.5 | Exceptions: try-throw-catch |  |  |  | 5.1.6 | Invariants, private members, and encapsulation |  |  |  | 5.1.7 | Constructors for the triangle class |  |  |  | 5.1.8 | Separating member functions from the class |  |  |  | 5.1.9 | Inline functions |  |  |  | 5.1.10 | Splitting a project into separate files |  |  |  | 5.1.11 | Const vs. non-const member functions |  |  |  | 5.1.12 | Enumeration classes |  |  |  | 5.1.13 | Static class members |  |  | 5.2 | Operator overloading |  |  |  | 5.2.1 | Introduction to operator overloading |  |  |  | 5.2.2 | Overloading the << operator |  |  |  | 5.2.3 | Overloading the == and != operators |  |  |  | 5.2.4 | Overloading the + and += operators |  |  |  | 5.2.5 | Overloading the - and -= operators |  |  |  | 5.2.6 | Overloading the * operator |  |  |  | 5.2.7 | Combining fundamental and user-defined types |  |  |  | 5.2.8 | The complete vector overloads: a header-only library |  |  |  | 5.2.9 | Summary: the matrix class |  |  | 5.3 | Input and output stream classes |  |  |  | 5.3.1 | Formatting output |  |  |  | 5.3.2 | I/O streams and files |  |  |  | 5.3.3 | File stream modes |  |  |  | 5.3.4 | Seeking |  |  |  | 5.3.5 | String streams |  |  |  | 5.3.6 | Buffered output |  |  |  | 5.3.7 | I/O error handling |  |  |  | 5.3.8 | Reading and writing binary files |  |  | 5.4 | Class friendship and inheritance |  |  |  | 5.4.1 | Friend functions |  |  |  | 5.4.2 | Uses for friend functions |  |  |  | 5.4.3 | Inheritance and derived classes |  |  |  | 5.4.4 | Deriving from a derived class |  |  |  | 5.4.5 | Deriving from two base classes |  |  |  | 5.4.6 | Protected members |  |  |  | 5.4.7 | Virtual functions |  |  |  | 5.4.8 | Creating custom exceptions |  | 6 | Numerical aspects of C++ |  |  | 6.1 | The C++ numerics library |  |  |  | 6.1.1 | Mathematical functions |  |  |  | 6.1.2 | Numeric algorithms |  |  |  | 6.1.3 | The complex number class |  |  |  | 6.1.4 | Mathematical constants |  |  | 6.2 | Random numbers |  |  |  | 6.2.1 | True random number generation |  |  |  | 6.2.2 | Pseudo-random number generation |  |  |  | 6.2.3 | Probability distributions |  | 7 | Templates and the standard template library |  |  | 7.1 | Templates |  |  |  | 7.1.1 | Introduction to templates |  |  |  | 7.1.2 | Function template example: the vector operator overloads |  |  |  | 7.1.3 | Class template example: the matrix class again |  |  |  | 7.1.4 | The standard template library |  |  | 7.2 | The array container: static (fixed-size) contiguous array |  |  |  | 7.2.1 | Introduction to STL arrays |  |  |  | 7.2.2 | Iterators |  |  |  | 7.2.3 | Performance and memory considerations with STL arrays |  |  |  | 7.2.4 | The vector operator overloads for arrays |  |  | 7.3 | The vector container: dynamic contiguous arrays |  |  |  | 7.3.1 | Introduction to STL vectors |  |  |  | 7.3.2 | Iterators and iterator invalidation |  |  |  | 7.3.3 | Interlude: measuring performance with chrono |  |  |  | 7.3.4 | Performance considerations with vectors |  |  | 7.4 | Other sequence containers: deque, forward_list, and list |  |  |  | 7.4.1 | Templates of templates: the auto keyword |  |  |  | 7.4.2 | The deque container: double-ended queue |  |  |  | 7.4.3 | The forward_list and list containers: singly-linked and doubly-linked list |  |  | 7.5 | Associative containers: sets and maps |  |  |  | 7.5.1 | Sets |  |  |  | 7.5.2 | Maps |  |  | 7.6 | The standard template library: algorithms |  |  |  | 7.6.1 | General syntax and lambda expressions |  |  |  | 7.6.2 | Non-modifying sequence operations |  |  |  | 7.6.3 | Modifying sequence operations |  |  |  | 7.6.4 | Other useful algorithms |  |  |  | 7.6.5 | Algorithms from the numerics library |  |  | 7.7 | Further reading on C++ |  | 8 | Development and collaboration tools |  |  | 8.1 | Optimizing your code |  |  |  | 8.1.1 | Compiler optimizations |  |  |  | 8.1.2 | Comparing execution time with different compiler optimizations |  |  |  | 8.1.3 | Writing optimized code |  |  | 8.2 | Configuring Visual Studio Code tasks |  |  |  | 8.2.1 | The tasks.json file |  |  |  | 8.2.2 | The launch.json file |  |  |  | 8.2.3 | Setting up additional tasks for your project |  |  |  | 8.2.4 | Using shell scripts |  |  | 8.3 | Customizing the compilation process |  |  |  | 8.3.1 | The stages of compilation |  |  |  | 8.3.2 | Preprocessor directives |  |  |  | 8.3.3 | Preventing double inclusion of header files |  |  |  | 8.3.4 | Using CMake |  |  |  | 8.3.5 | Customizing CMake |  |  | 8.4 | Documentation |  |  |  | 8.4.1 | Creating documentation using Markdown |  |  |  | 8.4.2 | Documenting your code using Doxygen |  |  | 8.5 | Version control |  |  |  | 8.5.1 | Installing and configuring Git |  |  |  | 8.5.2 | Using Git |  |  |  | 8.5.3 | Branching |  |  |  | 8.5.4 | Using GitHub |  | 9 | Advanced memory management in C++ |  |  | 9.1 | Dynamic memory allocation and related member functions |  |  |  | 9.1.1 | The new and delete operators |  |  |  | 9.1.2 | Avoiding memory leaks |  |  |  | 9.1.3 | Destructors |  |  |  | 9.1.4 | The matrix class template with manual memory allocation |  |  |  | 9.1.5 | Copy constructors |  |  |  | 9.1.6 | Overloading the assignment operator |  |  |  | 9.1.7 | Move constructors and move assignments |  |  |  | 9.1.8 | The full code for manually allocated matrices |  |  |  | 9.1.9 | emplace and emplace_back |  |  | 9.2 | Memory debugging and smart pointers |  |  |  | 9.2.1 | Memory debugging with Dr. Memory |  |  |  | 9.2.2 | "Resource Acquisition Is Initialization" |  |  |  | 9.2.3 | Smart pointers |  |  |  | 9.2.4 | Performance of smart pointers |  |  |  | 9.2.5 | The matrix class template with smart pointers |  | 10 | Epilogue | 
 | 
| This course is intended to give the students a good understanding of computing, solid programming skills in C and C++, experience with debugging, optimization, algorithm design, and numerical calculations, and the ability to produce high-quality and well-organized scientific software, both on their own and in collaboration with others. Please see the table of contents of the lecture notes for a detailed outline of the course. This is an advanced graduate-level course, and is not intended to be a first introduction to programming. Therefore, students must possess basic prior knowledge of some programming language (any language will do) in order to take this course. | 
|  | 
| There are hundreds of programming languages, and they can be categorized according to their level, which roughly indicates how close the language is to the way the computer actually operates at the hardware level. The lowest-level language is machine code, the binary instructions that are executed directly by the CPU. However, machine code is intended for CPUs, not for humans, and programmers never write machine code directly, which would be an extremely tedious task. Instead, code is generally written in higher-level languages, and this code is then automatically translated to machine code so that it can actually run on the CPU. A slightly higher-level language is assembly language, which is basically a way to write machine code directly in a language that humans can understand. Assembly language first appeared in 1949,  years ago! Assembly code still tells the CPU exactly what to do and how to do it - but with human-readable characters instead of binary codes. Theoretically, you could write your programs in assembly, but that would be almost as tedious as writing machine code. Furthermore, both assembly language and machine code differ from one CPU type to another; if you write code that will run on a particular CPU, and you want to run it on a different CPU, you will need to rewrite it. | 
| The next higher level after assembly is that of C and similar languages. C is one of the oldest programming languages in common use today, created in 1972,  years ago. This language adds a level of abstraction on top of assembly language; instead of telling the CPU directly what to do, you can define abstract entities such as variables and functions, and use them in human-readable statements that resemble spoken language. A program called the compiler then translates these abstract statements to machine code. This has the great advantage that you can write one C program and then use different compilers to compile the exact same program to machine code for a variety of different CPUs. You can think of C as "portable assembly". C is essentially the lowest-level language humans can efficiently write code in, and it provides the programmer with direct, low-level access to the computer's hardware. For this reason, it is often used to write the operating systems on which other programs run, the device drivers which provide an interface between software and hardware, and the compilers and interpreters which translate other programming languages (including all the ones listed below!) to machine code. Embedded systems in a variety of devices, from digital watches to electric cars, also use C almost exclusively due to their very limited resources, as higher-level programming languages generally require more resources to operate. A slightly higher level is that of C++ and similar languages. C++ is basically an extension of C which adds object-oriented programming, and it was created in 1985,  years ago. C++ adds an additional level of abstraction over C by allowing the programmer to define abstract entities called classes. We will see exactly what this means when we learn C++ later in the course. C++ is generally preferable to C for large-scale applications which can benefit from the increased abstraction. C and C++ are sometimes called "mid-level languages" because they are located in the middle between assembly language and higher-level languages. While higher-level programming languages offer more abstraction, which often simplifies the language and shortens development time, they are also slower and less flexible. Programs written in C and C++ generally run much faster than programs written in higher-level programming languages, and therefore they are especially suitable for applications where performance is of the utmost importance, including, of course, scientific computing.  Warning: The unmatched speed of C and C++ comes at a cost. Since they allow the programmer direct access to hardware components such as memory, it is very easy to make mistakes such as accessing the wrong memory address, which would be impossible to do in higher-level languages.  | 
| At the next higher level we have Java, C# (pronounced "C sharp"), and similar languages. These languages are newer (Java appeared in 1995 and C# in 2000), and they don't allow the programmer as much freedom or flexibility as C and C++ do. For example, instead of allowing direct access to the computer's memory, Java and C# manage the memory themselves; this is also called garbage collection. Although this can prevent problems such as accessing the wrong memory address, it also incurs a performance penalty. Finally, at the highest level we have interpreted languages such as Python and JavaScript. The languages we discussed up until now are compiled languages, which means the entire code is first translated to machine code using a compiler, and only then can be executed. In contrast, interpreted languages run using an interpreter, which reads the source code and executes it line-by-line. This has the advantage of being able to instantly run the same source code on any platform using a suitable interpreter, whereas compiled languages require compiling the source code separately for each type of CPU and operating system.  Warning: Although interpreted languages have many benefits in terms of ease of use, they are significantly slower and use much more resources compared to compiled ones. The difference in speed and/or memory usage between a Python program and a C program that does the same thing can sometimes be several orders of magnitude!  The programming languages we mentioned here - C, C++, Java, C#, Python, and JavaScript - are considered to be the most popular programming languages right now. However, there are hundreds of other languages, including some that are specifically intended for scientific computing, such as Mathematica. In this course, we will focus on C and C++, since they are the fastest and thus most suitable for scientific programming. | 
|  | 
| Throughout this course, we will be using the Visual Studio Code IDE (Integrated Development Environment). It provides a convenient GUI (Graphical User Interface) for writing programs in a variety of programming languages, as well as incredibly useful tools such as syntax highlighting, code completion, automatic code formatting, debugging, version control, and much more. Visual Studio Code is one of the most popular IDEs, used by millions of developers, and has a huge selection of extensions that provide additional functionality. It is also cross-platform, so you can use it on either Windows, Linux, or Mac. To install the IDE, visit the website, click on the "Download" button, and run the downloaded file. The 64-bit version, which is the one you should be using, will download by default. During installation, in the "Select Additional Tasks" page, make sure to select the following options:  Add an "Open with Code" action to file and directory context menus.Register Code as an editor for supported file types.Add to PATH. After you finish installing the app, launch it and click on View > Extensions in the menu bar. You can also use the keyboard shortcut Ctrl+Shift+X (note that the keyboard shortcuts I will be using here are for Windows and Linux, but on Mac you usually just need to replace Ctrl with Cmd). Yet another option is to click on the Extensions icon, which looks like four squares and should be the bottom one in the bar to the left. In the Extensions side bar, type "C++" and you should find the C/C++ extension by Microsoft. Simply click on the "Install" button to install the extension, which will allow you to use the IDE to write, compile, and debug C/C++ code. Alternatively, you can use this link to install the extension directly from your browser. This is the only extension you need if you plan to use the GCC or MSVC compilers. However, if you wish to use the Clang compiler, which is the one I recommend for this course, you will also need to install the extension CodeLLDB. | 
| It is highly recommended to use static code checkers, also known as "linters", to check for common programming mistakes in your code. The two tools I recommend the most are:  Clang-tidy, which is bundled with the official C/C++ VS Code extension that you already installed in the previous section. To enable it, press Ctrl+, to open the Settings page (or choose File > Preferences > Settings in the menu), then search for the setting C_Cpp.codeAnalysis.clangTidy.enabled.Cppcheck, which is bundled with the cpp-check-lint VS Code extension. However, make sure to disable the cpplint tool, which is also bundled with the same extension, by disabling the setting cpp-check-lint.cpplint.--enable. The reason is that cpplint is used to check that your code satisfies the Google style guide, which is not useful unless you work for Google.Note that the bundled version is often not the latest version. I recommend downloading the latest version of cppcheck here and installing it separately; the VS Code extension will automatically use the latest version if it is installed. | 
| The next step is to install a C/C++ compiler. There are plenty of compilers out there, but the most popular ones are:  For Windows: Clang (which is part of the LLVM project), GCC (the GNU Compiler Collection), or MSVC (Microsoft Visual C++).For Linux: Clang or GCC.For macOS: Only Clang. Since students using macOS are restricted to using Clang only, all students are encouraged to use Clang, so that everyone in the class uses the same compiler. However, students using Windows and Linux are also free to use GCC, which is highly compatible with Clang. (It is technically possible to install GCC on macOS, but it's unnecessarily complicated, so I recommend not bothering with it at this point.) I do not recommend using MSVC in this course; it's a perfectly good compiler, but it is unique to Windows, and has a different command line syntax than the other two compilers, so any compiler commands used in these lecture notes (e.g. to enable warnings) will not work on MSVC. However, when you write your own C++ projects, I do recommend to always test them on Windows using all 3 compilers, to ensure maximum compatibility. Installing Clang on Windows In this course we will install Clang through the MSYS2 platform. Download the latest installer for your CPU architecture: x64 if you have an Intel or AMD CPU, or arm64 if you have an ARM-based CPU, such as Qualcomm Snapdragon. Run the installer and accept the default installation directory, C:\msys64(since these notes will assume it's installed there). When the installer finishes, uncheck "run MSYS2 now". Go to the Start Menu and run MSYS2 CLANG64 (or MSYS2 CLANGARM64 if you have an ARM-based CPU). In the terminal window that opens, first update the packages by running the following command: pacman -Suyy --noconfirm
 This might close the terminal window, since it may update the terminal itself. If it does, just reopen it. Continue to run the update command until it says "there is nothing to do" (usually it takes at least 2 runs). Then install the Clang toolchain by running the following command: pacman -S mingw-w64-clang-x86_64-toolchain --noconfirm
 To verify that Clang was installed correctly, type the following command: clang -v
 This should display a few lines of information about the Clang version and its installation directory, which should be C:\msys64\clang64\bin. To use Clang in VS Code or from the Windows Terminal, you need to add this directory to thePATHenvironment variable. To do that, open the Start Menu, search for "Environment Variables", and choose "Edit the system environment variables". In the window that opens, click on "Environment Variables..." at the bottom right. In the next window, double click on thePathuser variable, then click "New" and add the pathC:\msys64\clang64\bin. To check that this worked, open a new Windows Terminal (or PowerShell/Command Prompt window) and type clang -vagain. You are now ready to use Clang on Windows! Installing Clang on Linux To install Clang on Linux, an easy one-line installation script is provided on the LLVM website. Simply open the terminal and run the following command: sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"
 This should install the latest version. Type clang -vto verify that it works. If it doesn't, you may have to link theclangcommand to the specific version that was installed. You can do that by typingCLANG_VER=Xin the terminal, whereXis the first number in the version numberX.Y.Zof the latest release, and then running the following command: sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-$CLANG_VER 100 --slave /usr/bin/clang++ clang++ /usr/bin/clang++-$CLANG_VER
 Installing Clang on macOS On macOS, Apple Clang will be installed automatically if you type clangin the terminal. However, Apple Clang is not the same as Clang, and does not support more advanced features such as C++20 modules. Therefore, macOS users should install the latest version of Clang using Homebrew. First, install Homebrew by running the following command in the terminal: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
 Then install Clang by typing the following command: brew install llvm
 To make Clang the default compiler instead of Apple Clang, make sure you run these commands in the terminal: echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.zshrc
echo 'export LDFLAGS="-L/usr/local/opt/llvm/lib"' >> ~/.zshrc
echo 'export CPPFLAGS="-I/usr/local/opt/llvm/include"' >> ~/.zshrc
 Alternatively, you can also edit the file .zshrcin your home directory and add theexportcommands manually. Note: You may need to modify these commands if LLVM was installed in a different folder. When you install the package, it will let you know where it was installed and exactly what commands you need to run. Now, restart the terminal, and type clang -vto check that you have the latest version and that it says "Homebrew clang" instead of "Apple clang". If it still says "Apple clang", please consult the Internet or your favorite LLM for a solution; otherwise, you may have to use Clang from the Homebrew installation folder directly. Usually, this folder is /usr/local/opt/llvm/bin/, so the C compiler will be/usr/local/opt/llvm/bin/clangand the C++ compiler will be/usr/local/opt/llvm/bin/clang++. Note: To run and debug your compiled programs, you may have to execute the command sudo DevToolsSecurity -enablein the terminal. | 
| After installing the compiler, first create a new empty folder in which to save your code, e.g. a folder named CSE701in your home directory. Now open Visual Studio Code, click on File > Open Folder... or press Ctrl+K Ctrl+O, and open the folder you created (you can also create the folder directly from the "Open Folder" window). Alternatively, if you enabled the option to add an "Open with Code" action to the context menu during installation, you should be able to simply right-click the folder and choose "Open with Code". If you get a window that asks "do you trust the authors of the files in this folder", select "yes" (unless you don't trust yourself). You should now see the folder in the Explorer side bar, which is accessible by clicking the top button on the left, or choosing View > Explorer in the menu, or pressing Ctrl+Shift+E. However, it does not have any files yet. Next, right-click in an empty space of the Explorer side bar and select "New File". Name your file hello.c. The file will be automatically opened in the editor (if not, just double-click on it). Copy and paste the following code into the file: #include <stdio.h>
int main(void)
{
    printf("Hello, World!\n");
}
 Click File > Save or Ctrl+S to save the file. This is the source code for the program, but we still need to tell VS Code how to compile it. Go to View > Command Palette... or press F1 or Ctrl+Shift+P. This will open the Command Palette. Start writing "config" and choose the option C/C++: Edit Configurations (UI) when it appears. Under "Compiler path", choose the path to the clangbinary (it will beclang.exeon Windows), and under "IntelliSense mode", choose<your OS>-clang-<your architecture>, where the architecture should bex64for Intel or AMD CPUs orarm64for ARM-based CPUs such as Qualcomm Snapdragon or Apple Silicon. For C standard, choosec23. You will notice that a folder named .vscodehas been created in your workspace folder, with a file namedc_cpp_properties.jsoninside it. If you double-click on that file in the Explorer side bar, you will see that the contents correspond to the configuration you chose. Now, with hello.copen in the active editor, click on Terminal > Configure Default Build Task, or open the Command Palette by pressing F1 and look up "build" to find that command. Then choose the option withclangfrom the drop-down menu. A file namedtasks.jsonwill be created in the.vscodefolder, and opened automatically in the editor. This file contains information about how to compile your code. By default, the argsfield intasks.jsonshould look something like this (here I am showing how it looks on Windows): "args": [
    "-fcolor-diagnostics",
    "-fansi-escape-codes",
    "-g",
    "${file}",
    "-o",
    "${fileDirname}\\${fileBasenameNoExtension}.exe"
],
 Here is what the arguments mean:  -fcolor-diagnostics: Print diagnostics (such as errors and warnings) in color, which makes them easier to read.-fansi-escape-codes: Use ANSI escape codes for coloring the output. This option is only used on Windows, but seems to be added by the VS Code C/C++ extension even on other platforms. If you do not see it, you do not need to add it.-g: Include debugging information in the compiled program. We will learn more about debugging later.${file}: The name of the currently active file in the editor, for examplehello.c.-o: Indicates that the next argument will be the name of the output file, that is, the compiled executable.${fileDirname}\\${fileBasenameNoExtension}.exe: The name of the output file. ${fileDirname}is the directory in which the active file is located.The double slash \\will be converted to a single slash\in the output (this is because\followed by another character has a special use, so if we want to use\on its own, we must type it twice).${fileBasenameNoExtension}is the name of the active file without its extension (for example,helloin the case ofhello.c)..exeis the extension for executable files on Windows. On Linux and macOS, executable files have no extension, and paths use forward slash instead of back slash, so you will see ${fileDirname}/${fileBasenameNoExtension}instead for the last argument. Let us add some more compiler arguments (also called flags). First, the arguments -Wall,-Wextra,-Wconversion,-Wsign-conversion, and-Wshadowwill instruct the compiler to warn us about many different types of common mistakes. VS Code will then display these warnings right in the editor. This is an incredibly useful feature, which will automatically detect and help prevent potential bugs in our code.  Warning: Never develop any C or C++ program without turning on all of these warning flags in the compiler!!!  In addition, -Wpedanticand-std=c23will instruct Clang to comply with the ISO C23 standard (the most recent standard, published in 2024), which will ensure that our program is maximally portable and can be compiled on other standards-complying compilers without too much hassle. Finally, on Windows only, we should add the flag -D__USE_MINGW_ANSI_STDIO=1, which will ensure that printing to the terminal follows the C standard. The end result (on Windows) should look similar to this: "args": [
    "-fcolor-diagnostics",
    "-fansi-escape-codes",
    "-g",
    "${file}",
    "-o",
    "${fileDirname}\\${fileBasenameNoExtension}.exe",
    "-Wall",
    "-Wextra",
    "-Wconversion",
    "-Wsign-conversion",
    "-Wshadow",
    "-Wpedantic",
    "-std=c23",
    "-D__USE_MINGW_ANSI_STDIO=1"
],
 I highlighted the new text. Make sure that each argument, both the old ones that were already there and the new ones that you added, is inside quotes, on a line of its own, and with a comma at the end of the line, except for the last one. Then save tasks.json. Note that the first few lines may look different if you're using a different OS, but what's important is that you add the new arguments at the end as shown in the highlighted text. With the hello.ctab open in the editor, click on the "Run and Debug" on the left (it looks like a "play" button with a little bug next to it), or press Ctrl+Shift+D. This will open the Run view, used for debugging. Click on "create a launch.json file", and choose "CodeLLDB" from the drop-down menu. (Note: The option should be "CodeLLDB", not "C++ (GDB/LLDB)".) If you don't see the "CodeLLDB" option, make sure you installed the CodeLLDB extension as instructed above. Note: Sometimes CodeLLDB is buggy, and will not create the launch.jsonfile, or will create one without any suitable configuration. In that case, choose "C++ (GDB/LLDB)" from the menu instead. Then click "Add Configuration..." at the bottom of thelaunch.jsonfile and choose "CodeLLDB: Launch". This file is not usable yet. You need to go back to tasks.jsonand find the compiler argument containing the name of the executable file to be created, which is the one following the"-o"argument. On Windows, it will be"${fileDirname}\\${fileBasenameNoExtension}.exe", and on other platforms it will be similar but with forward slashes and without the.exeextension, i.e."${fileDirname}/${fileBasenameNoExtension}". Copy that file name, go back tolaunch.json, and paste it after"program":, instead of the default value. Make sure to keep the quotes and the comma at the end of the line. Finally, in launch.json, go to the last line for the new configuration in the "configurations" section, which is the line before the curly brackets, which should say"cwd": "${workspaceFolder}". Add a comma at the end of this line and press Enter. Then write a quotes character". VS Code will automatically show a list of options; choose the optionpreLaunchTask, and then choose the name of the build task you created, which should be "C/C++: clang.exe build active file" (without the.exeon Linux or macOS). This should add the following line: "preLaunchTask": "C/C++: clang.exe build active file"
 | 
| For your convenience, here are examples for how the three JSON files in the .vscodefolder should look like. These examples will work on Windows, as long as the compiler has been added to thePATHenvironment variable as explained in the previous section. Later in the course we will learn more about how to customize these files. (Comments following//characters were added by me.) c_cpp_properties.json:
 {
    "configurations": [
        {
            "compilerPath": "C:/msys64/clang64/bin/clang.exe", // Will be /usr/bin/clang or similar for Linux/macOS
            "cppStandard": "c++23",
            "cStandard": "c23",
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "intelliSenseMode": "windows-clang-x64", // OS and architecture will be different for Linux/macOS and/or ARM CPUs
            "name": "Win32" // Will be a different name for Linux/macOS
        }
    ],
    "version": 4
}
 tasks.json:
 {
    "tasks": [
        {
            "args": [
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.exe", // Will be ${fileDirname}/${fileBasenameNoExtension} for Linux/macOS
                "-Wall",
                "-Wextra",
                "-Wconversion",
                "-Wsign-conversion",
                "-Wshadow",
                "-Wpedantic",
                "-std=c23",
                "-D__USE_MINGW_ANSI_STDIO=1" // Only needed on Windows
            ],
            "command": "C:/msys64/clang64/bin/clang.exe", // Will be /usr/bin/clang or similar for Linux/macOS
            "detail": "compiler: C:/msys64/clang64/bin/clang.exe", // Will be /usr/bin/clang or similar for Linux/macOS
            "group": {
                "isDefault": true,
                "kind": "build"
            },
            "label": "C/C++: clang.exe build active file", // Will be clang (without .exe) for Linux/macOS
            "options": {
                "cwd": "C:/msys64/clang64/bin" // Will be /usr/bin or similar for Linux/macOS
            },
            "problemMatcher": [
                "$gcc"
            ],
            "type": "cppbuild"
        }
    ],
    "version": "2.0.0"
}
 launch.json:
 {
    "configurations": [
        {
            "args": [],
            "cwd": "${workspaceFolder}",
            "name": "Launch",
            "preLaunchTask": "C/C++: clang.exe build active file", // Will be clang (without .exe) for Linux/macOS
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe", // Will be ${fileDirname}/${fileBasenameNoExtension} for Linux/macOS
            "request": "launch",
            "type": "lldb"
        }
    ],
    "version": "0.2.0"
}
 Now it is finally time to run our Hello World program! With hello.copen in the active editor, press F5 or choose Run > Start Debugging in the menu. On the bottom of the page, select the Terminal panel; if you can't see it, click View > Terminal or Ctrl+`. You should see the message"Hello, World!"there. Congratulations, you just compiled, ran, and (sort of) debugged your first C program! To run it again, simply press F5 again when you are in the editor tab for hello.c. You should also try changing the program a bit and see what happens when you run it. | 
| To learn more about how to use the IDE, you can use the following resources:  The documentation, also accessible within VS Code via Help > Documentation, contains a lot of information.The Tips and Tricks page (Help > Tips and Tricks) contains tips, tricks, and keyboard shortcuts which could save you a lot of time and effort.A basic tutorial is also provided within VS Code itself, under Help > Interactive Playground.Video tutorials are available under Help > Introductory Videos.Under Help > Keyboard Shortcuts Reference, you can find a printable PDF file with some important keyboard shortcuts.A full list of all available keyboard shortcuts can be viewed by going to View > Command Palette... (or pressing F1 or Ctrl+Shift+P) and choosing "Preferences: Open Keyboard Shortcuts". This list is also accessible via the shortcut Ctrl+K Ctrl+S. | 
| This chapter and the next one will serve two purposes: to teach you the basics of C, and to provide an easily-accessible reference for the rest of the course. I have decided to structure it in this way because I expect most students to already have some programming experience, if not in C then in some other programming language, and thus be familiar with concepts such as variables, selection and iteration statements, and so on. Therefore, instead of giving just a short explanation of each concept, I present each concept in full detail, focusing on the precise syntax and common mistakes unique to C, as well as the underlying structure, such as how variables are stored in memory.  Warning: You can't learn to code just by reading - you have to do some actual coding! During this course, you will be required to submit several programming projects. In addition, you should also take the initiative and write some small programs on your own to practice what you learned in each lecture. Finally, you should run the examples provided throughout these notes on your own system and try to modify them in various different ways to see what happens.  | 
|  | 
| Now that we have installed our IDE and compiler, and ran our first program, let us go over the lines in the program and explain what they do. The first line is: #include <stdio.h>
 This line instructs the C compiler to read the header file stdio.h, which contains definitions used in the C Standard Input and Output Library, and include those definitions in our program. We need to do this in order to print text to the terminal using theprintffunction, which is included in this library. Note thatstdio.hdoes not contain the functionprintfitself, only its definition. The second line is: int main(void)
 In C, functions are declared by first stating what kind of value the function returns as output (in this case: int), then the name of the function (in this case:main), and finally, in parentheses, any parameters that the function may take as input (in this case:void).  The mainfunction is a special function which must be included in all C programs, and it contains the code that is executed when the program starts - i.e., the main part of the program.intmeans integer, and therefore themainfunction - and thus, the program itself - returns an integer value as output, with 0 indicating that there were no errors. This is the standard way in which all C programs must be defined, and a value of 0 is automatically returned if the program finished executing successfully. Generally, you don't have to worry about the returned value unless you call your program from another program or script which needs to make use of this value.Inside the parentheses, voidmeans that the functionmain- and thus, the program itself - does not take any values as input. Often, programs get their input from the command line; we will see later how to declaremainin this case. Programs which employ a GUI (Graphical User Interface) can instead get their input using menus, buttons, and other interactive visual components. The third line is just a curly bracket {. Curly brackets (or braces) indicate that a code block (or compound statement) is starting. Any code that is entered between the opening bracket{and the closing bracket}will belong to that code block - in this case, the code block defining themainfunction and thus the main part of the program. The fourth line is:     printf("Hello, World!\n");
 Let us deconstruct this line carefully:  printfis a function to print (f)ormatted text to the terminal. Here, we did not use the "formatted" part, we just printed a single string of text. Soon, we will see how to output other types of values withprintf.The parentheses (and)contain the arguments of the functionprintf. In this case, there is just one argument: the string to be printed.The quotation marks "contain a string. A string is simply a sequence of characters enclosed within quotation marks. In this case, the string is"Hello, World!\n".The backslash \denotes a special character, which we usually cannot enter as is in the code editor. The\must always be followed by a letter indicating which special character should be used. In this case,\nmeans (n)ew line. This simply ensures that if we print other strings later, they will be printed in a new line in the terminal, instead of on the same line.The semicolon ;indicates that a command has ended. Finally, the fifth line is simply the closing bracket }, indicating that the definition of the functionmainis done. When the execution of the program reaches this line, the program exits. | 
|  Warning: A common mistake made by beginner C programmers is to forget the semicolon ;at the end of the line. Copy and paste this code to the IDE: #include <stdio.h>
int main(void)
{
    printf("Hello, ");
    printf("World!\n") // This line is missing a semicolon!
}
 First of all, notice that in the line before last, we have two slashes //. Any text that appears after two slashes (and until the end of the line) is not executed, and is used to provide comments on the code. In this case, I added the comment to let you know that this line is missing a semicolon. To write comments that span multiple lines, we can use/*to indicate the beginning of the comment and*/to indicate the end, for example: /* This is a comment that
    spans multiple lines */
 Now you can familiarize yourself with an indispensable feature of the IDE: static code analysis. A few seconds after you paste the new code, the IDE will already tell you that there is a problem with it, even though you have not tried to compile it yet. If this does not happen automatically, press Ctrl+S to save the file, which will trigger the analysis. You will see the closing bracket }highlighted with a squiggly red underline (just like a spellchecker). If you hover on it with the mouse, you will see the error messageexpected a ';'. Note that this message is followed by the text "C/C++", indicating that it came from the C/C++ extension itself via static code analysis, rather than from the compiler. Alternatively, on the bottom of the page, click on the Problems tab, and you will see the error there, along with the text "C/C++" again, as well as the pair of numbers [7, 1] which indicate the row and column number where the error was found. If you can't see the Problems tab, you can enable it by choosing View > Problems from the menu or pressing Ctrl+Shift+M. If you press F5 to run the program without fixing the problem, it will not compile. You might expect that the string "Hello, "will be printed, because the problem is only with the next line; however, this is not the case, because C is a compiled language. The compiler doesn't run each line individually, it compiles the program as a whole, so if there are any errors anywhere in the code, the program simply won't compile. After you press F5 and the code runs through the compiler, another error message will appear in the Problems tab. This error will also appear as a squiggly line where the semicolon should have been. Note that this message is followed by the text "gcc", indicating that it came from the compiler (this will be "gcc" even if your compiler is Clang, since they produce compatible output). So you now have two error messages, one from VS Code's C/C++ extension and one from the compiler. The compiler can also find many other errors that VS Code cannot, since it is much more thorough. If you add the missing ;, the problem labeled "C/C++" will disappear from the Problems tab after a few seconds, since VS Code will no longer detect an error. Now you can press F5 to compile the code again. The problem labeled "gcc" will then disappear as well, and the message"Hello, World!"will appear in the terminal, as expected. It is worth noting that the message "Hello, World!"is printed in one line, even though we have two instances ofprintf. This is because the first one,printf("Hello, "), does not contain a newline character\n, and therefore any additional output will appear in the same line. | 
| The C compiler mostly doesn't care about spaces or line breaks. For example, the "Hello, World!"program could also be written without any line breaks (except in the first line), as follows: #include <stdio.h>
int main(void) { printf("Hello, "); printf("World!\n"); }
 or with lots of spaces and line breaks, like so (please never format your code like this!): #include <stdio.h>
int
    main(void)        {
            printf
                (    "Hello, "    );
            printf
                (    "World!\n"    );
                 }
 The special command #include <stdio.h>, known as a preprocessor directive (we will talk about that later), has to be written in exactly one line, since it is not part of the actual code, but rather an instruction meant for the compiler. However, most other commands can be formatted as you wish. Generally, adding line breaks in some places - such as before and after the curly brackets, and after semicolons - significantly improves the clarity and readability of your program. Every C developer has their own style preferences. For example, sometimes you will see the opening bracket on the same line as the function instead of in a new line, i.e. int main(void) {. This is a matter of personal taste; I personally feel that having the opening and closing bracket on the same column makes it easier to see where each code block begins and ends, but other people may have different opinions. (Note that by default, Visual Studio Code formats code with the{in a new line.) Luckily, since we are using an IDE, we don't need to worry about formatting our code manually - the IDE will do it for us. Paste either of the two code snippets above into a Visual Studio Code C file, and then right-click anywhere in the editor and choose "Format Document" (or press Shift+Alt+F). The IDE will format your code, and it should look exactly like the code I wrote in the beginning of the previous section. In fact, VS Code can format your code automatically for you on-the-fly, without having to tell it to do it. To enable this feature (which I highly recommend), go to File > Preferences > Settings (or simply press Ctrl+,) and then choose Text Editor > Formatting from the menu on the left. Enable the checkboxes for Format On Paste, Format On Save, and Format On Type. Now your code will be formatted for you automatically whenever you do any of these actions. Next, choose Extensions > C/C++ from the menu on the left of the Settings tab, and make sure that C_Cpp.clang_format_fallbackStyle is set to "Visual Studio", since this is the style I will be using in this course (but feel free to try out other styles if you like). | 
| Now that we understand some basic facts about writing C programs, let us consider one of the most important entities in C (or any program language): variables. A variable has a value which can be changed (or varied), hence the name "variable". There are many types of data that can be stored inside variables. For now, we will only consider variables that have integer values. In C, there are many different data types used for integer variables. The basic types are charandint, and these are further modified by one or more of the modifierssigned,unsigned,short, andlong. Newer versions also added another useful type,bool. This variety of integer types is one of the most confusing parts of C for beginners. Let us discuss these data types now. | 
|  Warning: It is very important that you read and understand this section and the next one. Using the wrong integer data type in C is one of the most common mistake made by beginner C programmers, and can lead to serious bugs and data corruption.  The data type intstands for integer, and it is used to store integers within various different ranges - depending how many bits are allocated for it in memory. Anintcan either besignedorunsigned. Let N be the number of bits used to store the integer. If it isunsigned, then the range of values is simply from 0 to 2N-1. For example, for N=3 we have:  0 = 000,1 = 001,2 = 010,3 = 011,4 = 100,5 = 101,6 = 110,7 = 111 = 2N-1. If the integer is signed, then it should allow for negative values as well. Unfortunately, the computer only knows how to store data as sequences of bits - 0s and 1s - and nothing else. It cannot store a plus or minus sign explicitly in memory. Therefore the sign, whether plus or minus, must also be encoded using bits. In C, signed numbers are represented using a system called two's complement. Given a number in binary, its negative is represented by inverting all the bits and then adding one to the result. For example, for N=3 we have the positive numbers:  0 = 000,1 = 001,2 = 010,3 = 011 = 2N-1-1, and the negative numbers:  -1 (invert 001, get 110, then add 1) = 111,-2 (invert 010, get 101, then add 1) = 110,-3 (invert 011, get 100, then add 1) = 101. Notice that we haven't used the bit sequence 100, so we might as well use it to store an additional number. (3 bits allow for 23 = 8 combinations in total, and we only used 7.) Also notice that all the positive numbers have 0 as their leftmost bit, and all the negative numbers have 1 as their leftmost bit, so 100 should be a negative number. Therefore, it is natural to define: In conclusion, we see that signedintegers with N bits can take values from -2N-1 to 2N-1-1. In C, unlike in some higher-level languages, each variable must have a fixed data type. If we declare a variable as an integer with 8 bits of storage, for example, then we cannot assign to it an integer that requires more than 8 bits of storage later on without losing data, and we definitely cannot assign to it a string or some other data type. This is because, as I explained above, C provides direct, low-level access to the computer's hardware - including its memory. Therefore, sufficient space must be explicitly allocated in memory for each variable that you declare, and this means the compiler must know how much space the variable is going to take, which depends on what kind of data you intend to store in it. In some higher-level languages like Python and Mathematica, an integer can have any value, without limit, and reallocate memory on-the-fly when the integer becomes larger and requires more storage space. This is called arbitrary-precision or infinite-precision arithmetic. However, arbitrary-precision operations are significantly slower, since they cannot use the CPU's built-in integer operations, which are only available for integers with a fixed and bounded number of bits, as captured in C's integer data types. On the other hand, in C, if you are not careful, you might exceed the range that you allocated for your integers, causing bugs and loss of data; this is called integer overflow, and we will demonstrate it below. | 
| Modern 64-bit C compilers support integer sizes from 8 to 64 bits. The names of each size of integer vary between different compilers, which is a source of confusion. Copy and paste the following code to the IDE and run it to find out what the definitions mean on your system: #include <limits.h>
#include <stdio.h>
int main(void)
{
    printf("%s contains %zu bits.\n", "A char", 8 * sizeof(char));
    printf("    %s takes values from %d to %d.\n", "A signed char", SCHAR_MIN, SCHAR_MAX);
    printf("    %s takes values from %u to %u.\n", "An unsigned char", 0, UCHAR_MAX);
    printf("\n");
    printf("%s contains %zu bits.\n", "A short", 8 * sizeof(short));
    printf("    %s takes values from %d to %d.\n", "A signed short", SHRT_MIN, SHRT_MAX);
    printf("    %s takes values from %u to %u.\n", "An unsigned short", 0, USHRT_MAX);
    printf("\n");
    printf("%s contains %zu bits.\n", "A long", 8 * sizeof(long));
    printf("    %s takes values from %ld to %ld.\n", "A signed long", LONG_MIN, LONG_MAX);
    printf("    %s takes values from %u to %lu.\n", "An unsigned long", 0, ULONG_MAX);
    printf("\n");
    printf("%s contains %zu bits.\n", "A long long", 8 * sizeof(long long));
    printf("    %s takes values from %lld to %lld.\n", "A signed long long", LLONG_MIN, LLONG_MAX);
    printf("    %s takes values from %u to %llu.\n", "An unsigned long long", 0, ULLONG_MAX);
    printf("\n");
    printf("%s contains %zu bits.\n", "An int", 8 * sizeof(int));
    printf("    %s takes values from %d to %d.\n", "A signed int", INT_MIN, INT_MAX);
    printf("    %s takes values from %u to %u.\n", "An unsigned int", 0, UINT_MAX);
    printf("\n");
}
 We will first discuss the output, and then the new aspects of the code itself. On my computer, which is running Windows 11 with the 64-bit Clang compiler, the output is as follows: A char contains 8 bits.
    A signed char takes values from -128 to 127.
    An unsigned char takes values from 0 to 255.
A short contains 16 bits.
    A signed short takes values from -32768 to 32767.
    An unsigned short takes values from 0 to 65535.
A long contains 32 bits.
    A signed long takes values from -2147483648 to 2147483647.
    An unsigned long takes values from 0 to 4294967295.
A long long contains 64 bits.
    A signed long long takes values from -9223372036854775808 to 9223372036854775807.
    An unsigned long long takes values from 0 to 18446744073709551615.
An int contains 32 bits.
    A signed int takes values from -2147483648 to 2147483647.
    An unsigned int takes values from 0 to 4294967295.
 As a rule, a charalways contains exactly 8 bits. In some old computers, a byte could contain a number of bits other than 8, in which case this number would be given by the constantCHAR_BIT, but this is irrelevant for modern computers. On the other hand, the number of bits contained in an intdepends on the platform you are using (Windows, Linux, etc.), and can range from 16 to 64, withshortandlongmodifying that number, again on a platform-dependent basis. Note thatintis the actual data type, whileshort,long, andlong longare just shorthands forshort int,long int, andlong long int, but this distinction doesn't really matter. We see that on 64-bit Windows, the sizes are:  shorthas 16 bits.longandinthave 32 bits.long longhas 64 bits. This data model is called LLP64, which is a mnemonic for "(only) long longand pointers are 64 bits". We will learn about pointers later in the course. However, on 64-bit Linux-based operating systems (including macOS), the sizes are instead:  shorthas 16 bits.inthas 32 bits.longandlong longhave 64 bits. This data model is called LP64, which is a mnemonic for "longand pointers are 64 bits" (and sincelong longcannot be smaller thanlong, it is implied that it's also 64 bits). Both standard have advantages and disadvantages, which we will not cover here. The C standard itself does not enforce any specific sizes for the integer types; it merely requires that, regardless of which platform you are using, the following minimum sizes must be satisfied:  shortandintmust have at least 16 bits.longmust have at least 32 bits.long longmust have at least 64 bits. Unfortunately, this confusion regarding the number of bits in each integer type can result in programs that only work on one platform and not another. Luckily, this issue is solved using fixed-width integer types, which we will present below. Finally, note that all integer data types are signedby default. Sointactually meanssigned int,longmeanssigned long, and so on. However, if you have a variable that you know will never be negative (e.g. the size of an array), then you should define it as anunsignedinteger. There are two benefits to this:  unsignedintegers can represent numbers twice as large. If you're not using the negative range, then you're just wasting 1 bit of memory.More importantly, declaring a variable as unsignedindicates to the human reading your code that the variable is expected to always be non-negative, which adds to the readability of your code.  Warning: Only use unsignedintegers if you are absolutely sure their values will never be negative. As we will see below, assigning a negative number to an unsigned integer leads to unexpected behavior. | 
| Now, as promised, let us go over the new aspects of the code we used above. The first line is: #include <limits.h>
 This simply instructs the C compiler to read the file limits.h, which contains the definitions of the limits of the various integer sizes, and include those definitions in our program - just like we do forstdio.hin the second line. The mainfunction consists of manyprintfstatements. The first parameter forprintfis a string, as in the "Hello, World!" program. However, here the strings contain various format placeholders, which take the form of a percent sign%followed by one or more letters. When printfencounters a format placeholder, it takes the next parameter passed to the function and prints it out in the specified format. So the first % corresponds to the first parameter after the string (the second parameter overall), the second % corresponds to the second parameter after the string (the third parameter overall), and so on. Some common format placeholders are as follows:  %scorresponds to a string. Therefore, we used it in place of strings such as"A char","A signed char","An unsigned char"and so on.%d(or equivalently%i) corresponds to asigned int. Therefore, we used it in place ofsignedintegers that we knew had at most the same number of bits as anint, namelychar,short, andint. Here, the dmeans "decimal". One can optionally use%xor%Xto print the number as hexadecimal (base 16), with lowercase or uppercase letters respectively. For example,printf("%X", 255);will print"FF".%ucorresponds to anunsigned int. Therefore, we used it in place ofunsignedintegers that we knew had at most the same number of bits as anint.Adding the letter lin front ofdoruindicates that the placeholder corresponds to along. Thus,%ldis asigned longand%luis anunsigned long.Adding the letters llin front ofdoruindicates that the placeholder corresponds to along long. Thus,%lldis asigned long longand%lluis anunsigned long long.The placeholder %zuis a special placeholder that gets replaced with the type of the return value ofsizeof(see below). Usually on 64-bit systems this would belong long, but it might be different depending on the specific system. Since we don't know in advance what this type will be, we use%zuwhich is guaranteed to always be the correct type. In the first line of the mainfunction we have     printf("%s contains %zu bits.\n", "A char", 8 * sizeof(char));
 Here, the placeholder %sgets replaced with the string"A char", which is the second parameter passed toprintf. The placeholder%zugets replaced with theunsigned long longnumber8 * sizeof(char), which is the third parameter passed toprintf. But what is this number? sizeofis an operator which returns the size in bytes of a data type or a variable. We then multiply the result by 8 using the multiplication operator*to get the number of bits. Sosizeof(char)returns the value 1, which we multiply by 8 to get 8 bits. Note that on a 64-bit system this number will be anunsigned long long, hence%zuis equivalent to%llu. If you try replacing%zuwith%d, for example, you will get a warning from the compiler.
 In the second line of the mainfunction we have     printf("    %s takes values from %d to %d.\n", "A signed char", SCHAR_MIN, SCHAR_MAX);
 Here, the placeholder %sgets replaced with the string"A signed char". The placeholder%dgets replaced with the integerSCHAR_MIN, which is defined inlimits.hto be -128, the minimum value of asigned char. Finally, the last placeholder, which is also%d, gets replaced with the integerSCHAR_MAX, which is defined inlimits.hto be 127, the maximum value of asigned char. The same principles apply to the rest of theprintfstatements in our program. | 
| printfallows you to specify that the integer will occupy a fixed width on the screen, by adding the desired number of characters after the%. This is used for more aesthetically pleasing output by aligning different numbers together - although if you really want your program's output to look nice, you should probably implement a GUI instead!
 By default, the number is aligned to the right and padded with spaces on the left. If you add -after the%, the number will be aligned to the left instead, and if you add0after the%, the number will be padded with zeros instead. This is illustrated in the following program: #include <stdio.h>
int main(void)
{
    int x = 123456;
    printf("| %d |\n", x);
    printf("| %10d |\n", x);
    printf("| %-10d |\n", x);
    printf("| %010d |\n", x);
}
 The output is: | 123456 |
|     123456 |
| 123456     |
| 0000123456 |
 Note how the first line only prints out the 6 digits of the number without any padding, and is thus misaligned with the other lines, which we specified to have a width of 10 characters. Also note how the third line starts like the first line, but then pads spaces to the right in order to have a total of 10 characters. | 
| We spent a long time discussing the possible types and ranges of integers in C and how to print them. Now it's time to do some actual programming with integers! As we stressed above, every variable in C must be properly declared so that the compiler knows how much memory to allocate for it. To declare a variable of a specific type, we write the type followed by the name of the variable. For example, to declare an integer named x, we write: int x;
 Remember, this will be by default a signed32-bit integer on most systems. If we want to declare more than one variable of the same type, for example two integers xandy, we can do it in one line, separating the variable names by commas: int x, y;
 However, if we want to add comments explaining what each variable does, which would make our code clearer to the reader, then we should declare the variables separately, for example: int x; // The horizontal axis
int y; // The vertical axis
 Variable names in C can contain lowercase and uppercase English letters, digits, and underscores (_), but no other symbols. In addition, the name must start with a letter. Furthermore, there are 34 reserved keywords that are used by the language and therefore cannot be used as variables: auto,break,case,char,const,continue,default,do,double,else,enum,extern,float,for,goto,if,inline,int,long,register,restrict,return,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile, andwhile.  Warning: Variable names can also start with an underscore, but such names are often used by libraries and by additional reserved keywords we did not mention here (added in later versions of the C standard), and so should be avoided by the user.  Some examples of valid variable names are var,Var,var1, andvar_1. Some examples of invalid names are:  1var(begins with a digit),var-1(contains the symbol-which is not a letter, digit, or underscore; will be interpreted as subtracting 1 fromvar),long(reserved keyword). Also note that variable names in C are case-sensitive, so varandVarare not the same variable. Keywords are case-sensitive as well; for example,longis a data type, butLongis unused and could therefore technically be used as a variable name, although this is very strongly discouraged.  Warning: It is extremely important to give variables informative names. If you have 26 different variables in your program and you just name them a,b, ...,z, this pretty much guarantees that no one will be able to easily understand your code - including you in the future! Remember that variable names exist solely for human readers; if you changentonumber_of_events, the resulting machine code executed by the CPU will look exactly the same, but the C source code will be much more readable to humans. The so-called classic C naming convention uses lowercase words separated by underscore to name variables, e.g. my_variable_name, and uppercase words separated by underscore to name constants, e.g.MY_CONSTANT_NAME. This is the convention I will use here. Sometimes you will also see camel case, where instead of separating words by an underscore, each word starts with an uppercase letter. This has two variations: either the first word (and only the first word) starts with a lowercase letter, e.g. myVariableName, or all words start with an uppercase letter, e.g.MyVariableName.  Warning: In scientific programming, variables often contain values of measurements or physical constants. In this case, it is highly recommended to include the units of measurement in the variable's name, to prevent errors due to using the wrong units. For example, if instead of timeyou usetime_seconds, this will prevent you, or someone you are collaborating with, from later assuming that the variable is actually in milliseconds, which will lead to serious errors. | 
| A variable can be assigned a value either when it is declared: int x = 0;
 or later: int x;
x = 0;
 We can also assign values when we declare more than one variable: int x = 0, y = 1;
 What happens if we never assign a value? To answer that, let us run the following program: #include <stdio.h>
int main(void)
{
    long long a, b;
    printf("%lld, %lld\n", a, b);
}
 On my computer, the output is: 16, 12588272
 (In fact, for some reason the value of ais always 16, but the value ofbchanges every time.) Where did these numbers come from? When we declared the variables, unused space was automatically allocated in memory to store them - 64 bits of memory for each, since that is the number of bits in a long long. However, nothing was actually written to that space, as we did not explicitly give the variables any specific initial values, which is called initializing the variables. The numbers printed by the program are simply the numbers that happened to be stored in that particular space in memory before the program was executed. This is another example of C not "holding your hand" by doing things automatically for you, unlike most higher-level languages. The C compiler doesn't automatically initialize any variables to some default value; instead, you must always initialize them by hand.  Warning: Always initialize variables as soon as they are declared by assigning a value to them. Avoid uninitialized declarations such as int x, and instead only use initialized declarations such asint x = 0. In most cases, initializing the variables as soon as they are declared does not affect performance, since they have to be initialized sometime, if not now then later. However, if the initial value of a variable is not known when we write the code - for example, if the value is taken from a file or from user input - then we could potentially save a bit of CPU time by declaring the variable uninitialized, and only assigning the initial value later, when we know what it should be. Nevertheless, even in cases like this, it would still be safer to initialize the variable at declaration time - for example, to a default value that the variable should have in case the input operation fails. Initializing a variable only takes a small fraction of a second, so unless the operation is performed billions of times, there will not be any noticeable performance penalty. If you added the -Wextracompiler argument, as I explained above, then after you press F5, the Problems tab (Ctrl+Shift+M) should show you a warning:'a' is used uninitialized in this function. The program still runs, since unlike errors, warnings do not halt the compilation; therefore, paying attention to compiler warnings in the Problems tab is extremely important and can help you prevent potential bugs in your code.  Warning: It is good programming practice to declare and initialize all of the variables used by a function, including main, at the very beginning of the function's code block. This ensures that the names, types, and initial values of every single variable used in the function are immediately known to the reader without having to read through the whole function. In addition, it helps the programmer to avoid declaring the same variable twice in two different parts of the function by mistake. To illustrate the warning, compare this (not properly organized) code: #include <stdio.h>
int main(void)
{
    printf("Hello, World!\n");
    // do some stuff...
    short a = 5;
    printf("a = %d\n", a);
    // do some more stuff...
    unsigned long b;
    // do some more stuff...
    b = 7;
    printf("b = %lu\n", b);
}
 With this (properly organized) code: #include <stdio.h>
int main(void)
{
    short a = 5;
    unsigned long b = 7;
    printf("Hello, World!\n");
    // do some stuff...
    printf("a = %d\n", a);
    // do some more stuff...
    printf("b = %lu\n", b);
}
 Both programs do the same thing, but in the second program it is immediately clear to the reader that two variables, aandb, will be used in the program, and that they are initialized to5and7respectively. In the first program, the reader must read the whole program to get the same information. Furthermore, there is also a risk thatbwill be used uninitialized - especially if someone made changes to the code that resulted in accidentally erasing the line containing the initializationb = 7. | 
| Sometimes, we want to introduce a variable that will be used throughout the program, but its value will never change. In these cases, the variable can be declared as a constant using the keyword const. This has many subtle uses, that we won't get into here; however, its most basic and common use is to simply ensure that the programmer does not accidentally modify the variable later. Here is an example: #include <stdio.h>
int main(void)
{
    const int answer = 42;
    answer = 41;
}
 If you enter that code, before you even run it, the IDE will display the error expression must be a modifiable lvaluein the Problems tab on the bottom of the page. (If you can't see the Problems tab, you can enable it by choosing View > Problems from the menu, or pressing Ctrl+Shift+M.) Therefore, the variableansweris protected from accidental modification. In these lecture notes, I will make sure to always define constant variables as const. This is good programming practice, as it guarantees that constant variables are never accidentally modified, and thus prevents bugs. I will also make sure to do the same for any function arguments that are not modified by the function (see below). Of course, in the simple examples in these lecture notes, this isn't really necessary, but it's good to develop a habit of using constwherever possible. You should make sure to do the same when you write your own code! | 
| Once we declare variables, we can operate on them and combine them together in various ways. The following program demonstrates the most common arithmetic operators: #include <stdio.h>
int main(void)
{
    const int x = 5, y = 2;
    printf("Addition: %d + %d = %d\n", x, y, x + y);         // Addition: 5 + 2 = 7
    printf("Subtraction: %d - %d = %d\n", x, y, x - y);      // Subtraction: 5 - 2 = 3
    printf("Multiplication: %d * %d = %d\n", x, y, x * y);   // Multiplication: 5 * 2 = 10
    printf("Integer division: %d / %d = %d\n", x, y, x / y); // Integer division: 5 / 2 = 2
    printf("Modulo: %d %% %d = %d\n", x, y, x % y);          // Modulo: 5 % 2 = 1
}
 Note that integer division rounds towards zero, so 5 / 2 = 2.5 is rounded to 2 and -5 / 2 = -2.5 is rounded to -2. The modulo (or remainder) is defined such that ((x / y) * y) + (x % y)is equal tox. Also note that in the code above, to print out the character %in the last line, we had to enter it twice in theprintfstring. This is because a%indicates a format placeholder, so in order to print it as is, we use%%. This program only printed the results, but did not reassign any value to xandy. Any variables can be reassigned a new value, simply by using the=operator, for example: x = y + 1;
 C also employs some useful shorthands for reassigning values, in cases where the new value depends on the old value:  x += yis equivalent tox = x + y.x -= yis equivalent tox = x - y.x *= yis equivalent tox = x * y.x /= yis equivalent tox = x / y.x %= yis equivalent tox = x % y. Finally, if we just want to increase or decrease the value of an integer by 1, we can use the following shorthands:  x++is equivalent tox = x + 1.x--is equivalent tox = x - 1. | 
|  Warning: As we stressed above, it is extremely important that integers are declared with the proper range for the values they might take. Otherwise, this may lead to integer overflow.  To illustrate integer overflows for unsigned integers, let's run the following program: #include <stdio.h>
int main(void)
{
    unsigned char x = 255;
    x++;
    printf("255 + 1 = %d\n", x);
    unsigned char y = 0;
    y--;
    printf("0 - 1 = %d\n", y);
}
 If you run the program, you will see the following output: 255 + 1 = 0
0 - 1 = 255
 What happened? Recall that an unsigned char(8 bits) takes values from 0 to 255. In binary, we have  00000000 binary = 0 decimal,11111111 binary = 255 decimal. When we added 1 to 255, the result has 9 bits: 100000000. However, x, which is anunsigned char, can only store numbers with at most 8 bits. Therefore, the 9th bit gets truncated, and we are left with 00000000, which is just zero. Mathematically, the result was calculated modulo 28, that is, modulo 256. Here "modulo" refers not to the remainder, but rather to modular arithmetic in ℤ256. Essentially, the result "wraps around" by adding or subtracting a multiple of 256 until it fits in the range [0, 255]. This is similar to hours on a 24-hour clock, which are represented modulo 24 and thus always in the range [0, 23] (with 24 wrapping around to 0). So 255 + 1 wraps around to 0, and similarly, 0 - 1 wraps around in the opposite direction to 255, just like 1 hour before 0:00 is 23:00. More generally, when the result of an operation is stored in an unsigned integer with N bits, the result will be taken modulo 2N. This is called integer overflow, and when you program in C (unlike in some higher-level languages like Python), you have to be very careful to avoid it, because otherwise your program will not work correctly, since it will think that, for example, 256 is actually 0. It is possible to check if an overflow will occur before doing the calculation. Suppose we want to add two N-bit integers, xandy. An overflow will occur if x + y > 2N-1, so we could first check if x > 2N-1-y, and if so, issue an error to the user or deal with the problem in another way. Similarly, for subtraction, an overflow will occur if x - y < 0, so we could first check if x < y. However, as always, there is a tradeoff between speed and safety; if we perform a check every time we do an arithmetic operation, our code will take more time to run. What about integer overflows for signed integers? The C standard does not actually define what happens in the case of a signed integer overflow, so technically the result is undefined behavior, meaning it is unpredictable and depends on the implementation. In practice, most compilers will implement signed integer overflows as wrapping around the range of the signed integer, same as for unsigned integers. Here is an example: #include <stdio.h>
int main(void)
{
    signed char x = 127;
    x++;
    printf(" 127 + 1 = %4d\n", x);
    signed char y = -128;
    y--;
    printf("-128 - 1 = %4d\n", y);
}
 The output on my computer is:  127 + 1 = -128
-128 - 1 =  127
 It is highly unlikely that your computer will behave differently, but since the behavior is implementation-dependent, you should never rely on signed integer overflows wrapping around. (You should never rely on unsigned integer overflows wrapping around either, but at least that is defined behavior according to the C standard.) | 
| Confused by the variety of integer data types and the fact that they have different sizes on each platform? You're not alone. Because of this confusion, the C99 standard added fixed-width integer types, which guarantee that an integer will have precisely the number of bits that you want it to have, regardless of platform. The types are:  int8_t,int16_t,int32_t,int64_tforsignedintegers with width of exactly 8, 16, 32 and 64 bits respectively.uint8_t,uint16_t,uint32_t,uint64_tforunsignedintegers with width of exactly 8, 16, 32 and 64 bits respectively. To access them, we must include the header file stdint.h. Furthermore, there are also corresponding format placeholders forprintf:  PRId8,PRId16,PRId32,PRId64forsignedintegers with width of exactly 8, 16, 32 and 64 bits respectively.PRIu8,PRIu16,PRIu32,PRIu64forunsignedintegers with width of exactly 8, 16, 32 and 64 bits respectively. These are defined in the header file inttypes.h. To use them, replace e.g."%d\n"with"%"PRId32"\n". The following code is similar to the one we used above, but this time, the output will be exactly the same on any platform: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    printf("  int8_t contains %2zu bits and takes values from %" PRId8 " to %" PRId8 ".\n", 8 * sizeof(int8_t), INT8_MIN, INT8_MAX);
    printf(" uint8_t contains %2zu bits and takes values from 0 to %" PRIu8 ".\n", 8 * sizeof(uint8_t), UINT8_MAX);
    printf(" int16_t contains %2zu bits and takes values from %" PRId16 " to %" PRId16 ".\n", 8 * sizeof(int16_t), INT16_MIN, INT16_MAX);
    printf("uint16_t contains %2zu bits and takes values from 0 to %" PRIu16 ".\n", 8 * sizeof(uint16_t), UINT16_MAX);
    printf(" int32_t contains %2zu bits and takes values from %" PRId32 " to %" PRId32 ".\n", 8 * sizeof(int32_t), INT32_MIN, INT32_MAX);
    printf("uint32_t contains %2zu bits and takes values from 0 to %" PRIu32 ".\n", 8 * sizeof(uint32_t), UINT32_MAX);
    printf(" int64_t contains %2zu bits and takes values from %" PRId64 " to %" PRId64 ".\n", 8 * sizeof(int64_t), INT64_MIN, INT64_MAX);
    printf("uint64_t contains %2zu bits and takes values from 0 to %" PRIu64 ".\n", 8 * sizeof(uint64_t), UINT64_MAX);
}
 The output (on any platform) should be:   int8_t contains  8 bits and takes values from -128 to 127.
 uint8_t contains  8 bits and takes values from 0 to 255.
 int16_t contains 16 bits and takes values from -32768 to 32767.
uint16_t contains 16 bits and takes values from 0 to 65535.
 int32_t contains 32 bits and takes values from -2147483648 to 2147483647.
uint32_t contains 32 bits and takes values from 0 to 4294967295.
 int64_t contains 64 bits and takes values from -9223372036854775808 to 9223372036854775807.
uint64_t contains 64 bits and takes values from 0 to 18446744073709551615.
 Note that here we used the format placeholder %zuto print out the size of the integer types in bits.%zuis intended specifically to print out the result of thesizeofoperator, which has the special typesize_t. The purpose ofsize_tis to always be able to store the maximum theoretical size, in bytes, of any variable or array. Thus, on 64-bit systems,size_tis the same asuint64_t. Other than storing the result ofsizeof, the typesize_tcan be used, for example, to count the elements of an array. Nowadays there's no reason to write 32-bit programs (certainly not for scientific computing!), so there's never any reason to assume size_twill have only 32 bits. Thus, I usually prefer to useuint64_texplicitly instead ofsize_t, even if just for readability purposes, to let the human reader know that I expect this to be a 64-bit integer. I will take this approach in these notes as well. From now on, I will always use these fixed-width integer types in these lecture notes, and avoid using any ambiguous integer types (with the exception of the mainfunction itself, which must always returnint). You should make sure to do the same in your course projects, and in any other programs you write in C and C++! | 
|  | 
| The ifstatement is used to execute a code based on some condition. The basic syntax is: if (condition)
    statement;
 This checks conditionand, if it is satisfied, executesstatement. It is not necessary to writestatementon a separate line, although I think it looks clearer; you could also writeif (condition) statement;. Furthermore, if you want to execute several different statements if the condition is satisfied, you can enclose them in curly brackets: if (condition)
{
    statement1;
    statement2;
    // etc...
}
 For example: if (x > 0)
    x++;
 This increases xby 1, but only if its value is positive. More generally, the following comparison operators are available:  x == y: "x is equal to y".x != y: "x is not equal to y".x < y: "x is less than y".x > y: "x is greater than y".x <= y: "x is less than or equal to y".x >= y: "x is greater than or equal to y".  Warning: Notice that comparison ==has two equal signs, while assignment=only has one. It is a very common mistake in C to write something likeif (x = y); this does not comparextoy, it assigns toxthe value ofy, resulting in unexpected consequences! In addition, logical operators can be used to modify and combine conditions:  !A: "not A". For example, instead ofif (x <= y)we can equivalently writeif (!(x > y)); ifxis not greater thany, then it must be either less than or equal toy.A && B: "A and B", meaning that A and B must both be true. For example,(-1 <= x) && (x <= 1)checks ifxis in the interval [-1,1], that is, between -1 and 1 inclusive; note that-1 <= x <= 1is not possible in the C syntax.A || B: "A or B", meaning that at least one of A and B must be true. For example,(x < -1) || (1 < x)checks ifxis outside the interval [-1,1]. Optionally, one may add an elsestatement, which will be executed ifconditionis not satisfied: if (condition)
    statement;
else
    another_statement;
 Or with brackets: if (condition)
{
    statement1;
    statement2;
    // etc...
}
else
{
    another_statement1;
    another_statement2;
    // etc...
}
 For example: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 2;
    if (x % 2 == 0)
        printf("x is even.\n");
    else
        printf("x is odd.\n");
}
 elsemay also be followed by anotherif, for example:
 #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 7;
    if (x % 3 == 0)
        printf("%" PRId64 " is divisible by 3.\n", x);
    else if (x % 3 == 1)
        printf("%" PRId64 " divided by 3 has remainder 1.\n", x);
    else if (x % 3 == 2)
        printf("%" PRId64 " divided by 3 has remainder 2.\n", x);
}
 (Recall that int64_tis a 64-bit integer andPRId64is the format placeholder for it; see fixed-width integer types). | 
| Consider the following code: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 0;
    if (x)
        printf("x is true.\n");
    else
        printf("x is false.\n");
}
 If you run this code, you will get the output x is false. However, if you changexto any non-zero integer value, you will get the outputx is true. In other words, theifstatement considers the condition to be true if it evaluates to a non-zero integer. Indeed, let us run the following code: #include <stdio.h>
int main(void)
{
    printf("%d\n", 1 == 1);
    printf("%d\n", 1 == 2);
}
 From the output, we see that 1 == 1evaluates to1(a non-zero integer, and thus true), and1 == 2evaluates to0(and thus false). This convention in C can be very confusing, and it can cause errors if you are not careful. Here is one of the most common problems C programmers encounter. Before, I warned you that ==is a comparison operator, while=is an assignment operator. Now, an assignment operator actually evaluates to the value that was assigned. So for example,x = 1assigns1toxand also evaluates to1. This can cause an error if we accidentally write=instead of==. For example: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    int64_t x = 0;
    if (x = 1) // Notice that we (incorrectly) used = instead of == here!
        printf("x is 1.\n");
    else
        printf("x is not 1.\n");
}
 The output of this program is x is 1. The reason is thatx = 1does not comparexto1, it setsxto1, and evaluates to1, whichifthen interprets as "true"! If you configured the compiler to provide extra warnings, as I explained above, then you will see the warningsuggest parentheses around assignment used as truth valuein the Problems tab. This warning will appear whenever the compiler thinks you used=when you should have used==. Another problem with interpreting 0 as "false" and any other integer as "true" is that both 1 and 2 mean "true", but they are not equal to each other, which means that false equals false, but true does not always equal true! To avoid this and other confusions, C provides (since 1999) a Boolean data type called bool, which is accessible by including the header filestdbool.h. Once you include that file, you may declare variables using thebooltype, and their values can be eithertrueorfalse. These values can then be modified, e.g. using logic operators. Here is an example: #include <stdio.h>
#include <stdbool.h>
int main(void)
{
    const bool A = true;
    const bool B = false;
    if (A)
        printf("A is true.\n");
    if (B)
        printf("B is true.\n");
    if (!A)
        printf("A is not true.\n");
    if (!B)
        printf("B is not true.\n");
    if (A && B)
        printf("A and B are both true.\n");
    if (A || B)
        printf("At least one of A and B is true.\n");
}
 The output is: A is true.
B is not true.
At least one of A and B is true.
 You can change the truth values of AandBand see what you get. Note that e.g.if (A)is equivalent toif (A == true).  Warning: It is highly recommended to always use bool to represent boolean values whenever you need to directly manipulate logic values in your program.  | 
| A very convenient short form for the if-elsestatement is given by the conditional operator. The followingifstatement: if (condition)
    statement;
else
    another_statement;
 can alternatively be written using the conditional operator as follows: condition ? statement : another_statement;
 This is most often used not as a stand-alone expression, but rather as part of a larger expression. For example, recall the code we used above: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 2;
    if (x % 2 == 0)
        printf("x is even.\n");
    else
        printf("x is odd.\n");
}
 Instead of having several different lines for the ifandelsestatements, and two differentprintfstatements, we can use the following more compact and (arguably) elegant version: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 2;
    printf("x is %s.\n", x % 2 ? "odd" : "even");
}
 | 
| ifstatements only consider two cases: eitherconditionis satisfied or not.switchallows you to consider several different cases. The syntax is:
 switch (expression)
{
case 1:
    statement1;
    break;
case 2:
    statement2;
    break;
    // etc...
}
 This will evaluate expression, which must return an integer. Ifexpressionis equal to 1, it will evaluatestatement1; ifexpressionis equal to 2, it will evaluatestatement2; and so on.  Warning: A break;must be added after eachcase, otherwise all of the code until the closing bracket}, including the code for every subsequent case, will be evaluated. To illustrate the warning, let us run the following code: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 2;
    switch (x)
    {
    case 1:
        printf("1\n");
        // no break
    case 2:
        printf("2\n");
        // no break
    case 3:
        printf("3\n");
        // no break
    }
}
 The output will be: 2
3
 Since we did not properly break after each statement, switchstarted evaluating at the labelcase 2:, and the evaluation then ran until the closing bracket}. The proper was to do this is: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 2;
    switch (x)
    {
    case 1:
        printf("1\n");
        break;
    case 2:
        printf("2\n");
        break;
    case 3:
        printf("3\n");
        break;
    }
}
 Now the output will be 2, as expected. Finally, we can also add adefaultlabel, which will be evaluated if none of the other cases are satisfied: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 5;
    switch (x)
    {
    case 1:
        printf("1\n");
        break;
    case 2:
        printf("2\n");
        break;
    default:
        printf("default\n");
        break;
    }
}
 If we run this code, the output will be default, since the casex = 5is not accounted for in any of the other labels. switchcan be used to write the program we wrote above, which determines ifxis divisible by 3, in a different way:
 #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 7;
    switch (x % 3)
    {
    case 0:
        printf("%" PRId64 " is divisible by 3.\n", x);
        break;
    case 1:
        printf("%" PRId64 " divided by 3 has remainder 1.\n", x);
        break;
    case 2:
        printf("%" PRId64 " divided by 3 has remainder 2.\n", x);
        break;
    }
}
 | 
| An enumeration defines a new data type which can take integer values, while assigning a label to each value, so that we don't need to remember what each number represents. This is done using the keyword enum. The syntax is: enum name { LABEL, ANOTHER_LABEL, ... };
 Here, nameis the name of the new data type we are creating. The first label will be assigned the number 0, the second label will be assigned the number 1, and so on. It is conventional to use uppercase for the labels, in order to distinguish them from variables. Also, note that enumerations should usually be defined outside themainfunction, so that the have global scope and can be used by other functions as well (see variable scope and global variables below). If we want to assign specific integers to the labels, we can do so as follows: enum name { LABEL = value, ANOTHER_LABEL = another_value, ... };
 This is useful in cases where the numbers are generated by some other program, and we want to assign them meaningful labels in our program. Once we define an enumeration, the labels will be replaced with their corresponding numbers throughout the program. For example: #include <stdio.h>
enum color
{
    RED,
    GREEN,
    BLUE
};
int main(void)
{
    printf("RED = %d, GREEN = %d, BLUE = %d\n", RED, GREEN, BLUE);
}
 The output will be RED = 0, GREEN = 1, BLUE = 2. However, the most common use of enumerations is to define an actual variable that can take one of these values. We can then useswitchto execute different statements based on the value of the variable: #include <stdio.h>
enum color
{
    RED,
    GREEN,
    BLUE
};
int main(void)
{
    const enum color c = GREEN;
    switch (c)
    {
    case RED:
        printf("Color is red.\n");
        break;
    case GREEN:
        printf("Color is green.\n");
        break;
    case BLUE:
        printf("Color is blue.\n");
        break;
    }
}
 Here we first declare a variable called cwhose data type isenum color, which means it can be eitherRED,GREEN, orBLUE. We also initialize it toGREEN. Then we print a message based on the value. At no point did we need to use the actual integer values assigned to the different labels. Indeed, the whole point of enumerations is that we don't need to know the numerical values of the labels. Any program what uses enumerations must always use only the labels and not the numerical values, since the values might change, but the labels are fixed.  Warning: Never assign an integer value to an enumvariable manually; always assign a value using the appropriate label. That is, always write e.g.c = GREENand notc = 1. There are two reasons for this. First, the IDE and the compiler do not check that the numerical value of the integer corresponds to one that has actually been assigned a label. Second, if you later change the values of the labels or add more labels, any code that explicitly uses the integer values the labels had before will need to be rewritten.  Warning: Never perform an arithmetic operation, such as adding or subtracting, on an enumvariable. The integer value has no actual numerical meaning; it simply encodes a particular label. | 
|  | 
| The whilestatement allows us to define a loop which will run while a specific condition is satisfied. The loop will automatically stop when the condition is no longer satisfied. The syntax is: while (condition)
    statement;
 As usual, if the loop body contains more than one line (i.e. it is a compound statement), it must be enclosed in curly brackets: while (condition)
{
    statement1;
    statement2;
    // etc...
}
 For example, here is a program that prints the squares of the integers from 1 to 5: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    uint64_t i = 1;
    while (i <= 5)
    {
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
        i++;
    }
}
 Notice that here I used uint64_t, that is, an unsigned 64-bit integer, as the data type fori, since I know for certain that it will never have any negative values. I therefore also usedPRIu64, the format placeholder for an unsigned 64-bit integer, to print the value ofi. It is almost always a good idea to use unsigned integers in loops like this, where the integer is being used to count the number of times the loop has been executed, since a count is always positive, and using an unsigned integer type allows us to double the range of the counting variable. The output is: 1^2 = 1
2^2 = 4
3^2 = 9
4^2 = 16
5^2 = 25
 The dostatement does the same thing, except it only checks that the condition is satisfied after the loop body has been evaluated: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    uint64_t i = 1;
    do
    {
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
        i++;
    } while (i <= 5);
}
 This will have the same output. The main difference is that in a do-whileloop, the loop body will be evaluated at least once before the condition is checked. For example, if we replaceint i = 1withint i = 7, this code will first output7^2 = 49and only then check ifi <= 5. On the other hand, if we do the same with thewhileloop above, there will be no output.  Warning: Always make sure the iteration condition is set such that there are no circumstances where the loop repeats infinitely!  | 
| There are two ways to control the evaluation of a loop from within the loop body. break, which we already encountered above when we discussedswitchstatements, immediately ends the loop. For example, the following code will have the same output as the code above, except that instead of specifying a condition for thewhileloop, it checks manually ifi >= 5and if so, breaks: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    uint64_t i = 1;
    while (1)
    {
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
        if (i >= 5)
            break;
        i++;
    }
}
 Note that while (1)will keep looping infinitely (since1is equivalent totrue) unless you explicitly break the loop! This code was only written to illustrate how thebreakstatement works, and by no means should be interpreted as an example of good programming. Loop conditions such aswhile (1)should generally be avoided at all costs. Another option is to use continue, which will skip the rest of the loop body and continue immediately to the next iteration of the loop. For example, the following code skips the even numbers: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    uint64_t i = 0;
    while (i <= 5)
    {
        i++;
        if (i % 2 == 0)
            continue;
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
    }
}
 Note that we changed the order of the statements i++andprintfso thatcontinuewill skipprintfbut noti++. Otherwise we would have had an infinite loop, since onceireached an even number, it would stop incrementing, and thus the conditioni <= 5will always be satisfied (try switching the order and see what happens). | 
| The forstatement provides a more compact way of defining loops. It has the following syntax: for (initialization; condition; iteration)
    statement;
 or with a compound statement: for (initialization; condition; iteration)
{
    statement1;
    statement2;
    // etc...
}
 initializationis evaluated once, before the loop starts.conditionis evaluated before the loop body, and the loop terminates if it evaluates to false (that is, 0).iterationis evaluated after the loop body, and thenconditionis evaluated again, and so on.
 In the first whileexample above, we initializedi = 1before the start of the loop, then checked that the conditioni <= 5is satisfied before each iteration of the loop, and finally increasediby 1 at the end of each iteration of the loop. This can all be written using one compactforstatement as follows: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    for (uint64_t i = 1; i <= 5; i++)
    {
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
    }
}
 Generally, forloops should be used whenever the iteration is over a specific range of integers. Note thatforloops may be controlled usingbreakandcontinue, as before. | 
| All loops can be nested, which means one loop is inside another loop (which can itself be inside a third loop, and so on). This is especially common with forloops, since we can then iterate over every possible combination of the values of two (or more) integers. For example, the following code prints out a multiplication table: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    for (uint64_t i = 1; i <= 5; i++)
    {
        for (uint64_t j = 1; j <= 5; j++)
        {
            printf("| %" PRIu64 " * %" PRIu64 " = %2" PRIu64 " ", i, j, i * j);
        }
        printf("|\n");
    }
}
 The output is: | 1 * 1 =  1 | 1 * 2 =  2 | 1 * 3 =  3 | 1 * 4 =  4 | 1 * 5 =  5 |
| 2 * 1 =  2 | 2 * 2 =  4 | 2 * 3 =  6 | 2 * 4 =  8 | 2 * 5 = 10 |
| 3 * 1 =  3 | 3 * 2 =  6 | 3 * 3 =  9 | 3 * 4 = 12 | 3 * 5 = 15 |
| 4 * 1 =  4 | 4 * 2 =  8 | 4 * 3 = 12 | 4 * 4 = 16 | 4 * 5 = 20 |
| 5 * 1 =  5 | 5 * 2 = 10 | 5 * 3 = 15 | 5 * 4 = 20 | 5 * 5 = 25 |
 Note that we only printed a newline character \nafter each iteration ofi, so that all the iterations ofjwill appear in one line. We also printed the right border|of the table at the same time. Finally, we used%2in the format placeholder for the product to ensure that it is always printed with exactly 2 characters, adding a space if necessary, since some numbers will have one digit and others will have two, so this guarantees the table will be correctly aligned. | 
| In C, each variable has a scope within which it is accessible. Generally, whenever we are inside a code block indicated by a pair of curly brackets {and}, any variable declared within that block is considered a local variable, local to that block. When the block ends, the variable is no longer accessible, and the memory it used is freed up. Variables declared within the context of statements such asfor, even if they are not inside the curly brackets, are nonetheless local to the code block associated with that statement. For example, consider the following code: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    for (uint64_t i = 1; i <= 5; i++)
    {
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
    }
    printf("i = %" PRIu64 "\n", i); // This won't work; i was local to the for loop
}
 Since we initialized uint64_t i = 1within the scope of theforloop, the variableiis only defined within that scope. Once the loop finishes,ino longer exists. Therefore this code won't compile, and even before you try to compile it, the IDE will add the erroridentifier "i" is undefinedto the Problems tab and highlightiin the last line with a squiggly red underline. If we need to use the last value of i, we must declare it before the loop starts so that its scope is the entiremainfunction: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    uint64_t i;
    for (i = 1; i <= 5; i++)
    {
        printf("%" PRIu64 "^2 = %" PRIu64 "\n", i, i * i);
    }
    printf("i = %" PRIu64 "\n", i);
}
 This will print i = 6after theforloop finishes, since the value ofiwas increased to 6 via the iteration statementi++, and then the conditioni <= 5was no longer satisfied, which is why the loop stopped. When we declare a local variable, and another local variable with the same name exists within a higher scope, the new variable will replace the old variable until the scope of the new variable ends. For example, in the following code, iis declared in the first line of themainfunction and initialized with the value 9. Anotheriis then declared in theforloop, and iterates on the values 1 to 6. However, once theforloop ends, the scope of this newiends, and it disappears. If we then try to access the original variablei, it will still have the original value 9: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    uint64_t i = 9;
    printf("(main) i = %" PRIu64 "\n", i);
    for (uint64_t i = 1; i <= 5; i++)
    {
        printf("(for) i = %" PRIu64 "\n", i);
    }
    printf("(main) i = %" PRIu64 "\n", i);
}
 The output is: (main) i = 9
(for) i = 1
(for) i = 2
(for) i = 3
(for) i = 4
(for) i = 5
(main) i = 9
  Warning: It is highly recommended that every initialization statement in a forloop will be a declaration and initialization of the formtype i = valueinstead of just an assignmenti = value, since this ensures that if a variable namedihas already been declared and used elsewhere in the program, its value will not be modified by theforloop.  Warning: Even though it is perfectly legal to use the same variable name twice or more in different scopes, this should be avoided, as it can cause confusion and errors. In the case of a forloop, a good programming practice is to usei,j, andkexclusively as the names of the variables to be incremented in allforloops, and not to use these variable names anywhere else in the program. Note that a code block does not have to be associated with anything in particular; we can begin and end a code block manually wherever we want. This is very useful if we want to declare one or more temporary variables that will only be used for a specific task, and then thrown away. By declaring them within a nested code block, we ensure that they will not conflict with any variables declared elsewhere in the program, and that the memory they used has been freed up. For example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const uint64_t x = 9;
    printf("(main) x = %" PRIu64 "\n", x);
    {
        const uint64_t x = 7;
        printf("(nested) x = %" PRIu64 "\n", x);
    }
    printf("(main) x = %" PRIu64 "\n", x);
}
 The output is: (main) x = 9
(nested) x = 7
(main) x = 9
 However, it usually makes more sense to define a function rather than a nested code block in such cases. Notice also that even though we declared xasconstboth times, when we declare it for the second time inside the code block that doesn't count as "changing its value", since we are, of course, declaring a completely new variable in a local scope. | 
|  | 
| Arrays allow a single variable to hold multiple values. In an array of constant length, memory will be allocated for all of the values in advance, and the values will be stored contiguously (one after the other) in memory. For now, we will only consider arrays whose length is constant and determined at compilation time. Later we will discuss how to declare and allocate memory for arrays with variable length. To declare an array of constant size, we use the following syntax: type name[size];
 typeis the data type of the values, such asint64_t;nameis the name of the array; andsizeis the number of values in the array. TheNth element of the array can then be referred to usingname[N - 1].
  Warning: Arrays in C start with the index 0. Therefore, if the array acontains 3 elements, then the first element will bea[0], the second will bea[1], and the third and last element will bea[2]. This is a common source of confusion for beginner C programmers. The array can also be initialized, as follows: type name[] = {name[0], name[1], ..., name[size - 1]};
 In this case, we don't have to specify the size of the array manually - the size will be automatically determined based on how many elements are in the list. Again, note that the first element is name[0]and the last one isname[size - 1]. In the following example, we first define an array with the first 5 primes, and then print the elements of the array: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const uint64_t primes[] = {2, 3, 5, 7, 11};
    for (uint64_t i = 0; i < 5; i++)
        printf("primes[%" PRIu64 "] = %" PRIu64 "\n", i, primes[i]);
}
 The output will be: primes[0] = 2
primes[1] = 3
primes[2] = 5
primes[3] = 7
primes[4] = 11
 | 
| Above we warned that variables should always be initialized to a specific value as soon as they are declared, since memory is automatically allocated but not automatically initialized, so an uninitialized variable will just take whatever value happened to be stored in that particular address in memory at the time. The same goes for arrays. For example, consider this program: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const uint64_t nums[5];
    for (uint64_t i = 0; i < 5; i++)
        printf("nums[%" PRIu64 "] = %" PRIu64 "\n", i, nums[i]);
}
 On my computer, one possible output is: nums[0] = 229359221408
nums[1] = 140700945750376
nums[2] = 1582342677712
nums[3] = 4294967296
nums[4] = 140700945747968
 The numbers also change every time I run the program. On your computer, the output will be different, depending on whatever happens to be in those particular memory addresses at the time. To avoid potential errors, it is best to initialize the array as soon as we declare it so that all of the elements are zero. We could do this by writing the zeros explicitly, i.e. const uint64_t nums[5] = {0, 0, 0, 0, 0}, but if the array contained more than a few elements, this would be extremely tedious. Luckily, there is a shorthand for this. If we only specify some of the elements in the initialization list, and the array's size is larger than the number of elements we initialized, then the remaining elements will be initialized to zero. For example, replace the line const uint64_t nums[5];with const uint64_t nums[5] = {1, 2};
 The output is now: nums[0] = 1
nums[1] = 2
nums[2] = 0
nums[3] = 0
nums[4] = 0
 Therefore, to automatically initialize the entire array to zeros, we can simply initialize just one element to zero, and the rest will be initialized to zero as well: const uint64_t nums[5] = {0};
 Output: nums[0] = 0
nums[1] = 0
nums[2] = 0
nums[3] = 0
nums[4] = 0
 Of course, this means that we have to specify the size of the array manually so that the compiler knows how many elements to initialize.  Warning: Initializing an array with an empty list, e.g. const uint64_t nums[5] = {}, is not allowed in the C standard. If you try this, and you added the-Wpedanticargument as I instructed above, then the compiler will generate the warningISO C forbids empty initializer bracesin the Problems tab. | 
|  Warning: When you access an element of an array, the IDE and the compiler do not check if the element index is within the range of the array. Accessing an elements outside the range will lead to unexpected errors.  In the following example, we create an array of one element, and initialize this element to zero. This element is nums[0]. We then try to accessnums[-1]andnums[1], which are both outside the range of the array: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t nums[1] = {0};
    for (int64_t i = -1; i <= 1; i++)
        printf("nums[%" PRId64 "] = %" PRId64 "\n", i, nums[i]);
}
 On my computer, one possible output is: nums[-1] = 4294967296
nums[0] = 0
nums[1] = 1
 The "elements" nums[-1]andnums[1]are not actually part of the array; the numbers that were printed were simply the numbers that happened to be stored in memory before and after the space reserved for the array. | 
| The arrays we have defined so far were 1-dimensional, analogous to vectors. It is also possible to define higher-dimensional arrays, analogous to matrices or higher-rank tensors. The syntax to define an N-dimensional array is: type name[size1][size2]...[sizeN];
 This will define a size1bysize2by ... bysizeNarray. Elements of a multi-dimensional array can be accessed using the same notation, with the element index inside each bracket. Recall that the first element has index 0 - so the first element in the array will bename[0][0]...[0]. To initialize the array, we use nested curly brackets: type name[size1][size2]...[sizeN] = {elements1, elements2, ..., elementsN};
 where here elements1is a sub-array withsize1elements, given by a list inside curly brackets, and so on. For example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t a[2][3] = {{1, 2, 3}, {4, 5, 6}};
    printf("%" PRId64 "\n", a[0][1]);
}
 The program prints 2because that is the element in index[0][1]of the array; the0indicates we are in the first sub-array{1, 2, 3}, and the1indicates the second element of that sub-array. Note that we could also writea[][3], and the compiler will automatically recognize that the array has 2 sub-arrays; but the size of the sub-arrays has to be declared explicitly, soa[][]will not work. | 
| A string is a sequence of characters. In C, strings are literally arrays of elements of type char. To specify a specific value for a single character, we use single quotes: char c = 'A';
 This will define a variable cof data typecharwhich has as its value the letterA. To specify a specific value for a string of characters, we declare the string as an array of characters, and use double quotes: char s[] = "ABCDEFG";
 When using printf, we have seen that the format placeholder%sstands for a string. Similarly,%cstands for a single character. Since a string is just an array of characters, we can read and write each character individually, as we would for an array: #include <stdio.h>
int main(void)
{
    char s[] = "ABCDEFG";
    printf("%c\n", s[2]);       // Prints "C" since that is the 3rd character in the string (recall that arrays start at index 0)
    s[4] = 'e';                 // Changes the 5th character "E" to lowercase "e"
    printf("%s\n", s);          // Prints "ABCDeFG"
    printf("%zu\n", sizeof(s)); // Prints "8"
    printf("%d\n", s[7]);       // Prints "0"
}
 Note the last two lines: sizeof(s)is 8 bytes, but the string only has 7 characters. Furthermore,s[7], the 8th character in the string, has a value of 0. The reason is that in C, strings are null-terminated: the last character in the string is followed by a null character, which is simply acharwith a value of 0. The null character indicates that the string has terminated. This also means that'e'and"e"are not the same, as'e'is just one character (taking 1 byte of space) while"e"is a null-terminated string (taking 2 bytes of space, including the terminating null). C provides a variety of standard functions for manipulating strings. We will discuss some of them later in the course. | 
|  | 
| A function is a block of code that gets an input, executes some statements, and returns an output. C, by itself, does not contain any functions; they are either defined by the user, or imported from libraries. mainis an example of a function defined by the user, and indeed, it must be defined in any C program. Its (optional) input is given in the form of command-line arguments, as we will discuss below. Its executed statements are the main code of the program itself. Its output, as we discussed above, is an integer, with 0 (returned by default) indicating successful execution.
 An example of a function imported from a library is printf, which we import by including the header filestdio.h. Its (mandatory) input is a format string and the data to print. Its executed statements print the data to the terminal using the specified format. Its output is usually not important - it's an integer indicating the number of characters printed, or a negative value if an error occurred. Functions are mainly used as a form of abstraction. When you use a function, you only need to know that it takes a certain input, performs a certain well-defined task, and returns a certain output. The details of how the function works internally are not relevant. For example, you don't need to know how exactly printfworks; all you need to know is that it prints out the data you give it as input. Of course, when you write your own functions, you do know how they work, since you wrote them. However, what's important here is that you can modify how those functions work internally, for example to fix bugs or to optimize their performance, and yet safely keep all the rest of the code unchanged, since any statements which call those functions do so independently of how the function works internally. (This approach is taken to the next level in object-oriented programming, which we will cover later in the course.) Virtually any non-trivial C program must make use of some user-defined functions other than main. To define a function, we use the following syntax: type name (type1 arg1, type2 arg2, ...)
{
    statement1;
    statement2;
    // etc...
    return value;
}
 Where:  typeis the data type of the function's output. If the function returns no output, we usevoidas the data type.nameis the function's name, which must follow the same rules as variable names.The function's input takes the form of variables arg1,arg2, etc. which are of the data typestype1,type2, etc. respectively. Again, if the function doesn't take any arguments, we usevoidinstead. If the function does not change the value of the argument, useconstas part of the type declaration (see below).The statements executed by the function are those inside the code block indicated by the curly brackets.valueis the value to be returned as the function's output, and it must be of the data typetypedeclared in the function's definition. Note that there can be multiplereturnstatements in a function, e.g. if we want to return one value in one case and another value in another case; however, oncereturnis executed, the function terminates. If the function has a return type ofvoid, we do not need to write areturnstatement. To call this function, we simply use the syntax name(arg1, arg2, ...). If the function has no input, we use empty parentheses:name(). Any variables declared in the context of the function are completely independent of the rest of the program (recall the discussion on variable scope). Variables declared elsewhere in the program are not accessible within the function, and variables declared in the function, including both the arguments and any variables declared within the function's code block, are not accessible elsewhere in the program. A function may only be defined once in the program - if we define two functions with the same name, the compiler won't know which one to call. Furthermore, functions cannot be nested. This means that we cannot define a function inside another function, including inside the mainfunction. Instead, each function must be defined at the highest scope (the file scope). Most functions generally take a fixed number of arguments, which have to be of specific data types. However, printf, for example, can take any number of arguments of any type, as long as the first argument is a string specifying the number and type of the remaining arguments (e.g."%d %s"indicates that the function should expect one integer and one string). This is called a variadic function. User-defined functions can also be variadic, but this is almost never actually needed, so we will not cover it here. | 
| Here is a simple example of defining and using a function: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int64_t add(const int64_t a, const int64_t b)
{
    return a + b;
}
int main(void)
{
    printf("%" PRId64 "\n", add(5, 6));
}
 The function addsimply takes two 64-bit integers and gives their sum as output. This means thatadd(5, 6)will return the value11, which is then printed out usingprintf. Notice that we added the constmodifier to the arguments to indicate - both to the compiler and to the human reading our code - that these arguments will not be modified by the function. This can often help avoiding mistakes and bugs. If, for example, we write the statementa++;as the first line of the function, the program will not compile, sinceais constant and thus cannot be changed.  Warning: Make sure to always use the constmodifier on any variables that should not be changed. This applies both to variables declared within the function and to variables obtained as function arguments. Doing so will ensure that these variables are not accidentally modified, either by you or by someone else using your code, which may introduce bugs. It will also make your code easier to understand, by clearly indicating which variables are assumed to be constant. Often we do actually want the function to be able to modify the values of the arguments it receives as input, in which case we do not use const. However, note that in this case, only the local copy of the variable, within the function's local scope, is modified. Here is an example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void increment(int64_t x)
{
    x++;
    printf("increment(): x = %" PRId64 "\n", x);
}
int main(void)
{
    int64_t x = 0;
    increment(x);
    printf("main():      x = %" PRId64 "\n", x);
}
 The output of this program is: increment(): x = 1
main():      x = 0
 The local copy of xinside the functionincrement()has been incremented usingx++, but this does not affect the value ofxinmain(). The only way for a function to modify variables outside its local scope is by using pointers, which we will learn about later. | 
| Recursion is when a function calls itself. Of course, this only makes sense if the recursion terminates at some point, otherwise the function will be called an infinite number of times. For example, let's write a program which calculates the Fibonacci sequence. This sequence, denoted Fn, is defined for any non-negative integer n using the relations  F0 = 0,F1 = 1,Fn = Fn-1 + Fn-2 for n ≥ 2. Since each element in the sequence is defined in terms of the previous two elements, it is natural to calculate the elements of this sequence by recursion: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
uint64_t fibonacci(const uint64_t n)
{
    if (n == 0)
        return 0;
    else if (n == 1)
        return 1;
    else
        return fibonacci(n - 1) + fibonacci(n - 2);
}
int main(void)
{
    for (uint64_t i = 0; i <= 10; i++)
        printf("F_%" PRIu64 " = %" PRIu64 "\n", i, fibonacci(i));
}
 Output: F_0 = 0
F_1 = 1
F_2 = 1
F_3 = 2
F_4 = 3
F_5 = 5
F_6 = 8
F_7 = 13
F_8 = 21
F_9 = 34
F_10 = 55
 The function fibonacciis literally just the definition of the sequence: it returns 0 for n = 0, 1 for n = 1, and the sum of the two previous elements for n ≥ 2. | 
| Above we declared and defined add()beforemain(), becauseadd()is being used inmain(), and it needs to be declared before it is used. Let us move the functionadd()aftermain(): #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    printf("%" PRId64 "\n", add(5, 6));
}
int64_t add(const int64_t a, const int64_t b)
{
    return a + b;
}
 The program does not compile, and both the linter and the compiler give the warning "use of undeclared identifier 'add'". If we really have to define the function after main, we must at the very least declare it beforemain; the difference is that a definition specifies the actual code of the function, while a declaration only specifies the function's signature: return type, name, and argument types. If we add a declaration ofaddbeforemain, the program will execute correctly: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int64_t add(const int64_t a, const int64_t b);
int main(void)
{
    printf("%" PRId64 "\n", add(5, 6));
}
int64_t add(const int64_t a, const int64_t b)
{
    return a + b;
}
 This is called a forward declaration. In fact, we could have also declared the function as int64_t add(int64_t, int64_t), without theconstmodifiers or the argument names. The declaration only needs to include the minimum information required to know how to call the function, and theconstmodifiers and argument names do not change how the function is called, they are just internal details of the function. However, for documentation purposes, it is often useful to include this information in the declaration as well, as the user often only has access to the declaration but not the definition (we will see why later). You're probably wondering why the compiler can't just look ahead and find the declaration later in the code. This is certainly the case in many higher-level languages, but in C and C++, the language specifications require us to always declare functions before we use them. Although it should in principle be possible to modify the compiler so that it looks ahead for the declaration, this would be a very substantial change to the way the language works, and might break older code. | 
| In the simple case we just discussed, forward declaration was not actually necessary, since we could have just defined the function before main()and saved ourselves the trouble. However, forward declaration is mandatory in some situations, for example if two functions call each other recursively, also known as mutual recursion. Here is an example: #include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
bool is_odd(const int64_t n);
bool is_even(const int64_t n)
{
    if (n > 0)
        return is_odd(n - 1);
    else if (n < 0)
        return is_odd(n + 1);
    else
        return true;
}
bool is_odd(const int64_t n)
{
    if (n > 0)
        return is_even(n - 1);
    else if (n < 0)
        return is_even(n + 1);
    else
        return false;
}
int main(void)
{
    const int64_t n = 7;
    printf("%" PRId64 " is %s", n, is_even(n) ? "even" : "odd");
}
 This is an extremely inefficient way of determining whether an integer is even or odd - we are just giving it here as an example of mutual recursion. Basically, as long as the argument is not zero, each of the functions is_even()andis_odd()calls the other function with an argument closer to zero by one step (subtract 1 if positive, add 1 if negative). By simulating each step manually (e.g. on a piece of paper), you can convince yourself that this is indeed a (very bad) algorithm for finding if a number is even or odd. If you remove the forward declaration of is_odd(), you will see that the program does not execute, because thenis_odd()is called from withinis_even()without being declared first. In this case, there is no way to rearrange the program so that we do not need to declare at least one of the functions in advance. Therefore, this is one case where forward declaration must be used. We will see other cases later. | 
| Above we discussed local variables, which are only visible within a particular scope. Global variables are those which are defined in the highest possible scope, before any functions, including main, are declared. Since they are not local to any specific scope, they are accessible from anywhere in the program, including themainfunction and any other function, unless any local variables with the same names are defined. For example, the following program does not compile, since nis only defined locally in themainfunction and is thus inaccessible to theprint_nfunction: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_n(void)
{
    printf("%" PRId64 "\n", n);
}
int main(void)
{
    int64_t n = 5;
    print_n();
}
 If we declare nbeforemainbut afterprint_n, this still doesn't work, sinceprint_ndoes not know aboutn: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_n(void)
{
    printf("%" PRId64 "\n", n);
}
int64_t n;
int main(void)
{
    n = 5;
    print_n();
}
 However, if we declare nbeforeprint_nas well, the program now works: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int64_t n;
void print_n(void)
{
    printf("%" PRId64 "\n", n);
}
int main(void)
{
    n = 5;
    print_n();
}
 Note also that if we declare another variable named ninmain, then that is a local variable, visible only tomain, and therefore changing that variable doesn't change the value of the global variablen. In the following example, the output will not be 5, but 7: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int64_t n = 7;
void print_n(void)
{
    printf("%" PRId64 "\n", n);
}
int main(void)
{
    int64_t n = 5;
    print_n();
}
 Note that the compiler will warn us that nis an unused variable - this refers to the local variableninsidemain(), which we indeed never used, since only the globalnwas used.  Warning: In most cases, global variables can and should be avoided. For example, providing nas an argument forprint_nin the code above will have the same effect, without needing to use a global variable. With global variables, it is very easy to make mistakes which result in the wrong value being used, as in the last example, and it is also much harder to keep track of where exactly the global variable gets modified, since it can be modified anywhere in the program. | 
| We have seen that variables defined inside functions are local to that function. They are inaccessible by any other function, and moreover, their value is discarded once the function finishes. Static variables are variables that are local to a function, but keep their values after the function has finished executing, such that the value can still be accessed and modified every subsequent time the function is called. They are defined using the keyword staticbefore the variable definition. Here is an example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void count(void)
{
    static uint64_t n = 0;
    n++;
    printf("This function has been called %" PRIu64 " times.\n", n);
}
int main(void)
{
    count();
    count();
    count();
}
 The output is: This function has been called 1 times.
This function has been called 2 times.
This function has been called 3 times.
 However, if we delete the word static, all three lines will sayThis function has been called 1 times. Note that the line static int64_t n = 0itself is called only once, that is,nis only initialized the first time the function is called. Of course, this has to be the case, otherwise there would be no point to making the variable static. | 
|  | 
|  | 
| Debugging is the process of finding and fixing bugs in your program. In this course we will debug using LLDB, the debugger which comes with Clang. This debugger is conveniently integrated into Visual Studio Code's graphical user interface. For debugging to work, a workspace must be open in Visual Studio Code (the status bar must be blue, not purple), that workspace must include a .vscodefolder with a properly-configuredlaunch.jsonfile, and a C or C++ source file must be open in the editor. Click on the Run icon in the Activity bar on the left (looks like a "play" button with a little bug next to it), or press Ctrl+Shift+D. This opens up the Run view, which is the one that is used when debugging. If you did not configure launch.jsonyet, a big blue button with the label Run and Debug will appear. If that is the case, you need go back and configure Clang and Visual Studio Code properly according to my instructions above. If you did properly configure launch.json, you will see a dropdown menu at the top of the Run view with the name of the configuration you created. The play button can be used to start debugging, but it's usually easier to just press F5. The gear button gives quick access tolaunch.json. The overflow menu (three dots) allows you to enable or disable the four components of the Run view:  VariablesWatchCall StackBreakpoints Make sure all four are enabled. The last option in this menu opens the debug console (which can also be opened with Ctrl+Shift+Y). If you configured the argsfield in thetasks.jsonfile as I previously instructed, it should look similar to this: "args": [
    "-fcolor-diagnostics",
    "-fansi-escape-codes",
    "-g",
    "${file}",
    "-o",
    "${fileDirname}\\${fileBasenameNoExtension}.exe", // Will be ${fileDirname}/${fileBasenameNoExtension} for Linux/macOS
    "-Wall",
    "-Wextra",
    "-Wconversion",
    "-Wsign-conversion",
    "-Wshadow",
    "-Wpedantic",
    "-std=c23",
    "-D__USE_MINGW_ANSI_STDIO=1" // Only needed on Windows
],
 The first argument, -g, which was added automatically by VS Code, instructs the compiler to include debugging information in the compiled binary file. This information will be used by the debugger to debug the program.  Warning: The -gflag should only be used when debugging. When you are done writing and debugging the code, and are ready to compile and distribute the release version of your program, you should remove the debugging flag, and preferably add-O2or similar optimization flags instead (more on that later). | 
| One of the most important aspects of debugging is the ability to set particular points in the source code as breakpoints, meaning that when the execution of the program reaches any of these points, it pauses. To add a breakpoint at a particular line, press F9 while the cursor is in that line. This will cause a red dot to appear on the left margin of the editor, to the left of the line number, indicating that a breakpoint has been set at that line. Alternatively, you can also click with the mouse on the margin where the red dot should be. When you add a breakpoint, it will appear in the Breakpoints section of the Run view with the name of the source file and the line number. To remove the breakpoint from a line, press F9 while the cursor is in that line, click with the mouse on the red dot, right-click on it in the Breakpoints section and choose Remove Breakpoint, or hover over it with the mouse in the Breakpoints section and click on the "X". You can disable a breakpoint temporarily, without removing it, by clicking on the checkbox next to it in the Breakpoints section, or by directly right-clicking on the red dot in the editor and choosing Disable Breakpoint. The dot will turn grey instead of red, and the breakpoint will not cause the program to pause until you enable it again. If you hover the mouse over the Breakpoints section you will see three buttons. The button with two circles lets you deactivate all of the breakpoints, so that the execution will not stop on any breakpoints, whether enabled or disabled. The button with two squares permanently removes all of the breakpoints. To experiment with breakpoints, paste the following program into the editor: #include <stdio.h>
void test(void)
{
    printf("2 ");
    printf("3 ");
}
int main(void)
{
    printf("1 "); // Place a breakpoint here
    test();
    printf("4 ");
}
 Place a breakpoint at the indicated line and press F5 run the program. You will see that the program pauses its execution, the line where the breakpoint is located is highlighted, and the debug toolbar appears on top of the editor. This toolbar has the following options from left to right:  Continue / Pause (F5)Step Over (F10)Step Into (F11)Step Out (Shift+F11)Restart (Ctrl+Shift+F5)Stop (Shift+F5) First, let us try Continue / Pause (F5). You will see that the program will simply continue running until it ends. Try this, and then press F5 to run the program again and pause on the breakpoint. Now, let us try Step Over (F10). It will execute the first line on the mainfunction, and you will see the output1in the terminal. Press F10 again to step over the second line,test(). You will notice that the output2 3is written to the terminal, but the debugger doesn't enter thetestfunction itself - that is the meaning of stepping over the line. Finally, press F10 again to execute the last line of themainfunction, which will output4to the terminal. Press Restart (Ctrl+Shift+F5) to restart the program and pause on the breakpoint again. Use Step Over (F10) on the first line to step over the printffunction. But when the cursor is on the second line, press Step Into (F11) to step into thetestfunction instead of over it. By pressing Step Over (F10) inside the function, you will be able to execute it line-by-line, until you reach the end of the code block (}), which will transfer you back to themainfunction. If you now try to step into theprintffunction, you will be taken into the header filestdio.hwhere the function is defined, but not into the actual function, since its source code is not available to the debugger. Next, press Restart (Ctrl+Shift+F5) again, and Step Into (F11) the testfunction again. Then press Step Out (Shift+F11). You will see that you stepped out of thetestfunction and back to themainfunction, with the execution now on the last line of themainfunction. Press Stop (Shift+F5) to stop the execution without executing the last line. A function breakpoint is a breakpoint that will be triggered when a particular function is executed, instead of on a particular line. To add one, you can use one of the following methods:  Click on Run > New Breakpoint > Function Breakpoint....In the Run view, hover with the mouse over the Breakpoints section, and click on the plus sign.Use a keyboard shortcut. This is the most convenient way, but you will have to set up this shortcut yourself. You can do so by pressing F1 to bring up the Command Pallette and choosing "Preferences: Open Keyboard Shortcuts" (or just pressing Ctrl+K Ctrl+S). Then search for "Debug: Function Breakpoint" and set the keybinding (I like to use Ctrl+F10). Remove the existing breakpoint, and add two function breakpoints: one for mainand one fortest. Notice that the symbol for a function breakpoint is a red triangle instead of a red dot. When you press F5, you will see that the execution stops in the beginning of themainfunction. Press Continue / Pause (F5) and execution will continue, and then stop in the beginning of thetestfunction. | 
| A conditional breakpoint is a breakpoint that only triggers if a certain condition is met, rather than whenever execution reaches the line where the breakpoint was set. Copy the following program into the editor: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    for (uint64_t i = 1; i <= 10; i++)
        printf("%" PRIu64 " ", i); // Place a breakpoint here
}
 We would like to place a conditional breakpoint at the indicated line inside the forloop, which will only trigger whenireaches the value5. There are three ways to do this:  Create a normal breakpoint with F9, right-click on the red dot, select Edit Breakpoint..., and choose "Expression" from the dropdown menu.Click on Run > New Breakpoint > Conditional Breakpoint....Use a keyboard shortcut. As above, press F1 to bring up the Command Pallette and choose "Preferences: Open Keyboard Shortcuts" (or just press Ctrl+K Ctrl+S), search for "Debug: Conditional Breakpoint", and set the keybinding (I like to use Ctrl+F9). Using any of these methods, create a new conditional breakpoint at the indicated line, write i == 5as the condition, and press Enter. Notice that the red dot will have a tiny equal sign on it. Now press F5 to run the program. You will see that the numbers0 1 2 3 4will be printed to the terminal, and only then, when the conditioni == 5is met, the execution will stop on the breakpoint. If you press F5 to continue, the program will keep running without breaking again. A logpoint is similar to a breakpoint, except that instead of pausing the execution of the program, it prints a message to the debug console. Programmers often emulate logpoints manually by explicitly adding a statement such as printf("i is now %" PRIu64 "\n", i)into the source code, but a logpoint allows us to do this in a more convenient and dynamic way, without changing the source code itself. As with conditional breakpoints, there are three ways to create logpoints:  Create a normal breakpoint with F9, right-click on it, select Edit Breakpoint..., and choose "Log Message" from the dropdown menu.Click on Run > New Breakpoint > Logpoint....Set up a keybinding for "Debug: Add Logpoint..." (I like to use Alt+F9). First remove the conditional breakpoint, and then, using any of these methods, create a new logpoint at the indicated line and type for loop is runningas the message. Notice that the symbol will be a red diamond instead of a red dot. Press F5, and the message will be written to the debug console (Ctrl+Shift+Y) ten times. Logpoints can do more than just print messages: they can evaluate expressions when they are entered inside curly brackets {}. Edit the logpoint (right-click on it and select Edit Logpoint...) and change the message toi is now {i}. When you press F5, you will see that the messagesi is now 1,i is now 2, etc. will be printed to the debug console. However, be careful: expressions evaluated by logpoints will affect the program itself! For example, if you change the message to i is now {++i}, then you will see that only even values ofiwill be printed, both to the debug console and the terminal, sinceiis actually incremented twice in each run of the loop. Finally, note that logpoints can also be conditional; for example, under Edit Logpoint..., we can specify i == 5for Expression andStopped at i == {i}for Log Message. This will print the messageStopped at i == 5to the debug console when execution reachesi == 5, but will not otherwise pause the program. | 
| Consider the following program, which prints out a multiplication table: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_product(const uint64_t a, const uint64_t b)
{
    printf("%3" PRIu64 " ", a * b);
}
void mul_table(const uint64_t rows, const uint64_t cols)
{
    printf("Multiplication table with %" PRIu64 " rows and %" PRIu64 " columns:\n", rows, cols);
    for (uint64_t i = 1; i <= rows; i++)
    {
        for (uint64_t j = 1; j <= cols; j++)
            print_product(i, j);
        printf("\n");
    }
}
int main(void)
{
    const uint64_t num_rows = 10; // Place a breakpoint here
    const uint64_t num_cols = 10;
    mul_table(num_rows, num_cols);
}
 Let us place a (normal) breakpoint at the indicated line - or alternatively, add a function breakpoint for the mainfunction. Press F5 and look at the Variables section of the Run view. You will see a label "Locals", and under it the variablesnum_rowsandnum_cols, which are local to the scope of themainfunction. Notice that bothnum_rowsandnum_colsare currently uninitialized, so they will have garbage values. Next, look at the Call Stack section of the Run view. You will see that Thread #1 is currently "paused on breakpoint". Since our program is not multi-threaded (that is outside the scope of our course), Thread #1 is the only relevant one. If you expand this thread using the bracket on the left, you will see main()andmain.c(or whatever the name of your source file is) listed. This simply indicates that we are currently in themainfunction of the filemain.c. Now, follow these steps and notice the changes in the Run view each time a statement is executed:  Press F10. num_rowswill be initialized to10.num_colsis still uninitialized.Press F10. num_colswill also be initialized to10.Press F11 to step into the function mul_table. Notice that the Call Stack section changed to reflect that, with mul_tableappearing abovemain; the order of the functions in the stack indicates that whenmul_tablefinishes executing, control will be passed down tomain.Also notice that the variables in the Variables section are now rows, andcols, the variables local to the scope ofmul_table. They will both have the value of10that they obtained via the function call. If you click onmainin the call stack, you will seenum_rowsandnum_colsagain.Press F10. Now we have entered the scope of the first forloop. A new variableiwill be added to the list, uninitialized.Press F10. Now we have entered the scope of the second forloop.iwill be initialized to1. A new variablejwill be added to the list, uninitialized.Press F10. jwill be initialized to1.Press F11 to step into the function print_product. Again, notice that this function is now on top of the call stack.The Variables section now lists only the variables aandb, both equal1since those were the values passed to the function as arguments. I intentionally gave them different names to stress that they are different variables, not the same variablesiandjthat were local tomul_table.Press F10. 1will be printed to the terminal. You can continue pressing F10, and see the variables iandjgradually increase and their products being printed to the terminal. Also, if the focus is on the Variables section, you can start typing the name of a variable, and it will be highlighted. This feature is useful when you have many variables and you're looking for a specific one. In the Watches section of the Run view, you can click on the plus icon to create a new watch. This can be any expression, and it will be evaluated in real time as the program runs. Some examples of watches you can try include:  i * jwill show which number will be printed to the terminal in each iteration of the loop.cols * i + j - 10will show which element in the table (out of 100) is currently being printed.i == jwill evaluate to1if dealing with a diagonal element. However, note that when iand/orjare out of scope - for example, when if you step intoprint_product, or even when the secondforloop is finished printing a full row and thereforejis no longer defined - then expressions withiandjcannot be calculated. Finally, you can press Shift+F11 (Step Out) to step out of mul_table, which will take you back tomainwith the entire table printed. The variables in the Variables section can be of any type, including arrays, strings, and other types we will define below. Here is an example with a string: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    char letters[] = "abcde";
    for (uint64_t i = 0; i < 5; i++)
        printf("%c \n", letters[i]); // Place a breakpoint here
}
 When you place a breakpoint at the indicated line and press F5 run the program, you will see the variables iandlettersin the Variables section. Recall that strings in C are actually arrays of integers of typechar, terminated with a null character, which has the value0. You can expandlettersto see that this is in fact the case. Sometimes it's useful to be able to change the values of variables to see what happens. You can try it out with this program. First press F10, and you will see the letter aprinted to the terminal. Now, before the letterbis printed, double-click on it (element[1]of the array) and change its value to 122, which corresponds to the letterz. Press F10 two more times and you will see that the letterzwas printed to the terminal, notb. | 
| Above we saw that logpoints print log messages to the debug console. Another very important use of the debug console is to evaluate expressions manually during the run time of the program: simply write an expression at the bottom of the debug console and press Enter. You can also write expressions with multiple lines by pressing Shift+Enter to separate the lines (although this is seldom needed, since C++ doesn't care about newlines). To illustrate how to use the debug console, let us use the following program: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int64_t square(const int64_t x)
{
    return x * x;
}
void print_square(const int64_t x)
{
    printf("%" PRId64 "\n", square(x));
}
int main(void)
{
    const int64_t n = 2;
    print_square(n); // Place a breakpoint here
}
 Place a breakpoint at the indicated line, and press F5 run the program. Now go to the debug console (Ctrl+Shift+Y) and try the following:  Calculations using standard operations. For example, 1 + 1will print2to the debug console.Expressions involving variables in your program. For example: nwill print2to the debug console.n = 9will change the value ofnto9and print that value to the debug console. (Note that the value ofnwill not change in the Variables section, but it will change if you add a watch.)nwill now print9to the debug console.Calls to functions defined in the program. For example: square(5)will print25to the debug console.square(n)will print81to the debug console.print_square(n)will printvoidto the debug console, since that's the return type ofprint_square, but it will print81to the terminal.Calls to functions not defined in your program, but accessible by it, meaning that the proper header file has been included. For example, printf("test")will printtestto the terminal. | 
| If you want to know more about debugging in Visual Studio Code, the relevant chapters of the user guide and the C/C++ extension guide provide a good overview, which also includes many screenshots. In addition, this page lists advanced configuration options that can be used in launch.json. | 
|  | 
| For applications such as scientific computing, integers are not enough; we need to be able to do calculations with non-integer numbers. Furthermore, even a 64-bit long long intcan only take values with magnitude up to roughly 9×1018, but sometimes we need to do calculations with larger numbers. One option is to use infinite-precision (or arbitrary-precision) arithmetic, which allows doing calculations with arbitrary real numbers (or at least, arbitrary computable numbers) with any number of digits and of any magnitude. Unfortunately, infinite-precision arithmetic is not implemented at the hardware level, but rather at the software level using special libraries (which we will discuss later in the context of C and C++). Therefore, infinite-precision calculations are much slower compared to calculations that can be done directly at the level of the CPU. Unless you're doing calculations in pure math, where it is crucial to get precise results rather than approximations, it is almost never worth it to use infinite-precision arithmetic. The CPU represents and manipulates non-integer numbers using floating-point arithmetic. Unlike integer and infinite-precision numbers, which are always exact, floating-point numbers are almost always just an approximation. This is because there is an infinite number of real numbers within any interval, but only a limited number of floating-point representations, based on how many bits are allocated to represent the number. A 32-bit floating-point number, for example, will only allow storing at most 232 different real numbers, by definition. Therefore, we can only represent numbers with a limited number of significant digits. Floating-point arithmetic is generally very fast. In fact, a popular way to measure the performance of a computer is to find out how many floating-point operations per second (or FLOPS) it can achieve. Roughly speaking, an average laptop can achieve several hundred gigaFLOPS (where gigaFLOPS = 109 FLOPS), a high-end PC with a top-of-the-line GPU can achieve a few dozen teraFLOPS (where teraFLOPS = 1012 FLOPS), and the fastest supercomputers in the world right now can achieve several hundred petaFLOPS (where petaFLOPS = 1015 FLOPS). A floating-point number is encoded in the CPU in a form similar to: significand × baseexponent, where:  The significand (also called the mantissa) is an integer representing the significant digits of the number.The base is an integer representing the base of the number: usually 2 (binary), but it is also possible to use 10 (decimal) on some systems.The exponent is an integer representing the power to which we take the base. For example, the number 42.75 is represented as a floating-point number as follows: 42.75 = 4275 × 10-2. This example is in base 10 because that's the base we are used to; but in the computer, this number will be represented as sequence of bits. Here there are some complications that we will discuss below, but just for the purpose of illustration, in base 2, this number has the form 101010.11. Therefore, its floating-point representation will be (notice that the base and exponent are also in binary, so 10 is actually 2): 101010.11 = 10101011 × 10-10. Since the base is fixed, we only have two integers to store: the significand and the exponent. In this case, the significand requires 8 bits and the exponent requires 2 bits. Now, imagine that we only have 7 bits of storage for the significand. Then we will have to truncate 10101011 to 1010101 and compensate by decreasing the exponent, so the number will be: 101010.1 = 1010101 × 10-1. In decimal, this is 42.5, which is a very crude approximation of the original number. Of course, in practice, floating-point numbers have more than 7 bits of storage. But no matter how many bits you have, floating-point numbers will always be just approximations, except for some specific numbers that can be represented exactly, such as 42.5 in this example. In C (as in most programming languages), floating-point numbers are represented using the IEEE-754 floating-point standard, which is more complicated than our simple example. Here is how floating-point numbers are actually stored in memory:  Floating-point numbers are always signed, and the sign is stored in the first bit: 0 for positive and 1 for negative.The next bits represent the exponent. The exponent is also signed. The way this works is that, for N bits in the exponent, the actual exponent is the bit value minus 011..111 (zero followed by N-1 ones), that is, the value minus 2N-1-1. We say that the exponent has a bias of 2N-1-1. So 011..111 is an exponent of zero, 011..110 is -1, 100..000 (one followed by N-1 zeros) is +1, and so on. Furthermore, the representations 000..000 (all 0s) and 111..111 (all 1s) are reserved for special values, which we will discuss below. Therefore, the available range of exponents is from -(2N-1-2) to +2N-1-1.The rest of the bits represent the significand. However, although in the examples above I used an integer significand for simplicity, in IEEE-754 the significand is a fraction of the form 1 followed by a radix point (analogous to a decimal point, but in binary) and then the rest of the bits. This is similar to scientific notation; there is always exactly one digit before the radix point. Since that digit is always 1 (there are no other options - unlike in decimal, where it can be any digit from 1 to 9), the first 1 is implied and is not stored explicitly. So effectively we have one more bit of storage, but we must reserve a special value for zero. | 
| The three floating-point types used in C are:  float, or single-precision floating-point type. Uses a total of 32 bits, of which 1 is a sign, 8 are for the exponent, and 23 (effectively 24) for the significand.double, or double-precision floating-point type. Uses a total of 64 bits, of which 1 is a sign, 11 are for the exponent, and 52 (effectively 53) for the significand.long double, or extended-precision floating-point type. The number of bits here is platform-dependent, but on x86 systems it generally uses a total of 80 bits, of which 1 is a sign, 15 are for the exponent, and 64 for the significand. (Since this is not a standard IEEE-754 type, it does not use the trick where the first bit is implied to be 1.) Note that the C standard merely requires that long doubleis at least as precise asdoubleanddoubleis at least as precise asfloat, but the exact sizes depend on the implementation. However, the sizes of 32, 64, and 80 bits are implemented in most systems. The following program is analogous to the program we used above to display the ranges of integers: #include <float.h>
#include <stdio.h>
int main(void)
{
    printf("\nA %s has a minimum value of %e and a maximum value of %e, with at least %d decimal digits of precision.\n", "float", FLT_MIN, FLT_MAX, FLT_DIG);
    printf("\nA %s has a minimum value of %e and a maximum value of %e, with at least %d decimal digits of precision.\n", "double", DBL_MIN, DBL_MAX, DBL_DIG);
    printf("\nA %s has a minimum value of %Le and a maximum value of %Le, with at least %d decimal digits of precision.\n", "long double", LDBL_MIN, LDBL_MAX, LDBL_DIG);
}
 Here are some new things in this code:  The header file float.hstores the limits of floating-point types, just aslimits.hstores the limits of integer types. They are stored in the variablesFLT_MIN,FLT_MAX, and so on.The format placeholder %eforprintfindicates that a floating-point number is to be printed using scientific notation, i.e. as a significand followed by an exponent. The output will have the formatX.XXXe±YYYwhereX.XXXis the significand and±YYYis the exponent.%Edoes the same, but with an uppercaseEfor the exponent.The syntax %Leis necessary if using along double, similarly to how%lldis necessary if using along long. On my computer, on Windows 11 with an x86 CPU using Clang through MSYS2, the output is: A float has a minimum value of 1.175494e-38 and a maximum value of 3.402823e+38, with at least 6 decimal digits of precision.
A double has a minimum value of 2.225074e-308 and a maximum value of 1.797693e+308, with at least 15 decimal digits of precision.
A long double has a minimum value of 3.362103e-4932 and a maximum value of 1.189731e+4932, with at least 18 decimal digits of precision.
 On your computer, the output might be different, as your operating system, CPU, and/or compiler might not support an 80-bit long double. Also, note that the argument-D__USE_MINGW_ANSI_STDIO=1must be passed to the compiler (as instructed above) in order to get the correct output forlong doubleon Windows. In choosing between these data types, there are five factors that need to be taken into consideration:  Range: floathas a very limited range, which is often insufficient. For example, the Planck time, a fundamental unit of measurement in theoretical physics, is approximately5.4e-44seconds, which is too small to be represented by afloat.The range of doubleis sufficient for almost any conceivable scientific calculation.The range of long doubleis basically overkill and not needed in most applications.Precision: floathas very low precision, only guaranteeing 6 significant digits. It is therefore unsuitable for scientific calculations where accuracy is important.doubleprovides a very significant improvement, more than doubling the number of significant digits.The improvement provided by long doubleisn't as significant, but those few extra digits may prove to be very important in some applications.Memory: floattakes the least amount of space, 32 bits.doubletakes double that space, 64 bits.long double, even though it technically only uses 80 bits, is actually stored in memory using 96 or 128 bits, because the computer likes to access memory in 32-bit increments. If you need to store billions of numbers in memory, this might be an issue; for example, 1 billionfloatnumbers will take up 4 GB of memory, compared to up to 16 GB forlong doublenumbers.Performance: On most 64-bits systems, floatanddoubleoffer roughly the same performance.long doublewill generally be slower on 64-bit Intel and AMD CPUs, since it is calculated using an older and slower instruction set (namely x87, while the newer SSE instruction set only supports up to 64-bits floating-point numbers).Portability: floatanddoubleare supported by virtually all 64-bit CPUs, operating systems, and compilers.An 80-bit long doubleis not always supported. For example, in the Microsoft Visual C++ compiler,long doubleanddoubleare both 64-bit. (This is done for maximum compatibility with different types of CPUs.) My recommendation is as follows:  The only situation where you should use floatis if memory is limited. In all other cases,floatshould be avoided, as it provides much less precision with no boost to performance.doubleshould be used in most cases. It is also the default floating-point type used by the C standard library functions.long doubleshould only be used when very high precision and/or range are desired, provided you are absolutely sure your program will only run on platforms that support 80-bit floating-point types, and you are willing to accept the trade-off of slower performance and higher memory usage. | 
| Constant numbers written in the source code are automatically interpreted as doubleif they contain a decimal point followed by at least one digit. This digit can also be 0, so e.g.1is an integer but1.0is a floating-point number. To interpret them asfloatinstead, appendfto the number , e.g.1.0f. To interpret them aslong double, appendLto the number , e.g.1.0L. We have seen above that %eis used to print floating-point numbers in scientific notation, that is, with an exponent.%fprints the number as-is, without an exponent, which can sometimes result in very long numbers. If a number is added after the%, this specifies the width for the purpose of alignment on the screen - as we saw above for integers. For example,%20findicates that the number should take a space of (at least) 20 characters, padding with spaces if needed. Note that the decimal point also counts as one characters, so123.456has a width of 7. If a .is added after the width (or after the%, if no width is specified), this specifies the number of digits to print after the decimal point. For example,%.10findicates that there should be 10 digits after the decimal point. The default, if no value is entered, is 6 digits, so%fis equivalent to%.6fThis is illustrated in the following code: #include <stdio.h>
int main(void)
{
    const double d = 1.23456789;
    printf("%12.2f\n", d);
    printf("%12.4f\n", d);
    printf("%12f\n", d);
    printf("%12.8f\n", d);
    printf("%12.10f\n", d);
}
 The output is:         1.23
      1.2346
    1.234568
  1.23456789
1.2345678900
 Notice that the numbers get rounded, not truncated. With 2 digits after the decimal, the number gets rounded down to 1.23 since the next digit is 4, while with 4 digits, it gets rounded up to 1.2346 since the next digit is 6. | 
| In the last line of the program above, we printed the number 1.23456789 with 10 digits after the decimal, which is 2 more than the 8 digits after the decimal in the original number, and as a result it seemingly got padded with zeros. This seems to indicate that the number 1.23456789 is stored exactly as-is in memory. Unfortunately, that is not the case. As we explained above, only a very small set of real numbers can be represented exactly using the limited number of bits available to us - and 1.23456789 is not one of those numbers. The difference between the intended number and the number stored in memory is called a rounding error. For example, consider the following program: #include <stdio.h>
int main(void)
{
    const float f = 1.23456789f;
    const double d = 1.23456789;
    const long double l = 1.23456789L;
    printf("float:       %.20f\n", f);
    printf("double:      %.20f\n", d);
    printf("long double: %.20Lf\n", l);
}
 The output on my computer is: float:       1.23456788063049316406
double:      1.23456788999999989009
long double: 1.23456789000000000003
 We can see that all three floating-point data types get the first 8 significant digits right, but then differences begin to appear:  float, which has the smallest precision, essentially replaced the last digit, 9, with a number that is very close to 8, and the rounding error is of the order of 10-8.doubledoes a better job, replacing the last digit, 9, with something very close to 9, and the rounding error only appears in the 16th digit after the decimal, so it is of the order of 10-16long double, of course, provides the highest precision. The rounding error now only appears in the 20th digit after the decimal, so it is of the order of 10-20. However, there is still a rounding error! You will find that most real numbers inevitably introduce rounding errors when represented as floating-point numbers. This is especially true for non-integers, but even large enough integers cannot be represented exactly. For example, 4e10can be represented exactly as afloat, but5e10cannot, although is can still be represented exactly as adoubleorlong double. If we go up to5e22, it cannot even be represented exactly asdoubleanymore, but it can be represented exactly as along double: #include <stdio.h>
int main(void)
{
    printf("float:       %54.30f\n", 4e10f);
    printf("double:      %54.30f\n", 4e10);
    printf("long double: %54.30Lf\n\n", 4e10L);
    printf("float:       %54.30f\n", 5e10f);
    printf("double:      %54.30f\n", 5e10);
    printf("long double: %54.30Lf\n\n", 5e10L);
    printf("float:       %54.30f\n", 5e22f);
    printf("double:      %54.30f\n", 5e22);
    printf("long double: %54.30Lf\n", 5e22L);
}
 Output: float:                   40000000000.000000000000000000000000000000
double:                  40000000000.000000000000000000000000000000
long double:             40000000000.000000000000000000000000000000
float:                   49999998976.000000000000000000000000000000
double:                  50000000000.000000000000000000000000000000
long double:             50000000000.000000000000000000000000000000
float:       49999998890981541806080.000000000000000000000000000000
double:      49999999999999995805696.000000000000000000000000000000
long double: 50000000000000000000000.000000000000000000000000000000
 In the case of fractions, those that can be written exactly in binary (to within the bit limit of the significand) can generally be represented exactly as floating-point numbers. For example, 0.25 = 1/4 can be written in binary as 0.01, but 0.20 = 1/5 has the binary representation 0.0011 (i.e. 0011 repeating infinitely). Therefore, the former can be represented exactly, but the latter cannot - although it is, of course, more exact as a doubleand even more exact as along double: #include <stdio.h>
int main(void)
{
    printf("float:       %.30f\n", 0.25f);
    printf("double:      %.30f\n", 0.25);
    printf("long double: %.30Lf\n\n", 0.25L);
    printf("float:       %.30f\n", 0.20f);
    printf("double:      %.30f\n", 0.20);
    printf("long double: %.30Lf\n", 0.20L);
}
 Output: float:       0.250000000000000000000000000000
double:      0.250000000000000000000000000000
long double: 0.250000000000000000000000000000
float:       0.200000002980232238769531250000
double:      0.200000000000000011102230246252
long double: 0.200000000000000000002710505431
 | 
| Floating-point addition and multiplication are commutative, meaning that a + b == b + a, anda * b == b * a, as in integer arithmetic. However, due to rounding errors, these operations are not associative, meaning that in general(a + b) + c != a + (b + c)and(a * b) * c != a * (b * c). This is illustrated in the following program: #include <stdio.h>
int main(void)
{
    const double a = -1e20;
    const double b = 1e20;
    const double c = 1.0000000000000002;
    printf("a = %+.20e\n", a);
    printf("b = %+.20e\n", b);
    printf("c = %+.20e\n\n", c);
    printf("a + b = %+.20e\n", a + b);
    printf("b + c = %+.20e\n\n", b + c);
    printf("(a + b) + c = %+.20e\n", (a + b) + c);
    printf("a + (b + c) = %+.20e\n\n", a + (b + c));
    printf("a * b = %+.20e\n", a * b);
    printf("b * c = %+.20e\n\n", b * c);
    printf("(a * b) * c = %+.20e\n", (a * b) * c);
    printf("a * (b * c) = %+.20e\n", a * (b * c));
}
 On my computer, the output is: a = -1.00000000000000000000e+20
b = +1.00000000000000000000e+20
c = +1.00000000000000022204e+00
a + b = +0.00000000000000000000e+00
b + c = +1.00000000000000000000e+20
(a + b) + c = +1.00000000000000022204e+00
a + (b + c) = +0.00000000000000000000e+00
a * b = -1.00000000000000003038e+40
b * c = +1.00000000000000016384e+20
(a * b) * c = -1.00000000000000027216e+40
a * (b * c) = -1.00000000000000015127e+40
 Why these discrepancies? When we calculate (a + b) + c, we first calculatea + b, which is exactly0since the two values cancel each other out. Then we addc, so the result is exactly equal toc. On the other hand, when we calculatea + (b + c), we first calculateb + c, which mathematically should be exactly100000000000000000001.0000000000000002. However, this number has a lot more significant digits than the 15 digits allowed by thedoubledata type, so it gets truncated to1e20. We then addawhich exactly cancels it, resulting in a zero. Now let's consider multiplication. When we calculate (a * b) * c, we first calculatea * b. Mathematically, that should be exactly -1040. However, while1e20can be represented exactly as adouble,1e40cannot. We can see thata * bis instead represented as a number slightly larger (in magnitude) than -1040. We then multiply this number byc, which introduces additional rounding errors, as neither the numbers nor their product can be represented exactly. Calculatinga * (b * c)introduces different rounding errors in each step, and hence the final result is different. For similar reasons, floating-point arithmetic is not distributive, that is, a * (b + c) != (a * b) + (a * c)in general. For example: #include <stdio.h>
int main(void)
{
    const double a = 1e30;
    const double b = 1e40;
    const double c = 1e50;
    printf("a * (b + c) = %+.20e\n", a * (b + c));
    printf("(a * b) + (a * c) = %+.20e\n", (a * b) + (a * c));
}
 The output on my computer is: a * (b + c) = +1.00000000009999994502e+80
(a * b) + (a * c) = +1.00000000010000007666e+80
 | 
| Another interesting feature of floating-point numbers is that some sequences of bits are reserved to represent special values. These include:  Negative zero, -0.0, which is simply zero with the sign bit set to 1. It is the same as the usual (positive) zero, but behaves as a negative number in arithmetic, so for example -0 × +0 = -0 and -0 × -0 = +0.Positive and negative infinity, +∞ and -∞, which are obtained whenever a result's magnitude is above the maximum value allowed by the data type (e.g. above roughly 1.797693e+308for adouble), or for example when dividing by zero.Not-a-Number, NaN, which is obtained whenever the result is undefined or indeterminate, such as when dividing zero by zero or infinity by infinity. The following program demonstrates this: #include <stdio.h>
int main(void)
{
    printf("+0.0 * +0.0 = %+.1f\n", +0.0 * +0.0);
    printf("-0.0 * +0.0 = %+.1f\n", -0.0 * +0.0);
    printf("-0.0 * -0.0 = %+.1f\n\n", -0.0 * -0.0);
    printf("double:      2 * 1.7e+308 = %.1e\n", 2 * 1.7e+308);
    printf("long double: 2 * 1.7e+308 = %.1Le\n\n", 2 * 1.7e+308L);
    printf("1.0 / 0.0 = %e\n", 1.0 / 0.0);
    printf("0.0 / 0.0 = %e\n", 0.0 / 0.0);
}
 Output: +0.0 * +0.0 = +0.0
-0.0 * +0.0 = -0.0
-0.0 * -0.0 = +0.0
double:      2 * 1.7e+308 = inf
long double: 2 * 1.7e+308 = 3.4e+308
1.0 / 0.0 = inf
0.0 / 0.0 = nan
 Note that here we used the specifier +after%to print out the sign whether the number is positive or negative. All of these special values are represented by specific strings of bits within the floating-point representation that are not used for other numbers. We won't discuss how exactly this works, since it is beyond the scope of this course. Interested students can check the Wikipedia pages for floating-point arithmetic and IEEE 754 for this and many other technical details of floating-point numbers. | 
| The following program contains the function IEEE754_float(), which takes a number as a string and prints out its binary IEEE-754 representation, that is, the actual bits stored in memory, as well as some additional information. The reason it takes the number as a string is so that it can print out both the number we wanted and the number we actually got, which in most cases will be different. #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void IEEE754_float(const char* str)
{
    printf("32-bit IEEE-754 representation of %s:\n", str);
    const float flt = strtof(str, NULL);
    const uint32_t bits = *(uint32_t*)&flt;
    char sign = (char)(((bits >> 31) & 1) + '0');
    char exp[] = "00000000";
    for (size_t i = 0; i < 8; i++)
        exp[i] = (char)(((bits >> (30 - i)) & 1) + '0');
    char mantissa[] = "00000000000000000000000";
    for (size_t i = 0; i < 23; i++)
        mantissa[i] = (char)(((bits >> (22 - i)) & 1) + '0');
    printf("%c|%s|%s\n", sign, exp, mantissa);
    printf("Sign bit: %c -> %s\n", sign, sign == '1' ? "negative" : "positive");
    printf("Exponent: %s - 01111111 -> ", exp);
    const int32_t the_exp = (int32_t)(strtol(exp, NULL, 2) - 127);
    if (the_exp == 128)
        printf("128 (inf or nan)\n");
    else
        printf("%" PRId32 "\n", the_exp);
    char mantissa_val_str[33] = "001111111";
    strcat_s(mantissa_val_str, 33, mantissa);
    uint32_t mantissa_val_int = strtoul(mantissa_val_str, NULL, 2);
    printf("Mantissa: 1.%s -> %f\n", mantissa, *(float*)&mantissa_val_int);
    printf("Actual number: %.32f\n\n", *(float*)&bits);
}
int main(void)
{
    IEEE754_float("0");
    IEEE754_float("-0");
    IEEE754_float("1");
    IEEE754_float("-1");
    IEEE754_float("1.234");
    IEEE754_float("1.25");
    IEEE754_float("1e-20");
    IEEE754_float("1e+20");
    IEEE754_float("nan");
    IEEE754_float("inf");
}
 Here is the output: 0|00000000|00000000000000000000000
Sign bit: 0 -> positive
Exponent: 00000000 - 01111111 -> -127
Mantissa: 1.00000000000000000000000 -> 1.000000
Actual number: 0.00000000000000000000000000000000
32-bit IEEE-754 representation of -0:
1|00000000|00000000000000000000000
Sign bit: 1 -> negative
Exponent: 00000000 - 01111111 -> -127
Mantissa: 1.00000000000000000000000 -> 1.000000
Actual number: -0.00000000000000000000000000000000
32-bit IEEE-754 representation of 1:
0|01111111|00000000000000000000000
Sign bit: 0 -> positive
Exponent: 01111111 - 01111111 -> 0
Mantissa: 1.00000000000000000000000 -> 1.000000
Actual number: 1.00000000000000000000000000000000
32-bit IEEE-754 representation of -1:
1|01111111|00000000000000000000000
Sign bit: 1 -> negative
Exponent: 01111111 - 01111111 -> 0
Mantissa: 1.00000000000000000000000 -> 1.000000
Actual number: -1.00000000000000000000000000000000
32-bit IEEE-754 representation of 1.234:
0|01111111|00111011111001110110110
Sign bit: 0 -> positive
Exponent: 01111111 - 01111111 -> 0
Mantissa: 1.00111011111001110110110 -> 1.234000
Actual number: 1.23399996757507324218750000000000
32-bit IEEE-754 representation of 1.25:
0|01111111|01000000000000000000000
Sign bit: 0 -> positive
Exponent: 01111111 - 01111111 -> 0
Mantissa: 1.01000000000000000000000 -> 1.250000
Actual number: 1.25000000000000000000000000000000
32-bit IEEE-754 representation of 1e-20:
0|00111100|01111001110010100001000
Sign bit: 0 -> positive
Exponent: 00111100 - 01111111 -> -67
Mantissa: 1.01111001110010100001000 -> 1.475739
Actual number: 0.00000000000000000000999999968266
32-bit IEEE-754 representation of 1e+20:
0|11000001|01011010111100011101100
Sign bit: 0 -> positive
Exponent: 11000001 - 01111111 -> 66
Mantissa: 1.01011010111100011101100 -> 1.355253
Actual number: 100000002004087734272.00000000000000000000000000000000
32-bit IEEE-754 representation of nan:
0|11111111|10000000000000000000000
Sign bit: 0 -> positive
Exponent: 11111111 - 01111111 -> 128 (inf or nan)
Mantissa: 1.10000000000000000000000 -> 1.500000
Actual number: nan
32-bit IEEE-754 representation of inf:
0|11111111|00000000000000000000000
Sign bit: 0 -> positive
Exponent: 11111111 - 01111111 -> 128 (inf or nan)
Mantissa: 1.00000000000000000000000 -> 1.000000
Actual number: inf
 The output is mostly self-explanatory. Notice that infandnanare indicated by setting the exponent to 128. You should try it out with different numbers to see what you get. Developing intuition for how floating point numbers work is very important, because using them incorrectly can and will lead to incorrect results. In this program, I used some more advanced programming techniques such as pointers and string operations, which I will not explain right now, as we will learn about them later in a more organized fashion. Come back after the C portion of the course is over, and the code will be much easier to understand. | 
| The header file tgmath.hcontains many useful mathematical functions that act on floating-point numbers. In older C standards, you had to usemath.h, which contained a different function for each type; for example,exp()is the exponential function that takes adoubleand outputs adouble, whileexpf()does the same withfloatandexpl()withlong double. However,tgmath.h, which stands for type-generic math, is much convenient since it offers just one functionexp()which accepts all data types. Here is an example: #include <stdio.h>
#include <tgmath.h>
int main(void)
{
    const double x = 2.0;
    printf("sqrt(2)   = %f\n", sqrt(x));
    printf("exp(2)    = %f\n", exp(x));
    printf("log(2)    = %f\n", log(x));
    const long double pi = acos(-1.0L);
    printf("sin(pi/4) = %Lf\n", sin(pi / 4));
    printf("cos(pi/3) = %Lf\n", cos(pi / 3));
    printf("tan(pi/2) = %Lf\n", tan(pi / 2));
}
 The output on my computer is: sqrt(2)   = 1.414214
exp(2)    = 7.389056
log(2)    = 0.693147
sin(pi/4) = 0.707107
cos(pi/3) = 0.500000
tan(pi/2) = -36893488147419103232.000000
 Here I used a trick: to find the value of pi, I took the arccosine of -1,acos(-1.0L). Note thattan(pi/2)should be infinity, but due to accumulating errors, it is merely a very large but non-infinite number. As we stressed before, floating-point arithmetic is not precise! Infinite-precision arithmetic would have provided us with the exact answer "infinity", but would have taken longer to calculate. For a full list of the available functions in tgmath.h, please see the C reference. | 
| In scientific programming, we sometimes need to use complex numbers, that is, numbers of the form a+ib where i2=-1. For example, complex numbers are used in Fourier transforms. The header file tgmath.hallows us to use complex numbers in C. (There is also another header filecomplex.hwhich was used in older C standards, but it is not type-generic and thus less convenient to use.) We declare complex variables using float complex,double complex, orlong double complex, and enter the imaginary unit i usingI. Many functions declared intgmath.h, such asexp,log, and so on, work on complex numbers as well. Furthermore, there are functions specific to complex numbers:  crealreturns the real part.cimagreturns the imaginary part.conjreturns the complex conjugate.cargreturns the argument (or phase), i.e. the angle on the complex plane.fabsreturns the absolute value (or magnitude). Do not usecabs, as it is not type-generic.  Warning: If using tgmath.h, the symbolIis used for the imaginary unit, so uppercase I cannot be used as a variable name. Here is an example: #include <stdio.h>
#include <tgmath.h>
int main(void)
{
    const double complex z = 1 + 2 * I;
    printf("z      = %4.1f%+4.1fi\n", creal(z), cimag(z));
    printf("z*     = %4.1f%+4.1fi\n", creal(conj(z)), cimag(conj(z)));
    printf("|z|    = %9f\n", fabs(z));
    printf("arg(z) = %9f\n", carg(z));
    printf("z^2    = %4.1f%+4.1fi\n", creal(pow(z, 2)), cimag(pow(z, 2)));
    printf("1/z    = %4.1f%+4.1fi\n", creal(1 / z), cimag(1 / z));
}
 Output: z      =  1.0+2.0i
z*     =  1.0-2.0i
|z|    =  2.236068
arg(z) =  1.107149
z^2    = -3.0+4.0i
1/z    =  0.2-0.4i
 | 
| As we have seen, variables in C belong to very strictly defined and constrained data types. However, it is possible to convert from one type to another. | 
| When a value in one type is assigned to a variable declared in another type, that value is implicitly converted to the target variable's type. For example, a number with a decimal point in the source code is automatically interpreted as a double, but if we assign it to a variable with an integer data type, it will be implicitly converted to an integer. Note that the value will not be rounded to the nearest integer; anything after the decimal point will simply be thrown away. To illustrate, the following code will output 3, meaning thatxhas the value3and not3.6, due to implicit conversion fromdoubletoint64_t: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 3.6;
    printf("%" PRId64 "\n", x);
}
 If you turned on the -Wconversioncompiler flag as I instructed above, you will be warned that this implicit conversion changes the value ofx. Therefore, it is very important to turn this warning flag on, as otherwise you may not be aware thatxdoes not have the correct value.  Warning: Implicit conversion can lead to serious bugs. There is really no reason to ever use it intentionally, and it should be avoided at all costs. Make sure to always enable the -Wconversionflag in order to detect accidental implicit conversions! | 
| A value can be explicitly converted from one data type to another using type casting. To do this, add the target type in brackets before the value to be converted. One example where this is needed is when we divide two integers. In C, dividing two integers results in an integer, with anything after the decimal truncated. So for example, 5/3 ≈ 1.666667 will be truncated to 1. To get a fraction, we must first cast the integers to a floating-point data type such as double: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int64_t x = 5, y = 3;
    // Integer division truncates towards zero, so 5/3 will be truncated to 1.
    printf("Integer division:   x / y = %" PRId64 "\n", x / y);
    // Generates a warning and does not print the correct result, since we are trying to format an integer as a floating-point number.
    printf("No casting:         x / y = %f\n", x / y);
    // Generates a warning, but does print the correct result since x is cast to double.
    printf("Casting x:          x / y = %f\n", (double)x / y);
    // Same as the last one, but this time we cast y to double, with the same effect.
    printf("Casting y:          x / y = %f\n", x / (double)y);
    // To avoid the warning, we must cast both integers to double.
    printf("Casting both:       x / y = %f\n", (double)x / (double)y);
}
 Output: Integer division:   x / y = 1
No casting:         x / y = 0.000000
Casting x:          x / y = 1.666667
Casting y:          x / y = 1.666667
Casting both:       x / y = 1.666667
 | 
|  | 
|  Warning: Pointers are possibly the most confusing thing in C, so make sure to read this section very carefully!  Any variable defined in C (or indeed, any programming language) is stored in the computer's memory. We can find the variable's address - a number specifying where in the memory it is stored - by preceding the variable's name with an ampersand &. Consider the following program: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int8_t var_int8_t = 0;
    const int16_t var_int16_t = 0;
    const int32_t var_int32_t = 0;
    const int64_t var_int64_t = 0;
    const long double var_long_double = 0;
    const double var_double = 0;
    const float var_float = 0;
    printf("sizeof(var_int8_t)      = %2zu, &var_int8_t      = %p\n", sizeof(var_int8_t), (void *)&var_int8_t);
    printf("sizeof(var_int16_t)     = %2zu, &var_int16_t     = %p\n", sizeof(var_int16_t), (void *)&var_int16_t);
    printf("sizeof(var_int32_t)     = %2zu, &var_int32_t     = %p\n", sizeof(var_int32_t), (void *)&var_int32_t);
    printf("sizeof(var_int64_t)     = %2zu, &var_int64_t     = %p\n", sizeof(var_int64_t), (void *)&var_int64_t);
    printf("sizeof(var_long_double) = %2zu, &var_long_double = %p\n", sizeof(var_long_double), (void *)&var_long_double);
    printf("sizeof(var_double)      = %2zu, &var_double      = %p\n", sizeof(var_double), (void *)&var_double);
    printf("sizeof(var_float)       = %2zu, &var_float       = %p\n", sizeof(var_float), (void *)&var_float);
}
 On my computer, one possible output is as follows: sizeof(var_int8_t)      =  1, &var_int8_t      = 000000af047ff7ff
sizeof(var_int16_t)     =  2, &var_int16_t     = 000000af047ff7fc
sizeof(var_int32_t)     =  4, &var_int32_t     = 000000af047ff7f8
sizeof(var_int64_t)     =  8, &var_int64_t     = 000000af047ff7f0
sizeof(var_long_double) = 16, &var_long_double = 000000af047ff7e0
sizeof(var_double)      =  8, &var_double      = 000000af047ff7d8
sizeof(var_float)       =  4, &var_float       = 000000af047ff7d4
 This program prints out the memory addresses of variables of various bit sizes. The output will change every time you run the program, since the program's memory is allocated in a different location each time it runs. To print the addresses, I used the format placeholder %p, which is intended specifically to print memory addresses. The type cast(void *)is used to convert the pointers to the appropriate data type,void *, which is expected by%p. (The code will work even without the casting, but it will produce warnings.) %pprints out each address as a 16-digit hexadecimal number, which is appropriate since its size is exactly 64 bits. Hexadecimal is base 16, so one hexadecimal digit represents log216 = 4 bits, and thus 16 digits represent 16 × 4 = 64 bits.
 For the digits above 9, we use a= 10,b= 11,c= 12,d= 13,e= 14, andf= 15. Therefore,0000000000000000is the very first memory address, andffffffffffffffffwould be the largest address it is possible to access on a 64-bit system (if I happened to have 264 bytes = 16 exabytes of memory). The memory for these variables was allocated automatically by the compiler; I didn't have to explicitly allocate memory for them. This means that these variables are stored in the stack, which is the (small) portion of memory used to store all of the variables for which memory has been automatically allocated. The stack is small, typically around 1-8 MB, and is generally used to store small amounts of temporary data, such as local variables, and not anything that's too big (such as large arrays) or that needs to be stored for a long time, which should instead be stored in the heap (more on that later). As you can see by comparing the last digits of each address, newer variables stored at lower addresses than older variables. This is how addresses in the stack are usually allocated: from top to bottom. The data for the variables themselves is stored in consecutive bytes in ascending order, so for example, var_int16_ttakes up the 2 bytes at the addresses000000af047ff7fcand000000af047ff7fd. If you run the program several times, you may notice that the last digit always stays the same; this is due to stack alignment. On 64-bit CPUs, memory blocks are aligned to 16 bytes (or 128 bits), which means each block starts at a memory address that is a multiple of 16 - and therefore the starting address always ends with a 0, even though the preceding digits can be arbitrary. Since the stack for this program always contains the same variables with the same sizes, their addresses will always have the same last digit. The stack pointer holds the address of the last variable that was allocated in the stack. When a new variable is declared, the stack pointer goes down one step, and when the scope of a variable expires (i.e. we exit the code block in which it was declared), the stack pointer goes up one step. You can see an illustration on Wikipedia.  You may be wondering why there is a gap of 3 bytes between var_int8_tandvar_int16_t(in hexadecimal,f - c = 3), even thoughvar_int16_tonly takes up 2 bytes. This is again due to alignment; each variable must be stored at an address that is an integer multiple of the size of the variable, sovar_int16_tmust be stored at an even address. Storing it at00000012afdff9fdwould eliminate the gap, but would also violate the alignment, since this address is odd. If you add another 1-byte variable betweenvar_int8_tandvar_int16_t, then you will see that it fills the gap. | 
| A pointer is simply a variable which points to a particular address in memory. As I said in the introduction, C, unlike higher-level programming languages, allows you to access memory directly - which, if done correctly, can result in significantly improved flexibility, performance, and resource use compared to other languages. Directly accessing memory is done using pointers. Pointers are declared using an asterisk *, with the syntax: type *name;
 Here, typeindicates the type of the variable the pointer points to - not the type of the actual value of the pointer itself, which (on a 64-bit system) is always going to be a 64-bit integer, since memory addresses are 64 bits long.*indicates that we are declaring a pointer, andnameis the name of the pointer. Once we declared the pointer, namewill be the address it points to, while*namewill be the variable stored at that address. This is demonstrated in the following program: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    int32_t x = 7;
    int32_t *p = &x;
    printf("p = %p, *p = %d\n", (void *)p, *p);
    *p = 9;
    printf("x = %d\n", x);
}
 On my computer, the output is: p = 000000000061fe14, *p = 7
x = 9
 Let us go over the code line by line:  In the first line, int32_t x = 7;declares and initializes a 32-bit integer variablex. This means that 32 bits of memory are automatically allocated (in the stack), and the number7is then stored at that point in memory.In the second line, int32_t *p = &x;declares a pointerpwhich will be used to point to a 32-bit integer. We initializepto&x, which is the (64-bit) address where the value ofxis stored in memory.In the third line, we print out the values of pand*p. You can see thatpis a memory address, while*pis the value stored at that address, which is7since that is the value we assigned tox. Reading or writing the value at the address pointed to by a pointer via the syntax*pis called dereferencing a pointer.In the fourth line, *p = 9changes the value at the address pointed to byp(not the value ofpitself) to9. Of course, since the value at that address is none other than the value ofx, this means that the value ofxwill now be9. This is another example of dereferencing a pointer.In the fifth line, we print the value of xto verify this.  Warning: A pointer's value is always a 64-bit memory address, but it can only point to variables of a specific data type chosen at declaration time. The declaration type *means "pointer to a variable of the giventype". If you try to assign to a pointer the address of a variable of a different type, e.g.int16_t x; int8_t *p = &x;, you will get a warning from the compiler. Note that the declaration void *can be used to define (or type cast) a general pointer that doesn't refer to any specific data type; this is what we used in the programs above to print the addresses using the%pplaceholder. It is important to understand that even if we don't use pointers explicitly in our code, behind the scenes any variable is nothing but a pointer to a certain address in memory. This means that every time we write something like x = 7, what actually happens is*(&x) = 7, that is, the number 7 is stored in the address in memory pointed to by&x. In other words, once we assignp = &x, the dereference*pis essentially synonymous tox. | 
| Consider the following program: #include <stdio.h>
int main(void)
{
    double var = 0;
    double other_var = 0;
    const double con = 0;
    const double other_con = 0;
    double *var_ptr_to_var = &var;
    double *const con_ptr_to_var = &var;
    const double *var_ptr_to_con = &con;
    const double *const con_ptr_to_con = &con;
    var = 1; // Legal: var is not const, so can be changed.
    con = 1; // Illegal: con is const, so cannot be changed.
    var_ptr_to_var = &other_var; // Legal: pointer itself is not const, so can be changed.
    *var_ptr_to_var = 2;         // Legal: variable pointed to is not const, so can be changed.
    con_ptr_to_var = &other_var; // Illegal: pointer itself is const, so cannot be changed.
    *con_ptr_to_var = 2;         // Legal: variable pointed to is not const, so can be changed.
    var_ptr_to_con = &other_con; // Legal: pointer itself is not const, so can be changed.
    *var_ptr_to_con = 2;         // Illegal: variable pointed to is const, so cannot be changed.
    con_ptr_to_con = &other_con; // Illegal: pointer itself is const, so cannot be changed.
    *con_ptr_to_con = 2;         // Illegal: variable pointed to is const, so cannot be changed.
}
 This program illustrates an important distinction between:  A constant vs. non-constant variable,A constant vs. non-constant pointer.  A non-constant variable has the type type. The variable can then be changed at will.A constant variable has the type const type. Once it is defined and initialized, it can never be changed. This has two benefits. First, it helps the human reader know that the variable is not supposed to be changed, and if they try to modify the program in a way that changes the variable (which can potentially cause bugs), it will not compile. Second, it helps the compiler know that the variable will not change, which could result in better optimizations.A non-constant pointer has the type type *orconst type *. The pointer itself can then be changed at will, meaning that the memory address it points to can be changed to a different address. Whether the actual variable stored at that address can be changed depends on whether that variable is constant or not: type *is a non-constant pointer to a non-constant variable of typetype, so the variable can be changed as well.const type *is a non-constant pointer to a constant variable of typeconst type, so the variable cannot be changed, even though the pointer itself can be changed.A constant pointer has the type type *constorconst type *const. The pointer itself can never be changed, meaning that it forever points to the same memory address. Whether the actual variable stored at that address can be changed depends on whether that variable is constant or not: type *constis a constant pointer to a non-constant variable of typetype, so the variable can be changed even though the pointer cannot be changed.const type *constis a constant pointer to a constant variable of typeconst type, so the variable cannot be changed either. This can be confusing at first, but it becomes easier to understand once you realize that types in C must be read from right to left:  type xmeans "a variablexof typetype".const type xmeans "a variablexof typetypewhich isconstant".type *pmeans "a variablepwhich serves as a pointer (*) to a variable of typetype".const type *pmeans "a variablepwhich serves as a pointer (*) to a variable of typetypewhich isconstant".type *const pmeans "a variablepwhich isconstant and serves as a pointer (*) to a variable of typetype".const type *const pmeans "a variablepwhich isconstant and serves as a pointer (*) to a variable of typetypewhich isconstant". | 
| An array in C is actually nothing more than a pointer to the address in memory where the first element of the array is stored. Consider the following code: #include <stdint.h>
#include <stdio.h>
int main(void)
{
    const int32_t a[] = {2, 3, 5};
    printf("First element:  address %p, value %d\n", (void *)a, *a);
    printf("Second element: address %p, value %d\n", (void *)(a + 1), *(a + 1));
    printf("Third element:  address %p, value %d\n", (void *)(a + 2), *(a + 2));
}
 On my computer, the output is: First element:  address 000000000061fe14, value 2
Second element: address 000000000061fe18, value 3
Third element:  address 000000000061fe1c, value 5
 In the first line, we see that the value of the array aitself is, in fact, a pointer to the address where the first element is stored - on my computer, that address is000000000061fe14. When we dereference that pointer using*a, we get the first element of the array,a[0]. In the next line, we add 1 to the address. Confusingly, this does not result in 000000000061fe15, as one might expect, but rather in000000000061fe18. This is because each element of the array is a 32-bit integer, and 32 bits is 4 bytes; when we add 1 to the pointer, C instead adds the number of bytes required to advance to the next element in the array - in this case, 4 bytes. Similarly, when we add 2 to the pointer in the third line, C actually adds 8 to the address. This is called pointer arithmetic. From this we learn thata[n]in C is actually just a convenient shorthand for*(a + n)! Also note that ais automatically a constant pointer, since if you try to assign any value toaitself, the program will not compile. | 
| When a function gets its input arguments, they are stored in new local variables within that function's scope. These variables are, as we explained above, completely independent of any other variables in the program, and will be destroyed when the function finishes executing. Consider the following (incorrect) program: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void swap(int64_t x, int64_t y)
{
    const int64_t temp = x;
    x = y;
    y = temp;
}
int main(void)
{
    int64_t a = 1, b = 2;
    printf("Before swap: a = %" PRId64 ", b = %" PRId64 "\n", a, b);
    swap(a, b);
    printf("After swap:  a = %" PRId64 ", b = %" PRId64 "\n", a, b);
}
 If you run the program, you will see that aandbdid not actually swap their values. This is because when we calledswap(a, b), the values ofaandbare stored in the new local variablesxandy, which exist at a completely different place in memory. The function then swaps these two local variables with each other, but the original variablesaandbremain untouched. You might think that changing the names of aandbinmaintoxandywill solve the problem. However, as we stressed above (see variable scope and functions), any variable declared within a scope is a new variable local to that scope, regardless of whether any other variables with the same name already exist in other scopes. Therefore, this will not work. To make this program work, we must give the function swapthe pointers toaandbinstead of their values. Here is how to do it: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void swap(int64_t *const x, int64_t *const y)
{
    const int64_t temp = *x;
    *x = *y;
    *y = temp;
}
int main(void)
{
    int64_t a = 1, b = 2;
    printf("Before swap: a = %" PRId64 ", b = %" PRId64 "\n", a, b);
    swap(&a, &b);
    printf("After swap:  a = %" PRId64 ", b = %" PRId64 "\n", a, b);
}
 Now swapis declared with arguments that are pointers to integers, and accesses the values of these integers by dereferencing them, with the usual syntax*xand*y. When we callswap, we do not pass the values ofaandb, but rather their addresses, using the syntax&aand&b. If you run this code, you will see that it works as intended: Before swap: a = 1, b = 2
After swap:  a = 2, b = 1
 Notice that the arguments of swap()have the typeint64_t *const, which as we explained above means the pointer itself is constant, but not the variable it points to. If you instead writeconst int64_t *, you will get an error, because then the variable itself isconstand thus cannot be changed.int64_t *constmeans "aconstpointer to a variable of typeint64_t", thus the pointer (i.e. the address of the variable) is constant, which is indeed the case here since we never change the actual pointer, but the variable itself (i.e. the value at that address) can be changed, which must be the case here since we want to change both variables. | 
| Consider the following program: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_array(const char name[const], const uint64_t size, const uint64_t the_array[const])
{
    for (uint64_t i = 0; i < size; i++)
        printf("%s[%" PRIu64 "] = %2" PRIu64 " (%p)\n", name, i, the_array[i], (void *)&the_array[i]);
}
int main(void)
{
    const uint64_t primes[] = {2, 3, 5, 7, 11};
    print_array("primes", 5, primes);
}
 The function print_array()takes three arguments:  An array of chars, or in other words, a string, which provides the name of the array to print.The size of the array to print. This must be given, since the function can't tell the size of the array otherwise, so it won't know how many elements to print!An array of uint64_t, which is the actual array to print. (I'll explain what the [const]means in a moment.) The function prints the elements ofthe_array, as well as the memory address of each element. One possible output is: primes[0] =  2 (000000893a3ffd40)
primes[1] =  3 (000000893a3ffd48)
primes[2] =  5 (000000893a3ffd50)
primes[3] =  7 (000000893a3ffd58)
primes[4] = 11 (000000893a3ffd60)
 Notice that the values are stored in consecutive addresses, with 8 bytes (the size of an int64_t) between each address. Above we said that an array in C is equivalent to a pointer to the memory address of the first element. Therefore, when we pass an array to a function, we are actually passing a pointer. In fact, we can replace the arrays in the arguments of print_array()with pointers to the appropriate data types, and the program will run exactly the same: void print_array(const char *const name, const uint64_t size, const uint64_t *const the_array)
 By comparing this with the previous function definition we can see what [const]means in the declaration of the arrays as function arguments.type a[]is the same astype *a, that is, a non-constant pointer, whiletype a[const]is the same astype *const a, that is, a constant pointer. To illustrate, consider what happens if we add the following line to the function: name = "different name";
 Note that this does not modify the contents of the memory address pointed to by name! Instead, it modifiesnameitself to point to a different memory address. With the definition const char name[const](equivalent toconst char *const name), the pointernameis constant, and the code will not compile. However, if you change the definition toconst char name[](equivalent toconst char *name), the code will compile. Of course, whether you should define the pointer asconstor not depends on whether you intend to change it or not. So which syntax should you use for passing arrays to functions, array syntax type a[]or pointer syntaxtype *a? Personally I prefer the array syntax, since it gives more information to the reader. If you writetype *a, then the reader may thinkais just a pointer to a single variable. Writingtype a[]makes it clear thatais expected to be an array of multiple elements.  Warning: Because arrays are always passed as pointers, they are never copied. This means that when a function modifies an array, it doesn't modify a local copy, it modifies the original array. If you do not intend to modify the array at all, make sure to pass it as constto prevent accidental modification. If you do intend to modify the array, but you want to do so only locally within the function without affecting the original array, then you will have to copy the elements to a new array yourself; however, generally there is no reason to do this, and it can slow down your program, as copying large arrays takes time. | 
| For multi-dimensional arrays, the situation is a bit more complicated. If the dimensions of the array are known at compilation time, then we can pass the array directly: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_array_2D(const char name[const], const uint64_t the_array[const 3][2])
{
    for (uint64_t i = 0; i < 3; i++)
        for (uint64_t j = 0; j < 2; j++)
            printf("%s[%" PRIu64 "][%" PRIu64 "] = %" PRIu64 " (%p)\n", name, i, j, the_array[i][j], (void *)&the_array[i][j]);
}
int main(void)
{
    const uint64_t matrix[3][2] = {{1, 2},
                                   {3, 4},
                                   {5, 6}};
    print_array_2D("matrix", matrix);
}
 To pass the_arrayas a constant pointer, we addedconstinside the first bracket. If we intended to change the pointer for some reason, we would have passed it asthe_array[3][2]instead. One possible output is: matrix[0][0] = 1 (000000986d9ff930)
matrix[0][1] = 2 (000000986d9ff938)
matrix[1][0] = 3 (000000986d9ff940)
matrix[1][1] = 4 (000000986d9ff948)
matrix[2][0] = 5 (000000986d9ff950)
matrix[2][1] = 6 (000000986d9ff958)
 Notice, again, that the elements are stored consecutively in steps of 8 bytes. In fact, we see that this 2-dimensional array is just a 1-dimensional array in disguise! The element matrix[i][j]is actually the elementmatrix[2 * i + j]. This means that the compiler must know the number of columns in the array in order to use thematrix[i][j]notation; if the number of columns is unknown, then the compiler won't know how to convertmatrix[i][j]to the correct 1-dimensional array elementmatrix[2 * i + j]. Since the compiler only needs to know the number of columns, but not the number of rows, we can also define print_array_2Dwithout specifying the number of rows, as follows: void print_array_2D(const char name[const], const uint64_t the_array[const][2])
 However, if we don't specify any dimensions, the_array[][], or we only specify the number of rows,the_array[3][], then the program will not compile. This is not a desirable situation, since in general we want to have generic functions that can accept arrays of any size, as we had above for the 1-dimensional arrays. To write a generic function, we can use the following syntax: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_array_2D(const char name[const], const uint64_t rows, const uint64_t cols, const uint64_t the_array[const rows][cols])
{
    for (uint64_t i = 0; i < rows; i++)
        for (uint64_t j = 0; j < cols; j++)
            printf("%s[%" PRIu64 "][%" PRIu64 "] = %" PRIu64 " (%p)\n", name, i, j, the_array[i][j], (void *)&the_array[i][j]);
}
int main(void)
{
    const uint64_t matrix[3][2] = {{1, 2},
                                   {3, 4},
                                   {5, 6}};
    print_array_2D("matrix", 3, 2, matrix);
}
 Note that the arguments rowsandcolsmust appear before the array itself in the argument list, since we use their values to define the dimensions of the array. Another way to do this, which does not require passing rowsandcolsbefore the array itself, is to cast the array into a pointer to a 1-dimensional array, pass that pointer, and then usethe_array[cols * i + j]explicitly. This will have the same result: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
void print_array_2D(const char *const name, const uint64_t *const the_array, const uint64_t rows, const uint64_t cols)
{
    for (uint64_t i = 0; i < rows; i++)
        for (uint64_t j = 0; j < cols; j++)
            printf("%s[%" PRIu64 "][%" PRIu64 "] = %" PRIu64 " (%p)\n", name, i, j, the_array[cols * i + j], (void *)&the_array[cols * i + j]);
}
int main(void)
{
    const uint64_t matrix[3][2] = {{1, 2},
                                   {3, 4},
                                   {5, 6}};
    print_array_2D("matrix", (uint64_t *)matrix, 3, 2);
}
 (Here we also passed nameas a pointer, just for consistency.) | 
| The 2-dimensional array we used above had the shape of a matrix, with the same number of columns in each row. A jagged array is a 2-dimensional array in which each row can contain a different number of columns. More generally, a jagged n-dimensional array is an array of arrays (of arrays, etc...), with sub-arrays of different sizes. A 2-dimensional jagged array is thus as an array of arrays, which means an array of pointers. A commonly-used type of jagged array is an array of strings; since a string is just an array of chars, this is indeed an array of arrays. Here is an example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
void print_jagged_array(const char name[const], const char *const the_array[const], const uint64_t rows)
{
    for (uint64_t i = 0; i < rows; i++)
        printf("%s[%" PRIu64 "] = %-5s (%p -> %p, size %zu)\n", name, i, the_array[i], (void *)&the_array[i], (void *)the_array[i], strlen(the_array[i]));
}
int main(void)
{
    const char *const jagged[] = {"One", "Two", "Three", "Four"};
    print_jagged_array("jagged", jagged, 4);
}
 The output looks like this: jagged[0] = One   (0000004fb21ffd10 -> 00007ff6a85bb076, size 3)
jagged[1] = Two   (0000004fb21ffd18 -> 00007ff6a85bb07a, size 3)
jagged[2] = Three (0000004fb21ffd20 -> 00007ff6a85bb07e, size 5)
jagged[3] = Four  (0000004fb21ffd28 -> 00007ff6a85bb084, size 4)
 Each of the elements of jaggedis a pointer to a string. For example,jagged[0], the first element, is located at0000004fb21ffd10, and contains the pointer00007ff6a85bb076. At the memory address00007ff6a85bb076, we find the string"One", which has a size of 3. (We used the functionstrlen(), from the header filestring.h, to find the length of the string; you can find a list of all the functions available in this header file in the C reference.) Notice that the strings are also stored consecutively in memory, with the number of bytes used by each string being its size plus 1 (recall that in C, every string has a null character appended to it, which indicates where the string ends). For example, the string "One"uses the 4 bytes from00007ff6a85bb076to00007ff6a85bb079, while the string"Three"uses the 6 bytes from00007ff6a85bb07eto00007ff6a85bb083. We defined the function print_jagged_array()to take the array as aconst char *const the_array[const], which means "a constant pointer to an array ([const]) namedthe_arraywhich containsconstpointers (*) tochars which areconst". We could also define the argument as const char *const *const the_array, which means "a variable namedthe_arraywhich is aconstpointer (*) to aconstpointer (*) to acharwhich isconst". (Again, types in C are best understood by reading from right to left!) As with 1-dimensional arrays, the notation type *a[]is more informative thantype **a, since it tells usais supposed to be an array of pointers totype, and not just a pointer to a single pointer totype. Therefore, the notationtype *a[](with or withoutconst) should be preferred. Do we really need three separate consts in the function argument definition? Probably not... You will most likely never see anything likeconst char *const *constin "real life", but I used it here for pedagogical reasons, since it guarantees 100% that a beginner programmer cannot change any aspect of the input argument and potentially cause bugs. Experienced C programmers don't really need all this extra protection from human error, so you generally won't see threeconsts in one definition, but there's nothing wrong with taking precautions, except that it looks a bit cumbersome. However, since I initially defined jaggedasconst char *const jagged[], which has two separateconsts, if I just wrotechar **the_arrayor equivalentlychar *the_array[](without anyconsts), or evenconst char **the_arrayor equivalentlyconst char *the_array[], orchar *const *the_arrayor equivalentlychar *const the_array[](with just oneconst), I would have received a warning from the compiler, since the type ofthe_arraywould not have matched the type ofjagged. Remember:constis considered part of the type definition. The thirdconst, however, is optional; that's the one that protects the pointerthe_arrayitself, rather than defining the type of the array elements. | 
|  | 
| Above we said that when C allocates memory for a variable automatically, it does so in the stack. This can be done for any variable whose size is already known at compilation time - that is, we explicitly specified in the source code that we are, for example, allocating a 64-bit double, or an array of five 8-bitchars. However, often we do not know in advance how much memory we need to allocate. A common example is reading data from a file, which can be of any size. In this case, we must employ dynamic memory allocation, manually allocating the required amount of memory at run time. In this case, the memory will not be allocated from the stack, but rather from the heap. The stack is typically only a few MB in size. The heap is much larger - typically only limited by how much total memory the computer has. Therefore, for very large arrays, even if the size is already known at compile time, it is often best to allocate memory for them in the heap anyway - otherwise, we may run out of space in the stack, which results in a stack overflow error, and typically causes the program to crash. To use dynamic memory allocation, we must include the header file stdlib.h, which stands for "standard library". The relevant functions are:  malloc(total_size)allocatestotal_sizebytes and returns a pointer to the memory address where the allocated block begins.calloc(number, element_size)allocates memory for an array withnumberelements ofelement_sizebytes each, initializes all elements to zero, and returns a pointer to the memory address where the allocated block begins. Note thatcalloc(x, y)is equivalent tomalloc(x * y)in terms of how much space is allocated.realloc(pointer, new_size)resizes the memory block allocated atpointerbymalloc,calloc, orrealloctonew_size. Returns a new pointer, which may point to an address different from the old pointer, especially if the block size has increased and needed to be moved to a location with more free space. If the new address is different,reallocautomatically copies the contents of the memory block at the old address and frees it up.free(pointer)frees up (deallocates) the space allocated at thepointerbymalloc,calloc, orrealloc. | 
|  Warning: Improper use of dynamic memory allocation is a very common source of serious bugs and crashes, even for experienced C programmers!  To avoid bugs, you must make sure to use dynamic memory allocation properly, by following these guidelines:  Always check for allocation failure. If malloc,calloc, orreallocfail, they return a null pointer, which is given by the constantNULL. This should be checked whenever you use any of these functions, and if the result isNULL, the program should either do something else (e.g. try to allocate less memory), print out an error message, and/or quit.Always deallocate memory using freewhen you are done with it. Failure to do so will cause a memory leak, and the computer may run out of memory. If the pointer to the allocated memory was defined within a scope (such as a function), then it must be freed before the scope ends, since otherwise the variable containing the pointer will no longer be accessible.Never try to use memory before allocating it, or after freeing it. This will cause a segmentation fault and crash your program. The following program demonstrates proper use of dynamic memory allocation with mallocandfree: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    const uint64_t size = 1000;
    int64_t *array = malloc(size * sizeof(*array));
    if (array == NULL)
    {
        printf("Failed to allocate memory!\n");
        return -1;
    }
    printf("Successfully allocated memory for an array of %zu elements of %zu bytes each at address %p using malloc.\n", size, sizeof(*array), (void *)array);
    printf("First element (uninitialized): %" PRId64 ".\n", array[0]);
    free(array);
}
 Notice that if we fail to allocate the memory, we terminate the program by calling return -1. Recall thatreturnis used to return a certain value from a function. Butmain()is a special function, which contains the main code of the program, so callingreturninmain()terminates the program itself. The integer value that is returned can then (optionally) be processed by some other program. If no return value is specified, main()returns 0 by default, which means the program finished successfully. Any number other than 0 means there was an error, and here we are using the number -1 to indicate an error with memory allocation, although we could have used any other number. If we call this program as part of a script, then we can check the return value to figure out if the program ran successfully, or if not, what was the error. If memory is being allocated inside a function, and we wish to terminate the program, then of course we cannot call return, since that will just terminate that particular function and not the whole program. In such a case we should useexit(n)wherenis the number we want the program to return. For example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int64_t *allocate_memory(const uint64_t size)
{
    int64_t *array = malloc(size * sizeof(*array));
    if (array == NULL)
    {
        printf("Failed to allocate memory!\n");
        exit(-1);
    }
    printf("Successfully allocated memory for an array of %zu elements of %zu bytes each at address %p using malloc.\n", size, sizeof(*array), (void *)array);
    return array;
}
int main(void)
{
    int64_t *array = allocate_memory(1000);
    printf("First element (uninitialized): %" PRId64 ".\n", array[0]);
    free(array);
}
 In the following program, we replaced mallocwithcalloc, so now the array is initialized to zero, which is often the preferred behavior: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    const uint64_t size = 1000;
    int64_t *array = calloc(size, sizeof(*array));
    if (array == NULL)
    {
        printf("Failed to allocate memory!\n");
        return -1;
    }
    printf("Successfully allocated memory for an array of %zu elements of %zu bytes each at address %p using calloc.\n", size, sizeof(*array), (void *)array);
    printf("First element (initialized): %" PRId64 ".\n", array[0]);
    free(array);
}
 | 
| When using realloc, we need to be careful, because if it fails, the original pointer still remains valid and needs to be deallocated withfreeto prevent memory leaks. Therefore, we can't use the same variable to store both the old pointer and the new pointer. Here is how to usereallocproperly: #include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
    const uint64_t size1 = 1000, size2 = 100, size3 = 10000;
    int64_t *array1 = NULL, *array2 = NULL, *array3 = NULL;
    array1 = malloc(size1 * sizeof(*array1));
    if (array1 == NULL)
    {
        printf("Failed to allocate memory!\n");
        return -1;
    }
    printf("Successfully allocated memory for an array of %zu elements of %zu bytes each at address %p using malloc.\n", size1, sizeof(*array1), (void *)array1);
    array2 = realloc(array1, size2 * sizeof(*array2));
    if (array2 == NULL)
    {
        printf("Failed to reallocate memory!\n");
        free(array1);
        return -1;
    }
    printf("Successfully reallocated memory for an array of %zu elements of %zu bytes each at address %p using realloc.\n", size2, sizeof(*array2), (void *)array2);
    array3 = realloc(array2, size3 * sizeof(*array3));
    if (array3 == NULL)
    {
        printf("Failed to reallocate memory!\n");
        free(array2);
        return -1;
    }
    printf("Successfully reallocated memory for an array of %zu elements of %zu bytes each at address %p using realloc.\n", size3, sizeof(*array3), (void *)array3);
    free(array3);
}
 Here, since we don't use all of the pointers array1,array2, andarray3immediately, we made sure to initialize them toNULL, which stands for a null pointer, that is, a pointer that doesn't point to anything. If we did not initialize these pointers, they would have pointed to some random addresses in memory. As always, all variables must be properly initialized, and that includes pointers! On my computer, the output is: Successfully allocated memory for an array of 1000 elements of 8 bytes each at address 0000000000163f90 using malloc.
Successfully reallocated memory for an array of 100 elements of 8 bytes each at address 0000000000163f90 using realloc.
Successfully reallocated memory for an array of 10000 elements of 8 bytes each at address 0000000000620080 using realloc.
 Note how when we reallocated a smaller amount of memory, the same memory address was used, but to reallocate a larger amount of memory, the old location was not suitable anymore, so a new address was used instead. In any of the above programs, you can change the size of the array to a very large number (e.g. 1e15) to see what happens when the program fails to allocate or reallocate memory. | 
|  | 
| So far, we have always declared mainasint main(void), which means that it (and thus, the program as a whole) does not take any arguments. However, often - especially if your program does not have a graphical user interface - you want it to take arguments from the command line. In this case, you must declaremainas follows: int main(int argc, char *argv[])
  argc(argument count) is an integer which counts the number of arguments passed to the program, which are assumed to be separated by spaces.argv(argument vector) is an array ofargcpointers to strings which contain the actual arguments passed. Note that argcis usually at least 1, since it also counts the name of the executable file of the program itself, which is always stored inargv[0]. Here is an example: #include <stdio.h>
int main(int argc, char *argv[])
{
    printf("argc = %d\n", argc);
    for (int i = 0; i < argc; i++)
        printf("argv[%d] = %s\n", i, argv[i]);
}
 If you just run this program by pressing F5 in the IDE, you will get an output similar to this: argc = 1
argv[0] = main
 Alternatively, you can compile it and then write in the command line: main first second third "fourth fifth"
 Then the output will be: argc = 5
argv[0] = main
argv[1] = first
argv[2] = second
argv[3] = third
argv[4] = fourth fifth
 Note that "fourth fifth"counts as one argument, since the words are enclosed in quotes. | 
| Alternatively (or additionally), you can ask the user for input from the terminal after the program runs. This is done using the function scanf, which accepts a format string similar toprintfas its first arguments, and the addresses of the variables to store the input in the next arguments. As forprintf, each format placeholder corresponds to one consecutive argument. The output of scanfis anintcontaining the number of arguments received, or0in case the input does not match the format string. It is important to check the output, in case the user inputs the wrong value. In this case, the program can either terminate or ask again for the correct value. Try this example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    int64_t x = 0, y = 0;
    int r = 0;
    printf("Enter x: ");
    r = scanf("%" PRId64, &x);
    if (r == 0)
    {
        printf("Incompatible value entered!");
        return -1;
    }
    printf("Enter y: ");
    r = scanf("%" PRId64, &y);
    if (r == 0)
    {
        printf("Incompatible value entered!");
        return -1;
    }
    printf("The sum of the numbers is %" PRId64 " + %" PRId64 " = %" PRId64 ".\n", x, y, x + y);
}
 If you enter two integers, their sum is displayed. However, if you enter something else, such as a letters, the program will terminate.  Warning: In scientific programming, it is best not to take any input from the terminal during run time. Instead, take all input from files and/or command line arguments. If the user wants to run the program 50 times with 50 different sets of data, this can easily be automated using a script, but only if the program can run without requiring any live input from the user during run time.  | 
|  Warning: Before you run any code that opens files for input or output, review the steps above for configuring the launch.jsonfile. As indicated there, you must change your working folder to the workspace folder using"cwd": "${workspaceFolder}". To open a file, we use the function fopen, which is defined instdio.h. The first argument is the name of the file, and the second argument specifies the file access mode:  "r"opens the file for reading only."r+"opens the file for both reading and writing. If the file exists, reading begins from the start.If the file does not exist, an error occurs."w"opens the file for writing only."w+"opens the file for both reading and writing. If the file exists, its contents are destroyed.If the file does not exist, it is created."a"opens the file for appending."a+"opens the file for both reading and appending. If the file already exists, the output is appended to the existing contents.If the file does not exist, it is created.  Warning: If an existing file is opened with the access modes "w"or"w+", its contents will be permanently destroyed with no way to recover them! The output of fopenis a pointer to a variable of typeFILE, which is used whenever we want to access the file. Once we're done with the file, we must close it withfclose, which takes theFILEpointer as its argument. To read a character from a file, we usefgetc. To read a formatted string from a file, we usefscanf, which functions likescanfbut reads from a file rather than from the terminal. The following program prints out its own source file (assuming that the file is called print_self.c): #include <stdio.h>
int main(void)
{
    const char filename[] = "print_self.c";
    int chr = 0;
    FILE *file_ptr = NULL;
    file_ptr = fopen(filename, "r");
    if (file_ptr == NULL)
    {
        perror("Cannot open file");
        return -1;
    }
    while ((chr = fgetc(file_ptr)) != EOF)
    {
        putchar(chr);
    }
    if (ferror(file_ptr))
        printf("\nEncountered an error before reaching the end of file %s.", filename);
    else if (feof(file_ptr))
        printf("\nSuccessfully read file %s.", filename);
    fclose(file_ptr);
}
 After calling fopen, we store the generatedFILEpointer in the variablefile_ptr. This variable is used to access the file throughout the program. If file_ptr == NULL, this indicates that an error has occurred. We useperrorto inform the user of this error.perrorprints out the given string, followed by": ", followed by an automatically-generated description of the error code stored in the system variableerrno. For example, if you changefilename[]to a file that does not exist, you will see the error messageCannot open file: No such file or directory. We use a whileloop with the condition(chr = fgetc(file_ptr)) != EOFto read the file.fgetcreads one character fromfile_ptr, and we store the output in the variablechr. Notice thatchris anint, not achar; this is because it needs to be able to store not only characters, but also the special valueEOF(typically set to-1) which indicates that either an error has occurred or the end of the file has been reached. At each step of the loop, we compare the result (i.e. the value of chr) withEOF. As long asEOFhas not been reached, we print out the character that was read from the file using the functionputchar, which simply prints one character to the terminal. Once EOFhas been reached, we use the functionferror, which checksfile_ptrfor errors. If this function returnstrue, then we tell the user about the error. Otherwise, we use the functionfeofto confirm that we indeed reached the end of the file, and if it returnstrue, we notify the user of the successful operation. Finally, we close the file using fclose. This is very important, as it releases the file so that other programs can use it, frees up memory, and in the case of opening a file for writing, ensures that all the data we wrote to the file is saved on the disk. The function fscanfworks similarly toscanf, except that it takes a file pointer as its first argument. We can use it to read data from files in a particular format. For example, perhaps we would like to read integers stored in CSV (comma-separated values) format. Create a file nameddata.csvand enter the following into it: 1,2,3
4,5,6
7,8,9
 We can read this file with the following program: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const char filename[] = "data.csv";
    int num_read = 0;
    FILE *file_ptr = NULL;
    int64_t x = 0, y = 0, z = 0;
    uint64_t line = 1;
    file_ptr = fopen(filename, "r");
    if (file_ptr == NULL)
    {
        perror("Cannot open file");
        return -1;
    }
    do
    {
        num_read = fscanf(file_ptr, "%" PRId64 ",%" PRId64 ",%" PRId64, &x, &y, &z);
        if (num_read == 3)
            printf("Read values: (%" PRId64 ", %" PRId64 ", %" PRId64 ") on line %" PRIu64 ". Sum is %" PRId64 ".\n", x, y, z, line, x + y + z);
        else
        {
            printf("Error: Encountered invalid data on line %" PRIu64 "!\n", line);
            break;
        }
        line++;
    } while (!feof(file_ptr));
    if (feof(file_ptr))
        printf("Successfully read the file %s (%" PRIu64 " lines total).", filename, line - 1);
    fclose(file_ptr);
}
 To read the three integers we simply tell fscanfthat the format of the input is integer - comma - integer - comma - integer.fscanfwill return the total number of integers read, so if it's 3 we know the line was formatted correctly; if not, then either we've reached the end of the file, or there was invalid data. The output is: Read values: (1, 2, 3) on line 1. Sum is  6.
Read values: (4, 5, 6) on line 2. Sum is 15.
Read values: (7, 8, 9) on line 3. Sum is 24.
Successfully read the file data.csv (3 lines total).
 But if I change data.csvso that one of the lines contains invalid data, for example: 1,2,3
aaa,5,6
7,8,9
 Then the output will be: Read values: (1, 2, 3) on line 1. Sum is 6.
Error: Encountered invalid data on line 2!
 By the way, notice that in this program I defined num_read, the variable that stores the return value offscanf, asint. You may be wondering: doesn't that make the program not portable? Why did I not use, for example,int32_tinstead? The reason is that the C standard explicitly defines fscanfas a function that returnsint. So I cannot definenum_readas an integer with a specific number of bits, such asint32_t, since the number of bits stored in that variable may be different on different systems, depending on howintis defined on each system. This means that, perhaps counter-intuitively, the only portable way to usefscanfis to define its return variable asint. | 
| If we open a file for writing, we can use fputcto write one character orfprintfto write a formatted string. The syntax offprintfis exactly the same as that ofprintf, except that the first argument is aFILEpointer. Here is an example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
int main(void)
{
    const char filename[] = "squares.txt";
    const uint64_t num_squares = 100;
    FILE *file_ptr = NULL;
    file_ptr = fopen(filename, "w");
    if (file_ptr == NULL)
    {
        perror("Cannot open file");
        return -1;
    }
    for (uint64_t i = 1; i <= num_squares; i++)
    {
        if (fprintf(file_ptr, "%" PRIu64 "\n", i * i) < 0)
        {
            perror("Error occurred");
            fclose(file_ptr);
            return -1;
        }
    }
    printf("Squares of all numbers from 1 to %" PRIu64 " written successfully to file %s.\n", num_squares, filename);
    fclose(file_ptr);
}
 There is much more to be said about input, output, and other file operations, but we will not cover it here. Instead, please see the C reference. | 
|  | 
| As we have seen, arrays can be used to group together different values. However, all values in an array must have the same type. Structures, declared with the keyword struct, allow us to conveniently store any desired combination of different data types in one variable. The syntax is as follows: struct name
{
    type1 member1;
    type2 member2;
    // etc...
};
 Here nameis the name of the structure, andmember1,member2, and so on are the members, which have data typestype1,type2, etc. respectively. One example of where we might want to use a structis for database entries. For example, we can store a student's ID, name, and email address in onestructdata type as follows: struct student
{
    uint64_t id;
    char *name;
    char *email;
};
 Here, we have defined a new data type called struct student. Note that since we do not know in advance the size of the strings that will contain the name and email address, we don't define them as a fixed-size array (e.g.char name[20]) but rather as simply a pointer to a string, which will be stored elsewhere in memory. (This is not necessarily the fastest method, since the program will then have to navigate to the other memory addresses to access the strings, but it is the most convenient.) Once we define a struct, we have to declare an actual variable which will have the new data type. This is done as usual with the syntaxtype var_name, so in this case,struct struct_name var_name. The members are then accessed using a dot:var_name.member1,var_name.member2, and so on. In the example of a student entry,struct student sdeclaressas a variable of typestruct student. The fieldsid,name, andemailand then accessed usings.id,s.name, ands.email. To initialize a struct, we use the following syntax: struct name var_name = {.member1 = value1, .member2 = value2, ...};
 Note that structshould usually be defined outside themainfunction, so that it is a global definition that can be accessed by other functions. For example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
struct student
{
    uint64_t id;
    char *name;
    char *email;
};
int main(void)
{
    const struct student s = {.id = 654873, .name = "Alice", .email = "alice@universityname.ca"};
    printf("| Student ID: %" PRIu64 " | Name: %s | Email: %s |\n", s.id, s.name, s.email);
}
 | 
| Often, we will want to declare an array of structs. For example, for an array of 3 students, we can usestruct student s[3]. Thens[i]is theith element of the array (starting from zero, as usual). To access the individual values, we uses[i].id,s[i].name, ands[i].email. Here is an example: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
struct student
{
    uint64_t id;
    char *name;
    char *email;
};
int main(void)
{
    const uint64_t num_students = 3;
    struct student s[num_students];
    s[0].id = 654873;
    s[0].name = "Alice";
    s[0].email = "alice@universityname.ca";
    s[1].id = 23564;
    s[1].name = "Bob";
    s[1].email = "bob@universityname.ca";
    s[2].id = 7634126;
    s[2].name = "Charlie";
    s[2].email = "charlie@universityname.ca";
    for (uint64_t i = 0; i < num_students; i++)
        printf("| Student ID: %7" PRIu64 " | Name: %-7s | Email: %-25s |\n", s[i].id, s[i].name, s[i].email);
}
 Note that we used width specifiers in the %sformat placeholders to make the output look nice. The minus sign, just as for numbers, specifies that the string should align to the left. The output will be: | Student ID:  654873 | Name: Alice   | Email: alice@universityname.ca   |
| Student ID:   23564 | Name: Bob     | Email: bob@universityname.ca     |
| Student ID: 7634126 | Name: Charlie | Email: charlie@universityname.ca |
 | 
| We can streamline the previous program by defining a function that will store values inside a struct: #include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
struct student
{
    uint64_t id;
    char *name;
    char *email;
};
void store_student(struct student *const s, const uint64_t id, char *const name, char *const email)
{
    s->id = id;
    s->name = name;
    s->email = email;
}
int main(void)
{
    const uint64_t num_students = 3;
    struct student s[num_students];
    store_student(&s[0], 654873, "Alice", "alice@universityname.ca");
    store_student(&s[1], 23564, "Bob", "bob@universityname.ca");
    store_student(&s[2], 7634126, "Charlie", "charlie@universityname.ca");
    for (uint64_t i = 0; i < num_students; i++)
        printf("| Student ID: %7" PRIu64 " | Name: %-7s | Email: %-25s |\n", s[i].id, s[i].name, s[i].email);
}
 Since we want the function to modify the contents of the array, we pass the addresses of the array elements using the &modifier. This means that, inside the functionstore_student, the variablesis a pointer. In this case, we use the syntax->instead of.to access the values inside thestruct. Note thats->idis equivalent to(*s).id. | 
| A typedefis used to define a shorthand for a particular type. It has the syntax: typedef original_type new_name;
 We have encountered typedefs before. For example,uint64_tis actually atypedefforunsigned long long: typedef unsigned long long uint64_t;
 typedefs are often used to define shorter names forstructs. For example, we can write
 typedef struct
{
    uint64_t id;
    char *name;
    char *email;
} student;
 This will allow us to write just studentinstead ofstruct studentwhen we declare a variable of this type. | 
| Unfortunately, in this course we only have limited time. I chose to talk in detail here only about the most important things you need to know in order to start programming in C. For more information, a good starting point is the C reference, which lists all the keywords, types, functions, and other important aspects of C and its standard library. Stack Overflow is another very good website with trustworthy content in the form of questions and answers, where you can also ask your own questions if needed - but before posting a new question, make sure it has not been asked already! I find that most online C tutorials are not of very good quality, and sometimes even just plain wrong. For this reason, I recommend not to use them, or if you do, to take them with a grain of salt. For learning C seriously, there is no replacement for a proper textbook. I will not recommend any particular C textbooks here, since I don't think you'll need them - the intention of this course is for you to eventually program in C++, which is a much more useful and modern language than C. I will recommend some great C++ textbooks below. | 
| Now that we have a fairly good mastery of C, we can start learning C++. C is, for the most part, a subset of C++. Most C programs will compile using a C++ compiler, perhaps with a few changes, but the opposite is not true. We will see that many aspects of C remain the same in C++, but some things are done a bit differently, and many new features and concepts are added - most importantly, object-oriented programming. C++ can do everything C can do, and more. The main benefit of C compared to C++ is that it is lower-level, supported on more types of systems, and simpler. Therefore, it is great for embedded systems, operating systems, device drivers, and so on. However, for most applications, including scientific computing, it is generally better to use C++. You will soon see why. Note that both C and C++ are evolving languages. Some of the things I will list here as new in C++ compared to C are also new in C++ compared to older revisions of C++. However, I will always assume here that you are using a compiler which supports the latest revision, C++23, which is what you should have if you installed the latest version of Clang as I instructed in the beginning of the course. You should create a new Visual Studio Code workspace for your C++ programs, separate from the workspace you used so far for your C programs, since it will have slightly different .jsonconfiguration files. The steps to configure Visual Studio Code to run C++ programs and generate the.jsonfiles are exactly the same as in creating and configuring your first program above, except for two changes:  First, whenever the compiler clangis mentioned in the instructions above, replace it withclang++.Second, replace the argument -std=c23intasks.jsonwith-std=c++23. This will instruct the compiler to comply with the C++23 standard. The warning flags should be the same. Note that the -D__USE_MINGW_ANSI_STDIO=1flag is not required in C++, so you do not need to add it. | 
|  | 
| Copy and paste the following program to VS Code: #include <iostream>
int main()
{
    std::cout << "Hello, World!\n";
}
 Let us consider several new things in this program:  #include <iostream>is basically the C++ equivalent of#include <stdio.h>. However, in C++ the relevant header file isiostream, and we don't add.hto the file name; the actual file's name is, in fact, justiostreamwithout any extension.iostreamstands for input/output stream. To include the a C header file of the form NNN.h, use#include <cNNN>. For example, to includemath.h, we use#include <cmath>.int main()is the same oldmainfunction from C. However, while the C standard requires thatvoidappears in the parentheses if the program does not take command-line arguments, the C++ standard instead requires that nothing appears in the parentheses. Both options,voidor novoid, work in both languages, but here we will adhere to the standard. To get input from the command line, the exact same syntax as in C should be used:int main(int argc, char *argv[]).std::cout, which is declared iniostream, allows us to print output to the terminal. Althoughprintfstill works in C++,std::coutis preferred whenever we don't need the output to be formatted in a particular way. stdindicates that we are accessing the standard library namespace. A namespace is simply a space where we can declare names so that they do not conflict with names declared in other namespaces. For example, I can (although this is not a very good idea...) define another object namedcoutin another namespace, and it will not conflict withcoutin thestdnamespace.::indicates that we are accessing an object in the namespace.coutis an object in the namespacestd, which represents the standard output stream. Usually, the standard output stream is the terminal. The namecoutstands for character output. Note thatcoutis not a function, it is an object; it doesn't have any input or output, likeprintfdoes.The operator <<("put to") indicates that the string"Hello, World!\n"should be written into the standard output streamstd::cout. Since the stream represents the terminal, writing this string to the stream means that"Hello, World!\n"will be printed to the terminal. | 
| The object coutis in thestdnamespace, so we had to writestd::in front of it in order to access it. This can get tedious when an object is used frequently. However, we can eliminate the need to writestd::by invoking the following statement: using namespace std;
 This will instruct the compiler that we are "using" the namespace std, which essentially means we merge that namespace with the namespace of our program. Now we can simply writecout: #include <iostream>
using namespace std;
int main()
{
    cout << "Hello, World!\n";
}
 We will generally invoke using namespace stdthroughout the course, since it makes C++ code more compact and readable. The only downside is that we cannot define objects in our program which have the same names as objects in the C++ standard library (such ascout), but that would be a bad idea anyway. | 
| The 2011 version of the C++ standard, also called C++11, defined the following 83 reserved keywords: alignas,alignof,and,and_eq,asm,auto,bitand,bitor,bool,break,case,catch,char,char16_t,char32_t,class,compl,const,constexpr,const_cast,continue,decltype,default,delete,do,double,dynamic_cast,else,enum,explicit,export,extern,false,float,for,friend,goto,if,inline,int,long,mutable,namespace,new,noexcept,not,not_eq,nullptr,operator,or,or_eq,private,protected,public,register,reinterpret_cast,return,short,signed,sizeof,static,static_assert,static_cast,struct,switch,template,this,thread_local,throw,true,try,typedef,typeid,typename,union,unsigned,using,virtual,void,volatile,wchar_t,while,xor, andxor_eq. These include 33 of the 34 C keywords (restrictis the only one missing), plus 50 new keywords. However, many of the new keywords in fact already existed in C. For example, the operatorsand,or, andnotare just more readable alternatives for the operators&&,||, and!respectively. Similarly,bool,true, andfalsecan be used in C if we include the header filestdbool.h. The next two versions, C++14 and C++17, changed the meaning of some keywords but did not add any new ones. However, C++20 added 8 new keywords: char8_t,concept,consteval,constinit,co_await,co_return,co_yield, andrequires. | 
| In C++, we can define different functions with the same name, which accept different numbers and/or types of arguments. This is called function overloading. Here is an example: #include <iostream>
using namespace std;
void print_type(const int32_t x)
{
    cout << "I got a 32-bit integer: " << x << '\n';
}
void print_type(const int64_t x)
{
    cout << "I got a 64-bit integer: " << x << '\n';
}
void print_type(const double x)
{
    cout << "I got a double: " << x << '\n';
}
void print_type(const char x)
{
    cout << "I got a character: " << x << '\n';
}
int main()
{
    print_type(1);             // Prints "I got a 32-bit integer: 1"
    print_type(1000000000000); // Prints "I got a 64-bit integer: 1000000000000"
    print_type(1.0);           // Prints "I got a double: 1"
    print_type('1');           // Prints "I got a character: 1"
}
 By default, C++ interprets any literal integer (i.e. an integer written explicitly in the source code) as the smallest data type among int,long, andlong longthat can fit that integer.1fits intoint, which on most 64-bit platforms meansint32_t. However,1000000000000is too big to be represented using 32 bits, and thus it only fits into along long, which on most 64-bit platforms meansint64_t. You can remind yourself about integer data types here and read more about how integer literals are interpreted in C++ here. Therefore, when we invoke print_type(1), the functionprint_type(int32_t)is called, but when we invokeprint_type(1000000000000), the functionprint_type(int64_t)is called. Similarly,print_type(1.0)callsprint_type(double)andprint_type('1')callsprint_type(char x). Notice also that we used multiple <<'s to print out multiple strings and numbers by putting them intocoutin order. This is (arguably) more convenient than usingprintfand having to specify the exact type of each value we are printing using the appropriate format placeholder. Withcout, the type is automatically detected. Function overloading is especially useful in the case of mathematical functions. Recall that in C, unless we used tgmath.h, we had different function names for different types of arguments, e.g.sqrtfordouble,sqrtfforfloat, andsqrtlforlong double. In C++, there is just onesqrtfunction (defined in the header<cmath>) that can accept any floating-point or integer type. | 
| In C, we learned that the constkeyword allows us to define a variable that cannot be changed later; if we try to change it, we will get an error from the compiler. In C++, an additional keywordconstexpr(constant expression) is provided. While constcan be used even if the value of the constant is known only at run time (i.e. when the user actually runs the program),constexprcan only be used if the value is known at compile time (i.e. when we compile the code). Generally,constexpris preferred toconstsince it provides better performance. For example, consider the following code: (In C++, you do not need to include stdint.hmanually to use fixed-width integer types, it is automatically included when you includeiostream.) #include <iostream>
using namespace std;
int64_t add_one(const int64_t n)
{
    return n + 1;
}
int main()
{
    const int64_t x = add_one(1);
    cout << x;
}
 The value returned by add_one()is not known at run time, but that's okay, because when we definexasconst, all that really means is that we prevent it from being changed later; the initial value can be decided at any time. However, if we replaceconstwithconstexpr, the code will not compile, since the value ofxis not known until we evaluate the functionadd_one()at run time. If we also add the keyword constexprto the definition ofadd_one(), the code will work: #include <iostream>
using namespace std;
constexpr int64_t add_one(const int64_t n)
{
    return n + 1;
}
int main()
{
    constexpr int64_t x = add_one(1);
    cout << x;
}
 In this case, the function add_one()will be evaluated in advance at compilation time and the value ofxwill be recorded, so that we do not need to evaluate the function again at run time. Obviously, this wouldn't work if the function depended on something that will only be known at runtime, such as input from a file; in that case the program would not compile.  Warning: A good programming practice is to never use literals, such as 299792458for the speed of light in meters per second, anywhere in the program, aside from trivial ones such as0and1. Instead, defineconstexpr uint32_t speed_of_light = 299792458and usespeed_of_lightthroughout the program instead of the literal299792458. If you later decide to make a change - for example, use different units of measurement - then you don't have to replace the literal number299792458throughout the code, you can simply change the constant expressionspeed_of_light. | 
| Just as in C++ we usually prefer to use coutinstead ofprintfto print output to the terminal, we also usually prefercintoscanfto get input from the terminal. Here is an example: #include <iostream>
using namespace std;
int main()
{
    int64_t n = 0;
    cout << "Enter a number: ";
    cin >> n;
    cout << "The square of the number is " << n * n << ".\n";
}
 Here, >>is the operator "get from", the opposite of<<, the operator "put to". Instead of putting output intocout, we get input fromcin.  Warning: As we explained above, in scientific programming it is best not to take any input from the terminal during run time. Programs should only take input from files and/or from command line arguments, so that they can be automated without requiring live input from the user during run time.  | 
| Just as the stdnamespace is defined in<iostream>, we can define our own namespaces. This is used whenever we want to guarantee that none of the names we used in our code (variables, functions, etc.) will clash with the names used in other peoples' code, and vice versa. Here is an example: #include <iostream>
namespace other
{
    constexpr int64_t cout = 7;
}
int main()
{
    // Prints "Value of other::cout is 7."
    std::cout << "Value of other::cout is " << other::cout << ".\n";
}
 There are two couts in this code:  The usual std::cout, the standard output stream, defined in the namespacestd.Our custom other::cout, which is not a stream but an integer, defined in the custom namespaceother. You can see that both couts can be used in conjunction, even in the same statement, as long as the prefix indicating the namespace (std::orother::) is included. Of course, if we write justcoutwithout an explicit namespace for either of them, the code will not compile - since that name is only defined within the context of these namespaces. So far, we have added the statement using namespace stdto every program, which imports all of the names in the namespacestd. However, it is actually possible to only import individual names withusing. For example, let us import onlycinbut notcoutor anything else: #include <iostream>
using std::cin;
int main()
{
    int64_t n = 0;
    std::cout << "Enter n: ";
    cin >> n;
    std::cout << "The square of n is " << n * n << ".\n";
}
 You can see that cinworks without thestd::prefix here, but if you try to replacestd::coutwithcout, you will get an error. | 
| We can specify a default value for a function's argument, which will be used if that argument is omitted. For example: #include <cmath>
#include <iostream>
using namespace std;
double root(const double num, const int32_t rt = 2)
{
    return pow(num, 1.0 / rt);
}
int main()
{
    cout << "Square root of 5: " << root(5) << '\n';
    cout << "Cubic  root of 5: " << root(5, 3) << '\n';
}
 Here, the function root()takes the root of the first argument with respect to the second argument, but if the second argument is omitted, the default is a square root. (We used the functionpow()from the header<cmath>to calculate the roots;pow(base, exp)returnsbaseto the powerexp.) Note that arguments with default values cannot be followed by arguments without default values. In other words, the arguments with default values must be the last arguments of the function. So f(double x, double y = 0)is okay, and so isf(double x = 0, double y = 0), butf(double x = 0, double y)is invalid. | 
|  | 
| Consider a variable declared as type x, wheretypeis any data type andxis the variable's name. We have seen that this variable is simply a value of the corresponding data type, stored in a particular address in memory. This address can be determined by&x. We can define a pointer pthat will store the address of a variable usingtype *p = &x. When we dereference the pointer using the syntax*p, we can read or modify the value ofx- that is, the value stored at the address&x- without usingxitself. Unfortunately, pointers are one of the most confusing and prone-to-errors concepts in C. To remedy that, C++ introduces the new concept of references. A reference is similar to a constpointer, in that it points to an address in memory, but once it is initialized to the address of a particular variable, this address can never be changed. Furthermore, as you will see, unlike pointers, references do not need to be dereferenced - whenever the reference is used, it is automatically replaced with the variable it refers to. This essentially means that a reference is just another name, or alias, for an already existing variable. We never have to deal with the actual memory address directly. To define a reference, we use the following syntax: type &ref = var;
 Where refis the name of the reference,varis the name of the variable it will point to, andtypeis the data type. This is illustrated in the following example: #include <iostream>
using namespace std;
int main()
{
    int64_t var = 0;     // Declare an integer var and initialize it to 0
    int64_t &ref = var;  // Declare a reference ref and initialize it to be an alias of var
    int64_t *ptr = &var; // Declare a pointer ptr and initialize it to the address of var
    cout << "Initialization: "
         << "var = " << var << ", ptr = " << ptr << ", *ptr = " << *ptr << ", ref = " << ref << ".\n";
    var++;
    cout << "var++:          "
         << "var = " << var << ", ptr = " << ptr << ", *ptr = " << *ptr << ", ref = " << ref << ".\n";
    ref++;
    cout << "ref++:          "
         << "var = " << var << ", ptr = " << ptr << ", *ptr = " << *ptr << ", ref = " << ref << ".\n";
    (*ptr)++;
    cout << "(*ptr)++:       "
         << "var = " << var << ", ptr = " << ptr << ", *ptr = " << *ptr << ", ref = " << ref << ".\n";
    ptr++; // Warning: This increases the address ptr points to, NOT the value at the address!
    cout << "ptr++:          "
         << "var = " << var << ", ptr = " << ptr << ", *ptr = " << *ptr << ", ref = " << ref << ".\n";
}
 On my computer, the output is: Initialization: var = 0, ptr = 0x1f91fff648, *ptr = 0, ref = 0.
var++:          var = 1, ptr = 0x1f91fff648, *ptr = 1, ref = 1.
ref++:          var = 2, ptr = 0x1f91fff648, *ptr = 2, ref = 2.
(*ptr)++:       var = 3, ptr = 0x1f91fff648, *ptr = 3, ref = 3.
ptr++:          var = 3, ptr = 0x1f91fff650, *ptr = 135593457232, ref = 3.
 We see that adding 1tovarusingvar++is the same as adding1torefusingref++; they are essentially two names for the same variable. Dereferencingptrand adding1to the value at the address it points to using(*ptr)++is also equivalent. You can think of a reference as essentially a fixed pointer for which the*is automatically added whenever the reference is used. However, if we do not dereference ptr, and instead add1to the value ofptritself usingptr++, we change the address the pointer points to, and the value at the new address is garbage - whatever happens to be in memory at that address at the time. (On your computer, the address thatptrpoints to will be different, and the garbage value in the last line will also be different.) One way to avoid making mistakes like this is to declare the pointer as const, as we discussed above and as we have indeed done whenever possible in our code examples so far: int64_t *const ptr = &var;
 Now the code will not compile, since we are trying to change ptritself (which isconst) rather thanvar(which is notconst). References in C++ have many uses, and in particular, they simplify some uses that would have required pointers in C. We will see some of these uses below. | 
| Above, in functions and pointers, we considered the following program (slightly rewritten here for C++): #include <iostream>
using namespace std;
void swap(int64_t *const x, int64_t *const y)
{
    const int64_t temp = *x;
    *x = *y;
    *y = temp;
}
int main()
{
    int64_t a = 1, b = 2;
    cout << "Before swap: a = " << a << ", b = " << b << '\n';
    swap(&a, &b);
    cout << "After swap: a = " << a << ", b = " << b << '\n';
}
 By passing pointers to the variables to be swapped instead of the values of the variables, we can modify the variables from within the function, even though they are in a different scope. In C++, this code can be rewritten in a much cleaner way, without all the *s, using references instead of pointers: #include <iostream>
using namespace std;
void swap(int64_t &x, int64_t &y)
{
    const int64_t temp = x;
    x = y;
    y = temp;
}
int main()
{
    int64_t a = 1, b = 2;
    cout << "Before swap: a = " << a << ", b = " << b << '\n';
    swap(a, b);
    cout << "After swap: a = " << a << ", b = " << b << '\n';
}
 Notice that all we needed to do was add &in the argument list of the function's definition; every instance ofa,b,x, andyis simply the variable's name without any*s, resulting in more compact and easy to read code. Furthermore, when callingswap()we simply passaandbthemselves, rather than their addresses&aand&b, as we had to do in the case of pointers. Since references cannot be changed to refer to a different variable, we do not need to make them const; in other words, a reference works similarly to aconstpointer. | 
| As we discussed above, functions have their own scope, so arguments passed to a function are treated as new variables within that new scope, independent of variables in any other scope. This means that when a variable is passed to a function, its value has to be copied to a new address in memory, so that if it is modified, it will not affect the original variable. In the example of the swapfunction above, we explicitly wanted to modify the variables passed as arguments, instead of creating new variables in the function's scope. In order to do that, we passed the arguments by reference, which ensured that the function could directly access the passed variables instead of their copies. However, even if we don't need the function to be able to modify the variables that were passed as arguments, it is still often preferable to pass arguments by reference, since this can provide us with a performance boost. The reason is that copying the value of the variable to a different address in memory can be a time-consuming task - especially for large objects such as structs (orclasses, which we will discuss later). On the other hand, if we are not careful, passing an argument by reference may cause unexpected behavior. Consider this example: #include <iostream>
using namespace std;
void print_plus_one(int64_t x)
{
    x++;
    cout << "print_plus_one: " << x << '\n';
}
int main()
{
    int64_t a = 1;
    print_plus_one(a);             // Prints "print_plus_one: 2"
    cout << "main: " << a << '\n'; // Prints "main: 1"
}
 The function print_plus_oneaccepts an integer, increases it by one, and prints out the result. Notice that the variableain the scope of themainfunction was not modified byprint_plus_one, since it created a new variablexwithin its own scope. The value ofawas copied to a new address in memory for this purpose. Perhaps we would like to improve the performance of our program by passing the argument as reference. (Obviously, in this case it would make no difference, but if we're passing 1 GB of data to the function, the performance improvement would be very significant!) Naively, we would just add &to the argument of the function: #include <iostream>
using namespace std;
void print_plus_one(int64_t &x)
{
    x++;
    cout << "print_plus_one: " << x << '\n';
}
int main()
{
    int64_t a = 1;
    print_plus_one(a);             // Prints "print_plus_one: 2"
    cout << "main: " << a << '\n'; // Prints "main: 2"
}
 Notice that now the variable ain the scope of themainfunction has also changed, sincexin the scope ofprint_plus_oneis not an independent variable, it is merely an alias foraand refers to the same address in memory. This was not the intended behavior; we just wantedprint_plus_oneto print the value plus one, but not change the variable that was passed to it. This error could have been prevented if we declared the argument as a constreference. If you definevoid print_plus_one(const int64_t &x)and try to run the following program, it will not compile, and we will get the errorincrement of read-only reference 'x'from the compiler. The correct way to write this code using references is: #include <iostream>
using namespace std;
void print_plus_one(const int64_t &x)
{
    cout << "print_plus_one: " << x + 1 << '\n';
}
int main()
{
    int64_t a = 1;
    print_plus_one(a);             // Prints "print_plus_one: 2"
    cout << "main: " << a << '\n'; // Prints "main: 1"
}
 Now we enjoy increased performance (at least in principle, if the program was more complicated) by using a reference to xinstead of copying it, but we also do not modifyxbefore we print it, which would inadvertently modifyaas well. Finally, notice that if the argument of print_plus_oneis defined as a non-constantint64_t &x, then the function cannot take literals as arguments - that is, we cannot call, for example,print_plus_one(5). The reason is that5is a literal, not a variable, so there is no way to create an alias for it. However, if the argument ofprint_plus_oneis defined asconst int64_t &x, then we are free to call the function with either a variable or a literal. In conclusion, a good programming practice is to follow these rules:  Functions should only accept variables as arguments if they must make copies of the variables, and there is no simple way to use references instead.Otherwise, functions should always accept references. If the function is meant to change the value of the variable, as in swap, then the references should not beconst.Otherwise, the references should always be constto prevent accidental modification, allow passing literals as arguments, and increase readability. | 
| forloops are almost always used to iterate on a specific range or specific values. C++ introduces a new type offorloop which greatly simplifies this. Simply write
 for (type i : a)
 Where ais the array of values you want to iterate on, andtypeis the type of the values. You can also write explicitly for (type i : {value1, value2, ...})
 Where value1,value2, etc. are the values to be iterated on. Here is an example: #include <iostream>
using namespace std;
int main()
{
    constexpr uint64_t primes[] = {2, 3, 5, 7, 11};
    for (const uint64_t p : primes)
        cout << p << '\n';
}
 This will simply print out the elements of the array primes. It is important to understand that herepis not a variable that gets incremented in each step, as with a normalforloop; in each step,pwill be the actual value of each array element. Here we declared pasconstsince we do not changepitself inside the loop.pis a local variable inside the loop, hence a local copy, so we can change it if we want, without affecting the actual array elements; as with all other variables, if we do not plan to change it, then we should define it asconstboth to avoid accidental modification and to make the code more readable. In some cases, we do want to change p, while not changing the array elements themselves. This is okay, sincepis just a local copy of each element, so we can change it without changing the array; we just need to remove theconstqualifier. For example: #include <iostream>
using namespace std;
int main()
{
    constexpr uint64_t primes[] = {2, 3, 5, 7, 11};
    for (uint64_t p : primes)
    {
        p++;
        cout << p << '\n';
    }
    for (const uint64_t p : primes)
        cout << p << '\n';
}
 The first loop will increase the value of each prime and then print it. However, this will not affect the array primesitself, sincepis a copy. If the array we're looping on contains a lot of data, then we should avoid copying each element, since that can take a significant amount of time. As with passing arguments to functions, we can avoid copying the elements of the array by using references: #include <iostream>
using namespace std;
int main()
{
    constexpr uint64_t primes[] = {2, 3, 5, 7, 11};
    for (const uint64_t &p : primes)
        cout << p << '\n';
}
 Now pis not a copy, but rather a reference to each array element. This may considerably improve performance in many cases. However, now we should be careful; we can remove the constqualifier to allow modifyingp, but doing so will now also modify the array (as long as the array itself is notconstorconstexpr, of course). For example, in the following code we permanently increase each element of the array by1: #include <iostream>
using namespace std;
int main()
{
    uint64_t primes[] = {2, 3, 5, 7, 11};
    for (uint64_t &p : primes)
    {
        p++;
        cout << p << '\n';
    }
    for (const uint64_t &p : primes)
        cout << p << '\n';
}
 The first loop iterates over the references &p, which are used as aliases for the array elements. So in the first iteration&pwill be an alias forprimes[0], in the second iteration it will be an alias forprimes[1], and so on. When we writep++, this increases the array elements themselves, as if we were writingprimes[0]++and so on. The second loop iterates over the elements again and prints them - you will notice that their values have indeed increased by 1, which did not happen in the previous example, where we increased only the copies. In other words, the loop for (uint64_t &p : primes)
    p++;
 Is equivalent to for (uint64_t i = 0; i < 5; i++)
    primes[i]++;
 Again, as with passing references to functions, if we don't intend to change the array itself then we should define the references as const. Here it's even more important than in the first example, as we may be accidentally changing the array itself and not just a copy. The same rules we discussed in the case of passing arguments to functions as references also apply here:  You should only loop over copies if you must make copies of the array elements, and there is no simple way to use references instead.Otherwise, you should only loop over references. If the loop is meant to change the values of the array elements, then the references should not be const.Otherwise, the references should always be constto prevent accidental modification and to increase readability. | 
| In C, we saw that the null pointer (e.g. as returned by fopen) was given byNULLand had the value0. In C++, to prevent confusion between the integer0and the null pointer, the latter is instead given bynullptr, which in fact is a pointer and not an integer. Note that there is no "null reference". As an (artificial) example of the difference between NULLandnullptr, consider the following code: #include <iostream>
using namespace std;
void print_type(const uint64_t x)
{
    cout << "I got a 64-bit integer: " << x << '\n';
}
void print_type(const void *const x)
{
    cout << "I got a pointer: " << x << '\n';
}
int main()
{
    print_type(NULL);
}
 This code won't compile, and will produce the error call of overloaded 'analyze(NULL)' is ambiguous. This is because the compiler doesn't know ifNULLis meant to be an integer or a pointer. However, if we replaceNULLwithnullptr, the code will compile and will outputI got a pointer: 0. As in C, the null pointer is useful when we declare a pointer variable, but only assign a value to it later. In this case, the pointer will have a garbage value until we assign its value, which may lead to bugs. To avoid this, we can simply initialize it to the null pointer, for example: double *p = nullptr;
 | 
|  | 
| C++ introduces vectors, which are a much improved version of C arrays. Vectors are a type of container; we will discuss other C++ containers later. The main benefit of C++ vectors is that they manage memory automatically. You can create an array of any size, and later add or remove elements to it, and it will automatically allocate and deallocate memory as needed. Memory is also freed up automatically when the vector is no longer being used. This means you never have to worry about managing memory manually, which is prone to bugs and leaks. Furthermore, vectors can be accessed in a safe way which prevents accessing elements out of the range of the array. You pay for this convenience with slightly reduced performance. The penalty to performance is most significant in situations where the vector needs to resize itself, which may mean having to reallocate memory and copy everything to the new memory address. However, if you declare the vector in advance with a size big enough to store everything you plan to put in it, you can avoid the need to resize it later. Merely accessing the elements of a vector is essentially just as fast as accessing the elements of an array. To use vectors, we must #include <vector>. Then, we define vectors using the new typestd::vector, or justvectorif we areusing namespace std. The syntax is: vector<type> name(size);
 This will create a vector with sizeelements of typetype. Ifsizeis not specified, i.e. the parentheses are omitted, it will be zero by default. The elements will be automatically initialized, so we don't need to worry about initialization. Here is an example: #include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int64_t> vec(5);
    for (int64_t &i : vec)
        cout << i << '\n';
}
 This will output 5 zeros, since the vector has been automatically initialized to zeros. We can also initialize it manually with the usual array syntax, e.g.: vector<uint64_t> primes = {2, 3, 5, 7, 11};
 In this case we did not need to specify the size of the vector; it was automatically inferred from the initialization list. The standard array notation []works for vectors too, starting from zero as usual, so for example,primes[0]is2andprimes[4]is11. vectorcontains many useful member functions. These are functions that are accessed using the syntaxvec.function(arguments)wherevecis the name of the vector,functionis the name of the functions, andargumentsare optional arguments to be passed to the function.
 Here are some examples of commonly-used member functions:  size()returns the number of elements in the vector.push_back(value)adds an element with specified value to the end of the vector.assign({value1, value2, ...})re-initializes the vector with the specified elements.pop_back()removes the last element in the vector.clear()re-initializes the vector to size zero. These functions are demonstrated in the following program: #include <iostream>
#include <vector>
using namespace std;
void print_vector(const vector<uint64_t> &v)
{
    cout << "Size: " << v.size() << ", Elements: ";
    for (const uint64_t &i : v)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint64_t> vec;
    print_vector(vec); // Size: 0, Elements:
    vec.push_back(5);
    print_vector(vec); // Size: 1, Elements: 5
    vec.push_back(7);
    print_vector(vec); // Size: 2, Elements: 5 7
    vec.assign({1, 2, 3});
    print_vector(vec); // Size: 3, Elements: 1 2 3
    vec.pop_back();
    print_vector(vec); // Size: 2, Elements: 1 2
    vec.clear();
    print_vector(vec); // Size: 0, Elements:
}
 Notice that we passed the vector to print_vectorby (constant) reference in order to avoid copying the whole vector every time we call the function. For this reason, we also had to define the referenceiinside theforloop asconst uint64_t &i- otherwise, if it was not a constant reference, we could have modified the elements of the vector by modifyingi, which is forbidden since we passed the vector as a constant reference. (If you writefor (uint64_t &i : v)instead, you will get an error from the compiler.) Another very important member function is at(n), which returns the element at positionn, starting from0as usual.vec.at(n)is equivalent tovec[n]as long asnis between0andvec.size() - 1. However, ifnis out of range,vec[n]will simply return whatever garbage value is in the memory address being referred to, just as was the case for C arrays. That's not good! On the other hand, vec.at(n)will cause an error ifnis out of range. (More precisely, it will throw an exception; we will learn what that means later.) Naturally, this means that there is a bit of overhead toatcompared to[], sinceatmust spend some extra time checking if the requested element is in range or not. Therefore,[]may be preferred if you need the maximum performance possible, but only if you are absolutely sure that the element you are requesting will never be out of range. Here is an example of using []vs.at: #include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int64_t> vec = {1, 2, 3};
    // Accessing out-of-range element permitted: will print garbage
    cout << vec[5] << '\n';
    // Accessing out-of-range element prohibited: will terminate the program
    cout << vec.at(5) << '\n';
}
 For a full list of the available member functions for vector, please see the C++ reference or Microsoft's C++ reference. | 
| In C, strings were null-terminated arrays with elements of type char. C++ introduces a new way to define strings. To use vectors, we must#include <string>. Then, we define vectors using the new typestd::string, or juststringif we areusing namespace std. For example: #include <iostream>
#include <string>
using namespace std;
int main()
{
    string message = "This is a string.";
    cout << message << '\n';
}
 You can concatenate strings by using the + operator: #include <iostream>
#include <string>
using namespace std;
int main()
{
    string message1 = "This is ";
    string message2 = "a string.";
    string message = message1 + message2;
    cout << message << '\n'; // Prints "This is a string."
}
 Note that this was not possible in C, since you can't "add" arrays. (Instead, you would have had to #include <string.h>and use the functionstrcat.) However, it is still possible to use the array operator[]to access individual characters in the string, starting from0as usual: #include <iostream>
#include <string>
using namespace std;
int main()
{
    string message = "ABCDE";
    cout << message[0] << '\n'; // Prints "A"
    message[1] = 'b';
    cout << message << '\n'; // Prints "AbCDE"
}
 You can also compare two strings using the ==and!=operators: #include <iostream>
#include <string>
using namespace std;
int main()
{
    string password = "1234";
    string input;
    cout << "Enter password: ";
    cin >> input;
    if (input == password)
        cout << "Password is correct.\n";
    else
        cout << "Password is incorrect.\n";
}
 Again, in C this was not possible (you would have had to use the function strcmp). Notice that we did not initializeinput; one of the great benefits of usingstringis that strings are automatically initialized to an empty string if no initialization string is provided. stringhas many useful member functions, including:
  size()returns the length of the string.insert(position, string)insertsstringatposition.replace(position, length, string)replaceslengthcharacters atpositionwithstring.find(string)looks for an instance ofstringand returns its position. Returnsnposif the string was not found.substr(position, length)extracts a substring oflengthcharacters starting atposition. The following program illustrates these functions: #include <iostream>
#include <string>
using namespace std;
int main()
{
    string message = "I like apples.\n";
    cout << message; // I like apples.
    // Length of string: 15 characters.
    cout << "Length of string: " << message.size() << " characters.\n";
    message.insert(7, "red ");
    cout << message; // I like red apples.
    message.replace(7, 3, "green");
    cout << message; // I like green apples.
    // "apple" found at position 13.
    cout << "\"apple\" found at position " << message.find("apple") << ".\n";
    // 4 characters starting at position 2: "like".
    cout << "4 characters starting at position 2: \"" << message.substr(2, 4) << "\".\n";
}
 For a full list of the available member functions for string, please see the C++ reference or Microsoft's C++ reference. | 
|  | 
|  | 
| Classes are the most important and significant new feature offered by C++. They provide an additional level of abstraction beyond the abstractions we have discussed so far such as variables, functions, and structures. Essentially, a classis a user-defined type. Just like we can create, for example, a variable of typeint, we can also create a variable - or, more generally, an object - of a user-defined type defined using aclass. A classcan have two types of members: data, in the form of variables, and code, in the form of functions. The concept of an object with internal variables is already familiar to us from our discussion ofstructs in C above, and the concept of member functions is familiar to us from our discussion ofvectors andstrings - which are, in fact, classes defined in the C++ standard library (more precisely,vectoris a template, but I will explain what that means later). Classes have two separate parts: interface and implementation. The interface is the part that users can access directly. The implementation is only accessible internally, to the classitself, and not to the user. As an analogy, in a calculator, the interface is the buttons (input) and the display (output), while the implementation is the code that performs the actual calculations. We define a classusing the following syntax: class name
{
public:
// Public members
private:
// Private members
};
 The publicmembers correspond to the interface (they are accessible to the user) while theprivatemembers correspond to the implementation (they are inaccessible to the user). Note that members areprivateby default, but one should always include the labelspublic:andprivate:explicitly to enhance readability and prevent confusion. Most people who read the code for your classwould only be interested in knowing the syntax for thepublicfunctions that they can access via the interface, and not theprivatefunctions and data that are part of the internal implementation of theclass. Therefore,publicmembers should always appear first. | 
| A structin C++ is essentially equivalent to aclass. The only difference is that in astruct, members arepublicby default, while in aclass, members areprivateby default. We access members of astructor aclass, both data and functions, using a dot as in Cstructs - a notation that we are already familiar with from the classesstringandvector. Consider astructdefining a point: #include <iostream>
using namespace std;
struct point
{
    double x, y;
};
int main()
{
    point p;
    // Error: uninitialized, prints two arbitrary numbers
    cout << '(' << p.x << ", " << p.y << ")\n";
}
 The declaration point pcreates a new object of the structurepoint. We access the members of this object usingp.xandp.y, as usual. Note that we did not need to writestruct pointor use atypedef, as in C; we only need to writepointto declare an object. Of course, the problem here is that the user did not initialize the point, so the members have garbage values. To avoid such errors, we should add some default values to the definition of the struct: #include <iostream>
using namespace std;
struct point
{
    double x = 0, y = 0;
};
int main()
{
    point p;
    cout << '(' << p.x << ", " << p.y << ")\n"; // Prints (0, 0)
}
 If the user does want to initialize this point, they can do so using array syntax: point p = {1, 2};
cout << '(' << p.x << ", " << p.y << ")\n"; // Prints (1, 2)
 This syntax works because pointis a passive data structure (sometimes referred to as "plain old data" or POD), that is, it only holds specific data types in a specific order and does nothing else (just like a struct in C). For more complicated objects, we will need to use constructors. | 
| Let us now start using classinstead ofstruct, even though they are essentially the same thing;classis what you should normally use in C++, because it is preferable to hide members from the users by making themprivate. We rewrite the program as follows: #include <iostream>
using namespace std;
class point
{
public:
    double x = 0, y = 0;
};
int main()
{
    point p;
    cout << '(' << p.x << ", " << p.y << ")\n"; // Prints (0, 0)
}
 It would be nice if pointcould automatically print its coordinates to the terminal, so we won't have to write a clunkycoutstatement every time. This will also allow us to easily change the format of all outputs at once by changing howpointimplements the output. So let's add a public member functionprint, which prints the point as a tuple: #include <iostream>
using namespace std;
class point
{
public:
    void print()
    {
        cout << '(' << x << ", " << y << ")\n";
    }
    double x = 0, y = 0;
};
int main()
{
    point p1 = {1, 2};
    point p2 = {3, 4};
    p1.print(); // Prints (1, 2)
    p2.print(); // Prints (3, 4)
}
 Notice that we now declared two different points, namedp1andp2, and used the member functionprintto easily print each of the points. Also, notice that insidepointitself, the members are simply referred to by their names.xis justxwhen we're insidepoint, and it will always refer to the particularxfor the object for which the code ofprintis being executed. Finally, let us also add a member function to scale both coordinates of the point by the same factor: #include <iostream>
using namespace std;
class point
{
public:
    void print()
    {
        cout << '(' << x << ", " << y << ")\n";
    }
    void scale(const double &s)
    {
        x *= s;
        y *= s;
    }
    double x = 0, y = 0;
};
int main()
{
    point p = {1, 2};
    p.scale(3);
    p.print(); // Prints (3, 6)
}
 We could similarly add member functions to rotate the point around the origin, calculate its distance from the origin, and so on. | 
| Constructors are special member functions used to properly initialize a newly-created object of the given class. The constructors generate the object based on some input, and they are free to do whatever processing and error-checking is needed in order to properly create an object from the input. Constructors are defined as follows:  They are member functions with the same name as the class.They must be public, since the user needs to access them as part of the class interface in order to initialize objects.They have no return value.Their arguments are the input used to initialize the object. Often, there are multiple overloaded versions of constructors that take different kinds of inputs. In the case of point, let us define three different constructors: #include <iostream>
using namespace std;
class point
{
public:
    // Default constructor: coordinates are initialized to their default values as specified below
    point() {}
    // Constructor with one argument: assumes the argument is the value of both x and y
    point(const double &_xy) : x(_xy), y(_xy) {}
    // Constructor with two arguments
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    // Prints the coordinates of the point as a tuple
    void print()
    {
        cout << '(' << x << ", " << y << ")\n";
    }
    // Scales both coordinates by the specified amount
    void scale(const double &s)
    {
        x *= s;
        y *= s;
    }
    // The values of the coordinates
    double x = 0, y = 0;
};
int main()
{
    point p1;       // Uses default constructor
    p1.print();     // (0, 0)
    point p2(5);    // Uses constructor with one argument
    p2.print();     // (5, 5)
    point p3(6, 7); // Uses constructor with two arguments
    p3.print();     // (6, 7)
}
 Note that the constructors in this case are all empty code blocks {}; the actual assignment of values happens through the initializer list, which is of the formmember(argument), ..., assigning the arguments of the function to specific member variables. The constructor point(const double &_x, const double &_y) : x(_x), y(_y) {}
 Does the same thing as point(const double &_x, const double &_y)
{
    x = _x;
    y = _y;
}
 However, it is more compact. More importantly, the values are assigned before the body of the function is executed. Here this doesn't really matter, since the function body is empty anyway, but in other cases, it would ensure that the members have been properly initialized, so that the function body cannot possibly make use of uninitialized variables, which would - as usual - cause unexpected behavior. | 
| Consider a function invert()which returns the multiplicative inverse of a number: #include <iostream>
using namespace std;
double invert(const double &x)
{
    return 1.0 / x;
}
int main()
{
    cout << invert(10); // Prints "0.1"
}
 What happens if we call invert(0)? Nothing too catastrophic, actually; it simply returnsinf, that is, the special floating-point value representing infinity. So we could use the functionisinf(), defined in the header<cmath>, to check if the result is infinity, and issue an error message in that case: #include <cmath>
#include <iostream>
using namespace std;
double invert(const double &x)
{
    return 1.0 / x;
}
int main()
{
    const double result = invert(0);
    if (isinf(result))
        cout << "Error: Division by zero!";
    else
        cout << result;
}
 This works, but if we wanted to invert several different numbers, we would have to check for this error every single time. Things become even more complicated if, for example, we have a function that calls another function that calls another function that calls invert()... In that case, we would have to check for division by zero all over again in each function, and then propagate that error all the way back tomain(). This would quickly make the code very cumbersome. C++ includes a much smarter and more convenient way to handle errors in the form of exceptions. We enclose a block of code that may generate an error in brackets following the keyword try. The code may then generate an exception using the keywordthrow. If an exception is thrown, execution of thetryblock is terminated, and control transfers to acatchblock. If there is nocatchblock, or if we don't catch the specific exception that was thrown, the program itself terminates. Here's how to implement the division by zero check using exceptions: #include <iostream>
#include <stdexcept>
using namespace std;
double invert(const double &x)
{
    if (x == 0)
        throw invalid_argument("Division by zero!");
    return 1.0 / x;
}
int main()
{
    try
    {
        cout << invert(10) << '\n';
        cout << invert(0) << '\n';
        cout << invert(20) << '\n';
    }
    catch (const invalid_argument &e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 The output of this program is: 0.1
Error: Division by zero!
 In main(), wetryto print out the inverse of some numbers. The first line callsinvert(10), which returns0.1, and prints the result. The second line callsinvert(0). Looking at theinvert()function itself, we see that ifxis zero, the function uses thethrowkeyword to throw an exception. Once the exception is thrown, the execution of thetryblock terminates, so the third line, which callsinvert(20), never gets executed. In this case, we chose to throw the exception invalid_argument, which is a ready-made exception class defined in the header file<stdexcept>, intended to be used when an invalid argument (in this case 0) is passed to a function. Note that<stdexcept>is included automatically when we include<iostream>, so we don't need to include separately, but we included it anyway for readability purposes. When we writeinvalid_argument("Division by zero!"), we are constructing aninvalid_argumentobject and giving the constructor the string"Division by zero!"which is the error message to be (optionally) passed to the user. For a full list of pre-defined exception classes, please see the C++ reference. We can also make our own custom exceptions by deriving them from the exceptionclass, which is indeed howinvalid_argumentand other ready-made exceptions are defined; we will explain what "deriving" means later. In principle, we could throwany user-defined class, or even a fundamental data type such asint. However, that is not recommended, since the user will expect tocatchanexceptionobject likeinvalid_argument. If we throw other types of objects, the user will need to consult the documentation to know which ones to catch. Furthermore, usingexceptionobjects makes the code more readable since the reader can immediately know what each exception means. In the catchstatement, we caught the exception as a constant reference. Although in principle exceptions can be caught by value, that's not recommended, as it would mean having to make a local copy of the object; you will get a warning from the compiler if you try to do that. As with any other object, it is best to deal with a reference to the object and not a copy of the object, as that is faster and takes up less space in memory. The invalid_argumentclass, as well as any other class derived from theexceptionclass, only has one member function:what(), which simply returns the string passed to the constructor. Therefore, when we calle.what(), we get the string"Division by zero!", which we then print out as a user-friendly description of the error. | 
| Let us now define a classnamedtriangle, which describes a triangle with sides of lengthsa,b, andc. Unlike the coordinates of apoint, which can be any two real numbers, the sides of atrianglemust have non-negative length and must satisfy the triangle inequality. Since we can't just trust the user to give us valid input, our constructor must validate the input by checking that the numbers can define a triangle. Furthermore, the values of the sides must be private, so that once the sides have been validated, the user won't be able to change them to invalid values. To facilitate error checking, if invalid input is given, the constructor will throw an invalid_argumentexception. We also provide aprint()member function to print out the value of the sides. Here is how it works: #include <iostream>
#include <stdexcept>
using namespace std;
class triangle
{
public:
    triangle(const double &_a, const double &_b, const double &_c)
        : a(_a), b(_b), c(_c)
    {
        if ((a < 0) or (b < 0) or (c < 0))
            throw invalid_argument("Sides cannot be negative!");
        if ((a > b + c) or (b > c + a) or (c > a + b))
            throw invalid_argument("Triangle inequality must be satisfied!");
    }
    void print()
    {
        cout << '(' << a << ", " << b << ", " << c << ")\n";
    }
private:
    double a = 0, b = 0, c = 0;
};
int main()
{
    try
    {
        triangle t1(4, 2, 5);
        t1.print();
        triangle t2(6, -7, 8);
        t2.print();
        triangle t3(2, 2, 5);
        t3.print();
    }
    catch (const invalid_argument &e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 The first triangle t1gets initialized successfully. However, the second trianglet2has a negative side length, so it does not get initialized, and instead we get an exception. Execution of thetryblock will then terminate. If you remove or fixt2, thent3will also generate an exception, since it does not satisfy the triangle inequality. We defined a publicconstructor, but the side lengthsa,b, andcareprivateso they are inaccessible to the user. Indeed, if you try to write, for example, t1.a = 1;
 you will get errors saying that ais private and inaccessible. This is very important, since it ensures that the implementation oftrianglecan always assume it is dealing with a valid triangle, with non-negative side lengths which satisfy the triangle inequality. The condition for what constitutes valid data for a classis called an invariant. This condition is assumed to be satisfied always - it does not vary, which is why it's called "invariant". A well-writtenclassmust ensure that the invariant is always satisfied by:  Writing proper constructors which enforce the invariant,Making all data privateand thus inaccessible to the user,Ensuring that any member function which changes the data does so in a way which preserves the invariant. If this is done correctly - and it must, if you intend to write a good C++ program - it greatly simplifies the implementation, improves performance, and eliminates bugs. The reason is that verifying the invariant every time a member function is called could significantly decrease performance, while not verifying it would lead to bugs whenever the data is invalid. If your code guarantees that the invariant is always satisfied, you don't have to verify it every time a member function is called. Therefore, either we don't allow the user to change the sides at all once the triangleobject has been initialized, or we provide a member functionchange_sides()which will only change the sides if the input is valid. In that case, the constructor can simply call that same member function, so we don't have to write the validation code twice. Here is how it works: #include <iostream>
#include <stdexcept>
using namespace std;
class triangle
{
public:
    triangle(const double &_a, const double &_b, const double &_c)
    {
        change_sides(_a, _b, _c);
    }
    void change_sides(const double &_a, const double &_b, const double &_c)
    {
        if ((_a < 0) or (_b < 0) or (_c < 0))
            throw invalid_argument("Sides cannot be negative!");
        if ((_a > _b + _c) or (_b > _c + _a) or (_c > _a + _b))
            throw invalid_argument("Triangle inequality must be satisfied!");
        a = _a;
        b = _b;
        c = _c;
    }
    void print()
    {
        cout << '(' << a << ", " << b << ", " << c << ")\n";
    }
private:
    double a = 0, b = 0, c = 0;
};
int main()
{
    triangle t(4, 2, 5);
    t.print();
    try
    {
        t.change_sides(4, 3, 5);
        t.change_sides(1, 3, 5);
    }
    catch (const invalid_argument &e)
    {
        cout << "Error: " << e.what() << '\n';
    }
    t.print();
}
 The output is: (4, 2, 5)
Error: Triangle inequality must be satisfied!
(4, 3, 5)
 Therefore, t.change_sides(4, 3, 5)succeeded, and the sides were changed, butt.change_sides(1, 3, 5)failed, so thetryblock terminated and the sides were not allowed to change. When we print out the sides after thetry-catchblocks, we see that indeed, the sides were changed to(4, 3, 5)but not to(1, 3, 5). We have defined two classes so far: pointandtriangle. These classes illustrate encapsulation, one of the fundamental principles of object-oriented programming. This principle has two components:  Data should be bundled together with the functions that process and manipulate that data. In other words, an object - that is, an instance of a class- should be a self-sufficient package. For example, apointobject not only contains the data on the coordinates of the point, but also the member functionsprintandscalewhich operate with that data.Data should not be directly accessible, instead only accessible via member functions that ensure its validity. In other words, there should be a "filter" that does not let any invalid data be stored in the object. For example, a triangleobject ensures that it always holds data for a valid triangle, which would be impossible if the user was able to manually modify each side of the triangle instead of going through the constructor. | 
| There's no such thing as an "uninitialized" triangleobject. In its current form, atrianglecannot be declared without initializing all three sides explicitly. If we try to declare triangle t;
 we will get an error saying no default constructor exists for class "triangle". The compiler doesn't know how to construct this object, since the only constructor we have expects three arguments, while here we have zero arguments. Trying to initialize it astriangle t{};will also fail, this time with the errorno instance of constructor "triangle::triangle" matches the argument list. If we want to allow initializing an "empty" or degenerate triangle object, with all its sides set to the default value of zero, we must create a default constructor (i.e. a constructor which does not take any arguments) manually, as we did for the pointclass.  Warning: If you try to initialize the triangle with empty parentheses, triangle t();, this will seem to work, but in fact will not call any default constructor, or even create any object. The code will compile, but you will receive the warningempty parentheses were disambiguated as a function declaration. What this means is that, in the absence of an appropriate constructor, the compiler interpretedtriangle t();as a function declaration: it's a function namedtwhich takes no arguments (hence the empty parentheses) and returns atriangleobject as output! This is called the "most vexing parse". Let us, then, define a default constructor for the triangleclass; and while we're at it, let us also define constructors for each possible number of arguments. The class will now have four constructors:  The constructor with no arguments will create a degenerate triangle with all sides equal to zero.The constructor with one argument will create an equilateral triangle with all sides of the same length.The constructor with two arguments will create a right triangle with the third side given by the Pythagorean theorem (c2 = a2 + b2), using the function sqrt()from the header<cmath>.The constructor with three arguments will be the one we defined previously. All constructors will use change_sides()to validate the input. The final result is: #include <cmath>
#include <iostream>
#include <stdexcept>
using namespace std;
class triangle
{
public:
    // Constructor with no arguments: define a degenerate triangle with all sides equal to zero.
    triangle()
    {
        change_sides(0, 0, 0);
    }
    // Constructor with one argument: define an equilateral triangle with all sides equal to a.
    triangle(const double &_a)
    {
        change_sides(_a, _a, _a);
    }
    // Constructor with two arguments: define a right triangle with sides a and b. The third side will be determined by the Pythagorean theorem.
    triangle(const double &_a, const double &_b)
    {
        change_sides(_a, _b, sqrt(_a * _a + _b * _b));
    }
    // Constructor with three arguments: define an arbitrary triangle with sides a, b, c.
    triangle(const double &_a, const double &_b, const double &_c)
    {
        change_sides(_a, _b, _c);
    }
    // Change (or initialize) the sides of the triangle, after making sure the values are valid.
    void change_sides(const double &_a, const double &_b, const double &_c)
    {
        if ((_a < 0) or (_b < 0) or (_c < 0))
            throw invalid_argument("Sides cannot be negative!");
        if ((_a > _b + _c) or (_b > _c + _a) or (_c > _a + _b))
            throw invalid_argument("Triangle inequality must be satisfied!");
        a = _a;
        b = _b;
        c = _c;
    }
    // Print the sides of the triangle.
    void print()
    {
        cout << '(' << a << ", " << b << ", " << c << ")\n";
    }
private:
    // The lengths of the sides.
    double a = 0, b = 0, c = 0;
};
int main()
{
    try
    {
        triangle degenerate_triangle;
        degenerate_triangle.print();
        triangle equilateral_triangle(1);
        equilateral_triangle.print();
        triangle right_triangle(3, 4);
        right_triangle.print();
        triangle arbitrary_triangle(5, 6, 7);
        arbitrary_triangle.print();
    }
    catch (const invalid_argument &e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 We also added comments to explain how to class works, since it has become quite large. The output is: (0, 0, 0)
(1, 1, 1)
(3, 4, 5)
(5, 6, 7)
 | 
| To make the code (arguably) more readable, we can move the code for the member functions outside of the class, and only include their prototype declarations inside it. When we define a member outside of theclass, we must addtriangle::before the name of the member, so that the compiler knows whichclassit belongs to: #include <cmath>
#include <iostream>
#include <stdexcept>
using namespace std;
class triangle
{
public:
    // Constructor with no arguments: define a degenerate triangle with all its sides equal to zero.
    triangle();
    // Constructor with one argument: define an equilateral triangle with all sides equal to a.
    triangle(const double &_a);
    // Constructor with two arguments: define a right triangle with sides a and b, and the third side given by the Pythagorean theorem.
    triangle(const double &_a, const double &_b);
    // Constructor with three arguments: define an arbitrary triangle with sides a, b, c.
    triangle(const double &_a, const double &_b, const double &_c);
    // Change (or initialize) the sides of the triangle, after making sure the values are valid.
    void change_sides(const double &_a, const double &_b, const double &_c);
    // Print the sides of the triangle.
    void print();
private:
    // The lengths of the sides.
    double a = 0, b = 0, c = 0;
};
triangle::triangle()
{
    change_sides(0, 0, 0);
}
triangle::triangle(const double &_a)
{
    change_sides(_a, _a, _a);
}
triangle::triangle(const double &_a, const double &_b)
{
    change_sides(_a, _b, sqrt(_a * _a + _b * _b));
}
triangle::triangle(const double &_a, const double &_b, const double &_c)
{
    change_sides(_a, _b, _c);
}
void triangle::change_sides(const double &_a, const double &_b, const double &_c)
{
    if ((_a < 0) or (_b < 0) or (_c < 0))
        throw invalid_argument("Sides cannot be negative!");
    if ((_a > _b + _c) or (_b > _c + _a) or (_c > _a + _b))
        throw invalid_argument("Triangle inequality must be satisfied!");
    a = _a;
    b = _b;
    c = _c;
}
void triangle::print()
{
    cout << '(' << a << ", " << b << ", " << c << ")\n";
}
 Now, anyone who reads our code will immediately see that there are four constructors and one member function, and the comments will explain what they do. This is the external interface of the class, the part that the user is going to make use of. The details of the internal implementation of the class, that is, the code itself, are kept separate, as they are generally not of any interest to the user. | 
| Note that it is not just a matter of taste and/or readability whether to include the member function definitions inside or outside the class. Functions that are included inside aclassare automatically interpreted as inline functions, which means that the compiler will attempt to generate a copy of the function "inline" in the machine code, rather than a call to a function located elsewhere. Inlining can provide a performance boost, as calling a function takes time. On the other hand, it means that the code itself will be larger and occupy more memory, and that might actually hurt performance. Generally, functions should only be inlined if they consist of no more than one or two lines, but this is not a rule that applies to every situation. If we want to move a function outside of the class, but still have it be aninlinefunction, we can add the keywordinline. This is added only to the definition of the function, i.e. the part that contains the code, and not to the prototype declaration of the function inside the class itself, which is only meant to convey information about the interface. Whether the function isinlineor not is part of the implementation, not the interface, so it is not of interest to the user. For example: class triangle
{
public:
    triangle();
    // ...
}
inline triangle::triangle()
{
    change_sides(0, 0, 0);
}
 However, note that the inlinekeyword is only a suggestion; most compilers willinlineshort functions anyway as part of the optimization process. We will talk more aboutinlinefunctions and performance optimization later in the course. | 
| The code for the triangleclass has become quite long. It may be easier to handle if we split it into separate files. The standard way to do this in C++ is to split the class into two files: a header file (.hppextension) containing the prototype declarations, and a source file (.cppextension) containing the function definitions. We also split the class from the main program. Thus we split the project into three files:  main.cppwill contain themain()function and possibly some other functions that are unrelated to thetriangleclass.triangle.cppwill contain thetriangleclass and the definitions of all its member functions.triangle.hppwill contain the prototype declarations of thetriangleclass and all its member functions and variables, but not any actual code. What happens then isn't simply that the code for both files is compiled together. Instead, main.cppandtriangle.cppwill be compiled separately, and then linked together; we will discuss this in more detail later.main.cppmust know how the functions intriangle.cppwere declared in order to use them, e.g. what kind of arguments they get and what type of values they return. This is achieved by includingtriangle.hpp, since it contains the required information. You may ask why the compiler can't just look inside triangle.cppand figure out how the functions work on its own. Theoretically that may be possible, but the idea is that in C++, different.cppfiles must be able to compile independently of each other. So they compiler doesn't know or care abouttriangle.cppwhen you compilemain.cpp. In fact, sometimes you may not even have access to the source code fortriangle, only the pre-compiled machine code. As long as you have the appropriate header file, that will not be a problem. Here are the contents of the three files. main.cpp:
 #include "triangle.hpp"
int main()
{
    triangle t(1, 2, 3);
    t.print();
}
 Note that in this simple example we didn't have to include the lines #include <iostream>,#include <cmath>, orusing namespace stdinmain.cppsince they are never needed there; we only use functions from the C++ standard library within thetriangleclass. However, we did need to write#include "triangle.hpp"so that we have access to the class itself. triangle.hpp:
 class triangle
{
public:
    // Constructor with no arguments: define a degenerate triangle with all its sides equal to zero.
    triangle();
    // Constructor with one argument: define an equilateral triangle with all sides equal to a.
    triangle(const double &_a);
    // Constructor with two arguments: define a right triangle with sides a and b, and the third side given by the Pythagorean theorem.
    triangle(const double &_a, const double &_b);
    // Constructor with three arguments: define an arbitrary triangle with sides a, b, c.
    triangle(const double &_a, const double &_b, const double &_c);
    // Change (or initialize) the sides of the triangle, after making sure the values are valid.
    void change_sides(const double &_a, const double &_b, const double &_c);
    // Print the sides of the triangle.
    void print();
private:
    // The lengths of the sides.
    double a = 0, b = 0, c = 0;
};
 This header file contains only the interface of the class, with no actual code. It will be used, separately, by both main.cppandtriangle.cpp, and neither of them will compile without it. Therefore, both.cppfiles must contain the line#include "triangle.hpp". Furthermore, this is the file that the user will read in order to understand how the class works, so it should contain comments to explain the role of each member of the class. triangle.cpp:
 #include <cmath>
#include <iostream>
#include <stdexcept>
#include "triangle.hpp"
using namespace std;
triangle::triangle()
{
    change_sides(0, 0, 0);
}
triangle::triangle(const double &_a)
{
    change_sides(_a, _a, _a);
}
triangle::triangle(const double &_a, const double &_b)
{
    change_sides(_a, _b, sqrt(_a * _a + _b * _b));
}
triangle::triangle(const double &_a, const double &_b, const double &_c)
{
    change_sides(_a, _b, _c);
}
void triangle::change_sides(const double &_a, const double &_b, const double &_c)
{
    if ((_a < 0) or (_b < 0) or (_c < 0))
        throw invalid_argument("Sides cannot be negative!");
    if ((_a > _b + _c) or (_b > _c + _a) or (_c > _a + _b))
        throw invalid_argument("Triangle inequality must be satisfied!");
    a = _a;
    b = _b;
    c = _c;
}
void triangle::print()
{
    cout << '(' << a << ", " << b << ", " << c << ")\n";
}
 This file contains the actual implementation of triangle. Before we can compile the project with these three separate files, we will need to make some small modifications to the configuration files of our Visual Studio Code project (located in the .vscodesubdirectory). First, open the filetasks.json. Currently, theargsfield should look something like this: "args": [
    "${file}",
    "-o",
    "${fileDirname}\\${fileBasenameNoExtension}.exe",
    "-Wall",
    "-Wextra",
    "-Wconversion",
    "-Wsign-conversion",
    "-Wshadow",
    "-Wpedantic",
    "-std=c++23",
    "-ggdb3"
],
 (If you don't have these arguments in your JSON file, you should add them now - I explained their purpose in the beginning of the course and the meaning of -std=c++23in the beginning of this chapter.) Notice the highlighted string "${file}". Currently, VS Code is configured to only compile the active file;${file}is a placeholder that will get replaced by that file's name. If we try to compile the program now, only the active file will be compiled, eithermain.cpportriangle.cpp, but not both. Ifmain.cppis the active file, then it won't know about the classtriangleand the compilation will fail. Iftriangle.cppis the active file, then the compilation will also fail because there is nomainfunction in that file. We can fix this in one of two ways: either we write the names of the files explicitly, i.e. replace ${file}withmain.cpp triangle.cpp, or simply replace${file}with${workspaceFolder}\\*.cpp, which will automatically compile all of the files in the workspace folder.*is a wildcard which will match any file name, so*.cppwill match any file with the extension.cpp. The latter is usually the preferred option, since it means that we do not have to modify tasks.jsonevery time we add a new source file or change its name. Of course, this means that all of the files in the workspace folder must belong to the same project, since they will be compiled and linked together, and conversely, all files to be linked must be placed in the same folder. The second highlighted string determines the name of the executable file. Again, the default is to name the file after whatever source file is currently open in the editor. That is not a good idea, since if we are editing main.cppthe executable file will bemain.exeand if we're editingtriangle.cppthe executable file will betriangle.exe, even though in both cases the same executable file will be generated. To solve this problem, let us replace the string "${fileDirname}\\${fileBasenameNoExtension}.exe"with"${workspaceFolder}\\CSE701.exe". Of course, you can choose any other name for the executable file, and you can put it in any other folder you want; in fact, for big projects it makes sense to put the source, header, and executable files all in different folders, but there's no reason to do that for our simple project. Note: On Linux, the executable file should not have the extension .exe, and the path should have a slash/instead of a double backslash\\(this is actually just one backslash, but since a backslash is used to escape special characters, such as\nfor a newline character, we need to escape the backslash itself as well). Use${workspaceFolder}/*.cppfor the first highlighted line and"${workspaceFolder}/CSE701"for the second. Now let us open the launch.jsonfile. It should look something like this: {
    "version": "0.2.0",
    "configurations": [
        {
            // ...
            "program": "${fileDirname}\\${fileBasenameNoExtension}.exe",
            // ...
        }
    ]
}
 Replace the highlighted string with "${workspaceFolder}\\CSE701.exe", so that VS Code will know to execute the fileCSE701.exewhen we enter debug mode. On Linux, use"${workspaceFolder}/CSE701"instead. Lastly, open the file c_cpp_properties.json, which should look like this: {
    "configurations": [
        {
            // ...
            "includePath": [
                "${workspaceFolder}/**"
            ],
            // ...
        }
    ],
    "version": 4
}
 Make sure the includePathfield includes the folder${workspaceFolder}/**. This means that VS Code will look for include files (.hor.hpp) in the workspace folder and all its subfolders. The wildcard**matches not only any file name, but also any file in any subfolder. The .jsonfiles that I used to compile this program on my computer are as follows: tasks.json:
 {
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++.exe build active file",
            "command": "C:/Users/barak/mingw64/bin/g++.exe",
            "args": [
                "${workspaceFolder}\\*.cpp",
                "-o",
                "${workspaceFolder}\\CSE701.exe",
                "-Wall",
                "-Wextra",
                "-Wconversion",
                "-Wsign-conversion",
                "-Wshadow",
                "-Wpedantic",
                "-std=c++23",
                "-ggdb3"
            ],
            "options": {
                "cwd": "C:/Users/barak/mingw64/bin"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "compiler: C:/Users/barak/mingw64/bin/g++.exe"
        }
    ]
}
 launch.json:
 {
    "version": "0.2.0",
    "configurations": [
        {
            "name": "g++.exe - Build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}\\CSE701.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "C:\\Users\\barak\\mingw64\\bin\\gdb.exe",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++.exe build active file"
        }
    ]
}
 c_cpp_properties.json:
 {
    "configurations": [
        {
            "name": "Win32",
            "includePath": [
                "${workspaceFolder}/**"
            ],
            "defines": [
                "_DEBUG",
                "UNICODE",
                "_UNICODE"
            ],
            "windowsSdkVersion": "10.0.20348.0",
            "compilerPath": "C:/Users/barak/mingw64/bin/g++.exe",
            "intelliSenseMode": "windows-gcc-x64",
            "cStandard": "c23",
            "cppStandard": "c++23"
        }
    ],
    "version": 4
}
 I'm only including these files here for reference; they will not work on your system in their current form, unless your name is also Barak, you're also using Windows, and your compiler is also located in the folder C:/Users/barak/mingw64/bin. Now we are ready to run the program! No matter which file is currently open in the editor, pressing F5 will compile both main.cppandtriangle.cpp, link them into the executable filetriangle.exe, and then execute that file in debug mode. You should see the output(1, 2, 3)in the terminal. Finally, a note about inlinefunctions when using multiple files in a project. Aninlinefunction is copied as-is into the code, so the compiler needs to have the source code for the function. Therefore, aninlinefunction must be defined in the header file, because iftriangle.cppis already compiled and we don't have the source for it, then the compiler won't know what code to use for theinlinefunction. | 
| Some member functions of a classmodify an object's data, while others do not. We can let the compiler know that a member function does not modify the data by adding theconstkeyword to its definition. In the case of triangle,print()should be aconstmember function, since it does not modify the data. However,change_sides()should not beconst, since it obviously does modify the data. So we should change the declaration ofprint()intriangle.hppto: void print() const;
 and in triangle.cppto: void triangle::print() const
 Let us define two new member functions:  scalewill multiply each of the triangle's side lengths by a scalar. This changes the data, so will not be aconstmember function.areawill return the area of the triangle as calculated from the side lengths (using Heron's formula). This does not change the data, so it will be aconstmember function. Add the following to triangle.hpp, in thepublicsection: // Scale the triangle by the given scalar.
void scale(const double &);
// Calculate the area of the triangle.
double area() const;
 Add the following to triangle.cpp: void triangle::scale(const double &s)
{
    if (s < 0)
        throw invalid_argument("Scalars cannot be negative!");
    a *= s;
    b *= s;
    c *= s;
}
double triangle::area() const
{
    const double s = (a + b + c) / 2;
    return sqrt(s * (s - a) * (s - b) * (s - c));
}
 To test these functions, we can use the following main.cpp: #include <iostream>
#include "triangle.hpp"
using namespace std;
int main()
{
    triangle t(3, 4, 5);
    cout << "Triangle sides: ";
    t.print();
    cout << "Triangle area: ";
    cout << t.area() << '\n';
    const double s = 2;
    t.scale(s);
    cout << "Triangle sides after scaling by " << s << ": ";
    t.print();
    cout << "Triangle area after scaling by " << s << ": ";
    cout << t.area();
}
 It is important to declare member functions which do not change the object's data as constfor two main reasons. The first is readability:constprovides information about how the function behaves, not only the compiler, but also to the user who reads the header file. The second reason is that if the user defines a constobject, the compiler will generate an error when a non-constmember function is called. You can check this by changing the linetriangle t(3, 4, 5)toconst triangle t(3, 4, 5); the compiler will let you callarea(), but notscale(). | 
| Enumerations in C++ work very similarly to enumerations in C. However, in C, an enumjust assigned labels to integers, and any variable defined with thatenumwas secretly anint, which could lead to bugs if we're not careful - for example, by assigning to it an integer value that does not have a corresponding label. In C++, by defining an enumeration as anenum class, we define an entirely new type. For example, consider an improvement of our color enumeration from the C example: enum class color
{
    red,
    green,
    blue
};
 Notice that now we write the labels in lowercase, since they are no longer global constants, as they were in C. Instead, they are accessible using color::red,color:green, andcolor::blue. Also, we define a variable of this type usingcolor cinstead ofenum color c; in C++, you don't need to write theenumdirectly (same as forstruct). In the following example we are using a "plain" enuminstead of anenum class(also called a scoped enumeration): #include <iostream>
using namespace std;
enum color
{
    red,
    green,
    blue
};
int main()
{
    color c = red;   // Note that red is in the global scope, causing potential collisions.
    int x = c;       // Works; enum is implicitly converted to an int.
    cout << x + red; // Also works; can add int and color, which doesn't make sense. Prints 0.
}
 This is a bit problematic. First, just like with C enumerations, the labels red,green, andblueare in the global scope. Usually in C++ we prefer to separate names into different namespaces. Furthermore, when we create a newintand assign the value ofcto it, that value is implicitly converted to anint, and the same happens when we try to add anintand acolor. That doesn't make sense; as I explained regarding C enumerations, the integer values of each label should never be used explicitly. Let us now use enum classinstead: #include <iostream>
using namespace std;
enum class color
{
    red,
    green,
    blue
};
int main()
{
    color c = color::red;   // Note that red is in the color:: scope, not the global scope.
    int x = c;              // Error: a value of type "color" cannot be used to initialize an entity of type "int".
    cout << x + color::red; // Error: cannot add an int and a color.
}
 We see that coloris now indeed an entirely new type, which can only take the specific values we assigned as labels; it is not anint, nor can it be implicitly converted to anint. It is always better to use enum class, as it prevents bugs due to incorrectly comparing anenumwith anint- or even with anotherenum. For example, consider this code: #include <iostream>
using namespace std;
enum color
{
    red,
    green,
    blue
};
enum shape
{
    triangle,
    circle,
    square
};
int main()
{
    color c = red;
    shape s = triangle;
    if (c == s) // Comparing is possible with enum, but not with enum class
        cout << "Values are the same!";
}
 The first thing to notice here is that if we still used the triangleclass we defined above, it would clash with the labeltriangle. This is exactly why it is better to put things in separate namespaces. Furthermore, this program will print out "Values are the same!"because both of them are secretly equal to0, as both are the first labels in their respective enumerations, and there is an implicit conversion taking place here. (The compiler warns about it, but still compiles.) However, if we change both enums toenum class, and correspondinglyredtocolor::redandtriangletoshape::triangle, we will get an error - as we should, since this is very bad code - and the program won't compile. Finally, note that both in the case of enumandenum class, it is still not possible to write something likec = 0, as we could in C. (Well, you could writec = (color)0to explicitly typecast0tocolorif you really wanted, but you shouldn't.) | 
| Static class members, defined with the keyword static, are a generalization of static function variables. They are members that exist in the class itself, independently of any specific objects of that class. For example, we can use a static member variable to keep count of how many objects of a class have been created. This is illustrated in the following program: #include <iostream>
using namespace std;
class counter
{
public:
    counter()
    {
        total_count++;
        my_count = total_count;
    }
    void print_count() const
    {
        cout << "I am object #" << my_count << ".\n";
    }
    inline static uint64_t total_count = 0;
    uint64_t my_count;
};
int main()
{
    const counter first;
    const counter second;
    const counter third;
    cout << "Created " << counter::total_count << " objects in total.\n";
    first.print_count();
    second.print_count();
    third.print_count();
}
 The output is: Created 3 objects in total.
I am object #1.
I am object #2.
I am object #3.
 We see that the staticmember variabletotal_countkeeps count of how many objects were created in total, and it is accessed not via a specific object, but rather as a name in the namespacecounter, i.e.counter::total_count. This member exists independently of any specific objects of that class - in fact, it would still exist if we did not create any objects at all. In addition to static, we also declaredtotal_countasinline. The reason is that this allows us to initialize it within the class itself. Without theinlinekeyword, we would have had to initialize it outside the class definition, or rely on the user to initialize it manually; in both cases it is possible that due to oversighttotal_countwill not be properly initialized, and thus will start the count with some garbage value. We can also define static member functions. For example, it's a good idea to make total_countandmy_countprivate members, since the class invariants in this case are that:  total_countmust be equal to the total number of objects we created,my_countmust be consecutive and unique for each counter. So we don't want to user to manually change the values of these variables and break the invariant. However, if total_countis private, then we should make a publicstaticmember functionget_total_count()to allow read-only access to the variable: #include <iostream>
using namespace std;
class counter
{
public:
    counter()
    {
        total_count++;
        my_count = total_count;
    }
    void print_count() const
    {
        cout << "I am object #" << my_count << ".\n";
    }
    static uint64_t get_total_count()
    {
        return total_count;
    }
private:
    inline static uint64_t total_count = 0;
    uint64_t my_count;
};
int main()
{
    const counter first;
    const counter second;
    const counter third;
    cout << "Created " << counter::get_total_count() << " objects in total.\n";
    first.print_count();
    second.print_count();
    third.print_count();
}
 This will have the same output as before. Note that staticmember functions are accessed similar tostaticmember variables: with the class namespace prefix, e.g.counter::get_total_count(). Also note that we did not need to declareget_total_countas aconstmember function, since it cannot access member variables of any specific objects anyway, onlystaticmember variables of the class itself, and thus there is no situation in which aconstobject can accidentally be modified by the function.  Warning: While it is possible to access staticmembers as if they were members of a specific object, e.g.first.total_countorfirst.get_total_count(), this should be avoided, as it may incorrectly imply to the reader thattotal_counthas a value specific to that object. For maximum readability, always access static members using the namespace of the class, i.e.counter::total_countorcounter::get_total_count(). | 
|  | 
| Operators such as +and-have a well-defined meaning for fundamental types such asintanddouble. An extremely useful feature of C++ is the ability to give meaning to such operators for user-defined types (that is, classes) as well. For example, if we have a matrix class, we could define+to be the operation of matrix addition and*to be matrix multiplication. This is called operator overloading. We can overload almost every operator, as long as it is an existing operator in C++; we cannot create new operators with new symbols. The full list of operators that can be overloaded is:  Arithmetic operators: Binary infix: +(add),-(subtract),*(multiply),/(divide),%(modulo),+=(add and assign),-=(subtract and assign),*=(multiply and assign),/=(divide and assign),%=(modulo and assign)Unary prefix: +(positive),-(negative)Unary prefix or postfix: ++(increment),--(decrement)Bit manipulation: Binary infix: &(bitwise AND),|(bitwise OR),^(bitwise XOR),<<(bitwise left shift),>>(bitwise right shift),&=(bitwise AND and assign),|=(bitwise OR and assign),^=(bitwise XOR and assign),<<=(bitwise left shift and assign),>>=(bitwise right shift and assign)Unary prefix: ~(bitwise NOT)Comparison and logic: Binary infix: ==(equals),!=(does not equal),<(less than),>(greater than),<=(less than or equal),>=(greater than or equal),&&(and),||(or),<=>(the "spaceship operator" for three-way comparison, since C++20)Unary prefix: !(not)Member access: Binary infix: [](subscript / array element),->(member of pointer),->*(pointer to member of pointer)Unary prefix: *(pointer dereference),&(address of)Miscellaneous: Binary infix: =(assign),,(comma expression)N-ary infix: ()(function call) Infix means the operator comes between the two operands (e.g. x + y), prefix means the operator precedes the operand (e.g.-x), and postfix means the operator follows the operand (e.g.x++). Binary means there are two operands (e.g.x + y) and unary means there is one operand (e.g.-x). The function call operator ()is the only one that allows more than two operands, i.e.f(x, y, z, ...). Of course, it is possible to write something likex + y + z, but the+operator is still binary, since this expression will be evaluated as(x + y) + z, i.e. two binary operators evaluated one after the other. Some of these operators have not been used so far in this course. Let us quickly explain what they do:  The unary prefix +usually doesn't actually do anything;+xis equal tox.When ++comes as a prefix, i.e.++x, it is a "pre-increment" operator, which increments the variable and returns the incremented value. When++comes as a postfix, i.e.x++, it is a "post-increment" operator, which increments the variable but returns the original value. Same goes for--. To remember which is which, notice that they are executed from left to right: ++xmeans "first increment, then returnx" whilex++means "first returnx, then increment". Soint x = 1; y = ++x;will result inx = 2andy = 2, butint x = 1; y = x++;will result inx = 2andy = 1.We didn't mention the bit manipulation operators before because they are not used very often. They perform the usual bit operations AND, OR, XOR, and NOT bit-by-bit. The shift operators <<and>>are respectively equivalent to multiplying and dividing by powers of 2. You can read more about them on Wikipedia. Note that the most common use of <<and>>in C++ is not for bit manipulation; instead, these operators are overloaded to deal with input/output streams. Whenever you writecout <<orcin >>, you are using overloaded operators!The three-way comparison operator <=>was introduced in C++20. To use it, you need to enable C++20 (or later) in the compiler (see above) and#include <compare>in your program. We will not discuss this operator in our course.The comma operator ,is not the usual comma such as in a function callf(x, y), it is used to evaluate several expressions and keep only the result of the last one. For example, the statementsint x = 0; cout << (++x, ++x);will output2since the first++xincrementsxby1and gives the result1which is discarded, while the second++xincrementsxby1and gives the result2which is then printed.We have seen above that the operator ->is used to access a member of astructorclasswhen the latter is given as a pointer. The expressionx->yis equivalent to(*x).y. Similarly, for the operator->*, the expressionx->*yis equivalent to(*x).*y. Note that the operators ::(scope resolution),.(member of object),.*(pointer to member of object), and?:(ternary conditional operator) cannot be overloaded. Operators cannot be overloaded for fundamental types; you cannot redefine how +works forint, for example. At least one of the operands must be a user-defined type, although it is possible to combine a user-defined type with a fundamental type - for example, we could overload*so that if it is used with onedoubleand one user-defined matrix object, it multiplies the entire matrix by the number. Apart from the overloaded operators <<and>>that we discussed above, we already encountered another example of an overloaded operator when we talked about the C++ string class. To concatenate strings, we simple writestring1 + string2; this works because the operator+has been overloaded with a function that performs concatenation of strings. Overloaded operators retain their existing syntax and number of operands. For example, !(not) must still act on one object and<(less than) must still compare two objects. The syntax for operator overloading for unary operators such as!and++, which act on one operand, is: output_type operator@(input_type operand)
{
    // Function body
}
 Here, @should be replaced with the actual operator, e.g.operator!to replace the!operator. For example,!xwill call the functionoperator!(x). The syntax for operator overloading for binary operators such as +and*, which act on two operands, is: output_type operator@(input_type1 operand1, input_type2 operand2)
{
    // Function body
}
 Again, @should be replaced with the actual operator, e.g.operator+to replace the+operator. For example,x + ywill call the functionoperator+(x, y). Here are some important rules of thumb for how to use operator overloading:  The meaning of the operators should be retained when overloading. For example, if the class is a matrix, then +should be overloaded with matrix addition. If you overload+with matrix multiplication, your code will be very confusing.The usual behavior of the operators should be retained when overloading. For example, the user will expect that x + y - xshould be equal toy. Similarly, the user will expect that ifx < yandy < z, thenx < z.If an operator is defined, then related operators should also be defined. For example, if you define +you should also define+=and if you define<you should also define>. Generally, in such cases, one operator will refer to the other. For example, it is customary to use +=to define+(as we will do below), and to use<to define>(or vice versa).Overloaded operators should modify their operands if and only if the original operator does. For example, a + bis not expected to modifyaorb, buta++is expected to modifya.Operators should get their arguments by reference whenever possible. If the operator does not modify an operand, it should get it as a constreference, and if it does, it should get it as a non-constreference. However, if a temporary copy must be created anyway, it is often convenient to get the operand by value, as that eliminates the need to manually create a copy. See below for examples.Only use overloaded operators when it makes sense to do so. For example, it makes sense to define a subtraction operator between two dates, which will give the time difference between them. However, adding or multiplying two dates doesn't make sense.  Warning: If you do not follow these rules of thumb when overloading operators, your code may become very confusing. Remember that operator overloading is just a convenience, not a necessity; you can always just use normal functions to operate on objects.  | 
| Let us now consider a simple example. If we try to pass a vectortocoutin order to print it, we will get an error: #include <iostream>
#include <vector>
using namespace std;
int main()
{
    const vector<double> a = {1, 2, 3};
    cout << a; // Error: no match for 'operator<<'
}
 The compiler is telling is that there is no match for operator<<with the given operands, one of which is anostream(that is the type ofcout) and the other is avector<double>. This operator is defined when the second operand is, for example, a number or a string, but there is no built-in way to print out vectors in C++. Therefore, we need to define this operator ourselves using operator overloading: #include <iostream>
#include <vector>
using namespace std;
ostream& operator<<(ostream& out, const vector<double>& vec)
{
    out << '(';
    for (size_t i = 0; i < vec.size() - 1; ++i)
        out << vec[i] << ", ";
    out << vec[vec.size() - 1] << ')';
    return out;
}
int main()
{
    const vector<double> v = {1, 2, 3};
    cout << v; // (1, 2, 3)
}
 Let us explain each piece of the line ostream& operator<<(ostream& out, const vector<double>& vec):  ostream&: The function we are defining returns a reference to anostreamobject.operator<<: This function will act whenever we use the operator<<.(ostream& out, const vector<double>& vec): This operator will have two operands: one is a reference to anostreamobject and the other is a reference to avector<double>object.The first argument is not const, because we want to change it. The second argument is aconst, because we do not want to change it. When we call cout << v, this a<<operator with two operands. The first iscout, which is anostreamobject. The second isv, which is avector<double>object. The compiler looks for a suitable operator overload, and finds our function, which is executed. Note that if the return type was voidinstead ofostream &, the statementcout << vwould have still worked, but we would have not been able to chain the<<operator - as in, for example,cout << v << v. Returning a reference to the streamoutallows the same stream to be used again in a subsequent operation. The function itself is pretty simple:  We put a left parenthesis (intoout.We iterate over all the elements of the vector except the last one using a forloop, and print each one of them followed by a comma and a space.We output the last element of the vector followed by a right parenthesis ). (If we didn't want the parentheses or the commas, we could have used a much more compact range-based forloop of the formfor (const double& i : vec) out << i << ' '.) In the following sections, we will make use of the <<operator overload to print out vectors in order to demonstrate the other operator overloads we will create. To save space, I will create a header filevector_overloads.hppwhich will include the above code except for themain()function, and include it in each example. Every time I define a new overload, I will add it to this header file as well, and in the end we will have one big header file with all of our overloads. | 
| The next operators we will overload are the comparison operators ==and!=. Note that these operators are actually already overloaded in the standard library, but we will write our own overloads anyway, for pedagogical reasons. Clearly, two vectors are equal to each other if all their elements are the same. Therefore, the comparison must involve comparing every single element of both vectors. However, we should first check if the vectors are the same size; if not, then they are clearly not equal, and we do not need to check any elements at all. Furthermore, there is no point in defining two completely independent operators for ==and!=; one can simply be written in terms of the other. In fact, in C++20, the compiler will generate the!=operator automatically if the==operator is defined for the same operands, but we will write it explicitly anyway. Here is the code: #include "vector_overloads.hpp"
bool operator==(const vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        return false;
    for (size_t i = 0; i < lhs.size(); ++i)
        if (lhs[i] != rhs[i])
            return false;
    return true;
}
bool operator!=(const vector<double>& lhs, const vector<double>& rhs)
{
    return !(lhs == rhs);
}
int main()
{
    const vector<double> a = {1, 2, 3};
    const vector<double> b = {1, 2, 3};
    const vector<double> c = {1, 2, 3, 4};
    const vector<double> d = {5, 6, 7};
    cout << (a == a) << ' ' << (a != a) << '\n'; // 1 0
    cout << (a == b) << ' ' << (a != b) << '\n'; // 1 0
    cout << (a == c) << ' ' << (a != c) << '\n'; // 0 1
    cout << (a == d) << ' ' << (a != d) << '\n'; // 0 1
}
 Notice that, of course, a == a. We could potentially include something like this in the beginning ofoperator==(): if (&lhs == &rhs)
    return true;
 This will save us time if we compare a vector to itself, because we won't have to redundantly compare every single element to itself. However, comparing an object to itself is done very rarely if at all, and running this check every single time we're comparing any two vectors can have a detrimental effect on performance too, especially if we are comparing many different vectors. Importantly, when overloading the ==operator for any classT, the prototype must always bebool operator==(const T& lhs, const T& rhs), and similarly for!=. You can find the prototypes for other comparison operators here. | 
| Next, we would like to introduce vector addition using the operators +and+=. These operators will add two vectors element-by-element. The canonical way to implement addition or other arithmetic operators on any class is to first define+=and then reuse it in+, similarly to how we first defined==and then reused it in!=. The reason we don't do it the opposite way - define+and then reuse it in+=- will be explained shortly. Obviously, we cannot add two vectors of different sizes. Therefore, our addition operation must throw an exception if the sizes of the vectors don't match. Here is the code: #include "vector_overloads.hpp"
#include <stdexcept>
vector<double>& operator+=(vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        throw invalid_argument("Cannot add vectors of different sizes!");
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] += rhs[i];
    return lhs;
}
vector<double> operator+(vector<double> lhs, const vector<double>& rhs)
{
    lhs += rhs;
    return lhs;
}
int main()
{
    try
    {
        vector<double> a = {1, 2, 3};
        const vector<double> b = {4, 5, 6};
        const vector<double> c = {7, 8, 9};
        const vector<double> d = {1, 2, 3, 4};
        a += b;
        cout << a << '\n';         // (5, 7, 9)
        cout << b + c << '\n';     // (11, 13, 15)
        cout << a + b + c << '\n'; // (16, 20, 24)
        cout << a + d << '\n';     // Error: Cannot add vectors of different sizes!
    }
    catch (const invalid_argument& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 Let's go over operator+=()line by line:  The operator takes two arguments: vector<double>& lhs: The vector that we want to assign the sum to. Note that we must pass it by (non-const) reference so we can change it; if we passed it by value, we would only be changing a copy of it.const vector<double>& rhs: The vector that we want to add tolhs. We pass it byconstreference because we don't want to change it (hence theconst) but we also don't want to copy it, since that would be a waste of time (hence the reference).Importantly, when overloading the +=operator for any classT, the prototype must always beT& operator+=(T& lhs, const T& rhs), and similarly for all other compound assignment operators. You can find the prototypes for other assignment operators here.First, we compare the sizes of the two vectors, and throw an exception if they don't match.Next, we go over each element of lhsand add the corresponding element ofrhs, storing the result inlhsusing the built-in+=operator.Finally, we return a reference to lhs. Of course, we must return a reference, since we do not want to make a copy of it. Next, operator+():  The operator takes two arguments: vector<double> lhs: The first vector to add. Note that we pass it by value, even though the usual prototype for+isT operator+(const T& lhs, const T& rhs)(as you can see here). The reason is that in this case, we actually want to make a copy oflhs! This is because we cannot change either of the two vectors; we just want to add their elements and return a new vector with the result. We could pass both vectors as constreferences and create a new temporary vector to store the result, but that requires allocating and initializing the temporary vector. It's more efficient to just let the compiler make the copy automatically by passing one of the vectors by value, and this also lets us reuse our+=operator, making the+operator trivial.const vector<double>& rhs: The vector that we want to add tolhs, passed byconstreference.In the body of the function we simply use the operator +=, which we already defined, to addlhstorhsand store the result inlhs.The operator returns lhsby value. This has nothing to do with the originallhs, it is essentially a new vector containing the result of addinglhsandrhs. Returning it by value allows us to chain addition operations, as demonstrated in the example bya + b + c. | 
| Since we defined addition, we should also define subtraction. Note that there are two different -operators: an unary and a binary one. The unary-is easy: it which simply returns a copy of the vector with all its elements replaced with their negatives. The binary-can be implemented in terms of-=just as we did for+and+=; in fact, we only need to replace the+with a-. Here is the result: #include "vector_overloads.hpp"
vector<double>& operator-=(vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        throw invalid_argument("Cannot subtract vectors of different sizes!");
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] -= rhs[i];
    return lhs;
}
vector<double> operator-(vector<double> lhs, const vector<double>& rhs)
{
    lhs -= rhs;
    return lhs;
}
vector<double> operator-(vector<double> vec)
{
    for (size_t i = 0; i < vec.size(); ++i)
        vec[i] = -vec[i];
    return vec;
}
int main()
{
    try
    {
        vector<double> a = {1, 2, 3};
        const vector<double> b = {4, 5, 6};
        const vector<double> c = {7, 8, 9};
        const vector<double> d = {1, 2, 3, 4};
        a -= b;
        cout << a << '\n';         // (-3, -3, -3)
        cout << -a << '\n';        // (3, 3, 3)
        cout << b - c << '\n';     // (-3, -3, -3)
        cout << a + b - c << '\n'; // (-6, -6, -6)
        cout << a - d << '\n';     // Error: Cannot subtract vectors of different sizes!
    }
    catch (const invalid_argument& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 Note that we could have just defined the binary -operator asreturn lhs + (-rhs), taking advantage of the fact that we already defined an unary-operator. Indeed, in linear algebra, subtraction of vectors is actually defined as adding the additive inverse of the second vector to the first. However, this would mean that the program must run two loops, one to calculate-rhsand another to calculatelhs + (-rhs). By writing a single subtraction loop explicitly, we essentially cut the time it would take to subtract vectors by half. | 
| The overload for multiplication should return the dot product of the two vectors. As with addition and subtraction, we cannot multiply vectors of different sizes, so we should throw an exception in that case. In addition, the return value of *should be a scalar, not a vector. Therefore,*=is not a valid operator, as it would assign a scalar to a vector. Note also that there is no division of vectors, so we will not overload the/operator. Here is the code: #include "vector_overloads.hpp"
double operator*(const vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        throw invalid_argument("Cannot take the dot product of vectors of different sizes!");
    double result = 0;
    for (size_t i = 0; i < lhs.size(); ++i)
        result += lhs[i] * rhs[i];
    return result;
}
int main()
{
    try
    {
        const vector<double> a = {1, 2, 3};
        const vector<double> b = {4, 5, 6};
        const vector<double> c = {1, 2, 3, 4};
        cout << a * b << '\n';                        // 32
        cout << b * a << '\n';                        // 32 (the operation is commutative)
        cout << b * vector<double> {7, 8, 9} << '\n'; // 122 (used a literal vector)
        cout << (a - b) * a << '\n';                  // -18 (chained different operations)
        cout << a * c << '\n';                        // Error: Cannot take the dot product of vectors of different sizes!
    }
    catch (const invalid_argument& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 Here we used vector<double> {7, 8, 9}as a literal vector. What we mean by that is that instead of first declaring, for example,vector<double> d = {7, 8, 9}and then writingcout << b * d, we wrote the vector directly. This is the same as writing the number7directly in a statement instead of first declaringint x = 7and usingx. If we're just going to use an object once, it's better to use it as a literal instead of declaring a specific symbol for it. Objects created in this way are called temporary objects; since we didn't givevector<double> {7, 8, 9}a name, it only exists temporary in that line, and then automatically discarded. | 
| In addition to the dot product of two vectors, we should also define multiplication of a vector by a scalar. This means that we should overload *for the case where one operand is adoubleand the other is avector<double>. Since the two operands are of different types, we will need to overload*twice: one overload for the case where the vector is on the left and another when it's on the right. Also, in this case it does make sense to overload*=too, meaning that we assign to the vector its product with the given scalar. Here are the new overloads: #include "vector_overloads.hpp"
vector<double>& operator*=(vector<double>& lhs, const double rhs)
{
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] *= rhs;
    return lhs;
}
vector<double> operator*(vector<double> lhs, const double rhs)
{
    lhs *= rhs;
    return lhs;
}
vector<double> operator*(const double lhs, vector<double> rhs)
{
    rhs *= lhs;
    return rhs;
}
int main()
{
    vector<double> a = {1, 2, 3};
    cout << a * 3 << '\n'; // (3, 6, 9)
    cout << 4 * a << '\n'; // (4, 8, 12)
    a *= 5;
    cout << a << '\n'; // (5, 10, 15)
}
 | 
| The operator overloads that we defined above for vectors are very useful for doing linear algebra. Therefore, we should put them in portable form, so that they may be easily incorporated into different programs. In cases where there is not too much code to warrant a separate.cppfile (which will then need to be complied separately), C++ programmers often share code as a header-only library, meaning that the entire library of classes and/or functions is located in the header file itself. Then all one needs to do is to include that header file, and that's it. The header file, vector_overloads.hpp, is as follows: #include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
ostream& operator<<(ostream& out, const vector<double>& vec)
{
    out << '(';
    for (size_t i = 0; i < vec.size() - 1; ++i)
        out << vec[i] << ", ";
    out << vec[vec.size() - 1] << ')';
    return out;
}
bool operator==(const vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        return false;
    for (size_t i = 0; i < lhs.size(); ++i)
        if (lhs[i] != rhs[i])
            return false;
    return true;
}
bool operator!=(const vector<double>& lhs, const vector<double>& rhs)
{
    return !(lhs == rhs);
}
vector<double>& operator+=(vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        throw invalid_argument("Cannot add vectors of different sizes!");
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] += rhs[i];
    return lhs;
}
vector<double> operator+(vector<double> lhs, const vector<double>& rhs)
{
    lhs += rhs;
    return lhs;
}
vector<double>& operator-=(vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        throw invalid_argument("Cannot subtract vectors of different sizes!");
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] -= rhs[i];
    return lhs;
}
vector<double> operator-(vector<double> lhs, const vector<double>& rhs)
{
    lhs -= rhs;
    return lhs;
}
vector<double> operator-(vector<double> vec)
{
    for (size_t i = 0; i < vec.size(); ++i)
        vec[i] = -vec[i];
    return vec;
}
double operator*(const vector<double>& lhs, const vector<double>& rhs)
{
    if (lhs.size() != rhs.size())
        throw invalid_argument("Cannot take the dot product of vectors of different sizes!");
    double result = 0;
    for (size_t i = 0; i < lhs.size(); ++i)
        result += lhs[i] * rhs[i];
    return result;
}
vector<double>& operator*=(vector<double>& lhs, const double rhs)
{
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] *= rhs;
    return lhs;
}
vector<double> operator*(vector<double> lhs, const double rhs)
{
    lhs *= rhs;
    return lhs;
}
vector<double> operator*(const double lhs, vector<double> rhs)
{
    rhs *= lhs;
    return rhs;
}
 We can now use these overloads in any program by including vector_overloads.hppin the program. Here is an example of amain.cppmaking use of our library: #include "vector_overloads.hpp"
#include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
int main()
{
    try
    {
        vector<double> v = {1, 2, 3};
        vector<double> w = {4, 5, 6};
        vector<double> u = {1, 1, 1};
        cout << v + w << '\n'; // (5, 7, 9)
        cout << v * w << '\n'; // 32
        cout << -v << '\n';    // (-1, -2, -3)
        v += w;                //
        cout << v << '\n';     // (5, 7, 9)
        cout << v - w << '\n'; // (1, 2, 3)
        w -= u;                //
        cout << w << '\n';     // (3, 4, 5)
        cout << 2 * v << '\n'; // (10, 14, 18)
        cout << v * 3 << '\n'; // (15, 21, 27)
    }
    catch (const invalid_argument& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 Note that, unlike with the triangle class, in this case there is no separate .cppfile; everything is in the header filevector_overloads.hpp. When you write#include "vector_overloads.hpp", what happens is simply that the compiler includes all the code in the header file as-is in your source code. There is no separate compilation or linking taking place. For compact and lightweight C++ packages, this is often the desired format, as you only need to worry about one extra file. However, header-only libraries are only suitable for small code. Libraries which contain tens of thousands of lines of code are usually distributed as separate .hppand.cppfiles, and often, many such files. The reason is that compilation takes time, sometimes even several minutes or longer for large projects. By placing the library's code in a separate.cppfile, we only need to compile it once, and subsequently we can just link the already-compiled code. We will explain in more detail how that works later. | 
| So far, we have overloaded all operators as non-member functions. However, for user-defined classes, it is also possible to overload operators using member functions. For a unary operator @, the non-memberoperator@(x)is equivalent to the memberx.operator@(). For a binary operator@, the non-memberoperator@(x, y)is equivalent to the memberx.operator@(y). The operators =,(),[], and->must be overloaded using member functions. On the other hand, if we are overloading a class written by someone else, such as when overloading<<forostream, it is more convenient to overload using non-member functions. In other cases, it's mostly a matter of taste whether to implement overloaded operators as member functions or not. (Personally, I think overloading operators as non-member functions produces clearer and more organized code.) As an example, I will now define a class for matrices. Since in this case I am not just overloading operators for someone else's class (as in the vector overloads example), it makes sense to overload the compound assignment operators +=,-=, and*=(for multiplication by scalar) as member functions. This is because the matrix on the left is being assigned the result, so there is an asymmetry between the two operands. On the other hand, the binary operators+,-, and*themselves should still be implemented as non-member functions, as in this case there is a symmetry between the operands. This class will also include an overloaded operator ()used to access the matrix elements. Instead ofm[x][y], which is what we would have if the matrix was a multi-dimensional array, I use the more convenient notationm(x, y). The operator()is overloaded using a member function. Note that there will be two versions of the ()overload: one is not aconstand returns a non-constreference, and the other is aconstand returns aconstreference. The first version allows modification of the element being accessed. For example,m(0, 0) = 1changes the top-left element to1. This is possible becausem(0, 0)is a reference to that element in memory, and this allows the user to access that element directly for both reading and writing. On the other hand, when the second version is used, it returns a constant reference to the element, so the user cannot modify that element. (We could also have returned a copy, but as usual, returning a reference is faster since copying a large object takes time.) Similarly, the member functions get_rows()andget_cols()allow the user to obtain the number of rows and columns, but not change it, therefore they are marked asconst. If you remove the constkeyword from any of the constant member functions, the program will not compile, since the<<operator overload takes aconst matrix &argument, so it cannot call any member functions that are notconst. This is exactly why we needed two versions of the()overload. The overloaded operator<<accepts aconst matrix &m, so it will call the second (const) version of the()overload. On the other hand, themainfunction calls the first (non-const) version. I wrote this matrixclass to illustrate everything we learned about C++ so far. We will further improve it later in the course. Please refer to the comments in the header file for more information. matrix.hpp:
 #include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
class matrix
{
public:
    // Constructor to create a zero matrix.
    // First argument: number of rows.
    // Second argument: number of columns.
    matrix(const size_t, const size_t);
    // Constructor to create a diagonal matrix from a vector.
    // Argument: a vector containing the elements on the diagonal.
    // Number of rows and columns is inferred automatically.
    matrix(const vector<double>&);
    // Constructor to create a diagonal matrix from an initializer_list.
    // Argument: an initializer_list containing the elements on the diagonal.
    // Number of rows and columns is inferred automatically.
    matrix(const initializer_list<double>&);
    // Constructor to create a matrix from a vector.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: a vector containing the elements in row-major order.
    matrix(const size_t, const size_t, const vector<double>&);
    // Constructor to create a matrix from an initializer_list.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: an initializer_list containing the elements in row-major order.
    matrix(const size_t, const size_t, const initializer_list<double>&);
    // Member function to obtain (but not modify) the number of rows in the matrix.
    size_t get_rows() const;
    // Member function to obtain (but not modify) the number of columns in the matrix.
    size_t get_cols() const;
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // Non-const version: allows modification of the element.
    double& operator()(const size_t, const size_t);
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // const version: does not allow modification of the element.
    const double& operator()(const size_t, const size_t) const;
    // Member function to access matrix elements WITH range checking.
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // Non-const version: allows modification of the element.
    double& at(const size_t, const size_t);
    // Member function to access matrix elements WITH range checking.
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // const version: does not allow modification of the element.
    const double& at(const size_t, const size_t) const;
    // Overloaded binary operator += to add another matrix to this matrix.
    matrix& operator+=(const matrix&);
    // Overloaded binary operator -= to subtract another matrix from this matrix.
    matrix& operator-=(const matrix&);
    // Overloaded binary operator *= to multiply this matrix by a scalar.
    matrix& operator*=(const double);
    // Exception to be thrown if the number of rows or columns given to the constructor is zero.
    inline static invalid_argument zero_size = invalid_argument("Matrix cannot have zero rows or columns!");
    // Exception to be thrown if the vector of elements provided to the constructor is of the wrong size.
    inline static invalid_argument initializer_wrong_size = invalid_argument("Initializer does not have the expected number of elements!");
    // Exception to be thrown if two matrices of different sizes are added or subtracted.
    inline static invalid_argument incompatible_sizes_add = invalid_argument("Cannot add or subtract two matrices of different dimensions!");
    // Exception to be thrown if two matrices of incompatible sizes are multiplied.
    inline static invalid_argument incompatible_sizes_multiply = invalid_argument("Two matrices can only be multiplied if the number of columns in the first matrix is equal to the number of rows in the second matrix!");
    // Exception to be thrown if trying to access an element out of range.
    inline static invalid_argument out_of_range = invalid_argument("Tried to access an element out of range!");
private:
    // The number of rows.
    size_t rows = 0;
    // The number of columns.
    size_t cols = 0;
    // A vector storing the elements of the matrix in flattened (1-dimensional) form.
    vector<double> elements;
};
// Overloaded binary operator << to easily print out a matrix to a stream.
ostream& operator<<(ostream&, const matrix&);
// Overloaded binary operator == to compare two matrices.
bool operator==(const matrix&, const matrix&);
// Overloaded binary operator != to compare two matrices.
bool operator!=(const matrix&, const matrix&);
// Overloaded binary operator + to add two matrices.
matrix operator+(matrix, const matrix&);
// Overloaded unary operator - to take the negative of a matrix.
matrix operator-(const matrix);
// Overloaded binary operator - to subtract two matrices.
matrix operator-(matrix, const matrix&);
// Overloaded binary operator * to multiply two matrices.
matrix operator*(const matrix&, const matrix&);
// Overloaded binary operator * to multiply a matrix on the left and a scalar on the right.
matrix operator*(matrix, const double);
// Overloaded binary operator * to multiply a scalar on the left and a matrix on the right.
matrix operator*(const double, matrix);
 matrix.cpp:
 #include "matrix.hpp"
#include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
matrix::matrix(const size_t rows_, const size_t cols_) : rows(rows_), cols(cols_)
{
    if (rows == 0 or cols == 0)
        throw zero_size;
    elements = vector<double>(rows * cols);
}
matrix::matrix(const vector<double>& diagonal_) : rows(diagonal_.size()), cols(diagonal_.size())
{
    if (rows == 0)
        throw zero_size;
    elements = vector<double>(rows * cols);
    for (size_t i = 0; i < rows; ++i)
        elements[(cols * i) + i] = diagonal_[i];
}
matrix::matrix(const initializer_list<double>& diagonal_) : matrix(vector<double>(diagonal_)) {}
matrix::matrix(const size_t rows_, const size_t cols_, const vector<double>& elements_) : rows(rows_), cols(cols_), elements(elements_)
{
    if (rows == 0 or cols == 0)
        throw zero_size;
    if (elements_.size() != rows * cols)
        throw initializer_wrong_size;
}
matrix::matrix(const size_t rows_, const size_t cols_, const initializer_list<double>& elements_) : matrix(rows_, cols_, vector<double>(elements_)) {}
size_t matrix::get_rows() const
{
    return rows;
}
size_t matrix::get_cols() const
{
    return cols;
}
double& matrix::operator()(const size_t row, const size_t col)
{
    return elements[(cols * row) + col];
}
const double& matrix::operator()(const size_t row, const size_t col) const
{
    return elements[(cols * row) + col];
}
double& matrix::at(const size_t row, const size_t col)
{
    if ((row > rows - 1) or (col > cols - 1))
        throw out_of_range;
    return elements[(cols * row) + col];
}
const double& matrix::at(const size_t row, const size_t col) const
{
    if ((row > rows - 1) or (col > cols - 1))
        throw out_of_range;
    return elements[(cols * row) + col];
}
matrix& matrix::operator+=(const matrix& other)
{
    if ((rows != other.rows) or (cols != other.cols))
        throw incompatible_sizes_add;
    for (size_t i = 0; i < rows * cols; ++i)
        elements[i] += other.elements[i];
    return *this;
}
matrix& matrix::operator-=(const matrix& other)
{
    if ((rows != other.rows) or (cols != other.cols))
        throw incompatible_sizes_add;
    for (size_t i = 0; i < rows * cols; ++i)
        elements[i] -= other.elements[i];
    return *this;
}
matrix& matrix::operator*=(const double scalar)
{
    for (size_t i = 0; i < rows * cols; ++i)
        elements[i] *= scalar;
    return *this;
}
ostream& operator<<(ostream& out, const matrix& mat)
{
    for (size_t i = 0; i < mat.get_rows(); ++i)
    {
        out << "( ";
        for (size_t j = 0; j < mat.get_cols(); ++j)
            out << mat(i, j) << '\t';
        out << ")\n";
    }
    return out;
}
bool operator==(const matrix& lhs, const matrix& rhs)
{
    if ((lhs.get_rows() != rhs.get_rows()) or (lhs.get_cols() != rhs.get_cols()))
        return false;
    for (size_t i = 0; i < lhs.get_rows(); ++i)
        for (size_t j = 0; j < lhs.get_cols(); ++j)
            if (lhs(i, j) != rhs(i, j))
                return false;
    return true;
}
bool operator!=(const matrix& lhs, const matrix& rhs)
{
    return !(lhs == rhs);
}
matrix operator+(matrix lhs, const matrix& rhs)
{
    lhs += rhs;
    return lhs;
}
matrix operator-(matrix mat)
{
    for (size_t i = 0; i < mat.get_rows(); ++i)
        for (size_t j = 0; j < mat.get_cols(); ++j)
            mat(i, j) = -mat(i, j);
    return mat;
}
matrix operator-(matrix lhs, const matrix& rhs)
{
    lhs -= rhs;
    return lhs;
}
matrix operator*(const matrix& lhs, const matrix& rhs)
{
    if (lhs.get_cols() != rhs.get_rows())
        throw matrix::incompatible_sizes_multiply;
    matrix c(lhs.get_rows(), rhs.get_cols());
    for (size_t i = 0; i < lhs.get_rows(); ++i)
        for (size_t j = 0; j < rhs.get_cols(); ++j)
            for (size_t k = 0; k < lhs.get_cols(); ++k)
                c(i, j) += lhs(i, k) * rhs(k, j);
    return c;
}
matrix operator*(matrix lhs, const double rhs)
{
    lhs *= rhs;
    return lhs;
}
matrix operator*(const double lhs, matrix rhs)
{
    rhs *= lhs;
    return rhs;
}
 Sample main.cpp: #include "matrix.hpp"
#include <exception>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    try
    {
        // Constructor with two integers: create a 3x4 matrix of zeros.
        matrix A(3, 4);
        cout << "A:\n" << A;
        // Constructor with one vector: create a 3x3 matrix with 1, 2, 3 on the diagonal.
        matrix B(vector<double>{1, 2, 3});
        cout << "B:\n" << B;
        // Constructor with one initializer_list: create a 4x4 matrix with 1, 2, 3, 4 on the diagonal.
        matrix C{1, 2, 3, 4};
        cout << "C:\n" << C;
        // Constructor with two integers and one vector: create a 2x3 matrix with the given elements in row-major order.
        matrix D(2, 3, vector<double>{1, 2, 3, 4, 5, 6});
        cout << "D:\n" << D;
        // Constructor with two integers and one initializer_list: create a 2x2 matrix with the given elements in row-major order.
        matrix E(2, 2, {1, 2, 3, 4});
        cout << "E:\n" << E;
        // Demonstration of some of the overloaded operators.
        D(0, 2) = 7;
        cout << "D after D(0, 2) = 7:\n" << D;
        matrix F = D * B;
        cout << "F = D * B:\n" << F;
        cout << "D + F:\n" << D + F;
        cout << "7 * B:\n" << 7 * B;
        matrix G(3, 3, {1, 0, 0, 0, 2, 0, 0, 0, 3});
        cout << "B == G: " << (B == G) << '\n';
        cout << "B == F: " << (B == F) << '\n';
        D *= 2;
        cout << "D after D *= 2:\n" << D;
        cout << "D * 3:\n" << D * 3;
        cout << "4 * D:\n" << 4 * D;
        // initializer_list constructor will be used: create a 2x2 diagonal matrix with 1, 2 on the diagonal.
        cout << "matrix{1, 2}:\n";
        cout << matrix{1, 2};
        // (size_t, size_t) constructor will be used: create a 1x2 zero matrix.
        cout << "matrix(1, 2):\n";
        cout << matrix(1, 2);
        // Demonstration of range checking; the range of D is 0-1 for rows and 0-2 for columns.
        cout << "Range checking:\n";
        cout << "D.at(0, 0): " << D.at(0, 0) << '\n';
        cout << "D.at(1, 0): " << D.at(1, 0) << '\n';
        cout << "D.at(2, 0): " << D.at(2, 0) << '\n'; // Error: Tried to access an element out of range!
    }
    catch (const exception& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 Output: A:
( 0     0       0       0       )
( 0     0       0       0       )
( 0     0       0       0       )
B:
( 1     0       0       )
( 0     2       0       )
( 0     0       3       )
C:
( 1     0       0       0       )
( 0     2       0       0       )
( 0     0       3       0       )
( 0     0       0       4       )
D:
( 1     2       3       )
( 4     5       6       )
E:
( 1     2       )
( 3     4       )
D after D(0, 2) = 7:
( 1     2       7       )
( 4     5       6       )
F = D * B:
( 1     4       21      )
( 4     10      18      )
D + F:
( 2     6       28      )
( 8     15      24      )
7 * B:
( 7     0       0       )
( 0     14      0       )
( 0     0       21      )
B == G: 1
B == F: 0
D after D *= 2:
( 2     4       14      )
( 8     10      12      )
D * 3:
( 6     12      42      )
( 24    30      36      )
4 * D:
( 8     16      56      )
( 32    40      48      )
matrix{1, 2}:
( 1     0       )
( 0     2       )
matrix(1, 2):
( 0     0       )
Range checking:
D.at(0, 0): 2
D.at(1, 0): 8
D.at(2, 0): Error: Tried to access an element out of range!
 The two versions of the at()member function access the elements of thevectorusing its own pre-defined member functionat(), which throws the exceptionstd::out_of_rangeif the element being accessed is out of the range of thevector. We utilize this to indirectly implement range checking formatrixas well. For the second and fourth constructors, we use initializer_list, which is defined in the header file<initializer_list>. Aninitializer_listobject is simply a list of elements in the format used to initialize a C-style array, i.e.{element0, element1, ...}, with the addition of a few member functions such assize. When we tell the constructor to take a const initializer_list<double>&as an argument, it will expect to take a list of this form, with the elements being of typedouble. It will then simply convert that list to avector<double>, and delegate the construction of thematrixto the appropriate constructor that takes avector<double>as an argument. Note the syntax for delegating constructors: there is a:followed by the appropriate constructor, followed by{}to indicate that the delegating constructor is an empty function. Generally, an initializer_listshould be used as input whenever you want to quickly create a newmatrixwith specific elements, since the syntax is shorter and more convenient. Avectorshould be used as input whenever you want to create a newmatrixusing elements you previously collected into avector. The initializer_listconstructor takes precedence over any other constructor. Therefore,matrix{1, 2}will be a diagonal matrix with1and2on the diagonal, since the constructormatrix(const initializer_list<double>&)will be used, whilematrix(1, 2)will be a 1x2 zero matrix, since the constructormatrix(const size_t, const size_t)will be used. This is illustrated at the end of the samplemain.cpp. For convenience, we defined some ready-made exceptions (all of type invalid_argument) asinline staticelements of thematrixclass. This also allows the user to know which exceptions to expect the class to throw. Recall thatstaticmeans they are stored only once in the class itself, and not in individual objects, so this doesn't cause any waste of space. Also recall thatinlinesimply means we can initialize the static element within the class definition. Since matrixthrows two types of exceptions,out_of_rangeandinvalid_argument, we wrotecatch (const exception& e)in order to catch any kind of exception (as long as it is derived from the standardexceptionclass). We will explain how exactly this works later. | 
|  | 
| In C++, you can still use printfif you want; it's in the header<cstdio>. However, using stream objects such ascoutallows much more flexibility, especially because you do not have to worry about format placeholders matching the precise type of the argument. For example, to print an integer usingprintfyou would need to specify%dif it's signed or%uif it's unsigned, and addlorllaccording to its bit width. It is much easier to just insert it intocoutwith<<. To format output when using coutand other streams, we can use the following member functions:  precision(n)sets the maximum number of digits displayed for a floating point number, counting both the digits before and after the decimal point.precision()returns the current precision.width(n)sets the character width of the next output, padding with spaces if necessary. Note that it only works for one output, so it needs to be called every time you want to print a number with the desired width.width()returns the current width. In addition, streams can be formatted using manipulators. These are simply objects that you insert into the stream with the usual <<operator, which modify how the stream works for any subsequent objects inserted into it. Some useful manipulators include:  boolalpha: Displays Booleantrueandfalsevalues as text.noboolalphareturns the stream to the default setting, which displaystrueas1andfalseas0.showpos: Displays the+sign on positive numbers.noshowposreturns the stream to the default setting, which only displays the-sign on negative numbers, with no sign for positive numbers.hex: Displays numbers in hexadecimal.decreturns the stream to the default setting, which displays numbers in decimal.fixed: Displays floating point numbers with a fixed number of digits as given byprecision, adding zeros after the decimal if necessary.defaultfloatreturns the stream to the default setting, which does not add zeros.scientific: Displays floating point numbers in scientific notation.defaultfloatreturns the stream to the default setting.showpoint: Displays the decimal point for all floating point numbers, including whole numbers. It also adds trailing zeros to all numbers up to the number of digits given byprecision.noshowpointreturns the stream to the default setting.uppercase: Displays the letters in hexadecimal numbers and scientific notation in uppercase.nouppercasereturns the stream to the default setting, which displays these letters in lowercase.left: Adjusts the output to the left.rightreturns the stream to the default setting, which adjusts to the right. Used in conjunction withwidth. The following program demonstrates formatting with streams: #include <iostream>
using namespace std;
int main()
{
    cout << true << '\n' // 1
         << boolalpha
         << true << '\n' // true
         << noboolalpha << '\n';
    cout << 2 << ", " << -2 << '\n' // 2, -2
         << showpos
         << 2 << ", " << -2 << '\n' // +2, -2
         << noshowpos << '\n';
    cout << 2.0 << ", " << 2.1 << '\n' // 2, 2.1
         << showpoint
         << 2.0 << ", " << 2.1 << '\n' // 2.00000, 2.10000
         << noshowpoint << '\n';
    cout << 1234.56 << '\n' // 1234.56
         << fixed
         << 1234.56 << '\n' // 1234.560000
         << scientific
         << 1234.56 << '\n' // 1.234560e+03
         << uppercase
         << 1234.56 << '\n' // 1.234560E+03
         << nouppercase << defaultfloat << '\n';
    cout.precision(5);
    cout << 1.23456789 << '\n'; // 1.2346
    cout.precision(10);
    cout << 1.23456789 << '\n'; // 1.23456789
    cout.precision(20);
    cout << 1.23456789 << '\n'; // 1.2345678899999998901
    cout.precision(6);
    cout << '\n';
    cout << 255 << '\n' // 255
         << hex
         << 255 << '\n' // ff
         << uppercase
         << 255 << '\n' // FF
         << nouppercase << dec << '\n';
    cout << "| ";
    cout << 123 << " |" << '\n'; // | 123 |
    cout << "| ";
    cout.width(10);
    cout << 123 << " |" << '\n'; // |        123 |
    cout << "| ";
    cout << left;
    cout.width(10);
    cout << 123 << " |" << '\n' // | 123        |
         << right << '\n';
}
 Note that the member function precisionandwidthcan be replaced with the manipulatorssetprecisionandsetwrespectively, but only if you include the header file<iomanip>. This is especially useful for setting the width, since it allows you to do everything in one line. Here is an example: #include <iomanip>
#include <iostream>
using namespace std;
int main()
{
    cout << setprecision(5)
         << 1.23456789 << '\n' // 1.2346
         << setprecision(10)
         << 1.23456789 << '\n' // 1.23456789
         << setprecision(20)
         << 1.23456789 << '\n' // 1.2345678899999998901
         << '\n';
    cout << "| " << 123 << " |" << '\n'                      // | 123 |
         << "| " << setw(10) << 123 << " |" << '\n'          // |        123 |
         << "| " << left << setw(10) << 123 << " |" << '\n'; // | 123        |
}
 Finally, note that these manipulators also work for input; for example, sending hexintocinwill instruct it to expect numbers to be input in hexadecimal. | 
| In general, any type of input and output in C++ is done using streams. We have seen above that coutbelongs to the classostream(output stream). Similarly,cinbelongs to the classistream(input stream). Both are defined in the header file<iostream>. File input and output in C++ is also handled using streams: input using ifstream(input file stream), output usingofstream(output file stream), or both input and output usingfstream. These are defined in the header file<fstream>. To open a file, we create an object of the desired type and initialize it with the file name. For example: ifstream input("input.txt");
 or: ofstream output("output.txt");
 We can check if the file was opened correctly using the member function is_open(): ifstream input("input.txt");
if (!input.is_open())
    cout << "Error opening file!";
 When the object goes out of scope, its destructor closes the file automatically. However, it is a good programming practice to close files manually nonetheless, using the member function close, e.g.input.close(). Once we opened a file as a stream, we can read and write to it using the <<and>>operators, just as forcoutandcin. For example: #include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
    ifstream input("main.cpp");
    if (!input.is_open())
    {
        cout << "Error opening input file!";
        return -1;
    }
    ofstream output("out.txt");
    if (!output.is_open())
    {
        cout << "Error opening output file!";
        return -1;
    }
    string s;
    while (input >> s)
        output << s << '\n';
    output.close();
    input.close();
}
 Note that in the whileloop,input >> swill befalsewhen we input something that is not astring, but that will only happen when we reach the end of the file, since anything can be astring. If the name of the source file for this program ismain.cpp, then the fileout.txtwill contain the following output: #include
<fstream>
#include
<iostream>
(etc...)
 The reason is that strings are expected to be separated by whitespace characters, which include spaces, tabs, and newlines, so #includeand<fstream>in the line#include <fstream>are actually considered to be separate strings. We can fix that in one of two ways:  Read individual characters using get.Read individual lines using getline. Here is a program that uses get(we now output to the terminal instead of a file, for simplicity): #include <fstream>
#include <iostream>
using namespace std;
int main()
{
    ifstream input("main.cpp");
    if (!input.is_open())
    {
        cout << "Error opening file!";
        return -1;
    }
    char c;
    while (input.get(c))
        cout << c;
    input.close();
}
 Note that you can't just write input >> c(as you would for thecinstream), because the>>operator reads formatted input, so it will automatically skip whitespace characters. Therefore, for example,#include <fstream>will become#include<fstream>. On the other hand,getreads unformatted input, so it treats all characters the same, including whitespace. We can similarly useputto put just one character to an output stream. Using getlineas a member function of anifstreamobject is not recommended, since we have to specify a maximum number of characters to read, and then store them in an old-fashioned C-style string, i.e. an array ofchars. That is, we need to replace the read loop by something like char s[100];
while (input.getline(s, 100)) // Limited to reading strings of up to 100 characters!
    cout << s << '\n';
 The problem is that if a line happens to have more than 100 characters, then getlinewill not read the entire line. This is a problem that we would have had to deal with in C; luckily, in C++ we have the modernstringclass, which makes everything much easier! Instead of using getlineas a member function of the classifstream, we can use a differentgetlinethat is defined in the<string>header file. This function takes a stream as its first argument, and a string as its second argument. Here is the full code: #include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
    ifstream input("main.cpp");
    if (!input.is_open())
    {
        cout << "Error opening file!";
        return -1;
    }
    string s;
    while (getline(input, s))
        cout << s << '\n';
    input.close();
}
 | 
| The following modes can be specified as the second argument in the initialization list of a file stream:  ios::in: Read.ios::out: Write.ios::app: Append.ios::ate: At end; opens the file and then seeks to the end of the file. The difference between ios::appandios::ateis thatios::apponly allows you to write at the end of the file, whileios::ateallows you to seek to an earlier position; see below.ios::binary: Binary mode; see below.ios::trunc: Truncate the file to zero length. These modes can be combined with the |(bitwise or) operator. For example,ios::in | ios::outindicates opening for both reading and writing.  The default mode for ifstreamisios::in.The default mode for ofstreamisios::out | ios::trunc.The default mode for fstreamisios::in | ios::out.  Warning: If an existing file is opened for output with the default settings, its contents will be permanently destroyed with no way to recover them!  The following program illustrates overwriting, truncating, and appending to a file: #include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
    string filename = "out.txt";
    // ofstream default mode: Existing file contents will be overwritten and truncated (i.e. destroyed).
    ofstream output(filename);
    if (!output.is_open())
    {
        cout << "Error opening file " << filename << "!\n";
        return -1;
    }
    output << "12345";
    output.close();
    // ofstream with ios::app: Appends to what we wrote previously, without overwriting or truncating.
    // Note that the member function open() can be used to open another file with the same stream object.
    output.open(filename, ios::app);
    output << "67890";
    output.close();
    // fstream default mode: Existing file contents will be overwritten but NOT truncated.
    fstream inout(filename);
    if (!inout.is_open())
    {
        cout << "Error opening file " << filename << "!\n";
        return -1;
    }
    // The characters "123" will be overwritten, but the rest will remain untouched.
    inout << "ABC";
    inout.close();
    ifstream input(filename);
    if (!input.is_open())
    {
        cout << "Error opening file " << filename << "!\n";
        return -1;
    }
    string s;
    input >> s;
    cout << s; // Prints "ABC4567890"
    input.close();
}
 | 
| To seek to a different position in the file, we can use the member functions seekg(seek get) for input files orseekp(seek put) for output files. The syntax is: file.seekg(offset, direction);
 And similarly for seekp.directionspecifies where theoffsetis calculated with respect to:  ios::beg: The offset is the position relative to the beginning of the file. This is the default.ios::cur: The offset is the position relative to the current position.ios::end: The offset is the position relative to the end of the file. Here is an example: #include <fstream>
#include <iostream>
#include <string>
using namespace std;
void print_current_char(ifstream &input)
{
    char c;
    input.get(c);
    cout << c;
}
int main()
{
    ifstream input("input.txt"); // Contents of input file: 1234567890ABCDEFGHIJ
    if (!input.is_open())
    {
        cout << "Error opening file!";
        return -1;
    }
    print_current_char(input); // Prints "1"
    print_current_char(input); // Prints "2"
    input.seekg(0);            // Seeks to the beginning of the file
    print_current_char(input); // Prints "1" again
    input.seekg(10);           // Seeks to position 10 from the beginning of the file
                               // Equivalent to input.seekg(10, ios::beg);
    print_current_char(input); // Prints "A"
    input.seekg(5, ios::cur);  // Seeks 5 characters ahead of the current position
    print_current_char(input); // Prints "G"
    input.seekg(-2, ios::end); // Seeks to 2 characters before the end of the file
    print_current_char(input); // Prints "I"
    input.close();
}
 For ofstreamwe would useseekpinstead. If a file is opened for both input and output withfstream, you can use either one. | 
| We have seen two types of streams so far: the terminal or "standard" input/output (istream,ostream) and files (ifstream,ofstream). A string stream is a stream that allows you to read or write into a string stored in memory the same way you do with any other stream. String streams are defined in the header file<sstream>and correspond to the typesistringstreamfor input,ostringstreamfor output, andstringstreamfor both input and output. Essentially, you use string streams whenever you want to take advantage of the functionality of a stream. For example, streams have well-defined input >>and output<<operators, and these operators work on string streams as well, which means we can use a string stream to write formatted data into a string. The actual string can then be accessed using the member functionstr. This is illustrated in the following program: #include <iostream>
#include <sstream>
#include <string>
using namespace std;
int main()
{
    ostringstream out;
    out << "Here are some numbers: " << 0.5 << ", " << 7 << ", " << hex << 255 << '\n';
    string s = out.str();
    cout << s; // Prints "Here are some numbers: 0.5, 7, ff"
}
 This is especially convenient when we want to first process all the data in memory, and then output it all at once, either to the terminal or to a file. Another convenient usage of string streams is for converting a list of numbers separated by spaces into a vector: #include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
ostream &operator<<(ostream &out, const vector<double> &v)
{
    out << '(';
    for (uint64_t i = 0; i < v.size() - 1; i++)
        out << v[i] << ", ";
    out << v[v.size() - 1] << ')';
    return out;
}
vector<double> read_numbers(const string &in)
{
    vector<double> v;
    string s;
    istringstream string_stream(in);
    try
    {
        while (getline(string_stream, s, ' '))
            v.push_back(stod(s));
    }
    catch (const invalid_argument &e)
    {
        throw invalid_argument("Expected a number!");
    }
    catch (const out_of_range &e)
    {
        throw out_of_range("Number is out of range!");
    }
    return v;
}
int main()
{
    try
    {
        cout << read_numbers("1 5.7 3.2 18.99"); // Prints "(1, 5.7, 3.2, 18.99)"
    }
    catch (const exception &e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 The operator<<overload for vectors is the same one we used above. In the functionread_numbers(), we used the fact thatgetlinetakes a delimiter character as its third argument; by default this argument is'\n', so it reads until it reaches the end of the line, but if we replace it with' 'it will instead read until it reaches a space character. We loop on each substring read in this way until the end of the string stream is reached, and we convert each substring into adoublewith the functionstod(string to double). Of course, we need to validate the input string, as it may not be of the appropriate form. According to the C++ reference, stodcan throw two exceptions:invalid_argumentif it encountered something other than a number, orout_of_rangeif the value of out of the range of thedoubledata type (as we found above, the range is roughly between 2.2e-308 and 1.8e+308). We could catch these exceptions directly, but the problem is that the error messages obtained by what()are not very illuminating (on my computer the error is simply"stod".) Therefore, we catch these exceptions insideread_numbers(), and then throw the same exceptions with more informative error messages. You can test this by putting some letters inside the string (which will throwinvalid_argument), or a large number such as1e500(which will throwout_of_range). | 
| The computer's memory is usually much faster than any hard drives. Therefore, the data that is written to a file stream by the program will sometimes be automatically stored in a memory buffer before it is actually written to the disk. It is possible that data from many different write operations will be kept in the buffer. At some point the data will finally be written to the file, all at once. This is called flushing the buffer. Although using a buffer improves performance, it also means that if the program crashes, the crash could happen before the buffer is flushed. In this case, the data that was supposed to be written to the file (including the results of your very complicated 20-hour calculation!) may be lost. The buffer can be flushed manually, either by sending the manipulator flushto the output stream or by using the member functionflush(). In addition, the manipulatorendlcan be used to send a newline character\nand then flush the buffer. However,endlshould not be used every time you want to send a newline character; if you flush the buffer after every single line you write to the output stream, the program may run slower due to continuously accessing the disk. It's better to just use\nand, if desired, flush the buffer manually usingflush. Generally, you should let the buffer be flushed automatically to ensure maximum performance. However, to prevent data loss, it is a good idea to flush the buffer manually after writing important data such as the result of a long calculation. | 
| One example of an I/O error that you may encounter happens when your program expects to read data of one type, but gets another type instead. This is especially common when getting input from the user via the terminal. For example, the user may misunderstand the instructions and write letters when asked for a number. However, as I stressed before, you should generally not get input from the terminal when you are doing scientific programming. All the input should be taken from files and/or from command line arguments, for two reasons:  If your program does calculations that take hours or even days, you can't expect the user to sit by the computer for the entire run time of the program in order to input data.Your program will often be executed multiple times with different data each time. This can be automated using a script, but only if the program takes its data from a file; again, you can't expect the user to manually input different data each time the program runs. However, even when reading data from a file, it is possible that the file will not be formatted correctly, especially if the file was manually written by the user (rather than being automatically generated by another program). C++ provides error handling for streams using the following member functions:  goodreturnstrueif everything is okay.badreturnstrueif a fatal error has occurred. This includes, for example, the file no longer being accessible.failreturnstrueif a non-fatal error has occurred. This includes, for example, finding a letter when expecting a number. Note thatbadimpliesfail, but not the other way around.eofreturnstrueif the end of the file has been reached.clearrestores to stream to agoodstate once the error has been handled by the program. Here is an example of simple error checking: #include <fstream>
#include <iostream>
using namespace std;
int main()
{
    ifstream input("input.txt");
    if (!input.is_open())
    {
        cout << "Error opening file!";
        return -1;
    }
    double n = 0;
    while (input >> n)
        cout << "Found a number: " << n << ".\n";
    if (input.eof())
        cout << "Reached end of file.\n";
    else if (input.fail())
        cout << "Encountered input that was not a number.\n";
}
 If the file input.txtonly contains numbers (separated by spaces or newlines), these numbers will be displayed on the screen until the input reaches the end of the file, in which caseinput.eof()will betrue. However, ifinput.txtcontains something that is not a number at some point, thewhileloop will terminate prematurely, andinput.fail()will betrue. In addition, the C++ standard library offers the following character classification functions in the header file <cctype>, which returntrueif the input argument is a character of the indicated type:  islower(): Lowercase lettersabcdefghijklmnopqrstuvwxyz.isupper(): Uppercase lettersABCDEFGHIJKLMNOPQRSTUVWXYZ.isdigit(): Digits0123456789.isxdigit(): Hexadecimal digits0123456789abcdefABCDEF.ispunct(): Punctuation characters!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~.isalpha(): Alphabet characters, which includes lowercase letters and uppercase letters.isalnum(): Alphanumeric characters, which includes lowercase letters, uppercase letters, and digits.isprint(): Any printable character, which includes lowercase letters, uppercase letters, digits, punctuation characters, and space' '.isspace(): Whitespace characters, which includes space' ', form feed'\f', new line'\n', carriage return'\r', horizontal tab'\t', and vertical tab'\v'. As a side note, the same header file also provides the functions tolower()to convert a character to lowercase andtoupper()to convert a character to uppercase. Here is an example of a program that checks whether its input is a word (i.e. only composed of letters) or not: #include <fstream>
#include <iostream>
#include <cctype>
#include <string>
using namespace std;
bool is_word(const string &s)
{
    for (const char &c : s)
        if (!isalpha(c))
            return false;
    return true;
}
int main()
{
    ifstream input("input.txt");
    if (!input.is_open())
    {
        cout << "Error opening file!";
        return -1;
    }
    string s;
    while (input >> s)
        if (is_word(s))
            cout << "Found a word: " << s << ".\n";
        else
            cout << "Not a word: " << s << ".\n";
}
 So, what should your program do if invalid input was supplied by the user? In scientific programming, usually the best thing to do is to notify the user that the input is invalid, and then just terminate the program. Your program shouldn't try to guess what the user meant and fix their mistakes for them (like Google fixing your typos in searches), because that may lead to running the program with the wrong input. Scientific data analysis and calculations can sometimes run for days at a time, and you don't want to do that only to find out in the end that the user actually wanted to do something else. | 
| Essentially, the only difference between binary mode ios::binary(see above) and the default file I/O mode, or text mode, is that binary mode treats all characters equally, while text mode treats newline characters (and only newline characters) in an OS-specific way, so if you write\n(newline) to a file, the program may actually end up writing\r(carriage return) or\r\n, depending on the operating system. This is the only difference as far as the C++ file stream modes are concerned. Apart from that, there is a more fundamental difference in the way data is stored inside a file, which has nothing to do with ios::binary. Files are composed of bytes, which have 8 bits each. Each character in a string usually takes up one byte (it can take more than that if you are using wide character formats, which would happen, for example, if you use Unicode characters). The number of bytes taken up by other data types is equal to the bit width of the type, so for example,int32_twill take up 4 bytes whileint64_twill take up 8 bytes. When you write the 32-bit integer 1 as text, it will take up only 1 byte, because you just need to write the character 1. However, if you write it as binary, it will take up 4 bytes. On the other hand, the 32-bit integer 1234567890 will take up 10 bytes as text, one byte for each of the digits, but still only 4 bytes as binary. Many file formats, such as image (e.g. PNG), audio (e.g.MP3), video (e.g.MP4), and so on, are binary formats. Such files have the benefit of being smaller in size, but they are not readable by humans. In scientific computing, we generally want files to be human-readable, so it is recommended to never use binary formats unless you really have to. Generating large files is usually not a problem, since files can be easily compressed, for example using ZIP. (Of course, the ZIP file itself will be a binary file.) Furthermore, all modern operating systems can be configured to automatically store files in compressed format "behind the scenes", so that the file is human-readable when it is opened by any program, but actually takes up much less space on the disk. If you want to get input from a specific type of binary file, then there are open-source C++ libraries that you can #includein your program and will take care of reading the file for you; you can easily find such libraries online. Generally, you don't need to write your own code to handle binary files, unless you are dealing with a binary format for which a C++ library does not already exist, or you want low-level access to the contents of the file. As an example, let us consider how we might read and write a vector<double>to and from a file. C++ doesn't provide that functionality on its own, so we have to do it ourselves. We will first do this in text format, and then in binary format. If we want to read and write vectors in human-readable format (which, again, is the preferred way in scientific programming!), then we can simply overload <<and>>with the appropriate behavior: #include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
ostream &operator<<(ostream &out, const vector<double> &v)
{
    for (const double &i : v)
        out << i << ' ';
    out << '\n';
    return out;
}
istream &operator>>(istream &in, vector<double> &v)
{
    string line, s;
    getline(in, line);
    istringstream st_stream(line);
    while (getline(st_stream, s, ' '))
        v.push_back(stod(s));
    return in;
}
int main()
{
    vector<double> v = {1.2, 3.4, 5.6, 7.8, 9.0};
    vector<double> w = {3, 5, 7, 9};
    cout << "v: " << v
         << "w: " << w;
    string filename = "vectors.txt";
    ofstream output(filename);
    if (!output.is_open())
    {
        cout << "Error opening file " << filename << " for output!";
        return -1;
    }
    output << v;
    output << w;
    output.close();
    vector<double> a, b;
    ifstream input(filename);
    if (!input.is_open())
    {
        cout << "Error opening file " << filename << " for input!";
        return -1;
    }
    input >> a;
    input >> b;
    input.close();
    cout << "a: " << a
         << "b: " << b;
}
 Input and output of binary files in C++ is somewhat awkward. Here is one way to do it for vector<double>: #include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
ostream &operator<<(ostream &out, const vector<double> &v)
{
    for (const double &i : v)
        out << i << ' ';
    out << '\n';
    return out;
}
void write_binary_vector(ofstream &out, const vector<double> &v)
{
    uint64_t s = v.size();
    out.write(static_cast<char *>((void *)&s), sizeof(uint64_t));
    for (const double &i : v)
        out.write(static_cast<char *>((void *)&i), sizeof(double));
}
vector<double> read_binary_vector(ifstream &in)
{
    uint64_t s = 0;
    in.read(static_cast<char *>((void *)&s), sizeof(uint64_t));
    vector<double> v(s);
    for (uint64_t i = 0; i < s; i++)
        in.read(static_cast<char *>((void *)&v[i]), sizeof(double));
    return v;
}
int main()
{
    vector<double> v = {1.2, 3.4, 5.6, 7.8, 9.0};
    vector<double> w = {3, 5, 7, 9};
    cout << "v: " << v
         << "w: " << w;
    string filename = "vectors.bin";
    ofstream output(filename, ios::binary);
    if (!output.is_open())
    {
        cout << "Error opening file " << filename << " for output!";
        return -1;
    }
    write_binary_vector(output, v);
    write_binary_vector(output, w);
    output.close();
    vector<double> a, b;
    ifstream input(filename, ios::binary);
    if (!input.is_open())
    {
        cout << "Error opening file " << filename << " for input!";
        return -1;
    }
    a = read_binary_vector(input);
    b = read_binary_vector(input);
    input.close();
    cout << "a: " << a
         << "b: " << b;
}
 In this program, we are using the fstreammember functionsread()andwrite()to read and write binary data. They take two arguments: the first is a pointer to a C-style string, i.e. an array ofchars, and the second is the length of the string, i.e. the number of bytes it takes up in memory. They then read or write from the string into the file or vice versa. Here we are using a trick: instead of the first argument being a pointer to a string, we give a pointer to the raw data we want to write, which we cast to look like a pointer to a string using static_cast. The syntaxstatic_cast<new_type> expressioncastsexpressioninto the data typenew_type. So in the statement static_cast<char *>((void *)&s)
 we first cast &s, which is a pointer to auint64_t, intovoid *in order to "hide" the fact that it's a pointer to auint64_t, and then usestatic_cast<char *>to further cast it into a pointer to achar *, i.e. a C-style string. The end result is that the actual memory representation ofsis read from or written to the file. Similarly,static_cast<char *>((void *)&i)does the same for&i, which is a pointer to adouble. In text mode, we could simply format our file such that a newline character indicates the end of the vector, and thus we can infer the length of the vector from the number of elements in the line. However, in binary mode, there is no such thing as a newline. Everything we write is binary, and the binary representation of a newline character is also the binary representation of many other things - including a specific number represented as a double- so there is no way to distinguish a newline from actual vector elements. The way I chose to solve this problem here (which is certainly not the only way) is to first read or write the length sof the vector, which will take up 8 bytes since it's auint64_t, and then read or write the actual contents of the vector. So the file format is: (length of vector 1) (elements of vector 1) (length of vector 2) (elements of vector 2) and so on. If you want the see the binary data that was written to the file, you can install the Microsoft Hex Editor extension for Visual Studio Code, and use it to open the file vectors.binby right-clicking and selecting Open With... > Hex Editor. What you will find is the actual binary representation of each floating-point number, which we discussed above. For more information about file I/O in C++, please refer to the C++ reference or Microsoft's C++ reference. | 
|  | 
| Normally, privatemembers of a class are only accessible to member functions of that class, and inaccessible to the rest of the program. However, functions external to a class can be given access to itsprivatemembers by declaring them within the class with thefriendkeyword. Such functions are called friend functions. A friend function is not considered a member function of the class; it is just an external function that is given special access privileges. It can be declared anywhere in the class declaration, and the declaration is unaffected by access control keywords such as privateorpublic. Note that a function cannot just declare itself as a friend function to any class; only the actual implementation of the class can do that. Otherwise, this would undermine encapsulation, as it would be trivial to access privatemembers of any class. Friend functions are used when we want to implement a certain function independently as an external function, rather than as a member function. For example, consider the pointclass we defined earlier. Theprint()andscale()member functions need to access theprivatemembersxandy. If we want to implement them as non-member functions, we can do this by defining them asfriendfunctions as follows: #include <iostream>
using namespace std;
class point
{
    friend void print(const point &);
    friend void scale(point &, const double &);
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
private:
    double x = 0, y = 0;
};
void print(const point &p)
{
    cout << '(' << p.x << ", " << p.y << ")\n";
}
void scale(point &p, const double &s)
{
    p.x *= s;
    p.y *= s;
}
int main()
{
    point p(1, 2);
    print(p); // (1, 2)
    scale(p, 5);
    print(p); // (5, 10)
}
 If you delete the two lines starting with the keyword friend, you will get an error from the compiler saying thatp.xandp.yare inaccessible. Of course, we could have also defined the printfunction as an external function without it being a friend function, if we added member functionsget_xandget_ythat could be used to read the coordinates, but not change them: class point
{
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    double get_x() const { return x; }
    double get_y() const { return y; }
private:
    double x = 0, y = 0;
};
 In this case, we could have written printas follows: void print(const point &p)
{
    cout << '(' << p.get_x() << ", " << p.get_y() << ")\n";
}
 However, this solution is not always possible, for two reasons:  Functions often need to not only read but also modify private member variables, as is the case for scalein our example.Encapsulation sometimes requires that the privatedata of a class is inaccessible not just for writing, but also for reading. For example, if the user can read a pointer, then they can directly modify the internal data it points to, potentially violating the invariant. | 
| A common reason to define functions as friendis when overloading operators as non-member functions. For example, here is a version of our previous program which works more intuitively using overloaded operators: #include <iostream>
#include <cmath>
using namespace std;
class point
{
    friend ostream &operator<<(ostream &, const point &);
    friend point operator*(const double &, const point &);
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
private:
    double x = 0, y = 0;
};
ostream &operator<<(ostream &out, const point &p)
{
    out << '(' << p.x << ", " << p.y << ")\n";
    return out;
}
point operator*(const double &s, const point &p)
{
    return point(s * p.x, s * p.y);
}
point operator*(const point &p, const double &s)
{
    return s * p;
}
point operator*=(point &p, const double &s)
{
    p = s * p;
    return p;
}
int main()
{
    point p(1, 2);
    cout << p; // (1, 2)
    point q = 2 * p;
    cout << q; // (2, 4)
    p *= 5;
    cout << p; // (5, 10)
}
 Note that only the <<and*(double, point)overloads needed to be defined asfriend. The other two overloads simply call the*(double, point)overload, so they don't need to access any private members. In the *(double, point)overload itself, notice that we constructed the scaledpointobject within thereturnstatement itself. This means we don't construct a temporary object and then overwrite it, which would be slower as it would involve first initializing the object and then overwriting its contents. Later I'll show you how to avoid such redundant initializations, which can improve performance (but also introduce bugs if you're not careful). (In the case of the vectorandmatrix+/-/*overloads we accepted the argument by value, thus creating a temporary copy, and then performed the addition/subtraction/multiplication on that copy. This is a bit more optimal than creating a temporary copy pointlessly initialized to zeros and then overwriting those zeros, but not by much. Again, later we'll see how to do this in the most optimal way.) A friend function doesn't have to be an independent function; it can also be a member function of another class. For example, in the following code, we defined a class named printerwhich can be used to store anyostreamobject (such ascout) and print to it. The member functionprint_pointprints apointinto the specified stream, but it needs access to the coordinates of the point, so it must be declared a friend function: #include <iostream>
using namespace std;
class point;
class printer
{
public:
    printer(ostream &_out) : out(_out) {}
    void print_point(const point &);
private:
    ostream &out;
};
class point
{
    friend void printer::print_point(const point &);
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
private:
    double x = 0, y = 0;
};
void printer::print_point(const point &p)
{
    out << '(' << p.x << ", " << p.y << ")\n";
}
int main()
{
    point p(1, 2);
    printer r(cout);
    r.print_point(p); // (1, 2)
}
 Notice that we had to first declare pointusing forward declaration, so thatprinterknows it exists - otherwise we could not have declaredprint_point, since it takes apointas input. In addition to friend functions, we can also declare an entire class as a friend class. If Bis a friend class ofA, then all member functions ofBare automatically friend functions ofA. However, note that class friendship is not mutual; ifBis a friend class ofA, this doesn't meanAis a friend class ofB, unless we explicitly declare it as such. Similarly, class friendship is not transitive; ifBis a friend class ofAandCis a friend class ofB, this doesn't meanCis a friend class ofA. An as example, if we change the line friend void printer::print_point(const point &);
 in the previous example to friend class printer;
 then all member functions of printer, includingprint_pointand any other functions we may define in the future, will automatically be friend functions ofpoint. Friend functions must be used whenever a function needs to access the privatemembers of more than one class, which would otherwise be impossible since a function can only be a member function of at most one class. Sinceprint_pointcannot be a member function of bothpointandprinter, it must either be a member function of one of them and a friend of the other, or an external function and a friend of both. In the example above, we used the first option. Here is an example of the second option: #include <iostream>
using namespace std;
class point;
class printer
{
    friend void print_point(const printer &, const point &);
public:
    printer(ostream &_out) : out(_out) {}
private:
    ostream &out;
};
class point
{
    friend void print_point(const printer &, const point &);
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
private:
    double x = 0, y = 0;
};
void print_point(const printer &r, const point &p)
{
    r.out << '(' << p.x << ", " << p.y << ")\n";
}
int main()
{
    point p(1, 2);
    printer r(cout);
    print_point(r, p); // (1, 2)
}
 | 
| A derived class is a class that is derived from another class, called the base class. The derived class inherits the members of the base class, while also adding or replacing members as needed. An object in the derived class is considered to be an object in the base class, but not vice versa. We have already seen examples of derived classes when we talked about input and output streams. For example, the output file stream class ofstreamis derived from the output stream classostream. This means that member functions and overloaded operators ofostreamcan be used with anyofstreamobject, butofstreamprovides additional functionality thatostreamdoesn't have, namely handling files. In particular, since our printerclass defined above takes anostreamobject, it can also take anofstreamobject. You can verify this by adding#include <fstream>and replacing the lineprinter r(cout);with ofstream f("test.txt");
printer r(f);
 Similarly, an output string stream ostringstreamis also derived fromostream, soprintercan take anostringstreamas an argument as well. Importantly, derived classes cannot access privatemembers of their base class, onlypublicmembers. This has to be the case, because otherwise, any user of your class could easily gain access to all of itsprivatemembers simply by deriving a class from it, which would render the labelprivateessentially meaningless and go against the principle of encapsulation. The syntax for defining a derived class is: class derived_class : access_mode base_class
{
    // ...
}
 access_modecontrols how members of the base class will be accessible as members of the derived class. If the member isprivatein the base class, then it is always inaccessible outside of the base class, regardless ofaccess_mode. However, if the member ispublicin the base class, then its access mode as a member of the derived class will beaccess_mode. So ifaccess_modeispublic, thenpublicmembers will staypublic, but ifaccess_modeisprivate, thenpublicmembers will be converted toprivate.
 This is illustrated in the following code: class base_class
{
public:
    int32_t public_member = 0;
private:
    int32_t private_member = 0;
};
class derived_public_class : public base_class
{
};
class derived_private_class : private base_class
{
};
int main()
{
    base_class base;
    derived_public_class der_pub;
    derived_private_class der_priv;
    base.public_member = 1;  // Allowed: member is public
    base.private_member = 1; // NOT allowed: member is private
    der_pub.public_member = 1;  // Allowed: member is still public
    der_pub.private_member = 1; // NOT allowed: member is still private
    der_priv.public_member = 1;  // NOT allowed: member is now private
    der_priv.private_member = 1; // NOT allowed: member is private
}
 In most cases, your derived class will simply add new specialized functionality on top of the base class, so all publicmembers of the base class should still be accessible aspublicmembers of the derived class. Therefore,access_modeis usually set topublic. That way, both classes can share the same interface. The derived class must include its own constructors. In many cases, these will simply refer to the constructors of the base class. For example, let us add a new class, vec(for vector), which derives frompoint. A vector is not a point, but rather, an arrow connecting two points. Avecis assumed to be a vector from the origin to the coordinates(x, y). Thus, it has a property that apointdoes not have: a magnitude, which can be calculated using the member functionmagnitude. #include <cmath>
#include <iostream>
using namespace std;
class point
{
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    void print() const
    {
        cout << '(' << x << ", " << y << ")\n";
    }
    double x = 0, y = 0;
};
class vec : public point
{
public:
    vec(const double &_x, const double &_y) : point(_x, _y) {}
    double magnitude() const
    {
        return sqrt(x * x + y * y);
    }
};
int main()
{
    vec v(3, 4);
    v.print();             // (3, 4)
    cout << v.magnitude(); // 5
}
 Note that:  vec's constructor, simply calls the constructor ofpoint. In this case, there is nothing to add to the constructor, since we are just adding new functionality on top of the base class.vecinherited the member functionprint()frompoint, so we can print the vector usingv.print(), as ifvwas apoint.vecdefines the new member functionmagnitude(), which only works forvecobjects. Apointobject will not have access tomagnitude(). Note that friendship is not inherited; if pointdeclares a function or class as a friend, they will not be inherited byvec. (Of course, here it doesn't matter since there are no private members.) | 
| Let us now recall our triangleclass defined above. An isosceles triangle is a special case of a triangle which has two sides of equal length. Therefore, we may want to derive a specialized classisoscelesfrom the base classtriangle. An equilateral triangle, in turn, is a special case of an isosceles triangle which has three sides of equal length. Therefore, we may also want to derive a specialized class equilateralfrom the classisosceles. In this case,isoscelesis both a derived class (oftriangle) and a base class (ofequilateral): #include <cmath>
#include <iostream>
#include <stdexcept>
using namespace std;
class triangle
{
public:
    triangle(const double &_a, const double &_b, const double &_c)
        : a(_a), b(_b), c(_c)
    {
        if ((a < 0) or (b < 0) or (c < 0))
            throw invalid_argument("Sides cannot be negative!");
        if ((a > b + c) or (b > c + a) or (c > a + b))
            throw invalid_argument("Triangle inequality must be satisfied!");
    }
    double area() const
    {
        const double s = (a + b + c) / 2;
        return sqrt(s * (s - a) * (s - b) * (s - c));
    }
    void print() const
    {
        cout << '(' << a << ", " << b << ", " << c << ")\n";
    }
private:
    double a = 0, b = 0, c = 0;
};
class isosceles : public triangle
{
public:
    // Construct an isosceles triangle with two sides equal to the first argument and the third side equal to the second argument.
    isosceles(const double &_two_sides, const double &_one_side)
        : triangle(_two_sides, _two_sides, _one_side) {}
};
class equilateral : public isosceles
{
public:
    // Construct an equilateral triangle with all three sides equal to the argument.
    equilateral(const double &_all_sides)
        : isosceles(_all_sides, _all_sides) {}
};
int main()
{
    cout << "Arbitrary triangle:\n";
    triangle t(3, 4, 5);
    t.print();                            // (3, 4, 5)
    cout << "Area: " << t.area() << '\n'; // Area: 6
    cout << "Isosceles triangle:\n";
    isosceles i(3, 4);
    i.print();                            // (3, 3, 4)
    cout << "Area: " << i.area() << '\n'; // Area: 4.47214
    cout << "Equilateral triangle:\n";
    equilateral e(3);
    e.print();                            // (3, 3, 3)
    cout << "Area: " << e.area() << '\n'; // Area: 3.89711
}
 Note that the member function area()was inherited all the way down fromtriangleto its "grandchild",equilateral. | 
| A class can be derived from more than one base class. The syntax for that is class derived_class : access_mode1 base_class1, access_mode2 base_class2, ...
{
    // ...
}
 In the following example we have a variation on the two classes pointandprinterwe defined previously. The classpointincludes the member variablesxandy, which store the coordinates of the point, and the member functionscale(), which scales the point. The classprinterincludes the member variableout, which stores an output stream, and the member functionprint_string(), which prints a string into the stream. We combine these two classes into one derived class, point_with_printer, which inherits the members of both classes. This class then defines a new member function,print_point(), which usesprint_string()to print the coordinates: #include <iostream>
#include <string>
using namespace std;
class point
{
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    void scale(const double &s)
    {
        x *= s;
        y *= s;
    }
    double x = 0, y = 0;
};
class printer
{
public:
    printer(ostream &_out) : out(_out) {}
    void print_string(const string &s)
    {
        out << s;
    }
    ostream &out;
};
class point_with_printer : public point, public printer
{
public:
    point_with_printer(const double &_x, const double &_y, ostream &_out)
        : point(_x, _y), printer(_out) {}
    void print_point()
    {
        print_string('(' + to_string(x) + ", " + to_string(y) + ")\n");
    }
};
int main()
{
    point_with_printer p(1, 2, cout);
    p.print_point(); // (1.000000, 2.000000)
    p.scale(3);
    p.print_string("After scaling by 3:\n");
    p.print_point(); // (3.000000, 6.000000)
}
 Note that we used to_string()to convertdoubleinto astring, so we can pass the coordinates as a string toprint_string. This also has the unfortunate side effect that the digits after the decimal point don't get truncated for integers, which the overloaded operator<<does butto_stringdoes not do. We could fix this e.g. by using a string stream, but we won't bother with that for this simple example. | 
| In addition to the labels publicandprivate, a third label,protected, can be used to make members accessible to derived classes while keeping them inaccessible to the rest of the program. Essentially,protectedis equivalent toprivate, except thatprotectedmembers of a class can also be accessed by any classes derived from it. The access_modespecified in the inheritance syntaxclass derived_class : access_mode base_classcan also beprotected. The general rule is that ifaccess_modeis more strict than the original access mode in the base class, then the new access mode in the derived class will be determined byaccess_mode, otherwise it stays the same. Therefore:  If the member is privatein the base class, then it is always inaccessible outside of the base class, regardless ofaccess_mode.If the member is protectedin the base class, then its access mode as a member of the derived class will beprivateifaccess_modeisprivate. Otherwise, it staysprotected.If the member is publicin the base class, then its access mode as a member of the derived class will beaccess_mode. As an example, consider the vecclass derived from thepointclass, as we defined above. Let's say that we want to hide the membersxandyfrom the rest of the program, but still keep them accessible tovec. Then we could define them asprotected: #include <cmath>
#include <iostream>
using namespace std;
class point
{
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    void print() const
    {
        cout << '(' << x << ", " << y << ")\n";
    }
protected:
    double x = 0, y = 0;
};
class vec : public point
{
public:
    vec(const double &_x, const double &_y) : point(_x, _y) {}
    double magnitude() const
    {
        return sqrt(x * x + y * y);
    }
};
int main()
{
    vec v(3, 4);
    v.print();             // (3, 4)
    cout << v.magnitude(); // 5
}
 If you try to access v.xorv.yin themain()function, you will not be able to, as they are inaccessible to any functions outside ofpointandvec. Also, if you changeprotectedtoprivateinsidepoint, then the member functionmagnitude()will not be able to accessxandy, so the program won't compile. Generally, it is best not to use protectedunless you really have to. If a member of your class isprotected, then anyone can access it simply by defining a class derived from your class, potentially breaking encapsulation and violating the invariant. The safest thing to do is to have derived classes only access an object's data viapublicmember functions that guarantee preservation of the invariant - just like the rest of this program. We could also, as we did above, create public member functions get_x()andget_y()that allow the user to only read the values, but not modify them. Nowmagnitude()can simply useget_x()andget_y(), so it doesn't need access toxandythemselves, and they can be madeprivate: #include <cmath>
#include <iostream>
using namespace std;
class point
{
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    void print() const
    {
        cout << '(' << x << ", " << y << ")\n";
    }
    double get_x() const { return x; }
    double get_y() const { return y; }
private:
    double x = 0, y = 0;
};
class vec : public point
{
public:
    vec(const double &_x, const double &_y) : point(_x, _y) {}
    double magnitude() const
    {
        return sqrt(get_x() * get_x() + get_y() * get_y());
    }
};
int main()
{
    vec v(3, 4);
    v.print();             // (3, 4)
    cout << v.magnitude(); // 5
}
 | 
| A derived class often replaces public member functions of the base class with new ones that have the same name, but additional or different functionality. In this case, the member functions that were replaced should be declared as virtualin the base class. Let us illustrate why this is needed. Consider the following program: #include <iostream>
using namespace std;
class point
{
public:
    point(const double &_x, const double &_y) : x(_x), y(_y) {}
    void print() const
    {
        cout << '(' << x << ", " << y << ")\n";
    }
    double x = 0, y = 0;
};
class complex_point : public point
{
public:
    complex_point(const double &_x, const double &_y) : point(_x, _y) {}
    void print() const
    {
        cout << x << " + " << y << "i\n";
    }
};
void print_point(const point &p)
{
    p.print();
}
int main()
{
    point p(1, 2);
    p.print(); // Prints "(1, 2)", as expected.
    complex_point c(3, 4);
    c.print(); // Prints "3 + 4i", as expected.
    print_point(c); // Prints "(3, 4)", because print_point() doesn't know that c is a complex_point!
}
 The class complex_pointoverrides the member functionprint()with another function that prints the coordinates as a complex number instead of a tuple. This works when we call the member function directly,c.print(), since the compiler knows thatcis acomplex_point. The function print_point()takes apointas an argument, and sincecomplex_pointis derived frompoint,print_point()can take acomplex_pointas an argument as well. However, since it assumes the argument is apoint, it calls theprint()member function forpoint, not forcomplex_point. Perhaps this could be solved by changing the argument to be a complex_point, but then the function won't work on a regularpoint! Remember that an object of a derived class counts as an object of the base class, but not vice versa. The solution is to simply add the keyword virtualto the definition ofprintinpoint: virtual void print() const
{
    cout << '(' << x << ", " << y << ")\n";
}
 This will instruct the program to track down exactly what kind of object is calling this member function, and use the correct version of the function for that particular object. You may be wondering why member functions are not virtualby default. The main reason is performance. If a function is notvirtual, then the compiler decides which version of it to call at compilation time, but if it isvirtual, then that decision is made at run time. Therefore,virtualintroduces additional overhead, which may hurt performance.  Warning: As we demonstrated here, not using the keyword virtualin the base class when redefining a member function in a derived class can lead to errors and unexpected behavior. Always make sure to usevirtualif needed. Finally, note that if we redefined a member function and we want to ensure the member function of the correct class is called, we can add the class name followed by ::as a prefix. So for example,c.point::print()will always print(3, 4)whilec.complex_point::print()will always print3 + 4i. Similarly, if any member function ofcomplex_pointwants to call the old version ofprint(), it can do so by simply callingpoint::print(). | 
| Now that we know how class inheritance works, we can easily create our own exceptions by deriving them either from the basic std::exceptionclass itself, or from classes derived from it, such asstd::invalid_argument. Let us do so for thetriangleclass as an example: #include <iostream>
#include <stdexcept>
using namespace std;
class triangle
{
public:
    triangle(const double &_a, const double &_b, const double &_c)
        : a(_a), b(_b), c(_c)
    {
        if ((a < 0) or (b < 0) or (c < 0))
            throw negative_sides();
        if ((a > b + c) or (b > c + a) or (c > a + b))
            throw triangle_inequality();
    }
    void print()
    {
        cout << '(' << a << ", " << b << ", " << c << ")\n";
    }
    class negative_sides : public invalid_argument
    {
    public:
        negative_sides() : invalid_argument("Sides cannot be negative!"){};
    };
    class triangle_inequality : public invalid_argument
    {
    public:
        triangle_inequality() : invalid_argument("Triangle inequality must be satisfied!"){};
    };
private:
    double a = 0, b = 0, c = 0;
};
int main()
{
    try
    {
        triangle t1(4, 2, 5);
        t1.print();
        triangle t2(6, -7, 8);
        t2.print();
        triangle t3(2, 2, 5);
        t3.print();
    }
    catch (const triangle::negative_sides &e)
    {
        cout << "Oops - used a negative side!\n";
    }
    catch (const triangle::triangle_inequality &e)
    {
        cout << "Oops - did not satisfy the triangle inequality!\n";
    }
}
 The exceptions we defined are very simple: they derive from invalid_argument, and they have default constructors that construct aninvalid_argumentobject with the desired error message. We then throw them by constructing a new object within thethrowstatement itself using the default constructor. Defining custom exceptions like this has several advantages:  The exceptions have their own unique names in the namespace of the triangleclass, so they do not collide with any other names defined elsewhere.Before, both exceptions were just invalid_argumentobjects with differentwhat()strings, so the only way the user could have distinguished between them was to check that string, which is not a good idea since the string is meant to be read by the end user, not by the program itself. Now, each exception has a unique name, so the user can catch each specific exception individually and do something different in each case. In the example above, the hypothetical user took advantage of this to print their own custom error message instead of thewhat()error message supplied by our class.On the other hand, since we derived our exceptions from invalid_argument, the user can still catch the parent classinvalid_argument, as in the previous version of thetriangleclass, if they don't need to distinguish between the two exceptions. | 
|  | 
|  | 
| The header <cmath>includes many common mathematical functions such asabs(absolute value),max(return the larger of two numbers),exp(exponential),log(logarithm),pow(power),sqrt(square root),sin(sine),cos(cosine),sinh(hyperbolic sine),cosh(hyperbolic cosine),erf(error function),tgamma(gamma function),ceil(ceiling),floor(floor), and many others. Since C++17, <cmath>also includes some special functions. Examples includeriemann_zeta()for the Riemann zeta functions,legendre()for the Legendre polynomials,beta()for the Euler beta function, andcyl_bessel_j()for (cylindrical) Bessel function of the first kind. Please see the C++ reference for more information. All of these functions are overloaded, so they can accept any floating-point or integer argument, similar to the ones in the C header file <tgmath.h>. For the full list, please see the C++ reference. | 
| The header file <numeric>provides some useful numeric algorithms, including:  gcd(a, b): Returns the greatest common divisor of the integersaandb.lcm(a, b): Returns the least common multiple of the integersaandb.midpoint(a, b)(since C++20): Returns the average of the two numbersaandb. This is equal to(a + b) / 2, butmidpointguarantees that no overflows occur ifa + bis greater than the maximum value of the appropriate data type. I will not be giving any examples, since these are pretty straightforward. You can read more about these algorithms in the C++ reference. There are more algorithms which I will list below, after we learn about iterators. | 
| The header file <complex>contains the classcomplex(more precisely, a class template), used to do calculations with complex numbers. A complex number can be declared using: complex<type> c;
 or complex<type> c(real, imaginary);
 Here, typeis the data type used to represent the real and imaginary parts. Typically you would usedouble, but it can be any type that has arithmetic operators overloaded. If therealandimaginaryparts are not specified, they will be initialized to default values (zero for numeric types). This class has all the operator overloads you would expect, such as comparison, addition, multiplication, and even >>and<<for stream input and output in the format(real,imaginary). It also has the member functionsrealandimag, used to access the real and imaginary parts respectively. The following non-member functions operate on complex numbers:  abs(z)returns the absolute value (magnitude) ofz, i.e. the r in z=reiθ.arg(z)returns the argument (phase angle) ofz, i.e. the θ in z=reiθ.norm(z)returns the squared magnitude ofz.conj(z)returns the complex conjugate ofz.real(z)andimag(z)return the real and imaginary parts ofz; equivalent toz.real()andz.imag().polar(r, theta)returns the complex number reiθ. In addition, common mathematical functions such as exp,sin,pow, and so on have overloads that let them work on complex numbers as well. Finally, if you add the statementusing namespace complex_literals, you can write imaginary numbers by simply adding anisuffix, so for example1.0 + 2.0iwill represent the corresponding complex number: #include <complex>
#include <iostream>
using namespace std;
using namespace complex_literals;
int main()
{
    complex<double> c = 1.0 + 2.0i;
    cout << c << '\n';                               // (1,2)
    cout << (c + 3.0 + 4.0i) * (5.0 + 6.0i) << '\n'; // (-16,54)
}
 Note that 1 + 2iwill not work, because1is interpreted as anintwhile2iis adouble. However,1.0 + 2iwill work. For more information about the complexclass, please see the C++ reference. | 
| C++20 added many useful mathematical constants in the header <numbers>. Examples includee(Euler's number),pi(pi), andphi(the golden ratio). These constants are defined under the namespacestd::numbers, so you access them using e.g.numbers::e,numbers::pi, andnumbers::phi. (You could also invokeusing namespace numbers, but that is not recommended since the names might clash with other names in your program!) For example: #include <numbers>
#include <iostream>
using namespace std;
int main()
{
    cout.precision(10);
    cout << "e = " << numbers::e << '\n';
    cout << "pi = " << numbers::pi << '\n';
    cout << "phi = " << numbers::phi << '\n';
}
 For the full list of available constants, please see the C++ reference. | 
|  | 
| Random numbers are frequently used in scientific computing, for example in simulations or when sampling values out of a certain probability distribution. Random number generators come in two forms: true random and pseudo-random. True random number generators usually rely on the external randomness of the physical environment and/or an appropriate piece of hardware to generate random numbers. For example, the source of randomness can be thermal noise, or even the user's keyboard and mouse movements. Most modern computers and operating systems only come with limited capabilities for generating true random numbers. The measure of how much randomness is available to a true random number generator is called entropy. Usually, only a few random numbers can be generated using the available pool of entropy before the generator has to "harvest" more of it. This means that generating many true random numbers is a slow process. Pseudo-random number generators are actually deterministic, which means they are not truly random. An algorithm uses an initial seed to produce a sequence of numbers whose distribution appears random, but the entire sequence is completely predetermined by the seed. However, generating pseudo-random numbers is much faster, and it turns out to be good enough for most uses, as long as you are able to reliably choose different seeds in each run. C++ inherits basic random number generation capabilities from C, in the header file <cstdlib>. However, the quality of that generator depends on the implementation, and there is no guarantee that a good algorithm is used. Therefore, the<cstdlib>random number generator is not recommended for scientific programming, and you should use the functionality provided in the header file<random>instead. The C++ header file <random>offers a true random number generator in the classrandom_device. In principle, there is no guarantee thatrandom_deviceproduces true random numbers, as that is up to the implementation. However, with the GCC compiler,random_deviceshould be a true random number generator on Windows and Linux-based operating systems. Since random_deviceis a true random number generator, it has a limited pool of entropy, and its performance is significantly degraded once that pool is exhausted. Therefore, unless we only need to generate very few numbers, we generally do not use it to generate random numbers directly. Instead, we usually use it to seed a deterministic pseudo-random number generator. In old programs, people often used the computer's clock as a seed, but that meant if two people ran the program at the same exact moment, they would get the same exact sequence of random numbers. When using random_deviceto seed a deterministic generator, you guarantee that the results will be unpredictable, since even though they are deterministic, the initial seed is truly random. To use random_device, we declare a new object: random_device name;
 Once we create it, we can use the following member functions:  The ()operator, i.e.name(), returns the next random number in the sequence. Usually, you will only use this once or twice.minandmaxreturn the minimum and maximum values that can be generated. As the generated values will be anunsigned32-bit integer,minis always0andmaxis always4294967295(or 232-1).entropy, in principle, returns an estimate of the bits of entropy left in the device. This is a floating-point number between0and32, with0indicating no entropy (i.e. the device is deterministic). However, in practice this number is usually meaningless. For example, GCC will always return a value of0, while MSVC will always return32, and in both cases this number is fixed and has nothing to do with the actual entropy of the generator. We demonstrate how to use random_devicein the following example: #include <iostream>
#include <random>
using namespace std;
int main()
{
    random_device rd;
    cout << "rd.entropy() = " << rd.entropy() << '\n';
    cout << "rd.min() = " << rd.min() << '\n';
    cout << "rd.max() = " << rd.max() << '\n';
    cout << "3 random numbers: " << rd() << ", " << rd() << ", " << rd() << '\n';
}
 Sample output: rd.entropy() = 0
rd.min() = 0
rd.max() = 4294967295
3 random numbers: 2795223888, 78377749, 1584923679
 | 
| As we said above, random_deviceshould be used to seed a pseudo-random number generator.<random>provides a variety of such generators, but the most commonly used one - in C++ and in general - is the Mersenne Twister algorithm, accessed via the classmt19937for unsigned 32-bit integers ormt19937_64for unsigned 64-bit integers. To use mt19937, we declare a new object and give the seed (fromrandom_device) as an argument to the constructor: mt19937 mt(seed);
 Once we create it, we can use the following member functions:  The ()operator, i.e.mt(), returns the next random number in the sequence. You will use this every time you need a new random number.minandmaxreturn the minimum and maximum values that can be generated.minis always0, butmt19937has amaxvalue of4294967295(or 232-1) andmt19937_64has amaxvalue of18446744073709551615(or 264-1).seedcan be used to re-seed the generator, or provide the initial seed if we declared it without passing an argument to the constructor. Usually there is no need to use this function, since the generator only needs to be seeded once, and that is best done via the constructor, otherwise you might forget to do it later. We demonstrate how to use mt19937in the following example: #include <iostream>
#include <random>
using namespace std;
int main()
{
    random_device rd;
    mt19937 mt32(rd());
    cout << "mt32.min() = " << mt32.min() << '\n';
    cout << "mt32.max() = " << mt32.max() << '\n';
    cout << "3 random numbers: " << mt32() << ", " << mt32() << ", " << mt32() << '\n'
         << '\n';
    mt19937_64 mt64(rd());
    cout << "mt64.min() = " << mt64.min() << '\n';
    cout << "mt64.max() = " << mt64.max() << '\n';
    cout << "3 random numbers: " << mt64() << ", " << mt64() << ", " << mt64() << '\n';
}
 Sample output: t32.min() = 0
mt32.max() = 4294967295
3 random numbers: 3365933155, 1249942142, 4161793284
mt64.min() = 0
mt64.max() = 18446744073709551615
3 random numbers: 14228424373019279719, 7136662969432099652, 6887900356391559377
  Warning: If you create a pseudo-random number generator with mt19937and do not seed it, either by passing an argument to the constructor or using theseedmember function, it will generate the same numbers every time you run the program. The same will happen if you seed it using a fixed value instead of a value that changes every time the program runs. The best way to avoid this problem is to always pass a seed generated byrandom_deviceto the constructor, as in the example I provided here. Here is a demonstration of the consequences of not adhering to the warning above: #include <iostream>
#include <random>
using namespace std;
int main()
{
    mt19937 unseeded_1;
    mt19937 unseeded_2;
    // Both generators will be seeded with some fixed *default* value.
    // The same number will be generated by both every time the program runs.
    cout << "First number from unseeded_1: " << unseeded_1() << '\n';
    cout << "First number from unseeded_2: " << unseeded_2() << '\n';
    mt19937 fixed_seed_1(0);
    mt19937 fixed_seed_2(0);
    // Both generators will be seeded with a fixed *specific* value, namely 0.
    // Again, the same number will be generated by both every time the program runs.
    cout << "First number from fixed_seed_1: " << fixed_seed_1() << '\n';
    cout << "First number from fixed_seed_2: " << fixed_seed_2() << '\n';
}
 The following output will be generated every time you run this program: First number from unseeded_1: 3499211612
First number from unseeded_2: 3499211612
First number from fixed_seed_1: 2357136044
First number from fixed_seed_2: 2357136044
 | 
| As we have seen, mt19937andmt19937_64always generate numbers in the ranges 0 to 232-1 and 0 to 264-1 respectively. Generating numbers in a different range can be achieved using theuniform_int_distributionclass: uniform_int_distribution<type> name(min, max);
 This distribution will generate values in the range [min, max](inclusive), uniformly distributed.typecan be omitted, in which case it defaults toint, but as always, it is best to be specific for maximum readability and portability. We can use the following member functions:  The ()operator with a generator as an argument, i.e.name(generator), returns the next random number in the sequence. You will use this every time you need a new random number. Usually, the generator will be anmt19937object.minandmaxreturn the minimum and maximum values that can be generated. There are the same values you entered when you created the object. For example: #include <iostream>
#include <random>
using namespace std;
int main()
{
    random_device rd;
    mt19937 mt(rd());
    uniform_int_distribution<int64_t> uid(-10, 10);
    cout << "uid.min() = " << uid.min() << '\n';
    cout << "uid.max() = " << uid.max() << '\n';
    cout << "3 random numbers: " << uid(mt) << ", " << uid(mt) << ", " << uid(mt) << '\n';
}
 Possible output: uid.min() = -10
uid.max() = 10
3 random numbers: 6, 5, -8
  Warning: The typepassed touniform_int_distributionmust match the desired range. For example, if the type isuint32_tand the range contains negative integers, or exceeds 232-1, this would lead to unexpected behavior. <random>provides a wide range of other probability distributions that we can use, with the same syntax. These include, for example:
  uniform_real_distribution<type>(min, max): a uniform distribution of real (or more precisely, floating-point) numbers in the specified range.normal_distribution<type>(mean, std_dev): a normal (or Gaussian) distribution with the specified mean and standard deviation. In both cases, typemust be a floating-point type (doubleby default). We illustrate these two distributions in the following example: #include <iostream>
#include <random>
#include <vector>
using namespace std;
int main()
{
    vector<double> v(100);
    random_device rd;
    mt19937 mt(rd());
    cout << "uniform_real_distribution(-25, 25):\n";
    uniform_real_distribution<double> urd(-25, 25);
    for (double &i : v)
        i = urd(mt);
    sort(v.begin(), v.end());
    for (const double &i : v)
        cout << round(i * 10) / 10 << ' ';
    cout << "\n\n";
    cout << "normal_distribution(0, 10):\n";
    normal_distribution<double> nd(0, 10);
    for (double &i : v)
        i = nd(mt);
    sort(v.begin(), v.end());
    for (const double &i : v)
        cout << round(i * 10) / 10 << ' ';
}
 Here I used sort()to sort the generated numbers; I will explain how that works later. A possible output is: uniform_real_distribution(-25, 25):
-24.8 -24 -23.9 -23.3 -22.2 -21.6 -21.2 -21.1 -20.7 -20.6 -20.5 -20.1 -19.8 -19.8 -19.7 -19.5 -19 -18.7 -18.5 -18.3 -18 -17.8 -16.9 -16 -15.5 -14.9 -14.9 -13.9 -12.8 -12.3 -11.2 -11.1 -11 -10.9 -10.5 -10.3 -10.2 -10.2 -10 -8.4 -8 -6.7 -5.2 -5 -4.6 -4.4 -3 -2.6 -2.3 -2.1 -2 -1.8 -0.9 0.3 0.5 1.3 1.6 2.9 3.2 3.8 3.8 3.9 4 4.4 5.5 5.9 6.6 6.7 7.8 7.9 8.2 8.9 9 9.8 10.4 10.5 10.6 12.1 13.4 13.4 13.5 14 14.4 14.8 14.8 16.7 16.8 17.1 17.1 17.3 17.4 17.6 17.6 18.1 18.3 18.8 19.6 21 21 22.8
normal_distribution(0, 10):
-30.4 -25.3 -24.4 -17.9 -15.6 -13.7 -13.5 -12.9 -12.3 -11.3 -11.3 -11.2 -10.4 -10.3 -8.9 -8.6 -7.7 -7.5 -7.4 -6.7 -6.7 -6.6 -6.4 -6.4 -5.9 -5.7 -5.4 -4.9 -4.9 -4.6 -4.5 -4.3 -4.2 -4.2 -4.2 -4 -3.6 -3.2 -1.9 -1.9 -1.8 -1.7 -1.6 -1.2 -1.1 -0.9 -0.8 -0.7 -0.3 0.4 0.4 0.7 0.9 0.9 1 1.1 1.2 1.3 2.3 2.6 2.7 2.8 2.9 3.2 3.5 3.8 4.5 4.7 4.7 4.8 4.9 5.5 5.8 6.1 6.2 6.3 6.3 6.4 6.6 7.3 7.3 7.4 7.8 7.9 8.1 8.5 8.8 8.9 9.1 9.3 9.5 10.4 11.8 12.4 14.3 16.6 17.8 18.6 20.2 32.6
 A full list of the available distributions may be found in the C++ reference and Microsoft's C++ reference. | 
|  | 
|  | 
| C++ is a strongly-typed language. This means that all variables must have a specific type at compilation time. Indeed, so far, we have always defined classes and functions with variables of specific data types. For example, our matrixclass only stores elements of typedouble, and its member functions and overloaded operators only take and/or return values of typedouble. But what if we want to use other data types such asfloat,long double,int,complex, or even our own user-defined types instead - all using the same code? This can be done using templates. Essentially, a template is just what its name suggests: it's a template for making a generic class or a function, which is not defined for any particular data type. Later, we choose a data type, and the compiler automatically generates a class or a function that uses that data type. The template simply specifies the operations on the variables, but it does not care about their data type. In fact, we have already been using templates. As I mentioned above in passing, the standard library vector is not a class, but a template, and when we write vector<type>, withtypebeing any fundamental or user-defined data type, the compiler generates a specific class for vectors which store data of that type. Templates are declared using the following syntax: template <typename T>
 typenameis a special keyword which declares a "variable" that "stores" a data type as its "value". Here we usedTfor the name of that "variable" (as is the convention), but you can use any name you want. Any instance of the placeholderTin the function or class definition that follows this statement will be replaced with a concrete data type, either specified by the user or deduced by the compiler.
 We could also write classinstead oftypename, and the two keywords are completely equivalent; the reasontypenameis preferred is because it makes it clearer that the type does not have to be aclass- it can also be a fundamental type likedoubleorint. As an example, consider a function maximumthat takes twoint64_targuments and returns the larger of the two: #include <iostream>
using namespace std;
int64_t maximum(const int64_t &a, const int64_t &b)
{
    if (a > b)
        return a;
    else
        return b;
}
int main()
{
    cout << maximum(4, 3); // 4
}
 Now, let us convert it into a generic template: #include <iostream>
using namespace std;
template <typename T>
T maximum(const T &a, const T &b)
{
    if (a > b)
        return a;
    else
        return b;
}
int main()
{
    cout << maximum(4.5, 3.7); // 4.5
}
 We added the modifier template <typename T>, and replacedint64_twithTwhenever it appears. Then, when we calledmaximumwith twodoubles, the compiler automatically deduced thatTshould be replaced withdoublein the function's definition. We can also explicitly tell the compiler whatTshould be replaced with, using angled brackets (as withvector): cout << maximum<double>(4.5, 3.7); // 4.5
 Note that Tcan be any type, including user-defined classes - it doesn't have to be a numeric type. However, in this example, sincemaximumuses the>operator, the classTmust have the>operator overloaded, or you will get a compilation error. Template parameters behave much like function arguments. You can specify more than one type parameter: template <typename T1, typename T2>
 And you can even specify default arguments: template <typename T1, typename T2 = double>
 Furthermore, you can specify additional parameters that are not typename- as long as they are known at compilation time. For example, here is a template for aclassthat stores avectorof the size given by the parameterL, and initializes it to the value given in the constructor: #include <iostream>
#include <vector>
using namespace std;
template <typename T, uint64_t L>
class vector_of_value
{
public:
    vector_of_value(const T &x)
    {
        zeros = vector<T>(L, x);
    }
    void print() const
    {
        for (const T &i : zeros)
            cout << i << ' ';
    }
private:
    vector<T> zeros;
};
int main()
{
    vector_of_value<double, 100> v(7);
    v.print();
}
 | 
| Above we defined a collection of useful operator overloads for the vectorclass. However, we only defined them forvector<double>. In order to illustrate function templates, let us now expand this collection to work with vectors of any type using templates. We originally wrote the collection of vector overloads as a header-only library, instead of separating it into an .hppand a.cppfile. This is good, because templates must be provided in a header file. The compiler creates functions for specific types from the template on-the-fly at compilation time, so it needs to know which data types to do that for - but that information is inmain.cpp, which makes use of specific instances of the templates. Therefore, we cannot put the overload templates in a separate vector_overloads.cppfile, since that file would need to be compiled separately, and the compiler can't know in advance which instances of the templates need to be compiled. Hence, the templates must be in a singlevector_overloads.hppfile, which will then be included in its entirety in the code ofmain.cppby the compiler at complication time. To convert the overloads into templates, we make two straightforward modifications to the code: we add template <typename T>before each of the overloads, and we replace every instance ofdoublewithT. Also, we take this opportunity to replace the exceptions in the original code with own custom exceptions, since now we know how to do that. The end result will be the following vector_overloads.hppfile: #include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
namespace vector_overloads
{
class add_different_sizes : public invalid_argument
{
public:
    add_different_sizes() : invalid_argument("Cannot add vectors of different sizes!"){};
};
class subtract_different_sizes : public invalid_argument
{
public:
    subtract_different_sizes() : invalid_argument("Cannot subtract vectors of different sizes!"){};
};
class dot_different_sizes : public invalid_argument
{
public:
    dot_different_sizes() : invalid_argument("Cannot take the dot product of vectors of different sizes!"){};
};
} // namespace vector_overloads
template <typename T>
ostream& operator<<(ostream& out, const vector<T>& vec)
{
    out << '(';
    for (size_t i = 0; i < vec.size() - 1; ++i)
        out << vec[i] << ", ";
    out << vec[vec.size() - 1] << ')';
    return out;
}
template <typename T>
bool operator==(const vector<T>& lhs, const vector<T>& rhs)
{
    if (lhs.size() != rhs.size())
        return false;
    for (size_t i = 0; i < lhs.size(); ++i)
        if (lhs[i] != rhs[i])
            return false;
    return true;
}
template <typename T>
bool operator!=(const vector<T>& lhs, const vector<T>& rhs)
{
    return !(lhs == rhs);
}
template <typename T>
vector<T>& operator+=(vector<T>& lhs, const vector<T>& rhs)
{
    if (lhs.size() != rhs.size())
        throw vector_overloads::add_different_sizes();
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] += rhs[i];
    return lhs;
}
template <typename T>
vector<T> operator+(vector<T> lhs, const vector<T>& rhs)
{
    lhs += rhs;
    return lhs;
}
template <typename T>
vector<T>& operator-=(vector<T>& lhs, const vector<T>& rhs)
{
    if (lhs.size() != rhs.size())
        throw vector_overloads::subtract_different_sizes();
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] -= rhs[i];
    return lhs;
}
template <typename T>
vector<T> operator-(vector<T> lhs, const vector<T>& rhs)
{
    lhs -= rhs;
    return lhs;
}
template <typename T>
vector<T> operator-(vector<T> vec)
{
    for (size_t i = 0; i < vec.size(); ++i)
        vec[i] = -vec[i];
    return vec;
}
template <typename T>
T operator*(const vector<T>& lhs, const vector<T>& rhs)
{
    if (lhs.size() != rhs.size())
        throw vector_overloads::dot_different_sizes();
    T result = 0;
    for (size_t i = 0; i < lhs.size(); ++i)
        result += lhs[i] * rhs[i];
    return result;
}
template <typename T>
vector<T>& operator*=(vector<T>& lhs, const T rhs)
{
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] *= rhs;
    return lhs;
}
template <typename T>
vector<T> operator*(vector<T> lhs, const T rhs)
{
    lhs *= rhs;
    return lhs;
}
template <typename T>
vector<T> operator*(const T lhs, vector<T> rhs)
{
    rhs *= lhs;
    return rhs;
}
 Notice that I put all the exceptions inside the namespace vector_overloadsto avoid any potential collisions with names defined elsewhere in the program. We can test this file with the same sample main.cppfile as we had before, for example replacingdoublewithlong double: #include "vector_overloads.hpp"
#include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
int main()
{
    try
    {
        vector<long double> v = {1, 2, 3};
        vector<long double> w = {4, 5, 6};
        vector<long double> u = {1, 1, 1};
        cout << v + w << '\n';    // (5, 7, 9)
        cout << v * w << '\n';    // 32
        cout << -v << '\n';       // (-1, -2, -3)
        v += w;                   //
        cout << v << '\n';        // (5, 7, 9)
        cout << v - w << '\n';    // (1, 2, 3)
        w -= u;                   //
        cout << w << '\n';        // (3, 4, 5)
        cout << 2.0L * v << '\n'; // (10, 14, 18)
        cout << v * 3.0L << '\n'; // (15, 21, 27)
    }
    catch (const invalid_argument& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 Note that for multiplication by a scalar, we had to explicitly write 2.0L * vto ensure that2.0Lis interpreted as along double, since2 * vwould have been interpreted as multiplying anintwith a vector oflong doubleand2.0 * vwould have been interpreted as multiplying adoublewith a vector oflong double, neither of which have operator overloads defined for them. If we wanted to avoid this, we could allow multiplication of a vector by a scalar of a different type by replacing the operator*overloads with: template <typename T_scalar, typename T_vector>
vector<T_vector>& operator*=(vector<T_vector>& lhs, const T_scalar rhs)
{
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] *= rhs;
    return lhs;
}
template <typename T_scalar, typename T_vector>
vector<T_vector> operator*(vector<T_vector> lhs, const T_scalar rhs)
{
    lhs *= rhs;
    return lhs;
}
template <typename T_scalar, typename T_vector>
vector<T_vector> operator*(const T_scalar lhs, vector<T_vector> rhs)
{
    rhs *= lhs;
    return rhs;
}
 Now 2 * vandv * 3will work. However, this would mean that, for example,vector{3, 3} * 2.5would result in the vector(7, 7), because the output vector takes its type from the type of the input vector, and3is interpreted asintby default! This is probably not what you intended to get, and could lead to bugs, therefore I do not recommend doing this. (But note that if you enable the-Wconversioncompiler flag, you will get a warning when something like this happens.) Similarly, for addition to work, the two vectors must have the same type. We could define: template <typename T1, typename T2>
vector<T1>& operator+=(vector<T1>& lhs, const vector<T2>& rhs)
{
    if (lhs.size() != rhs.size())
        throw vector_overloads::add_different_sizes();
    for (size_t i = 0; i < lhs.size(); ++i)
        lhs[i] += rhs[i];
    return lhs;
}
template <typename T1, typename T2>
vector<T1> operator+(vector<T1> lhs, const vector<T2>& rhs)
{
    lhs += rhs;
    return lhs;
}
 However, this would mean that, for example, vector{1, 1} + vector{1.9, 1.9}would result in the vector(2, 2), since1is interpreted asintand thusT1isint, butT1is the type of the output vector. Again, this is most likely not what you intended. Personally, I would prefer to be restricted to always adding two vectors of the same type in order to ensure such errors cannot possibly happen. Generally, mixing types in C++ is never a good idea unless you are 100% sure you know what you're doing and you explicitly cast from one type to another. One potential solution to this problem could be to define specific overloads which produce a vector of the correct type, for example a specific overload for adding a vector<int32_t>with avector<double>which will explicitly return avector<double>. However, this means we would have to explicitly write overloads for all possible combinations, which is the exact opposite of what templates are meant to achieve. | 
| Above we defined the matrixclass withdoubleelements. Let us now generalize it to a template. Here are the changes we made:  We placed all of the code in one header file, matrix.hpp, so that the complete template implementation is accessible to the compiler (for reasons explained in the previous section).We added template <typename T>in front of the class definition and the definitions of the operator overloads.We replaced doublewithTeverywhere.We replaced matrixwithmatrix<T>in all function arguments and return types.We replaced the exceptions with derived exceptions, and replaced all throwstatements so that they throw objects constructed in place instead of the static objects we had before (by simply adding()after the object name).In the operator overloads, we added the keyword typenamebefore each exception in thethrowstatement. This is needed because the exceptions are defined as part of a template.Since now we know about friend functions, we defined some of the overloaded operators as friends, to enable easier access to private members. This isn't strictly necessary in this case, but is more convenient, less verbose, and also faster since there are fewer function calls (e.g. instead of calling get_rows()we can just accessrowsdirectly, instead of callingoperator()we can just accesselementsdirectly). However, operators which do not access any members, such asoperator+(), don't need to be friends. The file matrix.hppnow takes the form: #include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
template <typename T>
class matrix
{
public:
    // Constructor to create a zero matrix.
    // First argument: number of rows.
    // Second argument: number of columns.
    matrix(const size_t rows_, const size_t cols_) : rows(rows_), cols(cols_)
    {
        if (rows == 0 or cols == 0)
            throw zero_size();
        elements = vector<T>(rows * cols);
    }
    // Constructor to create a diagonal matrix from a vector.
    // Argument: a vector containing the elements on the diagonal.
    // Number of rows and columns is inferred automatically.
    matrix(const vector<T>& diagonal_) : rows(diagonal_.size()), cols(diagonal_.size())
    {
        if (rows == 0)
            throw zero_size();
        elements = vector<T>(rows * cols);
        for (size_t i = 0; i < rows; ++i)
            elements[(cols * i) + i] = diagonal_[i];
    }
    // Constructor to create a diagonal matrix from an initializer_list.
    // Argument: an initializer_list containing the elements on the diagonal.
    // Number of rows and columns is inferred automatically.
    matrix(const initializer_list<T>& diagonal_) : matrix(vector<T>(diagonal_)) {}
    // Constructor to create a matrix from a vector.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: a vector containing the elements in row-major order.
    matrix(const size_t rows_, const size_t cols_, const vector<T>& elements_) : rows(rows_), cols(cols_), elements(elements_)
    {
        if (rows == 0 or cols == 0)
            throw zero_size();
        if (elements_.size() != rows * cols)
            throw initializer_wrong_size();
    }
    // Constructor to create a matrix from an initializer_list.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: an initializer_list containing the elements in row-major order.
    matrix(const size_t rows_, const size_t cols_, const initializer_list<T>& elements_) : matrix(rows_, cols_, vector<T>(elements_)) {}
    // Member function to obtain (but not modify) the number of rows in the matrix.
    size_t get_rows() const
    {
        return rows;
    }
    // Member function to obtain (but not modify) the number of columns in the matrix.
    size_t get_cols() const
    {
        return cols;
    }
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // Non-const version: allows modification of the element.
    T& operator()(const size_t row, const size_t col)
    {
        return elements[(cols * row) + col];
    }
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // const version: does not allow modification of the element.
    const T& operator()(const size_t row, const size_t col) const
    {
        return elements[(cols * row) + col];
    }
    // Member function to access matrix elements WITH range checking.
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // Non-const version: allows modification of the element.
    T& at(const size_t row, const size_t col)
    {
        if ((row > rows - 1) or (col > cols - 1))
            throw out_of_range();
        return elements[(cols * row) + col];
    }
    // Member function to access matrix elements WITH range checking.
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // const version: does not allow modification of the element.
    const T& at(const size_t row, const size_t col) const
    {
        if ((row > rows - 1) or (col > cols - 1))
            throw out_of_range();
        return elements[(cols * row) + col];
    }
    // Overloaded binary operator += to add another matrix to this matrix.
    matrix<T>& operator+=(const matrix<T>& other)
    {
        if ((rows != other.rows) or (cols != other.cols))
            throw incompatible_sizes_add();
        for (size_t i = 0; i < rows * cols; ++i)
            elements[i] += other.elements[i];
        return *this;
    }
    // Overloaded binary operator -= to subtract another matrix from this matrix.
    matrix<T>& operator-=(const matrix<T>& other)
    {
        if ((rows != other.rows) or (cols != other.cols))
            throw incompatible_sizes_add();
        for (size_t i = 0; i < rows * cols; ++i)
            elements[i] -= other.elements[i];
        return *this;
    }
    // Overloaded binary operator *= to multiply this matrix by a scalar.
    matrix<T>& operator*=(const T scalar)
    {
        for (size_t i = 0; i < rows * cols; ++i)
            elements[i] *= scalar;
        return *this;
    }
    // Overloaded binary operator << to easily print out a matrix to a stream.
    friend ostream& operator<<(ostream& out, const matrix<T>& mat)
    {
        for (size_t i = 0; i < mat.rows; ++i)
        {
            out << "( ";
            for (size_t j = 0; j < mat.cols; ++j)
                out << mat(i, j) << '\t';
            out << ")\n";
        }
        return out;
    }
    // Overloaded binary operator == to compare two matrices.
    friend bool operator==(const matrix<T>& lhs, const matrix<T>& rhs)
    {
        if ((lhs.rows != rhs.rows) or (lhs.cols != rhs.cols))
            return false;
        for (size_t i = 0; i < lhs.rows * rhs.cols; ++i)
            if (lhs.elements[i] != rhs.elements[i])
                return false;
        return true;
    }
    // Overloaded binary operator != to compare two matrices.
    friend bool operator!=(const matrix<T>& lhs, const matrix<T>& rhs)
    {
        return !(lhs == rhs);
    }
    // Overloaded unary operator - to take the negative of a matrix.
    friend matrix<T> operator-(matrix<T> mat)
    {
        for (size_t i = 0; i < mat.rows * mat.cols; ++i)
            mat.elements[i] = -mat.elements[i];
        return mat;
    }
    // Overloaded binary operator * to multiply two matrices.
    friend matrix<T> operator*(const matrix<T>& lhs, const matrix<T>& rhs)
    {
        if (lhs.cols != rhs.rows)
            throw incompatible_sizes_multiply();
        matrix<T> c(lhs.rows, rhs.cols);
        for (size_t i = 0; i < lhs.rows; ++i)
            for (size_t j = 0; j < rhs.cols; ++j)
                for (size_t k = 0; k < lhs.cols; ++k)
                    c(i, j) += lhs(i, k) * rhs(k, j);
        return c;
    }
    // Exception to be thrown if the number of rows or columns given to the constructor is zero.
    class zero_size : public invalid_argument
    {
    public:
        zero_size() : invalid_argument("Matrix cannot have zero rows or columns!"){};
    };
    // Exception to be thrown if the vector of elements provided to the constructor is of the wrong size.
    class initializer_wrong_size : public invalid_argument
    {
    public:
        initializer_wrong_size() : invalid_argument("Initializer does not have the expected number of elements!"){};
    };
    // Exception to be thrown if two matrices of different sizes are added or subtracted.
    class incompatible_sizes_add : public invalid_argument
    {
    public:
        incompatible_sizes_add() : invalid_argument("Cannot add or subtract two matrices of different dimensions!"){};
    };
    // Exception to be thrown if two matrices of incompatible sizes are multiplied.
    class incompatible_sizes_multiply : public invalid_argument
    {
    public:
        incompatible_sizes_multiply() : invalid_argument("Two matrices can only be multiplied if the number of columns in the first matrix is equal to the number of rows in the second matrix!"){};
    };
    // Exception to be thrown if trying to access an element out of range.
    class out_of_range : public invalid_argument
    {
    public:
        out_of_range() : invalid_argument("Tried to access an element out of range!"){};
    };
private:
    // The number of rows.
    size_t rows = 0;
    // The number of columns.
    size_t cols = 0;
    // A vector storing the elements of the matrix in flattened (1-dimensional) form.
    vector<T> elements;
};
// Overloaded binary operator + to add two matrices.
template <typename T>
matrix<T> operator+(matrix<T> lhs, const matrix<T>& rhs)
{
    lhs += rhs;
    return lhs;
}
// Overloaded binary operator - to subtract two matrices.
template <typename T>
matrix<T> operator-(matrix<T> lhs, const matrix<T>& rhs)
{
    lhs -= rhs;
    return lhs;
}
// Overloaded binary operator * to multiply a matrix on the left and a scalar on the right.
template <typename T>
matrix<T> operator*(matrix<T> lhs, const T rhs)
{
    lhs *= rhs;
    return lhs;
}
// Overloaded binary operator * to multiply a scalar on the left and a matrix on the right.
template <typename T>
matrix<T> operator*(const T lhs, matrix<T> rhs)
{
    rhs *= lhs;
    return rhs;
}
 We can test this file with a main.cppfile similar to the one we had before, except that we now need to add the type name (here we chose to uselong double) inside<>whenever we create a newmatrix. We also had to replace7with7.0Lin7 * Band so on, so that both the scalar and the matrix will belong doubleand will match the operator overload: #include "matrix.hpp"
#include <exception>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    try
    {
        // Constructor with two integers: create a 3x4 matrix of zeros.
        matrix<long double> A(3, 4);
        cout << "A:\n" << A;
        // Constructor with one vector: create a 3x3 matrix with 1, 2, 3 on the diagonal.
        matrix<long double> B(vector<long double>{1, 2, 3});
        cout << "B:\n" << B;
        // Constructor with one initializer_list: create a 4x4 matrix with 1, 2, 3, 4 on the diagonal.
        matrix<long double> C{1, 2, 3, 4};
        cout << "C:\n" << C;
        // Constructor with two integers and one vector: create a 2x3 matrix with the given elements in row-major order.
        matrix<long double> D(2, 3, vector<long double>{1, 2, 3, 4, 5, 6});
        cout << "D:\n" << D;
        // Constructor with two integers and one initializer_list: create a 2x2 matrix with the given elements in row-major order.
        matrix<long double> E(2, 2, {1, 2, 3, 4});
        cout << "E:\n" << E;
        // Demonstration of some of the overloaded operators.
        D(0, 2) = 7;
        cout << "D after D(0, 2) = 7:\n" << D;
        matrix<long double> F = D * B;
        cout << "F = D * B:\n" << F;
        cout << "D + F:\n" << D + F;
        cout << "7.0L * B:\n" << 7.0L * B;
        matrix<long double> G(3, 3, {1, 0, 0, 0, 2, 0, 0, 0, 3});
        cout << "B == G: " << (B == G) << '\n';
        cout << "B == F: " << (B == F) << '\n';
        D *= 2.0L;
        cout << "D after D *= 2.0L:\n" << D;
        cout << "D * 3.0L:\n" << D * 3.0L;
        cout << "4.0L * D:\n" << 4.0L * D;
        // initializer_list constructor will be used: create a 2x2 diagonal matrix with 1, 2 on the diagonal.
        cout << "matrix{1, 2}:\n";
        cout << matrix<long double>{1, 2};
        // (size_t, size_t) constructor will be used: create a 1x2 zero matrix.
        cout << "matrix(1, 2):\n";
        cout << matrix<long double>(1, 2);
        // Demonstration of range checking; the range of D is 0-1 for rows and 0-2 for columns.
        cout << "Range checking:\n";
        cout << "D.at(0, 0): " << D.at(0, 0) << '\n';
        cout << "D.at(1, 0): " << D.at(1, 0) << '\n';
        cout << "D.at(2, 0): " << D.at(2, 0) << '\n'; // Error: Tried to access an element out of range!
    }
    catch (const exception& e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 | 
| The C++ standard template library (STL) consists of templates for a variety of different containers. Since they are templates, they can contain elements of any type. The containers in the STL can be divided into two groups:  Sequence containers store elements in a specific order. For each element, there is always a next element in the sequence. These containers include array,vector,deque,list, andforward_list.Associative containers store elements in non-sequential order: the elements are accessed using keys. These containers include set,map, andunorderedand/ormultivariations of them. We will discuss these containers in the next few sections. For more information, please see the C++ reference and Microsoft's C++ reference. | 
|  | 
| An array, defined in the header file<array>, is essentially a C-style fixed-size array, with the exact same performance, but with some extra features on top. We declare anarraywithsizeelements of the data typetypeas follows: array<type, size> name;
 Note that sizemust be known at compilation time, since this a template. Iftypeis a user-defined class, then the elements will be initialized using the constructor, but if it is just a fundamental type such asintordouble, the elements will be uninitialized, as for a C-style array. To initialize an array, we use the same syntax as for C-style arrays: array<type, size> name = {element0, element1, ...};
 If the number of elements in the list is less than the sizeof the array, and the array is of a numerictype, then the remaining elements will be initialized to zero. So{0}will initialize the entire array to zero. Note that the member functionfillcan be used to assign the same value to all the elements in the array. This is demonstrated by the following program: #include <array>
#include <iostream>
using namespace std;
// Print the elements of an array. Note that this function must be a template, since array is a template.
template <typename T, uint64_t s>
void print_elements(const array<T, s> &a)
{
    for (const T &i : a)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    // WARNING: Uninitialized array! Elements will have garbage values.
    array<uint32_t, 10> a;
    print_elements(a);
    // Initialize all elements to zero.
    array<uint32_t, 10> b = {0};
    print_elements(b);
    // Initialize only the first three elements, the rest will be initialized to zero.
    array<uint32_t, 10> c = {1, 2, 3};
    print_elements(c);
    // Initialize all elements to specific values.
    array<uint32_t, 10> d = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    print_elements(d);
    // Create an uninitialized array and fill it with 7's.
    array<uint32_t, 10> e;
    e.fill(7);
    print_elements(e);
}
 Sample output: 1937765920 134 1856969352 32758 2362293616 627 0 1 0 0
0 0 0 0 0 0 0 0 0 0
1 2 3 0 0 0 0 0 0 0
1 2 3 4 5 6 7 8 9 10
7 7 7 7 7 7 7 7 7 7
 The elements of an array, just like the elements of a C-style array, are stored contiguously in memory, i.e. one after the other in sequence. This means that the memory can be accessed directly using the[]operator as for C-style arrays, and there is no performance penalty when accessing elements (which would have been the case if accessing each element required first figuring out where in memory it is located). The number of elements in the arrayis given by the member functionsize. If thearrayis empty, then the member functionemptywill returntrue. The actual memory address where the array elements are stored can be obtained using the member functiondata, which returns a pointer that can be used in exactly the same way as a C-style array. The member functions frontandbackcan be used to access the first and last element respectively, soa.front()is equivalent toa[0]anda.back()is equivalent toa[a.size() - 1], wherearefers to anarrayobject. The elements can also be accessed using the member function at, which throws the exceptionstd::out_of_rangeif you try to access an out-of-range element - at the cost of a small performance overhead, since it has to check if the element is in range every time it is called. Usingatis safer than using[], but on the other hand, usually there is no ambiguity regarding the number of elements contained in the array (since it's a fixed-size array), so in most cases you can just use[]instead. This is demonstrated in the following program: #include <array>
#include <iostream>
using namespace std;
int main()
{
    array<uint32_t, 5> a = {2, 4, 8, 16, 32};
    cout << "First element: " << a.front() << '\n';
    cout << "Second element: " << a[1] << '\n';
    cout << "Third element: " << a.at(2) << '\n';
    cout << "Fourth element: " << a.data()[3] << '\n';
    cout << "Last element: " << a.back() << '\n';
    try
    {
        cout << "Sixth element: " << a.at(5) << '\n';
    }
    catch (const out_of_range &e)
    {
        cout << "N/A\n";
    }
    cout << "Size: " << a.size() << '\n';
    cout << "Empty: " << boolalpha << a.empty() << '\n';
}
 Output: First element: 2
Second element: 4
Third element: 8
Fourth element: 16
Last element: 32
Sixth element: N/A
Size: 5
Empty: false
 Two arrayobjects can be compared with each other using (overloaded) comparison operators, but only if both arrays have the same number of elements.==and!=check if the arrays have exactly the same elements. Inequality operators such as<or>=compare the elements lexicographically. For example: #include <array>
#include <iostream>
using namespace std;
int main()
{
    array<uint32_t, 5> a = {2, 4, 8, 16, 32};
    array<uint32_t, 5> b = {2, 4, 8, 16, 32};
    array<uint32_t, 5> c = {2, 4, 8, 16, 33};
    array<uint32_t, 5> d = {2, 4, 8, 15, 32};
    cout << boolalpha;
    cout << "a == b: " << (a == b) << '\n';
    cout << "a < b: " << (a < b) << '\n';
    cout << "a > b: " << (a > b) << '\n';
    cout << "a == c: " << (a == c) << '\n';
    cout << "a < c: " << (a < c) << '\n';
    cout << "a > c: " << (a > c) << '\n';
    cout << "a == d: " << (a == d) << '\n';
    cout << "a < d: " << (a < d) << '\n';
    cout << "a > d: " << (a > d) << '\n';
}
 Output: a == b: true
a < b: false
a > b: false
a == c: false
a < c: true
a > c: false
a == d: false
a < d: false
a > d: true
 Finally, as of C++20, if you have an old-fashioned C-style array, you can convert it to a more convenient STL arrayusing the member functionto_array. The type and size are inferred automatically: #include <array>
#include <iostream>
using namespace std;
int main()
{
    uint32_t C_array[] = {2, 4, 8, 16, 32};
    array a = to_array(C_array);
    for (const uint32_t &i : a)
        cout << i << ' ';
    cout << '\n';
}
 | 
| An iterator is a pointer-like object that points to a specific element in any STL container. Once you have an iterator, you can:  Access the element it points to by dereferencing it with the *operator.Increment it to the next element using the ++operator.Compare it with another iterator using the ==or!=operators to see if both point to the same element. These three operators are the bare minimum supported by iterators for all containers. They are described by the category LegacyInputIterator. However, many containers have iterators that support additional operations, depending on the type of the container. In particular,arrayhas iterators that belong to the categoryLegacyRandomAccessIterator. With such an iterator, you can also:  Decrement it to the previous element using the --operator.Add a positive integer nto it using the+operator to skipnelements forwards.Subtract a positive integer nfrom it using the-operator to skipnelements backwards.Compare it with another iterator using the <or>operators to see which iterator points to an element that appears earlier in the sequence. In addition, the operators +=,-=,<=, and>=act as expected, andi[n]is equivalent to*(i + n)(as for C-style arrays). The member function beginreturns an iterator to the first element of the array, whileendreturns an iterator to the element following the last element of the array. The member functionscbeginandcenddo the same, except that they return iterators that do not allow modifying the elements. So for example,*a.begin() = 7will change the first element of the arrayato7, but*a.cbegin() = 7will result in a compilation error. Here is an example: #include <array>
#include <iostream>
using namespace std;
int main()
{
    array<uint32_t, 8> a = {0, 1, 2, 3, 4, 5, 6, 7};
    // The iterator b will point to the first element.
    array<uint32_t, 8>::iterator b = a.begin();
    cout << "Value of b:        " << b << '\n';
    cout << "Value of *b:       " << *b << '\n';
    cout << "Value of b + 1:    " << b + 1 << '\n';
    cout << "Value of *(b + 1): " << *(b + 1) << '\n';
    // The iterator e will point to the element following the last element.
    // Note that this element doesn't exist, so e should never be dereferenced!
    array<uint32_t, 8>::iterator e = a.end();
    cout << "Value of e:        " << e << '\n';
    cout << "Value of *e:       " << *e << '\n'; // WARNING: Will be the garbage value a[8]!
    cout << "Value of e - 1:    " << e - 1 << '\n';
    cout << "Value of *(e - 1): " << *(e - 1) << '\n';
}
 Note that the type iteratorlives in the namespacearray<uint32_t, 8>. The output will be different every time, since both the memory addresses and the garbage valuea[8]will change: Value of b:        0x5ce9fff7b0
Value of *b:       0
Value of b + 1:    0x5ce9fff7b4
Value of *(b + 1): 1
Value of e:        0x5ce9fff7d0
Value of *e:       3925866448
Value of e - 1:    0x5ce9fff7cc
Value of *(e - 1): 7
 Why do we need iterators if we can already access the elements of the arraydirectly? Iterators are used in STL algorithms, which we will discuss below. The use of iterators allows these algorithms to be generic, since they can take iterators for any kind of container that supports iterators - even ones that are not sequential, and even those you define yourself (as long as you define proper iterators for them). For example, in the following program, we use iterators to print the elements of the array: #include <array>
#include <iostream>
using namespace std;
int main()
{
    array<uint32_t, 5> a = {2, 4, 8, 16, 32};
    for (array<uint32_t, 5>::iterator i = a.begin(); i != a.end(); i++)
        cout << *i << ' ';
}
 This forloop will actually work on any kind of container that supports iterators, since it only uses the operators*,++, and!=, which are defined for all types of iterators. | 
| The arraycontainer is the simplest one - essentially, it's just a C-style array that knows its own size. Therefore, it provides the best performance (as long as you don't useat). However, it also has a significant disadvantage: since the size of the array is a template parameter, it must be known at compilation time. Therefore, you cannot usearrayto define an array whose size is only known at run time. For that, you must usevector. Furthermore, arraycannot be used for very large arrays. Recall that the stack is a small portion of memory used to store all of the variables for which memory has been automatically allocated at compilation time, while the heap is a large portion of memory used to store arrays and object which we dynamically allocate at run time. It is important to note that an arrayallocates memory on the stack, just like a C-style array. This is in contrast with avector, or thenewoperator, which allocate memory on the heap. Allocating and accessing memory on the heap is generally slower than on the stack, so anarrayis faster than avector, and potentially even faster than a dynamically-allocated C-style array usingnew. On the other hand, the stack is very small, typically only a few MB in size. Therefore, you may only use arrayfor small arrays. For example, adoubletakes up 8 bytes, so anarray<double, 1000000>will use 8 MB, which is more than the stack can hold on most operating systems. In such cases you must usevector(safest option) or allocate memory withnew(fastest option) instead. | 
| As an example, let us convert the the vector operator overload templates to work with arrays. In doing so, we can now also take advantage of the fact that anarraycan be uninitialized to increase performance. Recall that most of the vectoroverloads work by creating a newvectorobject and storing the results of the calculation in that object. However,vectorinitializes itself to zeros. So this means we actually write all the elements twice: once to initialize, and another time to populate with the actual values. Since anarraycan be uninitialized, we can effectively double the performance of these overloads (although this won't be noticeable unless you're adding very large arrays and/or a very large number of small arrays). Furthermore, since the size of the arrayis part of the template type, we do not need to check that the sizes of two arrays match anymore - if they don't, then the compiler will simply not be able to find an appropriate template, as all of the templates in the code assume that both arrays have the same size, and your program won't compile. Therefore, we don't need to define or throw any exceptions in this case. One final simplification is that we don't need to use the size()member function anymore to determine how far loops should go; this is already pre-determined at compilation time as one of the template parameters, which we will callN. These overloads are thus both faster and simpler. The only downsize is that you have to know the size of the arrays at compilation time, and you cannot use arrays that are too big for the stack to handle. We will call the header file array_overloads.hpp: #include <array>
#include <iostream>
using namespace std;
template <typename T, uint64_t N>
ostream &operator<<(ostream &out, const array<T, N> &a)
{
    out << '(';
    for (uint64_t i = 0; i < N - 1; i++)
        out << a[i] << ", ";
    out << a[N - 1] << ')';
    return out;
}
template <typename T, uint64_t N>
array<T, N> operator+(const array<T, N> &a, const array<T, N> &b)
{
    array<T, N> c;
    for (uint64_t i = 0; i < N; i++)
        c[i] = a[i] + b[i];
    return c;
}
template <typename T, uint64_t N>
T operator*(const array<T, N> &a, const array<T, N> &b)
{
    T p = 0;
    for (uint64_t i = 0; i < N; i++)
        p += a[i] * b[i];
    return p;
}
template <typename T, uint64_t N>
array<T, N> operator+=(array<T, N> &a, const array<T, N> &b)
{
    a = a + b;
    return a;
}
template <typename T, uint64_t N>
array<T, N> operator-(const array<T, N> &a)
{
    array<T, N> c;
    for (uint64_t i = 0; i < N; i++)
        c[i] = -a[i];
    return c;
}
template <typename T, uint64_t N>
array<T, N> operator-(const array<T, N> &a, const array<T, N> &b)
{
    array<T, N> c;
    for (uint64_t i = 0; i < N; i++)
        c[i] = a[i] - b[i];
    return c;
}
template <typename T, uint64_t N>
array<T, N> operator-=(array<T, N> &a, const array<T, N> &b)
{
    a = a - b;
    return a;
}
template <typename T, uint64_t N>
array<T, N> operator*(const T &s, const array<T, N> &a)
{
    array<T, N> c;
    for (uint64_t i = 0; i < N; i++)
        c[i] = s * a[i];
    return c;
}
template <typename T, uint64_t N>
array<T, N> operator*(const array<T, N> &a, const T &s)
{
    return s * a;
}
 It can be checked with the following main.cpp: #include <array>
#include <iostream>
#include <stdexcept>
#include "array_overloads.hpp"
using namespace std;
int main()
{
    array<double, 3> a = {1, 2, 3};
    array<double, 3> b = {4, 5, 6};
    array<double, 3> c = {1, 1, 1};
    cout << a + b << '\n';   // Prints "(5, 7, 9)"
    cout << a * b << '\n';   // Prints "32"
    cout << -a << '\n';      // Prints "(-1, -2, -3)"
    a += b;                  //
    cout << a << '\n';       // Prints "(5, 7, 9)"
    cout << a - b << '\n';   // Prints "(1, 2, 3)"
    b -= c;                  //
    cout << b << '\n';       // Prints "(3, 4, 5)"
    cout << 2.0 * a << '\n'; // Prints "(10, 14, 18)"
    cout << a * 3.0 << '\n'; // Prints "(15, 21, 27)"
}
 | 
|  | 
| vectoris the most commonly used STL container. We have already utilized it several times in this course; we introduced it above, defined overloads for it, and used it in our matrix class template. Here we will take a deeper dive into the details of thevectorcontainer.
 The vectorcontainer is essentially an extension of thearraycontainer which:  Allows creating an array whose size is not known at compilation time,Allows dynamically resizing the array after it was created,Allocates and reallocates memory automatically on the heap as needed. Since most of the arrays you are going to create will probably be of a size determined at run time (usually based on the program's input), you will probably use vectorrather thanarrayfor most tasks which require an array. As an extension of array, thevectorcontainer supports all of the member functions and operators we described above forarray, includingat,[],front,back,data,size,empty,begin/cbegin,end/cend, and comparison operators. However, it does not support the member functionfill(since there's no notion of "filling" a variable-sized array), and you cannot useto_array(since it's not anarray). In addition to these, vectorintroduces new functionality that relates to its role as a dynamic array. First we have the member functions which modify the elements of thevector:  clear()clears the contents of the array and resets its size to zero.push_back(value)appendsvalueto the end of the array and increases its size by one.pop_back()removes the last element of the array and decreases its size by one.resize(n)resizes the array tonelements. If this decreases the size, any existing elements beyond the new size are discarded. If this increases the size, any new elements beyond the old size will have their default values (e.g. zero for numbers). Next we have member functions related to the capacity of the vector:  max_size()returns the maximum possible number of elements thevectorcan theoretically store; this is usually of the order of 261 for 64-bit systems, although of course the actual maximum size will be limited by the amount of RAM available on the computer.capacity()returns the number of elements for which thevectorhas allocated memory. This will typically be more then thesizeof thevector, for performance reasons. Resizing avectoris slow, so usually thevectorprefers to preallocate more memory than is needed, in order to allow adding more elements later without spending time on reallocating memory.shrink_to_fit()decreases thecapacityof thevectorto its currentsize, so that the space it takes up in memory is the minimum space required to hold all of the elements currently in the array. Of course, this means adding more elements in the future will be slower, so only use this when you know you will not be adding any more elements, or if you must prioritize memory usage over speed.reserve(n)increases thecapacityof thevectorton, allocating more memory if necessary, while not changing the actualsizeof the vector. Use this when you know you are going to be adding more and more elements gradually up to a known maximum size, to avoid having to reallocate memory multiple times as the elements are added, which could have a significant performance penalty.  Warning: reserveonly reserves memory for additional elements - it does not initialize that memory. The only elements in thevectorwhich are initialized are those in the firstsizeplaces. If you try to access any elements beyond the vector'ssize, you will get garbage - even if you are still within the vector'scapacity! All of the above member functions are illustrated in the following program: #include <cmath>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<uint64_t> v;
    cout.precision(0);
    cout << "Maximum possible size: " << v.max_size()
         << " (approximately " << scientific << (double)v.max_size()
         << " or 2^" << fixed << log2(v.max_size()) << ").\n";
    constexpr uint64_t max_size = 1000;
    uint64_t c = 0;
    for (uint64_t i = 0; i < max_size; i++)
    {
        v.push_back(i);
        if (c != v.capacity())
        {
            c = v.capacity();
            cout << "At size " << v.size() << ", capacity increased to " << c << ".\n";
        }
    }
    cout << "At size " << v.size() << ", capacity is " << v.capacity() << ".\n";
    v.shrink_to_fit();
    cout << "After shrink_to_fit(), size is " << v.size() << " and capacity is " << v.capacity() << ".\n";
    v.pop_back();
    cout << "After pop_back(), size is " << v.size() << " and capacity is " << v.capacity() << ".\n";
    v.resize(2000);
    cout << "After resize(2000), size is " << v.size() << " and capacity is " << v.capacity() << ".\n";
    v.reserve(2222);
    cout << "After reserve(2222), size is " << v.size() << " and capacity is " << v.capacity() << ".\n";
    v.clear();
    cout << "After clear(), size is " << v.size() << " and capacity is " << v.capacity() << ".\n";
}
 The output on my computer is: Maximum possible size: 1152921504606846975 (approximately 1e+18 or 2^60).
At size 1, capacity increased to 1.
At size 2, capacity increased to 2.
At size 3, capacity increased to 4.
At size 5, capacity increased to 8.
At size 9, capacity increased to 16.
At size 17, capacity increased to 32.
At size 33, capacity increased to 64.
At size 65, capacity increased to 128.
At size 129, capacity increased to 256.
At size 257, capacity increased to 512.
At size 513, capacity increased to 1024.
At size 1000, capacity is 1024.
After shrink_to_fit(), size is 1000 and capacity is 1000.
After pop_back(), size is 999 and capacity is 1000.
After resize(2000), size is 2000 and capacity is 2000.
After reserve(2222), size is 2000 and capacity is 2222.
After clear(), size is 0 and capacity is 2222.
 | 
| vectorcontains member functions which modify the elements using iterators:
  insert(pos, value)insertsvalueinto thevectorat the position given by the iteratorpos, with all the elements following that position shifted forward to make space for the new element, so that thesizeof thevectorincreases by1. For example, v.insert(v.begin() + n, value)will insertvalueat element numbern(counting from zero as usual), whilev.insert(v.end() - n, value)will insertvalueat element numbernfrom the end (withn == 0the latter is equivalent topush_back(value)).insert(pos, count, value)insertscountelements with the specifiedvalue.insert(pos, {value1, value2, ...})inserts all the values{value1, value2, ...}in order.erase(pos)erases the element at the position given by the iteratorpos, with all the elements following that position shifted backward, so that thesizeof thevectordecreases by1. erase(first, last)erases all the elements starting from the iteratorfirstand ending at the element before the iteratorlast, i.e. in the range[first, last). These are illustrated below: #include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const vector<T> &v)
{
    for (const T &i : v)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 1, 1, 1, 1};
    cout << "Initial vector:                        ";
    print_elements(v);
    v.insert(v.begin(), 2);
    cout << "v.insert(v.begin(), 2):                ";
    print_elements(v);
    v.insert(v.end(), 3);
    cout << "v.insert(v.end(), 3):                  ";
    print_elements(v);
    v.insert(v.begin() + 2, 3, 4);
    cout << "v.insert(v.begin() + 2, 3, 4):         ";
    print_elements(v);
    v.insert(v.end() - 3, {5, 6, 7});
    cout << "v.insert(v.end() - 3, {5, 6, 7}):      ";
    print_elements(v);
    v.erase(v.begin());
    cout << "v.erase(v.begin()):                    ";
    print_elements(v);
    v.erase(v.begin() + 1, v.begin() + 4);
    cout << "v.erase(v.begin() + 1, v.begin() + 4): ";
    print_elements(v);
}
 The output is: Initial vector:                        1 1 1 1 1
v.insert(v.begin(), 2):                2 1 1 1 1 1
v.insert(v.end(), 3):                  2 1 1 1 1 1 3
v.insert(v.begin() + 2, 3, 4):         2 1 4 4 4 1 1 1 1 3
v.insert(v.end() - 3, {5, 6, 7}):      2 1 4 4 4 1 1 5 6 7 1 1 3
v.erase(v.begin()):                    1 4 4 4 1 1 5 6 7 1 1 3
v.erase(v.begin() + 1, v.begin() + 4): 1 1 1 5 6 7 1 1 3
 It is important to note that after some operations on a vector, old iterators may no longer be valid. This is called iterator invalidation, and is a result of the fact that an iterator is a pointer to a particular address in memory, and some operations change where elements are stored in memory. Specifically, the operations that invalidate iterators are:  clear: Always invalidates all iterators, since it removes all of the elements.reserve,shrink_to_fit: If thevectorchanged itscapacity, all iterators are invalidated, since the elements may now be stored in a completely different location in memory.erase: Invalidates iterators to the erased elements and all following elements.push_back: If thevectorchanged itscapacity, all iterators are invalidated. If not, onlyendis invalidated, since an element has been added at the end.insert: If thevectorchanged itscapacity, all iterators are invalidated. If not, only iterators to the elements at or after the insertion point are invalidated.resize: If thevectorchanged itscapacity, all iterators are invalidated. If not, only iterators to elements that were erased are invalidated.pop_back: Invalidates iterators to the element that was erased and toend. Read-only operations never invalidate any iterators.  Warning: Using invalidated iterators will lead to unexpected results. Always keep track of operations that may invalidate iterators, and redefine iterators if needed.  The following program illustrated iterator invalidation: #include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int32_t> v;
    vector<int32_t>::iterator b = v.begin();
    v.insert(b, 1);
    v.insert(b, 1); // WARNING: Will crash, since b has been invalidated!
}
 | 
| In the next section, I would like to compare the time it would take to populate vectors with values in different ways. For that, I need to introduce an accurate way of measuring the execution time of various operations. C++ provides this functionality with the chronolibrary, available in the header file<chrono>. Note that C++ also provides thetimefunction from C, available in the header file<ctime>. However,timeonly measures time in whole seconds, so it cannot be used for accurate time measurements. We will not discuss the full scope of the chronolibrary here; instead we will just mention the essential information we will need in order to measure execution time. chronoincludes the classchrono::steady_clock, which represents a monotonic clock. Other clocks (e.g.chrono::system_clock) are not monotonic, since the time on the clock may be freely adjusted, either automatically by the operating system (e.g. when synchronizing the clock with other computers) or manually by the user. This means that the value of the clock at a later time may turn out to actually correspond to an earlier time. In contrast,chrono::steady_clockis monotonic, which means it is guaranteed to never decrease, and thus it can be used to measure time intervals reliably.
 chrono::steady_clockonly has one member function:now, which returns the current value of the clock. The return type ischrono::time_point, a class template which represents a point in time. The template's argument is the type of clock used, so the full class would bechrono::time_point<chrono::steady_clock>, but in some cases the argument can be inferred automatically by the compiler.
 chrono::time_pointhas the operator-overloaded, so once we have two time points, we can simply calculate the elapsed time by subtracting them. The class templatechrono::durationis used to represent a time interval (in contrast with a time point). The template allows us to choose what kind of data type we want to represent the interval with;chrono::duration<double>is usually a good choice, but integer types can be used if we only care about integer values.
 An optional second template argument to chrono::durationallows us to choose the units. The default is seconds, but one can also usenano,micro, ormillito represent nanoseconds, microseconds, or milliseconds respectively; for example,chrono::duration<double, micro>. Once we have achrono::durationobject, we can use the member functioncountto get the numerical value of the duration in the chosen units. Finally, the function template chrono::duration_castcan be used to convert achrono::durationto different units, where the argument is a specificchrono::duration, for examplechrono::duration_cast<chrono::duration<double, nano>>. This is demonstrated in the following program, which measures how much time it takes to add the first 100 million integers (note that the notation 100'000'000is just for human readability; the's are ignored): #include <chrono>
#include <iostream>
using namespace std;
int main()
{
    chrono::time_point start_time = chrono::steady_clock::now();
    uint64_t sum = 0;
    for (uint64_t i = 0; i <= 100'000'000; i++)
    {
        sum += i;
    }
    chrono::time_point end_time = chrono::steady_clock::now();
    cout << "Sum: " << sum << '\n';
    chrono::duration<double> elapsed_time_seconds = end_time - start_time;
    chrono::duration<double, milli> elapsed_time_milli = end_time - start_time;
    cout << "Elapsed time: " << elapsed_time_seconds.count() << " seconds, ";
    cout << elapsed_time_milli.count() << " milliseconds, ";
    cout << (chrono::duration_cast<chrono::duration<double, micro>>(elapsed_time_seconds)).count() << " microseconds, or ";
    cout << (chrono::duration_cast<chrono::duration<int64_t, nano>>(elapsed_time_seconds)).count() << " nanoseconds.\n";
}
 The output on my computer is: Sum: 5000000050000000
Elapsed time: 0.0928417 seconds, 92.8417 milliseconds, 92841.7 microseconds, or 92841700 nanoseconds.
 For convenience, I created a simple class that can be used to easily keep track of the execution times of various operations. Save the class in its own header file timer.hpp: #include <chrono>
using namespace std;
class timer
{
public:
    void start()
    {
        start_time = chrono::steady_clock::now();
    }
    void end()
    {
        elapsed_time = chrono::steady_clock::now() - start_time;
    }
    double seconds() const
    {
        return elapsed_time.count();
    }
private:
    chrono::time_point<chrono::steady_clock> start_time = chrono::steady_clock::now();
    chrono::duration<double> elapsed_time = chrono::duration<double>::zero();
};
 When a timerobject is initialized, the current time is saved in the member variablestart_time, andelapsed_timeis initialized to zero (note that we can't just write0, we have to usechrono::duration<double>::zero()which stands for adurationof zero). Then, when the member functionend()is called, the elapsed time is stored inelapsed_time. The elapsed time in seconds can then be read usingseconds(). The timer can be restarted by calling the member functionstart(). Here is the previous program, converted to using the timerclass: #include <iostream>
#include "timer.hpp"
using namespace std;
int main()
{
    timer t;
    uint64_t sum = 0;
    for (uint64_t i = 0; i <= 100'000'000; i++)
    {
        sum += i;
    }
    t.end();
    cout << "Sum: " << sum << '\n';
    cout << "Elapsed time: " << t.seconds() << " seconds.\n";
}
 | 
| To illustrate some potential performance issues when using vector, let us run a program comparing execution time for various methods of populating avectorwith values. In the last step we will use dynamic memory allocation, even though we haven't learned it yet, just to illustrate the performance gains from not initializing the vector; we will not explain how this works, but don't worry, we will explain it in detail later. #include <iostream>
#include <vector>
#include "timer.hpp"
using namespace std;
void compare_durations(const timer &t1, const timer &t2)
{
    cout << "This was " << t1.seconds() - t2.seconds() << " seconds shorter than the previous operation, making it ";
    cout << (t1.seconds() - t2.seconds()) / t1.seconds() * 100 << "% faster.\n";
}
int main()
{
    constexpr uint64_t size = 300'000'000;
    cout.precision(2);
    cout << fixed;
    // Write 8 * s bytes to memory starting with an empty vector of size zero.
    // The vector will increase in capacity multiple times during the loop's execution, causing significant slowdown.
    timer v1_t;
    {
        vector<uint64_t> v1;
        for (uint64_t i = 0; i < size; i++)
            v1.push_back(i);
    }
    v1_t.end();
    cout << "With push_back() but without reserve(), the operation took " << v1_t.seconds() << " seconds.\n";
    // This time we still start with an empty vector of size zero, but preallocate the required memory using reserve().
    // The vector will NOT increase in capacity during the loop's execution. However, there is still significant overhead.
    timer v2_t;
    {
        vector<uint64_t> v2;
        v2.reserve(size);
        for (uint64_t i = 0; i < size; i++)
            v2.push_back(i);
    }
    v2_t.end();
    cout << "With both push_back() and reserve(), the operation took " << v2_t.seconds() << " seconds.\n";
    compare_durations(v1_t, v2_t);
    // This time we start with a vector already of size s, initialized to zeros.
    // Populating is now much faster, but we also need to take the initialization time into account.
    // Initialization is wasted time in this case, since we are populating the vector with other values anyway.
    timer v3_t;
    {
        vector<uint64_t> v3(size);
        for (uint64_t i = 0; i < size; i++)
            v3[i] = i;
    }
    v3_t.end();
    cout << "With pre-initialized vector and direct access to elements, the operation took " << v3_t.seconds() << " seconds.\n";
    compare_durations(v2_t, v3_t);
    // Finally, we do the same with a manually-allocated C-style array.
    // This will, of course, be the fastest operation, since:
    // 1. We do not waste time initializing twice,
    // 2. We modify the elements directly, so there is no overhead due to push_back().
    timer a_t;
    {
        int64_t *const a = new int64_t[size];
        for (uint64_t i = 0; i < size; i++)
            a[i] = i;
        delete[] a;
    }
    a_t.end();
    cout << "With manually-allocated C-style array, the operation took " << a_t.seconds() << " seconds.\n";
    compare_durations(v3_t, a_t);
}
 Note how each vectoris defined within its own{}code block, so that its memory is deallocated once the code block ends and thevectorgoes out of scope. This ensures that we do not use too much memory, as eachvectoroccupies 2.5 GB of memory. For the C-style array, we have to deallocate memory manually as will be explained later. The output on my computer is: With push_back() but without reserve(), the operation took 3.16 seconds.
With both push_back() and reserve(), the operation took 2.60 seconds.
This was 0.56 seconds shorter than the previous operation, making it 17.76% faster.
With pre-initialized vector and direct access to elements, the operation took 1.43 seconds.
This was 1.17 seconds shorter than the previous operation, making it 45.08% faster.
With manually-allocated C-style array, the operation took 0.60 seconds.
This was 0.83 seconds shorter than the previous operation, making it 57.85% faster.
 For v1, we started from an emptyvector, and did not preallocate memory. This means that thevectorhad to be resized and memory had to be reallocated multiple times during the loop's execution - every time the size of thevectorexceeded the next power of 2. Reallocating memory usually also involves copying the elements to a completely different location in memory, which is a time-consuming operation. This is whyv1is the slowest to populate. For v2, we again started from an emptyvector, but this time we preallocated memory in advance usingreserve. This provided a mild boost to performance, since no reallocations were necessary. However,v2is still excruciatingly slow; there is still some overhead due to the fact thatpush_backdoes some operations behind the scenes every single time is it called, such as checking the vector'scapacityand increasing itssize. Note also that usingreservewas possible in this case because we knew the maximum size of thevectorin advance, but in other situations we may not have that information ahead of time. For v3, instead of starting from an emptyvectorand growing it gradually, we started from avectorof sizes, initialized (automatically) to zeros. In addition to eliminating the need to reallocate memory as thevectoris populated, this also eliminated the overhead due topush_back. This results in a significant boost to performance. However, note that we effectively initializedv3twice: once to zeros, and then a second time to the values we actually want it to have. Unfortunately, since avectorcannot be in an uninitialized state, this is the best we can do. Lastly, for a, which is not avectorbut a manually-allocated C-style array (again, we will learn exactly how this works later), we eliminated the need to initialize twice, since C-style arrays can be uninitialized. This, unsurprisingly, results in around half the execution time and a very significant boost to performance - since we are initializing the elements only once, instead of twice! As a side note, the above numbers may change drastically when using compiler optimizations, as we will learn later in the course, or when using another compiler. However, the numbers would still generally be in decreasing order; v1is always the slowest, andais always the fastest. | 
|  | 
| To streamline the rest of this chapter, I would like to write a generic function template which will print out all of the elements in a container, whether it's an array, avector, or any other container, and no matter what kind of data type it holds. This turns out to be a bit tricky. First of all, no template can match both array<A, B>andvector<A>for anyAandB. I can either write something like template <typename T, typename A>
void print_elements(const T<A> &c)
 which will capture containers with one argument, or something like template <typename T, typename A, typename B>
void print_elements(const T<A, B> &c)
 which will capture containers with two arguments. Therefore, I must instead write something like template <typename T>
void print_elements(const T &c)
 where now Tcan be any container. However, in this case, how will I know which type is stored in the container? The solution is as follows: #include <array>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    array<double, 5> a = {1.1, 2.2, 3.3, 4.4, 5.5};
    vector<char> v = {'a', 'b', 'c', 'd', 'e'};
    print_elements(a);
    print_elements(v);
}
 Here, Twill be replaced with the container, which would bearray<double, 5>foraandvector<char>forv. However, because I don't know what the actual data type inside the container is, I used the keywordautoin theforloop. Whenever the compiler seesauto, it replaces it with an automatically inferred data type. In this case,autowill be replaced withdoubleforaand withcharforv. In fact, you could in principle (although it is not recommended, see warnings below) use autoanywhere you want, even if you do not use templates, as long as you initialize the variable with a value from which the type can be inferred. For example: #include <cmath>
#include <iostream>
using namespace std;
int main()
{
    // Will automatically detect that x is a double.
    auto x = 1.9;
    // Will automatically detect that y is an int.
    auto y = 2;
    // Will automatically detect that z is a double, since that is the default output type of sqrt().
    auto z = sqrt(2);
    cout << "x = " << x << ", y = " << y << ", z = " << z;
}
  Warning: Although using autois very convenient, mentioning the type explicitly will make your program clearer to the reader and avoid any possible confusion regarding the types of variables. It should only be used in cases where the type must be automatically inferred, as with templates of templates, or when you just want to write a few temporary lines of code to quickly test something. In all other cases,autowill decrease your code's readability, and should be avoided!  Warning: Using automay lead to unexpected behavior. For example,auto x = 1will detectxto be anint, that is, asigned32-bit integer (on most systems), even though we might actually wantxto beunsigned, have a different bit width, or even be a floating-point variable. Again, you should not useautounless it is absolutely necessary. | 
| A deque(pronounced like "deck", short for double-ended queue), defined in the header file<deque>, is similar to avectoris that it is a linear sequence of elements which can be accessed, inserted, and deleted. However, unlike avector, the elements of adequeare not stored contiguously in memory. Instead, they are usually distributed among several different individually-allocated memory blocks. When inserting elements at the end of a vector, if itscapacityis exceeded, it may need to reallocate memory - which, as we've discussed, could mean copying the entire array to a new place in memory. Similarly, inserting elements at the beginning of avectormeans the entire array needs to be shifted forward. In both cases, inserting elements into avectorcan incur a significant performance penalty. On the other hand, inserting elements to a dequeis much faster, since it never copies or shifts elements - it simply puts inserted elements in different locations in memory as needed. Therefore,dequeis preferred tovectorif you plan to do many insertions and deletions. However, dequehas two significant drawbacks. The first is that, since the elements are not stored contiguously in memory, accessing them is more costly. To access an element of avectorwe simply need to dereference a pointer, but to access an element of adequewe need to dereference two pointers: one to the specific block where the element is located, and the other to the element's location in that block. The second drawback is that sincedequeallocates different memory blocks, it uses more memory than avector. dequesupports most of the member functions and operators supported byarray, including includingat,[],front,back,size,empty,begin/cbegin,end/cend, and comparison operators. However, likevector, it does not supportfillorto_array. It also does not supportdata, since there is no contiguous array to return a pointer to.
 In addition, dequesupports member functions supported byvector, includingclear,push_back,pop_back,resize,max_size,shrink_to_fit,insert,erase,emplace, andemplace_back. However, it does not supportcapacityorreservesince it allocates memory in a different way. Perhaps the biggest advantage of dequeis its ability to efficiently insert and remove elements not just at the end but also at the beginning of the container, without shifting or copying. For this reason, it adds the following new member functions:  push_frontinserts an element at the beginning.pop_frontremoves an element from the beginning.emplace_frontconstructs an element in place at the beginning. The following program illustrates the difference between dequeandvector: #include <deque>
#include <iomanip>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements_and_addresses(const T &d)
{
    cout << "Address       | Value\n";
    for (const auto &i : d)
        cout << setw(13) << &i << " | " << i << '\n';
}
int main()
{
    cout << left;
    cout << "Deque:\n";
    deque<int32_t> d = {1, 2};
    print_elements_and_addresses(d);
    d.push_back(3);
    print_elements_and_addresses(d);
    d.push_front(0);
    print_elements_and_addresses(d);
    cout << "\nVector:\n";
    vector<int32_t> v = {1, 2};
    print_elements_and_addresses(v);
    v.push_back(3);
    print_elements_and_addresses(v);
    v.insert(v.begin(), 0);
    print_elements_and_addresses(v);
}
 Here we expanded print_elements()into a functionprint_elements_and_addresses()which prints out the memory addresses of each element as well. Here is one possibility for the output: Deque:
Address       | Value
0x1bc72ca6ab0 | 1
0x1bc72ca6ab4 | 2
Address       | Value
0x1bc72ca6ab0 | 1
0x1bc72ca6ab4 | 2
0x1bc72ca6ab8 | 3
Address       | Value
0x1bc72ca6edc | 0
0x1bc72ca6ab0 | 1
0x1bc72ca6ab4 | 2
0x1bc72ca6ab8 | 3
Vector:
Address       | Value
0x1bc72c897d0 | 1
0x1bc72c897d4 | 2
Address       | Value
0x1bc72c88bb0 | 1
0x1bc72c88bb4 | 2
0x1bc72c88bb8 | 3
Address       | Value
0x1bc72c88bb0 | 0
0x1bc72c88bb4 | 1
0x1bc72c88bb8 | 2
0x1bc72c88bbc | 3
 Note that dequekeeps the elements in the exact same place in memory throughout the run time of the program. For example,1is always located at0x1bc72ca6ab0and2is always located at the subsequent address0x1bc72ca6ab4. Adding 3at the end places it at the subsequent memory address0x1bc72ca6ab8. However, adding0at the beginning places it at a completely different memory block, at the address0x1bc72ca6edc, which is actually after the address of3. We see that the array is definitely not contiguous. Meanwhile, vectorinitially places1and2at the addresses0x1bc72c897d0and0x1bc72c897d4respectively, but when we add3at the end, it actually copies the entire array to a different memory block starting at0x1bc72c88bb0(recall that above we saw that reallocation happens when the size exceeds the next power of 2). Then, when we add 0at the beginning, it is added at the beginning of the block, at0x1bc72c88bb0, and the other three elements are shifted to make place for it, preserving the contiguity of the array - at the cost of rewriting the entire array, which can significantly hurt performance for large arrays! | 
| The two list containers, forward_listandlist, are sequence containers that can store each element in a completely different location in memory - essentially, the complete opposite of avector. As you might expect, performance is opposite as well: accessing an element is slow, but inserting elements is fast. In a forward_list, each element in the sequence contains information about the location of the next element, so that they are all linked together, but only if you're going forward. In alist, each element in the sequence contains information about the location of both the next element and the previous element, so you can go both forward and backward. Due to the nature of these containers, the elements are not numbered, so you cannot access elements using []orat. Instead, the only way to specifically access element numbernis to start from the beginning and work your way up using iterators. Furthermore, if usingforward_list, there are nopush_back,pop_back, orsizemember functions; aforward_listdoesn't know where its last member is, or even how large it is. The following program is similar to the one we used above for deque, and demonstrates how the elements of alistorforward_listare stored in memory: #include <forward_list>
#include <iomanip>
#include <iostream>
#include <list>
using namespace std;
template <typename T>
void print_elements_and_addresses(const T &d)
{
    cout << "Address       | Value\n";
    for (const auto &i : d)
        cout << setw(13) << &i << " | " << i << '\n';
}
int main()
{
    cout << left;
    cout << "Forward List:\n";
    forward_list<int32_t> f = {1, 2};
    print_elements_and_addresses(f);
    f.insert_after(++f.begin(), 3);
    print_elements_and_addresses(f);
    f.push_front(0);
    print_elements_and_addresses(f);
    cout << "\nList:\n";
    list<int32_t> l = {1, 2};
    print_elements_and_addresses(l);
    l.push_back(3);
    print_elements_and_addresses(l);
    l.push_front(0);
    print_elements_and_addresses(l);
}
 One possible output on my computer is: Forward List:
Address       | Value
0x22987a49778 | 1
0x22987a49868 | 2
Address       | Value
0x22987a49778 | 1
0x22987a49868 | 2
0x22987a498a8 | 3
Address       | Value
0x22987a49968 | 0
0x22987a49778 | 1
0x22987a49868 | 2
0x22987a498a8 | 3
List:
Address       | Value
0x22987a499b0 | 1
0x22987a49a00 | 2
Address       | Value
0x22987a499b0 | 1
0x22987a49a00 | 2
0x22987a48b50 | 3
Address       | Value
0x22987a48ba0 | 0
0x22987a499b0 | 1
0x22987a49a00 | 2
0x22987a48b50 | 3
 I won't go into any more details about these containers, but you can find more information in the C++ reference and Microsoft's C++ reference. | 
|  | 
| A set, defined in the header file<set>, is simply a set (as in mathematical set theory) to which elements can be added in no particular order. An element can either be or not be in a set, and that's it. You insert an element withinsert(value)and check if it's in the set withcount(which will return either0or1, since each element is unique). You can go over the elements of the set with iterators or range-for loops as usual. The elements in a setare ordered lexicographically, which is useful for many algorithms. For example, if you're searching forCarolstarting from the beginning, and you've reachedDave, you immediately knowCarolis not in theset, since she should have appeared beforeDave. However, this also means inserting elements is slow, since every insertion also automatically sorts theset. A multiset, also defined in the header file<set>, is similar to aset, except that there can now be multiple elements with the same value. Therefore,countcan now be any non-negative integer.unordered_setandunordered_multiset, defined in the header file<unordered_set>, are similar tosetandmultiset, except that the elements are unordered. This is all illustrated in the following program: #include <iomanip>
#include <iostream>
#include <set>
#include <string>
#include <unordered_set>
using namespace std;
template <typename T>
void print_elements_and_addresses(const T &d)
{
    cout << "Address       | Value\n";
    for (const auto &i : d)
        cout << setw(13) << &i << " | " << i << '\n';
}
template <typename T>
void is_invited(const T &i, const string &s)
{
    if (i.count(s) > 1)
        cout << s << " has been invited " << i.count(s) << " times.\n";
    else if (i.count(s) == 1)
        cout << s << " has been invited once.\n";
    else
        cout << s << " has not been invited.\n";
}
int main()
{
    cout << left;
    cout << "Set:\n";
    set<string> invited_set;
    invited_set.insert("Alice");
    invited_set.insert("Carol");
    // A set is automatically sorted, so Bob will be inserted before Carol
    invited_set.insert("Bob");
    // A set does not allow multiple elements with the same name
    // Therefore, this has no effect
    invited_set.insert("Alice");
    print_elements_and_addresses(invited_set);
    is_invited(invited_set, "Alice");
    is_invited(invited_set, "Bob");
    is_invited(invited_set, "Dave");
    cout << "\nMultiset:\n";
    multiset<string> invited_multiset;
    invited_multiset.insert("Alice");
    invited_multiset.insert("Carol");
    // A multiset is automatically sorted, so Bob will be inserted before Carol
    invited_multiset.insert("Bob");
    // A multiset allows multiple elements with the same name, so Alice will appear twice
    // Both instances will be the top of the list, since it is sorted
    invited_multiset.insert("Alice");
    print_elements_and_addresses(invited_multiset);
    is_invited(invited_multiset, "Alice");
    is_invited(invited_multiset, "Bob");
    is_invited(invited_multiset, "Dave");
    cout << "\nUnordered Set:\n";
    unordered_set<string> invited_unordered_set;
    invited_unordered_set.insert("Alice");
    invited_unordered_set.insert("Carol");
    // An unordered_set is not automatically sorted, so this time Bob will be inserted after Carol
    invited_unordered_set.insert("Bob");
    // An unordered_set does not allow multiple elements with the same name
    // However, this will move Alice to the bottom
    invited_unordered_set.insert("Alice");
    print_elements_and_addresses(invited_unordered_set);
    is_invited(invited_unordered_set, "Alice");
    is_invited(invited_unordered_set, "Bob");
    is_invited(invited_unordered_set, "Dave");
    cout << "\nUnordered Multiset:\n";
    unordered_multiset<string> invited_unordered_multiset;
    invited_unordered_multiset.insert("Alice");
    invited_unordered_multiset.insert("Carol");
    // An unordered_multiset is not automatically sorted, so this time Bob will be inserted after Carol
    invited_unordered_multiset.insert("Bob");
    // A unordered_multiset allows multiple elements with the same name, so Alice will appear twice
    // Both instances will appear in the bottom of the list
    invited_unordered_multiset.insert("Alice");
    print_elements_and_addresses(invited_unordered_multiset);
    is_invited(invited_unordered_multiset, "Alice");
    is_invited(invited_unordered_multiset, "Bob");
    is_invited(invited_unordered_multiset, "Dave");
}
 On my computer, one possible output is: Set:
Address       | Value
0x21f5ae69d00 | Alice
0x21f5ae68bb0 | Bob
0x21f5ae66a20 | Carol
Alice has been invited once.
Bob has been invited once.
Dave has not been invited.
Multiset:
Address       | Value
0x21f5ae6a400 | Alice
0x21f5ae688f0 | Alice
0x21f5ae68880 | Bob
0x21f5ae6a470 | Carol
Alice has been invited 2 times.
Bob has been invited once.
Dave has not been invited.
Unordered Set:
Address       | Value
0x21f5ae86d58 | Bob
0x21f5ae86cf8 | Carol
0x21f5ae68c08 | Alice
Alice has been invited once.
Bob has been invited once.
Dave has not been invited.
Unordered Multiset:
Address       | Value
0x21f5ae86f18 | Bob
0x21f5ae86eb8 | Carol
0x21f5ae86f78 | Alice
0x21f5ae86db8 | Alice
Alice has been invited 2 times.
Bob has been invited once.
Dave has not been invited.
 | 
| A map, defined in the header<map>, holds key-value pairs, such that the values can be accessed via the corresponding keys. Think of the keys as a generalization of array indices; instead of accessing an element via its corresponding index - a non-negative integer - it can be accessed via any object of any type, fundamental or user-defined. You can also think of amapas a dictionary which translates one object to another. There also also three additional containers, similar to the set containers. multimap, also in<map>, permits multiple values assigned to the same key.unordered_mapandunordered_multimap, in<unordered_map>, are the unordered versions. To declare a map, we use the following syntax: map<key_type, value_type> name;
map<key_type, value_type> name = {{key1, value1}, {key2, value2}, ...};
 key_typeis the data type used for the keys, andvalue_typeis the data type used for the values. The first line creates an emptymap, while the second line creates a map and initializes it with some pairs of keys and values. To assign a value to a key, or to read the value assigned to a key, we simply use the[]operator. As in some other containers, we can also use theatmember function, which throwsout_of_rangeif the key does not exist.
 Whether we use []orat, the return value will be an object of the class templatepair<key_type, value_type>. The memberfirstcan then be used to access the key and the membersecondto access the value. This is demonstrated in the following example: #include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <unordered_map>
using namespace std;
template <typename T>
void print_map(const T &m)
{
    cout << "Address       | Key   | Value\n";
    for (const auto &i : m)
        cout << setw(13) << &i << " | " << setw(5) << i.first << " | " << i.second << '\n';
}
template <typename T>
void age_lookup(const T &ages, const string &name)
{
    cout << name << "'s age is ";
    try
    {
        cout << ages.at(name) << ".\n";
    }
    catch (const out_of_range &e)
    {
        cout << "not listed.\n";
    }
}
int main()
{
    cout << left;
    cout << "Initial list:\n";
    map<string, uint16_t> ages = {{"Alice", 25}, {"Carol", 23}};
    print_map(ages);
    cout << "\nAfter adding Bob:\n";
    ages["Bob"] = 34;
    print_map(ages);
    cout << '\n';
    age_lookup(ages, "Alice");
    age_lookup(ages, "Dave");
    cout << "\nAfter looking up Dave:\n";
    print_map(ages);
    // Note: Looking up Dave with .at() did not modify the map, but looking up Eve with [] will add a new key! Since the value associated with the key "Eve" was undefined, it will obtain the default value of zero.
    if (ages["Eve"] != 0)
        cout << "Eve's age is set.";
    cout << "\nAfter looking up Eve:\n";
    print_map(ages);
}
 On my computer, one possible output is: Initial list:
Address       | Key   | Value
0x11a4cc197b0 | Alice | 25
0x11a4cc18b60 | Carol | 23
After adding Bob:
Address       | Key   | Value
0x11a4cc197b0 | Alice | 25
0x11a4cc18be0 | Bob   | 34
0x11a4cc18b60 | Carol | 23
Alice's age is 25.
Dave's age is not listed.
After looking up Dave:
Address       | Key   | Value
0x11a4cc197b0 | Alice | 25
0x11a4cc18be0 | Bob   | 34
0x11a4cc18b60 | Carol | 23
After looking up Eve:
Address       | Key   | Value
0x11a4cc197b0 | Alice | 25
0x11a4cc18be0 | Bob   | 34
0x11a4cc18b60 | Carol | 23
0x11a4cc1a480 | Eve   | 0
 You can find more information about associative containers in the C++ reference and Microsoft's C++ reference. | 
| The C++ standard template library contains many useful function templates to implement a variety of algorithms on containers. They are all included in the <algorithm>header file. The algorithms are completely generic, since they are templates, and they utilize iterators. This means that any algorithm can be applied to values of any data type, stored in any kind of container that works with iterators - which includes all of the STL containers, as well as one you can write yourself. We will now list some of these algorithms. | 
| Many STL algorithms have the following syntax: algorithm(first, last, function);
 functionis a function that takes the appropriate number of arguments, usually one or two depending on what the algorithm does, and returns the appropriate return value. If the return value isbool, which is often the case, then the function is also called a predicate.
 firstis an iterator pointing to the first element on which we would like to executefunction, andlastis an iterator pointing to the element after the last element on which we would like to executefunction. This means that the algorithm will act on the range[first, last), i.e. inclusive offirstbut not inclusive oflast. If we want to act on the entire containerc, we simply use the syntaxalgorithm(c.begin(), c.end(), function).
 The function itself can be defined as an actual function, but in modern C++ we often specify it inline using a lambda expression or anonymous function. The simplest lambda functions have the following syntax: [](type1 arg1, type2 arg2, ...) { /* code */ }
 []is called a capture clause; you can specify some options inside the square brackets, but we won't discuss that here (see here and here for more information). The list inside the round brackets is the usual function argument list, and the code inside the curly brackets is the usual function code.
 | 
| The following algorithms act on sequences, but do not modify them.  all_of,any_of, andnone_of: all_of(first, last, predicate);
any_of(first, last, predicate);
none_of(first, last, predicate);
 Return trueif all, any, or none of the elements (respectively) in the range[first, last)satisfypredicate. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
bool is_even(const uint32_t &n)
{
    return (n % 2) == 0;
}
int main()
{
    vector<uint32_t> v = {1, 2, 27, 5, 8, 9, 5, 12, 5, 13, 15};
    cout << boolalpha;
    cout << "All numbers are even? " << all_of(v.begin(), v.end(), is_even) << '\n';
    cout << "Any numbers are even? " << any_of(v.begin(), v.end(), is_even) << '\n';
    cout << "No numbers are even? " << none_of(v.begin(), v.end(), is_even) << '\n';
}
 Output: All numbers are even? false
Any numbers are even? true
No numbers are even? false
 
 count(first, last, value);
count_if(first, last, predicate);
 Count how many elements in the range [first, last)are equal tovalueor satisfypredicaterespectively. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<uint32_t> v = {1, 2, 27, 5, 8, 9, 5, 12, 5, 13, 15};
    cout << "Number of times 5 appears in the container: ";
    cout << count(v.begin(), v.end(), 5);
    cout << "\nNumber of elements divisible by 3 in the container: ";
    cout << count_if(v.begin(), v.end(), [](const uint32_t &n) { return (n % 3) == 0; });
}
 Output: Number of times 5 appears in the container: 3
Number of elements divisible by 3 in the container: 4
 
  find,find_if, andfind_if_not: find(first, last, value);
find_if(first, last, predicate);
find_if_not(first, last, predicate);
 Find the first element in the range [first, last)which equalsvalue, satisfiespredicate, or does not satisfypredicaterespectively. Return an iterator to the element, or the input argumentlastif the element was not found. Example code: #include <algorithm>
#include <cctype>
#include <iostream>
#include <vector>
using namespace std;
void find_letter(const vector<char> &v, const char &c)
{
    vector<char>::const_iterator i = find(v.begin(), v.end(), c);
    if (i != v.end())
        cout << "Found letter " << *i << ".\n";
    else
        cout << "Did not find letter " << c << ".\n";
}
void find_first_digit(const vector<char> &v)
{
    vector<char>::const_iterator i = find_if(v.begin(), v.end(), [](const char &c) { return isdigit(c); });
    if (i != v.end())
        cout << "Found first digit: " << *i << ".\n";
    else
        cout << "Did not find any digits .\n";
}
int main()
{
    vector<char> v = {'a', 'g', '4', 'k', '1', 'b', '6'};
    find_letter(v, 'g');
    find_letter(v, 'c');
    find_first_digit(v);
}
 Output: Found letter g.
Did not find letter c.
Found first digit: 4.
 
 for_each(first, last, function);
 Applies functionto each element in the range[first, last). Note that this is formally classified as a non-modifying algorithm, but iffunctiongets the elements of the sequence by reference, it can nonetheless modify them. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<uint32_t> v = {2, 4, 8, 16, 32};
    for_each(v.begin(), v.end(), [](uint32_t &n) { n++; });
    for_each(v.begin(), v.end(), [](const uint32_t &n) { cout << n << ' '; });
}
 Output: 3 5 9 17 33
 | 
| The following algorithms act on sequences and modify them. copy(first, last, destination);
copy_if(first, last, destination, predicate);
 Copy the elements in the range [first, last)to the location pointed to by the iteratordestination. The destination can be in the same container or another container, but it can't be betweenfirstandlast. If usingcopy_if, only the elements satisfyingpredicatewill be copied. Note that copyandcopy_ifoverwrite the existing elements of the target container; they cannot enlarge the container or shift elements around. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 2, 3, 4};
    vector<uint32_t> w = {5, 6, 7, 8, 9, 10};
    // Copy all of v to the beginning of w
    copy(v.begin(), v.end(), w.begin());
    print_elements(w);
    // Copy only the even numbers in v to the beginning of w
    copy_if(v.begin(), v.end(), w.begin(), [](const uint32_t &n) { return (n % 2) == 0; });
    print_elements(w);
}
 Output: 1 2 3 4 9 10
2 4 3 4 9 10
 
 fill(first, last, value);
 Fills the elements in the range [first, last)withvalue. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 2, 3, 4};
    fill(v.begin(), v.end(), 5);
    print_elements(v);
}
 Output: 5 5 5 5
 
 remove(first, last, value);
remove_if(first, last, predicate);
 Remove the elements in the range [first, last)which are equal tovalueor satisfypredicaterespectively. Return an iterator pointing to the newendof the range. Note that the result will be a container of the same size, so typically you use the container'serasemember function with the return value ofremoveas the first argument (i.e. the beginning of the range to be removed) and theendof the container as the second argument. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 2, 3, 4, 5, 6, 7, 8};
    // Remove the number 4
    v.erase(remove(v.begin(), v.end(), 4), v.end());
    print_elements(v);
    // Remove all odd numbers
    v.erase(remove_if(v.begin(), v.end(), [](const uint32_t &n) { return (n % 2) != 0; }), v.end());
    print_elements(v);
}
 Output: 1 2 3 5 6 7 8
2 6 8
 
 replace(first, last, old_value, new_value);
replace_if(first, last, predicate, new_value);
 Replace any elements in the range [first, last)which are equal toold_valueor satisfypredicaterespectively. These elements are replaced withnew_value. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 2, 3, 4, 5, 6, 7, 8};
    // Replace 4 with 0
    replace(v.begin(), v.end(), 4, 0);
    print_elements(v);
    // Replace all odd numbers with 10
    replace_if(v.begin(), v.end(), [](const uint32_t &n) { return (n % 2) != 0; }, 10);
    print_elements(v);
}
 Output: 1 2 3 0 5 6 7 8
10 2 10 0 10 6 10 8
 
 swap(a, b);
swap_ranges(first, last, destination);
 swapswaps the values of the objectsaandb, andswap_rangesswaps the elements in the range[first, last)with an equal number of elements starting at the iteratordestination.
 Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 2, 3, 4, 5, 6, 7, 8};
    // Swap the positions of the first two elements
    swap(v[0], v[1]);
    print_elements(v);
    // Swap the first three elements with the last three elements
    swap_ranges(v.begin(), v.begin() + 3, v.end() - 3);
    print_elements(v);
}
 Output: 2 1 3 4 5 6 7 8
6 7 8 4 5 2 1 3
 
 reverse(first, last);
 Reverses the order of the elements in the range [first, last). Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    vector<uint32_t> v = {1, 2, 3, 4, 5, 6, 7, 8};
    reverse(v.begin(), v.end());
    print_elements(v);
}
 Output: 8 7 6 5 4 3 2 1
 | 
| sort(first, last);
is_sorted(first, last);
 sortsorts the elements in the range[first, last)in non-descending order.is_sortedchecks if the elements in the range[first, last)are sorted.
 Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    cout << boolalpha;
    vector<uint32_t> v = {1, 2, 3, 2, 2, 3, 1, 4, 2};
    print_elements(v);
    cout << "Is sorted? " << is_sorted(v.begin(), v.end()) << '\n';
    sort(v.begin(), v.end());
    print_elements(v);
    cout << "Is sorted? " << is_sorted(v.begin(), v.end()) << '\n';
}
 Output: 1 2 3 2 2 3 1 4 2
Is sorted? false
1 1 2 2 2 2 3 3 4
Is sorted? true
 
  max,max_element,min, andmin_element: max(a, b);
max({a, b, c, ...});
max_element(first, last);
min(a, b);
min({a, b, c, ...});
min_element(first, last);
 maxreturns the larger of the two objectsaandbor the largest of the objects in the initializer list{a, b, c, ...}.max_elementreturns an iterator to the largest element in the range[first, last).minandmin_elementdo the same for the smallest object or element respectively.
 Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<uint32_t> v = {1, 4, 2, 7, 3, 5};
    cout << "Smallest element: " << *min_element(v.begin(), v.end()) << '\n';
    cout << "Largest element: " << *max_element(v.begin(), v.end()) << '\n';
}
 Output: Smallest element: 1
Largest element: 7
 
 is_permutation(first1, last1, first2);
is_permutation(first1, last1, first2, last2);
 Returns trueif the elements in the range[first1, last1)are a permutation of the elements in the range[first2, last2), or in a range of the same size starting fromfirst2iflast2was not given. Example code: #include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
void print_elements(const T &c)
{
    for (const auto &i : c)
        cout << i << ' ';
    cout << '\n';
}
int main()
{
    cout << boolalpha;
    vector<uint32_t> v = {1, 2, 3, 4};
    vector<uint32_t> w = {3, 2, 4, 1};
    vector<uint32_t> u = {2, 4, 3, 5};
    cout << "Is w a permutation of v? " << is_permutation(v.begin(), v.end(), w.begin()) << '\n';
    cout << "Is u a permutation of v? " << is_permutation(v.begin(), v.end(), u.begin(), u.end()) << '\n';
}
 Output: Is w a permutation of v? true
Is u a permutation of v? false
 In this chapter, I only presented a few of the available algorithms, which seemed to me to be especially useful and/or simple. More information, including many function templates that I did not list here, may be found in the C++ reference and Microsoft's C++ reference. | 
| Above I mentioned a few algorithms from the header file <numeric>. Some other useful algorithms from that header file, which use iterators, include:  iota(first, last, value)fill the range[first, last)with the sequentially increasing values{value, value + 1, value + 2, ...}.accumulate(first, last, initial)calculates the sum of the values in the range[first, last), and addsinitialto the result.inner_product(first1, last1, first2, initial)calculates the inner product of the values in the range[first1, last1)with the values in the range of the same size starting fromfirst2, and addsinitialto the result. For more information, please see the C++ reference. | 
| While preparing these notes I often looked up information on the web. Unfortunately, I noticed that most free tutorial websites contain very poorly written code. These websites also usually feature old-fashioned C++ code that doesn't utilize the significant innovations of recent C++ revisions. My recommendation here will be similar to the one I made above for C: don't use these tutorials! Stack Overflow is a great website with trustworthy content in the form of questions and answers. The C++ reference and Microsoft's C++ reference are two comprehensive and trustworthy sources I used very frequently when writing these notes, and I encourage you to use them for further reading. The C++ Resources Network is also a somewhat useful website, but unfortunately it is only updated up to C++11, which is now a decade old, so I cannot recommend using it as a primary resource. The creator of C++, Bjarne Stroustrup, has written several textbooks at different levels. The C++ Programming Language is a very comprehensive and advanced textbook, intended for experienced programmers. Its 4th edition (2013) covers revisions of C++ up to C++11. Another book, Programming: Principles and Practice Using C++, is roughly the same length, but is intended for beginner programmers. Its 2nd edition (2014) covers revisions of C++ up to C++14. Both are excellent places to find more information, as well as to understand how to "think" properly as a C++ programmer, even if they don't cover all the latest features. | 
|  | 
|  | 
| The GCC compiler, like any decent compiler, includes many different optional optimizations that can potentially improve the performance of your program, sometimes by a very significant amount. The optimization level is set using the argument -O(note that this must be an uppercase O) followed by a digit, letter, or word. Sorted from the smallest to the largest number of optimizations that will be applied, the options are:  -O0: No optimizations. This is the default, and is not recommended for use except when debugging.-Og: Enables all-O1optimizations, except those that may interfere with debugging. For example, the compiler may decide that a certain variable is unnecessary and optimize it out, resulting in the debugger not being able to access that variable. I actually find that this sometimes nonetheless interferes with my debugging, so I don't use it, and instead usually use-O0(i.e. no optimization flag) when debugging.-O1: The basic level of optimization.-Os: Enables all-O2optimizations, except those that may increase code size. This is important if you're compiling code that will run on an embedded system with very limited memory, but otherwise usually not needed.-O2: A higher level of optimization. In most cases, this is the recommended optimization level that should be applied when compiling the version you intend to distribute and use.-O3: The highest level of optimization. Note that-O3is not guaranteed to produce improved performance compared to-O2, and in fact, it may sometimes actually produce slower code due to increased code size and/or increased memory usage. On rare instances, it may even break some code.-Ofast: Enables all-O3optimizations, as well as additional optimizations that are not valid for all standard-compliant programs. You should never use this option, as it may break your code. For example, among other things, this option actually changes the way floating-point calculations work, which may cause your program to produce different output than expected. To use optimizations when compiling from Visual Studio Code, add the appropriate argument to the argssection oftasks.json. Remember to also remove-ggdb3; generally you should only use-ggdb3when debugging, and-Owhen compiling the release version. Note that only one optimization level may be set; if you use more than one, then they will all be ignored except the last one. One tradeoff of increasing the optimization level is that compilation will take more time. However, this is only relevant during the debug phase, when you may be recompiling the program very often. When compiling the release version program, compilation time doesn't matter, only the performance of the program itself does. So, which option should you use? It is tempting to use -O3, but as I said above, this is not necessarily the best option. The correct thing to do is to test your code with different levels of optimization (except-Og, which should only be used for debugging, and-Ofast, which should never be used) and see which one produces the fastest code. Each of the optimization flags above automatically turns on a large number of different optimizations. It is also possible to manually turn specific optimizations on or off, but I won't go into details about that here. You can read many more details about GCC optimization flags here. | 
| Consider the following program: #include <chrono>
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
class timer
{
public:
    void start()
    {
        start_time = chrono::steady_clock::now();
    }
    void end()
    {
        elapsed_time = chrono::steady_clock::now() - start_time;
    }
    double seconds() const
    {
        return elapsed_time.count();
    }
private:
    chrono::time_point<chrono::steady_clock> start_time = chrono::steady_clock::now();
    chrono::duration<double> elapsed_time = chrono::duration<double>::zero();
};
void populate_vector(vector<double> &vec)
{
    for (uint64_t i = 0; i < vec.size(); i++)
        vec[i] = (double)i;
}
void square_vector_with_pow(vector<double> &vec)
{
    for (double &element : vec)
        element = pow(element, 2);
}
void square_vector_without_pow(vector<double> &vec)
{
    for (double &element : vec)
        element = element * element;
}
int main()
{
    timer t;
    cout.precision(2);
    cout << fixed;
    constexpr uint64_t size = 500'000'000;
    {
        vector<double> vec(size);
        populate_vector(vec);
        t.start();
        square_vector_with_pow(vec);
        t.end();
        cout << "square_vector_with_pow() took " << t.seconds() << " seconds.\n";
    }
    {
        vector<double> vec(size);
        populate_vector(vec);
        t.start();
        square_vector_without_pow(vec);
        t.end();
        cout << "square_vector_without_pow() took " << t.seconds() << " seconds.\n";
    }
}
 This program compares the execution time of two methods for squaring numbers:  Using the pow()function from<cmath>,By simply multiplying the number by itself. It measures time using the timerclass I first defined above. Eachvectoris defined within its own{}code block, so that its memory is deallocated once the code block ends and thevectorgoes out of scope. Instead of building and running this program in the VS Code debugger by pressing F5, build it in the integrated terminal (Ctrl+`) using the following command: g++ main.cpp -std=c++23 -o CSE701.exe -O0
 (As usual, on Linux, remove the .exe.) Now run it by typingCSE701on Windows (if you're using Command Prompt for your terminal) or./CSE701on Linux or Windows PowerShell. This will ensure that the results obtained are not affected by the debugger. When I run this program on my computer, it produces the following output: square_vector_with_pow() took 16.61 seconds.
square_vector_without_pow() took 2.98 seconds.
 Clearly, square_vector_with_pow()is much slower thansquare_vector_without_pow(); this is becausepow()uses a numerical algorithm to can accept any real number as the power, and this algorithm is not optimized for integer powers. Let us now enable some compiler optimizations. Replacing -O0with-Og, I get: square_vector_with_pow() took 13.14 seconds.
square_vector_without_pow() took 0.30 seconds.
 There is only a small improvement in square_vector_with_pow(), butsquare_vector_without_pow()is considerably faster, by about a factor of 10. This is possibly due to some optimizations that have to do with looping over array elements. Adding some more optimizations with -O1, I get: square_vector_with_pow() took 0.28 seconds.
square_vector_without_pow() took 0.31 seconds.
 Now square_vector_with_pow()is just as fast assquare_vector_without_pow()! This is probably because the compiler now recognizes that we are using an integer forpow()and simply does normal multiplication instead. Actually,square_vector_with_pow()is now a bit faster thansquare_vector_without_pow(), by around 10% - and this is not just a statistical anomaly, since I got similar results after running the program several times. I'm not exactly sure why this happens, but it's a negligible difference in any case. Going to the next optimization level, -Os, I get similar results. At the next level,-O2, I get: square_vector_with_pow() took 0.27 seconds.
square_vector_without_pow() took 0.27 seconds.
 Now both operations run at essentially the same speed; there was no improvement in square_vector_with_pow(), but a small improvement insquare_vector_without_pow(), bringing both functions to the same speed - in fact, it is entirely possible that the machine code for both functions is now exactly the same! Finally, at the highest optimization level, -O3, I get roughly the same results as-O2. Importantly, most of the improvement in the performance of square_vector_with_pow()was achieved in the optimization process due to the fact that the compiler essentially replacedelement = pow(element, 2)with the much fasterelement = element * element, which was possible because the second argument topow()was an integer. However, if I replace2with, for example,2.1, then I get, even with-O3: square_vector_with_pow() took 36.78 seconds.
square_vector_without_pow() took 0.27 seconds.
 Yes, square_vector_with_powwith2.1in the exponent now takes almost 37 seconds, with maximum optimizations enabled. This is much worse even compared to the case of2in the exponent with no optimizations at all! Essentially,pow()is now forced to calculate using a slow algorithm (e.g. using the Taylor series of ab=eb ln a), and no optimization can possibly make this algorithm any quicker. | 
| In scientific computing, sometimes your programs will be running calculations that may take days or weeks to complete, even on a high-performance computing cluster. For this reason, writing optimized code that has the fastest performance possible is essential for most scientific application. You should, of course, enable compiler optimizations - there is usually no reason not to, except when debugging. However, my philosophy is to never trust the compiler to do my job for me, and always write code that is optimized on its own, without relying on compiler optimizations. Throughout this course, whenever I introduced a new concept, I often gave advice on what is the most optimized way to use it. So keep in mind everything I taught you, and if you are not sure you are implementing something in the most optimized way, go back to that section in the notes and read what I wrote about it. It is very important to actively check the performance of your code. Try different methods and see what gives the fastest results. If you find that a particular calculation or algorithm is taking an unreasonably long time, you can use a debugger to look under the hood and try to figure out what may be the cause. If maximum performance is desired, you should use a profiler, which can, among other things, automatically measure the duration of each individual function call; but this is beyond the scope of our course. That being said, you should also remember that in some cases, speed has a cost. One example is using uninitialized arrays. We have seen above, and will also see below that this can improve performance; in most cases, you don't actually need an array to be initialized automatically to zeros (as vectordoes), because you are going to populate it with other elements anyway. However, we have also seen - and I have stressed multiple times in this course - that if you accidentally use uninitialized garbage values, this can lead to serious bugs. Another example is using doubleinstead oflong double. As I said above, calculations withdoubleare generally much faster than withlong double, but they are also less precise. If your application needs maximum precision, then you may need to sacrifice speed and uselong double, at least in critical calculations where rounding errors can accumulate. | 
|  | 
| We have often modified the file tasks.jsonin the.vscodefolder of the workspace to add compiler arguments, but we never really talked about what this file actually does. Thetasks.jsonfile allows you to configure various different tasks for integrating Visual Studio Code with external tools. Currently, the only external tool configured intasks.jsonis the compiler. The tasks.jsonI have been using to compile most of the C++ code in these lecture notes looks as follows: {
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cppbuild",
            "label": "Build for debugging",
            "command": "C:/Users/barak/mingw64/bin/g++.exe",
            "args": [
                "${workspaceFolder}/*.cpp",
                "-o",
                "${workspaceFolder}/CSE701.exe",
                "-Wall",
                "-Wextra",
                "-Wconversion",
                "-Wsign-conversion",
                "-Wshadow",
                "-Wpedantic",
                "-std=c++23",
                "-ggdb3"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Compile all .cpp files with warning and debugging flags",
            "presentation": {
                "clear": true,
                "echo": true,
                "focus": false,
                "panel": "shared",
                "reveal": "silent",
                "showReuseMessage": true
            }
        }
    ]
}
 I am using Windows 11, with GCC installed in the folder C:\Users\barak\mingw64. On your system, some arguments should be changed based on the operating system and the path to the GCC binary folder. Note that this task always creates an executable namedCSE701.exe; this needs to be replaced with the actual name of the program, but since the purpose of this task was to quickly compile and create short programs for this course, I just used a default name. The file format is JSON, which stands for JavaScript Object Notation, but it is used as a generic file format, independent of any specific programming language. It consists of objects enclosed in curly brackets {}, each containing a comma-delimited list of key-value pairs (similar to C++ maps) in the format: {
    "key": value,
    "key": value,
    // etc..
}
 The key must be a string (enclosed in quotes), but the value can be a string, a number, a Boolean value (trueorfalse), anull, an array in the format[element1, element2, ...], or another object enclosed in curly brackets, which will then have its own key-value pairs. In tasks.jsonwe see that the file consists of a root object with two keys:versionandtasks. The keyversionindicates the version of thetasks.jsonfile, which should be2.0.0in the current version of VS Code. The keytaskscontains an array (notice the square brackets) whose elements are the actual tasks. Each task is another object enclosed in curly brackets. Currently, there is only one task in the array. The task object contains the following keys:  type: The task's type.cppbuildindicates that the task is a C++ build task, which is specific to the C/C++ extension. Other options areshell, to execute a shell command, andprocess, to execute a specific program (althoughshellcan also be used to execute programs).label: The task's label, which will be displayed in the user interface when you choose Terminal > Run Task... or press F1 to bring up the Command Pallette and choose Tasks: Run Task. The label should be simple and concise, since it will also be used as an argument to other tasks.command: The command to execute. In this case, the value is the path to the executable file of theg++compiler. Note that backslashes\are escape characters, as in C strings, so you need to escape the backslash itself if you want to use it as a normal character, i.e.\\will result in just one\. So for Windows paths, you can either use a double backslash, e.g.C:\\Users\\..., or (preferably) just a normal slash, e.g.C:/Users/....args: An array of command line arguments to pass to the compiler in the specified order.options: An object specifying options for the task. cwd: Specifies the current working directory. Here I set it to the workspace folder.shell: Specifies which shell to use.env: Specifies environment variables for use in that shell, in the format{"name1": "value1", "name2": "value2", ...}.problemMatcher: The option$gccallows errors and warnings generated by the GCC compiler to be translated to problems in the Problems tab (Ctrl+Shift+M).group: Defines which group the task belongs to. This can be eitherbuildortest, i.e."group": "build"or"group": "test". Build tasks are for compiling source code, and test tasks are for everything else (in particular, tools used to test the code, like profilers or memory debuggers). In this case, since the task is also the default build task, i.e. the one executed when you choose Terminal > Run Build Task or press Ctrl+Shift+B, the value ofgroupis instead an object withkindset tobuildandisDefaultset totrue.detail: This is the text that will appear below the task'slabelwhen you choose Terminal > Run Task....presentation: An object with options that control how the output of the task will be presented. clearclears the terminal before running this task if set totrue.echo: Will write "Executing task:" followed by the task'slabelto the terminal if set totrue.focus: Will give focus to the terminal used for the task (and also always reveal it) if set totrue.panel:shareduses the same terminal panel for all tasks.dedicateduses a dedicated terminal panel for every run of this specific task.newcreates a new terminal panel every time the task runs (which is not recommended since it will lead to many panels being created).reveal: Sets when to reveal the terminal used for the task.alwayswill reveal the terminal every time,neverwill never reveal it, andsilentwill only reveal it if there were any errors.showReuseMessagewill display the message "Terminal will be reused by tasks, press any key to close it" when the task ends.dependsOn: Specifies a task which should run before running this task. The task should be specified using itslabelstring. (See example below.) dependsOncan also be an array of tasks (inside square brackets), in which case all tasks in the array will be executed. The keydependsOrderthen specifies whether the tasks should run one after the other (sequence) or simultaneously (parallel). These are not the only available keys; you can use VS Code's IntelliSense feature to see a full list of the possible keys by pressing Ctrl+Space to trigger code suggestions. Also, hovering with the mouse over a key will provide an explanation of what it does. Any string of the form ${variable}will be replaced with the value ofvariable. For example,${workspaceFolder}will be replaced with the currently active workspace folder, which in my case is"C:/Users/barak/CSE701". Therefore, withargsconfigured as above, the command that will be executed is: C:/Users/barak/mingw64/bin/g++.exe "C:/Users/barak/CSE701/*.cpp" -o "C:/Users/barak/CSE701/CSE701.exe" -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic -std=c++23 -ggdb3
 Notice that the first argument is the name of the source file (or source files, in this case), and the -oargument (lowercase o!) indicates the name of the output executable file. VS Code is smart enough to know to add quotes in any of the variables have spaces in them, so that the spaces won't be interpreted as starting new arguments. ${workspaceFolder}is just one of many variables we can use in tasks; see here for a full list of available variables and how to use them.
 | 
| The file launch.jsonis used to configure debugging in Visual Studio Code. Thelaunch.jsonI have been using to debug most of the C++ code in these lecture notes looks as follows: {
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Build and debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/CSE701.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "miDebuggerPath": "C:/Users/barak/mingw64/bin/gdb.exe",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "Build for debugging"
        }
    ]
}
 versionis again the version of the file, which should be0.2.0in the current version of VS Code.configurationsis an array of objects corresponding to debugging configurations, where each object has the following keys:
  name: The name of the configuration. It will appear in the dropdown menu on top of the Run view.type: The type of the configuration. Should becppdbgfor C++ debugging with GDB (or LLDB).request: The request type of the configuration. Should belaunchfor C++ debugging, which means a new process will be launched by the debugger. Can also beattach, which means the debugger will attach itself to an existing process.program: The program to execute. Should be the same as the output executable file we specified with the-ocompiler argument.args: An array of command line arguments to pass to the program being debugged. This is useful if your program takes command line arguments, since you will be launching it from the debugger rather than the command line.stopAtEntry: Automatically puts a breakpoint in the first line of themainfunction if set totrue.cwd: The working directory of the program being debugged. This is useful if your program opens files in that directory. Should usually be set to${workspaceFolder}.environment: An array of objects of the form{"name": "variable_name", "value": "variable_value"}, specifying any environment variables to be used when running the program.externalConsole: If set totrue, opens the program in an external console, rather than VS Code's integrated terminal (Ctrl+`). Can be more convenient in some cases, e.g. when you want to resize the terminal to take up the entire screen.MIMode: Indicates which debug engine to connect to. Should begdbif using GDB.miDebuggerPath: Indicates the path to the debug engine.setupCommands: An array of commands to execute in GDB. Currently, the option-enable-pretty-printingis set, which tells GDB to indent the structures it prints.preLaunchTask: Indicates a task to run before starting the debug session. This should be a label corresponding to a build task defined intasks.json, and that task should of course have compiler arguments appropriate for debugging, such as-ggdb3. More information on options in launch.jsonrelevant for C++ debugging may be found here. | 
| Tasks are a very useful feature in Visual Studio Code, and if used correctly can save you a lot of time and typing! As an example, let us set up the following tasks in Visual Studio Code:  A test task to delete the executable file generated by the build task.The default build task defined above, for general debugging purposes, but modified to delete the executable before building.A build task to compile the program with optimization flags and no warning or debugging flags.A test task to both compile and run the optimized program. In the end, the tasks.jsonfile should look like this: {
    "version": "2.0.0",
    "tasks": [
        {
            // Task 1
        },
        {
            // Task 2
        },
        {
            // Task 3
        },
        {
            // Task 4
        }
    ]
}
 First, let us create a keyboard shortcut to open the list of tasks. For some reason this does not appear in the list that you get by pressing F1 > "Preferences: Open Keyboard Shortcuts" or Ctrl+K Ctrl+S. However, you can click on a button on the top right of the Keyboard Shortcuts page, or press F1 and run the command "Preferences: Open Keyboard Shortcuts (JSON)", which will open the file keybindings.json. Add the following object to the array: {
    "key": "ctrl+f1",
    "command": "workbench.action.tasks.runTask"
}
 I chose Ctrl+F1, because it is similar to F1 which brings up the Command Pallette, but you can of course choose any keybinding you like. Now, let us create the tasks. Task #1 is a test task to delete the executable file generated by the build task: {
    "type": "shell",
    "label": "Delete executable file",
    "command": "cmd /c \"if exist ${workspaceFolder}\\CSE701.exe del ${workspaceFolder}\\CSE701.exe\"",
    "args": [],
    "options": {
        "cwd": "${workspaceFolder}"
    },
    "group": "test",
    "detail": "Delete the executable file generated by the build task",
    "presentation": {
        "clear": false,
        "echo": true,
        "focus": false,
        "panel": "shared",
        "reveal": "silent",
        "showReuseMessage": true
    }
},
 The commandfirst checks if the executable file exists, and if so, deletes it (otherwise you will get an error message if the file does not exist). This command will work on Windows; for Linux you can use[ -f ${workspaceFolder}/CSE701 ] && rm ${workspaceFolder}/CSE701or a similar shell command ([ -f filename ]is a quick way to check iffilenameexists). Task #2 is the same build task from above, modified via the dependsOnkey to delete the executable using the"Delete executable file"task before building: {
    "type": "cppbuild",
    "label": "Build for debugging",
    "command": "C:/Users/barak/mingw64/bin/g++.exe",
    "args": [
        "${workspaceFolder}/*.cpp",
        "-o",
        "${workspaceFolder}/CSE701.exe",
        "-Wall",
        "-Wextra",
        "-Wconversion",
        "-Wsign-conversion",
        "-Wshadow",
        "-Wpedantic",
        "-std=c++23",
        "-ggdb3"
    ],
    "options": {
        "cwd": "${workspaceFolder}"
    },
    "problemMatcher": [
        "$gcc"
    ],
    "group": {
        "kind": "build",
        "isDefault": true
    },
    "detail": "Compile all .cpp files with warning and debugging flags",
    "presentation": {
        "clear": true,
        "echo": true,
        "focus": false,
        "panel": "shared",
        "reveal": "silent",
        "showReuseMessage": true
    },
    "dependsOn": "Delete executable file"
},
 The reason I like to configure the build task to delete the executable first is that sometimes there are errors during the build, so a new executable will not be created, but the debugger will still run using the previously-built executable, which is generally not what you want. Task #3 is a build task to compile the release version of the program, with the warning and debugging flags disabled, and the optimization flag -O2enabled: {
    "type": "cppbuild",
    "label": "Build optimized",
    "command": "C:/Users/barak/mingw64/bin/g++.exe",
    "args": [
        "${workspaceFolder}/*.cpp",
        "-o",
        "${workspaceFolder}/CSE701.exe",
        "-std=c++23",
        "-O2"
    ],
    "options": {
        "cwd": "${workspaceFolder}"
    },
    "problemMatcher": [
        "$gcc"
    ],
    "group": "build",
    "detail": "Compile all .cpp files with optimization flags",
    "presentation": {
        "clear": true,
        "echo": true,
        "focus": true,
        "panel": "shared",
        "reveal": "always",
        "showReuseMessage": true
    },
    "dependsOn": "Delete executable file"
},
 I also set the terminal to always be focused and revealed, so I can make sure the build was successful. Finally, Task #4 is a test task to first compile the program using the task "Build optimized"and then run it: {
    "type": "process",
    "label": "Build and run optimized",
    "command": "${workspaceFolder}/CSE701.exe",
    "args": [],
    "problemMatcher": [
        "$gcc"
    ],
    "group": "build",
    "detail": "Compile all .cpp files with optimization flags and then run the program",
    "presentation": {
        "clear": true,
        "echo": false,
        "focus": true,
        "panel": "dedicated",
        "reveal": "always",
        "showReuseMessage": false
    },
    "dependsOn": "Build optimized"
},
 In this case, I set the task to run in a dedicated terminal that will be automatically focused on, and without any additional messages (echoandshowReuseMessagedisabled), so that I can see the precise output of the program. The task "Build for debugging"can be executed using Ctrl+Shift+B. It will also be executed automatically when you press F5, in which case VS Code will also run the built program in debug mode. To execute any of the other tasks, you need to use the shortcut key you defined before (Ctrl+F1) and choose the task from the menu. You can also define a keyboard shortcut for a specific task. For example, to compile and run the optimized program with F6, add the following tokeybindings.json: {
    "key": "f6",
    "command": "workbench.action.tasks.runTask",
    "args": "Build and run optimized"
},
 | 
| With the tasks we configured in the previous section, pressing F6 runs 3 tasks in sequence:  Delete executable fileBuild optimizedBuild and run optimized It works, but it's a bit cumbersome. The problem is that Visual Studio Code's tasks are limited to performing just one command, and not a series of commands. However, there is an easy solution, offered by the operating system itself - we can create a shell script (also called a batch file on Windows) which will simply execute the commands one after the other. We can then either run the script manually from the terminal, or configure a VS Code task that will run the script with one click. This will give us much more flexibility than defining separate tasks. Create a new file in the Explorer view of Visual Studio Code and name it build_run_optimized.cmdon Windows orbuild_run_optimizedon Linux. VS Code will recognize that we are creating a shell script and will provide syntax highlighting. On Windows, the contents of the file build_run_optimized.cmdshould be: @echo off
if exist CSE701.exe del CSE701.exe
g++ *.cpp -o CSE701.exe -std=c++23 -O2
CSE701
 The first line, @echo off, tells the shell not to write down each command in the terminal, and instead just show the output of each command. To run the script, simply writebuild_run_optimizedin the terminal. If the script doesn't run correctly, note that:  The script must be in the same directory as the .cppfiles.The folder where g++is located must be in thePATHenvironment variable, which should already be the case if you followed my instructions above. IfPATHis not configured correctly, try adding an explicit path, e.g. in my case I would replaceg++withC:/Users/barak/mingw64/bin/g++. On Linux, the contents of the file build_run_optimizedshould be: #!/bin/bash
[ -f CSE701 ] && rm CSE701
g++ *.cpp -o CSE701 -std=c++23 -O2
./CSE701
 The differences are:  Windows determines how to run a file based on its extension. Therefore, the .cmdextension indicated to Windows to treat the file as a shell script. On Linux, the extension doesn't matter, and you specify which program should be used to execute the file by writing the program's name prefixed by the characters#!in the beginning of the file. Here we are choosing to run the script using thebashshell, which is located at/bin/bash. (Also note that VS Code will only recognize the file as a shell script after you add this line.)On Windows, if you type CSE701orbuild_run_optimized, it will automatically recognize that you meanCSE701.exeorbuild_run_optimized.cmdrespectively. However, on Linux you must type the full name of the file, so it's more convenient for both executable files and shell scripts to have no extensions at all. This is why the program's executable is namedCSE701instead ofCSE701.exe, and the script is namedbuild_run_optimizedwithout an extension.Linux shells do not recognize programs in the current directory as valid shell commands. You can only run programs that are in the PATHenvironment variable, unless you specify the full path explicitly. For this reason, we had to write./CSE701instead of justCSE701, since.stands for the current directory, so adding./in front of the command specifies the full path of the file as being in the current directory. (Note that Windows PowerShell has the same behavior.)We replaced if exist CSE701.exewith[ -f CSE701 ] &&, which is the Linux command for checking if a file exists and executing a task if it does, anddelwithrm, which is the Linux command for deleting a file (short for "remove"). To run the shell script on Linux, we need to make it executable first. As we explained above, on Windows, any file with an extension such as .exeor.cmdis automatically executable, but on Linux, whether a file is executable or not is independent of its extension. To make the filebuild_run_optimizedexecutable, write the following in the terminal: chmod +x build_run_optimized
 The command chmodis used to change the access permissions of the file.+xmeans "add (+) executable (x) permissions". Once the file is marked as executable, write./build_run_optimizedto run it. Most high-performance computing systems use some form of Linux as their operating system, so even if your own computer runs Windows, you should be familiar with some basic Linux shell commands. Wikipedia has a comprehensive list of shell commands, most of which have separate Wikipedia articles. For a beginner-friendly introduction, see for example here or here. Even if you are already a Linux user, but you're used to graphical desktop environments such as GNOME or KDE, you will still need to learn some shell commands, as sometimes the only method of access you will get to a high-performance computing cluster will be through a textual terminal. Finally, we can modify the "Build and run optimized"task in Visual Studio Code to use our script, since it's more convenient to press F6 than to go to the terminal and writebuild_run_optimized. On Windows, the task will be: {
    "type": "process",
    "label": "Build and run optimized",
    "command": "${workspaceFolder}/build_run_optimized.cmd",
    "args": [],
    "problemMatcher": [
        "$gcc"
    ],
    "group": "build",
    "detail": "Compile all .cpp files with optimization flags and then run the program",
    "presentation": {
        "clear": true,
        "echo": false,
        "focus": true,
        "panel": "dedicated",
        "reveal": "always",
        "showReuseMessage": false
    }
}
 | 
|  | 
| For the discussion in this section, let us use the following multi-part "Hello, World!" program, consisting of 3 files: hello.hpp:
 // Function to print "Hello, World!"
void hello_world();
 hello.cpp:
 #include <iostream>
// Function to print "Hello, World!"
void hello_world()
{
    std::cout << "Hello, World!\n";
}
 main.cpp:
 #include "hello.hpp"
// Print "Hello, World!" using the hello_world() function
int main()
{
    hello_world();
}
 In the process of converting this C++ source code into an executable file, it undergoes 4 stages of compilation: Stage #1: Preprocessing. In this stage, the source code is "purified" by:  Removing any whitespace characters from the code,Removing comments from the code,Interpreting preprocessor directives (lines that start with #), such as replacing#includestatements with the actual contents of the header file,Performing other tasks, as detailed in the C++ reference. In order to see the output of the preprocessing stage when compiling the program above, let us execute the following commands in the terminal: g++ main.cpp -o main.ii -E
g++ hello.cpp -o hello.ii -E
 The -ocompiler argument indicates the output file, as usual, while the-Eargument indicates that we are only interested in going through the preprocessing stage and not any other stages. This will create two files, main.iiandhello.ii. The extension.iiindicates that the files contain preprocessed C++ source code. Here are the contents ofmain.ii: # 0 "main.cpp"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "main.cpp"
# 1 "hello.hpp" 1
void hello_world();
# 2 "main.cpp" 2
int main()
{
    hello_world();
}
 As you can see, comments have been removed, and the file hello.hpphas been explicitly included in the.iifile. Similarly, if you openhello.ii, you will see that the entire contents of theiostreamheader file have been explicitly included in the code (I won't show the result here since that file has thousands of lines). This means that to compile the program, we will only need the two.iifiles; the individual header or source files are no longer required. Stage #2: Compilation to assembly language. In this stage, the preprocessed C++ source code in the .iifiles is checked for errors, and if it is valid, it is compiled to assembly language. This is still human-readable code, except it is written directly in terms of the instructions that the CPU will execute. In order to see the output of this stage, let us execute the following commands in the terminal: g++ main.ii -o main.s -S
g++ hello.ii -o hello.s -S
 The -Scompiler argument indicates that the compiler should stop after compilation to assembly, and not go to the next stage. In addition, since the input files have the extension.ii, the compiler automatically knows not to preprocess them, so in fact compilation to assembly language is the only stage that is executed. This will create two files, main.sandhello.s. The extension.sindicates that the files contain code in assembly language. If you openmain.s, you may see something similar to:     .file   "main.cpp"
    .text
    .def    __main;  .scl    2;    .type    32;    .endef
    .globl  main
    .def    main;    .scl    2;    .type    32;    .endef
    .seh_proc    main
main:
.LFB0:
    pushq   %rbp
    .seh_pushreg    %rbp
    movq    %rsp, %rbp
    .seh_setframe    %rbp, 0
    subq    $32, %rsp
    .seh_stackalloc    32
    .seh_endprologue
    call    __main
    call    _Z11hello_worldv
    movl    $0, %eax
    addq    $32, %rsp
    popq    %rbp
    ret
    .seh_endproc
    .def    _Z11hello_worldv;    .scl    2;    .type    32;    .endef
 This is, specifically, assembly code for the x86_64(Intel and AMD) architecture, so if you have a CPU with the ARM architecture, the code will be different. Keywords with the.prefix are directives that are not actually executed by the CPU (much like the#prefix in C++), and keywords without this prefix are instructions. If you are interested in reading about how assembly language works, Wikibooks has a nice book about it; in particular, this page analyzes a (simpler) "Hello, World!" program. Also, if you want syntax highlighting for assembly language in VS Code, check out this extension. This assembly code doesn't actually do much except callthehello_worldfunction, which is done in the linecall _Z11hello_worldv. However, notice that this function itself is not defined here, but rather in thehello.sfile (which I won't reproduce here, since it's a bit long). The two functions,mainandhello_world, will be linked together in the last compilation stage. Stage #3: Assembly. In this stage, the assembly language code is translated from human-readable words into low-level machine code, the binary code that the CPU will actually execute. This machine code, in addition to information about the symbols (e.g. functions) defined in the code, is stored in an object file. Each C++ source file results in a separate object file. In order to see the output of the assembly stage, let us execute the following commands in the terminal: g++ main.s -o main.o -c
g++ hello.s -o hello.o -c
 The -carguments indicates that the compiler should stop after the assembly stage, and not go to the next stage. In addition, since the input files have the extension.s, the compiler knows they are already in assembly language, so in fact the assembly stage is the only stage that is executed. This will create two files, main.oandhello.o. The extension.oindicates that the files are object files. These are binary files, but you can open them using the Microsoft Hex Editor extension for Visual Studio Code to see what's inside. You will also see some text, including names of symbols like_Z11hello_worldv, and the string "Hello, World!" itself. Stage #4: Linking. In this stage, the object files are combined into one executable file. Even though the object files contain actual machine code, they are not executable by themselves. The reason is that main.ocalls the functionhello_world, which is inhello.o. The linking stage links the object files, so that they can call each other's functions. In order to see the output of the linking stage, let us execute the following command in the terminal: g++ main.o hello.o -o hello.exe
 On Linux, use helloinstead ofhello.exe. Note that now there is only one command, since the compiler needs to combine all of the object files together in one go. Since the input files have the extension.o, the compiler knows they are already object files, so the linking stage is the only stage that is executed. The filehello.exeis the final executable file, which we can execute by typinghelloon Windows Command Prompt or./helloon Linux or Windows PowerShell. You can also look inside with the hex editor if you want. When we call g++without any of the flags-E,-S, or-c, and with the.cppsource files as arguments, it actually goes through all of these four stages in order, producing the.ii,.s, and.ofiles corresponding to each.cppfile, and finally links all of the.ofiles together. | 
| A preprocessor directive is a line in a C++ source file that starts with the #character, followed by a keyword. Preprocessor directives are only executed in the preprocessing stage, so they are typically used to modify the source code before the actual compilation starts. The keywords include:  #include <filename>: Includes a file from the standard include directories, which usually contain the header files for the standard library.#include "filename": Includes a file from the current folder.#error message: Stops compilation, and displaysmessage.#define MACRO replacement: Replaces every occurrence ofMACROin the file withreplacement. Potentially, a parameter list inside parentheses can followMACRO. It is conventional for macros to always be in all uppercase letters in order to distinguish them from other names and keywords.#undef MACRO: Undefines a previously defined macro. Here is an example of macros: #include <iostream>
#define MESSAGE "Macros are evil!"
#define OUTPUT(STRING) std::cout << (STRING)
int main()
{
    OUTPUT(MESSAGE);
}
  Warning: Macros are evil, and you should never use them! Macros cannot be debugged, do not respect scopes or namespaces, do not comply with data types, and can cause unexpected errors in many different ways. You will often find macros in older code, which is why it's important to understand how they work, but in modern C++ they are very uncommon. Constant expressions, inline functions (example here), and/or templates provide all of the benefits of macros without any of the drawbacks, and should always be used instead.   #if expression: Checks ifexpressionis true, and if so, includes any code that follows, until an#endif,#else, or#elifis encountered.#else: Is to#ifaselseis toif. There can be at most one#elsein a conditional block.#elif expression: Is to#ifaselse ifis toif. There can be an unlimited number of#elifin a conditional block. Here is an example of conditional code inclusion: #include <iostream>
int main()
{
#if HELLO
    std::cout << "Hello, World!\n";
#elif GOODBYE
    std::cout << "Goodbye, World!\n";
#else
#error No message to print!
#endif
}
 Note that Visual Studio Code will automatically dim any lines that are not included in the code - in this case, the two coutstatements. Also note that due to the use of#error, this code will not compile (compilation will stop at the preprocessing stage) unless we include either a#define HELLO trueor#define GOODBYE true.  #ifdef MACRO: Checks ifMACROis defined, and if so, includes any code that follows, until an#endif,#else, or#elifis encountered.#ifndef MACRO: Checks ifMACROis defined, and if not, includes any code that follows, until an#endif,#else, or#elifis encountered. | 
| Consider the following set of files: main.cpp:
 #include "print.hpp"
#include "test.hpp"
int main()
{
    print("Testing:\n");
    test();
}
 print.hpp:
 #include <iostream>
void print(const char *message)
{
    std::cout << message;
}
 test.hpp:
 #include "print.hpp"
void test()
{
    print("Test successful!\n");
}
 This program won't compile, because we are including print.hpptwice, once inmain.cppand once intest.hpp, so the functionprintis declared twice. A temporary solution is to remove the line #include "print.hpp"from one of the filesmain.cpportest.hpp, but then we are relying on the fact that the other file includesprint.hpp, which may change in the future. Furthermore, if we remove the line#include "print.hpp"fromtest.hpp, then other source files that also usetest.hppmay break. The best solution is to simply add the preprocessor directive #pragma onceinprint.hpp, which instructs the preprocessor to only include the file once, even if we try to include it more than once. The fixed version ofprint.hppis: #pragma once
#include <iostream>
void print(const char *message)
{
    std::cout << message;
}
 Now the program will compile without issue. Generally, it is a good idea to automatically add #pragma onceto every single header file in the project, even ones that are not included twice, to avoid problems in the future. Note that#pragma onceis technically not part of the C++ standard, but it is nonetheless supported by the vast majority of modern compilers, including GCC. In older C++ code, before use of #pragma oncebecame widespread, people employed include guards, which used macros to obtain the same results. For example, inprint.hpp, we could write: #ifndef PRINT_HPP
#define PRINT_HPP
#include <iostream>
void print(const char *message)
{
    std::cout << message;
}
#endif // PRINT_HPP
 This method has two main drawbacks:  It requires using a different macro name for each file (e.g. PRINT_HPPforprint.hppandTEST_HPPfortest.hpp), which may cause confusion if we rename the files and/or conflict if we accidentally use the same name twice in different files.It is cumbersome; we need to add three lines, in both the beginning and end of the file, and the lines must be specific to each file. You will often see include guards in older code, and in code written for special compilers (e.g. for embedded systems) that may not support #pragma once. However, in modern C++,#pragma onceis always preferred, since it only requires adding one fixed line to the beginning of each file, thus avoiding the two drawbacks listed above. Furthermore, using #pragma oncecan improve compilation time, since the preprocessor doesn't need to read the entire file, only the first line; if you use include guards, then the preprocessor is forced to read the entire file until it gets to the#endif(although compilers often recognize include guards as a special case and optimize them accordingly). Finally, let us note that starting from C++20, modules provide a modern alternative to header files, which eliminates many problems associated with their use, including double inclusion. Since modules are a new concept that has not yet been fully implemented by compilers or fully adopted by developers, we will not cover it in this course. | 
| So far, we have been using Visual Studio Code's tasks to build our programs. This is very convenient for small and simple projects that we work on alone, but:  If we are working on a large project, with many source files and complicated dependencies between them, we will need a more sophisticated build system. In particular, a task that is configured to build all .cppfiles in the workspace will do exactly that every time it is executed, so even if we only changed one file, all the other files will also be recompiled upon running the task, which will be very time-consuming.If we are collaborating with others, or posting our source code online for others to use, then we must take into account that other users may not be using the same IDE, compiler, operating system, or even CPU architecture. Therefore, we must use a universal build system that will work for everyone, that is, it must be cross-platform, IDE-independent, and compiler-independent. Issue #1 can perhaps be resolved by using a more advanced IDE, such as Microsoft Visual Studio, which can handle the complexities of large projects. However, this does not resolve issue #2 - in fact, it only makes it worse, since unlike VS Code, Visual Studio is not cross-platform. Luckily, both issues can be resolved by using the cross-platform tool CMake. With only a single CMake configuration file, we can allow any user to compile our code using their IDE, compiler, operating system, and CPU architecture of choice. For this reason, CMake has become an essential tool for modern cross-platform C++ development. To use CMake, first download and install the binaries for the latest CMake release (3.21.2 at the time of writing) from the CMake website. Alternatively, you may use your favorite package manager. However, note that on Ubuntu, aptwill usually not provide the latest version of CMake, so it's better to install it directly from the CMake website. On Windows, using winget, you can install the latest version by simply typingwinget install cmake. To integrate CMake into Visual Studio Code, install the CMake language extension (for syntax highlighting) and the CMake Tools extension. You are now ready to use CMake in your projects. As an example, we will use the following multi-part "Hello, World!" program: hello.hpp:
 #pragma once
class hello_world
{
public:
    hello_world();
};
 hello.cpp:
 #include <iostream>
#include "hello.hpp"
hello_world::hello_world()
{
    std::cout << "Hello, World!\n";
}
 main.cpp:
 #include "hello.hpp"
int main()
{
    hello_world h;
}
 To configure this program with CMake, create a file named CMakeLists.txtin the workspace folder with the following contents: cmake_minimum_required(VERSION 3.1.0)
project("Hello World")
add_executable(hello main.cpp hello.cpp hello.hpp)
 Let us go over the commands:  cmake_minimum_requiredsets the minimum version of CMake required to build the project. Here we set it to3.1.0, but if you use any features introduced in later versions, you will need to increase the minimum version requirement.projectsets the name of the project to "Hello World".add_executableadds an executable to the project. In this case, we have just one executable. The first argument is the target name, which is also the executable file name, and must not contain any spaces. For example, on Windows the executable file will behello.exe, while on Linux it will just behello. The other arguments are the source files and header files required to compile this executable. After you save the file, press F1 to open the Command Palette and run the command "CMake: Configure". You will be prompted to select a kit, which specifies the compiler to be used to compile the program on your system. If you don't see GCC on the list, try clicking on the option "[Scan for kits]" first, and if the GCC binaries are in your PATHenvironment variable (as they should be), CMake Tools will find GCC on its own. Once GCC is on the list, click on it to perform the configuration. A new icon will be added for CMake in the side bar. If you click on it, you will see the project "Hello World" and a list of the executables in the project (in this case, just one: hello) and the source files associated with them. In addition, a folder namedbuildwill automatically be created with various files required to build the project.  Warning: Do not edit any files in the buildfolder; they are all generated automatically, so if you change them and then reconfigure the project, the files will be replaced and your changes will be lost. The project is now configured, and to create the executable file, we should build the project. This can be done in several ways:  Press F1 and execute the command "CMake: Build" from the Command Palette.Go to the CMake view in the side bar and click on the Build button. (The label "Build" will appear when you hover over the button with the mouse.)Or simply press F7. The project will be built using the kit you selected, and an executable file (hello.exeon Windows orhelloon Linux) will be generated in thebuildfolder. The object files, which were previously calledmain.oandhello.o, will be in the subfolderbuild/CMakeFiles/hello.dir, under the namesmain.cpp.objandhello.cpp.obj. For each.cppfile you will also find a corresponding.dfile which contains its dependencies, i.e. which source files it requires in order to successfully compile. To actually run the executable, you can:  Execute the command "CMake: Run Without Debugging" from the Command Palette, or press Shift+F5, to run without debugging.Execute the command "CMake: Debug", or press Ctrl+F5, to debug. However, you do not have to build the project every time you want to run it. When you run or debug the project, CMake will check if the executable exists, and if it doesn't, it will automatically build it. You can check this by cleaning up the compiled executable and object files using the command "CMake: Clean" and then pressing Shift+F5 to run the program; it will be built and then run. Let us now discover another useful feature of CMake. First of all, try to build the project again (F7) without changing any files, and you will see in the Output panel in VS Code that nothing actually happens; the build will start at 100%. (If you don't see the Output panel, press Ctrl+Shift+U, and if you don't see the CMake output, choose CMake/Build from the menu at the top right of the panel.) Now, change the file main.cpp- or even just press Ctrl+S to save the same file with a newer modification date. Then try to build the project again. CMake will detect thatmain.cppwas modified since the last build, so it will recompile only this file.hello.cppwas not modified, so there is no need to recompile it. This means that there will be a newmain.cpp.objfile, but thehello.cpp.objfile will be the same one from the last build. Both files will then be linked to create the final executable. Similarly, let us now change the file hello.cpp. When we press F7 we will see that, again, only the modified file has been recompiled. If we had 100 files, and we only changed one, then only that file (and anything that depends on it) will need to be recompiled, considerably speeding up the build time. Finally, let us change the file hello.hpp. This time, when we build the project, both.cppfiles will be recompiled, because both of them depend on the header filehello.hpp. | 
| The file build/compile_commands.jsoncontains the actual commands used to compile each.cppfile. Notice that the commands do not include our usual compiler flags. For example, the program does not compile with C++23 support by default. To check that, let us add the linechar8_t c;tomain.cppand press F7 to build the project. Since thechar8_ttype was only added in C++20, the program will now fail to compile. We cannot just tell CMake to add the flag -std=c++23to the compilation, since other compilers might require a different flag, and that would defeat the purpose of using a cross-platform and cross-compiler build system. Instead, we should tell CMake that the program requires C++23, and it will automatically make sure the compiler gets the correct flag, no matter which compiler we use. Let us change CMakeLists.txtto the following: cmake_minimum_required(VERSION 3.1.0)
project("Hello World")
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
add_executable(hello main.cpp hello.cpp hello.hpp)
 The command setsets the value of a variable. We added three variables:  CMAKE_CXX_STANDARDis a variable indicating the C++ standard used by all targets. We set it to 23, meaning C++23.CMAKE_CXX_STANDARD_REQUIREDis a boolean variable which, if set toON(which is the same astrue), indicates that the files must be compiled with the given C++ standard.CMAKE_CXX_EXTENSIONSis a boolean variable which, if set toOFF(which is the same asfalse), disables compiler-specific extensions. In the case of GCC, if we do not set this variable toOFF, CMake will use the flag-std=gnu++23, which enables GCC-specific extensions; we have not been using this flag in our course because we want our programs to be maximally portable, so we want to make sure we only use standard C++ code, which can be compiled with any standard-compliant C++ compiler. If we set this variable toOFF, CMake will use-std=c++23, which is the flag we have been using so far, and means we are only using standard C++23 and nothing else. By default, the CMake Tools extension should reconfigure the project automatically as soon as you save the file CMakeLists.txt. If it doesn't, simply run the command "CMake: Configure". If you now open the filecompile_commands.jsonagain you will see that the-std=c++23flag has been added to the command. If you press F7 to build the project, it will indeed compile without errors, and Shift-F5 will run it successfully. Usually, we also want to add warning flags to the compiler. However, again, the warning flags that work for GCC won't necessarily work for other compilers. We can, however, add something like this to CMakeLists.txt: if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    add_compile_options(-Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic)
endif()
if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
    add_compile_options(/Wall)
endif()
 Make sure to add this before the add_executablecommand. This code first checks if the compiler being used is GCC (GNU) or Clang, and if so, adds the flags-Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wpedantic, which mean the same thing in both compilers. It then checks if the compiler is MSVC, and if so, adds the flag/Wall, which is roughly equivalent to-Wallin GCC. If CMake is using another C++ compiler, no flags will be added; we could in principle add appropriate warning flags for every C++ compiler in existence, but GCC, Clang, and MSVC are the three most popular ones, so this should be enough for now. After adding these lines, reconfigure the project, and you will see that the warning flags have been added in compile_commands.json. If you press F7 to build the project, you will now see a warning in VS Code about the variablec, which we defined above, not being used in the program - indicating that the warning flags have indeed been applied. It's always a good idea to make sure your program compiles successfully, without any warnings or errors, using all popular compilers. If you have Clang and/or MSVC installed on your system, you can switch to them using the command "CMake: Select a Kit" and build the project to verify that the project indeed compiles. Finally, let us give a quick example of compiling multiple executable files at once using CMake. Create a file main2.cppwith the following contents: #include "hello.hpp"
int main()
{
    hello_world h1;
    hello_world h2;
}
 Then add the following line to CMakeLists.txt: add_executable(hello2 main2.cpp hello.cpp hello.hpp)
 If you now configure and build the project, you will see two executable files in the buildfolder:helloandhello2(with.exeadded on Windows). You can check that runninghello2prints the "Hello, World!" message twice, as expected. Furthermore, if we changemain2.cppand rebuild the project, onlyhello2will be recompiled. CMake can do much more, but unfortunately we won't have time to go into its other features in this course. Please see the official CMake documentation for more information. There are also many tutorials that you can find online. | 
|  | 
| Markdown is a very simple markup language that can be used to write formatted text in a plain text editor. Markdown can be displayed natively by a variety of different code editors, including Visual Studio Code. It can also be easily converted to HTML for displaying in web browsers. In fact, these notes are written entirely in Markdown! Markdown is very commonly used when documenting source code and programs. While you may have been using Microsoft Word or LaTeX to write documents so far, these formats are intended for printing, so they are not suitable for writing documentation for code, which most people will be viewing on their computer screens. Formatting in Markdown files is done by inserting special characters such as #for headings and*for lists. The following example illustrates some basic markdown syntax: # Heading 1
## Heading 2
### Heading 3
Normal font, **bold** font, *italic* font
1. Ordered
2. List
3. Of
4. Items
* Unordered
* List
* Of
* Items
Inline code: `#include <iostream>`
Inline math: $\sin^2 \theta + \cos^2 \theta = 1$
Non-inline math:
$$\int f(x) \mathrm{d} x$$
Link: [the course website](https://baraksh.com/CSE701/)
Image (with alt text): 
 You can also add multi-line code blocks by putting them between two ```, optionally adding the name of the language (e.g.cfor C orcppfor C++) after the first```to enable syntax highlighting for that language. For example: ```cpp
int main()
{
    std::cout << "Hello, World!\n";
}
```
 will display as: int main()
{
    std::cout << "Hello, World!\n";
}
 For more Markdown syntax, the Markdown Guide provides a very useful reference. Also, for more complicated formatting, you can simply add HTML tags. When editing a .mdin Visual Studio Code, you will automatically get syntax highlighting - even for code blocks in different languages within the Markdown file itself. Furthermore, if a Markdown file is open in the editor, there will be a button in the top-right corner labeled "Open Preview to the Side", which is also accessible by pressing Ctrl+K V. This will display the formatted output, which will be updated automatically as you change the source, and scrolling either the source or the preview will scroll the other as well. You may also be interested in the following VS Code extensions:  Markdown All in One includes many useful features such as keyboard shortcuts (e.g. press Ctrl+B to format text as bold), table of contents generation, automatic numbering of lists, and so on.markdownlint will warn you if you are using Markdown incorrectly or violating some formatting conventions. It produces a lot of warnings, and not all of them are useful, but specific warnings can be turned off in the settings. | 
| Doxygen is a tool that automatically generates documentation for your code based on special comments in the code itself. Even if you don't generate the separate documentation, the Doxygen comments themselves provide a structured and well-defined way to document your code. Furthermore, Visual Studio Code can parse these comments and display them in tooltips as part of its IntelliSense feature. Let us first install the Doxygen Documentation Generator extension for Visual Studio Code, which will make adding the Doxygen comments to the code easier by automatically generating comments on-the-fly in the appropriate format. To demonstrate how it works, let us use the following code: #include <iostream>
using namespace std;
double add(const double &a, const double &b)
{
    return a + b;
}
int main()
{
    cout << add(1, 2);
}
 Once you installed the Doxygen Documentation Generator extension, go to the line before the function add, write/**, and press Enter. This will automatically generate the following comment: /**
 * @brief
 *
 * @param a
 * @param b
 * @return double
 */
 This is a standard format for Doxygen comments, as well as comments for other automatic documentation generators, which originated with Javadoc. The comment must be a multi-line comment with /**in the first line (which has an extra*compared to a usual multi-line comment), and keywords prefixed with@are called tags or commands. You can find a full list of the available tags in the Doxygen documentation. Here we can see that three types of tags were automatically generated for us:  @briefprovides a brief description of the function.@paramfollowed by the name of a function argument describes the role of that argument.@returndescribes the return value of the function. For our addfunction, let us use the following descriptions: #include <iostream>
using namespace std;
/**
 * @brief Adds two numbers.
 *
 * @param a The first number to add.
 * @param b The second number to add.
 * @return The sum of the numbers.
 */
double add(const double &a, const double &b)
{
    return a + b;
}
int main()
{
    cout << add(1, 2);
}
 If we now hover with the mouse over the addfunction in VS Code, we will see a nice tooltip displaying the information we entered. Now, let's go to the very first line of the file, and then type/**and Enter. A new multi-line comment template will appear: /**
 * @file main.cpp
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2021-11-29
 * @copyright Copyright (c) 2021
 */
 If you go to the Doxygen Documentation Generator settings (Ctrl+, and search for doxygen), under "Generic: Author Email" and "Generic: Author Name", you can enter your details, so they will be added automatically by the extension when you use this shortcut (i.e. type /**and Enter in the first line of the file). The tags@file,@author,@version,@date, and@copyrightare self-explanatory. After filling out these values, the final version of the program will be: /**
 * @file main.cpp
 * @author Barak Shoshany (baraksh@gmail.com) (https://baraksh.com)
 * @brief A program to print the result of 1 + 2.
 * @version 0.1
 * @date 2021-11-29
 * @copyright Copyright (c) 2021 Barak Shoshany
 */
#include <iostream>
using namespace std;
/**
 * @brief Adds two numbers.
 *
 * @param a The first number to add.
 * @param b The second number to add.
 * @return The sum of the numbers.
 */
double add(const double &a, const double &b)
{
    return a + b;
}
int main()
{
    cout << add(1, 2);
}
 Adding Doxygen comments to your source code makes it much more readable, since it is a standard comment format that everyone knows, and it also allows IDEs such as Visual Studio Code to automatically generate nice tooltips for different elements of your code. If you want, you can also download the Doxygen tool itself and use it to generate HTML documentation for your program directly from the comments. However, I do not recommend doing that. The documentation will just be a list of classes, functions, and other parts of your code, and it can serve as a reference, but the user can also just look in your source code itself and find the same information. It is much more instructive to create your own documentation in Markdown format and explain how to use your code in a pedagogical way, with many details and examples. For a good example of using Markdown documentation and Doxygen comments to document a C++ library, check out my thread pool library on GitHub. | 
|  | 
| Visual Studio Code comes with built-in support for Git, a distributed version control system. Git provides a convenient way for many people to work on the same project, tracking all of the changes made by different people to each file. You may already be familiar with this concept from Microsoft Word or Google Docs, for example, but Git is much more sophisticated, as we will see. Git is commonly used by software developers, and it is also used to power websites such as GitHub - but you can use it independently of any specific website, because Git is a distributed version control system, meaning that every user stores the complete codebase, including the full history of changes, on their own computer, rather than only storing that information on a central server. Even if you don't intend to collaborate with others, Git is an extremely useful tool to track changes in your own code. Since Git saves the entire history of your code, you don't need to perform manual backups before making any substantial changes. You can save snapshots of the codebase, and revert back to old versions at any time. You can even make different branches of the project in order to try out different things without affecting the original branch. To install Git on Windows, Linux, or Mac, follow the instructions on the downloads page. Make sure to choose "Use Visual Studio Code as Git's default editor" during the installation. For everything else, just use the recommended options. You can also use a package manager: sudo apt-get install giton Ubuntu orwinget install giton Windows with winget. Now open a terminal window (you can also use VS Code's integrated terminal) and type: git config --global user.email "your@email.address"
git config --global user.name "Your Name"
 This will set up your identity in Git. When you make changes, the name and email you specified here will be used to identify you. You only need to do this once. (If these commands don't work, make sure the folder where gitwas installed is in your system'sPATH.) Throughout this chapter, I will be teaching you how to use Git in two equivalent ways: using VS Code's GUI, and using terminal commands. Using a GUI is always more convenient, but knowing the corresponding terminal commands is important in case you ever need to use Git through the terminal in a remote system, such as a high-performance computing cluster, without the benefit of a GUI. The first thing we need to do in order to use Git is to initialize a repository. In Git, a repository is simply a collection of files that will be tracked; generally, one repository will store the entirety of your project's files. A repository can be either local (stored on your computer) or remote (stored on a remote server such as GitHub). To initialize a repository in the currently open workspace folder:  In VS Code: Go to the Source Control view of the Activity bar on the left, or press Ctrl+Shift+G, and click on "Initialize Repository".In the terminal: Type git init. This will create a .gitfolder in the workspace folder, which will be used to store the complete history of changes made to files in the workspace folder and any subfolders. If you ever decide that you do not wish to use Git anymore, simply delete the.gitfolder.  Warning: Never make any changes manually to the .gitfolder itself. This may lead to loss of data. The .gitfolder will not be visible in VS Code's Explorer view, since it's not a folder you're supposed to make changes to manually, but you can see it in the operating system's File Explorer, or in the terminal by typingdir /aon Windows Command Prompt,dir -Forceon Windows PowerShell, orls -laon Linux. The first thing we're going to do is create a file named .gitignorein the workspace folder with the following contents: # Ignore Visual Studio Code configuration folder
.vscode/
# Ignore compiled object files and executables
*.o
*.exe
 This simply instructs Git to ignore any files that match the listed patterns. Note that lines starting with #are comments. We generally don't need to include the.vscodefolder in the repository, since it won't be of interest to people who are not using VS Code. We also don't want any compiled object files or executables to be included, since they won't be useful for people who use a different OS or CPU family. (Generally, people will compile the code separately on their computer, or download pre-compiled binaries that are not part of the repository.) Assuming your workspace folder was empty, other than potentially a .vscodefolder, the.gitignorefile will appear in both the Explorer view (Ctrl+Shift+E) or the Source Control view (Ctrl+Shift+G) with the letter U next to it, indicating that it is untracked. You can also see this information if you writegit statusin the terminal, which will list.gitignoreunder "untracked files". To start tracking changes in a file, we need to add it to the staging area:  In VS Code: Go to the Source Control view, hover over the file .gitignore, and click the button that looks like a+(or right-click and choose "Stage Changes").In the terminal: Type git add .gitignore. The file will move to the Staged Changes area of the Source Control view, and if you write git statusin the terminal, you will seenew file: git_test.cppunder "Changes to be committed". To actually record the staged changes, we need to commit them:  In VS Code: Go to the Source Control view, write the message Created .gitignorein the text box, and then either press Ctrl+Enter or click the button above the text box that looks like a check mark.In the terminal: Type git commit -m "Created .gitignore". One we commit the changes, Git creates a permanent snapshot of the project, called a commit or revision. The message Created .gitignorewill be attached to this commit; generally, the commit message should be short, and provide a concise summary of the changes that were made since the last commit. To view a history of commits:  In VS Code: Go to the Explorer view (Ctrl+Shift+E) and look in the Timeline section (if you can't see it, click on the overflow menu on top and choose "Timeline"), which will show you the history of commits for the file that is currently open in the editor.In the terminal: Type git log. Right now you will see one commit, with the message Created .gitignore, along with the name of the author and when the commit was performed. I highly recommend installing the GitLens extension for VS Code. It allows you to view a full history of commits, rather than just the commits for the currently active file, plus plenty of other extremely useful features; see the extension page for more information. | 
| To illustrate how to use Git, let us create a simple main.cppwhich displays the powers of 2 from 0 to 10: #include <cmath>
#include <iostream>
int main()
{
    for (uint64_t i = 0; i <= 10; i++)
        std::cout << "2^" << i << " = " << pow(2, i) << '\n';
}
 You can compile and run this program; notice that if you do so, the .exefile you created will be ignored by Git. (On Linux the executable won't have an extension, so it won't be ignored by the.gitignorefile we defined above, but you can just add the specific file name you want to use to.gitignore.) Stage and commit this file with the message Created main.cpp. Now, let us make a small change. Addusing namespace std;on top and removestd::from thecoutstatement: #include <cmath>
#include <iostream>
using namespace std;
int main()
{
    for (uint64_t i = 0; i <= 10; i++)
        cout << "2^" << i << " = " << pow(2, i) << '\n';
}
 Once you save the file, you will notice a few things in VS Code:  In both the Explorer view and the Source Control view, an M appears next to the file name, indicating that the file has been modified since the last commit.In the Timeline section, you will see "Uncommitted Changes" appear.In the source code itself, you will see different colored bars (called gutter indicators) appear next to the line numbers 4, 5, and 9, indicating that you made changes to those lines. Clicking on these indicators will reveal the changes and allow you to stage or revert each change individually. A red triangle indicates deleted lines, a green bar indicates added lines, and a blue bar indicates modified lines. Also, if you type git statusin the terminal, you will seemodified: main.cppunder "Changes not staged for commit". To see a full list of what has changed, we can:  In VS Code: Click on the button labeled "Open Changes" on the top right of the editor.Or click on the changed file in the Source Control view (or right-click and choose "Open Changes").Or double-click on the "Uncommitted Changes" in the Timeline section of the Explorer view for that file (or right-click and choose "Open Changes").In the terminal: Type git diff. Stage this file as explained above (click the +or typegit add main.cpp). Notice that the gutter indicators in the editor have disappeared, since they only appear for unstaged changes. However, if you go to the Timeline section and click on "Staged Changes", you will be able to see the changes. Similarly, if you typegit diffin the terminal, you do not see the staged changes. However, if you typegit diff --staged, you will be able to see them. Now let us practice how to unstage a change:  In VS Code: Go to the Source Control view, hover over the file main.cpp, and click the button that looks like a-(or right-click and choose "Unstage Changes"),In the terminal: Type git reset -- main.cpp. Not happy with the changes? We can easily revert to the last committed version:  In VS Code: Click on the button labeled "Discard Changes" (looks like a curved arrow) in the Source Control view (or right-click and choose "Discard Changes").In the terminal: Type git checkout -- main.cpp. Also, as a reminder, when you click on the gutter indicators, you can view, stage, and revert individual changes. Let us now make the changes again (or just press Ctrl+Z to undo the revert in VS Code) and then add a new file, README.md: # Powers of 2
This program prints out the powers of 2 from 0 to 10.
 In the Explorer or Source Control view, the file README.mdwill have a U next to it, whilemain.cppwill have an M next to it. This is becausemain.cppis in the staging area, whileREADME.mdis not. Sincemain.cppis in the staging area, the changes are tracked, and we don't need to add it to the staging area again. However, if we want to commit the changes, we still need to stage the changes using either the+button orgit add. This has the benefit that we don't have to commit all of the changes at once every time; we can pick and choose which changes we want to commit, and we can use a different message for each commit. So in this case, we could, for example, first stage README.mdand commit it with the messageCreated README.md, and then stagemain.cppand commit it with the messageAdded using namespace std to main.cpp. We could even stage and commit each of the two changes in main.cppseparately by clicking on the corresponding gutter indicator in VS Code and then clicking on the+button labeled "Stage Change"; for example, we could commit the first change asAdded using namespace std to main.cppand the second change asChanged std::cout to cout in main.cpp. However, it is usually not necessary to split a commit into such small parts. Instead of staging each file and/or each change individually, let us now stage all of the changes at once, both in main.cppandREADME.md:  In VS Code: Go to the Source Control view and click the button that looks like a +next to the title "Changed" (or right-click and choose "Stage All Changes"),In the terminal: Type git add .. To unstage all of the changes, we can similarly:  In VS Code: Go to the Source Control view and click the button that looks like a -next to the title "Staged Changed" (or right-click and choose "Stage All Changes"),In the terminal: Type git reset. In fact, if we want to save a few clicks, we can even commit all of the files in the workspace folder without staging them first:  In VS Code: Go to the Source Control view, write the message Created README.md and added using namespace stdin the text box, press Ctrl+Enter or click the check mark, and select "Yes" when asked "Would you like to stage all your changes and commit them directly?" (note that you can also select "Always" here to do this automatically from now on).In the terminal: Type git commit -m "Created README.md and added using namespace std" -a. The-ainstructs Git to automatically stage and commit all of the files in the repository. If we accidentally performed a commit, we can always undo the last commit, which means it will be as if we never performed the commit (it will not appear in the history):  In VS Code: Go to the Source Control view, click on the overflow menu (looks like three dots), and choose "Commit" > "Undo Last Commit".In the terminal: Type git reset --soft HEAD~. The flag--softmeans this is a soft reset, so the changes will still be staged - you only undid the commit itself. If you don't write--soft, the changes will not be staged.HEADis simply a pointer to the most recent commit, andHEAD~(equivalentHEAD~1) means "1 commit beforeHEAD". We could similarly useHEAD~2to go two commits back, and so on. After undoing the commit, you will notice that it does not appear in the commit history - not in VS Code (using the Git History extension) and also not if writing git logto the terminal. Let us now perform the commit again. What if we want to go back to a previous commit, but not undo this one? For example, go back to the very first commit? This can be done as follows:  In VS Code: Using the Git History extension, when viewing the history, click "More" next to the first commit (Added .gitignore) and choose "Checkout (...) commit" from the menu, where ... will be some short hash (a hexadecimal number representing the commit).In the terminal: First type git logand copy the long hash (hexadecimal number) above the first commit (Added .gitignore). Then typegit checkout ...where...is the hash you copied. Checking out a commit means changing the state of all of the files in the workspace folder to their state when that commit was performed. You will now see that both main.cppandREADME.mdhave been removed from the workspace folder! We reverted the repository to the very first commit, before these files were created. If you view the history, either using the Git History extension or by typinggit log, you will only see one commit - the one that you reverted to. Finally, to go back to (or checkout) the latest commit:  In VS Code: In the left corner of the status bar on the bottom of the window, you will see the same short hash from before. Click on it and choose masterfrom the menu. Notice that the name on the status bar will change tomaster(as it was before we checked out the first commit).Or go to the Source Control view, click on the overflow menu, choose "Checkout to...", and then choose masterfrom the menu.Or, using the Git History extension, when viewing the history, click on the menu next to the Search button, choose "All branches", click on the green button labeled masterthat will appear, and confirm that you want to "checkout to branchmaster".In the terminal: Type git checkout master. | 
| A branch in Git is essentially a different version of the codebase that is being developed separately from the main version. By default, a Git project only has one branch, the master branch, which is the official, stable branch of the project that most people should use. In addition, it may have a development or unstable branch, which is where new features are being added and tested. Changes to the development branch do not affect the master branch. Once the new features are sufficiently stable and bug-free, the changes can then be merged into the master branch. To list the branches currently in the repository:  In VS Code: Click on the branch name in the left corner of the status bar.Or go to the Source Control view, click on the overflow menu, and choose "Checkout to...".Or, using the Git History extension, when viewing the history, click on the menu next to the Search button.In the terminal: Type git branch. In both cases, there will currently be just one branch: master. (The one with the hash that we saw before was a temporary branch, also called a "detachedHEAD".) Let us now create a separate branch for our program, which will display powers of 3instead of2, with the highest power being20instead of10. The name for the new branch should be in lowercase, either one word or a few short words connected by a dash. We will choosepowers-of-3as the branch name:  In VS Code: Click on the branch name in the left corner of the status bar, choose "Create new branch...", and write powers-of-3.Or go to the Source Control view, click on the overflow menu, choose "Branch" > "Create Branch", and write powers-of-3.In the terminal: Type git branch powers-of-3and thengit checkout powers-of-3. (VS Code automatically checks out a newly-created branch.) We now change main.cppto: #include <cmath>
#include <iostream>
using namespace std;
int main()
{
    for (uint64_t i = 0; i <= 20; i++)
        cout << "3^" << i << " = " << (uint64_t)pow(3, i) << '\n';
}
 (I added a type cast to uint64_tin order to display the higher powers correctly as integers instead of scientific notation.) We also change README.mdto: # Powers of 3
This program prints out the powers of 3 from 0 to 20.
 Then, we commit the changes with the message Changed base to 3 and highest power to 20. Notice that the history now shows four commits, from the first one (Added .gitignore) to the one we just made. The three previous commits will be displayed as part of themasterbranch, while the most recent commit will be part of thepowers-of-3branch. Let us checkout the masterbranch again. We see that the files returned to their previous versions, and the new commit is gone from the history. We now make a change to themasterbranch - changing the highest power to15, but leaving the base at2inmain.cpp: #include <cmath>
#include <iostream>
using namespace std;
int main()
{
    for (uint64_t i = 0; i <= 15; i++)
        cout << "2^" << i << " = " << pow(2, i) << '\n';
}
 We also change README.mdto: # Powers of 2
This program prints out the powers of 2 from 0 to 15.
 Let us commit these changes with the message Changed highest power to 15. If we look at the Git history, we will see the three previous commits and the one we just made - all in the branchmaster. Now let us checkout the branchpowers-of-3. The base will now be3and the highest power20, and there is no sign of the last commit we made in themasterbranch. We can switch between the two branches masterandpowers-of-3at will, and work on each branch completely independently - commits in one branch will not affect the other branch. We can similarly create other branches and work on them independently as well. There is much more to Git, which I will unfortunately not have time to cover in this course. Please refer to the official Git documentation for more information. Also, if you want more substantial Git integration into VS Code, you should check out the extension GitLens — Git supercharged. | 
| GitHub is a website which hosts Git repositories, and provides many additional features such as access control. Using GitHub, you can collaborate with other people on a project, while also allowing the general public to download and use it. GitHub is extremely useful for open-source projects, including many scientific computing projects, as it lets anyone in the world suggest contributions to any project through pull requests. It is then up to the project's owners to decide if they want to accept (or merge) the changes into the main codebase. Alternatively, anyone can create their own branch of any GitHub repository and start developing a completely separate project, with a different focus or with specific features added or removed. You can publish to GitHub directly from Visual Studio Code. Instead of creating a local Git repository on your computer first, the easiest way is to simply open a folder without a Git repository in VS Code, go to the Source Control view (Ctrl+Shift+G), and click on "Publish to GitHub". This will open a browser window where you will need to authorize Visual Studio Code to access your GitHub account. Once you do that, the repository will have two versions: a local version on your computer, and a remote one on GitHub (Git refers to a remote repository as originby default). When you commit changes on your computer, they will only be committed locally. In order to send them to GitHub, you need to push the commits. This is done by going to the Source Control view, clicking on the overflow menu, and choosing "Push". Similarly, if you change the remote version directly on GitHub, or if you give someone else access and they push their own changes, these changes will be made to the remote repository only. To get the latest version of the project from GitHub to your computer, you need to pull it. This is done by going to the Source Control view, clicking on the overflow menu, and choosing "Pull". When you execute the pull, the entire repository - including all commits that have been made since the last pull - will be downloaded and merged into the local repository. GitHub has many more features that I will not have time to cover here, but for our purposes in this course - uploading the final course projects to GitHub - this short introduction will suffice. For more information, including how to access GitHub using Git from the terminal, please see the GitHub Learning Lab. For more about using GitHub from Visual Studio Code, please see the official documentation. | 
|  | 
|  | 
| Due to the presence of the containers such as vector in C++, in most cases there is no need to worry about manually allocating and deallocating memory, which is one of the most error-prone aspects of C. If you need to dynamically allocate an array of arbitrary size, simple declare a vectorof the appropriate data type. Nevertheless, in more complicated scenarios, or when very fast speed and very low memory usage are absolutely crucial, the programmer may wish to utilize the full power of C++'s manual memory management capabilities; this is, after all, one of the reasons we are using C++ instead of a higher-level language in the first place! Therefore, let us now discuss how manual memory allocation works in C++.  Warning: The same rules we discussed above for dynamic memory allocation in C still apply in C++. The operators are easier to use, but the same potential for bugs and memory leaks still exists.  In C++, dynamic memory allocation is performed using the operators newanddeleteinstead ofmallocandfree. The syntax fornewis: pointer = new type;       // Allocates a single object
pointer = new type[size]; // Allocates an array of objects
 where pointeris a pointer that will point to the beginning of the allocated memory block,typeis the data type, andsizeindicates the number of elements if we are allocating an array. If the allocation failed, then anstd::bad_allocexception is thrown. As usual, it is extremely important to catch this exception and handle the error. For example, the following program allocates an array of 100 doubles and prints out the first element: #include <iostream>
using namespace std;
int main()
{
    constexpr uint64_t size = 100;
    double *p = nullptr;
    try
    {
        p = new double[size];
    }
    catch (const bad_alloc &e)
    {
        cout << "Error: Failed to allocate memory!\n";
    };
    cout << p[0];
}
 Here the pointer pis declared and then initialized to the result ofnew. However, the array itself will not be initialized, so it will contain garbage. We can initialize it to zeros simply by adding an empty pair of parentheses: p = new double[size]();
 Now the program will print 0. (However, if you really need to initialize the array to zeros, you might as well just usevectorinstead!) When initializing just one object, we can use the usual initialization syntax. Consider for example allocating and initializing an object of the triangleclass we defined above: triangle *t = new triangle(2, 3, 4);
 This will create a new triangleobject and initialize it to have sides of lengths2,3, and4.  Warning: Single objects in C++ generally do not need to be dynamically allocated. Instead, the object itself should allocate memory for its internal data, usually as part of the constructor. We will learn how to do that, and how to deal with the complications that arise, later in this chapter.   Warning: People coming from Java to C++ should not confuse the newkeyword from Java with that of C++. In Java, thenewkeyword must be used every time you want to construct a new object. In C++, thenewkeyword is only used when you need to allocate memory dynamically; it is almost never used on single objects, and in fact, it is rarely used in general, due to the existence of the STL containers. Do not confuse thenewkeyword in C++ with the similar keyword in Java, they mean different things! To deallocate memory, we use delete: delete pointer;   // Deallocates a single object
delete[] pointer; // Deallocates an array of objects
  Warning: In almost all cases, it is preferred to use smart pointers to deallocate memory automatically instead of using deletemanually. In the following sections we will be deallocating memory manually, but when you actually write your C++ programs, you should use smart pointers instead. We will learn about smart pointers after we understand the basics of dynamic memory allocation in C++. | 
|  Warning: It is extremely important to make sure all memory allocated with newis explicitly deallocated withdeletein order to avoid memory leaks. The following program demonstrates a memory leak: #include <cstdint>
void leak(const uint64_t &s)
{
    int8_t *p = new int8_t[s]();
    // ERROR: We did not release the allocated memory!
}
int main()
{
    // Each call to leak() allocates 1 GB of memory but doesn't release it.
    // Desired behavior: Only 1 GB of memory will be used for the entire loop.
    // Actual behavior: Will allocate more and more memory until it crashes (unless you somehow have more than 1 TB of RAM).
    for (uint64_t i = 0; i < 1000; i++)
        leak(1'000'000'000);
}
 (To remind you, the notation 1'000'000'000is just a way to make large numbers more readable - adding'in the middle of the number doesn't change its value, but can help human readers see how many zeros it has more easily. Also, I included<cstdint>because I wanted access to the fixed-width integer types; usually there is no need to do that since this header file is automatically included when you include<iostream>, but here I never used any streams.) To see that memory is indeed leaking, first open an app that lets you monitor how much memory is used, such as Task Manager on Windows or System Monitor on Linux. You can also press F1 in VS Code, type "process", and choose "Developer: Open Process Explorer" to view memory usage from within the IDE. Alternatively, you can install the Resource Monitor VS Code extension to see CPU and memory usage directly in the status bar. Make sure to run the code above without compiler optimizations. You will see that more and more memory keeps getting allocated. Be careful - your system may crash if too much memory is allocated! (On modern operating systems it is actually possible to allocate more than the amount of physical RAM you have, due to virtual memory, but even that has its limits.) Now add the line delete[] p;at the end of the functionleak, and run it again. You can verify that this time, only 1 GB of memory is allocated for the entire run time of the program.  Warning: A commonly overlooked case where memory leaks can happen is when using exceptions. It is easy to forget that when an exception is thrown within a function, the rest of the function is never executed - including any deletestatements! You should avoid code that looks like this: void some_function()
{
    type *p = new type[size];
    // ...
    // Code that may throw an exception
    // ...
    delete[] p;
}
 If an exception is thrown, deletenever gets called, and you get a memory leak. | 
| In a class which stores an arbitrary amount of data, memory allocation will typically take place in the constructor. This means that memory will be allocated by the object as soon as we create it. An object may also allocate memory within various member functions during its lifetime. When this object is destroyed - for example, when its scope ends - this memory must be freed, or it will cause a memory leak. Memory deallocation should be done in the destructor, which is a member function that is executed automatically when the object is destroyed. The destructor should also do other resource-freeing tasks if needed, such as closing files that were opened by the object. Standard library containers such as vector, which allocate memory dynamically, have their own destructors - this is exactly why we don't have to free up memory manually when we usevector. However, for classes we define on our own, if we decide to manage memory ourselves rather than usevector, we also need to write our own destructors. Essentially, if (and only if) you usednewanywhere in the class, there must be a correspondingdeletein the destructor. The compiler automatically calls the destructors in reverse order of construction when a function ends. For example, consider the program: #include <string>
using namespace std;
int main()
{
    string a;
    string b;
}
 When the program exits, b's destructor is called first, and thena's destructor. Similarly, if we create an array of objects, the objects will be destructed in reverse order of construction. For example, if we had an array of three strings,string s[3], thens[2]is destructed first, thens[1], and finallys[0]. The same principle applies to objects created in any other context: First Constructed, Last Destructed. To define a destructor, we simply add a member function that has the same name as the class, no input or output, and the ~prefix. The general syntax is: class my_class
{
    // Constructor allocates memory
    my_class()
    {
        pointer = new type[size];
    }
    // Destructor deallocates memory
    ~my_class()
    {
        delete[] pointer;
    }
private:
    // The pointer to the allocated block of memory
    double *pointer = nullptr;
};
  Warning: Never explicitly call the destructor of any object. It will always be called automatically when the object's scope ends, whether the scope is a function, a nested code block, or the entire program.  | 
| To demonstrate how to properly use manual memory allocation in C++, let us modify our matrix class template. Recall that vectors are automatically initialized to zeros, but in many cases we actually want to initialize the vector with other values, so we have to initialize it twice, which is a huge waste of time. As we saw above, using manually-allocated C-style arrays can actually speed up our program by a factor of 2. If the matrixclass uses avectorto store the matrix elements, then the same issue occurs. Most matrix operations create a new temporary matrix to use as output. This new matrix will be initialized to zeros automatically, and the matrix operation will then re-initialize the matrix with the desired elements. Again, this is a waste of time. By storing the elements of the matrix inside a C-style array, we allow matrices to be constructed uninitialized. This eliminates redundant initializations, and thus speeds up matrix operations. Furthermore, we also allow the user to create an uninitialized matrix and populate it with values later, which will similarly improve performance. If we knew, at compilation time, exactly how many matrices we want to create and what their sizes are, and these matrices did not require more than a few MB of memory (so they could be stored in the stack), then we could have used the arraycontainer instead of C-style arrays. However, we want to allow creating matrices of arbitrary size at run time, so we must use dynamic memory allocation. In matrix.hpp, replace vector<T> elements;
 under private:in the class definition with T *elements = nullptr;
 Instead of using a vector, we will now be using a C-style array, andelementswill be a pointer to the first element in the array. As usual, pointers must be initialized to a null pointer so that we never accidentally use an uninitialized pointer. Replace the implementation of the first constructor with: template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    elements = new T[rows * cols];
}
 This will allocate a new uninitialized array of with rows * colselements of typeT. The second constructor creates a diagonal matrix from a vector, but we are not using vectors anymore, so let's convert it to taking C-style arrays instead. However, an array doesn't know its own size, so the user is going to have to specify the length of the diagonal manually. We change the declaration to matrix(const uint64_t &, const T *);
 and the implementation to template <typename T>
matrix<T>::matrix(const uint64_t &_size, const T *_diagonal)
    : rows(_size), cols(_size)
{
    if (rows == 0)
        throw zero_size();
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows; i++)
        for (uint64_t j = 0; j < cols; j++)
            elements[(cols * i) + j] = ((i == j) ? _diagonal[i] : 0);
}
 Here we are taking advantage of the fact that we are creating an uninitialized array, so instead of first initializing everything to zeros and then changing the diagonal elements from zero to the desired values, we populate the elements manually to either 0or_diagonal[i]based on whether the element is on the diagonal or not (using the conditional operator?:). Since the user is now giving us the size of the array as the first argument, it's up to them to make sure they give the correct size, otherwise the constructor may try to access memory addresses out of the range of the array, which will cause a segmentation fault. The third constructor creates a diagonal matrix from an initializer_list. Here we just defer to the previous constructor, using the member functionsize()to determine the size andbegin()to get a pointer to the first element. The implementation will be: template <typename T>
matrix<T>::matrix(const initializer_list<T> &_diagonal)
    : matrix(_diagonal.size(), _diagonal.begin()) {}
 The fourth constructor creates a new matrix by copying the elements of a vector, which are assumed to be the elements of the matrix in row-major order. Again, we convert it to using a C-style array instead of a vector. The declaration should change to: matrix(const uint64_t &, const uint64_t &, const T *);
 and the implementation to: template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols, const T *_elements)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = _elements[i];
}
 Again, we first create an uninitialized array and then populate all of the elements manually, avoiding double initialization. Note that this constructor no longer throws the exception initializer_wrong_size, since we have no way of knowing if the actual size of the array isrows * cols. As before, it's up to the user to make sure the array is of the appropriate size, otherwise a segmentation fault may occur. Finally, the fifth constructor creates a new matrix by copying the elements of an initializer_list. Before, this constructor simply delegated to the previous one; but since, unlike an array, aninitializer_listdoes know its own size, we will modify this constructor to check that the size of the initializer is compatible, and throwinitializer_wrong_sizeif not: template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols, const initializer_list<T> &_elements)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    if (_elements.size() != rows * cols)
        throw initializer_wrong_size();
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = _elements.begin()[i];
}
 Notice that we used the begin()member function of theinitializer_listclass to access the elements. In the at()member function, since a C-style array doesn't allow range checking, we must write our own code to detect if an index is out of range and throw an exception as needed. We thus replace the two versions of theatmember function with: template <typename T>
T &matrix<T>::at(const uint64_t &row, const uint64_t &col)
{
    if (row >= rows or col >= cols)
        throw matrix_out_of_range(row, col, rows, cols);
    return elements[(cols * row) + col];
}
template <typename T>
const T &matrix<T>::at(const uint64_t &row, const uint64_t &col) const
{
    if (row >= rows or col >= cols)
        throw matrix_out_of_range(row, col, rows, cols);
    return elements[(cols * row) + col];
}
 And we define a new exception matrix_out_of_rangeas follows: class matrix_out_of_range : public out_of_range
{
public:
    matrix_out_of_range(const uint64_t &row, const uint64_t &col, const uint64_t &rows, const uint64_t &cols) : out_of_range("Tried to access matrix element at row " + to_string(row) + ", column " + to_string(col) + ". Row must be in the range [0," + to_string(rows - 1) + "] and column must be in the range [0," + to_string(cols - 1) + "]."){};
};
 Since we are now creating an uninitialized array, we must modify the overloaded operator*. Previously, it simply added the products of each of the elements in rowiand columnjtoc(i, j), assuming that the initial value ofc(i, j)was automatically initialized to zero. Now we will have to initializec(i, j)to zero manually for eachiandj: template <typename T>
matrix<T> operator*(const matrix<T> &a, const matrix<T> &b)
{
    if (a.get_cols() != b.get_rows())
        throw typename matrix<T>::incompatible_sizes_multiply();
    matrix<T> c(a.get_rows(), b.get_cols());
    for (uint64_t i = 0; i < a.get_rows(); i++)
        for (uint64_t j = 0; j < b.get_cols(); j++)
        {
            c(i, j) = 0;
            for (uint64_t k = 0; k < a.get_cols(); k++)
                c(i, j) += a(i, k) * b(k, j);
        }
    return c;
}
 Lastly, notice that we have several news but nodeletes! If we don't fix that, this class will leak memory, since we will neverdeletethe arrayelements. To correct this, we must add a destructor. Add a declaration for the destructor inside thepublic:part of the class declaration: ~matrix();
 (I like to place the constructors first, the member functions in the middle, and the destructors last, since that reflects the order in which the functions will be executed.) At the end of the file, add the code for the destructor itself: template <typename T>
matrix<T>::~matrix()
{
    delete[] elements;
}
 This will fix the memory leak. But in its current form, the matrixclass is still not well-defined! We need to add a few more functions first. | 
| A copy constructor is a constructor used to create a new object as a copy of an existing object. The syntax for the copy constructor is the same as any other constructor, with the input given by a constreference to an object of the same class. For example, for thematrixclass, the copy constructor will be of the form: matrix(const matrix &m);
 If you do not create your own copy constructor, the compiler generates one automatically. This is known as an implicit copy constructor, and it simply copies the data stored in the old object to the new object. For example, for the matrixclass, the implicit copy constructor generated automatically by the compiler is equivalent to: matrix(const matrix &m) : rows(m.rows), cols(m.cols), elements(m.elements) {}
 Unfortunately, if the object contains pointers, as is the case with the elementspointer in our modifiedmatrixclass, then the implicit copy constructor generated by the compiler will simply copy the value of the pointer, that is, the address it points to. It will not automatically make copies of the actual values of the elements and store them elsewhere in memory. This is almost always undesired, since it means both objects will point to the same location in memory, so if we change the elements of one matrix, it will change the elements of the other as well. To see this, run the following main.cppprogram: #include <iostream>
#include "matrix.hpp"
using namespace std;
int main()
{
    matrix<double> m1{1, 2}; // Create a new 2x2 matrix m1 with 1, 2 on the diagonal using the initializer_list constructor.
    cout << "m1 =\n"
         << m1;            // Prints the matrix we created.
    matrix<double> m2(m1); // Create a new matrix object m2 by copying the existing object m1.
    cout << "After copying m1 to m2:\n";
    cout << "m2 =\n"
         << m2;   // Prints the same matrix; copy SEEMS to be successful.
    m2(0, 1) = 3; // Change the top-right element of m2 to 3. m1 will change as well, since they both point to the SAME array of elements.
    cout << "After changing top-right element of m2:\n";
    cout << "m2 =\n"
         << m2; // Prints a matrix with 3 in the upper left.
    cout << "m1 =\n"
         << m1; // Prints the same matrix; m1 was changed too!
} // Also, the program will crash upon exit since the destructor will attempt to free the same memory address twice!
 We see that copying doesn't quite work as intended. To correct this, we need to create our own copy constructor. The constructor will copy the values of rowsandcolsto the new object, but it will allocate new space in memory for the elements, and assign the new address to the pointerelementsin the new object. Then, it will copy the old elements to the new matrix one by one. Add this code to the matrixclass inmatrix.hpp(right after the other constructors): matrix(const matrix<T> &);
 and this code outside the class: template <typename T>
matrix<T>::matrix(const matrix<T> &m)
    : rows(m.rows), cols(m.cols)
{
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = m.elements[i];
}
 If you now run the program, you will find that changing m2no longer changesm1, and that the program no longer crashes at the end. Thus we have fixed the issues we had before. However, we are still not done... There is yet another issue, which we will fix below. | 
| Let us make a few minor changes to our previous main.cpp: #include <iostream>
#include "matrix.hpp"
using namespace std;
int main()
{
    matrix<double> m1{1, 2}; // Create a new 2x2 matrix m1 with 1, 2 on the diagonal using the initializer_list constructor.
    cout << "m1 =\n"
         << m1;              // Prints the matrix we created.
    matrix<double> m2(2, 2); // Create a new uninitialized 2x2 matrix m2.
    m2 = m1;                 // Assign m1 to m2.
    cout << "After assigning m1 to m2:\n";
    cout << "m2 =\n"
         << m2;   // Prints the same matrix; assignment SEEMS to be successful.
    m2(0, 1) = 3; // Change the top-right element of m2 to 3. m1 will change as well, since they both point to the SAME array of elements.
    cout << "After changing top-right element of m2:\n";
    cout << "m2 =\n"
         << m2; // Prints a matrix with 3 in the upper left.
    cout << "m1 =\n"
         << m1; // Prints the same matrix; m1 was changed too!
} // Also, the program will crash upon exit since the destructor will attempt to free the same memory address twice!
 In the program, we use the assignment operator =to assign the contents of onematrixobject to another. Doing this results in the compiler generating an implicit assignment operator, which just as in the case of the implicit copy constructor, will not work correctly if we are using pointers. In fact, in this case the compiler will warn you about this - since we wrote an explicit copy constructor, the compiler realizes an implicit assignment operator will probably not work correctly: implicitly-declared 'constexpr matrix<double>& matrix<double>::operator=(const matrix<double>&)' is deprecated [-Wdeprecated-copy]
 To solve this problem, we simply need to define an overload of operator=. As we mentioned above, the operator overload of=must be a member function. The object that owns the member function is the target of the assignment, and the argument of the function is the source. Let us add this declaration to matrix.hpp, right after the copy constructor: matrix<T> &operator=(const matrix<T> &);
 and this code below the class: template <typename T>
matrix<T> &matrix<T>::operator=(const matrix<T> &m)
{
    rows = m.rows;
    cols = m.cols;
    delete[] elements;
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = m.elements[i];
    return *this;
}
 If you run the program with this overloaded =, you will see that the issues we had before have been resolved. Notice that we first usedeleteto free up the memory we allocated for the old elements of the target matrix, and then usenewto reallocate memory for the new elements. This is because we want to allow assigning m2 = m1even if the two matrices have different sizes. After the assignment,m2will have the same size asm1, and the new number of elements may be smaller or larger than what we had before. We don't want to use more memory than we need to, and we definitely don't want to use areas of memory that we did not allocate, which will cause a segmentation fault. Another option could be to only allow assigning one matrix to another if they are of the same size (and throw an exception otherwise), in which case we can be certain that we have exactly the right amount of memory already allocated, so we could use the same memory block again. However, this would limit the usability of the assignment operator. Also notice that in the last line, we return the value *this. In C++, thethiskeyword provides a pointer to the object that owns the member function currently being executed. So in this case,thisis a pointer to the matrix object we are assigning to (i.e.m2). We could, for example, replace the statementrows = m.rowsin the first line withthis->rows = m.rowsif we wanted to. Returning the dereferenced pointer*thisallows chaining the assignment with other operators (e.g.a = b = c), which is standard C++ syntax, so the user would expect it to be possible. In conclusion, we have seen that there are two types of copying pointers associated to objects in C++:  Shallow copying means only the pointer is being copied, so that both the old object and the new object will end up pointing to the same memory address. This is what the implicit copy constructor and assignment operator created by the compiler do.Deep copying means copying the values pointed to by the pointer, so that the old object and the new object will end up pointing to different memory addresses. This is what the explicit copy constructor and assignment operator we created do. | 
| The assignment operator we defined in the previous section copies the contents of one matrix to another. This is desirable in case we want to have two distinct matrix objects in the end. However, consider the following program: #include <iostream>
#include "matrix.hpp"
using namespace std;
template <typename T>
matrix<T> generate_matrix(const uint64_t &rows, const uint64_t &cols, const T &value)
{
    matrix<T> m(rows, cols);
    for (uint64_t i = 0; i < rows; i++)
        for (uint64_t j = 0; j < cols; j++)
            m(i, j) = value;
    return m;
}
int main()
{
    matrix<double> ones = generate_matrix(5, 5, 1.0);
    cout << ones;
}
 (As a side note, in generate_matrix(), the typeTis inferred automatically by the compiler from the type of the third argument, so I had to write1.0in that argument, since1.0is interpreted as adouble. If I wrote1, thenTwould have been interpreted as anint, but you cannot assign amatrix<int>to amatrix<double>.) The function generate_matrix()generates a matrix with all of its elements initialized to a particular value. It then returns the actual matrixmas the return value of the function. This means (in principle) that all the elements inmwill be copied, which for large matrices could take a long time. Since mis destroyed anyway as soon as its scope ends, it would be much better performance-wise if we were able to reuse the memory already allocated formitself withingenerate_matrix()for the new matrixoneswe then create inmain(). To do this, we create a move constructor and a move assignment operator. These are similar to the copy constructor and assignment operator, except that they move the elements instead of creating a copy. They are defined exactly the same as their copy versions, except that the argument is a matrix &&instead of aconst matrix &. The argument is notconst, because we are modifying the source object. The notation &&is called an rvalue reference and it is usually only used when declaring function arguments. Essentially, an rvalue reference&&is a reference to a temporary object that will be immediately destroyed after we call the function. This is exactly what we want a move constructor or assignment to do. An lvalue represents an object that has a memory address, and an rvalue is anything that is not an lvalue. If you can use the &operator to access the address of an object, then that object is an lvalue. In particular, variables are generally lvalues, and literals (e.g. explicit numbers like1) are generally rvalues. You will usually see lvalues on the left-hand side of an assignment, and rvalues on the right-hand side, because you can only assign something to an object that is actually stored in memory. For example, x = 1is a valid assignment, sincexis an lvalue while1is an rvalue. However,1 = xis not valid, since1is not an lvalue, so there is no place in memory to assign the value ofxto. Of course, you can have lvalues on both sides, such as inx = y. However,x + 2is a rvalue, so you can writex = x + 2but notx + 2 = x. Let us add the following two declarations to matrix.hpp, the first after the copy constructor and the second after the copy assignment operator: matrix(matrix<T> &&);
 matrix<T> &operator=(matrix<T> &&m);
 The implementation of the move constructor will be: template <typename T>
matrix<T>::matrix(matrix<T> &&m)
    : rows(m.rows), cols(m.cols), elements(m.elements)
{
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
}
 First, the move constructor assigns the number of rows and columns, as well as the pointer to the elements, to the new matrix. Then, it sets the old matrix to a degenerate state, with zero elements, and sets the pointer to the elements of the old matrix to the null pointer nullptr. This is done because when m's destructor is executed, it willdelete[] elements, and if we do not changeelementsto a null pointer, it will deallocate the memory pointed to by the new object! Usingdeleteon a null pointer doesn't do anything, so by replacingelementswith a null pointer, we ensure that the elements do not get accidentally deleted by the destructor. Setting the number of rows and columns to zero is not strictly necessary, but I did it because I want the class invariant - the assumption that the number of elements is equal to rows * cols- to be satisfied even in this degenerate state, just in case. Now let us add the implementation of the move assignment operator: template <typename T>
matrix<T> &matrix<T>::operator=(matrix<T> &&m)
{
    rows = m.rows;
    cols = m.cols;
    delete[] elements;
    elements = m.elements;
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
    return *this;
}
 You will notice that this is essentially a combination of the move constructor with the copy assignment operator. First, we give the target matrix the same number of rows and columns as the source matrix. Then we use deleteto deallocate the memory we previously allocated for the elements of the target matrix, and instead simply setelementsto point to the address of the elements of the source matrix. Finally, we set the source matrix to a degenerate state, and return the target matrix. Technically speaking, the move constructor will not actually get called in the program we wrote above, because most compilers will be able to detect that a move is required (since they can see mis going to be destroyed after thereturn) and optimize the code to do a move instead of a copy automatically. However, this is only done in cases where the compiler can detect that a move is required, and it can be turned off by the user at compilation time by adding the compiler argument -fno-elide-constructors. You should always write optimized code instead of trusting the compiler's optimizations to do it for you! | 
| We have seen that in order to perform manual memory allocation for our matrixclass, which may improve performance compared to automatic memory allocation withvector, we must define five essential member functions that we did not need before:  DestructorCopy constructorCopy assignmentMove constructorMove assignment For reference, the complete matrix.hppafter all the modifications (including changing the comments where required) should be as follows: #include <initializer_list>
#include <iostream>
#include <stdexcept>
#include <vector>
using namespace std;
// =========
// Interface
// =========
template <typename T>
class matrix
{
public:
    // Constructor to create n UNINITIALIZED matrix.
    // First argument: number of rows.
    // Second argument: number of columns.
    matrix(const uint64_t &, const uint64_t &);
    // Constructor to create a diagonal matrix from an array.
    // First argument: length of the diagonal (equal to the number of rows and columns).
    // Second argument: an array containing the elements on the diagonal. The elements will be copied into the matrix.
    matrix(const uint64_t &, const T *);
    // Constructor to create a diagonal matrix from an initializer_list.
    // Argument: an initializer_list containing the elements on the diagonal.
    // Number of rows and columns is inferred automatically.
    matrix(const initializer_list<T> &);
    // Constructor to create a matrix from an array.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: an array containing the elements in row-major order. The elements will be copied into the matrix.
    matrix(const uint64_t &, const uint64_t &, const T *);
    // Constructor to create a matrix from an initializer_list.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: an initializer_list containing the elements in row-major order.
    matrix(const uint64_t &, const uint64_t &, const initializer_list<T> &);
    // Copy constructor.
    matrix(const matrix<T> &);
    // Move constructor.
    matrix(matrix<T> &&);
    // Overloaded copy assignment operator.
    matrix<T> &operator=(const matrix<T> &);
    // Overloaded move assignment operator.
    matrix<T> &operator=(matrix<T> &&m);
    // Member function to obtain (but not modify) the number of rows in the matrix.
    uint64_t get_rows() const;
    // Member function to obtain (but not modify) the number of columns in the matrix.
    uint64_t get_cols() const;
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // First version: allows modification of the element.
    T &operator()(const uint64_t &, const uint64_t &);
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // Second version: does not allow modification of the element.
    const T &operator()(const uint64_t &, const uint64_t &) const;
    // Member function to access matrix elements WITH range checking (throws out_of_range).
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // First version: allows modification of the element.
    T &at(const uint64_t &, const uint64_t &);
    // Member function to access matrix elements WITH range checking (throws out_of_range).
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // Second version: does not allow modification of the element.
    const T &at(const uint64_t &, const uint64_t &) const;
    // Exception to be thrown if the number of rows or columns given to the constructor is zero.
    class zero_size : public invalid_argument
    {
    public:
        zero_size() : invalid_argument("Matrix cannot have zero rows or columns!"){};
    };
    // Exception to be thrown if the vector of elements provided to the constructor is of the wrong size.
    class initializer_wrong_size : public invalid_argument
    {
    public:
        initializer_wrong_size() : invalid_argument("Initializer does not have the expected number of elements!"){};
    };
    // Exception to be thrown if two matrices of different sizes are added or subtracted.
    class incompatible_sizes_add : public invalid_argument
    {
    public:
        incompatible_sizes_add() : invalid_argument("Cannot add or subtract two matrices of different dimensions!"){};
    };
    // Exception to be thrown if two matrices of incompatible sizes are multiplied.
    class incompatible_sizes_multiply : public invalid_argument
    {
    public:
        incompatible_sizes_multiply() : invalid_argument("Two matrices can only be multiplied if the number of columns in the first matrix is equal to the number of rows in the second matrix!"){};
    };
    // Destructor.
    ~matrix();
    // Exception to be thrown if the requested matrix element is out of range.
    class matrix_out_of_range : public out_of_range
    {
    public:
        matrix_out_of_range(const uint64_t &row, const uint64_t &col, const uint64_t &rows, const uint64_t &cols) : out_of_range("Tried to access matrix element at row " + to_string(row) + ", column " + to_string(col) + ". Row must be in the range [0," + to_string(rows - 1) + "] and column must be in the range [0," + to_string(cols - 1) + "]."){};
    };
private:
    // The number of rows.
    uint64_t rows = 0;
    // The number of columns.
    uint64_t cols = 0;
    // An array storing the elements of the matrix in flattened (1-dimensional) form.
    T *elements = nullptr;
};
// Overloaded binary operator << to easily print out a matrix to a stream.
template <typename T>
ostream &operator<<(ostream &, const matrix<T> &);
// Overloaded binary operator + to add two matrices.
template <typename T>
matrix<T> operator+(const matrix<T> &, const matrix<T> &);
// Overloaded binary operator += to add two matrices and assign the result to the first one.
template <typename T>
matrix<T> operator+=(matrix<T> &, const matrix<T> &);
// Overloaded unary operator - to take the negative of a matrix.
template <typename T>
matrix<T> operator-(const matrix<T> &);
// Overloaded binary operator - to subtract two matrices.
template <typename T>
matrix<T> operator-(const matrix<T> &, const matrix<T> &);
// Overloaded binary operator -= to subtract two matrices and assign the result to the first one.
template <typename T>
matrix<T> operator-=(matrix<T> &, const matrix<T> &);
// Overloaded binary operator * to multiply two matrices.
template <typename T>
matrix<T> operator*(const matrix<T> &, const matrix<T> &);
// Overloaded binary operator * to multiply a scalar on the left and a matrix on the right.
template <typename T>
matrix<T> operator*(const T &, const matrix<T> &);
// Overloaded binary operator * to multiply a matrix on the left and a scalar on the right.
template <typename T>
matrix<T> operator*(const matrix<T> &, const T &);
// ==============
// Implementation
// ==============
template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    elements = new T[rows * cols];
}
template <typename T>
matrix<T>::matrix(const uint64_t &_size, const T *_diagonal)
    : rows(_size), cols(_size)
{
    if (rows == 0)
        throw zero_size();
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows; i++)
        for (uint64_t j = 0; j < cols; j++)
            elements[(cols * i) + j] = ((i == j) ? _diagonal[i] : 0);
}
template <typename T>
matrix<T>::matrix(const initializer_list<T> &_diagonal)
    : matrix(_diagonal.size(), _diagonal.begin()) {}
template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols, const T *_elements)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = _elements[i];
}
template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols, const initializer_list<T> &_elements)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    if (_elements.size() != rows * cols)
        throw initializer_wrong_size();
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = _elements.begin()[i];
}
template <typename T>
matrix<T>::matrix(const matrix<T> &m)
    : rows(m.rows), cols(m.cols)
{
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = m.elements[i];
}
template <typename T>
matrix<T>::matrix(matrix<T> &&m)
    : rows(m.rows), cols(m.cols), elements(m.elements)
{
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
}
template <typename T>
matrix<T> &matrix<T>::operator=(const matrix<T> &m)
{
    rows = m.rows;
    cols = m.cols;
    delete[] elements;
    elements = new T[rows * cols];
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = m.elements[i];
    return *this;
}
template <typename T>
matrix<T> &matrix<T>::operator=(matrix<T> &&m)
{
    rows = m.rows;
    cols = m.cols;
    delete[] elements;
    elements = m.elements;
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
    return *this;
}
template <typename T>
uint64_t matrix<T>::get_rows() const
{
    return rows;
}
template <typename T>
uint64_t matrix<T>::get_cols() const
{
    return cols;
}
template <typename T>
T &matrix<T>::operator()(const uint64_t &row, const uint64_t &col)
{
    return elements[(cols * row) + col];
}
template <typename T>
const T &matrix<T>::operator()(const uint64_t &row, const uint64_t &col) const
{
    return elements[(cols * row) + col];
}
template <typename T>
T &matrix<T>::at(const uint64_t &row, const uint64_t &col)
{
    if (row >= rows or col >= cols)
        throw matrix_out_of_range(row, col, rows, cols);
    return elements[(cols * row) + col];
}
template <typename T>
const T &matrix<T>::at(const uint64_t &row, const uint64_t &col) const
{
    if (row >= rows or col >= cols)
        throw matrix_out_of_range(row, col, rows, cols);
    return elements[(cols * row) + col];
}
template <typename T>
ostream &operator<<(ostream &out, const matrix<T> &m)
{
    out << '\n';
    for (uint64_t i = 0; i < m.get_rows(); i++)
    {
        out << "( ";
        for (uint64_t j = 0; j < m.get_cols(); j++)
            out << m(i, j) << '\t';
        out << ")\n";
    }
    return out;
}
template <typename T>
matrix<T> operator+(const matrix<T> &a, const matrix<T> &b)
{
    if ((a.get_rows() != b.get_rows()) or (a.get_cols() != b.get_cols()))
        throw typename matrix<T>::incompatible_sizes_add();
    matrix<T> c(a.get_rows(), a.get_cols());
    for (uint64_t i = 0; i < a.get_rows(); i++)
        for (uint64_t j = 0; j < a.get_cols(); j++)
            c(i, j) = a(i, j) + b(i, j);
    return c;
}
template <typename T>
matrix<T> operator+=(matrix<T> &a, const matrix<T> &b)
{
    a = a + b;
    return a;
}
template <typename T>
matrix<T> operator-(const matrix<T> &m)
{
    matrix<T> c(m.get_rows(), m.get_cols());
    for (uint64_t i = 0; i < m.get_rows(); i++)
        for (uint64_t j = 0; j < m.get_cols(); j++)
            c(i, j) = -m(i, j);
    return c;
}
template <typename T>
matrix<T> operator-(const matrix<T> &a, const matrix<T> &b)
{
    if ((a.get_rows() != b.get_rows()) or (a.get_cols() != b.get_cols()))
        throw typename matrix<T>::incompatible_sizes_add();
    matrix<T> c(a.get_rows(), a.get_cols());
    for (uint64_t i = 0; i < a.get_rows(); i++)
        for (uint64_t j = 0; j < a.get_cols(); j++)
            c(i, j) = a(i, j) - b(i, j);
    return c;
}
template <typename T>
matrix<T> operator-=(matrix<T> &a, const matrix<T> &b)
{
    a = a - b;
    return a;
}
template <typename T>
matrix<T> operator*(const matrix<T> &a, const matrix<T> &b)
{
    if (a.get_cols() != b.get_rows())
        throw typename matrix<T>::incompatible_sizes_multiply();
    matrix<T> c(a.get_rows(), b.get_cols());
    for (uint64_t i = 0; i < a.get_rows(); i++)
        for (uint64_t j = 0; j < b.get_cols(); j++)
        {
            c(i, j) = 0;
            for (uint64_t k = 0; k < a.get_cols(); k++)
                c(i, j) += a(i, k) * b(k, j);
        }
    return c;
}
template <typename T>
matrix<T> operator*(const T &s, const matrix<T> &m)
{
    matrix<T> c(m.get_rows(), m.get_cols());
    for (uint64_t i = 0; i < m.get_rows(); i++)
        for (uint64_t j = 0; j < m.get_cols(); j++)
            c(i, j) = s * m(i, j);
    return c;
}
template <typename T>
matrix<T> operator*(const matrix<T> &m, const T &s)
{
    return s * m;
}
template <typename T>
matrix<T>::~matrix()
{
    delete[] elements;
}
 We can test it with the following sample main.cpp: #include <exception>
#include <iostream>
#include "matrix.hpp"
using namespace std;
int main()
{
    try
    {
        // First constructor (rows, cols): create an UNINITIALIZED 3x4 matrix.
        matrix<double> A(3, 4);
        cout << "A:"
             << A << '\n';
        // Second constructor (size, diagonal elements): create a 3x3 matrix with 1, 2, 3 on the diagonal.
        double diag[] = {1, 2, 3};
        matrix<double> B(3, diag);
        cout << "B:"
             << B << '\n';
        // Third constructor (initializer_list): create a 4x4 matrix with 1, 2, 3, 4 on the diagonal.
        matrix<double> C{1, 2, 3, 4};
        cout << "C:"
             << C << '\n';
        // Fourth constructor (rows, cols, elements): create a 2x3 matrix with the given elements.
        double elements[] = {1, 2, 3, 4, 5, 6};
        matrix<double> D(2, 3, elements);
        cout << "D:"
             << D << '\n';
        // Fifth constructor (rows, cols, initializer_list): create a 3x2 matrix with the given elements.
        matrix<double> E(3, 2, {7, 8, 9, 10, 11, 12});
        cout << "E:"
             << E << '\n';
        // Clarification of the difference between the {} and () constructors. Note that this may lead to errors if the wrong type of brackets is used!
        // First constructor (rows, cols) will be used: create an UNINITIALIZED 1x2 matrix.
        cout << "matrix<double>(1, 2):";
        cout << matrix<double>(1, 2) << '\n';
        // Third constructor (initializer_list) will be used: create a 2x2 diagonal matrix with 1, 2 on the diagonal.
        cout << "matrix<double>{1, 2}:";
        cout << matrix<double>{1, 2} << '\n';
        // Demonstration of some of the overloaded operators.
        D(0, 2) = 7;
        cout << "D after D(0, 2) = 7:"
             << D << '\n';
        matrix<double> F = D * B;
        cout << "F = D * B:"
             << F << '\n';
        cout << "D + F:"
             << D + F << '\n';
        cout << "7.0 * B:"
             << 7.0 * B << '\n';
        // Any number of operations can be chained together.
        cout << "3.0 * B - 4.0 * E * D:"
             << 3.0 * B - 4.0 * E * D << '\n';
    }
    catch (const exception &e)
    {
        cout << "Error: " << e.what() << '\n';
    }
}
 See the comments for details. A sample output is as follows: A:
( -4.8367e-26   -4.8367e-26     -4.8367e-26     -4.8367e-26     )
( -4.8367e-26   -4.8367e-26     -4.8367e-26     -4.8367e-26     )
( -4.8367e-26   -4.8367e-26     -4.8367e-26     -4.8367e-26     )
B:
( 1     0       0       )
( 0     2       0       )
( 0     0       3       )
C:
( 1     0       0       0       )
( 0     2       0       0       )
( 0     0       3       0       )
( 0     0       0       4       )
D:
( 1     2       3       )
( 4     5       6       )
E:
( 7     8       )
( 9     10      )
( 11    12      )
matrix<double>(1, 2):
( -4.8367e-26   -4.8367e-26     )
matrix<double>{1, 2}:
( 1     0       )
( 0     2       )
D after D(0, 2) = 7:
( 1     2       7       )
( 4     5       6       )
F = D * B:
( 1     4       21      )
( 4     10      18      )
D + F:
( 2     6       28      )
( 8     15      24      )
7.0 * B:
( 7     0       0       )
( 0     14      0       )
( 0     0       21      )
3.0 * B - 4.0 * E * D:
( -153  -216    -388    )
( -196  -266    -492    )
( -236  -328    -587    )
 | 
| The vectorcontainer has two member functions that have the same functionality asinsert()andpush_back(), but they construct objects in place rather than first constructing an object and then copying it.  emplace(pos, args)inserts an object into thevectorat the position given by the iteratorpos, with the object constructed in place.argsare the arguments passed to the constructor.emplace_back(args)does the same, but inserts at the end of thevector. Note that emplace()andemplace_back()have the same iterator invalidation rules asinsert()andpush_back()respectively. insert()andpush_back()will first create a temporary object at some location in memory, and then move it into thevector, which is stored at another location in memory. This takes time.emplace()andemplace_back(), on the other hand, create the object in place at the memory address reserved byvector, so a move constructor does not need to be called. This is illustrated by the following program:
 #include <iostream>
#include <vector>
using namespace std;
class my_class
{
public:
    // Constructor
    my_class(const int64_t &in) : i(in)
    {
        cout << "Constructor called!\n";
    }
    // Move constructor
    my_class(my_class &&m) : i(m.i)
    {
        cout << "Move constructor called!\n";
    }
private:
    int64_t i = 0;
};
int main()
{
    vector<my_class> v;
    v.reserve(2);
    v.push_back(my_class(1));
    cout << "push_back() successful.\n\n";
    v.emplace_back(2);
    cout << "emplace_back() successful.\n";
}
 The output is: Constructor called!
Move constructor called!
push_back() successful.
Constructor called!
emplace_back() successful.
 We see that using push_back()requires calling the move constructor, while usingemplace_back()does not. Therefore, whenever the object to be inserted needs to be constructed on the spot,emplace()oremplace_back()should be used instead ofinsert()orpush_back(). | 
|  | 
| Above we wrote a program to demonstrate memory leaks. We "detected" the leaks by manually monitoring how much memory is used by the program. This naturally only allows detecting very big leaks, and it also doesn't tell us any specific information about where the leak is coming from. Let us now use demonstrate how to use a tool to automatically detect memory leaks, as well as many other types of memory-related errors. The tool we will use is called Dr. Memory. It is fully cross-platform, so it will work on every student's computer regardless of which OS or CPU architecture they use. Dr. Memory can detect a variety of memory errors, including:  Memory leaks,Reading from uninitialized memory,Accessing unallocated memory,Attempting to free the same memory block more than once,And more. To use Dr. Memory, download the latest version from the relevant link in the Download section of the website, and install it using the relevant instructions in the Documentation section of the website. We will use the following test program: int main()
{
    double *p = new double[1000];
    if (p[0] == 0) // ERROR: Reading from uninitialized memory address!
        p[1] = 0;
    p[1000] = 0; // ERROR: Writing to unallocated memory address!
    // ERROR: We did not free the allocated memory!
    double *t = new double[1000];
    delete[] t;
    delete[] t; // ERROR: Attempting to free the same memory block twice!
}
 Compile this program with the argument -ggdb3, as usual when debugging, plus the following additional arguments:  -static-libgccand-static-libstdc++instruct the compiler to place any code for the C and C++ standard libraries directly in the executable file itself (as a statically linked library), instead using a separate file shared by all C++ programs (a shared library). This will significantly increase the size of the executable file, but will allow the memory debugger to access the code for the standard library directly.-fno-inlinewill disable function inlining, which can interfere with memory debugging.-fno-omit-frame-pointerinstructs the compiler not to omit a certain pointer called the frame pointer in functions, which can also interfere with memory debugging. After adding these arguments, the argsfield of yourtasks.jsonshould look similar to this: "args": [
    "${workspaceFolder}\\*.cpp",
    "-o",
    "${workspaceFolder}\\CSE701.exe",
    "-Wall",
    "-Wextra",
    "-Wconversion",
    "-Wsign-conversion",
    "-Wshadow",
    "-Wpedantic",
    "-std=c++23",
    "-ggdb3",
    "-static-libgcc",
    "-static-libstdc++",
    "-fno-inline",
    "-fno-omit-frame-pointer"
],
 You can compile the program with these arguments without actually running it, by choosing Terminal > Run Build Task... or pressing Ctrl+Shift+B. Note that you will not get any warnings from the compiler, even though there are 4 very serious errors in the program! If you run the program, it will crash due to the double deletion of the array t. But we actually won't run it directly; we will run it through Dr. Memory in order to detect the memory issues. To run Dr. Memory, first create a subfolder called drmemoryin your workspace folder, and then go to the VS Code integrated terminal (Ctrl+`) and type: drmemory -logdir ./drmemory -batch -- CSE701.exe
 On Linux or Mac you should remove the .exeextension. Also, this assumes that Dr. Memory's binary folder has been added to your system'sPATHenvironment variable, which should be performed automatically by the installer. If it doesn't work, you may have to add that folder to thePATHmanually (I described how to add a folder to thePATHabove). The argument -logdir ./drmemorywill instruct Dr. Memory to store its logs in thedrmemorysubfolder, instead of in another folder outside of the workspace.-batchwill instruct Dr. Memory not to automatically open the results file after it runs. Finally,-- CSE701.exeindicates that the debugger should run the programCSE701.exe. The terminal will display a lot of information, including any errors detected in the program. After the execution is complete, some new files and folders will be created under the drmemorysubfolder. There will be a folder namedDrMemory-CSE701.exe.XXXXX.000withXXXXXbeing some number. The first time you run Dr. Memory, it may restart itself in order to automatically generate some required files, in which case there will be two such folders with different numbers. Open the folder DrMemory-CSE701.exe.XXXXX.000, and then open the fileresults.txt. This file includes the same information that was output to the terminal, but it's more convenient to read in the form of a file. If you have two folders, then one of them will contain an emptyresults.txtfile that you can safely ignore. Here is what I get in results.txt: Error #1: UNADDRESSABLE ACCESS beyond top of stack: reading 0x000000000065fab0-0x000000000065fab8 8 byte(s)
# 0 .text                                   [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/pesect.c:230]
# 1 _pei386_runtime_relocator               [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/pseudo-reloc.c:477]
# 2 __tmainCRTStartup                       [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:279]
# 3 .l_start                                [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:212]
# 4 KERNEL32.dll!BaseThreadInitThunk
Note: @0:00:00.401 in thread 14880
Note: 0x000000000065fab0 refers to 744 byte(s) beyond the top of the stack 0x000000000065fd98
Note: instruction: or     $0x0000000000000000 (%rcx) -> (%rcx)
Error #2: UNINITIALIZED READ: reading register xmm0
# 0 main               [C:/Users/barak/CSE 701/main.cpp:4]
Note: @0:00:00.425 in thread 14880
Note: instruction: ucomisd %xmm0 %xmm1
Error #3: UNADDRESSABLE ACCESS beyond heap bounds: writing 0x0000000001d54620-0x0000000001d54628 8 byte(s)
# 0 main               [C:/Users/barak/CSE 701/main.cpp:6]
Note: @0:00:00.427 in thread 14880
Note: instruction: movsd  %xmm0 -> (%rax)
Error #4: INVALID HEAP ARGUMENT to free 0x0000000001d54640
# 0 replace_operator_delete_array_nothrow               [d:\drmemory_package\common\alloc_replace.c:2999]
# 1 main                                                [C:/Users/barak/CSE 701/main.cpp:11]
Note: @0:00:00.444 in thread 14880
Note: memory was previously freed here:
Note: # 0 replace_operator_delete_array_nothrow               [d:\drmemory_package\common\alloc_replace.c:2999]
Note: # 1 main                                                [C:/Users/barak/CSE 701/main.cpp:10]
Error #5: POSSIBLE LEAK 26 direct bytes 0x0000000001d401c0-0x0000000001d401da + 0 indirect bytes
# 0 replace_malloc                    [d:\drmemory_package\common\alloc_replace.c:2577]
# 1 msvcrt.dll!realloc               +0x193    (0x00007ffb13fb9f44 <msvcrt.dll+0x19f44>)
# 2 msvcrt.dll!unlock                +0x40c    (0x00007ffb13fdb68d <msvcrt.dll+0x3b68d>)
# 3 msvcrt.dll!_getmainargs          +0x30     (0x00007ffb13fa7a01 <msvcrt.dll+0x7a01>)
# 4 pre_cpp_init                      [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:171]
# 5 msvcrt.dll!initterm              +0x42     (0x00007ffb13fda553 <msvcrt.dll+0x3a553>)
# 6 __tmainCRTStartup                 [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:269]
# 7 .l_start                          [C:/_/M/mingw-w64-crt-git/src/mingw-w64/mingw-w64-crt/crt/crtexe.c:212]
# 8 KERNEL32.dll!BaseThreadInitThunk
Error #6: LEAK 8000 direct bytes 0x0000000001d526e0-0x0000000001d54620 + 0 indirect bytes
# 0 replace_operator_new_array               [d:\drmemory_package\common\alloc_replace.c:2929]
# 1 main                                     [C:/Users/barak/CSE 701/main.cpp:3]
FINAL SUMMARY:
DUPLICATE ERROR COUNTS:
    Error #   1:      2
SUPPRESSIONS USED:
ERRORS FOUND:
      2 unique,     3 total unaddressable access(es)
      1 unique,     1 total uninitialized access(es)
      1 unique,     1 total invalid heap argument(s)
      0 unique,     0 total GDI usage error(s)
      0 unique,     0 total handle leak(s)
      0 unique,     0 total warning(s)
      1 unique,     1 total,   8000 byte(s) of leak(s)
      1 unique,     1 total,     26 byte(s) of possible leak(s)
ERRORS IGNORED:
      4 unique,     4 total,  74793 byte(s) of still-reachable allocation(s)
         (re-run with "-show_reachable" for details)
Details: C:\Users\barak\CSE 701\drmemory\DrMemory-CSE701.exe.6900.000\results.txt
 Let us go over the errors:  Error #1 and Error #5 are, as far as I can tell, not errors in our program itself. They could be false positives.Error #2 is an "UNINITIALIZED READ", detected at line 4 of main.cpp. This is the lineif (p[0] == 0), which reads the first element of the uninitialized array, which means it will read a garbage value.Error #3 is an "UNADDRESSABLE ACCESS beyond heap bounds" of 8 bytes (one double), detected at line 6 ofmain.cpp. This is the linep[1000] = 0, which writes to element number 1001 (counting from zero) of a 1000-element array, thus writing beyond the bounds of the allocated memory.Error #4 is an "INVALID HEAP ARGUMENT to free", detected at line 11 of main.cpp. This is the seconddelete[] t, which attempts to free a memory block that has already been freed. Indeed, the log tells us that the memory was previously freed in line 10.Error #6 is a "LEAK" of 8000 bytes (1000 doubles, i.e. the entire arrayp), detected at line 3 ofmain.cpp. This is the linedouble *p = new double[1000], which allocates the array. Indeed, we forgot todeletethis array.  Warning: As previously emphasized, C and C++ let the programmer manage memory manually, instead of managing it automatically like most higher-level languages, and this can result in a substantial performance increase, but it is also extremely prone to errors. Even if you don't explicitly use dynamic memory allocation in your program, you may still be, for example, using uninitialized values or accessing memory out of the bounds of an array. Therefore, it is important to use memory debugging tools such as Dr. Memory to check for memory-related errors.  Fix all the errors (I'll let you figure out how to do that on your own), recompile the program, and run Dr. Memory again. You will see that all of the errors (aside from any potential false positives, such as #1 and #5 above) will disappear. Once you are done with memory debugging, you should remove all the new compiler arguments you added to tasks.jsonat the beginning of this section. | 
| Resource Acquisition Is Initialization (RAII) is a very important concept in C++ programming. Essentially, it means that each object should manage its own resources, and resources should only be managed in this way. These resources can include memory, files, disk space, network connections, and anything else that exists in limited supply. You can think of RAII as an extension of the concepts of encapsulation and class invariants:  Each object encapsulates not only the data and the functions that process that data, but also any and all resources needed to store and process the data.Resource allocation is considered a class invariant, so if done correctly, it can always be assumed that the resources have been allocated and are available for use. One common situation where memory leaks may happen is if you allocate memory in one class, and then pass the pointer to the allocated memory block to another class. In such a case, it would be hard to guarantee that the memory will be properly deallocated; it may end up never being deallocated at all, causing a memory leak, or being deallocated more than once, causing a crash. Instead, you should follow the RAII principle by both allocating memory and accessing the allocated memory only within a single class. The memory should be allocated as part of the constructor, and deallocated as part of the destructor. Other classes should never access that memory directly, and instead do so only through member functions of the memory-managing class. This will both greatly simplify the way memory allocation works in your program, and reduce the chance of mistakes that may lead to memory leaks and other memory-related bugs. A good example of following this principle is in my matrix class template with dynamic memory allocation; the matrixclass manages its own memory, separately for each object, and no other class has direct access to this memory. The only way to access the managed memory is through the member functionsoperator()orat(). This ensures that memory management is 100% encapsulated by the class. | 
| One way to ensure that memory leaks do not happen is to use STL vectors, which automatically allocate, reallocate, and deallocate memory on the heap as needed. However, we also saw that this incurs a performance penalty. A more sophisticated and optimized way of avoiding memory leaks in modern C++ is using smart pointers. When you allocate memory with the newoperator, instead of storing the result in a raw pointer that you must make sure todeletelater, you store it in a smart pointer. The smart pointer object now owns the raw pointer, and when the smart pointer goes out of scope, for example when a code block or function ends, it automatically deallocates the associated memory block. Smart pointers are C++'s alternative to garbage collection, but unlike garbage collection in higher-level languages, smart pointers have essentially no performance overhead - so there is absolutely no reason not to use them. Using smart pointers, you don't need to worry about deallocating the memory manually with deleteanymore, and in particular, you don't have to worry about thedeletestatement not being reached due to an exception or some other unforeseen circumstance, which is usually how memory leaks occur. No matter what happens, the memory is guaranteed to be deallocated automatically. There are three types of smart pointers, defined in the header file <memory>:  unique_ptris a unique smart pointer, which means that the same raw pointer cannot be owned by more than oneunique_ptrobject. The reason the smart pointer needs to be unique is that if a raw pointer is managed by two different smart pointer objects, then they will both try todeletethe same raw pointer when they go out of scope, which will cause the program to crash. Aunique_ptris essentially just as efficient as a raw pointer, and is the most commonly used smart pointer.shared_ptris a shared smart pointer, which means that the same raw pointer can be owned by more than oneshared_ptrobject. By keeping track behind the scenes of how manyshared_ptrobjects refer to the same raw pointer, it can be guaranteed that the raw pointer will only be deleted once, after all of its owners have gone out of scope. However, this process, called reference counting, adds complexity and impedes performance, so it is not recommended to useshared_ptrunless you have to.weak_ptrmay be used when you want to access an existingshared_ptrwithout participating in reference counting. We will only discuss unique_ptrin this course. It is used as follows: unique_ptr<T> pointer(new T);
 Here, Tis the type of the object we are allocating, andpointeris the name of the smart pointer. We can also allocate memory for an (uninitialized) array: unique_ptr<T[]> pointer(new T[size]);
 where sizeis the size of the array. If we want to zero-initialize the array, we can do so by adding()to thenewoperator as usual: unique_ptr<T[]> pointer(new T[size]());
 Once we have a unique_ptrdefined, we can carry on as normal, knowing that when the smart pointer goes out of scope (which usually means the object that declared it is destroyed), the object or array we allocated will be automatically deallocated for us. We can use the following member functions:  get()returns the raw pointer owned by the smart pointer, ornullptrif no raw pointer is owned.The booloperator (i.e. the result of putting the smart pointer in anifstatement) returnstrueif the smart pointer owns an object, orfalseotherwise.reset(pointer)frees up the currently owned pointer (if a pointer is owned) and instructs the smart pointer to own the raw pointerpointerinstead.The assignment operator =is a move operator used to transfer ownership between twounique_ptrobjects. It must be used in conjunction with the functionmove(). The syntax isp1 = move(p2), which will result inp1taking ownership of the raw pointer previously owned byp2, andp2not owning any raw pointer anymore. In addition:  If the smart pointer points to a single object, i.e. the template is of the form unique_ptr<T>, the raw pointer can be dereferenced using*and members of the object can be accessed using->, as for any pointer to an object.If the smart pointer points to an array, i.e. the template is of the form unique_ptr<T[]>, the elements can be accessed using[], as for any array. We demonstrate the use of smart pointers in the following program: #include <iostream>
#include <memory>
#include <string>
using namespace std;
template <typename T>
void print_smart_pointer(const T &ptr, const string &name, const uint64_t &size)
{
    if (ptr)
    {
        cout << "The smart pointer " << name << " owns a raw pointer with the address " << ptr.get() << ".\n";
        cout << "The elements of the array at that address are: ";
        for (uint64_t i = 0; i < size; i++)
            cout << ptr[i] << ' ';
        cout << '\n';
    }
    else
        cout << "The smart pointer " << name << " does not own a raw pointer.\n";
}
template <typename T>
void print_smart_pointers(const T &ptr1, const T &ptr2, const uint64_t &size)
{
    print_smart_pointer(ptr1, "ptr1", size);
    print_smart_pointer(ptr2, "ptr2", size);
}
template <typename T>
void set_elements(const T &ptr, const uint64_t &size)
{
    for (uint64_t i = 0; i < size; i++)
        ptr[i] = i * i;
}
int main()
{
    constexpr uint64_t size = 5;
    unique_ptr<int64_t[]> ptr1, ptr2;
    cout << "Initial state:\n";
    print_smart_pointers(ptr1, ptr2, size);
    ptr1.reset(new int64_t[size]);
    cout << "\nAfter ptr1.reset(new int64_t[size]):\n";
    set_elements(ptr1, size);
    print_smart_pointers(ptr1, ptr2, size);
    ptr2 = move(ptr1);
    cout << "\nAfter ptr2 = move(ptr1):\n";
    print_smart_pointers(ptr1, ptr2, size);
}
 Output: Initial state:
The smart pointer ptr1 does not own a raw pointer.
The smart pointer ptr2 does not own a raw pointer.
After ptr1.reset(new int64_t[size]):
The smart pointer ptr1 owns a raw pointer with the address 0x1a23dffa030.
The elements of the array at that address are: 0 1 4 9 16
The smart pointer ptr2 does not own a raw pointer.
After ptr2 = move(ptr1):
The smart pointer ptr1 does not own a raw pointer.
The smart pointer ptr2 owns a raw pointer with the address 0x1a23dffa030.
The elements of the array at that address are: 0 1 4 9 16
  Warning: Creating a raw pointer with newand only later assigning it to a smart pointer via a constructor or thereset()member function should be avoided whenever possible, since there is always the chance that some error will occur betweennewand assigning the raw pointer to the smart pointer, resulting in a memory leak. Always prefer to usenewdirectly inside the argument to the constructor or thereset()member function of a smart pointer, to guarantee that the smart pointer will immediately own the raw pointer. In other words, avoid doing something like this: double *raw_pointer = new double[1000];
do_some_things();
// BAD: An error could have occurred before we assigned the smart pointer!
unique_ptr<double[]> smart_pointer(raw_pointer);
 or unique_ptr<double[]> smart_pointer;
double *raw_pointer = new double[1000];
do_some_things();
// BAD: An error could have occurred before we assigned the smart pointer!
smart_pointer.reset(raw_pointer);
 Instead, do this: // GOOD: Use new directly inside the argument to the constructor, so the smart pointer is assigned immediately, with no intermediate steps.
unique_ptr<double[]> smart_pointer(new double[1000]);
// We can still use the raw pointer, but only AFTER assigning the smart pointer, using get():
double *raw_pointer = smart_pointer.get();
 or this: unique_ptr<double[]> smart_pointer;
// GOOD: Use new directly inside the argument to the reset member function, so the smart pointer is assigned immediately, with no intermediate steps.
smart_pointer.reset(new double[1000]);
// We can still use the raw pointer, but only AFTER assigning the smart pointer, using get():
double *raw_pointer = smart_pointer.get();
 | 
| We can compare the performance of vector,unique_ptr, and raw pointers using the following program: #include <chrono>
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class timer
{
public:
    void start()
    {
        start_time = chrono::steady_clock::now();
    }
    void end()
    {
        elapsed_time = chrono::steady_clock::now() - start_time;
    }
    double seconds() const
    {
        return elapsed_time.count();
    }
private:
    chrono::time_point<chrono::steady_clock> start_time = chrono::steady_clock::now();
    chrono::duration<double> elapsed_time = chrono::duration<double>::zero();
};
int main()
{
    const uint64_t size = 1'000'000'000;
    timer tmr;
    cout.precision(2);
    cout << fixed;
    {
        tmr.start();
        vector<int64_t> test(size);
        for (uint64_t i = 0; i < size; i++)
            test[i] = i;
        tmr.end();
    }
    cout << "vector, access via []:              " << tmr.seconds() << " seconds.\n";
    {
        tmr.start();
        vector<int64_t> test(size);
        int64_t *ptr_to_test = test.data();
        for (uint64_t i = 0; i < size; i++)
            ptr_to_test[i] = i;
        tmr.end();
    }
    cout << "vector, access via raw pointer:     " << tmr.seconds() << " seconds.\n";
    {
        tmr.start();
        unique_ptr<int64_t[]> test(new int64_t[size]);
        for (uint64_t i = 0; i < size; i++)
            test[i] = i;
        tmr.end();
    }
    cout << "unique_ptr, access via []:          " << tmr.seconds() << " seconds.\n";
    {
        tmr.start();
        unique_ptr<int64_t[]> test(new int64_t[size]);
        int64_t *ptr_to_test = test.get();
        for (uint64_t i = 0; i < size; i++)
            ptr_to_test[i] = i;
        tmr.end();
    }
    cout << "unique_ptr, access via raw pointer: " << tmr.seconds() << " seconds.\n";
    {
        tmr.start();
        int64_t *test = new int64_t[size];
        for (uint64_t i = 0; i < size; i++)
            test[i] = i;
        tmr.end();
        // Note: Here we must free the memory manually since we are not using a smart pointer!
        delete[] test;
    }
    cout << "Pure raw pointer:                   " << tmr.seconds() << " seconds.\n";
}
 The timerclass I used here is the same one used above when we discussed compiler optimizations. Each array is 8 GB in size (1 billion elements of 8 bytes each), to get more statistically significant measurements. If your system has less than 16 GB of RAM in total, you should reduce the size of the array accordingly before running this code. Since the program allocates 32 GB of memory in total, which would use up all the RAM I have on my computer, I placed each part inside a code block {}. When the code blocks for thevectororunique_ptrobjects end, they go out of scope, and the memory is deallocated automatically thanks to the smart pointer; you can easily see this if you keep an eye on the memory usage as I explained above. I placed the manually-allocated array in its own code block as well, just for consistency; it will of course not be automatically deallocated when that code block ends, which is why I added a manualdelete. Running this program without any compiler optimizations, I find: vector, access via []:              3.88 seconds.
vector, access via raw pointer:     3.76 seconds.
unique_ptr, access via []:          8.26 seconds.
unique_ptr, access via raw pointer: 2.35 seconds.
Pure raw pointer:                   2.20 seconds.
 We see that unique_ptris essentially just as fast as a raw pointer, but only if we access the raw pointer directly, rather than via the[]operator of the smart pointer. In the latter case, it is actually twice as slow asvector! This is probably due to overhead that accumulates every time[]is called. Therefore, it is a good idea to instead callget()once, and use the obtained raw pointer to assign values to the array, as we did here. One way to implement this optimization in a class that allocates an array is to store both a smart pointer and a raw pointer obtained via get()as class members. The smart pointer will be used to ensure the memory is deallocated properly, while the raw pointer will be used to access the actual array elements. This is the approach I will take in the updatedmatrixclass in the next section. We also see that accessing a vectorvia a raw pointer (obtained viadata) is slightly faster than accessing it via the[]operator. However, even then it is still almost twice as slow as accessing a manually-allocated array. This is mostly because, as I stressed before, avectorautomatically initializes all its elements to zero upon construction, which is a waste of time since we then re-initialize them to other values anyway. Interestingly, if I add ()to all thenewstatements, i.e.new int64_t[size](), in order to force them to initialize to zero as well, I find similar results. This may be because the compiler is optimizing the initialization away (even though no optimization flags are enabled). If I enable compiler optimizations, things change drastically. After employing the -O3argument intasks.json, which instructs GCC to utilize all available optimizations, the differences between the two access methods ([]vs. raw pointer) vanish, both forvectorand forunique_ptr: vector, access via []:              1.25 seconds.
vector, access via raw pointer:     1.23 seconds.
unique_ptr, access via []:          0.93 seconds.
unique_ptr, access via raw pointer: 0.93 seconds.
Pure raw pointer:                   0.90 seconds.
 The reason is that the compiler now recognizes that we are using []in the first and third cases, and as part of the optimization process, it automatically does exactly what I did manually in the second and fourth cases.unique_ptris now essentially just as fast as a pure raw pointer in all cases, althoughvectorstill lags behind considerably, as the double initializations are not optimized away even at maximum optimization. As I said before, my philosophy for performance optimizations is to never trust the compiler to do my job for me, and always write code that is optimized on its own, without relying on compiler optimizations. Therefore, my recommendation in cases where maximum performance is required is to always use unique_ptr, but access the array via a raw pointer. In conclusion, smart pointers provide the exact same performance as raw pointers, but they are 100% safe to use, so there is simply no reason to ever use newanddeletemanually in modern C++, except in special cases which you will probably never encounter in scientific programming. Note also that there are many good reasons to use vectorinstead of smart pointers, since avectorknows its own size, can easily be resized, provides iterators that can be used with STL algorithms, works with range-based for loops, and has many other convenient features. Pretty much the only downside of vectoris the reduced performance due to the double initializations, but in many cases you actually do want to initialize it to zeros, and in any case the penalty to performance is really only relevant when initializing or resizing the array - accessing it after it has been initialized is just as fast. Finally, let me comment that the performance-related behavior we encountered in this section seems to be specific to GCC. On both Windows and Linux, when compiling with g++and no compiler optimizations, I get similar results. However, when compiling with MSVC (the Microsoft Visual C++ compiler) with all optimizations disabled (/Od), I get very different results: vector, access via []:              3.07 seconds.
vector, access via raw pointer:     2.91 seconds.
unique_ptr, access via []:          2.52 seconds.
unique_ptr, access via raw pointer: 2.23 seconds.
Pure raw pointer:                   2.01 seconds.
 This could be because MSVC implements vectors and smart pointers differently, which in this particular case results in better performance out of the box. With maximum optimizations (/O2), I get similar results to what I got with-O3in GCC. The lesson is that you should always do your own performance tests to see what works best with the specific compiler and operating system you are using. You can read more about smart pointers in the C++ reference or Microsoft's C++ reference. | 
| Now that we have learned about smart pointers, we can write the "ultimate" version of our matrix class template), which will feature the fastest performance due to avoiding double initializations, while also being protected against memory leaks due to the use of smart pointers. First, we add the line #include <memory>at the top. Then, we add aunique_ptrmember namedsmartto the class, but we still keep the raw pointerelements, which will be the one we will actually use to access the elements, for maximum performance. The smart pointer will be declared at the very end of the class, afterelements: unique_ptr<T[]> smart = nullptr;
 In the constructors, we replace each of the 6 instances of elements = new T[rows * cols];
 with smart.reset(new T[rows * cols]);
elements = smart.get();
 Note that by first assigning the raw pointer to smartusingreset()and only then storing the raw pointer obtained withget()inelements, we are ensuring thatelementsnever exists - even for a fraction of a second - as a raw pointer without being managed bysmart. We also remove the destructor ~matrix(), since the smart pointer will take care of deallocating memory for us; if you don't remove the destructor, you will notice that if you try to create a matrix, the program crashes due to trying to deallocate memory twice, once automatically via the smart pointer and once manually viadelete! There are two other places where we executedelete[] elements: in the copy and move assignment operators, so we should remove those as well. In the move constructor, we must first move the smart pointer using smart = move(m.smart), which transfers the raw pointer from being owned bym.smartto being owned bysmart; this meansm.smartwill no longer own anything, so we don't need to reset it manually. Then we store the raw pointer itself usingelements = smart.get(); we could also just writeelements = m.elements, but that wouldn't work ifm.elementswas somehow corrupted, so usingsmart.get()is safer. Finally, we set the moved matrix to a degenerate state. Thus the new move constructor is: template <typename T>
matrix<T>::matrix(matrix<T> &&m)
    : rows(m.rows), cols(m.cols)
{
    smart = move(m.smart);
    elements = smart.get();
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
}
 Finally, we must do the same thing in the move assignment operator: template <typename T>
matrix<T> &matrix<T>::operator=(matrix<T> &&m)
{
    rows = m.rows;
    cols = m.cols;
    smart = move(m.smart);
    elements = smart.get();
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
    return *this;
}
 I also made some additional tweaks for this final version:  I declared all overloaded operators as friend functions, so they don't have to use get_rows()andget_cols()to access the number of rows and columns - they just have direct access to therowsandcolsmember functions. This both shortens and simplifies the code, and avoids a few extra function calls. I moved the declarations of the operators into the class declaration, and changed the template parameter fromTtoUsince the same template parameter cannot be used in nested templates.I declared the short functions get_rows(),get_cols(),operator(),at(),operator+=,operator-=, and the third version ofoperator*, as inline functions for increased performance. The compiler will most likely implement them asinlineanyway due to optimizations, but since I don't like to rely on the compiler doing my job for me, I declared them manually.I improved the operator<<overload to use the manipulatorsetwinstead of tabs for formatting. The overload figures out how many characters to use by sending the elements into a string stream and then finding the maximum width of the resulting strings. Note that this required including the header files<iomanip>and<sstream>. Furthermore, the overload now prints out degenerate matrices with zero rows and columns, which are the result of a move constructor or assignment, as()- instead of not printing anything at all, which was the behavior so far. The end result is the following matrix.hppfile: #include <initializer_list>
#include <iomanip>
#include <iostream>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <vector>
using namespace std;
// =========
// Interface
// =========
template <typename T>
class matrix
{
public:
    // === Constructors ===
    // Constructor to create n UNINITIALIZED matrix.
    // First argument: number of rows.
    // Second argument: number of columns.
    matrix(const uint64_t &, const uint64_t &);
    // Constructor to create a diagonal matrix from an array.
    // First argument: length of the diagonal (equal to the number of rows and columns).
    // Second argument: an array containing the elements on the diagonal. The elements will be copied into the matrix.
    matrix(const uint64_t &, const T *);
    // Constructor to create a diagonal matrix from an initializer_list.
    // Argument: an initializer_list containing the elements on the diagonal.
    // Number of rows and columns is inferred automatically.
    matrix(const initializer_list<T> &);
    // Constructor to create a matrix from an array.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: an array containing the elements in row-major order. The elements will be copied into the matrix.
    matrix(const uint64_t &, const uint64_t &, const T *);
    // Constructor to create a matrix from an initializer_list.
    // First argument: number of rows.
    // Second argument: number of columns.
    // Third argument: an initializer_list containing the elements in row-major order.
    matrix(const uint64_t &, const uint64_t &, const initializer_list<T> &);
    // Copy constructor.
    matrix(const matrix<T> &);
    // Move constructor.
    matrix(matrix<T> &&);
    // === Member functions ===
    // Overloaded copy assignment operator.
    matrix<T> &operator=(const matrix<T> &);
    // Overloaded move assignment operator.
    matrix<T> &operator=(matrix<T> &&m);
    // Member function to obtain (but not modify) the number of rows in the matrix.
    uint64_t get_rows() const;
    // Member function to obtain (but not modify) the number of columns in the matrix.
    uint64_t get_cols() const;
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // First version: allows modification of the element.
    T &operator()(const uint64_t &, const uint64_t &);
    // Overloaded operator () to access matrix elements WITHOUT range checking.
    // The indices start from 0: m(0, 1) would be the element at row 1, column 2.
    // Second version: does not allow modification of the element.
    const T &operator()(const uint64_t &, const uint64_t &) const;
    // Member function to access matrix elements WITH range checking (throws out_of_range).
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // First version: allows modification of the element.
    T &at(const uint64_t &, const uint64_t &);
    // Member function to access matrix elements WITH range checking (throws out_of_range).
    // The indices start from 0: m.at(0, 1) would be the element at row 1, column 2.
    // Second version: does not allow modification of the element.
    const T &at(const uint64_t &, const uint64_t &) const;
    // === Friend functions ===
    // Overloaded binary operator << to easily print out a matrix to a stream.
    template <typename U>
    friend ostream &operator<<(ostream &, const matrix<U> &);
    // Overloaded binary operator + to add two matrices.
    template <typename U>
    friend matrix<U> operator+(const matrix<U> &, const matrix<U> &);
    // Overloaded binary operator += to add two matrices and assign the result to the first one.
    template <typename U>
    friend matrix<U> operator+=(matrix<U> &, const matrix<U> &);
    // Overloaded unary operator - to take the negative of a matrix.
    template <typename U>
    friend matrix<U> operator-(const matrix<U> &);
    // Overloaded binary operator - to subtract two matrices.
    template <typename U>
    friend matrix<U> operator-(const matrix<U> &, const matrix<U> &);
    // Overloaded binary operator -= to subtract two matrices and assign the result to the first one.
    template <typename U>
    friend matrix<U> operator-=(matrix<U> &, const matrix<U> &);
    // Overloaded binary operator * to multiply two matrices.
    template <typename U>
    friend matrix<U> operator*(const matrix<U> &, const matrix<U> &);
    // Overloaded binary operator * to multiply a scalar on the left and a matrix on the right.
    template <typename U>
    friend matrix<U> operator*(const U &, const matrix<U> &);
    // Overloaded binary operator * to multiply a matrix on the left and a scalar on the right.
    template <typename U>
    friend matrix<U> operator*(const matrix<U> &, const U &);
    // === Exceptions ===
    // Exception to be thrown if the number of rows or columns given to the constructor is zero.
    class zero_size : public invalid_argument
    {
    public:
        zero_size() : invalid_argument("Matrix cannot have zero rows or columns!"){};
    };
    // Exception to be thrown if the vector of elements provided to the constructor is of the wrong size.
    class initializer_wrong_size : public invalid_argument
    {
    public:
        initializer_wrong_size() : invalid_argument("Initializer does not have the expected number of elements!"){};
    };
    // Exception to be thrown if two matrices of different sizes are added or subtracted.
    class incompatible_sizes_add : public invalid_argument
    {
    public:
        incompatible_sizes_add() : invalid_argument("Cannot add or subtract two matrices of different dimensions!"){};
    };
    // Exception to be thrown if two matrices of incompatible sizes are multiplied.
    class incompatible_sizes_multiply : public invalid_argument
    {
    public:
        incompatible_sizes_multiply() : invalid_argument("Two matrices can only be multiplied if the number of columns in the first matrix is equal to the number of rows in the second matrix!"){};
    };
    // Exception to be thrown if the requested matrix element is out of range.
    class matrix_out_of_range : public out_of_range
    {
    public:
        matrix_out_of_range(const uint64_t &row, const uint64_t &col, const uint64_t &rows, const uint64_t &cols) : out_of_range("Tried to access matrix element at row " + to_string(row) + ", column " + to_string(col) + ". Row must be in the range [0," + to_string(rows - 1) + "] and column must be in the range [0," + to_string(cols - 1) + "]."){};
    };
private:
    // The number of rows.
    uint64_t rows = 0;
    // The number of columns.
    uint64_t cols = 0;
    // An array storing the elements of the matrix in flattened (1-dimensional) form.
    T *elements = nullptr;
    // A smart pointer to manage the memory allocated for the matrix elements.
    unique_ptr<T[]> smart = nullptr;
};
// ==============
// Implementation
// ==============
// === Constructors ===
template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    smart.reset(new T[rows * cols]);
    elements = smart.get();
}
template <typename T>
matrix<T>::matrix(const uint64_t &_size, const T *_diagonal)
    : rows(_size), cols(_size)
{
    if (rows == 0)
        throw zero_size();
    smart.reset(new T[rows * cols]);
    elements = smart.get();
    for (uint64_t i = 0; i < rows; i++)
        for (uint64_t j = 0; j < cols; j++)
            elements[(cols * i) + j] = ((i == j) ? _diagonal[i] : 0);
}
template <typename T>
matrix<T>::matrix(const initializer_list<T> &_diagonal)
    : matrix(_diagonal.size(), _diagonal.begin()) {}
template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols, const T *_elements)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    smart.reset(new T[rows * cols]);
    elements = smart.get();
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = _elements[i];
}
template <typename T>
matrix<T>::matrix(const uint64_t &_rows, const uint64_t &_cols, const initializer_list<T> &_elements)
    : rows(_rows), cols(_cols)
{
    if (rows == 0 or cols == 0)
        throw zero_size();
    if (_elements.size() != rows * cols)
        throw initializer_wrong_size();
    smart.reset(new T[rows * cols]);
    elements = smart.get();
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = _elements.begin()[i];
}
template <typename T>
matrix<T>::matrix(const matrix<T> &m)
    : rows(m.rows), cols(m.cols)
{
    smart.reset(new T[rows * cols]);
    elements = smart.get();
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = m.elements[i];
}
template <typename T>
matrix<T>::matrix(matrix<T> &&m)
    : rows(m.rows), cols(m.cols)
{
    smart = move(m.smart);
    elements = smart.get();
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
}
// === Member functions ===
template <typename T>
matrix<T> &matrix<T>::operator=(const matrix<T> &m)
{
    rows = m.rows;
    cols = m.cols;
    smart.reset(new T[rows * cols]);
    elements = smart.get();
    for (uint64_t i = 0; i < rows * cols; i++)
        elements[i] = m.elements[i];
    return *this;
}
template <typename T>
matrix<T> &matrix<T>::operator=(matrix<T> &&m)
{
    rows = m.rows;
    cols = m.cols;
    smart = move(m.smart);
    elements = smart.get();
    m.rows = 0;
    m.cols = 0;
    m.elements = nullptr;
    return *this;
}
template <typename T>
inline uint64_t matrix<T>::get_rows() const
{
    return rows;
}
template <typename T>
inline uint64_t matrix<T>::get_cols() const
{
    return cols;
}
template <typename T>
inline T &matrix<T>::operator()(const uint64_t &row, const uint64_t &col)
{
    return elements[(cols * row) + col];
}
template <typename T>
inline const T &matrix<T>::operator()(const uint64_t &row, const uint64_t &col) const
{
    return elements[(cols * row) + col];
}
template <typename T>
inline T &matrix<T>::at(const uint64_t &row, const uint64_t &col)
{
    if (row >= rows or col >= cols)
        throw matrix_out_of_range(row, col, rows, cols);
    return elements[(cols * row) + col];
}
template <typename T>
inline const T &matrix<T>::at(const uint64_t &row, const uint64_t &col) const
{
    if (row >= rows or col >= cols)
        throw matrix_out_of_range(row, col, rows, cols);
    return elements[(cols * row) + col];
}
// === Friend functions ===
template <typename T>
ostream &operator<<(ostream &out, const matrix<T> &m)
{
    out << '\n';
    if (m.rows == 0 && m.cols == 0)
        out << "()\n";
    else
    {
        ostringstream ss;
        uint64_t max_width = 0;
        for (uint64_t i = 0; i < m.rows; i++)
            for (uint64_t j = 0; j < m.cols; j++)
            {
                ss << m(i, j);
                max_width = max(max_width, ss.str().size());
                ss.str("");
            }
        for (uint64_t i = 0; i < m.rows; i++)
        {
            out << "( ";
            for (uint64_t j = 0; j < m.cols; j++)
                out << setw((int)max_width) << m(i, j) << ' ';
            out << ")\n";
        }
    }
    return out;
}
template <typename T>
matrix<T> operator+(const matrix<T> &a, const matrix<T> &b)
{
    if ((a.rows != b.rows) or (a.cols != b.cols))
        throw typename matrix<T>::incompatible_sizes_add();
    matrix<T> c(a.rows, a.cols);
    for (uint64_t i = 0; i < a.rows; i++)
        for (uint64_t j = 0; j < a.cols; j++)
            c(i, j) = a(i, j) + b(i, j);
    return c;
}
template <typename T>
inline matrix<T> operator+=(matrix<T> &a, const matrix<T> &b)
{
    a = a + b;
    return a;
}
template <typename T>
matrix<T> operator-(const matrix<T> &m)
{
    matrix<T> c(m.rows, m.cols);
    for (uint64_t i = 0; i < m.rows; i++)
        for (uint64_t j = 0; j < m.cols; j++)
            c(i, j) = -m(i, j);
    return c;
}
template <typename T>
matrix<T> operator-(const matrix<T> &a, const matrix<T> &b)
{
    if ((a.rows != b.rows) or (a.cols != b.cols))
        throw typename matrix<T>::incompatible_sizes_add();
    matrix<T> c(a.rows, a.cols);
    for (uint64_t i = 0; i < a.rows; i++)
        for (uint64_t j = 0; j < a.cols; j++)
            c(i, j) = a(i, j) - b(i, j);
    return c;
}
template <typename T>
inline matrix<T> operator-=(matrix<T> &a, const matrix<T> &b)
{
    a = a - b;
    return a;
}
template <typename T>
matrix<T> operator*(const matrix<T> &a, const matrix<T> &b)
{
    if (a.cols != b.rows)
        throw typename matrix<T>::incompatible_sizes_multiply();
    matrix<T> c(a.rows, b.cols);
    for (uint64_t i = 0; i < a.rows; i++)
        for (uint64_t j = 0; j < b.cols; j++)
        {
            c(i, j) = 0;
            for (uint64_t k = 0; k < a.cols; k++)
                c(i, j) += a(i, k) * b(k, j);
        }
    return c;
}
template <typename T>
matrix<T> operator*(const T &s, const matrix<T> &m)
{
    matrix<T> c(m.rows, m.cols);
    for (uint64_t i = 0; i < m.rows; i++)
        for (uint64_t j = 0; j < m.cols; j++)
            c(i, j) = s * m(i, j);
    return c;
}
template <typename T>
inline matrix<T> operator*(const matrix<T> &m, const T &s)
{
    return s * m;
}
 We can check it using the following main.cpp #include <iostream>
#include "matrix.hpp"
using namespace std;
int main()
{
    matrix<double> m1{1, 2};
    cout << "m1 ="
         << m1 << '\n';
    // Test the copy constructor.
    matrix<double> m2(m1);
    cout << "After copy construction of m2 from m1:\n";
    cout << "m1 ="
         << m1 << '\n';
    cout << "m2 ="
         << m2 << '\n';
    cout << "After changing the top-right element of m2 to 3:\n";
    m2(0, 1) = 3;
    cout << "m1 ="
         << m1 << '\n';
    cout << "m2 ="
         << m2 << '\n';
    // Test the copy assignment operator.
    matrix<double> m3(2, 2);
    m3 = m2;
    cout << "After copy assignment of m3 from m2:\n";
    cout << "m2 ="
         << m2 << '\n';
    cout << "m3 ="
         << m3 << '\n';
    cout << "After changing the bottom-left element of m3 to 4:\n";
    m3(1, 0) = 4;
    cout << "m2 ="
         << m2 << '\n';
    cout << "m3 ="
         << m3 << '\n';
    // Test the move constructor.
    matrix<double> m4 = move(m3);
    cout << "After move construction of m4 from m3:\n";
    cout << "m3 ="
         << m3 << '\n';
    cout << "m4 ="
         << m4 << '\n';
    cout << "After changing the top-left element of m4 to 5:\n";
    m4(0, 0) = 5;
    cout << "m3 ="
         << m3 << '\n';
    cout << "m4 ="
         << m4 << '\n';
    // Test the move assignment operator.
    matrix<double> m5(2, 2);
    m5 = move(m4);
    cout << "After move assignment of m5 from m4:\n";
    cout << "m4 ="
         << m4 << '\n';
    cout << "m5 ="
         << m5 << '\n';
    cout << "After changing the bottom-right element of m5 to 6:\n";
    m5(1, 1) = 6;
    cout << "m4 ="
         << m4 << '\n';
    cout << "m5 ="
         << m5 << '\n';
}
 The output should be: m1 =
( 1 0 )
( 0 2 )
After copy construction of m2 from m1:
m1 =
( 1 0 )
( 0 2 )
m2 =
( 1 0 )
( 0 2 )
After changing the top-right element of m2 to 3:
m1 =
( 1 0 )
( 0 2 )
m2 =
( 1 3 )
( 0 2 )
After copy assignment of m3 from m2:
m2 =
( 1 3 )
( 0 2 )
m3 =
( 1 3 )
( 0 2 )
After changing the bottom-left element of m3 to 4:
m2 =
( 1 3 )
( 0 2 )
m3 =
( 1 3 )
( 4 2 )
After move construction of m4 from m3:
m3 =
()
m4 =
( 1 3 )
( 4 2 )
After changing the top-left element of m4 to 5:
m3 =
()
m4 =
( 5 3 )
( 4 2 )
After move assignment of m5 from m4:
m4 =
()
m5 =
( 5 3 )
( 4 2 )
After changing the bottom-right element of m5 to 6:
m4 =
()
m5 =
( 5 3 )
( 4 6 )
 | 
| After 36 hours of lectures, more than 750,000 (!) characters and 80,000 words in these lecture notes, and three comprehensive course projects, our course has finally come to an end. I hope that the knowledge and skills you obtained in this course will be useful to you for the rest of your scientific careers, and that these lecture notes will continue to serve you as a reference in your future scientific programming projects. | 
  | © 2025 Barak Shoshany |