Skip to main content

Chapter 1

C++ Crash Course

Hello World

Importing

  • #include replaced by import in C++20. New concept called modules. e.g. import <iostream> instead of #include <iostream>.

Preprocessor Directives

  • Directives starting with # uses the preprocessor.
  • Function declaration: signature for the function, inputs, output, types.
  • Function definition: actual code of the function.
  • With C++20 modules, no need to separate declaration from definition. Can do it if wanted to.
  • Old C standard library do not use namespace. Standard C library prefixed with c use the std namespace. C standard library are not guaranteed to be importable with import in C++20.
  • Common preprocessor directive:
    • #include: header files.
    • #define: macro.
    • #ifdef...#endif/#ifndef...#endif: prevents circular includes Example:
      #ifndef TEST_FILE_H
      #define TEST_FILE_H
      // ...
      #endif
    • #pragma: compiler specific directive. Can replace the above with #pragma once.

Main function

  • main(): start of the program.
    • Return type is int.
    • zero returned automatically if not explicit.
    • can take no params or two params: int argc, char* argv[]
    • argc: number of args passed to program.
    • argv: actual args starting at index 1. Index 0 can be the program name.

I/O Streams

  • std::cout: outputs to user console.
  • std::cerr: outputs to error console.
  • <<: outputs down the stream.
  • std::endl: end of line sequence. Output everything and move to next line. Alternatively use \n escape sequence, i.e. a new line char.
  • Use std::format with C++20, from <format>.
  • Common escape sequences to be used with quotes:
    • \n: new line, moves cursor to beginning of next line and advance.
    • \r: carriage return, moves cursor to beginning of next line but doesn't advance.
    • \t: tab.
    • \\: backslash.
    • \": quotation.
  • std::endl flushes the buffer, so it will have a performance impact. \n doesn't flush the buffer.
  • std::cin: accept input from user with the >> operator.
  • Use iostream and format instead of printf and scanf, since they provide type safety.

Namespaces

  • avoids naming conflict, defines a context for the names. Example:
    namespace my_lib {
    void foo() {
    std::cout << "Hi!" << std::endl;
    }
    }
  • Call using scope resolution operator::. Example: my_lib::foo();.
  • Code in the same namespace can call each other without usiing scope resolution. Makes code readable.
  • using directive: signals the compiler that subsequent code is from the specified namespace.
  • Don't overuse using, will create name conflicts.
  • Can use using for a particular item. Example: using std::cout and then just referring cout. That way everything else still needs to use std::.
  • Never put using in a header file or global scope. Use it in namespace or class scope. OK to use using in a module interface file as long as not exporting.

Nested namespace

  • namespace inside another one. Example:
    // before c++17
    namespace foo {
    namespace bar {

    }
    }
    // after c++17
    namespace foo::bar {

    }

