Essential C++ Interview Concepts Explained
1. Pointer Constants vs. Constant Pointers
Pointer constant: The pointer itself is constant. It can only point to a specific memory location and cannot be redirected elsewhere. (The address is immutable, but the value is modifiable)
Note: A pointer constant must be initialized at definition.
int x = 10, y = 20;
int* const ptr = &x;
*ptr = 15; // Valid, can modify the value
ptr = &y; // Error, cannot change the address
Constant pointer: A pointer that points to constant data. The data it points to cannot be modified through this pointer, but the pointer itself can be redirected to point to other constants. (The address is mutable, but the value is immutable)
int x = 10, y = 20;
const int *ptr = &x;
*ptr = 15; // Error, cannot modify constant value
ptr = &y; // Valid, can point to another constant
Mnemonic: When const appears to the left of *, the data is constant (value cannot change). When const appears to the right of *, the pointer is constant (address cannot change). When const appears on both sides, both the pointer and the data are constant.
2. Dynamic Libraries vs. Static Libraries
The fundamental distinction between static and dynamic libraries is whether the library code is compiled into the target program.
Static libraries: Typically have extensions like .a or .lib. These libraries are directly integrated into the target program during compilation. Programs linked with static libraries become larger but can run independently without requiring external library files. However, if the library needs updating, the entire program must be recompiled.
Dynamic libraries: Typically have extensions like .so or .dll. Unlike static libraries, dynamic libraries are not fully embedded in the program. Instead, the program contains only a reference to the library. When the executable needs to use library functions, it loads them at runtime. Executables linked with dynamic libraries cannot run independently and require the library files to be present. The advantage is that updating the library doesn't require recompiling the entire program—only the library file needs to be replaced.
In summary: For released libraries, dynamic libraries are preferable as they allow updates without recompiling the entire application. However, when integrating multiple sub-libraries into a single dynamic library, all sub-libraries should first be compiled into a static library, which is then incorporated into the final dynamic library.
3. Purpose of Header File Guards
The #ifndef/#define/#endif directives serve as preprocessor guards to ensure that a header file is only included once in a compilation unit, preventing multiple definition errors.
4. Processes vs. Threads
- A process represents an instance of a program execution, while a thread is an execution unit within a process.
- Processes are independent, with separate memory spaces and context environments. Threads operate within the memory space of their parent process.
- Generally, processes cannot access the memory space of other processes. Threads created within the same process share the same memory space.
- Code segments within the same process cannot execute simultaneously unless multithreading is implemented.
5. Binary Tree Traversal Methods
Pre-order: Root → Left subtree → Right subtree
In-order: Left subtree → Root → Right subtree
Post-order: Left subtree → Right subtree → Root
Consider a binary tree as consisting of a root node and two subtrees (left and right). For example, with nodes X, Y, and Z, the pre-order traversal would be X Y Z. If node Y has its own subtree Y1 Y2, the traversal becomes X Y Y1 Y2 Z. If Y2 has a child Y3, the traversal becomes X Y Y1 Y2 Y3 Z, with the right subtree following the same pattern.
For a sample binary tree, the traversal orders would be:
Pre-order: P Q R S T U V W X
In-order: R Q S P U W V T X
Post-order: R S Q W V U X T P
6. Uses of the static Keyword in C++
Global static variables: Located in static storage, persist throughout program execution, and are invisible to external files. This means a global variable is only visible to functions in the same file.
Local static variables: Located in static storage, accessible within their local scope, but retain their values after the scope ends. This means the variable's value persists across function calls.
Static functions: Functions declared with static are not exported (extern by default). Static functions can only be called within the same file. Avoid declaring static functions in header files as they are only effective within their implementation file. This limits function visibility to the same file.
Class static members: Enable data sharing among different class instances without breaking encapsulation. Static members can be accessed without instantiating the class. Class static variables are modifiable and can be accessed via
Class static functions: Cannot access non-static members. They can only be called via
7. What is Memory Thrashing?
Memory thrashing refers to the phenomenon of frequent page replacement, which causes a significant decrease in system efficiency. Thrashing typically occurs due to inefficient memory allocation algorithms, insufficient memory, or poor program design that leads to excessive paging between memory and disk.
8. The Foundation of File Operations
The sole basis for file operations is the file handle, which serves as a unique identifier similar to an ID. The file descriptor, returned by the open function, is the handle used for subsequent read or write operations.
int open(const char *pathname, int flags, mode_t mode);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
In file I/O, to read data from a file, an application first calls an operating system function with the filename and path. This function returns a unique identifier called the file handle. To read data from the file, the application calls a read function, passing the file handle and the number of bytes to copy. After completion, the file is closed by calling another system function.
9. Using new and delete
The new and delete operators are used for dynamic memory allocation and deallocation.
int *ptr = new int; // Allocates memory for an integer and assigns its address to ptr
int *ptr = new int(42); // Allocates and initializes an integer to 42
int *arr = new int[100]; // Allocates an array of 100 integers
int **matrix = new int[5][6]; // Allocates a 2D array
1. Deallocating single variable memory:
int *ptr = new int;
delete ptr; // Frees memory for a single integer
2. Deallocating array memory:
int *arr = new int[5];
delete []arr; // Frees memory for the integer array
10. Differences Between new and malloc
- new is an operator, malloc is a function;
- new throws an exception on allocation failure, malloc returns NULL;
- new automatically calculates the required memory size, malloc requires explicit size specification;
- new automatically handles type conversion, malloc requires explicit casting.
// malloc example
char *buffer = (char*)malloc(1024); // Allocates 1024 bytes for characters
if (buffer == NULL) {
// Handle allocation failure
}
// Usage
free(buffer); // Don't forget to free allocated memory
11. int *p vs. int* p in C++
Both int *p and int* p declare p as a pointer to an integer. However, placing the asterisk (*) adjacent to the variable name improves readability.
For example:
int* p1, p2;
This means p1 is an integer pointer, while p2 is a regular integer variable. They don't both share the pointer type. Writing it as:
int *p1, p2;
Conveys the same meaning but is clearer and less ambiguous.