ref: https://github.com/0voice/cpp-learning-2025
I. STL (Standard Template Library)
The STL (Standard Template Library) is a core C++ library that encapsulates common data structures (containers) and algorithms, following the “generic programming” paradigm. It is a high-frequency topic in technical interviews. Its core components are: Containers, Iterators, and Algorithms.
1. Common Containers
Containers are “containers” for storing data. Based on their underlying implementation, they are divided into Sequence Containers (ordered storage) and Associative Containers (storage by key-value pairs/unique values). Core container comparison:
| Container Type | Specific Container | Underlying Implementation | Core Characteristics | Typical Use Cases |
|---|---|---|---|---|
| Sequence Containers | vector |
Dynamic Array | Random access O(1), Tail insertion/deletion O(1), Middle insertion/deletion O(n), Contiguous storage | Frequent access, tail-end insertions/deletions (most common) |
list |
Doubly Linked List | Random access O(n), Insertion/deletion at any position O(1), Non-contiguous storage, No capacity concept | Frequent insertions/deletions in the middle | |
deque |
Segmented Dynamic Array | Random access O(1), Head/tail insertion/deletion O(1), Middle insertion/deletion O(n), Supports expansion without moving all elements | Frequent insertions/deletions at both ends, occasional random access | |
| Associative Containers (Ordered) | map |
Red-Black Tree | Keys ordered (ascending by default), Keys unique, Insert/Delete/Find O(log n), Key-value pair storage | Need ordered key-value pairs, deduplication, frequent lookup |
set |
Red-Black Tree | Elements ordered (ascending by default), Elements unique, Insert/Delete/Find O(log n), Stores values only | Need ordered sets, deduplication | |
multimap |
Red-Black Tree | Keys ordered, Keys can duplicate, Insert/Delete/Find O(log n) | Ordered key-value pairs, allowing duplicate keys | |
multiset |
Red-Black Tree | Elements ordered, Elements can duplicate, Insert/Delete/Find O(log n) | Ordered collections, allowing duplicate elements | |
| Associative Containers (Unordered) | unordered_map |
Hash Table | Keys unordered, Keys unique, Insert/Delete/Find avg O(1), worst O(n), Key-value pair storage | High-frequency lookup, order not required (recommended) |
unordered_set |
Hash Table | Elements unordered, Elements unique, Insert/Delete/Find avg O(1), worst O(n), Stores values only | High-frequency deduplication, order not required (recommended) | |
unordered_multimap |
Hash Table | Keys unordered, Keys can duplicate, avg O(1) | Unordered key-value pairs, allowing duplicate keys | |
unordered_multiset |
Hash Table | Elements unordered, Elements can duplicate, avg O(1) | Unordered collections, allowing duplicate elements |
Core Container Usage Examples
(1) vector (Dynamic Array, Most Common)
1 |
|
(2) unordered_map (Hash Table Key-Value Pairs, High-Frequency Use)
1 |
|
Key Points for unordered_map:
- Average case time complexity for insertion, deletion, and lookup is O(1), but can degrade to O(n) with poor hash functions or high collisions.
- Iterators are invalidated only when rehashing occurs (due to load factor threshold being exceeded), not on individual insertions/deletions (except for the element being erased).
- Use
reserve()to pre-allocate buckets if the approximate number of elements is known, improving performance.
2. Iterators
Iterators are “the bridge between containers and algorithms.” They are essentially objects that encapsulate pointers, providing a uniform way to traverse containers (abstracting away underlying differences).
(1) Iterator Categories
| Iterator Category | Capabilities | Supported Containers |
|---|---|---|
| Input Iterator | Read-only, forward traversal (only ++) |
All containers |
| Output Iterator | Write-only, forward traversal (only ++) |
All containers |
| Forward Iterator | Read/write, forward traversal (++) |
forward_list, unordered_* |
| Bidirectional Iterator | Read/write, bidirectional traversal (++/--) |
list, map, set |
| Random Access Iterator | Read/write, random access ([], +, -) |
vector, deque, arrays |
(2) Iterator Usage Conventions
- A container’s
begin()returns an iterator to the first element,end()returns an iterator to one past the last element (the “past-the-end” iterator, must not be dereferenced). - Constant iterators (
const_iterator): Can only read elements, not modify (forconstcontainers or read-only scenarios). - C++11+ added
cbegin()/cend()(return constant iterators),rbegin()/rend()(reverse iterators, traverse from end to beginning).
(3) Iterator Invalidation (High-Frequency Interview Topic)
Iterator invalidation occurs when the memory location an iterator points to becomes invalid (e.g., element deleted, container reallocated). Continuing to use an invalid iterator leads to crashes or logic errors. Core invalidation scenarios:
| Container | Invalidation Scenarios |
|---|---|
vector |
1. Insertion causing reallocation (all iterators invalidated); 2. Insertion/deletion in the middle (iterators after the point become invalid); 3. After clear(), all iterators become invalid. |
list |
1. Only iterators to the erased element become invalid, others remain valid; 2. Insertion does not affect any iterators. |
unordered_* |
1. Insertion causing rehashing (load factor exceeds threshold) invalidates all iterators; 2. Deletion invalidates only the iterator to the erased element. |
map/set |
1. Erasing an element invalidates its iterator; other iterators remain valid; 2. Insertion does not affect any iterators. |
Mitigation Strategies:
- After potential invalidation, reacquire valid iterators (e.g., don’t reuse old iterators after a
vectorinsertion). - When erasing elements, use the return value to update the iterator (e.g.,
list.erase(it)returns an iterator to the next valid element):1
2
3
4
5
6
7
8
9// Correct way to erase elements in a list (avoiding iterator invalidation)
list<int> lst = {1,2,3,4,5};
for (auto it = lst.begin(); it != lst.end(); ) {
if (*it % 2 == 0) {
it = lst.erase(it); // erase returns the next valid iterator, don't increment
} else {
++it;
}
}
3. Common Algorithms
STL algorithms encapsulate common operations (sorting, searching, counting, etc.), defined in the <algorithm> header. Most operate on containers via iterators and support custom logic (via lambdas or function objects).
Core Algorithm Examples
| Function | Purpose | Example Code |
|---|---|---|
sort() |
Sort range | sort(vec.begin(), vec.end()); (ascending default); sort(vec.begin(), vec.end(), greater<int>()); (descending) |
find() |
Find value | auto it = find(vec.begin(), vec.end(), 3); (returns iterator to found element, else end()) |
count() |
Count occurrences | int cnt = count(vec.begin(), vec.end(), 2); |
for_each() |
Apply function to each element | for_each(vec.begin(), vec.end(), [](int num){ cout << num << " "; }); |
find_if() |
Find element satisfying condition | auto it = find_if(vec.begin(), vec.end(), [](int num){ return num > 5; }); |
unique() |
Remove consecutive duplicates | vec.erase(unique(vec.begin(), vec.end()), vec.end()); (typically requires range to be sorted first) |
reverse() |
Reverse range | reverse(vec.begin(), vec.end()); |
accumulate() |
Accumulate values | int sum = accumulate(vec.begin(), vec.end(), 0); (requires <numeric>) |
Example: Combined Algorithm and Lambda Usage
1 |
|
4. Lambdas with STL
Lambdas (anonymous functions) are a core C++11+ feature, allowing in-place function definition without separate function declarations. They work perfectly with STL algorithms for custom logic.
Lambda Syntax Recap
1 | [capture-list](parameters) -> return-type { body } |
Core Capture List Usages
| Capture | Meaning |
|---|---|
[] |
Capture nothing |
[=] |
Capture all by value (read-only copies inside lambda) |
[&] |
Capture all by reference (modifiable references inside lambda) |
[x] |
Capture x by value only |
[&y] |
Capture y by reference only |
[=, &z] |
Capture all by value except z, which is captured by reference |
[&, x] |
Capture all by reference except x, captured by value |
[this] |
Capture the current object this pointer (access members) |
Note: Avoid default capture [=] or [&] in long-lived lambdas (e.g., stored in objects) to prevent dangling references or unintended copies. Prefer explicit capture lists.
Practical Example: Lambda with STL Sorting/Searching
1 |
|
II. File I/O
File I/O enables “data persistence” (storing data to disk files or reading data from files). C++ offers two styles: C-style file I/O (<cstdio>) and C+±style file I/O (<fstream>). The C++ style is recommended (object-oriented, type-safe).
1. Core Headers and Classes
| Header | Core Class | Purpose |
|---|---|---|
<fstream> |
ifstream |
Input file stream (read from file) |
ofstream |
Output file stream (write to file) | |
fstream |
Input/output file stream (both read and write) |
2. File Opening Modes
| Mode Flag | Meaning |
|---|---|
ios::in |
Read mode (default for ifstream) |
ios::out |
Write mode (default for ofstream). Creates file if doesn’t exist; truncates if exists. |
ios::app |
Append mode (append to end of file, do not truncate) |
ios::binary |
Binary mode (default is text mode) for binary files (images, videos, etc.) |
ios::trunc |
Truncate mode (truncate file if exists; default with ios::out) |
ios::ate |
Open and seek immediately to end of file (at end) |
ios::nocreate |
Open fails if file doesn’t exist (non-standard, use with caution) |
ios::noreplace |
Open fails if file exists (non-standard) |
Combination Examples:
ios::in | ios::out: Read/write mode (file must exist for reading).ios::out | ios::app: Append write mode.ios::in | ios::binary: Binary read mode.ios::out | ios::trunc | ios::binary: Binary write mode with truncation (default forofstreamwithbinary).
3. Text File Read/Write (Most Common)
Text files store character sequences (human-readable). Newline characters (\n) are automatically translated per platform during read/write.
(1) Writing Text Files
1 |
|
(2) Reading Text Files
1 |
|
4. Binary File Read/Write
Binary files store byte sequences (not human-readable). Used for structs, images, videos, etc. Use ios::binary mode; no newline translation occurs.
(1) Writing Binary Files (Storing Structs)
1 |
|
(2) Reading Binary Files (Restoring Structs)
1 |
|
Important Note on Binary I/O:
- Binary I/O of complex classes with pointers/virtual functions is non-portable and generally unsafe. Use for Plain Old Data (POD) types or implement serialization.
- Structure padding can differ across compilers/platforms. Use
#pragma packor compiler attributes for control if cross-platform compatibility is needed.
5. File Pointer Manipulation
File pointers (stream position indicators) point to the current read/write location. They can be adjusted using seekg() (for read/get pointer) and seekp() (for write/put pointer), along with tellg()/tellp() to get the current position.
Core Functions
| Function | Purpose |
|---|---|
seekg(pos, mode) |
Move read pointer: pos is offset, mode is base (ios::beg, ios::cur, ios::end) |
seekp(pos, mode) |
Move write pointer (same usage) |
tellg() |
Return current read pointer position (bytes from start) |
tellp() |
Return current write pointer position |
Example: Read Last 10 Bytes of a File
1 |
|
6. Data Persistence Notes
- For text files,
stringobjects work directly with<<and>>. For binary files, avoid storingstringdirectly (due to dynamic memory), prefer fixed-size character arrays or serialize separately. - Always check file open success (
is_open()or!ifs.fail()) to avoid errors from incorrect paths, permissions, etc. - Explicitly close files (
close()) or rely on RAII (destructor calls close). For long-lived streams, closing frees resources early. - Binary file cross-platform compatibility: Differences in endianness (byte order) and struct alignment can cause issues. Consider platform-independent serialization libraries for complex data.
III. Exception Handling
Exception handling is C++'s mechanism for dealing with runtime errors (e.g., division by zero, file open failure, memory allocation failure). The core idea is to “separate error detection from error handling,” leading to more robust and readable code.
1. Core Syntax: try, catch, throw
1 | try { |
Basic Example: Division by Zero
1 |
|
2. Custom Exception Classes (Recommended)
The C++ standard library provides the base exception class exception (in <exception>). Custom exception classes should inherit from exception (or its derived classes like runtime_error) and override the what() method (returns a C-style string description) for uniform handling.
Example: Custom Exception Class
1 |
|
3. Standard Library Exception Classes
The C++ standard library provides predefined exception classes, all derived from exception. Common ones:
| Exception Class | Header | Typical Cause |
|---|---|---|
runtime_error |
<stdexcept> |
Runtime errors that could be avoided (e.g., invalid argument, range error) |
logic_error |
<stdexcept> |
Logic errors detectable before runtime (e.g., invalid argument, domain error) |
bad_alloc |
<new> |
Memory allocation failure (new) |
bad_cast |
<typeinfo> |
Failed dynamic_cast |
out_of_range |
<stdexcept> |
Out-of-range access (e.g., vector::at(), string::at()) |
invalid_argument |
<stdexcept> |
Invalid argument passed to a function |
length_error |
<stdexcept> |
Exceeds maximum allowed size (e.g., string too long) |
Example: Using Standard Exceptions
1 |
|
4. noexcept (C++11+)
noexcept is an exception specifier indicating that a function does not throw any exceptions. Its core purposes are:
- Allows compiler optimizations (reduces exception handling overhead).
- Clearly documents function’s exception behavior for callers.
- If a function declared
noexceptdoes throw,std::terminate()is called (default behavior).
Syntax and Examples
1 | // Declare function as noexcept (does not throw) |
Typical Use Cases
- Destructors (implicitly
noexceptsince C++11 unless specified otherwise). - Move constructors and move assignment operators (often marked
noexceptfor efficiency in containers likevector). - Simple functions with no possibility of throwing (e.g., getters, simple calculations).
5. Exception Handling Best Practices
- Use exceptions only for exceptional, recoverable runtime errors (e.g., file not found, network disconnect). For logic errors (e.g., precondition violations), consider assertions (
assert) or error codes. - Prefer custom exception classes (derived from
std::exception) to categorize errors. - Throw exceptions by value, catch by const reference (
catch (const ExceptionType& e)). - Avoid throwing exceptions from destructors (can lead to termination if stack unwinding is already in progress).
- Catch exceptions in order from most derived to most base (specific to general).
- Use RAII to ensure resource cleanup even when exceptions are thrown.
- Document exception guarantees (nothrow, basic, strong) for functions.
IV. Memory Management
Memory management is a core and challenging aspect of C++, and a high-frequency interview topic. The goal is to “allocate memory appropriately, release it timely, and avoid leaks, dangling pointers, etc.”
1. Memory Layout
A C++ program’s memory at runtime is divided into several segments, each with different lifetimes and management:
| Segment | Stores | Lifetime | Managed By |
|---|---|---|---|
| Stack | Local variables, function parameters, return addresses | Automatic (destroyed when scope ends) | Compiler (push/pop) |
| Heap (Free Store) | Dynamically allocated memory (new/malloc) |
Manual (delete/free) or smart pointers |
Programmer |
| Static/Global Storage | Global variables, static variables (local/class) | Entire program duration | Compiler (allocated at program start, freed at exit) |
| Constant Data | String literals, const global/static data |
Entire program duration | Compiler, read-only |
| Code (Text) | Executable machine instructions | Entire program duration | Compiler, read-only |
Key Differences: Stack vs. Heap
| Aspect | Stack | Heap |
|---|---|---|
| Allocation | Automatic, compiler-managed | Manual, programmer-controlled (new, malloc) |
| Deallocation | Automatic (scope exit) | Manual (delete, free) or smart pointers |
| Size Limit | Limited (OS-dependent, typically MBs) | Large (limited by OS/address space) |
| Speed | Very fast (stack pointer adjustment) | Slower (involves OS, potential fragmentation) |
| Fragmentation | None (LIFO order) | Possible (frequent alloc/dealloc) |
| Growth Direction | Usually downwards (high to low addresses) | Usually upwards (low to high) |
| Access Pattern | Contiguous, local variables | Random, pointers required |
2. new/delete vs. malloc/free Differences (Interview Core)
new/delete are C++ operators; malloc/free are C library functions. Both allocate heap memory, but new/delete are integrated with C++'s object model.
| Aspect | new / delete |
malloc / free |
|---|---|---|
| Nature | C++ operators | C library functions |
| Type Safety | Returns correctly typed pointer (no cast needed) | Returns void*, requires cast |
| Construction/Destruction | new calls constructor; delete calls destructor |
Only allocates/frees memory; no constructor/destructor calls |
| On Failure | Throws std::bad_alloc (default) |
Returns nullptr |
| Array Allocation | new Type[size] and delete[] |
malloc(size * sizeof(Type)) and free |
| Overridable | Yes (class/global operator new/delete) |
No (only through library hooks) |
| Size Calculation | Compiler calculates for new[] |
Manual byte calculation required |
Comparison Examples
1 | // 1. Basic type |
Key Point: Never mix new with free or malloc with delete. They use different memory management infrastructures.
3. Smart Pointers (unique_ptr, shared_ptr, weak_ptr)
Smart pointers are a core C++11+ feature that wrap raw pointers and follow RAII (Resource Acquisition Is Initialization). They automatically manage heap memory (deallocating when the smart pointer goes out of scope), preventing memory leaks and dangling pointers.
(1) unique_ptr (Exclusive Ownership)
- Core characteristic: At any time, only one
unique_ptrowns a given heap object. Cannot be copied, only moved. - Use case: Single-owner scenarios (e.g., a local dynamic object, a member that uniquely owns a resource).
- Key operations:
make_unique<T>(args...): Creates aunique_ptr(preferred, exception-safe).std::move(p): Transfers ownership.reset(): Releases the owned object (calls deleter) and can take a new pointer.release(): Releases ownership (returns raw pointer, caller must manage).get(): Returns raw pointer (use with caution).
- Custom deleters are supported (e.g., for arrays, FILE*).
Example
1 |
|
(2) shared_ptr (Shared Ownership)
- Core characteristic: Multiple
shared_ptrinstances can share ownership of the same object. Uses reference counting; object is deleted when the lastshared_ptris destroyed/reset. - Use case: Shared resources (e.g., stored in containers, shared across threads).
- Key operations:
make_shared<T>(args...): Createsshared_ptr(preferred, often more efficient due to single allocation for object and control block).use_count(): Returns reference count (for debugging, not for program logic).reset(): Decrements reference count, releases ownership if last.get(): Returns raw pointer.
- Custom deleters are also supported.
Example
1 |
|
(3) weak_ptr (Weak Reference)
- Problem:
shared_ptrcircular reference (twoshared_ptrs point to each other, reference count never reaches zero -> memory leak). - Core characteristic: A
weak_ptrrefers to an object managed by ashared_ptrbut does not contribute to the reference count. To use the object, it must be converted to ashared_ptrvialock(). - Use case: Breaking circular references, caches, observer patterns.
Circular Reference Example (Problem)
1 |
|
Solution with weak_ptr
1 |
|
4. Memory Leaks and Prevention
A memory leak occurs when dynamically allocated heap memory is not deallocated, causing the memory to be permanently occupied, potentially exhausting system memory over time.
Common Leak Scenarios
- Forgetting to call
delete/freeafternew/malloc. - Circular references with
shared_ptr(withoutweak_ptr). - Exception thrown between
newanddelete(if not using RAII/smart pointers). - Overwriting a pointer before deleting the old memory (e.g.,
int* p = new int(5); p = new int(10);leaks the first allocation). - Incorrect use of
deleteinstead ofdelete[]for arrays (undefined behavior, may leak).
Prevention Strategies
- Prefer smart pointers (
unique_ptr,shared_ptr) over raw pointers for ownership. - Use
weak_ptrto breakshared_ptrcircular references. - Adopt RAII for all resources (files, locks, memory) – acquire in constructor, release in destructor.
- Use containers (
vector,string) instead of manual array allocation. - In development, use memory debugging tools (Valgrind, AddressSanitizer, Visual Studio debugger memory checks).
- Establish and follow clear ownership conventions in code.
5. Memory Management Best Practices
- Prefer stack allocation for automatic management and speed. Use for small, short-lived objects.
- Prefer smart pointers over raw
new/delete. Useunique_ptrby default for exclusive ownership; useshared_ptronly when shared ownership is necessary. - Avoid raw
new/deletein application code. If necessary, ensure they are paired and exception-safe (often using smart pointers immediately). - Never return a pointer/reference to a local stack variable (dangling pointer/reference).
- Use standard containers (
vector,map, etc.) which manage their own memory. - Be mindful of object slicing when passing polymorphic objects by value.
- Understand the Rule of Three/Five/Zero for classes managing resources.
V. Modern C++ Features (C++11 and beyond)
C++11 and subsequent standards introduced many practical features, significantly improving development efficiency and code readability. They are high-frequency interview topics.
1. auto (Automatic Type Deduction)
- Purpose: The compiler deduces the variable’s type from its initializer, simplifying code, especially for complex types (iterators, smart pointers, lambda types).
- Rules:
autovariable must be initialized (type cannot be deduced otherwise).- Deduction strips top-level
const,volatile, and references. Useconst auto&to preserve them. - For arrays and functions,
autodecays to pointer (unlessauto&is used).
Examples
1 |
|
Appropriate Use Cases
- Iterator and complex template type declarations.
- Lambda capture and storage.
- Generic code where the type is obvious or unimportant to the reader.
- Range-based for loops.
Inappropriate Use Cases
- Function parameters (use templates or concrete types).
- Class non-static data members (before C17; C17 allows
autofor non-static members only withstatic constin-class initialization? Actually,autois not allowed for non-static data members directly. Usedecltype(auto)or a trailing return type for complex member types if needed). - When the type is crucial for readability and not obvious from context.
2. decltype (Type Deduction)
- Purpose: Obtains the type of an expression or entity without evaluating it. Complementary to
auto(autodeduces a variable’s type from its initializer,decltypededuces the type of any expression). - Rules:
decltype(entity): Ifentityis a variable or function, yields its declared type (includingconst,volatile, references).decltype((variable))(note double parentheses): yields a reference type (lvalue reference).decltype(expression): For other expressions, yields the type and value category of the expression (reference if expression is an lvalue).
Examples
1 |
|
Primary Uses
- Defining return types in templates (especially with
decltypeandautotogether). - Declaring variables that must match the type of an expression exactly.
- Metaprogramming and type traits.
3. Lambda Expressions (Detailed in STL Section)
Lambdas are anonymous function objects. Their core advantage is “define locally, use locally,” eliminating the need for separate function declarations.
Additional Notes: Lambda to Function Pointer Conversion
- A lambda with no capture can be implicitly converted to a function pointer with the same signature.
- A lambda with captures cannot be converted to a function pointer (needs to store captured variables).
1 | void callFunc(int (*funcPtr)(int, int)) { |
4. Other Common Modern Features
(1) Range-based for loop (C++11)
Simplifies iteration over containers, arrays, or any range providing begin() and end().
1 | vector<int> vec = {1,2,3,4,5}; |
(2) Uniform Initialization and Initializer Lists (C++11)
Uses curly braces {} for initialization, providing a consistent syntax and preventing narrowing conversions.
1 | // Variable initialization |
(3) nullptr (C++11)
A keyword representing a null pointer constant of type std::nullptr_t. Safer than NULL (which is often 0) because it is not convertible to integral types.
1 | void func(int) { cout << "func(int)" << endl; } |
(4) constexpr (C++11/14)
Indicates that a variable/function can be evaluated at compile time. Enables compile-time computation, improving runtime performance.
1 | // constexpr variables |
C++17 added if constexpr for compile-time conditional compilation within templates.
5. Modern Feature Usage Recommendations
- Use
autoto reduce verbosity, especially with complex types, but not when the type is important for clarity. - Prefer uniform initialization
{}for its safety (no narrowing) and consistency. - Use lambdas for short, local operations (e.g., predicates for STL algorithms). For complex logic, consider named functions or function objects.
- Adopt smart pointers as the default for dynamic memory ownership.
- Use
nullptrinstead ofNULLor0for pointers. - For new projects, target at least C11 (preferably C14/17/20) to leverage modern features.
- For interviews, be prepared to discuss
auto,decltype, lambdas, smart pointers,nullptr,constexpr, move semantics, and maybe variadic templates.
说些什么吧!