C & C++ Intermediate Course
Level up your C and C++ skills. Switch between languages using the buttons below — each has 20 lessons, 3 projects, and a capstone.
C Intermediate Lessons
File I/O (fopen/fclose)
C reads and writes files using FILE pointers. fopen opens a file in a mode (read, write, append), and fclose closes it. Always check if fopen returns NULL before using the file.
#include <stdio.h>
int main() {
FILE *fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("Error opening file\n");
return 1;
}
fprintf(fp, "Hello File!\n");
fclose(fp);
printf("File written\n");
return 0;
}
Pointers & Arrays Relationship
An array name decays to a pointer to its first element. array[i] and *(array + i) are equivalent. This dual nature lets you pass arrays to functions efficiently without copying them.
#include <stdio.h>
int main() {
int nums[] = {10, 20, 30};
int *ptr = nums;
printf("%d\n", nums[1]);
printf("%d\n", *(ptr + 1));
printf("%d\n", ptr[2]);
return 0;
}
Pointer Arithmetic
Adding 1 to a pointer moves it by the size of its type, not one byte. This makes iterating arrays with pointers natural. Pointer arithmetic is the foundation of C's speed and power.
#include <stdio.h>
int main() {
int nums[] = {5, 10, 15, 20};
int *p = nums;
for (int i = 0; i < 4; i++) {
printf("%d ", *p);
p++;
}
printf("\n");
return 0;
}
Function Pointers
A function pointer stores the address of a function, letting you call different functions dynamically. This powers callbacks and flexible designs where behavior is chosen at runtime.
#include <stdio.h>
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int main() {
int (*op)(int, int);
op = add;
printf("Add: %d\n", op(5, 3));
op = mul;
printf("Mul: %d\n", op(5, 3));
return 0;
}
Dynamic Memory (calloc/realloc)
calloc allocates and zeroes memory, while realloc resizes an existing allocation. Together with malloc and free, they give full control over runtime-sized data. Always free to prevent leaks.
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int*)calloc(3, sizeof(int));
arr[0] = 10;
arr[1] = 20;
arr = (int*)realloc(arr, 5 * sizeof(int));
arr[4] = 99;
printf("arr[0]=%d, arr[1]=%d, arr[4]=%d\n", arr[0], arr[1], arr[4]);
free(arr);
return 0;
}
2D Arrays
A 2D array is an array of arrays. It is declared with two sizes and accessed with two indices. 2D arrays model grids, matrices, and tables.
#include <stdio.h>
int main() {
int grid[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
printf("Center: %d\n", grid[1][1]);
for (int i = 0; i < 3; i++) {
printf("%d ", grid[i][i]);
}
printf("\n");
return 0;
}
String Functions Deep (strcpy/strcmp/strcat)
string.h provides essential string functions. strcpy copies, strcmp compares (0 means equal), and strcat concatenates. These operate on null-terminated char arrays.
#include <stdio.h>
#include <string.h>
int main() {
char dest[30] = "Hello";
char src[] = " World";
strcat(dest, src);
printf("After strcat: %s\n", dest);
printf("Compare: %d\n", strcmp(dest, "Hello World"));
strcpy(dest, "Reset");
printf("After strcpy: %s\n", dest);
return 0;
}
Command Line Arguments
main can accept arguments from the command line using argc (argument count) and argv (argument values). argv[0] is the program name, and argv[1] onward are user inputs.
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("Argument count: %d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
return 0;
}
Structs & Pointers
Pointers to structs use -> to access members, equivalent to (*ptr).member. Passing struct pointers to functions allows modification without copying the whole struct.
#include <stdio.h>
struct Point {
int x;
int y;
};
void move(struct Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
int main() {
struct Point pt = {3, 4};
move(&pt, 2, -1);
printf("Point: (%d, %d)\n", pt.x, pt.y);
return 0;
}
Linked Lists
A linked list is a chain of nodes, each storing data and a pointer to the next node. Unlike arrays, lists grow and shrink easily. Insertion and deletion are O(1) at the head.
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
int main() {
struct Node *head = NULL;
struct Node *n1 = (struct Node*)malloc(sizeof(struct Node));
n1->data = 10; n1->next = NULL;
head = n1;
struct Node *n2 = (struct Node*)malloc(sizeof(struct Node));
n2->data = 20; n2->next = NULL;
n1->next = n2;
struct Node *cur = head;
while (cur != NULL) {
printf("%d -> ", cur->data);
cur = cur->next;
}
printf("NULL\n");
return 0;
}
Unions
A union stores only one of its members at a time, sharing the same memory. Its size is the largest member. Unions save memory when only one value is needed at a time.
#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data d;
d.i = 42;
printf("Integer: %d\n", d.i);
d.f = 3.14;
printf("Float: %.2f\n", d.f);
return 0;
}
Bitwise Operators
Bitwise operators work on individual bits: & (AND), | (OR), ^ (XOR), ~ (NOT), << (left shift), >> (right shift). They are used for flags, masks, and low-level manipulation.
#include <stdio.h>
int main() {
int a = 5; // 0101
int b = 3; // 0011
printf("AND: %d\n", a & b);
printf("OR: %d\n", a | b);
printf("XOR: %d\n", a ^ b);
printf("Left shift: %d\n", a << 1);
return 0;
}
Macros vs Functions
Macros (#define) are preprocessor text substitutions, while functions are real calls. Macros are faster (no call overhead) but can have side effects. Functions are safer and type-checked.
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main() {
printf("Square: %d\n", SQUARE(5));
printf("Max: %d\n", MAX(10, 25));
return 0;
}
Header Files & Multi-file Programs
Header files (.h) declare functions and types shared across .c files. #include brings declarations in. This splits programs into modules and enables code reuse.
// math.h
#ifndef MATH_H
#define MATH_H
int add(int a, int b);
#endif
// math.c
#include "math.h"
int add(int a, int b) {
return a + b;
}
// main.c
#include <stdio.h>
#include "math.h"
int main() {
printf("%d\n", add(5, 3));
return 0;
}
Recursion
Recursion is when a function calls itself. It is elegant for problems like factorials, Fibonacci, and tree traversal. Every recursive function needs a base case to stop.
#include <stdio.h>
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
printf("Factorial of 5: %d\n", factorial(5));
return 0;
}
Sorting (Bubble Sort)
Bubble sort repeatedly steps through the array, swapping adjacent elements that are out of order. Larger values 'bubble' to the end. It is simple but O(n²), good for learning and small arrays.
#include <stdio.h>
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int nums[] = {5, 2, 8, 1, 9};
bubbleSort(nums, 5);
for (int i = 0; i < 5; i++) printf("%d ", nums[i]);
printf("\n");
return 0;
}
Searching (Linear & Binary)
Linear search checks each element until found — works on unsorted arrays, O(n). Binary search repeatedly halves a sorted array — O(log n), much faster for large data.
#include <stdio.h>
int binarySearch(int arr[], int low, int high, int target) {
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
int main() {
int nums[] = {1, 3, 5, 7, 9};
printf("Index of 7: %d\n", binarySearch(nums, 0, 4, 7));
printf("Index of 6: %d\n", binarySearch(nums, 0, 4, 6));
return 0;
}
Error Handling & errno
C reports errors through return values and the global errno variable. strerror() converts an errno code to a readable message. Always check function return values for failure.
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main() {
FILE *fp = fopen("missing.txt", "r");
if (fp == NULL) {
printf("Error: %s\n", strerror(errno));
return 1;
}
fclose(fp);
return 0;
}
Storage Classes Deep (static/extern/register)
Storage classes control lifetime and visibility. static in a function preserves value between calls; static global limits scope to the file. extern declares a variable defined elsewhere. register hints the compiler to use a CPU register.
#include <stdio.h>
void counter() {
static int count = 0;
count++;
printf("Count: %d\n", count);
}
int main() {
counter();
counter();
counter();
return 0;
}
C Intermediate Recap + Projects
You now know file I/O, pointers and arrays, pointer arithmetic, function pointers, dynamic memory, 2D arrays, string functions, command-line args, struct pointers, linked lists, unions, bitwise ops, macros, header files, recursion, sorting, searching, error handling, and storage classes. Apply them in projects like a Contact Book or a Simple Database.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Contact {
char name[50];
char phone[20];
};
int main() {
struct Contact contacts[3];
int count = 0;
strcpy(contacts[0].name, "Ali");
strcpy(contacts[0].phone, "0300-1234567");
count++;
strcpy(contacts[1].name, "Sara");
strcpy(contacts[1].phone, "0301-7654321");
count++;
for (int i = 0; i < count; i++) {
printf("%s - %s\n", contacts[i].name, contacts[i].phone);
}
return 0;
}
C Intermediate Projects
About this project
Build a singly linked list with insert, display, and search using dynamic memory and struct pointers.
What you'll practice
Topics used
Linked Lists, Structs & Pointers, Dynamic Memory, Pointer Arithmetic
#include <stdio.h>
#include <stdlib.h>
struct Node {
int data;
struct Node *next;
};
struct Node* insertHead(struct Node *head, int value) {
struct Node *n = (struct Node*)malloc(sizeof(struct Node));
n->data = value;
n->next = head;
return n;
}
void display(struct Node *head) {
struct Node *cur = head;
while (cur != NULL) {
printf("%d -> ", cur->data);
cur = cur->next;
}
printf("NULL\n");
}
int main() {
struct Node *head = NULL;
head = insertHead(head, 30);
head = insertHead(head, 20);
head = insertHead(head, 10);
display(head);
return 0;
}
About this project
Sort an array with bubble sort, then search using binary search.
What you'll practice
Topics used
Sorting, Searching, Function Pointers, Arrays
#include <stdio.h>
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n - 1; i++)
for (int j = 0; j < n - i - 1; j++)
if (arr[j] > arr[j + 1]) {
int t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
int binarySearch(int arr[], int low, int high, int target) {
while (low <= high) {
int mid = low + (high - low) / 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
int main() {
int nums[] = {9, 2, 7, 1, 5};
int size = 5;
bubbleSort(nums, size);
for (int i = 0; i < size; i++) printf("%d ", nums[i]);
printf("\n");
printf("Index of 7: %d\n", binarySearch(nums, 0, size - 1, 7));
return 0;
}
About this project
Split a sentence into words using strtok and count them — practicing string functions and pointers.
What you'll practice
Topics used
String Functions, Pointers & Arrays, Pointer Arithmetic
#include <stdio.h>
#include <string.h>
int main() {
char sentence[] = "learn C programming today";
int count = 0;
char *token = strtok(sentence, " ");
while (token != NULL) {
printf("%s\n", token);
count++;
token = strtok(NULL, " ");
}
printf("Words: %d\n", count);
return 0;
}
C Capstone Project
About this project
Save and load contacts using structs, dynamic memory, and error handling with file I/O.
What you'll practice
Topics used
File I/O, Structs, String Functions, Dynamic Memory, Error Handling (errno)
#include <stdio.h>
#include <string.h>
#include <errno.h>
struct Contact {
char name[50];
char phone[20];
};
int main() {
struct Contact contacts[2] = {
{"Ali", "0300-111"},
{"Sara", "0301-222"}
};
FILE *fp = fopen("contacts.txt", "w");
if (fp == NULL) {
printf("Error: %s\n", strerror(errno));
return 1;
}
for (int i = 0; i < 2; i++) {
fprintf(fp, "%s %s\n", contacts[i].name, contacts[i].phone);
}
fclose(fp);
printf("Contacts saved to contacts.txt\n");
return 0;
}
C++ Intermediate Lessons
References Deep Dive
A reference is an alias for an existing variable, declared with &. Unlike pointers, references cannot be null and cannot be reassigned. They provide pass-by-reference and are the basis for modern C++ patterns like return-by-reference.
#include <iostream>
void increment(int &num) {
num++;
}
int main() {
int x = 10;
increment(x);
std::cout << "x: " << x << std::endl;
return 0;
}
Const Correctness
const protects data from modification. const references pass data efficiently without allowing changes. const member functions promise not to modify the object. Const correctness catches bugs at compile time.
#include <iostream>
#include <string>
class Person {
private:
std::string name;
public:
Person(std::string n) : name(n) {}
std::string getName() const {
return name;
}
};
int main() {
const Person p("Ali");
std::cout << p.getName() << std::endl;
return 0;
}
Copy Constructor & Copy Assignment
The copy constructor creates a new object from an existing one, and the copy assignment operator copies between existing objects. They control how object state is duplicated, especially important with dynamic resources.
#include <iostream>
#include <string>
class Box {
public:
std::string label;
Box(std::string l) : label(l) {
std::cout << "Constructor: " << label << std::endl;
}
Box(const Box& other) : label(other.label) {
std::cout << "Copy: " << label << std::endl;
}
};
int main() {
Box a("First");
Box b = a; // copy constructor
return 0;
}
Move Semantics & rvalue References
Move semantics transfer resources instead of copying them, using rvalue references (&&). std::move enables this. Moving avoids expensive deep copies, especially for large containers.
#include <iostream>
#include <string>
int main() {
std::string source = "Hello";
std::string dest = std::move(source);
std::cout << "Dest: " << dest << std::endl;
std::cout << "Source empty: " << (source.empty() ? "yes" : "no") << std::endl;
return 0;
}
Operator Overloading
Operator overloading lets you define how operators (+, ==, <<, etc.) work for your classes. This makes user-defined types behave like built-in types and produces readable code.
#include <iostream>
class Point {
public:
int x, y;
Point(int a, int b) : x(a), y(b) {}
Point operator+(const Point& other) const {
return Point(x + other.x, y + other.y);
}
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};
int main() {
Point p1(1, 2);
Point p2(3, 4);
Point p3 = p1 + p2;
std::cout << "p3: (" << p3.x << ", " << p3.y << ")" << std::endl;
std::cout << "Equal: " << (p1 == p2) << std::endl;
return 0;
}
Friend Functions & Classes
A friend function or class can access private members of another class. The friend keyword grants special access. It is used for operator overloading and tight coupling between related types.
#include <iostream>
class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
friend double area(const Circle& c);
};
double area(const Circle& c) {
return 3.14159 * c.radius * c.radius;
}
int main() {
Circle c(2);
std::cout << "Area: " << area(c) << std::endl;
return 0;
}
Smart Pointers (unique_ptr/shared_ptr)
Smart pointers automatically manage memory. unique_ptr owns a resource exclusively and deletes it on destruction. shared_ptr uses reference counting to share ownership. They prevent memory leaks.
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> up = std::make_unique<int>(42);
std::shared_ptr<int> sp = std::make_shared<int>(99);
std::cout << "unique_ptr: " << *up << std::endl;
std::cout << "shared_ptr: " << *sp << std::endl;
return 0;
}
RAII (Resource Acquisition Is Initialization)
RAII ties resource lifetime to object lifetime. Resources are acquired in constructors and released in destructors. When an object goes out of scope, its destructor runs, guaranteeing cleanup even on exceptions.
#include <iostream>
class FileGuard {
public:
FileGuard() {
std::cout << "Resource acquired" << std::endl;
}
~FileGuard() {
std::cout << "Resource released" << std::endl;
}
};
int main() {
{
FileGuard guard;
}
std::cout << "Out of scope" << std::endl;
return 0;
}
STL Containers Overview
The Standard Template Library provides reusable containers: vector (dynamic array), list (doubly-linked), map (key-value), set (unique sorted), and more. Each has different performance trade-offs for different operations.
#include <iostream>
#include <vector>
#include <set>
#include <map>
int main() {
std::vector<int> vec = {3, 1, 2};
std::set<int> s = {3, 1, 2, 2};
std::map<std::string, int> ages;
ages["Ali"] = 25;
std::cout << "Vector size: " << vec.size() << std::endl;
std::cout << "Set size: " << s.size() << std::endl;
std::cout << "Ali's age: " << ages["Ali"] << std::endl;
return 0;
}
STL Iterators
Iterators generalize traversal across containers. begin() returns an iterator to the first element, end() to one past the last. They let algorithms work with any container uniformly.
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {10, 20, 30, 40};
for (auto it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
STL Algorithms
The <algorithm> header provides ready-made operations: sort, find, count, reverse, and more. They work with iterators, so one algorithm works on many container types.
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> nums = {5, 2, 8, 1, 9};
std::sort(nums.begin(), nums.end());
int count8 = std::count(nums.begin(), nums.end(), 8);
int maxVal = *std::max_element(nums.begin(), nums.end());
for (int n : nums) std::cout << n << " ";
std::cout << "\nCount of 8: " << count8 << std::endl;
std::cout << "Max: " << maxVal << std::endl;
return 0;
}
Function Templates
Function templates let one function work with many types. The type parameter T is deduced at compile time. Templates provide generic programming without duplicating code.
#include <iostream>
template <typename T>
T maxValue(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << maxValue(5, 10) << std::endl;
std::cout << maxValue(3.5, 2.5) << std::endl;
std::cout << maxValue('a', 'z') << std::endl;
return 0;
}
Class Templates
Class templates let a class work with any type, like std::vector<T>. The type parameter is specified when instantiating the object. This is how generic containers are built.
#include <iostream>
template <typename T>
class Box {
private:
T value;
public:
Box(T v) : value(v) {}
T get() const {
return value;
}
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.get() << std::endl;
std::cout << strBox.get() << std::endl;
return 0;
}
Custom Exception Classes
You can define your own exception classes by inheriting from std::exception and overriding what(). Custom exceptions make errors domain-specific and catchable by type.
#include <iostream>
#include <exception>
class InsufficientFundsException : public std::exception {
public:
const char* what() const noexcept override {
return "Insufficient funds";
}
};
void withdraw(double balance, double amount) {
if (amount > balance) {
throw InsufficientFundsException();
}
}
int main() {
try {
withdraw(50, 100);
} catch (const InsufficientFundsException& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
Virtual Destructors
A base class destructor should be virtual when deleting derived objects through a base pointer. Otherwise, only the base destructor runs, leaking derived resources.
#include <iostream>
class Base {
public:
virtual ~Base() {
std::cout << "Base destroyed" << std::endl;
}
};
class Derived : public Base {
public:
~Derived() {
std::cout << "Derived destroyed" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr;
return 0;
}
Abstract Classes & Pure Virtual
An abstract class has at least one pure virtual function (declared with = 0). It cannot be instantiated and serves as an interface. Derived classes must implement all pure virtual functions.
#include <iostream>
class Shape {
public:
virtual double area() const = 0;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
};
int main() {
Circle c(2);
std::cout << "Area: " << c.area() << std::endl;
return 0;
}
Namespaces Deep Dive
Namespaces group related code and avoid name collisions. You can nest namespaces, alias them, and use using-declarations to bring specific names into scope. The std namespace contains all standard library names.
#include <iostream>
namespace math {
int add(int a, int b) { return a + b; }
}
namespace util {
int add(int a, int b) { return a * b; }
}
int main() {
std::cout << "math::add: " << math::add(5, 3) << std::endl;
std::cout << "util::add: " << util::add(5, 3) << std::endl;
return 0;
}
String Streams (istringstream/ostringstream)
String streams treat strings like I/O streams. istringstream reads from a string, ostringstream builds one, and stringstream does both. They are useful for parsing and formatting.
#include <iostream>
#include <sstream>
int main() {
std::istringstream input("42 3.14 hello");
int i;
double d;
std::string s;
input >> i >> d >> s;
std::ostringstream output;
output << "Parsed: " << i << ", " << d << ", " << s;
std::cout << output.str() << std::endl;
return 0;
}
Lambda Captures Deep
Lambdas capture outside variables using []. [=] captures by value, [&] by reference, and you can capture specific variables explicitly. Captures let lambdas use surrounding scope data.
#include <iostream>
int main() {
int base = 10;
auto addBase = [base](int x) { return base + x; };
auto increment = [&base]() { base++; };
increment();
std::cout << "addBase(5): " << addBase(5) << std::endl;
std::cout << "base after increment: " << base << std::endl;
return 0;
}
C++ Intermediate Recap + Projects
You now know references, const correctness, copy and move semantics, operator overloading, friends, smart pointers, RAII, STL containers, iterators, algorithms, templates, exceptions, virtual destructors, abstract classes, namespaces, string streams, and lambda captures. Apply them in projects like a Smart Contact Manager or a Template-based Stack.
#include <iostream>
#include <vector>
#include <memory>
#include <string>
class Contact {
public:
std::string name;
std::string phone;
Contact(std::string n, std::string p) : name(n), phone(p) {}
};
int main() {
std::vector<std::unique_ptr<Contact>> contacts;
contacts.push_back(std::make_unique<Contact>("Ali", "0300-111"));
contacts.push_back(std::make_unique<Contact>("Sara", "0301-222"));
for (const auto& c : contacts) {
std::cout << c->name << " - " << c->phone << std::endl;
}
return 0;
}
C++ Intermediate Projects
About this project
Build a resource manager using smart pointers and RAII — memory is automatically freed.
What you'll practice
Topics used
Smart Pointers, RAII, Move Semantics, Classes
#include <iostream>
#include <memory>
#include <vector>
#include <string>
class Resource {
public:
std::string name;
Resource(std::string n) : name(n) {
std::cout << "Acquired: " << name << std::endl;
}
~Resource() {
std::cout << "Released: " << name << std::endl;
}
};
int main() {
std::vector<std::unique_ptr<Resource>> resources;
resources.push_back(std::make_unique<Resource>("DB Connection"));
resources.push_back(std::make_unique<Resource>("File Handle"));
for (const auto& r : resources) {
std::cout << "Using: " << r->name << std::endl;
}
return 0;
}
About this project
Build a generic stack using class templates and overloaded operators.
What you'll practice
Topics used
Class Templates, Operator Overloading, STL Containers
#include <iostream>
#include <vector>
template <typename T>
class Stack {
private:
std::vector<T> items;
public:
void operator+=(T item) { items.push_back(item); }
T top() const { return items.back(); }
void pop() { if (!items.empty()) items.pop_back(); }
bool empty() const { return items.empty(); }
};
int main() {
Stack<int> s;
s += 10;
s += 20;
s += 30;
while (!s.empty()) {
std::cout << s.top() << " ";
s.pop();
}
std::cout << std::endl;
return 0;
}
About this project
Parse numbers using string streams and handle errors with a custom exception class.
What you'll practice
Topics used
String Streams, Custom Exceptions, Lambda Captures
#include <iostream>
#include <sstream>
#include <exception>
class ParseException : public std::exception {
public:
const char* what() const noexcept override {
return "Invalid number format";
}
};
int main() {
std::istringstream input("42 3.14 hello");
try {
int i;
double d;
input >> i >> d;
if (input.fail()) throw ParseException();
auto sum = [](int a, double b) { return a + b; };
std::cout << "Sum: " << sum(i, d) << std::endl;
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}
C++ Capstone Project
About this project
Build a calculator that parses expressions using string streams, overloaded operators, custom exceptions, and lambdas.
What you'll practice
Topics used
String Streams, Custom Exceptions, Operator Overloading, Lambda Captures
#include <iostream>
#include <sstream>
#include <string>
#include <exception>
class DivideByZeroException : public std::exception {
public:
const char* what() const noexcept override {
return "Cannot divide by zero";
}
};
int main() {
std::istringstream input("10 / 4 + 2");
double a, b;
char op1, op2;
std::string extra;
input >> a >> op1 >> b >> op2 >> extra;
try {
double result;
if (op1 == '/') {
if (b == 0) throw DivideByZeroException();
result = a / b;
}
auto addTwo = [result](double x) { return result + x; };
if (op2 == '+') {
result = addTwo(std::stod(extra));
}
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cout << "Error: " << e.what() << std::endl;
}
return 0;
}