Initial Course

This commit is contained in:
David Gil de Gómez Pérez
2026-08-11 17:40:36 +03:00
commit 9aed1d1d86
202 changed files with 3420 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Build directories
build/
cmake-build-*/
out/
# CLion / IDE
.idea/
*.iml
.vscode/
# Compiled artifacts
*.o
*.obj
*.a
*.so
*.dylib
*.exe
# Test artifacts
Testing/
*.gcno
*.gcda
# OS
.DS_Store
Thumbs.db
+36
View File
@@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.20)
project(
modern_cpp_course
VERSION 1.0.0
DESCRIPTION "Modern C++ course from basics to C++26"
LANGUAGES CXX
)
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
option(COURSE_BUILD_TESTS "Build Google Test exercise targets" ON)
option(COURSE_WARNINGS_AS_ERRORS "Treat compiler warnings as errors" OFF)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(CourseOptions)
include(CourseExercise)
if(COURSE_BUILD_TESTS)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.15.2
)
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
include(GoogleTest)
endif()
add_subdirectory(modules)
+119
View File
@@ -0,0 +1,119 @@
# Modern C++ Course (Basics → C++26)
A hands-on C++ course with **56 Google Test exercises** organized into **14 modules**, from your first functions to modern standard-library features.
## Requirements
- **CMake** 3.20+
- **C++ compiler** with C++23 support for later modules
- Apple Clang 15+, GCC 13+, or MSVC 19.34+ recommended
- **CLion** (recommended) or any CMake-aware IDE
- Internet access on first configure (Google Test is fetched automatically)
## Quick Start (CLion)
1. Open this folder in CLion (`File → Open…`).
2. CLion detects `CMakeLists.txt` and configures automatically.
3. Pick an exercise target (e.g. `01_fundamentals_hello_world`) in the run configuration dropdown.
4. Implement the TODOs in the matching `src/*.cpp` file.
5. Run the test target until all assertions pass.
## Quick Start (Terminal)
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failure
```
Run a single exercise:
```bash
cmake --build build --target 01_fundamentals_hello_world
./build/modules/01_fundamentals/01_fundamentals_hello_world
```
## Project Structure
```
cppc/
├── CMakeLists.txt # Root project + Google Test
├── cmake/
│ ├── CourseExercise.cmake # add_course_exercise() helper
│ └── CourseOptions.cmake # Warnings and standards
└── modules/
├── 01_fundamentals/ # Types, operators, I/O
├── 02_control_flow/ # if, switch, loops
├── 03_functions/ # Functions and recursion
├── 04_arrays_and_strings/ # Arrays, string, vector
├── 05_pointers_and_references/
├── 06_oop_basics/ # Classes, inheritance
├── 07_stl_containers/
├── 08_stl_algorithms/
├── 09_cpp11/ # C++11 features
├── 10_cpp14/
├── 11_cpp17/
├── 12_cpp20/ # Concepts, ranges, <=>
├── 13_cpp23/ # expected, mdspan, format
└── 14_cpp26/ # Frontier / C++26 direction
```
Each module contains:
- `README.md` — learning goals and exercise list
- `include/` — function and class declarations (your contract)
- `src/`**your implementation** (start here)
- `test/` — Google Test files (do not edit unless extending)
## How to Study
1. Read the module `README.md`.
2. Open the exercise header to understand the required API.
3. Implement TODOs in the corresponding `src/` file.
4. Run that exercises test target in CLion or via CTest.
5. Move to the next exercise only when tests pass.
## Course Map
| Module | Topic | Exercises | C++ Standard |
|--------|-------|-----------|--------------|
| 01 | Fundamentals | 4 | C++17 |
| 02 | Control flow | 4 | C++17 |
| 03 | Functions | 4 | C++17 |
| 04 | Arrays & strings | 4 | C++17 |
| 05 | Pointers & references | 4 | C++17 |
| 06 | OOP basics | 4 | C++17 |
| 07 | STL containers | 4 | C++17 |
| 08 | STL algorithms | 4 | C++17 |
| 09 | C++11 | 4 | C++11 |
| 10 | C++14 | 4 | C++14 |
| 11 | C++17 | 4 | C++17 |
| 12 | C++20 | 4 | C++20 |
| 13 | C++23 | 4 | C++23 |
| 14 | C++26 frontier | 4 | C++23+ |
## CMake Options
| Option | Default | Description |
|--------|---------|-------------|
| `COURSE_BUILD_TESTS` | ON | Build Google Test exercise targets |
| `COURSE_WARNINGS_AS_ERRORS` | OFF | Treat warnings as errors |
Example:
```bash
cmake -S . -B build -DCOURSE_WARNINGS_AS_ERRORS=ON
```
## CLion Tips
- Use **Run | Run…** and filter by module prefix (e.g. `12_cpp20`).
- Enable **Google Test** integration in CLion to see individual tests in the tree view.
- Set breakpoints in your `src/` implementation while debugging failing tests.
- `compile_commands.json` is exported for clangd/clang-tidy if you use external tools.
## Notes on C++26
C++26 is still in active standardization. Module 14 focuses on techniques and library directions that remain relevant—deep `constexpr`, monadic `expected`, ranges pipelines, and `std::formatter`—using C++23 as the practical baseline.
Happy learning!
+52
View File
@@ -0,0 +1,52 @@
function(add_course_exercise)
set(options "")
set(oneValueArgs NAME MODULE STANDARD)
set(multiValueArgs SOURCES)
cmake_parse_arguments(
EXERCISE
"${options}"
"${oneValueArgs}"
"${multiValueArgs}"
${ARGN}
)
if(NOT EXERCISE_NAME)
message(FATAL_ERROR "add_course_exercise requires NAME")
endif()
if(NOT EXERCISE_MODULE)
message(FATAL_ERROR "add_course_exercise requires MODULE")
endif()
if(NOT EXERCISE_SOURCES)
message(FATAL_ERROR "add_course_exercise requires SOURCES")
endif()
if(NOT EXERCISE_STANDARD)
set(EXERCISE_STANDARD 23)
endif()
set(target_name "${EXERCISE_MODULE}_${EXERCISE_NAME}")
add_executable(${target_name} ${EXERCISE_SOURCES})
target_include_directories(
${target_name}
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/include"
)
course_set_cxx_standard(${target_name} ${EXERCISE_STANDARD})
course_apply_warnings(${target_name})
if(COURSE_BUILD_TESTS)
target_link_libraries(${target_name} PRIVATE GTest::gtest_main)
gtest_discover_tests(
${target_name}
DISCOVERY_MODE PRE_TEST
PROPERTIES LABELS "${EXERCISE_MODULE};${EXERCISE_NAME}"
)
endif()
set_target_properties(
${target_name}
PROPERTIES
FOLDER "modules/${EXERCISE_MODULE}"
)
endfunction()
+25
View File
@@ -0,0 +1,25 @@
function(course_apply_warnings target_name)
if(MSVC)
target_compile_options(${target_name} PRIVATE /W4 /permissive-)
if(COURSE_WARNINGS_AS_ERRORS)
target_compile_options(${target_name} PRIVATE /WX)
endif()
else()
target_compile_options(
${target_name}
PRIVATE
-Wall
-Wextra
-Wpedantic
-Wconversion
-Wshadow
)
if(COURSE_WARNINGS_AS_ERRORS)
target_compile_options(${target_name} PRIVATE -Werror)
endif()
endif()
endfunction()
function(course_set_cxx_standard target_name standard)
target_compile_features(${target_name} PRIVATE cxx_std_${standard})
endfunction()
+35
View File
@@ -0,0 +1,35 @@
add_course_exercise(
NAME hello_world
MODULE 01_fundamentals
STANDARD 17
SOURCES
src/hello_world.cpp
test/hello_world_test.cpp
)
add_course_exercise(
NAME variables_and_types
MODULE 01_fundamentals
STANDARD 17
SOURCES
src/variables_and_types.cpp
test/variables_and_types_test.cpp
)
add_course_exercise(
NAME operators
MODULE 01_fundamentals
STANDARD 17
SOURCES
src/operators.cpp
test/operators_test.cpp
)
add_course_exercise(
NAME basic_io
MODULE 01_fundamentals
STANDARD 17
SOURCES
src/basic_io.cpp
test/basic_io_test.cpp
)
+34
View File
@@ -0,0 +1,34 @@
# Module 01: C++ Fundamentals
## Learning Goals
- Understand the structure of a C++ program (`main`, headers, translation units)
- Work with fundamental types (`int`, `double`, `bool`, `char`)
- Use literals, constants, and basic I/O
- Apply arithmetic, relational, and logical operators
- Read and write formatted output
## Exercises
| Exercise | Topic | Standard |
|----------|-------|----------|
| `hello_world` | First program, returning values from functions | C++17 |
| `variables_and_types` | Types, `auto`, type aliases | C++17 |
| `operators` | Arithmetic, comparisons, bitwise ops | C++17 |
| `basic_io` | Streams, string formatting | C++17 |
## How to Work
1. Open the exercise header in `include/` to see the required API.
2. Implement the functions in `src/`.
3. Run the corresponding test target in CLion or with CTest.
4. All tests must pass before moving on.
## Run Tests (CLion)
Select a target like `01_fundamentals_hello_world` and click Run, or use:
```bash
cmake --build build --target 01_fundamentals_hello_world
ctest --test-dir build -R 01_fundamentals_hello_world
```
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <vector>
namespace fundamentals {
[[nodiscard]] auto join_words(const std::vector<std::string>& words, char separator) -> std::string;
[[nodiscard]] auto parse_integers(const std::string& csv) -> std::vector<int>;
[[nodiscard]] auto format_score(const std::string& player, int score) -> std::string;
} // namespace fundamentals
@@ -0,0 +1,12 @@
#pragma once
namespace fundamentals {
// Return a greeting message for the given name.
// Example: greet("Ada") -> "Hello, Ada!"
[[nodiscard]] auto greet(const char* name) -> const char*;
// Return the program entry convention value for success.
[[nodiscard]] constexpr auto program_exit_success() -> int { return 0; }
} // namespace fundamentals
@@ -0,0 +1,13 @@
#pragma once
#include <cstdint>
namespace fundamentals {
[[nodiscard]] auto absolute_difference(int lhs, int rhs) -> int;
[[nodiscard]] auto is_in_range(int value, int min, int max) -> bool;
[[nodiscard]] auto count_set_bits(std::uint8_t value) -> int;
[[nodiscard]] auto logical_and(bool lhs, bool rhs) -> bool;
[[nodiscard]] auto logical_or(bool lhs, bool rhs) -> bool;
} // namespace fundamentals
@@ -0,0 +1,15 @@
#pragma once
#include <cstdint>
#include <string>
namespace fundamentals {
[[nodiscard]] auto add_integers(int lhs, int rhs) -> int;
[[nodiscard]] auto multiply_doubles(double lhs, double rhs) -> double;
[[nodiscard]] auto is_even(std::int64_t value) -> bool;
[[nodiscard]] auto describe_type(int value) -> std::string;
[[nodiscard]] auto describe_type(double value) -> std::string;
[[nodiscard]] auto describe_type(bool value) -> std::string;
} // namespace fundamentals
+28
View File
@@ -0,0 +1,28 @@
#include "basic_io.hpp"
#include <sstream>
#include <string>
namespace fundamentals {
auto join_words(const std::vector<std::string>& words, char separator) -> std::string {
// TODO: Join words with separator, no trailing separator.
(void)words;
(void)separator;
return {};
}
auto parse_integers(const std::string& csv) -> std::vector<int> {
// TODO: Parse comma-separated integers (e.g. "1,2,3").
(void)csv;
return {};
}
auto format_score(const std::string& player, int score) -> std::string {
// TODO: Return "<player> scored <score> points".
(void)player;
(void)score;
return {};
}
} // namespace fundamentals
@@ -0,0 +1,11 @@
#include "hello_world.hpp"
namespace fundamentals {
auto greet(const char* name) -> const char* {
// TODO: Return a static string in the format "Hello, <name>!"
(void)name;
return "Hello, World!";
}
} // namespace fundamentals
+40
View File
@@ -0,0 +1,40 @@
#include "operators.hpp"
namespace fundamentals {
auto absolute_difference(int lhs, int rhs) -> int {
// TODO: Return |lhs - rhs| without using std::abs.
(void)lhs;
(void)rhs;
return 0;
}
auto is_in_range(int value, int min, int max) -> bool {
// TODO: Return true when min <= value <= max (inclusive).
(void)value;
(void)min;
(void)max;
return false;
}
auto count_set_bits(std::uint8_t value) -> int {
// TODO: Count how many bits are set to 1.
(void)value;
return 0;
}
auto logical_and(bool lhs, bool rhs) -> bool {
// TODO: Implement logical AND without using &&.
(void)lhs;
(void)rhs;
return false;
}
auto logical_or(bool lhs, bool rhs) -> bool {
// TODO: Implement logical OR without using ||.
(void)lhs;
(void)rhs;
return false;
}
} // namespace fundamentals
@@ -0,0 +1,43 @@
#include "variables_and_types.hpp"
namespace fundamentals {
auto add_integers(int lhs, int rhs) -> int {
// TODO: Return the sum.
(void)lhs;
(void)rhs;
return 0;
}
auto multiply_doubles(double lhs, double rhs) -> double {
// TODO: Return the product.
(void)lhs;
(void)rhs;
return 0.0;
}
auto is_even(std::int64_t value) -> bool {
// TODO: Return true when value is divisible by 2.
(void)value;
return false;
}
auto describe_type(int value) -> std::string {
// TODO: Return "int:" followed by the decimal representation.
(void)value;
return "int:0";
}
auto describe_type(double value) -> std::string {
// TODO: Return "double:" followed by the value with one decimal place.
(void)value;
return "double:0.0";
}
auto describe_type(bool value) -> std::string {
// TODO: Return "bool:true" or "bool:false".
(void)value;
return "bool:false";
}
} // namespace fundamentals
@@ -0,0 +1,18 @@
#include "basic_io.hpp"
#include <gtest/gtest.h>
TEST(BasicIo, JoinWords) {
EXPECT_EQ(fundamentals::join_words({"C", "Plus", "Plus"}, '-'), "C-Plus-Plus");
EXPECT_EQ(fundamentals::join_words({"solo"}, ','), "solo");
EXPECT_EQ(fundamentals::join_words({}, ','), "");
}
TEST(BasicIo, ParseIntegers) {
EXPECT_EQ(fundamentals::parse_integers("1,2,3"), (std::vector<int>{1, 2, 3}));
EXPECT_EQ(fundamentals::parse_integers("42"), (std::vector<int>{42}));
}
TEST(BasicIo, FormatScore) {
EXPECT_EQ(fundamentals::format_score("Alice", 150), "Alice scored 150 points");
}
@@ -0,0 +1,12 @@
#include "hello_world.hpp"
#include <gtest/gtest.h>
TEST(HelloWorld, GreetsByName) {
EXPECT_STREQ(fundamentals::greet("Ada"), "Hello, Ada!");
EXPECT_STREQ(fundamentals::greet("Bjarne"), "Hello, Bjarne!");
}
TEST(HelloWorld, ExitSuccessIsZero) {
EXPECT_EQ(fundamentals::program_exit_success(), 0);
}
@@ -0,0 +1,25 @@
#include "operators.hpp"
#include <gtest/gtest.h>
TEST(Operators, AbsoluteDifference) {
EXPECT_EQ(fundamentals::absolute_difference(5, 9), 4);
EXPECT_EQ(fundamentals::absolute_difference(-3, 2), 5);
}
TEST(Operators, RangeCheck) {
EXPECT_TRUE(fundamentals::is_in_range(5, 1, 10));
EXPECT_FALSE(fundamentals::is_in_range(0, 1, 10));
}
TEST(Operators, CountSetBits) {
EXPECT_EQ(fundamentals::count_set_bits(0b00001111), 4);
EXPECT_EQ(fundamentals::count_set_bits(0b10101010), 4);
}
TEST(Operators, LogicalOperations) {
EXPECT_TRUE(fundamentals::logical_and(true, true));
EXPECT_FALSE(fundamentals::logical_and(true, false));
EXPECT_TRUE(fundamentals::logical_or(false, true));
EXPECT_FALSE(fundamentals::logical_or(false, false));
}
@@ -0,0 +1,25 @@
#include "variables_and_types.hpp"
#include <gtest/gtest.h>
TEST(VariablesAndTypes, AddsIntegers) {
EXPECT_EQ(fundamentals::add_integers(2, 3), 5);
EXPECT_EQ(fundamentals::add_integers(-4, 10), 6);
}
TEST(VariablesAndTypes, MultipliesDoubles) {
EXPECT_DOUBLE_EQ(fundamentals::multiply_doubles(2.5, 4.0), 10.0);
}
TEST(VariablesAndTypes, DetectsEvenNumbers) {
EXPECT_TRUE(fundamentals::is_even(0));
EXPECT_TRUE(fundamentals::is_even(42));
EXPECT_FALSE(fundamentals::is_even(7));
}
TEST(VariablesAndTypes, DescribesTypes) {
EXPECT_EQ(fundamentals::describe_type(7), "int:7");
EXPECT_EQ(fundamentals::describe_type(3.5), "double:3.5");
EXPECT_EQ(fundamentals::describe_type(true), "bool:true");
EXPECT_EQ(fundamentals::describe_type(false), "bool:false");
}
+8
View File
@@ -0,0 +1,8 @@
add_course_exercise(NAME conditionals MODULE 02_control_flow STANDARD 17
SOURCES src/conditionals.cpp test/conditionals_test.cpp)
add_course_exercise(NAME switch_statements MODULE 02_control_flow STANDARD 17
SOURCES src/switch_statements.cpp test/switch_statements_test.cpp)
add_course_exercise(NAME loops MODULE 02_control_flow STANDARD 17
SOURCES src/loops.cpp test/loops_test.cpp)
add_course_exercise(NAME break_continue MODULE 02_control_flow STANDARD 17
SOURCES src/break_continue.cpp test/break_continue_test.cpp)
+17
View File
@@ -0,0 +1,17 @@
# Module 02: Control Flow
## Learning Goals
- Write conditional logic with `if`, `else if`, and `else`
- Use `switch` statements and understand fall-through
- Implement loops: `for`, `while`, and range-based patterns
- Control loop execution with `break` and `continue`
## Exercises
| Exercise | Topic |
|----------|-------|
| `conditionals` | Classification, min/max, sign |
| `switch_statements` | Menu dispatch, enum handling |
| `loops` | Summation, factorial, search |
| `break_continue` | Filtering, early exit |
@@ -0,0 +1,11 @@
#pragma once
#include <vector>
namespace control_flow {
[[nodiscard]] auto sum_positive(const std::vector<int>& data) -> int;
[[nodiscard]] auto first_multiple_of(const std::vector<int>& data, int divisor) -> int;
[[nodiscard]] auto collect_until_negative(const std::vector<int>& data) -> std::vector<int>;
} // namespace control_flow
@@ -0,0 +1,11 @@
#pragma once
namespace control_flow {
enum class Grade { A, B, C, D, F };
[[nodiscard]] auto classify_score(int score) -> Grade;
[[nodiscard]] auto max_of_three(int a, int b, int c) -> int;
[[nodiscard]] auto sign_of(int value) -> int;
} // namespace control_flow
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include <vector>
namespace control_flow {
[[nodiscard]] auto sum_range(int from, int to) -> int;
[[nodiscard]] auto factorial(int n) -> long long;
[[nodiscard]] auto count_occurrences(const std::vector<int>& data, int target) -> int;
[[nodiscard]] auto first_index_of(const std::vector<int>& data, int target) -> int;
} // namespace control_flow
@@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace control_flow {
enum class Operation { Add, Subtract, Multiply, Divide, Unknown };
[[nodiscard]] auto parse_operation(char op) -> Operation;
[[nodiscard]] auto apply_operation(Operation op, int lhs, int rhs) -> int;
[[nodiscard]] auto day_name(int day) -> std::string;
} // namespace control_flow
@@ -0,0 +1,20 @@
#include "break_continue.hpp"
namespace control_flow {
auto sum_positive(const std::vector<int>& data) -> int {
(void)data;
return 0;
}
auto first_multiple_of(const std::vector<int>& data, int divisor) -> int {
(void)data; (void)divisor;
return -1;
}
auto collect_until_negative(const std::vector<int>& data) -> std::vector<int> {
(void)data;
return {};
}
} // namespace control_flow
@@ -0,0 +1,22 @@
#include "conditionals.hpp"
namespace control_flow {
auto classify_score(int score) -> Grade {
// TODO: A>=90, B>=80, C>=70, D>=60, else F
(void)score;
return Grade::F;
}
auto max_of_three(int a, int b, int c) -> int {
(void)a; (void)b; (void)c;
return 0;
}
auto sign_of(int value) -> int {
// TODO: Return -1, 0, or 1
(void)value;
return 0;
}
} // namespace control_flow
+26
View File
@@ -0,0 +1,26 @@
#include "loops.hpp"
namespace control_flow {
auto sum_range(int from, int to) -> int {
(void)from; (void)to;
return 0;
}
auto factorial(int n) -> long long {
(void)n;
return 1;
}
auto count_occurrences(const std::vector<int>& data, int target) -> int {
(void)data; (void)target;
return 0;
}
auto first_index_of(const std::vector<int>& data, int target) -> int {
// TODO: Return index or -1 if not found
(void)data; (void)target;
return -1;
}
} // namespace control_flow
@@ -0,0 +1,21 @@
#include "switch_statements.hpp"
namespace control_flow {
auto parse_operation(char op) -> Operation {
(void)op;
return Operation::Unknown;
}
auto apply_operation(Operation op, int lhs, int rhs) -> int {
(void)op; (void)lhs; (void)rhs;
return 0;
}
auto day_name(int day) -> std::string {
// TODO: 1=Monday ... 7=Sunday, else "Invalid"
(void)day;
return "Invalid";
}
} // namespace control_flow
@@ -0,0 +1,15 @@
#include "break_continue.hpp"
#include <gtest/gtest.h>
TEST(BreakContinue, SumPositive) {
EXPECT_EQ(control_flow::sum_positive({1, -2, 3, -4, 5}), 9);
}
TEST(BreakContinue, FirstMultipleOf) {
EXPECT_EQ(control_flow::first_multiple_of({1, 3, 4, 8}, 4), 8);
EXPECT_EQ(control_flow::first_multiple_of({1, 3, 5}, 4), -1);
}
TEST(BreakContinue, CollectUntilNegative) {
EXPECT_EQ(control_flow::collect_until_negative({1, 2, 3, -1, 4}), (std::vector<int>{1, 2, 3}));
}
@@ -0,0 +1,19 @@
#include "conditionals.hpp"
#include <gtest/gtest.h>
TEST(Conditionals, ClassifyScore) {
EXPECT_EQ(control_flow::classify_score(95), control_flow::Grade::A);
EXPECT_EQ(control_flow::classify_score(82), control_flow::Grade::B);
EXPECT_EQ(control_flow::classify_score(55), control_flow::Grade::F);
}
TEST(Conditionals, MaxOfThree) {
EXPECT_EQ(control_flow::max_of_three(1, 9, 3), 9);
EXPECT_EQ(control_flow::max_of_three(-1, -5, -2), -1);
}
TEST(Conditionals, SignOf) {
EXPECT_EQ(control_flow::sign_of(10), 1);
EXPECT_EQ(control_flow::sign_of(0), 0);
EXPECT_EQ(control_flow::sign_of(-3), -1);
}
@@ -0,0 +1,21 @@
#include "loops.hpp"
#include <gtest/gtest.h>
TEST(Loops, SumRange) {
EXPECT_EQ(control_flow::sum_range(1, 5), 15);
EXPECT_EQ(control_flow::sum_range(5, 5), 5);
}
TEST(Loops, Factorial) {
EXPECT_EQ(control_flow::factorial(0), 1);
EXPECT_EQ(control_flow::factorial(5), 120);
}
TEST(Loops, CountOccurrences) {
EXPECT_EQ(control_flow::count_occurrences({1, 2, 2, 3}, 2), 2);
}
TEST(Loops, FirstIndexOf) {
EXPECT_EQ(control_flow::first_index_of({4, 5, 6}, 5), 1);
EXPECT_EQ(control_flow::first_index_of({4, 5, 6}, 9), -1);
}
@@ -0,0 +1,18 @@
#include "switch_statements.hpp"
#include <gtest/gtest.h>
TEST(SwitchStatements, ParseOperation) {
EXPECT_EQ(control_flow::parse_operation('+'), control_flow::Operation::Add);
EXPECT_EQ(control_flow::parse_operation('?'), control_flow::Operation::Unknown);
}
TEST(SwitchStatements, ApplyOperation) {
EXPECT_EQ(control_flow::apply_operation(control_flow::Operation::Add, 2, 3), 5);
EXPECT_EQ(control_flow::apply_operation(control_flow::Operation::Multiply, 4, 5), 20);
}
TEST(SwitchStatements, DayName) {
EXPECT_EQ(control_flow::day_name(1), "Monday");
EXPECT_EQ(control_flow::day_name(7), "Sunday");
EXPECT_EQ(control_flow::day_name(0), "Invalid");
}
+8
View File
@@ -0,0 +1,8 @@
add_course_exercise(NAME function_basics MODULE 03_functions STANDARD 17
SOURCES src/function_basics.cpp test/function_basics_test.cpp)
add_course_exercise(NAME overloading MODULE 03_functions STANDARD 17
SOURCES src/overloading.cpp test/overloading_test.cpp)
add_course_exercise(NAME default_parameters MODULE 03_functions STANDARD 17
SOURCES src/default_parameters.cpp test/default_parameters_test.cpp)
add_course_exercise(NAME recursion MODULE 03_functions STANDARD 17
SOURCES src/recursion.cpp test/recursion_test.cpp)
+17
View File
@@ -0,0 +1,17 @@
# Module 03: Functions
## Learning Goals
- Declare and define functions with clear interfaces
- Pass arguments by value, reference, and `const` reference
- Overload functions and use default parameters
- Implement recursive algorithms safely
## Exercises
| Exercise | Topic |
|----------|-------|
| `function_basics` | Value/reference parameters, return values |
| `overloading` | Function overloading |
| `default_parameters` | Default arguments |
| `recursion` | Recursive algorithms |
@@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace functions {
[[nodiscard]] auto repeat(char ch, int count = 1) -> std::string;
[[nodiscard]] auto power(int base, int exponent = 2) -> long long;
[[nodiscard]] auto clamp(int value, int min = 0, int max = 100) -> int;
} // namespace functions
@@ -0,0 +1,13 @@
#pragma once
#include <string>
#include <vector>
namespace functions {
[[nodiscard]] auto square(int value) -> int;
void swap_integers(int& lhs, int& rhs);
[[nodiscard]] auto concatenate(const std::vector<std::string>& parts) -> std::string;
void append_suffix(std::string& text, const std::string& suffix);
} // namespace functions
@@ -0,0 +1,13 @@
#pragma once
#include <string>
namespace functions {
[[nodiscard]] auto max_value(int a, int b) -> int;
[[nodiscard]] auto max_value(double a, double b) -> double;
[[nodiscard]] auto max_value(const std::string& a, const std::string& b) -> std::string;
[[nodiscard]] auto area(int side) -> int;
[[nodiscard]] auto area(int width, int height) -> int;
} // namespace functions
@@ -0,0 +1,9 @@
#pragma once
namespace functions {
[[nodiscard]] auto fibonacci(int n) -> long long;
[[nodiscard]] auto sum_digits(int n) -> int;
[[nodiscard]] auto is_palindrome(int n) -> bool;
} // namespace functions
@@ -0,0 +1,9 @@
#include "default_parameters.hpp"
namespace functions {
auto repeat(char ch, int count) -> std::string { (void)ch; (void)count; return {}; }
auto power(int base, int exponent) -> long long { (void)base; (void)exponent; return 0; }
auto clamp(int value, int min, int max) -> int { (void)value; (void)min; (void)max; return 0; }
} // namespace functions
@@ -0,0 +1,10 @@
#include "function_basics.hpp"
namespace functions {
auto square(int value) -> int { (void)value; return 0; }
void swap_integers(int& lhs, int& rhs) { (void)lhs; (void)rhs; }
auto concatenate(const std::vector<std::string>& parts) -> std::string { (void)parts; return {}; }
void append_suffix(std::string& text, const std::string& suffix) { (void)text; (void)suffix; }
} // namespace functions
+11
View File
@@ -0,0 +1,11 @@
#include "overloading.hpp"
namespace functions {
auto max_value(int a, int b) -> int { (void)a; (void)b; return 0; }
auto max_value(double a, double b) -> double { (void)a; (void)b; return 0.0; }
auto max_value(const std::string& a, const std::string& b) -> std::string { (void)a; (void)b; return {}; }
auto area(int side) -> int { (void)side; return 0; }
auto area(int width, int height) -> int { (void)width; (void)height; return 0; }
} // namespace functions
+9
View File
@@ -0,0 +1,9 @@
#include "recursion.hpp"
namespace functions {
auto fibonacci(int n) -> long long { (void)n; return 0; }
auto sum_digits(int n) -> int { (void)n; return 0; }
auto is_palindrome(int n) -> bool { (void)n; return false; }
} // namespace functions
@@ -0,0 +1,18 @@
#include "default_parameters.hpp"
#include <gtest/gtest.h>
TEST(DefaultParameters, Repeat) {
EXPECT_EQ(functions::repeat('*'), "*");
EXPECT_EQ(functions::repeat('-', 3), "---");
}
TEST(DefaultParameters, Power) {
EXPECT_EQ(functions::power(5), 25);
EXPECT_EQ(functions::power(2, 10), 1024);
}
TEST(DefaultParameters, Clamp) {
EXPECT_EQ(functions::clamp(150), 100);
EXPECT_EQ(functions::clamp(-5), 0);
EXPECT_EQ(functions::clamp(42, 10, 50), 42);
}
@@ -0,0 +1,21 @@
#include "function_basics.hpp"
#include <gtest/gtest.h>
TEST(FunctionBasics, Square) { EXPECT_EQ(functions::square(7), 49); }
TEST(FunctionBasics, SwapIntegers) {
int a = 1, b = 2;
functions::swap_integers(a, b);
EXPECT_EQ(a, 2);
EXPECT_EQ(b, 1);
}
TEST(FunctionBasics, Concatenate) {
EXPECT_EQ(functions::concatenate({"C", "++"}), "C++");
}
TEST(FunctionBasics, AppendSuffix) {
std::string text = "modern";
functions::append_suffix(text, "_cpp");
EXPECT_EQ(text, "modern_cpp");
}
@@ -0,0 +1,10 @@
#include "overloading.hpp"
#include <gtest/gtest.h>
TEST(Overloading, MaxInt) { EXPECT_EQ(functions::max_value(3, 9), 9); }
TEST(Overloading, MaxDouble) { EXPECT_DOUBLE_EQ(functions::max_value(3.1, 2.9), 3.1); }
TEST(Overloading, MaxString) { EXPECT_EQ(functions::max_value("abc", "abd"), "abd"); }
TEST(Overloading, Area) {
EXPECT_EQ(functions::area(4), 16);
EXPECT_EQ(functions::area(3, 5), 15);
}
@@ -0,0 +1,17 @@
#include "recursion.hpp"
#include <gtest/gtest.h>
TEST(Recursion, Fibonacci) {
EXPECT_EQ(functions::fibonacci(0), 0);
EXPECT_EQ(functions::fibonacci(1), 1);
EXPECT_EQ(functions::fibonacci(10), 55);
}
TEST(Recursion, SumDigits) {
EXPECT_EQ(functions::sum_digits(12345), 15);
}
TEST(Recursion, IsPalindrome) {
EXPECT_TRUE(functions::is_palindrome(121));
EXPECT_FALSE(functions::is_palindrome(123));
}
@@ -0,0 +1,8 @@
add_course_exercise(NAME c_arrays MODULE 04_arrays_and_strings STANDARD 17
SOURCES src/c_arrays.cpp test/c_arrays_test.cpp)
add_course_exercise(NAME std_string MODULE 04_arrays_and_strings STANDARD 17
SOURCES src/std_string.cpp test/std_string_test.cpp)
add_course_exercise(NAME std_vector MODULE 04_arrays_and_strings STANDARD 17
SOURCES src/std_vector.cpp test/std_vector_test.cpp)
add_course_exercise(NAME multidimensional MODULE 04_arrays_and_strings STANDARD 17
SOURCES src/multidimensional.cpp test/multidimensional_test.cpp)
+17
View File
@@ -0,0 +1,17 @@
# Module 04: Arrays and Strings
## Learning Goals
- Use C-style arrays and understand their limitations
- Work with `std::string` manipulation
- Master `std::vector` as a dynamic array
- Handle multi-dimensional data structures
## Exercises
| Exercise | Topic |
|----------|-------|
| `c_arrays` | Fixed-size arrays, bounds |
| `std_string` | String operations |
| `std_vector` | Dynamic arrays |
| `multidimensional` | 2D vectors and matrices |
@@ -0,0 +1,11 @@
#pragma once
#include <cstddef>
namespace arrays {
[[nodiscard]] auto array_sum(const int* data, std::size_t size) -> int;
[[nodiscard]] auto array_max(const int* data, std::size_t size) -> int;
void reverse_array(int* data, std::size_t size);
} // namespace arrays
@@ -0,0 +1,13 @@
#pragma once
#include <vector>
namespace arrays {
using Matrix = std::vector<std::vector<int>>;
[[nodiscard]] auto create_matrix(int rows, int cols, int fill) -> Matrix;
[[nodiscard]] auto transpose(const Matrix& matrix) -> Matrix;
[[nodiscard]] auto row_sums(const Matrix& matrix) -> std::vector<int>;
} // namespace arrays
@@ -0,0 +1,12 @@
#pragma once
#include <string>
namespace arrays {
[[nodiscard]] auto to_uppercase(std::string text) -> std::string;
[[nodiscard]] auto trim(const std::string& text) -> std::string;
[[nodiscard]] auto replace_all(std::string text, char from, char to) -> std::string;
[[nodiscard]] auto starts_with(const std::string& text, const std::string& prefix) -> bool;
} // namespace arrays
@@ -0,0 +1,12 @@
#pragma once
#include <vector>
namespace arrays {
[[nodiscard]] auto vector_sum(const std::vector<int>& data) -> int;
void remove_value(std::vector<int>& data, int value);
[[nodiscard]] auto unique_sorted(std::vector<int> data) -> std::vector<int>;
[[nodiscard]] auto chunk(const std::vector<int>& data, std::size_t size) -> std::vector<std::vector<int>>;
} // namespace arrays
@@ -0,0 +1,9 @@
#include "c_arrays.hpp"
namespace arrays {
auto array_sum(const int* data, std::size_t size) -> int { (void)data; (void)size; return 0; }
auto array_max(const int* data, std::size_t size) -> int { (void)data; (void)size; return 0; }
void reverse_array(int* data, std::size_t size) { (void)data; (void)size; }
} // namespace arrays
@@ -0,0 +1,9 @@
#include "multidimensional.hpp"
namespace arrays {
auto create_matrix(int rows, int cols, int fill) -> Matrix { (void)rows; (void)cols; (void)fill; return {}; }
auto transpose(const Matrix& matrix) -> Matrix { (void)matrix; return {}; }
auto row_sums(const Matrix& matrix) -> std::vector<int> { (void)matrix; return {}; }
} // namespace arrays
@@ -0,0 +1,10 @@
#include "std_string.hpp"
namespace arrays {
auto to_uppercase(std::string text) -> std::string { (void)text; return {}; }
auto trim(const std::string& text) -> std::string { (void)text; return {}; }
auto replace_all(std::string text, char from, char to) -> std::string { (void)text; (void)from; (void)to; return {}; }
auto starts_with(const std::string& text, const std::string& prefix) -> bool { (void)text; (void)prefix; return false; }
} // namespace arrays
@@ -0,0 +1,10 @@
#include "std_vector.hpp"
namespace arrays {
auto vector_sum(const std::vector<int>& data) -> int { (void)data; return 0; }
void remove_value(std::vector<int>& data, int value) { (void)data; (void)value; }
auto unique_sorted(std::vector<int> data) -> std::vector<int> { (void)data; return {}; }
auto chunk(const std::vector<int>& data, std::size_t size) -> std::vector<std::vector<int>> { (void)data; (void)size; return {}; }
} // namespace arrays
@@ -0,0 +1,19 @@
#include "c_arrays.hpp"
#include <gtest/gtest.h>
TEST(CArrays, Sum) {
const int data[] = {1, 2, 3, 4};
EXPECT_EQ(arrays::array_sum(data, 4), 10);
}
TEST(CArrays, Max) {
const int data[] = {1, 9, 3};
EXPECT_EQ(arrays::array_max(data, 3), 9);
}
TEST(CArrays, Reverse) {
int data[] = {1, 2, 3, 4};
arrays::reverse_array(data, 4);
EXPECT_EQ(data[0], 4);
EXPECT_EQ(data[3], 1);
}
@@ -0,0 +1,20 @@
#include "multidimensional.hpp"
#include <gtest/gtest.h>
TEST(Multidimensional, CreateMatrix) {
const auto m = arrays::create_matrix(2, 3, 7);
ASSERT_EQ(m.size(), 2U);
ASSERT_EQ(m[0].size(), 3U);
EXPECT_EQ(m[1][2], 7);
}
TEST(Multidimensional, Transpose) {
const arrays::Matrix m{{1, 2}, {3, 4}};
const auto t = arrays::transpose(m);
EXPECT_EQ(t, (arrays::Matrix{{1, 3}, {2, 4}}));
}
TEST(Multidimensional, RowSums) {
const arrays::Matrix m{{1, 2, 3}, {4, 5, 6}};
EXPECT_EQ(arrays::row_sums(m), (std::vector<int>{6, 15}));
}
@@ -0,0 +1,19 @@
#include "std_string.hpp"
#include <gtest/gtest.h>
TEST(StdString, ToUppercase) {
EXPECT_EQ(arrays::to_uppercase("Hello"), "HELLO");
}
TEST(StdString, Trim) {
EXPECT_EQ(arrays::trim(" hi "), "hi");
}
TEST(StdString, ReplaceAll) {
EXPECT_EQ(arrays::replace_all("a-b-c", '-', '_'), "a_b_c");
}
TEST(StdString, StartsWith) {
EXPECT_TRUE(arrays::starts_with("C++26", "C++"));
EXPECT_FALSE(arrays::starts_with("C++26", "Java"));
}
@@ -0,0 +1,21 @@
#include "std_vector.hpp"
#include <gtest/gtest.h>
TEST(StdVector, Sum) {
EXPECT_EQ(arrays::vector_sum({1, 2, 3}), 6);
}
TEST(StdVector, RemoveValue) {
std::vector<int> data = {1, 2, 2, 3};
arrays::remove_value(data, 2);
EXPECT_EQ(data, (std::vector<int>{1, 3}));
}
TEST(StdVector, UniqueSorted) {
EXPECT_EQ(arrays::unique_sorted({3, 1, 2, 2, 3}), (std::vector<int>{1, 2, 3}));
}
TEST(StdVector, Chunk) {
EXPECT_EQ(arrays::chunk({1, 2, 3, 4, 5}, 2),
(std::vector<std::vector<int>>{{1, 2}, {3, 4}, {5}}));
}
@@ -0,0 +1,8 @@
add_course_exercise(NAME pointer_basics MODULE 05_pointers_and_references STANDARD 17
SOURCES src/pointer_basics.cpp test/pointer_basics_test.cpp)
add_course_exercise(NAME references MODULE 05_pointers_and_references STANDARD 17
SOURCES src/references.cpp test/references_test.cpp)
add_course_exercise(NAME pointer_arithmetic MODULE 05_pointers_and_references STANDARD 17
SOURCES src/pointer_arithmetic.cpp test/pointer_arithmetic_test.cpp)
add_course_exercise(NAME dynamic_memory MODULE 05_pointers_and_references STANDARD 17
SOURCES src/dynamic_memory.cpp test/dynamic_memory_test.cpp)
@@ -0,0 +1,17 @@
# Module 05: Pointers and References
## Learning Goals
- Understand pointers, addresses, and dereferencing
- Use references as aliases and for function parameters
- Apply pointer arithmetic carefully
- Manage dynamic memory with `new`/`delete` and prefer smart pointers later
## Exercises
| Exercise | Topic |
|----------|-------|
| `pointer_basics` | Address-of, dereference, null |
| `references` | Reference parameters and return |
| `pointer_arithmetic` | Iterating with pointers |
| `dynamic_memory` | Manual allocation patterns |
@@ -0,0 +1,11 @@
#pragma once
#include <cstddef>
namespace pointers {
[[nodiscard]] auto allocate_and_fill(std::size_t count, int value) -> int*;
void deallocate(int* ptr);
[[nodiscard]] auto clone_array(const int* source, std::size_t count) -> int*;
} // namespace pointers
@@ -0,0 +1,11 @@
#pragma once
#include <cstddef>
namespace pointers {
[[nodiscard]] auto pointer_distance(const int* begin, const int* end) -> std::size_t;
[[nodiscard]] auto find_pointer(const int* begin, const int* end, int target) -> const int*;
auto reverse_in_place(int* begin, int* end) -> void;
} // namespace pointers
@@ -0,0 +1,10 @@
#pragma once
namespace pointers {
[[nodiscard]] auto get_value(int* ptr) -> int;
void set_value(int* ptr, int value);
[[nodiscard]] auto is_null(const int* ptr) -> bool;
void swap_via_pointers(int* a, int* b);
} // namespace pointers
@@ -0,0 +1,10 @@
#pragma once
namespace pointers {
[[nodiscard]] auto double_value(const int& value) -> int;
void increment(int& value);
[[nodiscard]] auto max_ref(const int& a, const int& b) -> const int&;
[[nodiscard]] auto sum_three(const int& a, const int& b, const int& c) -> int;
} // namespace pointers
@@ -0,0 +1,9 @@
#include "dynamic_memory.hpp"
namespace pointers {
auto allocate_and_fill(std::size_t count, int value) -> int* { (void)count; (void)value; return nullptr; }
void deallocate(int* ptr) { delete[] ptr; }
auto clone_array(const int* source, std::size_t count) -> int* { (void)source; (void)count; return nullptr; }
} // namespace pointers
@@ -0,0 +1,9 @@
#include "pointer_arithmetic.hpp"
namespace pointers {
auto pointer_distance(const int* begin, const int* end) -> std::size_t { (void)begin; (void)end; return 0; }
auto find_pointer(const int* begin, const int* end, int target) -> const int* { (void)begin; (void)end; (void)target; return end; }
void reverse_in_place(int* begin, int* end) { (void)begin; (void)end; }
} // namespace pointers
@@ -0,0 +1,10 @@
#include "pointer_basics.hpp"
namespace pointers {
auto get_value(int* ptr) -> int { (void)ptr; return 0; }
void set_value(int* ptr, int value) { (void)ptr; (void)value; }
auto is_null(const int* ptr) -> bool { (void)ptr; return true; }
void swap_via_pointers(int* a, int* b) { (void)a; (void)b; }
} // namespace pointers
@@ -0,0 +1,10 @@
#include "references.hpp"
namespace pointers {
auto double_value(const int& value) -> int { (void)value; return 0; }
void increment(int& value) { (void)value; }
auto max_ref(const int& a, const int& b) -> const int& { (void)a; (void)b; return a; }
auto sum_three(const int& a, const int& b, const int& c) -> int { (void)a; (void)b; (void)c; return 0; }
} // namespace pointers
@@ -0,0 +1,18 @@
#include "dynamic_memory.hpp"
#include <gtest/gtest.h>
TEST(DynamicMemory, AllocateAndFill) {
int* data = pointers::allocate_and_fill(3, 7);
ASSERT_NE(data, nullptr);
EXPECT_EQ(data[0], 7);
EXPECT_EQ(data[2], 7);
pointers::deallocate(data);
}
TEST(DynamicMemory, CloneArray) {
int source[] = {1, 2, 3};
int* copy = pointers::clone_array(source, 3);
ASSERT_NE(copy, nullptr);
EXPECT_EQ(copy[1], 2);
pointers::deallocate(copy);
}
@@ -0,0 +1,19 @@
#include "pointer_arithmetic.hpp"
#include <gtest/gtest.h>
TEST(PointerArithmetic, Distance) {
int data[] = {1, 2, 3, 4};
EXPECT_EQ(pointers::pointer_distance(data, data + 4), 4U);
}
TEST(PointerArithmetic, FindPointer) {
int data[] = {1, 2, 3};
EXPECT_EQ(pointers::find_pointer(data, data + 3, 2), data + 1);
}
TEST(PointerArithmetic, ReverseInPlace) {
int data[] = {1, 2, 3, 4};
pointers::reverse_in_place(data, data + 4);
EXPECT_EQ(data[0], 4);
EXPECT_EQ(data[3], 1);
}
@@ -0,0 +1,21 @@
#include "pointer_basics.hpp"
#include <gtest/gtest.h>
TEST(PointerBasics, GetSetValue) {
int x = 42;
pointers::set_value(&x, 100);
EXPECT_EQ(pointers::get_value(&x), 100);
}
TEST(PointerBasics, IsNull) {
EXPECT_TRUE(pointers::is_null(nullptr));
int x = 1;
EXPECT_FALSE(pointers::is_null(&x));
}
TEST(PointerBasics, SwapViaPointers) {
int a = 1, b = 2;
pointers::swap_via_pointers(&a, &b);
EXPECT_EQ(a, 2);
EXPECT_EQ(b, 1);
}
@@ -0,0 +1,19 @@
#include "references.hpp"
#include <gtest/gtest.h>
TEST(References, DoubleValue) { EXPECT_EQ(pointers::double_value(21), 42); }
TEST(References, Increment) {
int x = 5;
pointers::increment(x);
EXPECT_EQ(x, 6);
}
TEST(References, MaxRef) {
int a = 3, b = 9;
EXPECT_EQ(&pointers::max_ref(a, b), &b);
}
TEST(References, SumThree) {
EXPECT_EQ(pointers::sum_three(1, 2, 3), 6);
}
+8
View File
@@ -0,0 +1,8 @@
add_course_exercise(NAME classes_and_objects MODULE 06_oop_basics STANDARD 17
SOURCES src/classes_and_objects.cpp test/classes_and_objects_test.cpp)
add_course_exercise(NAME constructors MODULE 06_oop_basics STANDARD 17
SOURCES src/constructors.cpp test/constructors_test.cpp)
add_course_exercise(NAME inheritance MODULE 06_oop_basics STANDARD 17
SOURCES src/inheritance.cpp test/inheritance_test.cpp)
add_course_exercise(NAME polymorphism MODULE 06_oop_basics STANDARD 17
SOURCES src/polymorphism.cpp src/inheritance.cpp test/polymorphism_test.cpp)
+17
View File
@@ -0,0 +1,17 @@
# Module 06: Object-Oriented Programming Basics
## Learning Goals
- Design classes with encapsulation
- Implement constructors, destructors, and the Rule of Three/Five
- Use inheritance and virtual functions
- Apply polymorphism through base class interfaces
## Exercises
| Exercise | Topic |
|----------|-------|
| `classes_and_objects` | Members, methods, invariants |
| `constructors` | RAII, special members |
| `inheritance` | Base/derived classes |
| `polymorphism` | Virtual functions, overriding |
@@ -0,0 +1,22 @@
#pragma once
#include <string>
namespace oop {
class BankAccount {
public:
explicit BankAccount(std::string owner, double balance = 0.0);
[[nodiscard]] auto owner() const -> const std::string&;
[[nodiscard]] auto balance() const -> double;
void deposit(double amount);
auto withdraw(double amount) -> bool;
private:
std::string owner_;
double balance_;
};
} // namespace oop
@@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <vector>
namespace oop {
class StringBuilder {
public:
StringBuilder() = default;
explicit StringBuilder(std::string initial);
StringBuilder(const StringBuilder& other);
auto operator=(const StringBuilder& other) -> StringBuilder&;
~StringBuilder() = default;
void append(const std::string& text);
[[nodiscard]] auto str() const -> std::string;
[[nodiscard]] auto size() const -> std::size_t;
private:
std::vector<char> buffer_;
};
} // namespace oop
@@ -0,0 +1,35 @@
#pragma once
#include <string>
namespace oop {
class Shape {
public:
virtual ~Shape() = default;
[[nodiscard]] virtual auto name() const -> std::string = 0;
[[nodiscard]] virtual auto area() const -> double = 0;
};
class Circle : public Shape {
public:
explicit Circle(double radius);
[[nodiscard]] auto name() const -> std::string override;
[[nodiscard]] auto area() const -> double override;
private:
double radius_;
};
class Rectangle : public Shape {
public:
Rectangle(double width, double height);
[[nodiscard]] auto name() const -> std::string override;
[[nodiscard]] auto area() const -> double override;
private:
double width_;
double height_;
};
} // namespace oop
@@ -0,0 +1,30 @@
#pragma once
#include "inheritance.hpp"
#include <memory>
#include <string>
#include <vector>
namespace oop {
[[nodiscard]] auto total_area(const std::vector<std::unique_ptr<Shape>>& shapes) -> double;
[[nodiscard]] auto shape_names(const std::vector<std::unique_ptr<Shape>>& shapes)
-> std::vector<std::string>;
class Counter {
public:
Counter() = default;
Counter(const Counter&) { ++copies_; }
auto operator=(const Counter&) -> Counter& {
++copies_;
return *this;
}
[[nodiscard]] static auto copies() -> int;
private:
inline static int copies_ = 0;
};
} // namespace oop
@@ -0,0 +1,22 @@
#include "classes_and_objects.hpp"
namespace oop {
BankAccount::BankAccount(std::string owner, double balance)
: owner_(std::move(owner)), balance_(balance) {}
auto BankAccount::owner() const -> const std::string& { return owner_; }
auto BankAccount::balance() const -> double { return balance_; }
void BankAccount::deposit(double amount) {
// TODO: Add amount when positive
(void)amount;
}
auto BankAccount::withdraw(double amount) -> bool {
// TODO: Return false if insufficient funds, else subtract and return true
(void)amount;
return false;
}
} // namespace oop
@@ -0,0 +1,30 @@
#include "constructors.hpp"
namespace oop {
StringBuilder::StringBuilder(std::string initial) {
// TODO: Initialize buffer_ from initial
(void)initial;
}
StringBuilder::StringBuilder(const StringBuilder& other) : buffer_(other.buffer_) {}
auto StringBuilder::operator=(const StringBuilder& other) -> StringBuilder& {
if (this != &other) {
buffer_ = other.buffer_;
}
return *this;
}
void StringBuilder::append(const std::string& text) {
// TODO: Append all characters from text
(void)text;
}
auto StringBuilder::str() const -> std::string {
return std::string(buffer_.begin(), buffer_.end());
}
auto StringBuilder::size() const -> std::size_t { return buffer_.size(); }
} // namespace oop
+13
View File
@@ -0,0 +1,13 @@
#include "inheritance.hpp"
namespace oop {
Circle::Circle(double radius) : radius_(radius) {}
auto Circle::name() const -> std::string { return "Circle"; }
auto Circle::area() const -> double { return 3.141592653589793 * radius_ * radius_; }
Rectangle::Rectangle(double width, double height) : width_(width), height_(height) {}
auto Rectangle::name() const -> std::string { return "Rectangle"; }
auto Rectangle::area() const -> double { return width_ * height_; }
} // namespace oop
@@ -0,0 +1,26 @@
#include "polymorphism.hpp"
namespace oop {
auto total_area(const std::vector<std::unique_ptr<Shape>>& shapes) -> double {
double total = 0.0;
for (const auto& shape : shapes) {
// TODO: Add each shape's area using polymorphism
(void)shape;
}
return total;
}
auto shape_names(const std::vector<std::unique_ptr<Shape>>& shapes)
-> std::vector<std::string> {
std::vector<std::string> names;
for (const auto& shape : shapes) {
// TODO: Collect shape->name()
(void)shape;
}
return names;
}
auto Counter::copies() -> int { return copies_; }
} // namespace oop
@@ -0,0 +1,16 @@
#include "classes_and_objects.hpp"
#include <gtest/gtest.h>
TEST(ClassesAndObjects, DepositAndWithdraw) {
oop::BankAccount account("Ada", 100.0);
account.deposit(50.0);
EXPECT_DOUBLE_EQ(account.balance(), 150.0);
EXPECT_TRUE(account.withdraw(30.0));
EXPECT_DOUBLE_EQ(account.balance(), 120.0);
EXPECT_FALSE(account.withdraw(1000.0));
}
TEST(ClassesAndObjects, OwnerName) {
oop::BankAccount account("Grace");
EXPECT_EQ(account.owner(), "Grace");
}
@@ -0,0 +1,16 @@
#include "constructors.hpp"
#include <gtest/gtest.h>
TEST(Constructors, InitialAndAppend) {
oop::StringBuilder builder("Hi");
builder.append(" there");
EXPECT_EQ(builder.str(), "Hi there");
}
TEST(Constructors, CopySemantics) {
oop::StringBuilder original("C++");
oop::StringBuilder copy = original;
copy.append("11");
EXPECT_EQ(original.str(), "C++");
EXPECT_EQ(copy.str(), "C++11");
}
@@ -0,0 +1,12 @@
#include "inheritance.hpp"
#include <gtest/gtest.h>
TEST(Inheritance, CircleArea) {
oop::Circle circle(2.0);
EXPECT_NEAR(circle.area(), 12.566370614359172, 1e-9);
}
TEST(Inheritance, RectangleArea) {
oop::Rectangle rect(3.0, 4.0);
EXPECT_DOUBLE_EQ(rect.area(), 12.0);
}
@@ -0,0 +1,16 @@
#include "polymorphism.hpp"
#include <gtest/gtest.h>
TEST(Polymorphism, TotalArea) {
std::vector<std::unique_ptr<oop::Shape>> shapes;
shapes.push_back(std::make_unique<oop::Rectangle>(2.0, 3.0));
shapes.push_back(std::make_unique<oop::Circle>(1.0));
EXPECT_NEAR(oop::total_area(shapes), 6.0 + 3.141592653589793, 1e-9);
}
TEST(Polymorphism, ShapeNames) {
std::vector<std::unique_ptr<oop::Shape>> shapes;
shapes.push_back(std::make_unique<oop::Circle>(1.0));
shapes.push_back(std::make_unique<oop::Rectangle>(1.0, 2.0));
EXPECT_EQ(oop::shape_names(shapes), (std::vector<std::string>{"Circle", "Rectangle"}));
}
+8
View File
@@ -0,0 +1,8 @@
add_course_exercise(NAME sequential_containers MODULE 07_stl_containers STANDARD 17
SOURCES src/sequential_containers.cpp test/sequential_containers_test.cpp)
add_course_exercise(NAME associative_containers MODULE 07_stl_containers STANDARD 17
SOURCES src/associative_containers.cpp test/associative_containers_test.cpp)
add_course_exercise(NAME unordered_containers MODULE 07_stl_containers STANDARD 17
SOURCES src/unordered_containers.cpp test/unordered_containers_test.cpp)
add_course_exercise(NAME container_adapters MODULE 07_stl_containers STANDARD 17
SOURCES src/container_adapters.cpp test/container_adapters_test.cpp)
+17
View File
@@ -0,0 +1,17 @@
# Module 07: STL Containers
## Learning Goals
- Choose the right sequential container for the job
- Use associative containers (`map`, `set`) effectively
- Leverage unordered containers for average O(1) lookup
- Apply container adapters (`stack`, `queue`)
## Exercises
| Exercise | Topic |
|----------|-------|
| `sequential_containers` | vector, list, deque |
| `associative_containers` | map, set, multimap |
| `unordered_containers` | unordered_map, unordered_set |
| `container_adapters` | stack, queue, priority_queue |
@@ -0,0 +1,16 @@
#pragma once
#include <map>
#include <set>
#include <string>
#include <vector>
namespace stl_containers {
[[nodiscard]] auto word_frequencies(const std::vector<std::string>& words)
-> std::map<std::string, int>;
[[nodiscard]] auto unique_sorted(const std::vector<int>& data) -> std::set<int>;
[[nodiscard]] auto invert_map(const std::map<int, std::string>& input)
-> std::map<std::string, int>;
} // namespace stl_containers
@@ -0,0 +1,14 @@
#pragma once
#include <queue>
#include <stack>
#include <string>
#include <vector>
namespace stl_containers {
[[nodiscard]] auto is_balanced_parentheses(const std::string& text) -> bool;
[[nodiscard]] auto simulate_queue(const std::vector<int>& arrivals) -> std::vector<int>;
[[nodiscard]] auto top_k_largest(const std::vector<int>& data, int k) -> std::vector<int>;
} // namespace stl_containers
@@ -0,0 +1,14 @@
#pragma once
#include <deque>
#include <list>
#include <vector>
namespace stl_containers {
[[nodiscard]] auto merge_sorted_vectors(const std::vector<int>& a, const std::vector<int>& b)
-> std::vector<int>;
[[nodiscard]] auto list_to_vector(const std::list<int>& data) -> std::vector<int>;
[[nodiscard]] auto rotate_deque(std::deque<int> data, int steps) -> std::deque<int>;
} // namespace stl_containers
@@ -0,0 +1,15 @@
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
namespace stl_containers {
[[nodiscard]] auto first_unique(const std::vector<std::string>& words) -> std::string;
[[nodiscard]] auto group_anagrams(const std::vector<std::string>& words)
-> std::unordered_map<std::string, std::vector<std::string>>;
[[nodiscard]] auto has_duplicate(const std::vector<int>& data) -> bool;
} // namespace stl_containers
@@ -0,0 +1,21 @@
#include "associative_containers.hpp"
namespace stl_containers {
auto word_frequencies(const std::vector<std::string>& words)
-> std::map<std::string, int> {
(void)words;
return {};
}
auto unique_sorted(const std::vector<int>& data) -> std::set<int> {
(void)data;
return {};
}
auto invert_map(const std::map<int, std::string>& input) -> std::map<std::string, int> {
(void)input;
return {};
}
} // namespace stl_containers
@@ -0,0 +1,20 @@
#include "container_adapters.hpp"
namespace stl_containers {
auto is_balanced_parentheses(const std::string& text) -> bool {
(void)text;
return false;
}
auto simulate_queue(const std::vector<int>& arrivals) -> std::vector<int> {
(void)arrivals;
return {};
}
auto top_k_largest(const std::vector<int>& data, int k) -> std::vector<int> {
(void)data; (void)k;
return {};
}
} // namespace stl_containers
@@ -0,0 +1,21 @@
#include "sequential_containers.hpp"
namespace stl_containers {
auto merge_sorted_vectors(const std::vector<int>& a, const std::vector<int>& b)
-> std::vector<int> {
(void)a; (void)b;
return {};
}
auto list_to_vector(const std::list<int>& data) -> std::vector<int> {
(void)data;
return {};
}
auto rotate_deque(std::deque<int> data, int steps) -> std::deque<int> {
(void)data; (void)steps;
return {};
}
} // namespace stl_containers
@@ -0,0 +1,21 @@
#include "unordered_containers.hpp"
namespace stl_containers {
auto first_unique(const std::vector<std::string>& words) -> std::string {
(void)words;
return {};
}
auto group_anagrams(const std::vector<std::string>& words)
-> std::unordered_map<std::string, std::vector<std::string>> {
(void)words;
return {};
}
auto has_duplicate(const std::vector<int>& data) -> bool {
(void)data;
return false;
}
} // namespace stl_containers
@@ -0,0 +1,18 @@
#include "associative_containers.hpp"
#include <gtest/gtest.h>
TEST(AssociativeContainers, WordFrequencies) {
const auto freq = stl_containers::word_frequencies({"a", "b", "a"});
EXPECT_EQ(freq.at("a"), 2);
EXPECT_EQ(freq.at("b"), 1);
}
TEST(AssociativeContainers, UniqueSorted) {
EXPECT_EQ(stl_containers::unique_sorted({3, 1, 2, 2}), (std::set<int>{1, 2, 3}));
}
TEST(AssociativeContainers, InvertMap) {
const std::map<int, std::string> input{{1, "one"}, {2, "two"}};
const auto inverted = stl_containers::invert_map(input);
EXPECT_EQ(inverted.at("one"), 1);
}

Some files were not shown because too many files have changed in this diff Show More