C++ Basic Concepts
Datais any information processed or stored by a computer.- A
Valueis a specific piece of data (e.g.42,'A',3.14). - An
Objectis a region of storage (memory) with a type and a value. - A
Variableis a named object used to store data. Initializationis the process of giving an initial value to an object or variable.Literals: fixed values like 42, 3.14, ‘A’, “Hello”, true, nullptr.Operators: symbols that act on values (+ - * / %, == != < >, && || !, = += -=, etc.).- An
Expressionis a combination of operators, constants and variables that evaluate to a value. - A
functionis a reusable block of code that performs a specific task.
1. Initialization
C++ provides several ways to initialize objects. Each form has different semantics and use cases.
1.1. Default Initialization
int a;
T object;
new T;
Performed when an object is created without any initializer.
- Built-in types: uninitialized
- Class types: default constructor is called
1.2. Value Initialization
int a{};
T();
new T();
T object{};
T{};
new T{};
/// @brief Also used in constructors
Class::Class() : member() {}
Class::Class() : member{} {}
Performed when an object is created with an empty initializer.
- Built-in types: zero-initialized
- Class types: default constructor is called
1.3 Direct Initialization
T object(arg);
T object(arg1, arg2);
T object{arg}; // since C++11
new T(args...);
static_cast<T>(other);
/// @brief Used in constructors and lambdas:
Class::Class() : member(args...) {}
[arg]() {};
Initializes an object using explicit constructor arguments.
1.4 Copy Initialization
T object = other;
f(other);
return other;
throw object;
catch (T object);
/// @brief Also used for arrays
T array[N] = { /* values */ };
Initializes an object from another object or expression.
1.5 List Initialization (since C++11)
/// @brief Direct List Initialization
T object{arg1, arg2};
new T{arg1, arg2};
Class::Class() : member{arg1, arg2} {}
/// @brief Copy List Initialization
T object = {arg1, arg2};
return {arg1, arg2};
function({arg1, arg2});
/// @brief Designated Initializers (since C++20)
T object{ .des1 = arg1, .des2 = arg2 };
T object = { .des1 = arg1, .des2 = arg2 };
Initializes objects using brace-enclosed initializer lists { }.
1.6 Aggregate Initialization
T object = {arg1, arg2};
T object{arg1, arg2}; // since C++11
/// @brief Designated initializers for aggregates (since C++20)
T object = { .des1 = arg1, .des2 = arg2 };
T object{ .des1 = arg1, .des2 = arg2 };
Initializes aggregate types (no user-defined constructors).
2. Functions and Files
2.1. Function
returnType functionName(); ///< Forward Declaration
/// @brief Function Definition
returnType functionName(){
}
- A
forward declarationallows us to tell the compiler about the existence of an identifier before actually defining the identifier.
2.2. Namespace
namespace nested_config{
namespace config{
int value = 1;
}
}
using namespace cfg = nested_config::config;
void f(){
std::cout << cfg::value;
}
namespaceis a way to group names (variables, functions, classes, …) together and avoid name conflicts. It guarantees that all identifiers within the namespace are uniqueusing namespace: this is a using-directive that allows us to access names in the std namespace with no namespace prefix- The
scope resolution operator (::)used to access namespace member - Can create
namespace aliases, which allow us to temporarily shorten a long sequence of namespaces into something shorter. - namespaces can be nested inside other namespaces.
2.3. Preprocessor
The preprocessor is a process that runs on the code before it is compiled.
#include <iostream> // insert file contents
#define NAME "Alex" // replace NAME -> "Alex"
#ifdef NAME_DEFINED // only compile if defined
std::cout << NAME;
#endif
2.3. Header Files
#include <iostream> // search system only
#include "my_header.h" // search local first, then system
/// @brief Add include directories with -I
// g++ -o main -I./source/includes -I/home/abc/moreHeaders main.cpp
Header files are files designed to propagate declarations to code files.
Header guardsprevent the contents of a header from being included more than once into a given code file.- For cross-platform library code,
#ifndefis safest. - For modern projects,
#pragma onceis simpler and safe.
3. Fundamental Data Types
Memory can only store bits. Data type help compiler and CPU take care of encoding the value into the sequence of bits.
- Fundamental Data Types
- Compound Data Types
3.1 Primitive Type
| 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 |
sizeofused to get the size of a type in bytes. (pointer has 4 or 8 bytes based on the arch)signed integerscan hold both positive and negative numbers (and 0).unsigned integerscan only hold non-negative whole numbers.fixed-width integersare the set of integer types that are guaranteed to be the same size on any architecture#include <cstdint>fast integers: guarantee at least # bits, but pick the type that the CPU can process fastest (even if it uses more memory).least integers: guarantee at least # bits, but pick the type that uses the least memory (even if it’s slower).
size_tvsstd::size_tis an unsigned integral type that is used to represent the size or length of objects. (<stddef.h>,) scientific notation e/Eused to present the times 10 to the power of the equation. e.g. (e.g. 5.9722 x 10²⁴ -> 5.9722e24)Infrepresents infinity. Inf is signed, and can be positive (+Inf) or negative (-Inf). (5/0)NaNstands for “Not a Number”. (mathematically invalid)
3.2. Chars
- A
charvariable are interpreted as anASCII character. 't': Text between single quotes is treated as a char literal, which represents a single character."text": Text between double quotes (e.g. “Hello, world!”) is treated as a C-style string literal, which can contain multiple characters.
4. Constant
- A
constantis a value that is not be changed during the program’s execution. There are two types of constants:- Named constants: are constant values that are associated with an identifier.
- Constant variables
- Macros with substitution text
- Enumerated constant
- Literal are constant values that are not associated with an identifier.Literals are values that are inserted directly into the code.
- Named constants: are constant values that are associated with an identifier.
4.1. Named constants
const double gravity = 9.8; ///< Const variable
#define MY_NAME "Phong" ///< Object-like macros with substitution text
enum Color {
RED, // Assigned 0
GREEN, // Assigned 1
BLUE = 5, // Manually assigned 5
YELLOW // Assigned 6 (5 + 1)
};///< Enumerated constant
4.2. Literals
return 5; ///< 5 is an integer literal -> type: int
bool myNameIsAlex { true }; ///< true is a boolean literal -> type: bool
double d { 3.4 }; ///< 3.4 is a double literal -> type: double
cout << "Hello, world!"; ///< "Hello, world!" is a C-style string literal -> type: const char[14]
cout << 5.0 << '\n'; ///< 5.0 (no suffix) is type double (by default)
cout << 5.0f << '\n'; ///< 5.0f is type float
Type of a literalis deduced from the literal’s value.Literal suffixesused to explicitly declare the type for a literal.
4.3. Numeral systems (decimal, binary, hexadecimal, and octal)
- Numeral system literals:
- Decimal (no prefix, 42)
- Binary (0b101010)
b - Hexadecimal (0x2A)
x - Octal (052) — all represent the same value.
- Can change the output format via use of the
std::dec, std::oct, and std::hexI/O manipulators: - Can define a
std::bitsetvariable and tellstd::bitsethow many bits we want to store.
int bin{}; // assume 16-bit int
bin = 0x0001; // assign binary 0000 0000 0000 0001 to the variable
bin = 0b1; // assign binary 0000 0000 0000 0001 to the variable
bin = 0b11; // assign binary 0000 0000 0000 0011 to the variable
cout << std::bitset<4>{ 0b1010 } << '\n'; // create a temporary std::bitset and print it
// C++14: quotation mark (‘) as a digit separator
int bin { 0b1011'0010 }; // assign binary 1011 0010 to the variable
long value { 2'132'673'462 }; // much easier to read than 2132673462
4.4. Constant Expression
-
Constant expressionsis an expressions whose values can be fully determined at compile time. -
constexpris used to declare compile-time constants -
Benefits:
- Safer code
- Optimizations
- Compile-time evaluation
-
constexprfunction is is a function that is allowed to be called in a constant expression, can be evaluated at compile time or runtime. -
constevalfunction is a function that must evaluate at compile-time.
constexpr int square(int x) {
return x * x;
}
consteval int cube(int x) {
return x * x * x;
}
constexpr int a = square(5); // compile-time
int b = square(5); // runtime allowed
constexpr int c = cube(5); // OK
// int d = cube(5); // Error if not compile-time
5. Operators & Bit Manipulation
5.1. Operators
- Refer : https://www.learncpp.com/cpp-tutorial/operator-precedence-and-associativity/
Increment/decrement:++x: increment x, then return xx++: copy x, then increment x, return the copy
Comma:(x, y): Evaluate x then y, returns value of y- avoid use this
5.2. Bit manipulation.
- To define a set of bit flags, use
uint8/16/32… orstd::bitset - Refers: https://www.learncpp.com/cpp-tutorial/bit-flags-and-bit-manipulation-via-stdbitset/
6. Control Flows
6.1. Constexpr if statement (C++17)
- Condition will be evaluated at runtime.
- e.g.
int main()
{
constexpr double gravity{ 9.8 };
if constexpr (gravity == 9.8) // now using constexpr if
std::cout << "Gravity is normal.\n";
else
std::cout << "We are not on Earth.\n";
return 0;
}
6.2. Switch fallthrough and scoping
- The
[[fallthrough]]attribute modifies a null statement to indicate that fallthrough is intentional (and no warnings should be triggered). - Initialization is not allowed before case labels because control flow in a switch may jump over the initializer, leaving the variable uninitialized.
- Declarations without an initializer are allowed before case labels because they only reserve space for the variable in the function’s stack frame (decided at compile time). No runtime initialization code is generated, so nothing can be “skipped” by jumping to a case.
- e.g.
int main() {
int x = 2;
switch (x) {
int a; // allowed (no initializer, just reserves space)
// int b{5}; // not allowed (initializer may be skipped)
case 1:
a = 10; // safe: 'a' exists, we assign here
std::cout << "Case 1, a = " << a << '\n';
[[fallthrough]]; // intentional fallthrough to case 2
case 2:
a = 20; // reassign
std::cout << "Case 2, a = " << a << '\n';
break;
default:
std::cout << "Default case\n";
break;
}
return 0;
}
6.3. Halts
- Halts allow us to terminate our program.Only use a halt if there is no safe or reasonable way to return normally from the main function. If you haven’t disabled exceptions, prefer using exceptions for handling errors safely.
std::exitis called implicitly when main() returns, it does not clean up local variables in the current function or up the call stack.std::abort()function causes your program to terminate abnormally. Abnormal termination means the program had some kind of unusual runtime error and the program couldn’t continue to run.std::terminate()function is typically used in conjunction with exceptions . By default, it callsstd::abort()- e.g.
#include <iostream>
#include <cstdlib> // for std::exit, std::abort
#include <exception> // for std::terminate
void cleanup() {
std::cout << "Cleaning up...\n";
}
void riskyFunction(bool fatalError) {
if (fatalError) {
std::cout << "Fatal error occurred!\n";
// std::abort: abnormal termination, no cleanup
std::abort();
// Or: std::terminate(); // usually called when exception handling fails
}
}
int main() {
cleanup(); // local function call
// Example 1: return normally from main
std::cout << "Program running normally...\n";
// Example 2: using std::exit (implicit when main returns)
if (false) {
std::cout << "Exiting via std::exit...\n";
std::exit(0); // does not call destructors of locals in main()
}
// Example 3: risky code that might abort/terminate
riskyFunction(true);
// Example 4: normal end of main calls std::exit implicitly
std::cout << "Main returns normally.\n";
return 0; // std::exit(0) is called implicitly here
}