Welcome to C++
C++ is a powerful, high-performance programming language that builds upon the C language with object-oriented features. Developed by Bjarne Stroustrup in the early 1980s, C++ combines low-level memory manipulation with high-level abstractions, making it suitable for system programming, game development, and performance-critical applications.
Known for its efficiency and flexibility, C++ gives programmers fine-grained control over system resources while providing powerful features like classes, templates, and the Standard Template Library (STL). This combination makes C++ a valuable skill for developers working on operating systems, game engines, embedded systems, and high-frequency trading applications.
Introduction to C++
How to set up a C++ development environment and write your first "Hello, World!" program.
C++ is one of the most widely used programming languages, known for its performance and versatility. Setting up a C++ development environment is straightforward, with options available for all major operating systems.
Step 1: Install a C++ Compiler
-
Windows
Install MinGW-w64 or Microsoft Visual Studio with C++ support. For MinGW, download from mingw-w64.org and add the bin directory to your PATH. -
macOS
Install Xcode from the App Store, which includes the Clang compiler. Alternatively, install Command Line Tools by runningxcode-select --install
in Terminal. -
Linux
Use your distribution's package manager. For Ubuntu/Debian:sudo apt install g++
Step 2: Verify Installation
Open a terminal or command prompt and type:
g++ --version
This should display the installed compiler version.
Step 3: Write and Compile Your First C++ Program
-
Create a file named
hello.cpp
with the following content:#include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }
-
Compile the program:
g++ hello.cpp -o hello
-
Run the executable:
./hello # On Linux/macOS hello.exe # On Windows
You should see the output:
Hello, World!
Congratulations! You have successfully set up a C++ environment and run your first program. 🎉
C++ Syntax Basics
C++ syntax is derived from C, with additions for object-oriented programming. Understanding the basic syntax is crucial for writing correct C++ programs.
1. Basic Structure of a C++ Program
Every C++ program has a specific structure:
#include <iostream> // Preprocessor directive
using namespace std; // Using the standard namespace
int main() { // Main function - program entry point
// Program statements go here
return 0; // Return 0 to indicate successful execution
}
2. Semicolons and Braces
C++ uses semicolons to terminate statements and braces {}
to define code blocks:
int x = 5; // Statement ends with semicolon
if (x > 3) { // Braces define the if block
cout << "x is greater than 3";
} // Closing brace
3. Comments
C++ supports single-line and multi-line comments:
// This is a single-line comment
/*
This is a multi-line comment
It can span multiple lines
*/
4. Case Sensitivity
C++ is case-sensitive, meaning it distinguishes between uppercase and lowercase letters:
int myVar = 5; // Different from myvar
int MyVar = 10; // Different from myVar
cout << myVar; // Outputs: 5
cout << MyVar; // Outputs: 10
5. Variables and Static Typing
C++ is statically typed, meaning you must declare the type of a variable explicitly:
int x = 10; // Integer
float y = 3.14; // Floating-point number
char z = 'A'; // Character
string name = "C++"; // String (requires #include <string>)
cout << typeid(x).name(); // Outputs the type name
Conclusion
Understanding C++ syntax is essential for writing correct and efficient programs. Key takeaways include:
- C++ programs start execution from the
main()
function - Statements end with semicolons
- Code blocks are defined with braces
{}
- C++ is case-sensitive
- C++ uses static typing - variable types must be declared
Output with cout
The cout
object in C++ is used to display output on the screen. It is part of the iostream library and is one of the most commonly used features for basic output and debugging.
1. Basic cout Usage
The simplest way to use cout
is with the insertion operator <<
:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!"; // Outputs: Hello, World!
cout << 42; // Outputs: 42
return 0;
}
2. Outputting Multiple Values
You can chain multiple values with the insertion operator:
cout << "Hello" << " " << "C++"; // Outputs: Hello C++
3. Using endl for New Lines
The endl
manipulator inserts a newline and flushes the output buffer:
cout << "First line" << endl;
cout << "Second line" << endl;
Output:
First line Second line
4. Formatting Output
C++ provides various manipulators for formatting output:
#include <iomanip> // For formatting manipulators
cout << fixed << setprecision(2) << 3.14159; // Outputs: 3.14
cout << setw(10) << "Hello"; // Outputs: " Hello" (right-aligned in 10 spaces)
5. Printing Variables
You can print variables just like literals:
string name = "Alice";
int age = 25;
cout << "Name: " << name << ", Age: " << age;
Conclusion
The cout
object is a fundamental tool for output in C++. Key points:
- Use
cout
with the insertion operator<<
- Chain multiple values with multiple
<<
operators - Use
endl
for new lines - Include
<iomanip>
for advanced formatting
Welcome to C++
C++ is a powerful, high-performance programming language that builds upon the C language with object-oriented features. Developed by Bjarne Stroustrup in the early 1980s, C++ combines low-level memory manipulation with high-level abstractions, making it suitable for system programming, game development, and performance-critical applications.
Known for its efficiency and flexibility, C++ gives programmers fine-grained control over system resources while providing powerful features like classes, templates, and the Standard Template Library (STL). This combination makes C++ a valuable skill for developers working on operating systems, game engines, embedded systems, and high-frequency trading applications.
Arithmetic Operators in C++
C++ provides standard arithmetic operators for mathematical calculations. These operators work with numeric data types like integers and floating-point numbers.
Basic Arithmetic Operators
#include <iostream>
using namespace std;
int main() {
int a = 15, b = 4;
cout << "a + b = " << (a + b) << endl; // Addition: 19
cout << "a - b = " << (a - b) << endl; // Subtraction: 11
cout << "a * b = " << (a * b) << endl; // Multiplication: 60
cout << "a / b = " << (a / b) << endl; // Division: 3 (integer division)
cout << "a % b = " << (a % b) << endl; // Modulus: 3
float x = 15.0, y = 4.0;
cout << "x / y = " << (x / y) << endl; // Division: 3.75 (float division)
return 0;
}
Increment and Decrement Operators
int count = 5;
cout << "count = " << count << endl; // 5
cout << "++count = " << ++count << endl; // 6 (pre-increment)
cout << "count++ = " << count++ << endl; // 6 (post-increment)
cout << "count = " << count << endl; // 7
cout << "--count = " << --count << endl; // 6 (pre-decrement)
Common Pitfalls
- Integer division truncates the fractional part:
5 / 2
equals2
, not2.5
- Modulus operator
%
only works with integer types - Division by zero causes undefined behavior
Comparison Operators in C++
Comparison operators compare two values and return a boolean result (true
or false
). These are essential for conditional statements and loops.
Comparison Operators
#include <iostream>
using namespace std;
int main() {
int x = 7, y = 10;
cout << "x == y: " << (x == y) << endl; // Equal to: false
cout << "x != y: " << (x != y) << endl; // Not equal to: true
cout << "x > y: " << (x > y) << endl; // Greater than: false
cout << "x < y: " << (x < y) << endl; // Less than: true
cout << "x >= 7: " << (x >= 7) << endl; // Greater than or equal: true
cout << "y <= 7: " << (y <= 7) << endl; // Less than or equal: false
return 0;
}
Using Comparisons in Conditions
int age = 18;
if (age >= 18) {
cout << "You are an adult" << endl;
} else {
cout << "You are a minor" << endl;
}
Common Pitfalls
- Confusing assignment
=
with equality==
- Comparing floating-point numbers for exact equality can be problematic due to precision issues
Logical Operators in C++
Logical operators combine boolean expressions and return true
or false
. C++ provides three logical operators: &&
(AND), ||
(OR), and !
(NOT).
Logical Operators
#include <iostream>
using namespace std;
int main() {
bool isSunny = true;
bool isWarm = false;
cout << "isSunny && isWarm: " << (isSunny && isWarm) << endl; // AND: false
cout << "isSunny || isWarm: " << (isSunny || isWarm) << endl; // OR: true
cout << "!isWarm: " << (!isWarm) << endl; // NOT: true
// Complex conditions
int age = 25;
bool hasLicense = true;
if (age >= 18 && hasLicense) {
cout << "You can drive" << endl;
}
return 0;
}
Short-Circuit Evaluation
// In AND (&&), if first condition is false, second isn't evaluated
// In OR (||), if first condition is true, second isn't evaluated
int x = 5;
if (x != 0 && 10/x > 1) { // Safe because x != 0 is checked first
cout << "Condition satisfied" << endl;
}
Common Pitfalls
- Using bitwise operators (
&
,|
) instead of logical operators (&&
,||
) - Forgetting that logical operators have lower precedence than comparison operators
Bitwise Operators in C++
Bitwise operators perform operations on individual bits of integer types. They are used in low-level programming, embedded systems, and performance optimization.
Bitwise Operators
#include <iostream>
#include <bitset>
using namespace std;
int main() {
unsigned int a = 6; // binary: 0110
unsigned int b = 3; // binary: 0011
cout << "a = " << bitset<4>(a) << " (" << a << ")" << endl;
cout << "b = " << bitset<4>(b) << " (" << b << ")" << endl;
cout << "a & b = " << bitset<4>(a & b) << " (" << (a & b) << ")" << endl; // AND: 0010 (2)
cout << "a | b = " << bitset<4>(a | b) << " (" << (a | b) << ")" << endl; // OR: 0111 (7)
cout << "a ^ b = " << bitset<4>(a ^ b) << " (" << (a ^ b) << ")" << endl; // XOR: 0101 (5)
cout << "~a = " << bitset<4>(~a) << " (" << (~a) << ")" << endl; // NOT: 1001 (9)
cout << "a << 1 = " << bitset<4>(a << 1) << " (" << (a << 1) << ")" << endl; // Left shift: 1100 (12)
cout << "b >> 1 = " << bitset<4>(b >> 1) << " (" << (b >> 1) << ")" << endl; // Right shift: 0001 (1)
return 0;
}
Practical Applications
// Setting flags
const unsigned int FLAG_A = 1; // 0001
const unsigned int FLAG_B = 2; // 0010
const unsigned int FLAG_C = 4; // 0100
unsigned int settings = 0;
settings |= FLAG_A; // Set flag A
settings |= FLAG_C; // Set flag C
// Check if flag B is set
if (settings & FLAG_B) {
cout << "Flag B is set" << endl;
} else {
cout << "Flag B is not set" << endl;
}
Common Pitfalls
- Confusing bitwise AND
&
with logical AND&&
- Right-shifting negative numbers has implementation-defined behavior
- Shifting beyond the bit width causes undefined behavior
Assignment Operators in C++
Assignment operators assign values to variables. C++ provides compound assignment operators that combine assignment with arithmetic or bitwise operations.
Assignment Operators
#include <iostream>
using namespace std;
int main() {
int x = 10; // Simple assignment
cout << "x = " << x << endl;
x += 5; // Equivalent to x = x + 5
cout << "x += 5: " << x << endl; // 15
x -= 3; // Equivalent to x = x - 3
cout << "x -= 3: " << x << endl; // 12
x *= 2; // Equivalent to x = x * 2
cout << "x *= 2: " << x << endl; // 24
x /= 4; // Equivalent to x = x / 4
cout << "x /= 4: " << x << endl; // 6
x %= 4; // Equivalent to x = x % 4
cout << "x %= 4: " << x << endl; // 2
// Bitwise assignment operators
unsigned int y = 5;
y &= 3; // AND assignment: y = y & 3
y |= 8; // OR assignment: y = y | 8
y ^= 4; // XOR assignment: y = y ^ 4
return 0;
}
Multiple Assignment
int a, b, c;
a = b = c = 10; // All variables get value 10
// Compound assignment in loops
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i; // Add i to sum
}
cout << "Sum: " << sum << endl; // 15
Common Pitfalls
- Confusing
=
(assignment) with==
(equality comparison) - Assignment has right-to-left associativity:
a = b = c
meansa = (b = c)
Integer Data Types in C++
C++ provides several integer types with different sizes and ranges. Choosing the right integer type depends on the required range and memory constraints.
Basic Integer Types
#include <iostream>
#include <climits>
using namespace std;
int main() {
// Signed integers (can represent negative numbers)
short s = 100; // Typically 2 bytes
int i = 100000; // Typically 4 bytes
long l = 100000L; // Typically 4 or 8 bytes
long long ll = 100000LL; // Typically 8 bytes
// Unsigned integers (only non-negative numbers)
unsigned short us = 100;
unsigned int ui = 100000;
unsigned long ul = 100000UL;
unsigned long long ull = 100000ULL;
cout << "short range: " << SHRT_MIN << " to " << SHRT_MAX << endl;
cout << "int range: " << INT_MIN << " to " << INT_MAX << endl;
cout << "unsigned int range: 0 to " << UINT_MAX << endl;
return 0;
}
Integer Operations
int a = 10, b = 3;
cout << "a + b = " << (a + b) << endl; // 13
cout << "a - b = " << (a - b) << endl; // 7
cout << "a * b = " << (a * b) << endl; // 30
cout << "a / b = " << (a / b) << endl; // 3 (integer division)
cout << "a % b = " << (a % b) << endl; // 1 (modulus)
Type Conversion
// Implicit conversion
int num = 3.14; // num becomes 3 (truncation)
// Explicit conversion (C-style)
int x = (int)3.14;
// Explicit conversion (C++ style)
int y = static_cast<int>(3.14);
// String to integer
#include <string>
string str = "123";
int z = stoi(str); // C++11
Common Pitfalls
- Integer overflow when result exceeds type's maximum value
- Integer division truncates fractional parts
- Mixing signed and unsigned types can cause unexpected behavior
Floating-Point Data Types in C++
Floating-point types represent real numbers with fractional parts. C++ provides three floating-point types with different precision levels.
Floating-Point Types
#include <iostream>
#include <iomanip>
#include <cmath>
using namespace std;
int main() {
float f = 3.14159f; // Single precision (typically 4 bytes)
double d = 3.14159265358979; // Double precision (typically 8 bytes)
long double ld = 3.141592653589793238L; // Extended precision
cout << fixed << setprecision(10);
cout << "float: " << f << endl;
cout << "double: " << d << endl;
cout << "long double: " << ld << endl;
// Special values
cout << "sqrt(-1): " << sqrt(-1) << endl; // NaN (Not a Number)
cout << "1.0/0.0: " << 1.0/0.0 << endl; // inf (Infinity)
cout << "-1.0/0.0: " << -1.0/0.0 << endl; // -inf
return 0;
}
Floating-Point Operations
double a = 2.5, b = 1.5;
cout << "a + b = " << (a + b) << endl; // 4.0
cout << "a - b = " << (a - b) << endl; // 1.0
cout << "a * b = " << (a * b) << endl; // 3.75
cout << "a / b = " << (a / b) << endl; // 1.66667
// Mathematical functions
cout << "sqrt(a) = " << sqrt(a) << endl;
cout << "pow(a, b) = " << pow(a, b) << endl;
cout << "sin(pi/2) = " << sin(M_PI/2) << endl;
Type Conversion
// String to floating-point
string str = "3.14159";
double pi = stod(str);
// Floating-point to integer (truncation)
double d = 3.99;
int i = static_cast<int>(d); // i becomes 3
// Integer to floating-point
int x = 5;
double y = static_cast<double>(x); // y becomes 5.0
Common Pitfalls
- Floating-point numbers have limited precision and rounding errors
- Comparing floating-point numbers for exact equality is often problematic
- Operations with very large or very small numbers can cause overflow/underflow
Strings in C++
C++ provides the string
class for working with text. Strings are sequences of characters and offer many convenient operations for text manipulation.
Creating and Using Strings
#include <iostream>
#include <string>
using namespace std;
int main() {
// Different ways to create strings
string s1 = "Hello";
string s2("World");
string s3(5, 'A'); // "AAAAA"
string s4 = s1 + " " + s2; // Concatenation
cout << "s1: " << s1 << endl;
cout << "s2: " << s2 << endl;
cout << "s3: " << s3 << endl;
cout << "s4: " << s4 << endl;
// Accessing characters
cout << "First character: " << s1[0] << endl; // 'H'
cout << "Second character: " << s1.at(1) << endl; // 'e'
// String length
cout << "Length of s1: " << s1.length() << endl;
cout << "Is s1 empty? " << (s1.empty() ? "Yes" : "No") << endl;
return 0;
}
String Comparison and Searching
string str1 = "apple", str2 = "banana";
// Comparison
if (str1 == str2) {
cout << "Strings are equal" << endl;
} else if (str1 < str2) {
cout << str1 << " comes before " << str2 << endl;
} else {
cout << str1 << " comes after " << str2 << endl;
}
// Searching
string text = "Hello World";
size_t pos = text.find("World");
if (pos != string::npos) {
cout << "Found 'World' at position " << pos << endl;
}
String Modification
string str = "Hello";
// Append
str.append(" World");
str += "!"; // Same as above
// Insert
str.insert(5, " C++");
// Replace
str.replace(0, 5, "Hi"); // Replace 5 chars from position 0
// Erase
str.erase(2, 4); // Erase 4 chars from position 2
cout << "Modified string: " << str << endl;
Common Pitfalls
- Accessing characters beyond string length with
[]
causes undefined behavior - Use
at()
for bounds-checked access - C-style strings (
char*
) and C++ strings (std::string
) are different types
String Functions in C++
The C++ string class provides many useful member functions for string manipulation, including trimming, searching, replacing, and converting.
Substring Operations
#include <iostream>
#include <string>
using namespace std;
int main() {
string text = "Hello World";
// Extract substring
string sub = text.substr(6, 5); // From position 6, 5 characters
cout << "Substring: " << sub << endl; // "World"
// Find operations
size_t pos1 = text.find('o'); // First 'o'
size_t pos2 = text.find('o', 5); // First 'o' after position 5
size_t pos3 = text.rfind('o'); // Last 'o'
cout << "First 'o': " << pos1 << endl; // 4
cout << "Second 'o': " << pos2 << endl; // 7
cout << "Last 'o': " << pos3 << endl; // 7
return 0;
}
String Manipulation
// Trimming (C++ doesn't have built-in trim, but we can implement)
string trim(const string& str) {
size_t start = str.find_first_not_of(" \t\n\r");
size_t end = str.find_last_not_of(" \t\n\r");
return (start == string::npos) ? "" : str.substr(start, end - start + 1);
}
string text = " hello world ";
cout << "Trimmed: '" << trim(text) << "'" << endl;
// Case conversion
#include <algorithm>
#include <cctype>
string mixed = "Hello World";
transform(mixed.begin(), mixed.end(), mixed.begin(), ::toupper);
cout << "Uppercase: " << mixed << endl;
transform(mixed.begin(), mixed.end(), mixed.begin(), ::tolower);
cout << "Lowercase: " << mixed << endl;
String Splitting and Joining
// Splitting a string
string data = "apple,banana,cherry";
size_t start = 0, end;
vector<string> fruits;
while ((end = data.find(',', start)) != string::npos) {
fruits.push_back(data.substr(start, end - start));
start = end + 1;
}
fruits.push_back(data.substr(start));
// Joining strings
string joined;
for (size_t i = 0; i < fruits.size(); ++i) {
if (i > 0) joined += "-";
joined += fruits[i];
}
cout << "Joined: " << joined << endl;
Common Pitfalls
find()
returnsstring::npos
when pattern is not found- Always check return value of
find()
before using the position - String manipulation functions often create new strings rather than modifying in place
String Formatting in C++
C++ provides several ways to format strings, from traditional C-style functions to modern C++ approaches with string streams and format libraries.
Using String Streams
#include <iostream>
#include <sstream>
#include <iomanip>
using namespace std;
int main() {
string name = "Alice";
int age = 25;
double score = 95.5;
// Using stringstream
stringstream ss;
ss << "Name: " << name << ", Age: " << age << ", Score: " << fixed << setprecision(1) << score;
string result = ss.str();
cout << result << endl;
// Extracting from stringstream
string data = "123 45.67 hello";
stringstream ss2(data);
int n;
double d;
string s;
ss2 >> n >> d >> s;
cout << "Extracted: " << n << ", " << d << ", " << s << endl;
return 0;
}
C-Style Formatting
#include <cstdio>
char buffer[100];
int count = 5;
double value = 3.14159;
// sprintf for string formatting
sprintf(buffer, "Count: %d, Value: %.2f", count, value);
cout << buffer << endl;
// snprintf for safer formatting (C++11)
snprintf(buffer, sizeof(buffer), "Count: %d, Value: %.2f", count, value);
cout << buffer << endl;
Modern C++ Formatting (C++20)
// C++20 format library
#include <format>
string name = "Bob";
int visits = 3;
double rating = 4.5;
// Using std::format (C++20)
// string message = format("Hello {}, you have {} visits with rating {:.1f}", name, visits, rating);
// cout << message << endl;
// Alternative: Use fmt library if C++20 not available
// cout << fmt::format("Hello {}, you have {} visits", name, visits) << endl;
Number Formatting
#include <iomanip>
double pi = 3.14159265358979;
cout << "Default: " << pi << endl;
cout << "Fixed: " << fixed << pi << endl;
cout << "Scientific: " << scientific << pi << endl;
cout << "Precision 2: " << fixed << setprecision(2) << pi << endl;
// Integer formatting
int number = 255;
cout << "Decimal: " << number << endl;
cout << "Hex: " << hex << number << endl;
cout << "Octal: " << oct << number << endl;
Common Pitfalls
- C-style
sprintf
can cause buffer overflows if not careful - Always prefer
snprintf
oversprintf
for safety - Format specifiers must match the variable types
- String streams can be less efficient for simple formatting
Arrays in C++
Arrays are collections of elements of the same type stored in contiguous memory. C++ supports both fixed-size arrays and dynamic arrays.
Fixed-Size Arrays
#include <iostream>
using namespace std;
int main() {
// Declaration and initialization
int numbers[5] = {1, 2, 3, 4, 5};
double prices[] = {1.99, 2.99, 3.49}; // Size inferred
// Accessing elements
cout << "First element: " << numbers[0] << endl;
cout << "Last element: " << numbers[4] << endl;
// Modifying elements
numbers[2] = 100;
// Array size
int size = sizeof(numbers) / sizeof(numbers[0]);
cout << "Array size: " << size << endl;
// Iterating through array
for (int i = 0; i < size; i++) {
cout << "numbers[" << i << "] = " << numbers[i] << endl;
}
return 0;
}
Multi-dimensional Arrays
// 2D array (matrix)
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Accessing elements
cout << "Element at [1][2]: " << matrix[1][2] << endl; // 6
// Nested loops for 2D array
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
Array Limitations
// Arrays have fixed size
// Cannot use range-based for with pointer-decayed arrays
// No bounds checking by default
int arr[5] = {1, 2, 3, 4, 5};
// arr[10] = 100; // Undefined behavior - no bounds checking!
Common Pitfalls
- Array indices start at 0, not 1
- No bounds checking - accessing out-of-bounds causes undefined behavior
- Arrays decay to pointers when passed to functions
- Cannot return arrays from functions directly
Array Indexing in C++
Array indexing allows access to individual elements in an array using their position. C++ uses zero-based indexing, meaning the first element is at index 0.
Basic Array Indexing
#include <iostream>
using namespace std;
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// Accessing elements by index
cout << "numbers[0] = " << numbers[0] << endl; // First element: 10
cout << "numbers[2] = " << numbers[2] << endl; // Third element: 30
cout << "numbers[4] = " << numbers[4] << endl; // Last element: 50
// Modifying elements
numbers[1] = 25; // Change second element from 20 to 25
numbers[3] = 45; // Change fourth element from 40 to 45
// Display modified array
for (int i = 0; i < 5; i++) {
cout << "numbers[" << i << "] = " << numbers[i] << endl;
}
return 0;
}
Multi-dimensional Array Indexing
// 2D array indexing
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
cout << "matrix[0][1] = " << matrix[0][1] << endl; // 2
cout << "matrix[1][2] = " << matrix[1][2] << endl; // 6
// 3D array indexing
int cube[2][2][2] = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
};
cout << "cube[1][0][1] = " << cube[1][0][1] << endl; // 6
Character Array Indexing
char word[] = "Hello";
cout << "word[0] = " << word[0] << endl; // 'H'
cout << "word[4] = " << word[4] << endl; // 'o'
// Strings are null-terminated
cout << "word[5] = " << (int)word[5] << endl; // 0 (null character)
Common Pitfalls
- Accessing out-of-bounds indices causes undefined behavior
- Array indices start at 0, not 1
- Negative indices are not allowed in standard arrays
- No bounds checking by default
Array Functions in C++
C++ provides several ways to work with arrays, including standard library functions and algorithms from the STL.
Array Size and Basic Operations
#include <iostream>
#include <algorithm> // For std::sort, std::fill, etc.
using namespace std;
int main() {
int arr[] = {5, 2, 8, 1, 9};
int size = sizeof(arr) / sizeof(arr[0]);
// Get array size
cout << "Array size: " << size << endl;
// Find maximum and minimum
int* maxPtr = max_element(arr, arr + size);
int* minPtr = min_element(arr, arr + size);
cout << "Max: " << *maxPtr << ", Min: " << *minPtr << endl;
// Sort array
sort(arr, arr + size);
cout << "Sorted array: ";
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
Array Manipulation Functions
#include <cstring> // For C-style string functions
// Working with character arrays
char str1[20] = "Hello";
char str2[20] = "World";
// Copy strings
strcpy(str1, "Hello"); // Copy "Hello" to str1
strcat(str1, " "); // Append space
strcat(str1, str2); // Append "World"
cout << "Concatenated: " << str1 << endl;
// Compare strings
if (strcmp(str1, str2) == 0) {
cout << "Strings are equal" << endl;
} else {
cout << "Strings are different" << endl;
}
// Get string length
cout << "Length: " << strlen(str1) << endl;
Array Algorithms
#include <numeric> // For std::accumulate
int numbers[] = {1, 2, 3, 4, 5};
int size = 5;
// Sum of elements
int sum = accumulate(numbers, numbers + size, 0);
cout << "Sum: " << sum << endl;
// Fill array with value
fill(numbers, numbers + size, 10);
cout << "After fill: ";
for (int i = 0; i < size; i++) {
cout << numbers[i] << " ";
}
cout << endl;
// Reverse array
reverse(numbers, numbers + size);
cout << "Reversed: ";
for (int i = 0; i < size; i++) {
cout << numbers[i] << " ";
}
cout << endl;
Common Pitfalls
- C-style arrays don't know their own size - you must track it separately
- Arrays decay to pointers when passed to functions
- C-string functions don't check buffer sizes, leading to potential overflows
- Always ensure arrays are large enough for operations
Pointers in C++
Pointers are variables that store memory addresses. They provide direct access to memory and are essential for dynamic memory allocation and efficient data structures.
Basic Pointer Operations
#include <iostream>
using namespace std;
int main() {
int number = 42;
int* ptr = &number; // ptr stores address of number
cout << "Value of number: " << number << endl;
cout << "Address of number: " << &number << endl;
cout << "Value of ptr: " << ptr << endl;
cout << "Value at ptr: " << *ptr << endl; // Dereferencing
// Modifying through pointer
*ptr = 100;
cout << "New value of number: " << number << endl;
// Pointer to different types
double pi = 3.14159;
double* dptr = π
cout << "Value at dptr: " << *dptr << endl;
return 0;
}
Pointers and Arrays
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // Array name decays to pointer to first element
cout << "First element: " << *ptr << endl; // 10
cout << "Second element: " << *(ptr + 1) << endl; // 20
cout << "Third element: " << ptr[2] << endl; // 30 (array syntax works)
// Pointer arithmetic
ptr++; // Move to next element
cout << "After increment: " << *ptr << endl; // 20
ptr += 2; // Move two elements forward
cout << "After +2: " << *ptr << endl; // 40
Null Pointers and Safety
// Different ways to create null pointers
int* ptr1 = nullptr; // C++11 preferred
int* ptr2 = 0;
int* ptr3 = NULL;
// Always check pointers before use
if (ptr1 != nullptr) {
cout << *ptr1 << endl;
} else {
cout << "Pointer is null" << endl;
}
// Dangerous: dereferencing null pointer
// *ptr1 = 5; // CRASH! Undefined behavior
Common Pitfalls
- Dereferencing null or uninitialized pointers causes crashes
- Pointer arithmetic can easily go out of bounds
- Memory leaks if dynamically allocated memory isn't freed
- Dangling pointers after memory is freed
Pointer Arithmetic in C++
Pointer arithmetic allows you to navigate through memory by adding or subtracting from pointers. This is particularly useful with arrays and dynamic memory.
Basic Pointer Arithmetic
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;
cout << "Pointer at: " << ptr << endl;
cout << "Value: " << *ptr << endl; // 10
ptr++; // Move to next int (4 bytes forward typically)
cout << "After ++: " << ptr << endl;
cout << "Value: " << *ptr << endl; // 20
ptr += 2; // Move two ints forward
cout << "After += 2: " << ptr << endl;
cout << "Value: " << *ptr << endl; // 40
ptr--; // Move one int backward
cout << "After --: " << ptr << endl;
cout << "Value: " << *ptr << endl; // 30
return 0;
}
Pointer Differences and Comparisons
int numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int* start = numbers;
int* end = numbers + 10; // Pointer one past the end
// Calculate number of elements between pointers
int distance = end - start;
cout << "Distance: " << distance << " elements" << endl;
// Pointer comparisons
int* middle = numbers + 5;
if (start < middle) {
cout << "start comes before middle" << endl;
}
if (middle < end) {
cout << "middle comes before end" << endl;
}
// Iterate using pointers
for (int* p = start; p < end; p++) {
cout << *p << " ";
}
cout << endl;
Pointer Arithmetic with Different Types
// Different types have different sizes
char chars[] = {'a', 'b', 'c', 'd', 'e'};
double doubles[] = {1.1, 2.2, 3.3, 4.4, 5.5};
char* cptr = chars;
double* dptr = doubles;
cout << "char pointer: " << (void*)cptr << endl;
cptr++;
cout << "after ++: " << (void*)cptr << endl; // Moves 1 byte
cout << "double pointer: " << dptr << endl;
dptr++;
cout << "after ++: " << dptr << endl; // Moves 8 bytes (typically)
Practical Applications
// Finding array length
template
size_t arraySize(T (&array)[N]) {
return N;
}
int data[] = {1, 2, 3, 4, 5};
cout << "Array size: " << arraySize(data) << endl;
// String length using pointers
const char* str = "Hello";
const char* p = str;
while (*p != '\0') {
p++;
}
cout << "String length: " << (p - str) << endl;
Common Pitfalls
- Pointer arithmetic only valid within same array or one past the end
- Adding two pointers is not allowed
- Pointer arithmetic on void pointers is not allowed (unknown size)
- Going beyond array bounds causes undefined behavior
Variables in C++
Variables are named storage locations in memory that hold data. C++ is a statically-typed language, meaning variable types must be declared explicitly.
Variable Declaration and Initialization
#include <iostream>
#include <string>
using namespace std;
int main() {
// Declaration and initialization
int age = 25;
double price = 19.99;
char grade = 'A';
string name = "Alice";
bool isActive = true;
// Multiple variables
int x = 5, y = 10, z = 15;
// Different initialization styles
int a = 10; // Copy initialization
int b(20); // Direct initialization
int c{30}; // Uniform initialization (C++11)
int d = {40}; // Copy list initialization
cout << "a: " << a << ", b: " << b << ", c: " << c << ", d: " << d << endl;
return 0;
}
Variable Scope
int globalVar = 100; // Global variable
void demoFunction() {
int localVar = 50; // Local variable
cout << "Local: " << localVar << ", Global: " << globalVar << endl;
{
int blockVar = 25; // Block scope
cout << "Block: " << blockVar << endl;
}
// cout << blockVar << endl; // Error: blockVar not accessible here
}
int main() {
demoFunction();
// cout << localVar << endl; // Error: localVar not accessible here
return 0;
}
Constants and Type Modifiers
// Constants
const double PI = 3.14159;
const int MAX_SIZE = 100;
// PI = 3.14; // Error: const variable cannot be modified
// Type modifiers
unsigned int positiveOnly = 4000000000;
short smallNumber = 32767;
long bigNumber = 2147483647;
long long veryBigNumber = 9223372036854775807LL;
// Volatile (tells compiler variable may change unexpectedly)
volatile int hardwareRegister = 0;
// Auto type deduction (C++11)
auto number = 42; // int
auto decimal = 3.14; // double
auto text = "Hello"; // const char*
auto active = true; // bool
Variable Lifetime and Storage Classes
// Automatic (default) - created when block entered, destroyed when exited
void automaticDemo() {
int x = 10; // Automatic storage
}
// Static - persists for program lifetime
void staticDemo() {
static int count = 0; // Initialized only once
count++;
cout << "Count: " << count << endl;
}
// External - accessible across files
extern int externalVar; // Defined in another file
int main() {
automaticDemo();
staticDemo(); // Count: 1
staticDemo(); // Count: 2
staticDemo(); // Count: 3
return 0;
}
Common Pitfalls
- Uninitialized variables contain garbage values
- Using variables outside their scope
- Name shadowing (local variables hiding global ones)
- Forgetting that
char
can be signed or unsigned depending on implementation
Conditional Statements: if, else if, else
Conditional statements allow your program to make decisions and execute different code blocks based on conditions.
Basic if Statement
#include <iostream>
using namespace std;
int main() {
int number;
cout << "Enter a number: ";
cin >> number;
// Simple if statement
if (number > 0) {
cout << "The number is positive" << endl;
}
// if-else statement
if (number % 2 == 0) {
cout << "The number is even" << endl;
} else {
cout << "The number is odd" << endl;
}
return 0;
}
if-else if-else Chain
int score;
cout << "Enter your score (0-100): ";
cin >> score;
if (score >= 90) {
cout << "Grade: A" << endl;
} else if (score >= 80) {
cout << "Grade: B" << endl;
} else if (score >= 70) {
cout << "Grade: C" << endl;
} else if (score >= 60) {
cout << "Grade: D" << endl;
} else {
cout << "Grade: F" << endl;
}
Nested if Statements
int age;
bool hasLicense;
cout << "Enter your age: ";
cin >> age;
cout << "Do you have a license? (1 for yes, 0 for no): ";
cin >> hasLicense;
if (age >= 18) {
if (hasLicense) {
cout << "You can drive legally" << endl;
} else {
cout << "You need to get a license first" << endl;
}
} else {
cout << "You are too young to drive" << endl;
}
Conditional Operator (Ternary)
int a = 10, b = 20;
// Traditional if-else
int max;
if (a > b) {
max = a;
} else {
max = b;
}
// Using ternary operator
int max2 = (a > b) ? a : b;
cout << "Maximum: " << max2 << endl;
// Nested ternary (use sparingly!)
int x = 5, y = 10, z = 15;
int largest = (x > y) ? ((x > z) ? x : z) : ((y > z) ? y : z);
cout << "Largest: " << largest << endl;
Common Pitfalls
- Using assignment
=
instead of equality==
in conditions - Forgetting braces
{}
for multi-statement blocks - Dangling else problem (else matches with nearest if)
- Overusing nested ternary operators reduces readability
for Loop in C++
The for loop is used when you know exactly how many times you want to iterate. It has three parts: initialization, condition, and increment.
Basic for Loop
#include <iostream>
using namespace std;
int main() {
// Count from 1 to 5
for (int i = 1; i <= 5; i++) {
cout << "Count: " << i << endl;
}
// Countdown from 10 to 1
for (int i = 10; i >= 1; i--) {
cout << i << " ";
}
cout << endl;
// Iterate through array
int numbers[] = {2, 4, 6, 8, 10};
for (int i = 0; i < 5; i++) {
cout << numbers[i] << " ";
}
cout << endl;
return 0;
}
Advanced for Loop Variations
// Multiple variables in initialization
for (int i = 0, j = 10; i < j; i++, j--) {
cout << "i=" << i << ", j=" << j << endl;
}
// Omitting parts (infinite loop with break)
int count = 0;
for (;;) {
cout << count << " ";
count++;
if (count >= 5) break;
}
cout << endl;
// Range-based for loop (C++11)
int arr[] = {1, 2, 3, 4, 5};
for (int value : arr) {
cout << value << " ";
}
cout << endl;
// Range-based with references (modify original)
for (int& value : arr) {
value *= 2; // Double each element
}
Nested for Loops
// Multiplication table
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
cout << i * j << "\t";
}
cout << endl;
}
// Pattern printing
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= i; j++) {
cout << "*";
}
cout << endl;
}
Common Pitfalls
- Off-by-one errors (using
<=
instead of<
) - Modifying loop counter inside the loop body
- Infinite loops when condition never becomes false
- Variable scope issues with loop counter declaration
while Loop in C++
The while loop repeats a block of code as long as a condition is true. It's useful when you don't know in advance how many iterations are needed.
Basic while Loop
#include <iostream>
using namespace std;
int main() {
// Count from 1 to 5
int i = 1;
while (i <= 5) {
cout << "Count: " << i << endl;
i++;
}
// User input validation
int number;
cout << "Enter a positive number: ";
cin >> number;
while (number <= 0) {
cout << "Invalid input. Enter a positive number: ";
cin >> number;
}
cout << "Thank you! You entered: " << number << endl;
return 0;
}
do-while Loop
// do-while executes at least once
char choice;
do {
cout << "Menu:" << endl;
cout << "1. Option One" << endl;
cout << "2. Option Two" << endl;
cout << "q. Quit" << endl;
cout << "Enter your choice: ";
cin >> choice;
switch (choice) {
case '1': cout << "Option One selected" << endl; break;
case '2': cout << "Option Two selected" << endl; break;
case 'q': cout << "Goodbye!" << endl; break;
default: cout << "Invalid choice" << endl;
}
} while (choice != 'q');
Practical while Loop Examples
// Reading until sentinel value
int value, sum = 0;
cout << "Enter numbers (0 to stop): ";
cin >> value;
while (value != 0) {
sum += value;
cin >> value;
}
cout << "Sum: " << sum << endl;
// File reading
#include <fstream>
ifstream file("data.txt");
string line;
while (getline(file, line)) {
cout << line << endl;
}
file.close();
// Infinite loop with break
while (true) {
int num;
cout << "Enter a number (negative to quit): ";
cin >> num;
if (num < 0) break;
cout << "Square: " << num * num << endl;
}
Common Pitfalls
- Forgetting to update the loop control variable
- Infinite loops when condition never becomes false
- Using assignment
=
instead of comparison==
in condition - Off-by-one errors in loop conditions
Loop Control Statements
C++ provides three loop control statements: break
, continue
, and goto
(use sparingly). These statements alter the normal flow of loops.
break Statement
#include <iostream>
using namespace std;
int main() {
// break exits the loop immediately
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // Exit loop when i reaches 5
}
cout << i << " ";
}
cout << endl; // Output: 1 2 3 4
// Search example
int numbers[] = {2, 4, 6, 8, 10, 12, 14};
int target = 8;
for (int i = 0; i < 7; i++) {
if (numbers[i] == target) {
cout << "Found " << target << " at position " << i << endl;
break; // Stop searching once found
}
}
return 0;
}
continue Statement
// continue skips the rest of current iteration
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // Skip even numbers
}
cout << i << " "; // Only odd numbers printed
}
cout << endl; // Output: 1 3 5 7 9
// Input validation with continue
int sum = 0;
for (int i = 0; i < 5; i++) {
int num;
cout << "Enter positive number " << (i + 1) << ": ";
cin >> num;
if (num <= 0) {
cout << "Invalid number, skipping..." << endl;
i--; // Don't count this iteration
continue;
}
sum += num;
}
cout << "Sum of positive numbers: " << sum << endl;
Nested Loops with Control Statements
// break only affects innermost loop
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
cout << "Breaking inner loop" << endl;
break; // Only breaks inner loop
}
cout << i << " * " << j << " = " << i * j << endl;
}
}
// Using labels with break (rarely needed)
outer: for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i * j > 4) {
cout << "Breaking both loops" << endl;
break outer; // Break both loops
}
cout << i << " * " << j << " = " << i * j << endl;
}
}
goto Statement (Use With Caution)
// goto can make code hard to read - use sparingly!
int attempts = 0;
retry:
if (attempts >= 3) {
cout << "Too many attempts!" << endl;
return 1;
}
int number;
cout << "Enter a positive number: ";
cin >> number;
if (number <= 0) {
cout << "Invalid input. ";
attempts++;
goto retry;
}
cout << "Valid number: " << number << endl;
Common Pitfalls
- Using
break
whencontinue
is more appropriate - Overusing
goto
creates "spaghetti code" break
only exits the innermost loop- Placing code after
continue
that will never execute
Nested Loops in C++
Nested loops are loops within loops. They are essential for working with multi-dimensional data, matrices, and complex patterns.
Basic Nested Loops
#include <iostream>
using namespace std;
int main() {
// Simple nested loop
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << "(" << i << "," << j << ") ";
}
cout << endl;
}
/* Output:
(1,1) (1,2) (1,3)
(2,1) (2,2) (2,3)
(3,1) (3,2) (3,3) */
// Multiplication table
cout << "\nMultiplication Table:" << endl;
for (int i = 1; i <= 5; i++) {
for (int j = 1; j <= 5; j++) {
cout << i * j << "\t";
}
cout << endl;
}
return 0;
}
Pattern Printing with Nested Loops
// Right triangle
int rows = 5;
for (int i = 1; i <= rows; i++) {
for (int j = 1; j <= i; j++) {
cout << "*";
}
cout << endl;
}
// Inverted triangle
for (int i = rows; i >= 1; i--) {
for (int j = 1; j <lt<= i; j++) {
cout << "*";
}
cout << endl;
}
// Pyramid
for (int i = 1; i <= rows; i++) {
// Print spaces
for (int j = 1; j <= rows - i; j++) {
cout << " ";
}
// Print stars
for (int j = 1; j <= 2 * i - 1; j++) {
cout << "*";
}
cout << endl;
}
Working with 2D Arrays
// Matrix operations
const int ROWS = 3;
const int COLS = 3;
int matrix[ROWS][COLS] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// Print matrix
cout << "Matrix:" << endl;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
// Sum of all elements
int sum = 0;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
sum += matrix[i][j];
}
}
cout << "Sum of all elements: " << sum << endl;
// Transpose matrix
cout << "Transpose:" << endl;
for (int i = 0; i < COLS; i++) {
for (int j = 0; j < ROWS; j++) {
cout << matrix[j][i] << " ";
}
cout << endl;
}
Mixed Loop Types
// while inside for
for (int i = 1; i <= 3; i++) {
int j = 1;
while (j <= 3) {
cout << "i=" << i << ", j=" << j << endl;
j++;
}
}
// do-while inside for
for (int i = 1; i <= 2; i++) {
int j = 1;
do {
cout << "i=" << i << ", j=" << j << endl;
j++;
} while (j <= 2);
}
Common Pitfalls
- O(n²) time complexity can be inefficient for large data
- Using wrong loop variables in inner vs outer loops
- Forgetting to reset inner loop variables
- Excessive nesting reduces readability (try to keep to 2-3 levels)
Functions in C++
Functions are reusable blocks of code that perform specific tasks. They help in organizing code, reducing duplication, and making programs more modular.
Function Definition and Declaration
#include <iostream>
#include <cmath>
using namespace std;
// Function declaration (prototype)
double calculateArea(double radius);
// Function definition
double calculateArea(double radius) {
return M_PI * radius * radius;
}
// Function with multiple parameters
int addNumbers(int a, int b) {
return a + b;
}
// Function without parameters
void greet() {
cout << "Hello, welcome to the program!" << endl;
}
// Function without return value (void)
void printMessage(string message) {
cout << "Message: " << message << endl;
}
int main() {
greet();
double radius = 5.0;
double area = calculateArea(radius);
cout << "Area of circle with radius " << radius << " is " << area << endl;
int sum = addNumbers(10, 20);
cout << "Sum: " << sum << endl;
printMessage("Functions make code organized!");
return 0;
}
Function Parameters
// Pass by value (default)
void incrementByValue(int x) {
x++; // Only modifies local copy
cout << "Inside function: " << x << endl;
}
// Pass by reference
void incrementByReference(int &x) {
x++; // Modifies original variable
cout << "Inside function: " << x << endl;
}
// Pass by pointer
void incrementByPointer(int *x) {
(*x)++; // Modifies original variable
cout << "Inside function: " << *x << endl;
}
// Default parameters
void displayInfo(string name, int age = 18, string city = "Unknown") {
cout << "Name: " << name << ", Age: " << age << ", City: " << city << endl;
}
int main() {
int num = 5;
incrementByValue(num);
cout << "After pass by value: " << num << endl; // Still 5
incrementByReference(num);
cout << "After pass by reference: " << num << endl; // Now 6
incrementByPointer(&num);
cout << "After pass by pointer: " << num << endl; // Now 7
displayInfo("Alice"); // Uses default age and city
displayInfo("Bob", 25); // Uses default city
displayInfo("Charlie", 30, "NYC"); // Uses all provided values
return 0;
}
Function Overloading
// Same function name, different parameters
int multiply(int a, int b) {
return a * b;
}
double multiply(double a, double b) {
return a * b;
}
int multiply(int a, int b, int c) {
return a * b * c;
}
// Overloaded print functions
void print(int value) {
cout << "Integer: " << value << endl;
}
void print(double value) {
cout << "Double: " << value << endl;
}
void print(string value) {
cout << "String: " << value << endl;
}
int main() {
cout << multiply(3, 4) << endl; // Calls int version
cout << multiply(2.5, 3.5) << endl; // Calls double version
cout << multiply(2, 3, 4) << endl; // Calls three-parameter version
print(10);
print(3.14);
print("Hello");
return 0;
}
Recursive Functions
// Factorial using recursion
unsigned long long factorial(int n) {
if (n <= 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}
// Fibonacci sequence
int fibonacci(int n) {
if (n <= 1) return n; // Base cases
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
cout << "Factorial of 5: " << factorial(5) << endl;
cout << "Fibonacci sequence: ";
for (int i = 0; i < 10; i++) {
cout << fibonacci(i) << " ";
}
cout << endl;
return 0;
}
Common Pitfalls
- Forgetting return statement in non-void functions
- Function declaration mismatch with definition
- Infinite recursion without proper base case
- Default parameters must be at the end of parameter list
Include Directives and Headers in C++
Header files contain declarations that can be shared across multiple source files. Include directives tell the preprocessor to include the contents of header files.
Standard Library Headers
// Input/output operations
#include <iostream>
using namespace std;
// String manipulation
#include <string>
// Mathematical functions
#include <cmath>
// C-style string functions
#include <cstring>
// Standard template library
#include <vector>
#include <algorithm>
#include <map>
// File operations
#include <fstream>
// Memory management
#include <memory>
// Time and date
#include <ctime>
int main() {
// Examples using different headers
string text = "Hello World";
cout << "String length: " << text.length() << endl;
cout << "Square root of 16: " << sqrt(16) << endl;
vector<int> numbers = {1, 2, 3, 4, 5};
cout << "Vector size: " << numbers.size() << endl;
return 0;
}
Creating and Using Custom Headers
// math_utils.h (header file)
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
// Function declarations
int add(int a, int b);
double calculateCircleArea(double radius);
bool isPrime(int number);
#endif
// math_utils.cpp (implementation)
#include "math_utils.h"
#include <cmath>
int add(int a, int b) {
return a + b;
}
double calculateCircleArea(double radius) {
return M_PI * radius * radius;
}
bool isPrime(int number) {
if (number <= 1) return false;
for (int i = 2; i * i <= number; i++) {
if (number % i == 0) return false;
}
return true;
}
// main.cpp
#include <iostream>
#include "math_utils.h"
using namespace std;
int main() {
cout << "5 + 3 = " << add(5, 3) << endl;
cout << "Area of circle with radius 2: " << calculateCircleArea(2) << endl;
cout << "Is 17 prime? " << (isPrime(17) ? "Yes" : "No") << endl;
return 0;
}
Include Guards and Pragma Once
// Traditional include guards
#ifndef MY_HEADER_H
#define MY_HEADER_H
// Header content here
#endif
// Modern alternative (compiler-specific but widely supported)
#pragma once
// Header content here
/* Why use include guards?
Prevents multiple inclusion of the same header
Avoids redefinition errors
Improves compilation speed */
Common Header Organization
// Typical header structure:
// 1. Include guards
#ifndef MY_CLASS_H
#define MY_CLASS_H
// 2. System includes (angle brackets)
#include <iostream>
#include <vector>
// 3. Forward declarations (if needed)
class OtherClass;
// 4. Namespace (if using)
using namespace std;
// 5. Class/function declarations
class MyClass {
private:
int data;
public:
MyClass(int value);
void display() const;
int getData() const;
};
// 6. Inline function definitions (if any)
inline int MyClass::getData() const {
return data;
}
// 7. End include guard
#endif
Common Pitfalls
- Missing include guards causing redefinition errors
- Circular includes (A includes B, B includes A)
- Including implementation files (.cpp) instead of headers
- Forgetting to include necessary headers for used functionality
File Input/Output in C++
C++ provides file stream classes for reading from and writing to files. The main classes are ifstream
(input), ofstream
(output), and fstream
(both).
Basic File Operations
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// Writing to a file
ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "Hello, File!" << endl;
outFile << "This is line 2" << endl;
outFile << "Number: " << 42 << endl;
outFile.close();
cout << "File written successfully" << endl;
} else {
cout << "Error opening file for writing" << endl;
}
// Reading from a file
ifstream inFile("example.txt");
if (inFile.is_open()) {
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
} else {
cout << "Error opening file for reading" << endl;
}
return 0;
}
Different File Opening Modes
// Various file opening modes
ofstream file1("output.txt"); // Default: truncate
ofstream file2("output.txt", ios::app); // Append mode
ofstream file3("output.txt", ios::ate); // Start at end
ofstream file4("output.txt", ios::binary); // Binary mode
fstream file5("data.txt", ios::in | ios::out); // Read and write
// Example: Append to file
ofstream logFile("log.txt", ios::app);
if (logFile.is_open()) {
logFile << "New log entry" << endl;
logFile.close();
}
Reading Different Data Types
// Writing mixed data types
ofstream dataFile("data.txt");
if (dataFile.is_open()) {
dataFile << "John Doe 25 85.5" << endl;
dataFile << "Jane Smith 30 92.0" << endl;
dataFile.close();
}
// Reading mixed data types
ifstream inputFile("data.txt");
if (inputFile.is_open()) {
string firstName, lastName;
int age;
double score;
while (inputFile >> firstName >> lastName >> age >> score) {
cout << "Name: " << firstName << " " << lastName
<< ", Age: " << age << ", Score: " << score << endl;
}
inputFile.close();
}
// Reading character by character
ifstream charFile("example.txt");
if (charFile.is_open()) {
char ch;
while (charFile.get(ch)) {
cout << ch;
}
charFile.close();
}
Binary File Operations
#include <iomanip>
struct Person {
char name[50];
int age;
double salary;
};
int main() {
// Writing binary data
Person p1 = {"Alice", 30, 50000.0};
Person p2 = {"Bob", 25, 45000.0};
ofstream binOut("people.dat", ios::binary);
if (binOut.is_open()) {
binOut.write(reinterpret_cast<char*>(&p1), sizeof(Person));
binOut.write(reinterpret_cast<char*>(&p2), sizeof(Person));
binOut.close();
}
// Reading binary data
Person p;
ifstream binIn("people.dat", ios::binary);
if (binIn.is_open()) {
while (binIn.read(reinterpret_cast<char*>(&p), sizeof(Person))) {
cout << "Name: " << p.name << ", Age: " << p.age
<< ", Salary: " << fixed << setprecision(2) << p.salary << endl;
}
binIn.close();
}
return 0;
}
File Position Operations
// Working with file positions
fstream file("data.txt", ios::in | ios::out);
if (file.is_open()) {
// Get current position
streampos pos = file.tellg();
cout << "Current position: " << pos << endl;
// Seek to beginning
file.seekg(0, ios::beg);
// Seek to end
file.seekg(0, ios::end);
streampos size = file.tellg();
cout << "File size: " << size << " bytes" << endl;
// Seek to specific position
file.seekg(10, ios::beg); // 10 bytes from beginning
file.close();
}
Common Pitfalls
- Forgetting to check if file opened successfully
- Not closing files (automatic in destructors, but good practice)
- Mixing text and binary modes
- Assuming file operations always succeed - always check for errors
Standard Template Library (STL) Introduction
The Standard Template Library (STL) is a powerful set of C++ template classes that provide common data structures and algorithms. It includes containers, iterators, and algorithms.
STL Components Overview
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
// Containers - store data
vector<int> numbers = {3, 1, 4, 1, 5, 9, 2, 6};
// Iterators - access elements
cout << "Using iterators: ";
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
cout << *it << " ";
}
cout << endl;
// Algorithms - operate on data
sort(numbers.begin(), numbers.end());
cout << "Sorted: ";
for (int num : numbers) {
cout << num << " ";
}
cout << endl;
// Function objects (functors)
struct Square {
int operator()(int x) const { return x * x; }
};
Square square;
cout << "5 squared: " << square(5) << endl;
return 0;
}
Key STL Concepts
// 1. Templates - generic programming
template
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// 2. Iterators - uniform access to containers
vector words = {"apple", "banana", "cherry"};
for (auto it = words.rbegin(); it != words.rend(); ++it) {
cout << *it << " "; // Reverse iteration
}
cout << endl;
// 3. Algorithms - reusable operations
vector<int> data = {1, 2, 3, 4, 5};
vector<int> result;
transform(data.begin(), data.end(), back_inserter(result),
[](int x) { return x * x; });
// 4. Function objects and lambdas
auto isEven = [](int x) { return x % 2 == 0; };
int evenCount = count_if(data.begin(), data.end(), isEven);
STL Header Organization
// Sequence containers
#include <vector> // Dynamic array
#include <deque> // Double-ended queue
#include <list> // Doubly-linked list
#include <forward_list> // Singly-linked list
#include <array> // Fixed-size array
// Associative containers
#include <set> // Ordered unique elements
#include <map> // Ordered key-value pairs
#include <unordered_set> // Hash-based set
#include <unordered_map> // Hash-based map
// Container adapters
#include <stack> // LIFO
#include <queue> // FIFO
#include <priority_queue> // Priority-based
// Algorithms and utilities
#include <algorithm> // Sort, search, etc.
#include <numeric> // Numeric operations
#include <iterator> // Iterator utilities
#include <functional> // Function objects
Benefits of Using STL
// 1. Type safety
vector<int> v1;
// v1.push_back("hello"); // Compile error - type mismatch
// 2. Memory management
vector<int> numbers;
numbers.reserve(100); // Pre-allocate memory
for (int i = 0; i < 100; i++) {
numbers.push_back(i); // Automatic memory management
}
// 3. Algorithm efficiency
vector<int> largeData(1000000);
sort(largeData.begin(), largeData.end()); // Highly optimized
// 4. Code reuse
template<typename Container>
void printContainer(const Container& c) {
for (const auto& element : c) {
cout << element << " ";
}
cout << endl;
}
// Works with any container type
vector<int> v = {1, 2, 3};
list<string> l = {"a", "b", "c"};
printContainer(v);
printContainer(l);
Common Pitfalls
- Invalidating iterators by modifying containers
- Not checking
end()
iterators before use - Using wrong comparison functions for custom types
- Forgetting that algorithms work with iterator ranges, not containers directly
STL Containers
STL containers are data structures that store collections of objects. They can be categorized as sequence containers, associative containers, and container adapters.
Sequence Containers
#include <iostream>
#include <vector>
#include <deque>
#include <list>
#include <array>
using namespace std;
int main() {
// Vector - dynamic array
vector vec = {1, 2, 3};
vec.push_back(4); // Add to end
vec.insert(vec.begin(), 0); // Insert at beginning
cout << "Vector: ";
for (int v : vec) cout << v << " ";
cout << endl;
// Deque - double-ended queue
deque dq = {2, 3, 4};
dq.push_front(1); // Add to front
dq.push_back(5); // Add to end
cout << "Deque: ";
for (int d : dq) cout << d << " ";
cout << endl;
// List - doubly-linked list
list lst = {1, 2, 4};
auto it = lst.begin();
advance(it, 2);
lst.insert(it, 3); // Insert in middle
cout << "List: ";
for (int l : lst) cout << l << " ";
cout << endl;
// Array - fixed size
array<int, 5> arr = {1, 2, 3, 4, 5};
cout << "Array size: " << arr.size() << endl;
return 0;
}
Associative Containers
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>
int main() {
// Set - unique elements, sorted
set s = {3, 1, 4, 1, 5}; // Duplicates removed
s.insert(2);
cout << "Set: ";
for (int val : s) cout << val << " "; // 1 2 3 4 5
cout << endl;
// Map - key-value pairs, sorted by key
map ages;
ages["Alice"] = 25;
ages["Bob"] = 30;
ages["Charlie"] = 35;
cout << "Ages:" << endl;
for (const auto& pair : ages) {
cout << pair.first << ": " << pair.second << endl;
}
// Unordered set - hash-based
unordered_set us = {"apple", "banana", "cherry"};
// Unordered map - hash-based
unordered_map prices;
prices["apple"] = 1.99;
prices["banana"] = 0.99;
return 0;
}
Container Adapters
#include <stack>
#include <queue>
#include <priority_queue>
int main() {
// Stack - LIFO (Last In First Out)
stack st;
st.push(1);
st.push(2);
st.push(3);
cout << "Stack top: " << st.top() << endl; // 3
st.pop();
cout << "After pop: " << st.top() << endl; // 2
// Queue - FIFO (First In First Out)
queue q;
q.push("first");
q.push("second");
q.push("third");
cout << "Queue front: " << q.front() << endl; // first
q.pop();
cout << "After pop: " << q.front() << endl; // second
// Priority Queue - highest priority first
priority_queue pq;
pq.push(30);
pq.push(10);
pq.push(20);
cout << "Priority queue: ";
while (!pq.empty()) {
cout << pq.top() << " "; // 30 20 10
pq.pop();
}
cout << endl;
return 0;
}
Container Operations and Performance
// Common operations across containers
vector v = {1, 2, 3, 4, 5};
// Size operations
cout << "Size: " << v.size() << endl;
cout << "Empty: " << (v.empty() ? "Yes" : "No") << endl;
// Element access
cout << "Front: " << v.front() << endl;
cout << "Back: " << v.back() << endl;
cout << "Element at 2: " << v[2] << endl;
// Modifiers
v.push_back(6);
v.pop_back();
v.insert(v.begin() + 2, 99);
v.erase(v.begin() + 1);
// Performance characteristics
/*
Container | Insert | Delete | Access | Search
-------------|--------|--------|--------|--------
vector | O(1)* | O(1)* | O(1) | O(n)
deque | O(1) | O(1) | O(1) | O(n)
list | O(1) | O(1) | O(n) | O(n)
set/map | O(log n)|O(log n)| O(log n)|O(log n)
unordered_* | O(1)* | O(1)* | O(1) | O(1)*
* = amortized constant time
*/
Common Pitfalls
- Using
[]
operator withmap
creates elements - Invalidating iterators when modifying containers
- Forgetting that associative containers are sorted
- Not reserving space in
vector
when known size
STL Algorithms
The STL provides a rich set of algorithms that operate on ranges of elements. These algorithms are generic and work with any container that provides iterators.
Non-modifying Algorithms
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
using namespace std;
int main() {
vector numbers = {1, 2, 3, 4, 5, 2, 3, 2};
// Count occurrences
int count2 = count(numbers.begin(), numbers.end(), 2);
cout << "Count of 2: " << count2 << endl;
// Find element
auto it = find(numbers.begin(), numbers.end(), 3);
if (it != numbers.end()) {
cout << "Found 3 at position: " << distance(numbers.begin(), it) << endl;
}
// Check conditions
bool allPositive = all_of(numbers.begin(), numbers.end(),
[](int x) { return x > 0; });
cout << "All positive: " << (allPositive ? "Yes" : "No") << endl;
// Accumulate (sum)
int sum = accumulate(numbers.begin(), numbers.end(), 0);
cout << "Sum: " << sum << endl;
return 0;
}
Modifying Algorithms
vector data = {1, 2, 3, 4, 5};
// Transform (apply function to each element)
vector squared;
transform(data.begin(), data.end(), back_inserter(squared),
[](int x) { return x * x; });
// Fill with value
fill(data.begin(), data.end(), 0);
// Generate with function
vector randomNumbers(5);
generate(randomNumbers.begin(), randomNumbers.end(),
[]() { return rand() % 100; });
// Replace values
replace(data.begin(), data.end(), 0, 10);
// Remove elements
vector withDuplicates = {1, 2, 2, 3, 2, 4};
auto newEnd = remove(withDuplicates.begin(), withDuplicates.end(), 2);
withDuplicates.erase(newEnd, withDuplicates.end());
Sorting and Searching
vector nums = {5, 2, 8, 1, 9, 3};
// Sort
sort(nums.begin(), nums.end());
cout << "Sorted: ";
for (int n : nums) cout << n << " ";
cout << endl;
// Binary search (requires sorted range)
bool found = binary_search(nums.begin(), nums.end(), 5);
cout << "5 found: " << (found ? "Yes" : "No") << endl;
// Lower and upper bound
auto lb = lower_bound(nums.begin(), nums.end(), 3); // First >= 3
auto ub = upper_bound(nums.begin(), nums.end(), 3); // First > 3
cout << "Range for 3: [" << distance(nums.begin(), lb) << ", "
<< distance(nums.begin(), ub) << ")" << endl;
// Partial sort
vector large = {9, 3, 6, 1, 7, 2, 8, 4, 5};
partial_sort(large.begin(), large.begin() + 3, large.end());
cout << "First 3 sorted: ";
for (int i = 0; i < 3; i++) cout << large[i] << " ";
cout << endl;
Set Operations and Permutations
vector set1 = {1, 2, 3, 4, 5};
vector set2 = {3, 4, 5, 6, 7};
vector result;
// Union
set_union(set1.begin(), set1.end(), set2.begin(), set2.end(),
back_inserter(result));
cout << "Union: ";
for (int n : result) cout << n << " ";
cout << endl;
// Intersection
result.clear();
set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(),
back_inserter(result));
cout << "Intersection: ";
for (int n : result) cout << n << " ";
cout << endl;
// Permutations
vector perm = {1, 2, 3};
cout << "Permutations: " << endl;
do {
for (int n : perm) cout << n << " ";
cout << endl;
} while (next_permutation(perm.begin(), perm.end()));
Custom Comparisons and Function Objects
// Custom sorting
vector words = {"apple", "banana", "cherry", "date"};
sort(words.begin(), words.end(),
[](const string& a, const string& b) {
return a.length() < b.length(); // Sort by length
});
// Using function objects
struct CaseInsensitive {
bool operator()(const string& a, const string& b) const {
return lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](char c1, char c2) { return tolower(c1) < tolower(c2); }
);
}
};
set caseInsensitiveSet = {"Apple", "banana", "apple"};
// Only one "apple" will be stored
Common Pitfalls
- Using invalid iterator ranges
- Modifying containers while iterating
- Using unsorted ranges with binary search algorithms
- Forgetting that algorithms don't change container size
Classes and Objects in C++
Classes are user-defined types that encapsulate data and functions. Objects are instances of classes. C++ supports object-oriented programming with features like encapsulation, inheritance, and polymorphism.
Basic Class Definition
#include <iostream>
#include <string>
using namespace std;
class Person {
private: // Access specifier - only class members can access
string name;
int age;
public: // Public interface
// Constructor
Person(string n, int a) : name(n), age(a) {}
// Member functions
void display() const {
cout << "Name: " << name << ", Age: " << age << endl;
}
// Getter functions
string getName() const { return name; }
int getAge() const { return age; }
// Setter functions
void setName(string n) { name = n; }
void setAge(int a) {
if (a >= 0) age = a; // Validation
}
};
int main() {
// Create objects
Person alice("Alice", 25);
Person bob("Bob", 30);
// Use objects
alice.display();
bob.display();
// Use getters and setters
alice.setAge(26);
cout << "Alice's new age: " << alice.getAge() << endl;
return 0;
}
Constructors and Destructors
class Rectangle {
private:
double width, height;
public:
// Default constructor
Rectangle() : width(1.0), height(1.0) {}
// Parameterized constructor
Rectangle(double w, double h) : width(w), height(h) {}
// Copy constructor
Rectangle(const Rectangle& other)
: width(other.width), height(other.height) {
cout << "Copy constructor called" << endl;
}
// Destructor
~Rectangle() {
cout << "Rectangle destroyed: " << width << "x" << height << endl;
}
// Member function
double area() const {
return width * height;
}
};
int main() {
Rectangle rect1; // Default constructor
Rectangle rect2(5.0, 3.0); // Parameterized constructor
Rectangle rect3 = rect2; // Copy constructor
cout << "Area: " << rect2.area() << endl;
return 0;
// Destructors called automatically when objects go out of scope
}
Static Members and Friends
class BankAccount {
private:
string owner;
double balance;
static int totalAccounts; // Static member - shared by all objects
public:
BankAccount(string o, double b) : owner(o), balance(b) {
totalAccounts++; // Increment when account created
}
~BankAccount() {
totalAccounts--; // Decrement when account destroyed
}
// Static member function
static int getTotalAccounts() {
return totalAccounts;
}
// Friend function - can access private members
friend void transfer(BankAccount& from, BankAccount& to, double amount);
};
// Define static member
int BankAccount::totalAccounts = 0;
// Friend function definition
void transfer(BankAccount& from, BankAccount& to, double amount) {
if (from.balance >= amount) {
from.balance -= amount;
to.balance += amount;
}
}
int main() {
BankAccount acc1("Alice", 1000);
BankAccount acc2("Bob", 500);
cout << "Total accounts: " << BankAccount::getTotalAccounts() << endl;
transfer(acc1, acc2, 200);
return 0;
}
Operator Overloading
class Complex {
private:
double real, imag;
public:
Complex(double r = 0, double i = 0) : real(r), imag(i) {}
// Overload + operator
Complex operator+(const Complex& other) const {
return Complex(real + other.real, imag + other.imag);
}
// Overload - operator
Complex operator-(const Complex& other) const {
return Complex(real - other.real, imag - other.imag);
}
// Overload << operator for output
friend ostream& operator<<(ostream& os, const Complex& c) {
os << c.real << " + " << c.imag << "i";
return os;
}
// Overload == operator
bool operator==(const Complex& other) const {
return real == other.real && imag == other.imag;
}
};
int main() {
Complex c1(2.0, 3.0);
Complex c2(1.0, 2.0);
Complex sum = c1 + c2;
Complex diff = c1 - c2;
cout << "c1: " << c1 << endl;
cout << "c2: " << c2 << endl;
cout << "Sum: " << sum << endl;
cout << "Difference: " << diff << endl;
cout << "Equal? " << (c1 == c2 ? "Yes" : "No") << endl;
return 0;
}
Common Pitfalls
- Forgetting to initialize members in constructors
- Not following rule of three/five for resource management
- Overusing friend functions breaks encapsulation
- Not making destructors virtual in base classes
Inheritance in C++
Inheritance allows a class to inherit properties and behaviors from another class. This promotes code reuse and establishes hierarchical relationships between classes.
Basic Inheritance
#include <iostream>
#include <string>
using namespace std;
// Base class
class Animal {
protected: // Accessible by derived classes
string name;
int age;
public:
Animal(string n, int a) : name(n), age(a) {}
void eat() {
cout << name << " is eating." << endl;
}
void sleep() {
cout << name << " is sleeping." << endl;
}
// Virtual function for polymorphism
virtual void makeSound() {
cout << name << " makes a generic sound." << endl;
}
// Virtual destructor for proper cleanup
virtual ~Animal() = default;
};
// Derived class
class Dog : public Animal { // Public inheritance
private:
string breed;
public:
Dog(string n, int a, string b) : Animal(n, a), breed(b) {}
// Override base class function
void makeSound() override {
cout << name << " barks: Woof! Woof!" << endl;
}
void fetch() {
cout << name << " is fetching the ball." << endl;
}
void displayInfo() {
cout << "Dog: " << name << ", Age: " << age
<< ", Breed: " << breed << endl;
}
};
// Another derived class
class Cat : public Animal {
public:
Cat(string n, int a) : Animal(n, a) {}
void makeSound() override {
cout << name << " meows: Meow! Meow!" << endl;
}
void climb() {
cout << name << " is climbing a tree." << endl;
}
};
int main() {
Dog myDog("Buddy", 3, "Golden Retriever");
Cat myCat("Whiskers", 2);
myDog.displayInfo();
myDog.eat();
myDog.makeSound();
myDog.fetch();
cout << endl;
myCat.eat();
myCat.makeSound();
myCat.climb();
return 0;
}
Types of Inheritance
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
// Public inheritance
class PublicDerived : public Base {
// publicVar remains public
// protectedVar remains protected
// privateVar is inaccessible
};
// Protected inheritance
class ProtectedDerived : protected Base {
// publicVar becomes protected
// protectedVar remains protected
// privateVar is inaccessible
};
// Private inheritance
class PrivateDerived : private Base {
// publicVar becomes private
// protectedVar becomes private
// privateVar is inaccessible
};
// Multiple inheritance
class A {
public:
void funcA() { cout << "Function A" << endl; }
};
class B {
public:
void funcB() { cout << "Function B" << endl; }
};
class MultipleDerived : public A, public B {
public:
void funcC() {
funcA(); // From A
funcB(); // From B
cout << "Function C" << endl;
}
};
Constructor and Destructor Order
class BaseClass {
public:
BaseClass() { cout << "BaseClass constructor" << endl; }
virtual ~BaseClass() { cout << "BaseClass destructor" << endl; }
};
class DerivedClass : public BaseClass {
public:
DerivedClass() { cout << "DerivedClass constructor" << endl; }
~DerivedClass() { cout << "DerivedClass destructor" << endl; }
};
class MostDerived : public DerivedClass {
public:
MostDerived() { cout << "MostDerived constructor" << endl; }
~MostDerived() { cout << "MostDerived destructor" << endl; }
};
int main() {
cout << "Creating MostDerived object:" << endl;
MostDerived obj;
cout << "Destroying MostDerived object:" << endl;
return 0;
}
/* Output:
Creating MostDerived object:
BaseClass constructor
DerivedClass constructor
MostDerived constructor
Destroying MostDerived object:
MostDerived destructor
DerivedClass destructor
BaseClass destructor
*/
Virtual Functions and Abstract Classes
// Abstract base class (cannot be instantiated)
class Shape {
protected:
double x, y;
public:
Shape(double xCoord, double yCoord) : x(xCoord), y(yCoord) {}
// Pure virtual function - makes class abstract
virtual double area() const = 0;
// Virtual function with implementation
virtual void display() const {
cout << "Shape at (" << x << ", " << y << ")" << endl;
}
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double x, double y, double r) : Shape(x, y), radius(r) {}
// Must implement pure virtual function
double area() const override {
return 3.14159 * radius * radius;
}
void display() const override {
cout << "Circle at (" << x << ", " << y
<< ") with radius " << radius << endl;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double x, double y, double w, double h)
: Shape(x, y), width(w), height(h) {}
double area() const override {
return width * height;
}
void display() const override {
cout << "Rectangle at (" << x << ", " << y
<< ") " << width << "x" << height << endl;
}
};
int main() {
// Shape s(1, 2); // Error: cannot instantiate abstract class
Circle circle(0, 0, 5);
Rectangle rect(2, 3, 4, 5);
circle.display();
cout << "Area: " << circle.area() << endl;
rect.display();
cout << "Area: " << rect.area() << endl;
// Using base class pointers
Shape* shapes[] = {&circle, &rect};
for (Shape* shape : shapes) {
shape->display();
cout << "Area: " << shape->area() << endl;
}
return 0;
}
Common Pitfalls
- Not making destructors virtual in polymorphic base classes
- Slicing problem when copying derived objects to base objects
- Diamond problem with multiple inheritance
- Forgetting to call base class constructors in derived classes
Polymorphism in C++
Polymorphism allows objects of different types to be treated as objects of a common base type. C++ supports compile-time (static) and runtime (dynamic) polymorphism.
Runtime Polymorphism (Virtual Functions)
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Animal {
public:
virtual void speak() const {
cout << "Some animal sound" << endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() const override {
cout << "Woof! Woof!" << endl;
}
};
class Cat : public Animal {
public:
void speak() const override {
cout << "Meow! Meow!" << endl;
}
};
class Bird : public Animal {
public:
void speak() const override {
cout << "Chirp! Chirp!" << endl;
}
};
int main() {
vector<unique_ptr<Animal>> animals;
animals.push_back(make_unique());
animals.push_back(make_unique());
animals.push_back(make_unique());
// Runtime polymorphism - calls appropriate derived class method
for (const auto& animal : animals) {
animal->speak();
}
return 0;
}
Compile-time Polymorphism (Templates)
// Function template - works with any type that supports + operator
template<typename T>
T add(const T& a, const T& b) {
return a + b;
}
// Class template
template<typename T>
class Container {
private:
T value;
public:
Container(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
void display() const {
cout << "Value: " << value << endl;
}
};
// Template specialization
template<>
class Container<string> {
private:
string value;
public:
Container(string v) : value(v) {}
string getValue() const { return value; }
void setValue(string v) { value = v; }
void display() const {
cout << "String: \"" << value << "\"" << endl;
}
void toUpperCase() {
for (char& c : value) {
c = toupper(c);
}
}
};
int main() {
// Function template instantiation
cout << "add(5, 3): " << add(5, 3) << endl;
cout << "add(2.5, 3.7): " << add(2.5, 3.7) << endl;
string s1 = "Hello", s2 = "World";
cout << "add(strings): " << add(s1, s2) << endl;
// Class template instantiation
Container<int> intContainer(42);
Container<double> doubleContainer(3.14);
Container<string> stringContainer("Hello Templates!");
intContainer.display();
doubleContainer.display();
stringContainer.display();
stringContainer.toUpperCase();
stringContainer.display();
return 0;
}
Function Overloading
// Compile-time polymorphism through function overloading
class Calculator {
public:
// Same function name, different parameters
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
string add(const string& a, const string& b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
};
// Operator overloading
class Vector2D {
public:
double x, y;
Vector2D(double x = 0, double y = 0) : x(x), y(y) {}
// Overload + operator
Vector2D operator+(const Vector2D& other) const {
return Vector2D(x + other.x, y + other.y);
}
// Overload - operator
Vector2D operator-(const Vector2D& other) const {
return Vector2D(x - other.x, y - other.y);
}
// Overload * operator (scalar multiplication)
Vector2D operator*(double scalar) const {
return Vector2D(x * scalar, y * scalar);
}
// Overload << operator for output
friend ostream& operator<<(ostream& os, const Vector2D& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
};
int main() {
Calculator calc;
cout << "calc.add(2, 3): " << calc.add(2, 3) << endl;
cout << "calc.add(2.5, 3.7): " << calc.add(2.5, 3.7) << endl;
cout << "calc.add(1, 2, 3): " << calc.add(1, 2, 3) << endl;
Vector2D v1(1, 2), v2(3, 4);
Vector2D sum = v1 + v2;
Vector2D diff = v1 - v2;
Vector2D scaled = v1 * 2.5;
cout << "v1: " << v1 << endl;
cout << "v2: " << v2 << endl;
cout << "v1 + v2: " << sum << endl;
cout << "v1 - v2: " << diff << endl;
cout << "v1 * 2.5: " << scaled << endl;
return 0;
}
Virtual Function Mechanism
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
void func3() { cout << "Base::func3" << endl; } // Non-virtual
};
class Derived : public Base {
public:
void func1() override { cout << "Derived::func1" << endl; }
void func2() override { cout << "Derived::func2" << endl; }
void func3() { cout << "Derived::func3" << endl; } // Hides Base::func3
};
void demonstratePolymorphism() {
Base* basePtr = new Derived();
// Virtual function calls - runtime resolution
basePtr->func1(); // Derived::func1
basePtr->func2(); // Derived::func2
// Non-virtual function call - compile-time resolution
basePtr->func3(); // Base::func3 (not overridden)
delete basePtr;
}
// Using typeid and dynamic_cast
void typeInformation(Base* ptr) {
cout << "Type: " << typeid(*ptr).name() << endl;
// Safe downcasting
if (Derived* derivedPtr = dynamic_cast<Derived*>(ptr)) {
cout << "Successfully cast to Derived" << endl;
derivedPtr->func3(); // Now calls Derived::func3
} else {
cout << "Cast to Derived failed" << endl;
}
}
Common Pitfalls
- Object slicing when copying derived objects to base objects
- Forgetting to make base class destructors virtual
- Hiding instead of overriding functions (different signatures)
- Using
dynamic_cast
without RTTI enabled
Advanced C++ Concepts
Advanced C++ features provide powerful tools for writing efficient, safe, and maintainable code. These include smart pointers, move semantics, lambda expressions, and more.
Smart Pointers
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Resource {
private:
string name;
public:
Resource(string n) : name(n) {
cout << "Resource " << name << " created" << endl;
}
~Resource() {
cout << "Resource " << name << " destroyed" << endl;
}
void use() {
cout << "Using resource " << name << endl;
}
};
void demonstrateSmartPointers() {
// unique_ptr - exclusive ownership
unique_ptr<Resource> res1 = make_unique<Resource>("Unique1");
res1->use();
// unique_ptr cannot be copied, only moved
unique_ptr<Resource> res2 = move(res1);
if (!res1) {
cout << "res1 is now empty" << endl;
}
res2->use();
// shared_ptr - shared ownership
shared_ptr<Resource> shared1 = make_shared<Resource>("Shared1");
{
shared_ptr<Resource> shared2 = shared1; // Reference count increases
cout << "Reference count: " << shared1.use_count() << endl;
shared2->use();
} // shared2 destroyed, reference count decreases
cout << "Reference count: " << shared1.use_count() << endl;
shared1->use();
// weak_ptr - non-owning reference
weak_ptr<Resource> weak = shared1;
if (auto locked = weak.lock()) {
cout << "Resource still exists: ";
locked->use();
} else {
cout << "Resource has been destroyed" << endl;
}
}
int main() {
demonstrateSmartPointers();
cout << "All smart pointers automatically cleaned up" << endl;
return 0;
}
Move Semantics and Rvalue References
class Buffer {
private:
int* data;
size_t size;
public:
// Constructor
Buffer(size_t s) : size(s), data(new int[s]) {
cout << "Buffer constructed with size " << size << endl;
}
// Copy constructor
Buffer(const Buffer& other) : size(other.size), data(new int[other.size]) {
copy(other.data, other.data + size, data);
cout << "Buffer copy constructed" << endl;
}
// Move constructor
Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
other.data = nullptr;
other.size = 0;
cout << "Buffer move constructed" << endl;
}
// Copy assignment
Buffer& operator=(const Buffer& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
copy(other.data, other.data + size, data);
cout << "Buffer copy assigned" << endl;
}
return *this;
}
// Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
cout << "Buffer move assigned" << endl;
}
return *this;
}
~Buffer() {
delete[] data;
cout << "Buffer destroyed" << endl;
}
};
Buffer createBuffer() {
return Buffer(100); // Return value optimization (RVO)
}
int main() {
Buffer b1(50); // Normal construction
Buffer b2 = b1; // Copy construction
Buffer b3 = move(b1); // Move construction
Buffer b4 = createBuffer(); // May use move or RVO
b2 = b3; // Copy assignment
b3 = createBuffer(); // Move assignment
return 0;
}
Lambda Expressions
#include <algorithm>
#include <vector>
#include <functional>
void demonstrateLambdas() {
vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Basic lambda
auto simpleLambda = []() { cout << "Hello Lambda!" << endl; };
simpleLambda();
// Lambda with parameters
auto adder = [](int a, int b) { return a + b; };
cout << "5 + 3 = " << adder(5, 3) << endl;
// Lambda with capture
int factor = 2;
auto multiplier = [factor](int x) { return x * factor; };
cout << "5 * " << factor << " = " << multiplier(5) << endl;
// Mutable lambda (can modify captured values)
auto counter = [count = 0]() mutable { return ++count; };
cout << "Counter: " << counter() << ", " << counter() << ", " << counter() << endl;
// Using lambdas with algorithms
cout << "Even numbers: ";
for_each(numbers.begin(), numbers.end(), [](int n) {
if (n % 2 == 0) cout << n << " ";
});
cout << endl;
// Lambda as predicate
auto isPrime = [](int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
};
auto primeIt = find_if(numbers.begin(), numbers.end(), isPrime);
if (primeIt != numbers.end()) {
cout << "First prime: " << *primeIt << endl;
}
}
// Lambda with std::function
function<int(int)> createFibonacci() {
function<int(int)> fib;
fib = [&fib](int n) -> int {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
};
return fib;
}
Exception Handling
#include <stdexcept>
#include <fstream>
class FileError : public runtime_error {
public:
FileError(const string& message) : runtime_error(message) {}
};
void readFile(const string& filename) {
ifstream file(filename);
if (!file.is_open()) {
throw FileError("Cannot open file: " + filename);
}
string line;
while (getline(file, line)) {
if (line.empty()) {
throw invalid_argument("Empty line found in file");
}
cout << line << endl;
}
}
void demonstrateExceptions() {
try {
readFile("nonexistent.txt");
}
catch (const FileError& e) {
cout << "File error: " << e.what() << endl;
}
catch (const invalid_argument& e) {
cout << "Invalid argument: " << e.what() << endl;
}
catch (const exception& e) {
cout << "Standard exception: " << e.what() << endl;
}
catch (...) {
cout << "Unknown exception occurred" << endl;
}
// Exception-safe resource management
try {
unique_ptr<int> ptr = make_unique<int>(42);
// Resource automatically cleaned up even if exception thrown
throw runtime_error("Something went wrong");
}
catch (const exception& e) {
cout << "Exception caught: " << e.what() << endl;
}
}
Type Traits and SFINAE
#include <type_traits>
// Using type traits
template<typename T>
void process(const T& value) {
if constexpr (is_pointer<T>::value) {
cout << "Pointer to: " << *value << endl;
} else if constexpr (is_arithmetic<T>::value) {
cout << "Arithmetic value: " << value << endl;
} else {
cout << "Other type" << endl;
}
}
// SFINAE (Substitution Failure Is Not An Error)
template<typename T>
typename enable_if<is_integral<T>::value, T>::type
increment(const T& value) {
return value + 1;
}
template<typename T>
typename enable_if<is_floating_point<T>::value, T>::type
increment(const T& value) {
return value + 0.1;
}
// Concepts (C++20)
/*
template<typename T>
concept Arithmetic = is_arithmetic_v<T>;
template<Arithmetic T>
T multiply(T a, T b) {
return a * b;
}
*/
int main() {
int x = 5;
double y = 3.14;
string z = "hello";
process(x); // Arithmetic value: 5
process(&x); // Pointer to: 5
process(z); // Other type
cout << "Increment int: " << increment(5) << endl;
cout << "Increment double: " << increment(3.14) << endl;
return 0;
}
Common Pitfalls
- Not using
noexcept
for move operations - Throwing exceptions from destructors
- Capturing dangling references in lambdas
- Forgetting to handle all exception cases
Vectors in C++
Vectors are dynamic arrays that can grow and shrink in size. They are part of the Standard Template Library (STL) and provide many convenient operations.
Creating and Using Vectors
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Different ways to create vectors
vector<int> numbers; // Empty vector
vector<int> primes = {2, 3, 5, 7, 11}; // Initialized
vector<string> names(3, "Unknown"); // 3 elements, all "Unknown"
// Adding elements
numbers.push_back(10);
numbers.push_back(20);
numbers.push_back(30);
// Accessing elements
cout << "First: " << numbers[0] << endl;
cout << "Second: " << numbers.at(1) << endl; // Bounds-checked
// Size and capacity
cout << "Size: " << numbers.size() << endl;
cout << "Capacity: " << numbers.capacity() << endl;
cout << "Empty? " << (numbers.empty() ? "Yes" : "No") << endl;
return 0;
}
Vector Operations
vector<int> vec = {1, 2, 3, 4, 5};
// Iterating
cout << "Elements: ";
for (int i = 0; i < vec.size(); i++) {
cout << vec[i] << " ";
}
cout << endl;
// Range-based for loop (C++11)
cout << "Using range-based for: ";
for (int num : vec) {
cout << num << " ";
}
cout << endl;
// Modifying elements
vec[0] = 100;
vec.at(1) = 200;
// Inserting and erasing
vec.insert(vec.begin() + 2, 300); // Insert at position 2
vec.erase(vec.begin() + 3); // Remove element at position 3
// Clearing
vec.clear(); // Remove all elements
Vector vs Array
// Arrays: Fixed size, stack allocation
int arr[5] = {1, 2, 3, 4, 5};
// arr[5] = 6; // Error - fixed size
// Vectors: Dynamic size, heap allocation
vector<int> vec = {1, 2, 3, 4, 5};
vec.push_back(6); // OK - can grow
vec.pop_back(); // Remove last element
Common Pitfalls
- Accessing elements with
[]
doesn't do bounds checking - Use
at()
for bounds-checked access - Vectors can be less efficient than arrays for very small, fixed-size data
- Inserting/erasing in the middle is O(n) operation
Comments
Post a Comment
By posting a comment, you agree to keep discussions respectful and relevant. Inappropriate or offensive content may be removed at the moderator’s discretion.