Namespace alias

  • give new/shorter name to a namespace. Example: `namespace baz = foo::bar;

Literals

  • Literals: used to write numbers or strings.
    • decimal, 123
    • octal, 0173
    • hex, 0x7B
    • binary, 0b1111011
    • float, 3.14f
    • double, 3.14
    • hex float, 0x3.ABCp-10
    • single char, 'a'
    • zero terminated char array
  • Can define custom literal.
  • Digit separator can be used in numeric literals, i.e. single quote char. Example: 23'456'789.

Variables

  • Uninitialized variables: potential source of bugs.
  • Uniform initialization syntax: Example:
    int my_int = 8;
    // vs from C++11
    int my_int {8};
  • Use uniform initialization.
  • Variables are strongly typed.
  • char is different than signed char or unsigned char. Only use char to represent characters.
  • From C++17 std::byte in <cstddef>. Before char or unsigned char used for byte.
  • No string type in C++. STL has std::string.

Numerical Limits

  • Use std::numeric_limits from <limits> instead of C #defines.
  • std::numeric_limits<int>::min() == std::numeric_limits<int>::lowest(), but std::numeric_limits<double>::min() != std::numeric_limits<double>::lowest(),
  • Minimum for float is the smallest possible value. Lowest for float is the most negative value representable.

Zero Initialization

  • Empty curly brackets {}
  • Initialzes primitive types to 0, pointer to nullptr
  • Calls default constructor for objects.

Casting

  • Use static_cast<>.
  • Variables can be auto casted, e.g. short to long.
  • Can lose data with casting.
  • OK to cast if types are compatible.

Floating-Point Numbers

  • FP math with order of magnitude differences can error.
  • Difference between two close FP can lose precision.
  • Many decimals can't be represented.
  • Special floats:
    • +/- infinity: e.g. 1/0.
    • NaN: not a number, e.g 0/0.
  • Use std::isnan()

Operators

  • Binary operator: operates on two expressions.
  • Unary operator: operates on single expression.
  • Ternary operator: operates on three expressions.
  • What is the output of this program?
    int someInteger { 256 };
    short someShort;
    long someLong;
    float someFloat;
    double someDouble;
    someInteger++;
    someInteger *= 2;
    someShort = static_cast<short>(someInteger);
    someLong = someShort * 10000;
    someFloat = someLong + 0.785f;
    someDouble = static_cast<double>(someFloat) / 100000;
    cout << someDouble << endl;
  • Output:
    • increment by 1 -> 257.
    • times 2 -> 514.
    • static cast to short -> short is 2 bytes, so can hold 514.
    • times 10,000 -> 5,140,000 -> implicit cast to long -> long is 4 bytes -> in range, so can hold.
    • float in range, long gets implicit casted to float -> big float added with small float -> loss of precision -> 5,140,001.0
    • Cast to double and divide -> 51.40001 (with cout.precision(7);)
  • Order of execution: /,*,% first from left to right, then +,- and the bitwise.
  • Make order explicit with paranthesis.
  • Evaluation order decided by precedence.
  • Ops with higher precedance evaluated before lower ones.

Enumerated Types

  • Defines a sequence, can declare variables with that sequence.
  • Strongly typed enumeration types: enum class MyEnum {Foo, Bar, Baz};
  • Tightly defines the range of values for a variable.
  • Enums are just integers.
  • If integers are not specified, compiler assigns automatically.
    • Starts with 0.
    • Can define some and leave some out.
    • Undefined types are assigned a value of the previous enum member incremented by 1.
  • Cannot automatically convert to integers.
  • By default, enum value is an integer. Can change it, e.g. enum class MyEnum: unsigned long {...}
  • Enum values not automatically exported to enclosing scope, useful for giving short names to enum values.
  • Have to fully qualify enum values or use a using enum or using.
  • C++20, can use using enum MyEnum. Minimize the scope when using this, so that name clashes are avoided.

Old-style Enumerated Types

  • enum instead of enum class.
  • Use strongly typed enum instead of old style.
  • Values exported to enclosing scope, so can use in the parent scope without fully qualifying, can result in name clashes. Need to make enum values unique to avoid this.
  • Not strongly typed, so not type safe.
  • Interpreted as ints, so can compare different enums or pass wrong enum types.

Structs

  • Encapsulate one or more types
  • Module interface file (.cppm)
    • First line is module declaration, states that file is defining a module.
    • explicitly export.
    • Example:
      export module employee;
      export struct Employee {
      ...
      };
    • Angle brackets must not be used when importing modules.

Conditional Statements

Two types on conditional statements in C++.

if/else Statements

  • Expression inside parenthesis must be a boolean.
  • Value of 0 evaluates to false, non-zero is true.
  • if (<condition 1>) {

    } else if (<condition 2>) {

    } else {

    }
Initializers for if Statements
  • Can include initializer inside an if statement.
  • Syntax:
    if (<initializer>; <conditional>) {
    ...
    } else if (...) {
    ...
    } else {
    ...
    }
  • Variable in the initializer available to all the other parts of the if statement (also other places in a cascaded if statements). Initializer variables not available anywhere else.
  • Example:
    if (Employee employee { getEmployee() }; employee.salary > 1000) { ... }

switch Statements

The Conditional Operator

Logical Evaluation Operators

Three-Way Comparisons

Functions

Function Return Type Deduction

Current Function’s Name

Function Overloading

Attributes

[[nodiscard]]

[[maybe_unused]]

[[noreturn]]

[[deprecated]]

[[likely]]

[[unlikely]]

C-Style Arrays

std::array

std::vector

std::pair

std::optional

Structured Bindings

Loops

The while Loop

The do/while Loop

The for Loop

The Range-Based for Loop

Initializer Lists

Strings in C++

C++ as an Object-Oriented Language

Defining Classes

Using Classes

Scope Resolution

Uniform Initialization

Designated Initializers

Pointers and Dynamic Memory

The Stack and the Free Store

Working with Pointers

Dynamically Allocated Arrays

Null Pointer Constant

The Use of const

const as a Qualifier for a Type

const Methods

The constexpr Keyword

The consteval Keyword

References

Reference Variables

Reference Data Members

Reference Parameters

Reference Return Values

Deciding Between References and Pointers

const_cast()

Exceptions

Type Aliases

typedefs

Type Inference

The auto Keyword

The decltype Keyword

The Standard Library

Your First Bigger C++ Program

An Employee Records System

The Employee Class

Employee.cppm

Employee.cpp

EmployeeTest.cpp

The Database Class

Database.cppm

Database.cpp

DatabaseTest.cpp

The User Interface

Evaluating the Program

Summary

Exercises