philosophy
The underlying design philosophy of C and C++ can be summed up as “trust the programmer” – which is both wonderful and dangerous. C++ is designed to allow the programmer a high degree of freedom to do what they want. However, this also means the language often won’t stop you from doing things that don’t make sense, because it will assume you’re doing so for some reason it doesn’t understand. There are quite a few pitfalls that new programmers are likely to fall into if caught unaware. This is one of the primary reasons why knowing what you shouldn’t do in C/C++ is almost as important as knowing what you should do.
Typically, good solutions have the following characteristics:
- They are straightforward (not overly complicated or confusing).
- They are well documented (especially around any assumptions being made or limitations).
- They are built modularly, so parts can be reused or changed later without impacting other parts of the program.
- They can recover gracefully or give useful error messages when something unexpected happens.
terminology
- CTAD (class template argument deduction)
compiler
Build compiles all modified code files in the project or workspace/solution, and then links the object files into an executable. If no code files have been modified since the last build, this option does nothing.
- compile
- linking
g++(GNU Compiler Collection for C++)
g++
是 GNU 编译器集合(GNU Compiler Collection, GCC)的一部分,专门用于编译 C++ 程序。GCC 是由 GNU 项目开发的开源编译器,支持多种编程语言,包括 C、C++、Objective-C、Fortran、Ada 和 Go 等。1
2
3
4
5use
g++ -o my_program my_program.cpp
specify compiler standard
g++ --std=gnu++17 {filename}.cppclang++(Clang C++ Frontend)
clang++
是 Clang 项目的一部分,Clang 是一个由苹果公司主导开发的编译器前端,用于替代 GCC。它不仅支持 C++,还支持 C、Objective-C 和其他语言。1
2
3
4
5use
clang++ -o my_program my_program.cpp
specify compiler standard
clang++ --std=c++17 {filename}.cppClean removes all cached objects and executables so the next time the project is built, all files will be recompiled and a new executable produced.
Rebuild does a “clean”, followed by a “build”.
Compile recompiles a single code file (regardless of whether it has been cached previously). This option does not invoke the linker or produce an executable.
Run/start executes the executable from a prior build. Some IDEs (e.g. Visual Studio) will invoke a “build” before doing a “run” to ensure you are running the latest version of your code. Otherwise (e.g. Code::Blocks) will just execute the prior executable.
grammer
Basics
operator
- The
::
symbol is an operator called the scope resolution operator.
iostream
std::endl
output a newline; Using
std::endl
is often inefficient than'\n'
1 | std::cin and std::cout always go on the left-hand side of the operator. |
- Avoid implementation-defined and unspecified behavior whenever possible, as they may cause your program to malfunction on other implementations.
addition (+
), subtraction (-
), multiplication (*
), division (/
). assignment (=
)
function
When a function parameter exists but is not used in the body of the function, do not give it a name. You can optionally put a name inside a comment.
1
2
3
4
5
6
7void doSomething(int) // ok: unnamed parameter will not generate warning
{
}
void doSomething(int /*count*/)
{
}Names used for function parameters or variables declared in a function body are only visible within the function that declares them. This means local variables within a function can be named without regard for the names of variables in other functions. This helps keep functions independent.
Define your local variables as close to their first use as reasonable.
declaration, tells the compiler about the existence of an identifier and its associated type information.
1
2int add(int x, int y); // tells the compiler about a function named "add" that takes two int parameters and returns an int. No body!
int x; // tells the compiler about an integer variable named xdefinition is a declaration that actually implements (for functions and types) or instantiates (for variables) the identifier.
In C++, all definitions are declarations. Thereforeint x;
is both a definition and a declaration.the compiler compiles each file individually.
Use double quotes to include header files that you’ve written or are expected to be found in the current directory. Use angled brackets to include headers that come with your compiler, OS, or third-party libraries you’ve installed elsewhere on your system.
header guard
1
2
3
4
5
6
int add(int x, int y);
- If a parameter is given a default argument, all subsequent parameters (to the right) must also be given default arguments.
Data Types
- The smallest unit of memory is a binary digit (also called a bit), which can hold a value of 0 or 1.
- Each memory address holds 1 byte of data. A byte is a group of bits that are operated on as a unit. The modern standard is that a byte is comprised of 8 sequential bits.
Types | Category | Meaning | Example |
---|---|---|---|
float double long double | Floating Point | a number with a fractional part | 3.14159 |
bool | Integral (Boolean) | true or false | true |
char wchar_t char8_t (C++20) char16_t (C++11) char32_t (C++11) |
Integral (Character) | a single character of text | ‘c’ |
short int int long int long long int (C++11) | Integral (Integer) | positive and negative whole numbers, including 0 | 64 |
std::nullptr_t (C++11) | Null Pointer | a null pointer | nullptr |
void | Void | no type | n/a |
Category | Type | Minimum Size | Typical Size | Note |
---|---|---|---|---|
Boolean | bool | 1 byte | 1 byte | |
character | char | 1 byte | 1 byte | always exactly 1 byte |
wchar_t | 1 byte | 2 or 4 bytes | ||
char8_t | 1 byte | 1 byte | ||
char16_t | 2 bytes | 2 bytes | ||
char32_t | 4 bytes | 4 bytes | ||
integer | short | 2 bytes | 2 bytes | |
int | 2 bytes | 4 bytes | ||
long | 4 bytes | 4 or 8 bytes | ||
long long | 8 bytes | 8 bytes | ||
floating point | float | 4 bytes | 4 bytes | |
double | 8 bytes | 8 bytes | ||
long double | 8 bytes | 8, 12, or 16 bytes | ||
pointer | std::nullptr_t | 4 bytes | 4 or 8 bytes |
integer
Size / Type | Range |
---|---|
8-bit signed | -128 to 127 |
16-bit signed | -32,768 to 32,767 |
32-bit signed | -2,147,483,648 to 2,147,483,647 |
64-bit signed | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
Name | Type | Range | Notes |
---|---|---|---|
std::int8_t | 1 byte signed | -128 to 127 | **Treated like a signed char on many systems. ** |
std::uint8_t | 1 byte unsigned | 0 to 255 | **Treated like an unsigned char on many systems. ** |
std::int16_t | 2 byte signed | -32,768 to 32,767 | |
std::uint16_t | 2 byte unsigned | 0 to 65,535 | |
std::int32_t | 4 byte signed | -2,147,483,648 to 2,147,483,647 | |
std::uint32_t | 4 byte unsigned | 0 to 4,294,967,295 | |
std::int64_t | 8 byte signed | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | |
std::uint64_t | 8 byte unsigned |
floating point
Size | Range | Precision |
---|---|---|
4 bytes | ±1.18 x 10-38 to ±3.4 x 1038 and 0.0 | 6-9 significant digits, typically 7 |
8 bytes | ±2.23 x 10-308 to ±1.80 x 10308 and 0.0 | 15-18 significant digits, typically 16 |
80-bits (typically uses 12 or 16 bytes) | ±3.36 x 10-4932 to ±1.18 x 104932 and 0.0 | 18-21 significant digits |
16 bytes | ±3.36 x 10-4932 to ±1.18 x 104932 and 0.0 | 33-36 significant digits |
When outputting floating point numbers,
std::cout
has a default precision of 6 – that is, it assumes all floating point variables are only significant to 6 digits (the minimum precision of a float), and hence it will truncate anything after that.Inf, which represents infinity. Inf can be positive or negative. NaN, which stands for “Not a Number”.
By default, floating point values whose decimal part is 0 print without the decimal places (e.g.
5.0
prints as5
).
const and string
const vs constexpr
Purpose:
const
is used to specify that a variable or function parameter cannot be modified, whileconstexpr
is used to specify that an expression can be evaluated at compile-time.Scope: A
const
variable can have a scope that includes the current block, while aconstexpr
expression must be within the scope of a function or class definition.Evaluation time: A
const
variable is evaluated at runtime, while aconstexpr
expression is evaluated at compile-time.
Numeral systems
There are 4 main numeral systems available in C++. In order of popularity, these are: decimal (base 10), binary (base 2), hexadecimal (base 16), and octal (base 8).
To use an octal literal, prefix your literal with a
0 (zero)
, e.g.int x{ 012 }
To use a hexadecimal literal, prefix your literal with
0x
, e.g.int x{ 0xF }
By default, C++ outputs values in decimal. However, you can change the output format via use of the
std::dec
,std::oct
, andstd::hex
I/O manipulators:1
2
3
4
5
6
7int x { 12 };
std::cout << x << '\n'; // decimal (by default)
std::cout << std::hex << x << '\n'; // hexadecimal
std::cout << x << '\n'; // now hexadecimal
std::cout << std::oct << x << '\n'; // octal
std::cout << std::dec << x << '\n'; // return to decimal
std::cout << x << '\n'; // decimal
scope duration linkage
Type | Example | Scope | Duration | Linkage | Notes |
---|---|---|---|---|---|
Local variable | int x; | Block | Automatic | None | |
Static local variable | static int s_x; | Block | Static | None | |
Dynamic local variable | int* x { new int{} }; | Block | Dynamic | None | |
Function parameter | void foo(int x) | Block | Automatic | None | |
Internal non-const global variable | static int g_x; | Global | Static | Internal | Initialized or uninitialized |
External non-const global variable | int g_x; | Global | Static | External | Initialized or uninitialized |
Inline non-const global variable (C++17) | inline int g_x; | Global | Static | External | Initialized or uninitialized |
Internal constant global variable | constexpr int g_x { 1 }; | Global | Static | Internal | Must be initialized |
External constant global variable | extern const int g_x { 1 }; | Global | Static | External | Must be initialized |
Inline constant global variable (C++17) | inline constexpr int g_x { 1 }; | Global | Static | External | Must be initialized |
Variables declared inside a namespace are also global variables.
Prefer defining global variables inside a namespace rather than in the global namespace.
Scope determines where a variable is accessible. Duration determines when a variable is created and destroyed.
Linkage determines whether the variable can be exported to another file or not.
Global variables can have either internal or external linkage, via the static and extern keywords respectively.
Initialize your static local variables. Static local variables are only initialized the first time the code is executed, not on subsequent calls.
control flow
Category | Meaning | Implemented in C++ by |
---|---|---|
Conditional statements | Causes a sequence of code to execute only if some condition is met. | if, else, switch |
Jumps | Tells the CPU to start executing the statements at some other location. | goto, break, continue |
Function calls | Jump to some other location and back. | function calls, return |
Loops | Repeatedly execute some sequence of code zero or more times, until some condition is met. | while, do-while, for, ranged-for |
Halts | Terminate the program. | std::exit(), std::abort() |
Exceptions | A special kind of flow control structure designed for error handling. | try, throw, catch |
References and Pointers
Lvalue
- Lvalue expressions evaluate to an identifiable object.
- An lvalue reference acts as an alias for an existing lvalue (such as a variable).
- Lvalue references can only bind to modifiable lvalues.
- Lvalue references to const can bind to modifiable lvalues, non-modifiable lvalues, and rvalues. This makes them a much more flexible type of reference.
Rvalue
- Rvalue expressions evaluate to a value.
Reference
- Pass by reference allows us to pass arguments to a function without making copies of those arguments each time the function is called.
- Pass by reference can only accept modifiable lvalue arguments
- Class types can be expensive to copy (sometimes significantly so), so they are typically passed by const reference. Fundamental types and enumerated types are cheap to copy, so they are typically passed by value.
- When an object being referenced is destroyed before a reference to it, the reference is left referencing an object that no longer exists. Such a reference is called a dangling reference
Pointer
- A pointer is an object that holds a memory address (typically of another variable) as its value. This allows us to store the address of some other object to use later.
- Use
nullptr
when you need a null pointer literal for initialization, assignment, or passing a null pointer to a function. - When we pass an address to a function, that address is copied from the argument into the pointer parameter (which is fine, because copying an address is fast).
- pointers must hold the address of an object ( can’t have a pointer to a reference, since references aren’t objects )
- Objects returned by reference must live beyond the scope of the function returning the reference, or a dangling reference will result. Never return a (non-static) local variable or temporary by reference.
summary
- Prefer pass by reference to pass by address unless you have a specific reason to use pass by address.
Compound Types
Enums and Structs
Type | Meaning | Examples |
---|---|---|
Fundamental | A type built into the core C++ language | int, std::nullptr_t |
Compound | A type built from fundamental types | int&, double*, std::string, Fraction |
User-defined | A class type or enumerated type (Includes those defined in the standard library or implementation) (In casual use, typically used to mean program-defined types) | std::string, Fraction |
Program-defined | A class type or enumerated type (Excludes those defined in standard library or implementation) | Fraction |
- A class template is a template definition for instantiating class types (structs, classes, or unions). Class template argument deduction (CTAD) is a C++17 feature that allows the compiler to deduce the template type arguments from an initializer.
- The members of a struct are public by default
classes
Access level | Access specifier | Member access | Derived class access | Public access |
---|---|---|---|---|
Public | public: | yes | yes | yes |
Protected | protected: | yes | yes | no |
Private | private: | yes | no | no |
The members of a class are private by default. Private members can be accessed by other members of the class, but can not be accessed by the public.
A class with private members is no longer an aggregate, and therefore can no longer use aggregate initialization.
Classes should generally make member variables private (or protected), and member functions public.
Structs should generally avoid using access specifiers (all members will default to public).
Prefer using the member initializer list to initialize your members over assigning values in the body of the constructor.
If you want your class to be able to be evaluated at compile-time, make your member functions and constructor constexpr.
A non-const member function can modify members of non-const objects.
A constexpr member function can be called in either runtime contexts or compile-time contexts.
All non-static member functions have a
this
const pointer that holds the address of the implicit object.
vector
- Avoid array indexing with integral values whenever possible.
- For range-based for loops, prefer to define the element type as:
auto
when you want to modify copies of the elements.auto&
when you want to modify the original elements.const auto&
otherwise (when you just need to view the original elements).
- The length of a vector is how many elements are “in use”.
The capacity of a vector is how many elements have been allocated in memory. - Tracking capacity separately from length allows the
std::vector
to avoid some reallocations when length is changed. - To increase the number of elements in a
std::vector
:
Useresize()
when accessing a vector via indexing. This changes the length of the vector so your indices will be valid.
Usereserve()
when accessing a vector using stack operations. This adds capacity without changing the length of the vector.
array
- Define
std::array
as constexpr whenever possible. If yourstd::array
is not constexpr, consider using astd::vector
instead. - Use class template argument deduction (CTAD) to have the compiler deduce the type and length of a
std::array
from its initializers.
lambda
Lambdas are anonymous functions, A lambda expression (also called a lambda or closure) allows us to define an anonymous function inside another function. The nesting is important, as it allows us both to avoid namespace naming pollution, and to define the function as close to where it is used as possible (providing additional context).
1 | [ captureClause ] ( parameters ) -> returnType |
In actuality, lambdas aren’t functions (which is part of how they avoid the limitation of C++ not supporting nested functions). They’re a special kind of object called a functor. Functors are objects that contain an overloaded operator()
that make them callable like a function.
memory
The memory that a program uses is typically divided into a few different areas, called segments:
- The code segment (also called a text segment), where the compiled program sits in memory. The code segment is typically read-only.
- The bss segment (also called the uninitialized data segment), where zero-initialized global and static variables are stored.
- The data segment (also called the initialized data segment), where initialized global and static variables are stored.
- The heap, where dynamically allocated variables are allocated from.
- The call stack, where function parameters, local variables, and other function-related information are stored.
memory allocation
Static memory allocation happens for static and global variables. Memory for these types of variables is allocated once when your program is run and persists throughout the life of your program.
Automatic memory allocation happens for function parameters and local variables. Memory for these types of variables is allocated when the relevant block is entered, and freed when the block is exited, as many times as necessary.
accessing heap-allocated objects is generally slower than accessing stack-allocated objects. Because the compiler knows the address of stack-allocated objects, it can go directly to that address to get a value. Heap allocated objects are typically accessed via pointer. This requires two steps: one to get the address of the object (from the pointer), and another to get the value.
relationship
Property\Type | Composition | Aggregation | Association | Dependency |
---|---|---|---|---|
Relationship type | Whole/part | Whole/part | Otherwise unrelated | Otherwise unrelated |
Members can belong to multiple classes | No | Yes | Yes | Yes |
Members existence managed by class | Yes | No | No | No |
Directionality | Unidirectional | Unidirectional | Unidirectional or bidirectional | Unidirectional |
Relationship verb | Part-of | Has-a | Uses-a | Depends-on |
inheritance
- that C++ always constructs the “first” or “most base” class first. It then walks through the inheritance tree in order and constructs each successive derived class.
tool
vscode
config file
c_cpp_properties.json
1
2
3config include path
Open the Command Palette (Ctrl+Shift+P) and type C/C++: Edit Configurations (UI).
This will open the c_cpp_properties.json file. If it doesn't exist, it will be created.setting.json
tasks.json
error detection and handing
- Favor
static_assert
overassert()
whenever possible.