Contents
- 1.0 - C++ Basics
1.0 - C++ Basics
1.1 - Statements
Types of statements:
- declaration statements
- jump statements
- expression statements
- compound statements
- selection statements (conditionals)
- iteration statements (loops)
- try blocks
NOTE
Computers have “control characters” that have a meaning but aren’t meant to displayed traditionally. e.g. “escape”, “tab”, “backspace”
1.2 - Comments
Use comments for
- describing any program, library, or function (intention)
- within the program/library/function describe how the code is going to accomplish its goal (method)
- explain certain statements, reasonings
// The player just drank a potion of blindness and can not see anything
sight = 0;TIP
1.3 - Objects and Variables
Variables: single piece of data. objects with names Literals: Values placed directly in source code Objects: regions of storage that can hold a value
TIP
avoid defining multiple variables in a single statement
int a; double b; // correct (but not recommended)
// correct and recommended (easier to read)
int a;
double b;1.4 - Variable Assignment and Initialization
1.4.1 - Assignment
= is the assignment operator
int width; // define an integer variable named width
width = 5; // assignment of value 5 into variable widthCopy-assignment: By default assignment copies the value of the RHS to the variable on the LHS.
1.4.2 - Initialization
Assignment takes two statements for a variable to have a value
- defining
- assigning
Combining the two gives you Initialization
#include <iostream>
int main()
{
int width { 5 }; // define variable width and initialize with initial value 5
std::cout << width; // prints 5
return 0;
}There are 5 common types of initialization
int a; // default-initialization (no initializer)
// Traditional initialization forms:
int b = 5; // copy-initialization (initial value after equals sign)
int c ( 6 ); // direct-initialization (initial value in parenthesis)
// Modern initialization forms (preferred):
int d { 7 }; // direct-list-initialization (initial value in braces)
int e {}; // value-initialization (empty braces)NOTE
1.4.2.1 - Default Initialization
When no initializer is provided, leaves the variable with indeterminate value (garbage)
1.4.2.2 - Copy Initialization
Inherited from C
int width = 5; Copy-initialization had fallen out of favor in modern C++ due to being less efficient than other forms of initialization for some complex types. However, C++17 remedied the bulk of these issues, and copy-initialization is now finding new advocates
NOTE
Copy-initialization is also used wherever values are implicitly copied,
- passing arguments to function by value
- returning from function by value
- catching exceptions by value
1.4.2.3 - Direct-Initialization
int width (5);Direct-initialization was initially introduced to allow for more efficient initialization of complex objects (those with class types, which we’ll cover in a future chapter). Just like copy-initialization, direct-initialization had fallen out of favor in modern C++, largely due to being superseded by direct-list-initialization. However, direct-list-initialization has a few quirks of its own, and so direct-initialization is once again finding use in certain cases.
TIP
Direct-initialization is also used when values are explicitly cast to another type (e.g. via
static_cast)
1.4.2.4 - List-Initialization
int width { 5 }; // direct-list-initialization of initial value 5 into variable width (preferred)
int height = { 6 }; // copy-list-initialization of initial value 6 into variable height (rarely used)List-initialization was introduced to provide a initialization syntax that works in almost all cases, behaves consistently, and has an unambiguous syntax that makes it easy to tell where we’re initializing an object.
IMPORTANT
List-Initialization disallows narrowing conversions What would normally be a warning becomes an error → see the below snippet
int main()
{
// An integer can only hold non-fractional values.
// Initializing an int with fractional value 4.5 requires the compiler to convert 4.5 to a value an int can hold.
// Such a conversion is a narrowing conversion, since the fractional part of the value will be lost.
int w1 { 4.5 }; // compile error: list-init does not allow narrowing conversion
int w2 = 4.5; // compiles: w2 copy-initialized to value 4
int w3 (4.5); // compiles: w3 direct-initialized to value 4
return 0;
}1.4.2.5 - Value-Initialization
When a variable is initialized using an empty set of braces, a special form of list-initialization called value-initialization takes place. In most cases, value-initialization will implicitly initialize the variable to zero (or whatever value is closest to zero for a given type). In cases where zeroing occurs, this is called zero-initialization.
int width {}; // value-initialization / zero-initialization to value 0NOTE
For class types, value-initialization (and default-initialization) may instead initialize the object to predefined default values, which may be non-zero
1.4.3 - [[maybe_unused]]
C++17 introduced the [[maybe_unused]] attribute, which allows us to tell the compiler that we’re okay with a variable being unused. The compiler will not generate unused variable warnings for such variables.
The following program should generate no warnings/errors:
#include <iostream>
int main()
{
[[maybe_unused]] double pi { 3.14159 }; // Don't complain if pi is unused
[[maybe_unused]] double gravity { 9.8 }; // Don't complain if gravity is unused
[[maybe_unused]] double phi { 1.61803 }; // Don't complain if phi is unused
std::cout << pi << '\n';
std::cout << phi << '\n';
// The compiler will not warn about gravity not being used
return 0;
}The [[maybe_unused]] attribute should only be applied selectively to variables that have a specific and legitimate reason for being unused (e.g. because you need a list of named values, but which specific values are actually used in a given program may vary). Otherwise, unused variables should be removed from the program.
There are more such attributes here
- [[
nodiscard]] – when specified with a function declaration, tells the compiler to emit a warning if function’s return value is ignored; when specified with a struct emits a warning wherever the struct is returned and ignored. - [[
fallthrough]] – suppresses compiler warning about a switch-case statement without a break; in other words, a case statement that falls through into another case. - [[
no_unique_address]] – tells the compiler to perform empty base optimization on marked data member. - [[
deprecated]] – emits a warning when marked function, struct, namespace, or variable is used. - [[
noreturn]] – tells the compiler that the marked function never returns; emits a warning if it does. - [[
maybe_unused]] – suppressed a warning when marked variable, function, or argument is not used. - [[
likely]] – tell the compiler this is the most likely path of execution; allows for optimizations.
1.5 - iostream: cout, cin, endl
1.5.1 - std::cout
is buffered i.e. lines your text onto the train station, and eventually the train will take it to the console
Key Insight
Writing data to a buffer is typically fast, whereas transferring a batch of data to an output device is comparatively slow. Buffering can significantly increase performance by batching multiple output requests together to minimize the number of times output has to be sent to the output device.
1.5.2 - std::endl
does two things
- moves cursor back to beginning (new line)
- flushes the buffer (train leaves the station)
buffer flushing is slow, so prefer to use
\ninstead
#include <iostream> // for std::cout
int main()
{
int x{ 5 };
std::cout << "x is equal to: " << x << '\n'; // single quoted (by itself) (conventional)
std::cout << "Yep." << "\n"; // double quoted (by itself) (unconventional but okay)
std::cout << "And that's all, folks!\n"; // between double quotes in existing text (conventional)
return 0;
}try to prefer 2nd or 3rd options
1.5.3 - std::cin
For advanced readers
The C++ I/O library does not provide a way to accept keyboard input without the user having to press enter. If this is something you desire, you’ll have to use a third party library. For console applications, we’d recommend pdcurses, FXTUI, cpp-terminal, or notcurses. Many graphical user interface libraries have their own functions to do this kind of thing.
is buffered
The basic extraction process
Here’s a simplified view of how operator >> works for input.
- If
std::cinis not in a good state (e.g. the prior extraction failed andstd::cinhas not yet been cleared), no extraction is attempted, and the extraction process aborts immediately. - Leading whitespace characters (spaces, tabs, and newlines at the front of the buffer) are discarded from the input buffer. This will discard an unextracted newline character remaining from a prior line of input.
- If the input buffer is now empty, operator
>>will wait for the user to enter more data. Any leading whitespace is discarded from the entered data. - operator
>>then extracts as many consecutive characters as it can, until it encounters either a newline character (representing the end of the line of input) or a character that is not valid for the variable being extracted to.
The result of the extraction process is as follows:
- If the extraction aborted in step 1, then no extraction attempt occurred. Nothing else happens.
- If any characters were extracted in step 4 above, extraction is a success. The extracted characters are converted into a value that is then copy-assigned to the variable.
- If no characters could be extracted in step 4 above, extraction has failed. The object being extracted to is copy-assigned the value
0(as of C++11), and any future extractions will immediately fail (untilstd::cinis cleared).
For example, given the following snippet:
int x{};
std::cin >> x;Here’s what happens in a three different input cases:
- If the user types
5aand enter,5a\nwill be added to the buffer.5will be extracted, converted to an integer, and assigned to variablex.a\nwill be left in the input buffer for the next extraction. - If the user types ‘b’ and enter,
b\nwould be added to the buffer. Becausebis not a valid integer, no characters can be extracted, so this is an extraction failure. Variablexwould be set to0, and future extractions will fail until the input stream is cleared. - If
std::cinis not in a good state due to a prior failed extraction, nothing happens here. The value of variablexis not altered.
Standard Streams
std::coutis essentially just the file descriptor ofstdoutin C++. In C it’s simplystdoutand the POSIX definition isSTDOUT_FILENO. Simplereinterpret_cast<std::ostream>(1)doesn’t work tho :(similarily for
std::cinandstdin
1.7 - Keywords and Naming Variables
[1] https://youtu.be/-J3wNP6u5YU?si=88RiPt65T8rncjeg
C++ reserves 92 keywords that you can’t use as variable names
**Variable Naming Rules**C++ Keywords
- alignas - alignof - and - and_eq - asm - auto - bitand - bitor - bool - break - case - catch - char - char8_t (since C++20) - char16_t - char32_t - class - compl - concept (since C++20) - const - consteval (since C++20) - constexpr - constinit (since C++20) - const_cast - continue - co_await (since C++20) - co_return (since C++20) - co_yield (since C++20) - 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 - requires (since C++20) - 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 - xor_eq
- name can not be a keyword
- only be alphanumeric +
- - can not begin with a number
- case sensitive
int value; // conventional
int Value; // unconventional (should start with lower case letter)
int VALUE; // unconventional (should start with lower case letter and be in all lower case)
int VaLuE; // unconventional (see your psychiatrist) ;)int my_variable_name; // conventional (separated by underscores/snake_case)
int my_function_name(); // conventional (separated by underscores/snake_case)
int myVariableName; // conventional (intercapped/camelCase)
int myFunctionName(); // conventional (intercapped/camelCase)
int my variable name; // invalid (whitespace not allowed)
int my function name(); // invalid (whitespace not allowed)
int MyVariableName; // unconventional (should start with lower case letter)
int MyFunctionName(); // unconventional (should start with lower case letter)1.9 - Introduction to literals and operators
NOTE
For the operators we call primarily for their side effects (e.g. operator= or operator<<), it’s not always obvious what return values they produce (if any). For example, what return value would you expect x = 5 to have?
Both operator= and operator<< (when used to output values to the console) return their left operand. Thus, x = 5 returns x, and std::cout << 5 returns std::cout. This is done so that these operators can be chained.
For example, x = y = 5 evaluates as x = (y = 5). First y = 5 assigns 5 to y. This operation then returns y, which can then be assigned to x.
std::cout << "Hello " << "world!" evaluates as (std::cout << "Hello ") << "world!". This first prints "Hello " to the console. This operation returns std::cout, which can then be used to print "world!" to the console as well.