learncpp Ch 1-6 & bit manipulation Flashcards
basics, functions, files, debugging, data types, operators, bit manipulation, scope, duration, and linkage
Statement
type of instruction that causes the program to perform some action. Statements are often terminated by a semicolon.
function
collection of statements that execute sequentially. Every C++ program must include a special function named main. When you run your program, execution starts at the top of the main function.
syntax
he rules that govern how elements of the C++ language are constructed
syntax error
occurs when you violate the grammatical rules of the language.
comments
allow the programmer to leave notes in the code. C++ supports two types of comments. Line comments start with a // and run to the end of the line. Block comments start with a /* and go to the paired */ symbol. Don’t nest block comments.
You can use comments to temporarily disable lines or sections of code. This is called commenting out your code.
data
any information that can be moved, processed, or stored by a computer.
value
A single piece of data, stored somewhere in memory
variable
named piece of memory that we can use to store values.
identifier
variable’s name
definition statement
In order to create a variable, we use a statement
instantiated
When the program is run, each defined variable is ___________, which means it is assigned a memory address.
data type
tells the compiler how to interpret a piece of data into a meaningful value.
integer
number that can be written without a fractional component, such as 4, 27, 0, -2, or -12.
copy assignment
(via operator=) can be used to assign an already created variable a value.
initialization
can be used to give a variable a value at the point of creation. C++ supports 3 types of initialization:
Copy initialization.
Direct initialization (also called parenthesis initialization).
List initialization (also called uniform initialization or brace initialization).
You should prefer brace initialization over the other initialization forms, and prefer initialization over assignment.
Although you can define multiple variables in a single statement, it’s better to define and initialize each variable on its own line, in a separate statement.
std::cout «
allow us to output an expression to the console as text.
std::endl
outputs a newline character, forcing the console cursor to move to the next line.
std::cin»_space;
allow us to get a value from the keyboard.
uninitializaed variable
A variable that has not been given a value
Trying to get the value of an uninitialized variable will result in _____ ______, which can manifest in any number of ways.
undefined behavior
keywords
These have special meaning within the language and may not be used as variable names.
literal constant
fixed value inserted directly into the source code. Examples are 5 and “Hello world!”.
operation
mathematical process involving zero or more input values, called operands.
The specific operation to be performed is denoted by the provided ______. The result of an operation produces an output value.
operator
unary
operators take one operand.
binary
operators take two operands, often called left and right.
Ternary
operators take three operands
Nullary
operators take zero operands.
expression
combination of literals, variables, operators, and function calls that are evaluated to produce a single output value.
An expression is a combination of literals, variables, operators, and function calls that are evaluated to produce a single output value. The calculation of this output value is called
evaluation
An expression is a combination of literals, variables, operators, and function calls that are evaluated to produce a single output value. The calculation of this output value is called evaluation. The value produced is the ______ of the expression.
result
expression statement
an expression that has been turned into a statement by placing a semicolon at the end of the expression.
When writing programs, add a few lines or a function, compile, resolve any errors, and make sure it works. Don’t wait until you’ve written an entire program before compiling it for the first time!
Focus on getting your code working. Once you are sure you are going to keep some bit of code, then you can spend time removing (or commenting out) temporary/debugging code, adding comments, handling error cases, formatting your code, ensuring best practices are followed, removing redundant logic, etc…
First-draft programs are often messy and imperfect. Most code requires cleanup and refinement to get to great!
What is the difference between initialization and assignment?
Initialization provides a variable with an initial value (at the point of creation). Assignment gives a variable a new value after the variable has already been defined.
When does undefined behavior occur? What are the consequences of undefined behavior?
Undefined behavior occurs when the programmer does something that is ill-specified by the C++ language. The consequences could be almost anything, from crashing to producing the wrong answer to working correctly anyway.
Write a program that asks the user to enter a number, and then enter a second number. The program should tell the user what the result of adding and subtracting the two numbers is.
The output of the program should match the following (assuming inputs of 6 and 4):
Enter an integer: 6
Enter another integer: 4
6 + 4 is 10.
6 - 4 is 2.
include <iostream></iostream>
int main()
{
std::cout «_space;“Enter an integer: “;
int x{};
std::cin»_space; x;
std::cout << "Enter another integer: "; int y{}; std::cin >> y; std::cout << x << " + " << y << " is " << x + y << ".\n"; std::cout << x << " - " << y << " is " << x - y << ".\n"; return 0; }
function
a reusable sequence of statements designed to do a particular job
user-defined functions
Functions you write yourself
function call
an expression that tells the CPU to execute a function.
caller
The function initiating the function call
calle or called function
function being called
Do not forget to include parenthesis when making a function call.
The curly braces and statements in a function definition are called the
function body
value-returning function
A function that returns a value
return type
indicates the type of value that the function will return.
return statement
determines the specific return value that is returned to the caller.
A return value is copied from the function back to the caller – this process is called return by value. Failure to return a value from a non-void function will result in
undefined behavior
status code
The return value from function main. It tells the operating system (and any other programs that called yours) whether your program executed successfully or not. By consensus a return value of 0 means success, and a non-zero return value means failure.
DRY programming
“don’t repeat yourself”. Make use of variables and functions to remove redundant code.
Functions with a return type of void do not return a value to the _____
caller
void function or non-value returning function
A function that does not return a value.
Void functions can’t be called where a value is required.
early return
A return statement that is not the last statement in a function. Such a statement causes the function to return to the caller immediately.
function parameter
variable used in a function where the value is provided by the caller of the function
argument
the specific value passed from the caller to the function.
pass by value
When an argument is copied into the parameter
local variables
Function parameters and variables defined inside the function body
lifetime
The time in which a variable exists
runtime
Variables are created and destroyed at, _______. which is when the program is running.
A variable’s scope determines where it can be seen and used. When a variable can be seen an used, we say it is in scope.
When it can not be seen, it can not be used, and we say it is out of scope.
Scope is a ____-____ property, meaning it is enforced at compile time.
compile-time
forward declaration
allows us to tell the compiler about the existence of an identifier before actually defining the identifier.
To write a forward declaration for a function, we use a function prototype
which includes the function’s return type, name, and parameters, but no function body, followed by a semicolon.
definition
actually implements (for functions and types) or instantiates (for variables) an identifier.
declaration
is a statement that tells the compiler about the existence of the identifier. In C++, all definitions serve as declarations.
pure declarations
declarations that are not also definitions (such as function prototypes).
Most non-trivial programs contain ____ files.
multiple
When two identifiers are introduced into the same program in a way that the compiler or linker can’t tell them apart, the compiler or linker will error due to a _____ _____
naming collision
namespace
guarantees that all identifiers within the namespace are unique. The std namespace is one such namespace.
proprocessor
process that runs on the code before it is compiled
directives
special instructions to the preprocessor. Directives start with a # symbol and end with a newline.
macro
a rule that defines how input text is converted to a replacement output text.
header files
files designed to propagate declarations to code files. When using the #include directive, the #include directive is replaced by the contents of the included file. When including headers, use angled brackets when including system headers (e.g. those in the C++ standard library), and use double quotes when including user-defined headers (the ones you write). When including system headers, include the versions with no .h extension if they exist.
header guards
prevent the contents of a header from being included more than once into a given code file. They do not prevent the contents of a header from being included into multiple different code files.
syntax error
error that occurs when you write a statement that is not valid according to the grammar of the C++ language. The compiler will catch these.
semantic error
occurs when a statement is syntactically valid, but does not do what the programmer intended.
debugging
The process of finding and removing errors from a program
We can use a five step process to approach debugging:
Find the root cause.
Understand the problem.
Determine a fix.
Repair the issue.
Retest.
Finding an error is usually the hardest part of debugging.
static analysis tools
tools that analyze your code and look for semantic issues that may indicate problems with your code.
Being able to reliably reproduce an issue is the first and most important step in debugging.
There are a number of tactics we can use to help find issues:
Commenting out code.
Using output statements to validate your code flow.
Printing values.
When using print statements, use std::cerr instead of std::cout. But even better, avoid debugging via print statements.
log file
file that records events that occur in a program. The process of writing information to a log file is called logging.
The process of restructuring your code without changing how it behaves is called ________. This is typically done to make your program more organized, modular, or performant.
refactoring
unit testing
software testing method by which small units of source code are tested to determine whether they are correct.
defensive programming
a technique whereby the programmer tries to anticipate all of the ways the software could be misused. These misuses can often be detected and mitigated.
program state
All of the information tracked in a program (variable values, which functions have been called, the current point of execution)
debugger
tool that allows the programmer to control how a program executes and examine the program state while the program is running
integrated debugger
debugger that integrates into the code editor.
stepping
name for a set of related debugging features that allow you to step through our code statement by statement.
step into
executes the next statement in the normal execution path of the program, and then pauses execution. If the statement contains a function call, step into causes the program to jump to the top of the function being called.
step over
executes the next statement in the normal execution path of the program, and then pauses execution. If the statement contains a function call, step over executes the function and returns control to you after the function has been executed.
step out
executes all remaining code in the function currently being executed and then returns control to you when the function has returned.
run to cursor
executes the program until execution reaches the statement selected by your mouse cursor.
continue
runs the program, until the program terminates or a breakpoint is hit.
start
is the same as continue, just from the beginning of the program.
breakpoint
is a special marker that tells the debugger to stop execution of the program when the breakpoint is reached.
watching a variable
allows you to inspect the value of a variable while the program is executing in debug mode. The watch window allows you to examine the value of variables or expressions.
call stack
list of all the active functions that have been executed to get to the current point of execution. The call stack window is a debugger window that shows the call stack.
binary digit or bit
The smallest unit of memory
byte
The smallest unit amount of memory that can be addressed directly
The modern standard is that a byte equals
8 bits
data type
tells the compiler how to interpret the contents of memory in some meaningful way.
C++ comes with support for many fundamental data types
floating point numbers, integers, boolean, chars, null pointers, and void.
void
used to indicate no type. It is primarily used to indicate that a function does not return a value.
Different types take different amounts of memory, and the amount of memory used may vary by machine.
sizeof
operator can be used to return the size of a type in bytes.
signed integers
used for holding positive and negative whole numbers, including 0.
range
The set of values that a specific data type can hold. When using integers, keep an eye out for overflow and integer division problems.
unsigned integers
only hold positive numbers (and 0), and should generally be avoided unless you’re doing bit-level manipulation.
fixed-width integers
are integers with guaranteed sizes, but they may not exist on all architectures. The fast and least integers are the fastest and smallest integers that are at least some size. std::int8_t and std::uint8_t should generally be avoided, as they tend to behave like chars instead of integers.
size_t
an unsigned integral type that is used to represent the size or length of objects.
scientific notation
shorthand way of writing lengthy numbers. C++ supports scientific notation in conjunction with floating point numbers.
significant digits
The digits in the significand (the part before the e)
floating point
set of types designed to hold real numbers (including those with a fractional component).
precision
a number defines how many significant digits it can represent without information loss.
rounding error
can occur when too many significant digits are stored in a floating point number that can’t hold that much precision. Rounding errors happen all the time, even with simple numbers such as 0.1. Because of this, you shouldn’t compare floating point numbers directly.
boolean
type is used to store a true or false value.
if statement
allow us to execute one or more lines of code if some condition is true. The conditional expression of an if statement is interpreted as a boolean value.
Angled brackets are typically used in C++ to represent something that needs a parameterizable type.
This is used with static_cast to determine what data type the argument should be converted to (e.g. static_cast<int>(x) will convert x to an int).</int>
constant
a value that may not be changed. C++ supports two types of constants: const variables, and literals. A variable whose value can not be changed is called a constant variable.
const
keyword is used to make a variable constant.
symbolic constant
is a name given to a constant value. Constant variables are one type of symbolic constant, as are object-like macros with substitution text.
contant expression
expression that can be evaluated at compile-time.
compile-time constant
constant whose value is known at compile-time.
runtime constant
constant whose initialization value isn’t known until runtime.
constexpr
variable must be a compile-time constant.
literals
values inserted directly into the code. Literals have types, and literal suffixes can be used to change the type of a literal from the default type.
magic number
a literal (usually a number) that either has an unclear meaning or may need to be changed later. Don’t use magic numbers in your code. Instead, use symbolic constants.
In everyday life, we count using decimal numbers, which have 10 digits. Computers use binary, which only has 2 digits. C++ also supports octal (base 8) and hexadecimal (base 16). These are all examples of _____ _____, which are collections of symbols (digits) used to represent numbers.
numeral systems
string
collection of sequential characters that is used to represent text (such as names, words, and sentences). String literals are always placed between double quotes. String literals in C++ are C-style strings, which have a strange type that is hard to work with.
std::string
offers an easy and safe way to deal with text strings. std::string lives in the <string> header. std::string is expensive to initialize and copy.</string>
std::string_view
provides read-only access to an existing string (a C-style string literal, a std::string, or a char array) without making a copy.
dangling view
A std::string_view that is viewing a string that has been destroyed
When a std::string is modified, all views into that std::string are _______, meaning those views are now invalid. Using an invalidated view (other than to revalidate it) will produce undefined behavior.
invalidated
Always use parentheses to disambiguate the precedence of operators if there is any question or opportunity for confusion.
The arithmetic operators all work like they do in normal mathematics. The remainder (%) operator returns the remainder from an integer division.
The increment and decrement operators can be used to easily increment or decrement numbers. Avoid the ______ versions of these operators whenever possible.
postfix
Beware of side effects, particularly when it comes to the order that function parameters are evaluated. Do not use a variable that has a side effect applied more than once in a given statement.
The comma operator can compress multiple statements into one. Writing the statements separately is usually better.
The conditional operator is a nice short version of an _______, but don’t use it as an alternative to an if-statement. Only use the conditional operator if you use its result.
if-statement
Relational operators can be used to compare floating point numbers. Beware using equality and inequality on floating point numbers.
_____ operators allow us to form compound conditional statements.
Logical
On modern computer architectures, the smallest addressable unit of memory is a byte. Since all objects need to have unique memory addresses, this means objects must be at least one byte in size. For most variable types, this is fine. However, for Boolean values, this is a bit wasteful (pun intended). Boolean types only have two states: true (1), or false (0). This set of states only requires one bit to store. However, if a variable must be at least a byte, and a byte is 8 bits, that means a Boolean is using 1 bit and leaving the other 7 unused.
In the majority of cases, this is fine – we’re usually not so hard-up for memory that we need to care about 7 wasted bits (we’re better off optimizing for understandability and maintainability). However, in some storage-intensive cases, it can be useful to “pack” 8 individual Boolean values into a single byte for storage efficiency purposes.
Doing these things requires that we can manipulate objects at the bit level. Fortunately, C++ gives us tools to do precisely this. Modifying individual bits within an object is called ______
bit manipulation
instead of viewing objects as holding a single value, we can instead view them as a collection of individual bits. When individual bits of an object are used as Boolean values, the bits are called __ _____
bit flags
In computing, a ____ is a value that signals when some condition exists in a program. With a bit flag, a value of true means the condition exists.
flag
To define a set of bit flags, we’ll typically use an unsigned integer of the appropriate size (8 bits, 16 bits, 32 bits, etc… depending on how many flags we have), or std::bitset.
include <bitset></bitset>
// for std::bitset
std::bitset<8> mybitset {};
// 8 bits in size means room for 8 flags
Best practice
Bit manipulation is one of the few times when you should unambiguously use ____ _____ (or std::bitset).
unsigned integers
Given a sequence of bits, we typically number the bits from right to left, starting with 0 (not 1). Each number denotes a ___ _____.
76543210 Bit position
00000101 Bit sequence
Given the bit sequence 0000 0101, the bits that are in position 0 and 2 have value 1, and the other bits have value 0.
bit position
std::bitset provides 4 key member functions that are useful for doing bit manipulation:
test() allows us to query whether a bit is a 0 or 1
set() allows us to turn a bit on (this will do nothing if the bit is already on)
reset() allows us to turn a bit off (this will do nothing if the bit is already off)
flip() allows us to flip a bit value from a 0 to a 1 or vice versa
Each of these functions takes the position of the bit we want to operate on as their only argument.
What if we want to get or set multiple bits at once
In order to do this, or if we want to use unsigned integer bit flags instead of std::bitset, we need to turn to more traditional methods. We’ll cover these in the next couple of lessons.
One potential surprise is that std::bitset is optimized for speed, not memory savings. The size of a std::bitset is typically the number of bytes needed to hold the bits, rounded up to the nearest sizeof(size_t), which is 4 bytes on 32-bit machines, and 8-bytes on 64-bit machines.
Thus, a std::bitset<8> will typically use either 4 or 8 bytes of memory, even though it technically only needs 1 byte to store 8 bits. Thus, std::bitset is most useful when we desire convenience, not memory savings.
compound statement or block
group of zero or more statements that is treated by the compiler as if it were a single statement. Blocks begin with a { symbol, end with a } symbol, with the statements to be executed placed in between. Blocks can be used anywhere a single statement is allowed. No semicolon is needed at the end of a block. Blocks are often used in conjunction with if statements to execute multiple statements.
user-defined namespaces
namespaces that are defined by you for your own declarations. Namespaces provided by C++ (such as the global namespace) or by libraries (such as namespace std) are not considered user-defined namespaces.
You can access a declaration in a namespace via the __________. The scope resolution operator tells the compiler that the identifier specified by the right-hand operand should be looked for in the scope of the left-hand operand. If no left-hand operand is provided, the global namespace is assumed.
scope resolution operator (::)
Local variables are variables defined within a function (including function parameters). Local variables have block scope, meaning they are in-scope from their point of definition to the end of the block they are defined within.
Local variables have automatic storage duration, meaning they are created at the point of definition and destroyed at the end of the block they are defined in.
A name declared in a nested block can _____ or _____ hide an identically named variable in an outer block. This should be avoided.
shadow
name hide
global variables
variables defined outside of a function.
Global variables have ____ _____, which means they are visible from the point of declaration until the end of the file in which they are declared.
file scope
Global variables have _____ ______, which means they are created when the program starts, and destroyed when it ends. Avoid dynamic initialization of static variables whenever possible.
static duration
identifiers linkage
determines whether other declarations of that name refer to the same object or not. Local variables have no linkage.
identifiers with internal linkage
can be seen and used within a single file, but it is not accessible from other files.
identifiers with external linkage
can be seen and used both from the file in which it is defined, and from other code files (via a forward declaration).
Avoid non-const global variables whenever possible. Const globals are generally seen as acceptable. Use inline variables for global constants if your compiler is C++17 capable.
Avoid non-const global variables whenever possible. Const globals are generally seen as acceptable. Use_____ _____ for global constants if your compiler is C++17 capable.
inline variables
Local variables can be given static duration via the static keyword.
inline variables
Local variables can be given static duration via the ____ keyword
static
qualified name
name that includes an associated scope (e.g. std::string).
unqualified name
name that does not include a scoping qualifier (e.g. string).
using statements (including using declarations and using directives)
can be used to avoid having to qualify identifiers with an explicit namespace
using declaration
allows us to use an unqualified name (with no scope) as an alias for a qualified name.
using directive
imports all of the identifiers from a namespace into the scope of the using directive. Both of these should generally be avoided.
inline functions
were originally designed as a way to request that the compiler replace your function call with inline expansion of the function code. You should not need to use the inline keyword for this purpose because the compiler will generally determine this for you. In modern C++, the inline keyword is used to exempt a function from the one-definition rule, allowing its definition to be imported into multiple code files. Inline functions are typically defined in header files so they can be #included into any code files that needs them.
constexpr function
is a function whose return value may be computed at compile-time. To make a function a constexpr function, we simply use the constexpr keyword in front of the return type. A constexpr function that is eligible for compile-time evaluation must be evaluated at compile-time if the return value is used in a context that requires a constexpr value. Otherwise, the compiler is free to evaluate the function at either compile-time or runtime.
C++20 introduces the keyword consteval, which is used to indicate that a function must evaluate at compile-time, otherwise a compile error will result. Such functions are called ____ _____
immediate functions
C++ also supports inline namespaces, which provide some primitive versioning capabilities for namespaces.
C++ supports unnamed namespaces, which implicitly treat all contents of the namespace as if it had internal linkage.
immediate functions
C++ supports unnamed namespaces, which implicitly treat all contents of the namespace as if it had internal linkage.
C++ also supports inline namespaces, which provide some primitive versioning capabilities for namespaces.
bit mask
predefined set of bits that is used to select which specific bits will be modified by subsequent operations.
constexpr std::uint8_t mask0{ 0b0000’0001 };
// represents bit 0
constexpr std::uint8_t mask1{ 0b0000’0010 };
// represents bit 1
constexpr std::uint8_t mask2{ 0b0000’0100 };
// represents bit 2
constexpr std::uint8_t mask0{ 0x01 };
// hex for 0000 0001
constexpr std::uint8_t mask1{ 0x02 };
// hex for 0000 0010
constexpr std::uint8_t mask2{ 0x04 };
// hex for 0000 0100
constexpr std::uint8_t mask3{ 0x08 };
// hex for 0000 1000
hexadecimal
To determine if a bit is on or off, we use bitwise AND in conjunction with the
bit mask
turn on multiple bits at the same time using Bitwise OR:
flags |= (mask4 | mask5);
// turn bits 4 and 5 on at the same time
turn off multiple bits at the same time:
flags &= ~(mask4 | mask5);
// turn bits 4 and 5 off at the same time
Naming our bit masks “mask1” or “mask2” tells us what bit is being manipulated, but doesn’t give us any indication of what that bit flag is actually being used for.
A best practice is to give your bit masks useful names as a way to document the meaning of your bit flags. Here’s an example from a game we might write:
[[maybe_unused]] constexpr std::uint8_t isHungry{ 1 «_space;0 };
// 0000 0001
[[maybe_unused]] constexpr std::uint8_t isSad{1 << 1 }; // 0000 0010
[[maybe_unused]] constexpr std::bitset<8> isMad{ 0b0000’0100 };
[[maybe_unused]] constexpr std::bitset<8> isHappy{0b0000'1000 };
std::cout «_space;“I am happy? “ «_space;(me & isHappy).any() «_space;‘\n’;
When are bit flags most useful?
in programs where there are tens of thousands or even millions of similar objects, using bit flags can reduce memory use substantially. It’s a useful optimization to have in your toolkit if you need it.
use the bitwise operators with unsigned operands or
std::bitset
bitwise shift left
shifts bits to the left. The left operand is the expression to shift the bits of, and the right operand is an integer number of bits to shift left by.
So when we say x «_space;1, we are saying “shift the bits in the variable x left by 1 place”. New bits shifted in from the right side receive the value 0.
0011 «_space;1 is 0110
0011 «_space;2 is 1100
0011 «_space;3 is 1000
Note that in the third case, we shifted a bit off the end of the number! Bits that are shifted off the end of the binary number are lost forever.
bitwise right shift
shifts bits to the right.
1100»_space; 1 is 0110
1100»_space; 2 is 0011
1100»_space; 3 is 0001
Note that in the third case we shifted a bit off the right end of the number, so it is lost.
bitwise NOT
easiest to understand of all the bitwise operators. It simply flips each bit from a 0 to a 1, or vice versa. Note that the result of a bitwise NOT is dependent on what size your data type is.
Flipping 4 bits:
~0100 is 1011
Flipping 8 bits:
~0000 0100 is 1111 1011
In both the 4-bit and 8-bit cases, we start with the same number (binary 0100 is the same as 0000 0100 in the same way that decimal 7 is the same as 07), but we end up with a different result.
bitwise OR
works much like its logical OR counterpart. However, instead of applying the OR to the operands to produce a single result, bitwise OR applies to each bit! For example, consider the expression 0b0101 | 0b0110.
logical AND
works similarly to the above. Logical AND evaluates to true if both the left and right operand evaluate to true. Bitwise AND evaluates to true (1) if both bits in the column are 1. Consider the expression 0b0101 & 0b0110.
Bitwise XOR
also known as exclusive or.
When evaluating two operands, XOR evaluates to true (1) if one and only one of its operands is true (1). If neither or both are true, it evaluates to 0. Consider the expression 0b0110 ^ 0b0011
Left shift assignment «= x «= y Shift x left by y bits
Right shift assignment »= x»_space;= y Shift x right by y bits
Bitwise OR assignment |= x |= y Assign x | y to x
Bitwise AND assignment &= x &= y Assign x & y to x
Bitwise XOR assignment ^= x ^= y Assign x ^ y to x
When evaluating bitwise OR, if any bit in a column is 1, the result for that column is _____.
When evaluating bitwise AND, if all bits in a column are 1, the result for that column is ___.
When evaluating bitwise XOR, if there are an odd number of 1 bits in a column, the result for that column is ____.
1