Chapter 1
C++ Crash Course
Hello World
Importing
#include
replaced byimport
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 thestd
namespace. C standard library are not guaranteed to be importable withimport
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.
- Return type is
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
andscanf
, 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 referringcout
. That way everything else still needs to usestd::
. - Never put
using
in a header file or global scope. Use it in namespace or class scope. OK to useusing
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 thansigned char
orunsigned char
. Only usechar
to represent characters.- From C++17
std::byte
in<cstddef>
. Beforechar
orunsigned 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()
, butstd::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
orusing
. - 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 ofenum 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 istrue
. -
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) { ... }