diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 13fd6e0a..32b897e4 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -1,6 +1,6 @@ name: Testing Tasks -on: [push, pull_request] +on: [push] jobs: test: @@ -30,12 +30,17 @@ jobs: tasks=("addition" "rms" "print_bits" "check_flags" "length_lit" "quadratic" "char_changer" "swap_ptr" "func_array" "longest" "last_of_us" "little_big" "pretty_array" "data_stats" "unique" "range" "minmax" "find_all" "os_overload" "easy_compare" "filter" "enum_operators" - "stack" "queue" "ring_buffer" "phasor") + "stack" "queue" "ring_buffer" "phasor" + "tracer" "string_view" "cow_string" "simple_vector") declare -i passed_count=0 declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for changed tasks ===" for task in "${tasks[@]}"; do @@ -49,13 +54,16 @@ jobs: if ./build/tasks/test_$task; then echo "✅ test_$task PASSED" passed_count+=1 + passed_tasks+=("$task") else echo "❌ test_$task FAILED" failed_count+=1 + failed_tasks+=("$task") fi else echo "❌ test_$task build FAILED" failed_count+=1 + failed_tasks+=("$task") fi fi done @@ -63,8 +71,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_01.yml b/.github/workflows/testing_week_01.yml index 3ece11af..be25e996 100644 --- a/.github/workflows/testing_week_01.yml +++ b/.github/workflows/testing_week_01.yml @@ -18,6 +18,9 @@ on: - quadratic - char_changer + schedule: + - cron: '59 20 4 12 *' # UTC: 20:59 = 23:59 MSK 4 December + jobs: test: runs-on: ubuntu-latest @@ -33,7 +36,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 01_week/tasks/*/; do @@ -48,6 +52,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -55,6 +61,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -80,8 +90,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_02.yml b/.github/workflows/testing_week_02.yml index dc37cec9..f4f63b94 100644 --- a/.github/workflows/testing_week_02.yml +++ b/.github/workflows/testing_week_02.yml @@ -17,6 +17,9 @@ on: - little_big - pretty_array + schedule: + - cron: '59 20 11 12 *' # UTC: 20:59 = 23:59 MSK 11 December + jobs: test: runs-on: ubuntu-latest @@ -32,7 +35,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 02_week/tasks/*/; do @@ -47,6 +51,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -54,6 +60,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -79,8 +89,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_03.yml b/.github/workflows/testing_week_03.yml index fdb0892f..38993cfa 100644 --- a/.github/workflows/testing_week_03.yml +++ b/.github/workflows/testing_week_03.yml @@ -38,7 +38,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 03_week/tasks/*/; do @@ -53,6 +54,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -60,6 +63,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -85,8 +92,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_04.yml b/.github/workflows/testing_week_04.yml index 9f93eaed..e27e55bb 100644 --- a/.github/workflows/testing_week_04.yml +++ b/.github/workflows/testing_week_04.yml @@ -33,7 +33,8 @@ jobs: - name: Determine tasks to run id: get-tasks run: | - if [ "${{ github.event.inputs.tasks }}" = "all" ]; then + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then # Find all tasks TASKS=() for dir in 04_week/tasks/*/; do @@ -48,6 +49,8 @@ jobs: - name: Build and run tests for selected tasks run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" @@ -55,6 +58,10 @@ jobs: declare -i failed_count=0 declare -i task_count=0 + # task name arrays + passed_tasks=() + failed_tasks=() + echo "=== Starting tests for selected tasks ===" for task in "${tasks[@]}"; do @@ -80,8 +87,8 @@ jobs: echo "=== Test Results Summary ===" echo "Total tasks in list: ${#tasks[@]}" echo "Processed: $task_count" - echo "✅ Passed: $passed_count" - echo "❌ Failed: $failed_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" if [ $failed_count -gt 0 ]; then echo "❌ Some tasks failed!" diff --git a/.github/workflows/testing_week_05.yml b/.github/workflows/testing_week_05.yml new file mode 100644 index 00000000..312f5c25 --- /dev/null +++ b/.github/workflows/testing_week_05.yml @@ -0,0 +1,105 @@ +name: Testing Tasks Week 05 + +on: + workflow_dispatch: + inputs: + tasks: + description: 'Select tasks to test' + required: true + type: choice + default: 'all' + options: + - all + - tracer + - string_view + - cow_string + - simple_vector + + schedule: + - cron: '59 20 16 02 *' # UTC: 20:59 = 23:59 MSK 16 February + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install compiler and CMake + run: sudo apt install -y cmake build-essential g++-14 libgtest-dev libgmock-dev + + - name: Configure project + run: cmake -B build + + - name: Determine tasks to run + id: get-tasks + run: | + if [["${{ github.event_name }}" = "schedule"]] || + [[ "${{ github.event.inputs.tasks }}" = "all" ]]; then + # Find all tasks + TASKS=() + for dir in 05_week/tasks/*/; do + task=$(basename "$dir") + TASKS+=("$task") + done + echo "tasks=${TASKS[*]}" >> $GITHUB_OUTPUT + else + # Используем указанную задачу + echo "tasks=${{ github.event.inputs.tasks }}" >> $GITHUB_OUTPUT + fi + + - name: Build and run tests for selected tasks + run: | + echo "Event: ${{ github.event_name }}" + echo "Tasks to test: '${{ steps.get-tasks.outputs.tasks }}'" + + IFS=' ' read -ra tasks <<< "${{ steps.get-tasks.outputs.tasks }}" + + declare -i passed_count=0 + declare -i failed_count=0 + declare -i task_count=0 + + # task name arrays + passed_tasks=() + failed_tasks=() + + echo "=== Starting tests for selected tasks ===" + + for task in "${tasks[@]}"; do + task_count+=1 + echo "=== Processing $task ===" + + if cmake --build build --target test_$task; then + echo "✅ test_$task built successfully" + + if ./build/tasks/test_$task; then + echo "✅ test_$task PASSED" + passed_count+=1 + passed_tasks+=("$task") + else + echo "❌ test_$task FAILED" + failed_count+=1 + failed_tasks+=("$task") + fi + else + echo "❌ test_$task build FAILED" + failed_count+=1 + failed_tasks+=("$task") + fi + done + + echo "=== Test Results Summary ===" + echo "Total tasks in list: ${#tasks[@]}" + echo "Processed: $task_count" + echo "✅ Passed: $passed_count [$(echo ${passed_tasks[@]} | tr ' ' ', ')]" + echo "❌ Failed: $failed_count [$(echo ${failed_tasks[@]} | tr ' ' ', ')]" + + if [ $failed_count -gt 0 ]; then + echo "❌ Some tasks failed!" + exit 1 + elif [ $task_count -eq 0 ]; then + echo "No tasks were processed (no changes)" + exit 0 + else + echo "✅ All processed tasks passed!" + exit 0 + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..261348ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +build-asan +.vscode +main +main.cpp \ No newline at end of file diff --git a/01_week/tasks/addition/addition.cpp b/01_week/tasks/addition/addition.cpp index 92872802..b9263084 100644 --- a/01_week/tasks/addition/addition.cpp +++ b/01_week/tasks/addition/addition.cpp @@ -1,7 +1,6 @@ #include -#include int64_t Addition(int a, int b) { - throw std::runtime_error{"Not implemented"}; + return static_cast(a) + b; } \ No newline at end of file diff --git a/01_week/tasks/char_changer/char_changer.cpp b/01_week/tasks/char_changer/char_changer.cpp index 3a7344d9..9d1bce8b 100644 --- a/01_week/tasks/char_changer/char_changer.cpp +++ b/01_week/tasks/char_changer/char_changer.cpp @@ -1,7 +1,53 @@ #include -#include +#include size_t CharChanger(char array[], size_t size, char delimiter = ' ') { - throw std::runtime_error{"Not implemented"}; + // pos - позиция в массиве array без дубликатов + size_t pos = 0; + + // size - размер массива с учётом '\0' в конце + for (size_t i = 0; i < size - 1; ++i) { + // сохраняем символ до его замены и приводим его к uint + // функции isdigit, islower и др. не работают с отрицательным кодами + char old_symbol = static_cast(array[i]); + + // определяем тип замены + if (isdigit(old_symbol)) { + array[pos++] = '*'; + } + else if (old_symbol == ' ') { + array[pos++] = delimiter; + } + else if (islower(old_symbol)) { + array[pos++] = toupper(old_symbol); + } + else if (isupper(old_symbol)) { + array[pos++] = old_symbol; + } + else { + array[pos++] = '_'; + } + + // поиск и подсчёт дубликатов + size_t j = i + 1; + while (j < size && array[j] == old_symbol) { + ++j; + } + + // если есть дубликаты + if (size_t duplicates = j - i - 1; duplicates > 0) { + // обновляем указатель i + i += duplicates; + + // добавляем счётчик после повторяющегося символа (кроме ' ') + if (old_symbol != ' ') + array[pos++] = duplicates + 1 >= 10 ? '0' : duplicates + 1 + '0'; + } + } + + // устанавливаем новый конец массива + array[pos] = '\0'; + + return pos; } diff --git a/01_week/tasks/check_flags/check_flags.cpp b/01_week/tasks/check_flags/check_flags.cpp index 75e7c652..4baf93e1 100644 --- a/01_week/tasks/check_flags/check_flags.cpp +++ b/01_week/tasks/check_flags/check_flags.cpp @@ -1,6 +1,7 @@ #include -#include - +#include +#include +#include enum class CheckFlags : uint8_t { NONE = 0, @@ -13,6 +14,40 @@ enum class CheckFlags : uint8_t { ALL = TIME | DATE | USER | CERT | KEYS | DEST }; +CheckFlags operator&(CheckFlags lhs, CheckFlags rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} + void PrintCheckFlags(CheckFlags flags) { - throw std::runtime_error{"Not implemented"}; + // Проверка на выход за диапозон значений CheckFlags + if ((~static_cast(CheckFlags::ALL) & static_cast(flags)) != 0) { + return; + } + + const static std::pair checks[] { + {CheckFlags::TIME, "TIME"}, + {CheckFlags::DATE, "DATE"}, + {CheckFlags::USER, "USER"}, + {CheckFlags::CERT, "CERT"}, + {CheckFlags::KEYS, "KEYS"}, + {CheckFlags::DEST, "DEST"} + }; + + bool first = true; + + std::cout << "["; + + for (const auto& [flag, name] : checks) { + if ((flags & flag) != CheckFlags::NONE) { + if (first) { + first = false; + } + else { + std::cout << ","; + } + std::cout << name; + } + } + + std::cout << ']'; } diff --git a/01_week/tasks/length_lit/length_lit.cpp b/01_week/tasks/length_lit/length_lit.cpp index e69de29b..e5f29adb 100644 --- a/01_week/tasks/length_lit/length_lit.cpp +++ b/01_week/tasks/length_lit/length_lit.cpp @@ -0,0 +1,60 @@ +namespace { + constexpr long double INCHES_PER_FOOT = 12.0L; + constexpr long double CM_PER_INCH = 2.54L; + constexpr long double M_PER_FOOT = 0.3048L; + constexpr long double CM_PER_FOOT = M_PER_FOOT * 100.0L; + constexpr long double M_PER_INCH = CM_PER_INCH / 100.0L; + constexpr long double CM_PER_METER = 100.0L; +} + +// ==== Перевод футов ==== +constexpr long double operator""_ft_to_m(long double ft) { + return ft * M_PER_FOOT; +} + +constexpr long double operator""_ft_to_cm(long double ft) { + return ft * CM_PER_FOOT; +} + +constexpr long double operator""_ft_to_in(long double ft) { + return ft * INCHES_PER_FOOT; +} + +// ==== Перевод дюймов ==== +constexpr long double operator""_in_to_m(long double in) { + return in * M_PER_INCH; +} + +constexpr long double operator""_in_to_cm(long double in) { + return in * CM_PER_INCH; +} + +constexpr long double operator""_in_to_ft(long double in) { + return in / INCHES_PER_FOOT; +} + +// ==== Перевод метров ==== +constexpr long double operator""_m_to_ft(long double m) { + return m / M_PER_FOOT; +} + +constexpr long double operator""_m_to_in(long double m) { + return m / M_PER_INCH; +} + +constexpr long double operator""_m_to_cm(long double m) { + return m * CM_PER_METER; +} + +// ==== Перевод сантиметров ==== +constexpr long double operator""_cm_to_m(long double cm) { + return cm / CM_PER_METER; +} + +constexpr long double operator""_cm_to_ft(long double cm) { + return cm / CM_PER_FOOT; +} + +constexpr long double operator""_cm_to_in(long double cm) { + return cm / CM_PER_INCH; +} \ No newline at end of file diff --git a/01_week/tasks/print_bits/print_bits.cpp b/01_week/tasks/print_bits/print_bits.cpp index a48a43c1..7fa649f8 100644 --- a/01_week/tasks/print_bits/print_bits.cpp +++ b/01_week/tasks/print_bits/print_bits.cpp @@ -1,7 +1,22 @@ -#include -#include +#include +#include void PrintBits(long long value, size_t bytes) { - throw std::runtime_error{"Not implemented"}; + size_t bits = bytes * 8; + unsigned long long bit_index = 1ll << (bits - 1); // 0b1000'...'0000 + size_t delims = 1 + 2 * (bytes - 1); + // Выделяем буфер под биты, апострофы и '\0' в конце + size_t buffer_size = bits + delims + 1; + + std::cout << "0b"; + for (size_t i = 0; i < buffer_size - 1; ++i) { + if (i % 5 == 4) { + std::cout << '\''; + } else { + std::cout << static_cast(static_cast(value & bit_index) + '0'); + bit_index = bit_index >> 1; + } + } + std::cout << std::endl; } diff --git a/01_week/tasks/quadratic/quadratic.cpp b/01_week/tasks/quadratic/quadratic.cpp index abf7d632..73ad53e0 100644 --- a/01_week/tasks/quadratic/quadratic.cpp +++ b/01_week/tasks/quadratic/quadratic.cpp @@ -1,6 +1,37 @@ -#include +#include +#include +#include void SolveQuadratic(int a, int b, int c) { - throw std::runtime_error{"Not implemented"}; + if (a == 0 && b == 0 && c == 0) { + std::cout << "infinite solutions"; + return; + } + + if (a == 0) { + b != 0 ? std::cout << -(static_cast(c) / b) : std::cout << "no solutions"; + return; + } + + if (b == 0 && c == 0) { + std::cout << 0; + return; + } + + double discriminant = static_cast(b) * b - 4.0 * a * c; + + if (discriminant > 0) { + double sqrt_d = std::sqrt(discriminant); + double x1 = (-b - sqrt_d) / (2.0 * a); + double x2 = (-b + sqrt_d) / (2.0 * a); + std::cout << std::setprecision(6) << x1 << ' ' << x2; + } + else if (discriminant == 0) { + double x = -b / (2.0 * a); + std::cout << std::setprecision(6) << x; + } + else { + std::cout << "no solutions"; + } } \ No newline at end of file diff --git a/01_week/tasks/rms/rms.cpp b/01_week/tasks/rms/rms.cpp index 6882f0a9..63daf000 100644 --- a/01_week/tasks/rms/rms.cpp +++ b/01_week/tasks/rms/rms.cpp @@ -1,7 +1,14 @@ -#include -#include +#include double CalculateRMS(double values[], size_t size) { - throw std::runtime_error{"Not implemented"}; + if (size == 0 || values == nullptr) return .0; + + double result = 0; + for (size_t i = 0; i < size; ++i) { + result += values[i]*values[i]; + } + + // size неявно преобразуется к double + return std::sqrt(result / size); } \ No newline at end of file diff --git a/02_week/tasks/func_array/func_array.cpp b/02_week/tasks/func_array/func_array.cpp index b327e68d..1fbd9527 100644 --- a/02_week/tasks/func_array/func_array.cpp +++ b/02_week/tasks/func_array/func_array.cpp @@ -1,6 +1,15 @@ -#include +#include -double ApplyOperations(double a, double b /* other arguments */) { - throw std::runtime_error{"Not implemented"}; +double ApplyOperations(double a, double b, double (*func_arr[])(double, double), size_t size) { + if (size == 0) return 0; + + double result = 0; + + for (size_t i = 0; i < size; ++i) { + if (func_arr[i]) + result += func_arr[i](a, b); + } + + return result; } \ No newline at end of file diff --git a/02_week/tasks/last_of_us/last_of_us.cpp b/02_week/tasks/last_of_us/last_of_us.cpp index c7bf1a25..96995c94 100644 --- a/02_week/tasks/last_of_us/last_of_us.cpp +++ b/02_week/tasks/last_of_us/last_of_us.cpp @@ -1,6 +1,30 @@ -#include +#include +const int* FindLastElement(const int* begin, const int* end, bool (*predicate)(int)) { + if (!begin || !end || !predicate || begin > end) + return end; -/* return_type */ FindLastElement(/* ptr_type */ begin, /* ptr_type */ end, /* func_type */ predicate) { - throw std::runtime_error{"Not implemented"}; + const int* last = end; + + for (; begin < end; ++begin) { + if (predicate(*begin)) { + last = begin; + } + } + + return last; +} + +int* FindLastElement(int* begin, int* end, bool (*predicate)(int)) { + return const_cast( + FindLastElement( + const_cast(begin), + const_cast(end), + predicate + ) + ); +} + +inline int* FindLastElement(std::nullptr_t, std::nullptr_t, bool (*)(int)) { + return nullptr; } \ No newline at end of file diff --git a/02_week/tasks/little_big/little_big.cpp b/02_week/tasks/little_big/little_big.cpp index abe24379..f114553c 100644 --- a/02_week/tasks/little_big/little_big.cpp +++ b/02_week/tasks/little_big/little_big.cpp @@ -1,10 +1,24 @@ -#include +#include +#include +void PrintMemory(const u_char* begin, size_t size, bool is_little_endian) { + const u_char* end = begin + size; -void PrintMemory(int /* write arguments here */) { - throw std::runtime_error{"Not implemented"}; + std::cout << "0x" << std::hex << std::uppercase; + for ( + const u_char* ptr = is_little_endian ? end - 1 : begin; + is_little_endian ? ptr >= begin : ptr < end; + is_little_endian ? --ptr : ++ptr + ) { + std::cout << std::setfill('0') << std::setw(2) << static_cast(*ptr); + } + std::cout << std::endl; } -void PrintMemory(double /* write arguments here */) { - throw std::runtime_error{"Not implemented"}; -} \ No newline at end of file +void PrintMemory(int num, bool is_little_endian = false) { + PrintMemory(reinterpret_cast(&num), sizeof(num), is_little_endian); +} + +void PrintMemory(double num, bool is_little_endian = false) { + PrintMemory(reinterpret_cast(&num), sizeof(num), is_little_endian); +} diff --git a/02_week/tasks/longest/longest.cpp b/02_week/tasks/longest/longest.cpp index 04b3c354..be3163e2 100644 --- a/02_week/tasks/longest/longest.cpp +++ b/02_week/tasks/longest/longest.cpp @@ -1,6 +1,42 @@ -#include +#include -/* return_type */ FindLongestSubsequence(/* ptr_type */ begin, /* ptr_type */ end, /* type */ count) { - throw std::runtime_error{"Not implemented"}; +char* FindLongestSubsequence(const char* begin, const char* end, size_t& count) { + if (!begin || !end || begin >= end) { + count = 0; + return nullptr; + } + + int max_len = 1; + int curr_len = 1; + char* sub_begin = nullptr; + char* result = const_cast(begin); + + for (; begin < end - 1; ++begin) { + if (*(begin + 1) == *begin) { + if (sub_begin == nullptr) { + sub_begin = const_cast(begin); + } + ++curr_len; + continue; + } + else if (curr_len > max_len) { + max_len = curr_len; + result = sub_begin; + + } + + curr_len = 1; + sub_begin = nullptr; + } + + if (curr_len > max_len) { + max_len = curr_len; + curr_len = 1; + result = sub_begin; + } + + count = max_len; + + return result; } diff --git a/02_week/tasks/pretty_array/pretty_array.cpp b/02_week/tasks/pretty_array/pretty_array.cpp index 48eab341..d5c17aa9 100644 --- a/02_week/tasks/pretty_array/pretty_array.cpp +++ b/02_week/tasks/pretty_array/pretty_array.cpp @@ -1,6 +1,27 @@ -#include +#include -void PrintArray(/* write arguments here */) { - throw std::runtime_error{"Not implemented"}; +void PrintArray(const int* begin, const int* end, const int threshold = 0) { + if (!begin || !end) { + std::cout << "[]\n"; + return; + } + + std::cout << "["; + for ( + const int* ptr = begin; + begin < end ? ptr < end : ptr > end; + begin < end ? ++ptr : --ptr + ) { + if (threshold && static_cast(ptr - begin) % threshold == 0 && ptr != begin) { + std::cout << "...\n "; + } + + std::cout << *ptr; + + if (ptr != (begin < end ? end - 1 : end + 1)) { + std::cout << ", "; + } + } + std::cout << "]" << std::endl; } \ No newline at end of file diff --git a/02_week/tasks/swap_ptr/swap_ptr.cpp b/02_week/tasks/swap_ptr/swap_ptr.cpp index 93db625d..228e1a68 100644 --- a/02_week/tasks/swap_ptr/swap_ptr.cpp +++ b/02_week/tasks/swap_ptr/swap_ptr.cpp @@ -1,6 +1,5 @@ -#include - - -void SwapPtr(/* write arguments here */) { - throw std::runtime_error{"Not implemented"}; +void SwapPtr(auto*& ptr1, auto*& ptr2) { + auto temp = ptr1; + ptr1 = &*ptr2; + ptr2 = &*temp; } \ No newline at end of file diff --git a/03_week/tasks/data_stats/data_stats.cpp b/03_week/tasks/data_stats/data_stats.cpp index b941c211..0e2dd483 100644 --- a/03_week/tasks/data_stats/data_stats.cpp +++ b/03_week/tasks/data_stats/data_stats.cpp @@ -1,4 +1,6 @@ -#include +#include +#include +#include struct DataStats { @@ -6,6 +8,20 @@ struct DataStats { double sd = 0.0; }; -/* return_type */ CalculateDataStats(/* args */) { - throw std::runtime_error{"Not implemented"}; +DataStats CalculateDataStats(const std::vector& arr) { + DataStats data_stats{}; + + if (arr.empty()) return data_stats; + + // Считаем обычную сумму и сумму квадратов вектора + for (size_t i = 0; i < arr.size(); ++i) { + data_stats.avg += arr[i]; + data_stats.sd += std::pow(arr[i], 2); + } + + // По суммам считаем среднее и sd + data_stats.avg /= arr.size(); + data_stats.sd = std::sqrt(data_stats.sd / arr.size() - std::pow(data_stats.avg, 2)); + + return data_stats; } diff --git a/03_week/tasks/easy_compare/easy_compare.cpp b/03_week/tasks/easy_compare/easy_compare.cpp index dd5cb7f6..36c8cf85 100644 --- a/03_week/tasks/easy_compare/easy_compare.cpp +++ b/03_week/tasks/easy_compare/easy_compare.cpp @@ -1,10 +1,11 @@ -#include - +#include +#include +#include struct Date { - unsigned year; - unsigned month; - unsigned day; + unsigned year = 0u; + unsigned month = 0u; + unsigned day = 0u; }; struct StudentInfo { @@ -13,4 +14,55 @@ struct StudentInfo { int score; unsigned course; Date birth_date; -}; \ No newline at end of file +}; + +bool operator<(const Date& lhs, const Date& rhs) { + return std::tie(lhs.year, lhs.month, lhs.day) < std::tie(rhs.year, rhs.month, rhs.day); +} + +bool operator==(const Date& lhs, const Date& rhs) { + return std::tie(lhs.year, lhs.month, lhs.day) == std::tie(rhs.year, rhs.month, rhs.day); +} + +bool operator!=(const Date& lhs, const Date& rhs) { + return !(lhs == rhs); +} + +bool operator>(const Date& lhs, const Date& rhs) { + return !(lhs < rhs || lhs == rhs); +} + +bool operator>=(const Date& lhs, const Date& rhs) { + return !(lhs < rhs); +} + +bool operator<=(const Date& lhs, const Date& rhs) { + return lhs < rhs || lhs == rhs; +} + +bool operator<(const StudentInfo& lhs, const StudentInfo& rhs) { + return ( + std::tie(rhs.mark, lhs.score, rhs.course, lhs.birth_date) + < std::tie(lhs.mark, rhs.score, lhs.course, rhs.birth_date) + ); +} + +bool operator==(const StudentInfo& lhs, const StudentInfo& rhs) { + return lhs.mark == rhs.mark && lhs.score == rhs.score; +} + +bool operator!=(const StudentInfo& lhs, const StudentInfo& rhs) { + return !(lhs == rhs); +} + +bool operator>(const StudentInfo& lhs, const StudentInfo& rhs) { + return !(lhs < rhs || lhs == rhs); +} + +bool operator>=(const StudentInfo& lhs, const StudentInfo& rhs) { + return !(lhs < rhs); +} + +bool operator<=(const StudentInfo& lhs, const StudentInfo& rhs) { + return lhs < rhs || lhs == rhs; +} \ No newline at end of file diff --git a/03_week/tasks/enum_operators/enum_operators.cpp b/03_week/tasks/enum_operators/enum_operators.cpp index a539be38..38fbaa04 100644 --- a/03_week/tasks/enum_operators/enum_operators.cpp +++ b/03_week/tasks/enum_operators/enum_operators.cpp @@ -1,5 +1,6 @@ -#include -#include +#include +#include +#include enum class CheckFlags : uint8_t { NONE = 0, @@ -12,22 +13,78 @@ enum class CheckFlags : uint8_t { ALL = TIME | DATE | USER | CERT | KEYS | DEST }; -/* return_type */ operator|(/* args */) { - throw std::runtime_error{"Not implemented"}; +std::pair truncate_invalid_flags(CheckFlags lhs, CheckFlags rhs) { + uint8_t all = static_cast(CheckFlags::ALL); + uint8_t left = static_cast(lhs) & all; + uint8_t right = static_cast(rhs) & all; + return std::pair{left, right}; } -/* return_type */ operator&(/* args */) { - throw std::runtime_error{"Not implemented"}; +uint8_t truncate_invalid_flags(uint8_t flags) { + uint8_t all = static_cast(CheckFlags::ALL); + uint8_t flags_cleared = flags & all; + return flags_cleared; } -/* return_type */ operator^(/* args */) { - throw std::runtime_error{"Not implemented"}; +CheckFlags operator|(CheckFlags lhs, CheckFlags rhs) { + auto [left, right] = truncate_invalid_flags(lhs, rhs); + return static_cast(left | right); } -/* return_type */ operator~(/* args */) { - throw std::runtime_error{"Not implemented"}; +bool operator&(CheckFlags lhs, CheckFlags rhs) { + auto [left, right] = truncate_invalid_flags(lhs, rhs); + + if (left == 0u || right == 0u) { + return false; + } + + uint8_t result = left & right; + return (result == left || result == right); +} + +CheckFlags operator^(CheckFlags lhs, CheckFlags rhs) { + auto [left, right] = truncate_invalid_flags(lhs, rhs); + return static_cast(left ^ right); +} + +CheckFlags operator~(CheckFlags flags) { + uint8_t flags_inverted = ~static_cast(flags); + uint8_t flags_cleared = truncate_invalid_flags(flags_inverted); + return static_cast(flags_cleared); } -/* return_type */ operator<<(/* args */) { - throw std::runtime_error{"Not implemented"}; +std::ostream& operator<<(std::ostream& os, CheckFlags flags) { + const static std::pair checks[] { + {CheckFlags::TIME, "TIME"}, + {CheckFlags::DATE, "DATE"}, + {CheckFlags::USER, "USER"}, + {CheckFlags::CERT, "CERT"}, + {CheckFlags::KEYS, "KEYS"}, + {CheckFlags::DEST, "DEST"} + }; + + bool empty = true; + bool first = true; + + for (const auto& [flag, name] : checks) { + uint8_t flags_int = static_cast(flags); + uint8_t check_int = static_cast(flag); + CheckFlags single_flag = static_cast(flags_int & check_int); + if (single_flag != CheckFlags::NONE) { + if (first) { + first = false; + } + else { + os << ", "; + } + os << name; + empty = false; + } + } + + if (empty) { + os << "NONE"; + } + + return os; } diff --git a/03_week/tasks/filter/filter.cpp b/03_week/tasks/filter/filter.cpp index 6648cb39..86e7855a 100644 --- a/03_week/tasks/filter/filter.cpp +++ b/03_week/tasks/filter/filter.cpp @@ -1,6 +1,18 @@ -#include +#include +#include -/* return_type */ Filter(/* args */) { - throw std::runtime_error{"Not implemented"}; +void Filter(std::vector& arr, bool (*filter_func)(int)) { + if (!filter_func) return; + + size_t j = 0; + + for (size_t i = 0; i < arr.size(); ++i) { + if (filter_func(arr[i])) { + arr[j++] = arr[i]; + } + } + + size_t new_size = j; + arr.resize(new_size); } \ No newline at end of file diff --git a/03_week/tasks/find_all/find_all.cpp b/03_week/tasks/find_all/find_all.cpp index 74f393b2..9f088ff4 100644 --- a/03_week/tasks/find_all/find_all.cpp +++ b/03_week/tasks/find_all/find_all.cpp @@ -1,6 +1,20 @@ -#include +#include +#include -/* return_type */ FindAll(/* args */) { - throw std::runtime_error{"Not implemented"}; +std::vector FindAll(const std::vector& arr, bool (*bool_func)(int)) { + std::vector result{}; + + if (!bool_func) return result; + + result.reserve(arr.size()); + + for (size_t i = 0; i < arr.size(); ++i) { + if (bool_func(arr[i])) { + result.push_back(i); + } + } + + result.shrink_to_fit(); + return result; } \ No newline at end of file diff --git a/03_week/tasks/minmax/minmax.cpp b/03_week/tasks/minmax/minmax.cpp index c2869799..adc13ccc 100644 --- a/03_week/tasks/minmax/minmax.cpp +++ b/03_week/tasks/minmax/minmax.cpp @@ -1,6 +1,29 @@ -#include +#include +typedef std::pair::const_iterator, std::vector::const_iterator> MinMaxIter; -/* return_type */ MinMax(/* args */) { - throw std::runtime_error{"Not implemented"}; +MinMaxIter MinMax(const std::vector& arr) { + auto first = arr.begin(), last = arr.end(); + + if (first == last) + return {last, last}; + + auto min = first, max = first; + + while (++first != last) { + auto i = first; + if (++first == last) { + min = (*i < *min) ? i : min; + max = (*i >= *max) ? i : max; + break; + } + + auto smaller = (*first < *i) ? first : i; + auto larger = (*first < *i) ? i : first; + + min = (*smaller < *min) ? smaller : min; + max = (*larger >= *max) ? larger : max; + } + + return {min, max}; } diff --git a/03_week/tasks/os_overload/os_overload.cpp b/03_week/tasks/os_overload/os_overload.cpp index e473418d..86d3b8d2 100644 --- a/03_week/tasks/os_overload/os_overload.cpp +++ b/03_week/tasks/os_overload/os_overload.cpp @@ -1,21 +1,49 @@ -#include #include #include +#include struct Coord2D { - int x; - int y; + int x = 0; + int y = 0; }; struct Circle { - Coord2D coord; - unsigned radius; + Coord2D coord = {}; + unsigned radius = 1; }; using CircleRegion = std::pair; using CircleRegionList = std::vector; -/* return_type */ operator<<(/* args */) { - throw std::runtime_error{"Not implemented"}; +std::ostream& operator<<(std::ostream& lhs, const Coord2D& rhs) { + return lhs << '(' << rhs.x << ", " << rhs.y << ')'; +} + +std::ostream& operator<<(std::ostream& lhs, const Circle& rhs) { + if (rhs.radius == 0) { + return lhs << "circle[]"; + } + + return lhs << "circle[" << rhs.coord << ", r = " << rhs.radius << "]"; +} + +std::ostream& operator<<(std::ostream& lhs, const CircleRegion& rhs) { + return lhs << (rhs.second ? '+' : '-') << rhs.first; +} + +std::ostream& operator<<(std::ostream& lhs, const CircleRegionList& rhs) { + lhs << "{"; + + if (!rhs.empty()) { + lhs << "\n\t" << rhs[0]; + } + + for (size_t i = 1; i < rhs.size(); ++i) { + lhs << ",\n\t" << rhs[i]; + } + + lhs << (rhs.empty() ? "}" : "\n}"); + + return lhs; } diff --git a/03_week/tasks/range/range.cpp b/03_week/tasks/range/range.cpp index d2085495..9869410a 100644 --- a/03_week/tasks/range/range.cpp +++ b/03_week/tasks/range/range.cpp @@ -1,7 +1,16 @@ -#include #include +#include -std::vector Range(int from, int to, int step) { - throw std::runtime_error{"Not implemented"}; +std::vector Range(int from, int to, const int step = 1) { + std::vector result{}; + result.reserve(std::abs(from - to)); + + while ((from > to && step < 0) || (from < to && step > 0)) { + result.push_back(from); + from += step; + } + + result.shrink_to_fit(); + return result; } diff --git a/03_week/tasks/unique/unique.cpp b/03_week/tasks/unique/unique.cpp index 9d2545bb..291e4733 100644 --- a/03_week/tasks/unique/unique.cpp +++ b/03_week/tasks/unique/unique.cpp @@ -1,6 +1,17 @@ -#include #include +#include -/* return_type */ Unique(/* args */) { - throw std::runtime_error{"Not implemented"}; + +std::vector Unique(const std::vector& arr) { + std::vector unique{}; + unique.reserve(arr.size()); + + for (size_t i = 0; i < arr.size(); ++i) { + if (unique.empty() || unique[unique.size() - 1] != arr[i]) { + unique.push_back(arr[i]); + } + } + + unique.shrink_to_fit(); + return unique; } diff --git a/04_week/04_class.md b/04_week/04_class.md deleted file mode 100644 index 46e00076..00000000 --- a/04_week/04_class.md +++ /dev/null @@ -1,902 +0,0 @@ -# Лекция 4. ООП. Класс - -1. [ООП](#oop) - - [Инкапсуляция](#encapsulation) - - [Наследование](#inheritance) - - [Полиморфизм](#polymorphism) - - [Абстракция](#abstraction) -1. [Класс](#class) - - [Спецификаторы доступа](#access_specifiers) - - [Отличие класса и структуры](#class_struct_diff) - - [Пустой класс](#class_empty) - - [Поля класса](#class_fields) - - [Инициализация полей значением по умолчанию](#field_default_init) - - [Конструктор](#ctor) - - [Список инициализации полей класса](#member_initializer_list) - - [Параметризованный конструктор](#parameterized_ctor) - - [Конструктор по умолчанию](#default_ctor) - - [Конструктор копирования](#copy_ctor) - - [Указатель на себя `this`](#this_ptr) - - [Копирующий оператор присваивания](#copy_assignment) - - [Деструктор](#dtor) - - [Конструктор преобразования](#converting_ctor) - - [Ключевое слово `explicit`](#explicit) - - [Конструктор от `std::initializer_list`(_C++11_)](#ctor_std_initializer_list) - - [Делегирующий конструктор (_C++11_)](#delegating_ctor) - - [Ключевое слово `default` (_С++11_)](#keyword_default) - - [Ключевое слово `delete` (_С++11_)](#keyword_delete) - - [Методы](#methods) - - [Определение методов вне класса](#methods_definition_outside) - - [CV-квалификация методов ](#cv_for_methods) - - [Оператор преобразования](#conversion_operator) - - [Перегрузка операторов внутри класса](#class_operator_overloading_inside) - - [Перегрузка операторов вне класса](#class_operator_overloading_outside) - - [Ключевое слово `friend`](#keyword_friend) - - [Ключевое слово `mutable`](#keyword_mutable) - - -## ООП - -Объектно-ориентированное программирование - парадигма программирования, которая -основывается на представление в коде программы различных объектов, взаимодействующих -друг с другом. - -Класс - пользовательский тип данных, шаблон (макет) для создания объектов и описания -их характеристик, функций. - -Объект - экземпляр класса. Объект включает данные (поля) и методы (функции). -Что позволяет хранить характеристики объекта, изменять их и взаимодействовать с -другими объектами. - -Основные принципы ООП: - -- Инкапсуляция -- Наследование -- Полиморфизм -- Абстракция - -### Инкапсуляция - -Инкапсуляция - объединение данных и методов для работы с данными внутри класса. -Сокрытие деталей реализации класса. - -```c++ -class Budget { -public: - void increase_balance(double value) { - budget_ += value; - } -private: - double budget_; -}; -``` - -### Наследование - -Наследование - механизм создания новых классов на основе существующих. Позволяет -строить иерархию классов и переиспользовать код классов родителей внутри -классов наследников. - -```c++ -class Animal { /* common data */}; -class Cat : public Animal {}; -class Dog : public Animal {}; -``` - -### Полиморфизм - -Полиморфизм - возможность различного поведения сущностей C++. - -Виды полиморфизма: - -- статический (на этапе компиляции, шаблоны, перегрузка функций) -- динамический (во время выполнения программы, виртуальные методы) - -```c++ -class Shape { -public: - virtual void draw() = 0; -}; -class Circle : public Shape { - void draw() override { /* рисуем круг */ } -}; -``` - -### Абстракция - -Абстракция - упрощение сложных вещей через выделение основных характеристик. - -## Класс - -Класс - пользовательский тип данных, который объединяет в себе данные (поля класса) -и функции для работы с данными (методы класса), представляет собой макет для -создания объектов (экземпляров) данного типа. - -Синтаксис: `class {};` - -- `` - имя класса, пользовательского типа данных -- `` - тело класса, включающее поля, методы, конструкторы и деструктор - -### Спецификаторы доступа - -Для ограничения видимости полей и методов внутри класса используются -спецификаторы доступа, весь код после спецификатора имеет соответствующий тип -доступа: - -- `public` - публичный доступ, поле или метод класса доступны извне -- `protected` - защищенный доступ, поля и методы доступны наследникам класса - и внутри класса -- `private` - приватный доступ, поля и методы доступны только внутри класса. - -Синтаксис внутри класса или структуры: `:` - -Указывать спецификаторы доступа можно произвольное число раз. - -```c++ -class User { -public: - /* some data and functions for everyone */ -protected: - /* some data and functions for children classes */ -private: - /* some data and functions inside class */ -}; -``` - -Приватные поля и методы будут недоступны снаружи, то есть **НЕЛЬЗЯ** к ним -обратится или вызвать через экземляр класс, используя операторы `.`, `->`. - -Всё содержимое класса по умолчанию имеет спецификатор доступа `private`, -несмотря на это часто принято явно указывать данный спецификатор, даже при -определении полей класса в самом начале тела класса. - -### Отличие класса и структуры - -Структура `struct` и класс `class` имеют одинаковые возможности в C++. - -Отличие заключается, что содержимое структуры по умолчанию имеет публичный -доступ `public`, а содержимое класса приватный `private` - -Структура нужна для взаимодействия с `legacy` кодом на языке C, а также для -публичных классов. - -Несмотря на одинаковые возможности, принято разделять структуру и класс -семантически. Так, структуру используют только с публичными полями, а класс -с приватными. Создавать классы и структуры со смешанным типом полей не -рекомендуется, так как это может быть не очевидно и не понятно программистам, -читающим код. - -### Пустой класс - -Пустой класс имеет размер 1 байт, поскольку объект такого класса можно создать -и необходимо иметь адресс данного объекта, чтобы иметь адресс, необходимо что-то -положить в память по определенному адресу. - -Чаще используется пустая структура. Такая структура может понадобитсья в качестве -именнованного тега. Пока будем просто считать, что иногда надо. - -### Поля класса - -Поля класса представляют собой внутренние переменные произвольного типа. -К полям класса внутри класса можно обращаться по имени. В качестве поля можно -использовать указатели и ссылки. - -В случае ссылок необходимо их инициализировоать при создании объекта. Например, -можно проинициализирвоать адресом объекта из глобальной области видимости. А еще -это можно сделать в списке инициализации при конструировании объекта. - -Существуют разные стили кода к именованию полей класса. Часто встречается: - -- `m_` - добавляют `m_` в качестве префикса к перименной (`m` - `member`) -- `_` - добавляют `_` в качестве постфикса к переменной. - -```c++ -// inside class -int m_value; -int value_; -``` - -Поля класса хранятся в классе и инициализируются в порядке их объявления. - -Поля уникальны для каждого экземпляра класса. - -### Инициализация полей значением по умолчанию - -Аналогично структурам рекомендуется всегда инициализировать поля внутри класса. - -```c++ -class Time { -private: - int hour_ = 0; - int minute_{0}; // uniform -}; -``` - -Иначе в полях класса также может лежать мусор. - -### Конструктор - -Конструктор это особый метод класса, который используется для конструирования -объекта. - -Синтаксис: `() {}` - -- `` - имя конструктора должно совпадать с именем класса -- `` - аргументы конструктора. -- `` - тело конструктора. - -В зависимости от аргументов конструктора выделяют различные типы конструктора. -Основные способы конструирования объекта: - -- Параметризованный конструктор -- Конструктор по умолчанию -- Конструктор копирования -- Копирующий оператор присваивания -- Конструктор перемещения -- Перемещающий оператор присваивания - -Важно понимать, что если конструкторы не определены, то компилятор самостоятельно -сгенерирует конструкторы. Но если определен, хотя бы один конструктор, то -компилятор скорее всего этого не сделает. - -Важно понимать, что при входе в тело конструктора все поля уже проинициализированы -и в теле может происходить только присваивание новых значений полям класса. -Следовательно, в теле уже нельзя изменить константное поле или инициализировать -ссылку. - -Проинициализировать константу и ссылку можно не только значением по -умолчанию или значением (адресом) переменной из глобальной области видимости. -Для этого в синтаксисе конструктора предусмотрен список инициализации. - -### Список инициализации полей класса - -Список инициализации полей (_member initializer list_) позволяет инициализировать -поля в момент создания объекта. В списке инициализации доступны аргументы -конструктора и имена полей класса. Список инициализации указывается между сигнатурой -и телом конструктора, и выглядит как перечисление после символа `:` через запятую -полей класса и внутри `()` или `{}` их инициализирующих значений. - -Синтаксис: `() : {}` - -- `` - список инициализации = `(), {}` - -```c++ -class InitList { -public: - InitList(int val) : a_(val), b_(val), c_(val) {} -public: - int a_; int b_; int c_; -}; -``` - -Причем имена полей класса и имена параметров могут полностью совпадать, конфликта -имен не будет, поскольку компилятор понимает, что нужно инициализировать поля. - -```c++ -class InitSameName { -public: - InitSameName(int a, int b, int c) : a(a), b(b), c(c) {} -public: - int a; int b; int c; -}; -``` - -Также, следует отметить, что в качестве инициализирующего значения, может использоваться -не только переменная или константа, но также выражение (_expression_) и результат -вызова функции. - -**ВАЖНО**, что инициализация происходит в порядке полей класса, и не зависит от -порядка в списке инициализации. Поэтому важно самостоятельно отслеживать -правильный порядок инициализации. - -```c++ -class BadOrderInit { -public: - BadOrderInit(int val) : c(val), b(c + 1), a(10) {} -public: - int a; int b; int c; -}; -``` -- `c` используется неинициализированной при инициализации `b` (**UB**) - -Если поля класса объявлены со значением по умолчанию, то они будут проигнорированы -для полей в списке инициализации. - -```c++ -class BadOrderInit { -public: - BadOrderInit(int val) : c(val), b(c + 1), a(10) {} -public: - int a = 7; int b = 7; int c = 7; -}; -``` -- значение `7` будет проигнорированно, по-прежнему **UB** - -Списки инициализации могут быть неполными. Тогда недостающие поля будут -сконструированы со значениями по умолчанию, а при их отсутствии инициализируются -мусором. - -```c++ -class BadOrderInitNoAB { -public: - BadOrderInitNoAB(int val) : c(val) {} -public: - int a; int b = 7; int c = 7; -}; -``` -- в поле `b` будет значение `7`, в `a` будет мусор - -Список инициализации позволяет сконструировать константное поле и поле ссылку извне: - -```c++ -class RefConst { -public: - RefConst(int value, int& ref, const double& cref) - : id_(value), ref_(ref), const_ref_(cref) {} -private: - const int id_; - int& ref_; - const double& const_ref_; -}; -``` - -### Параметризованный конструктор - -Конструктор, который имеет параметры (аргументы) называют параметризованным -конструктором (конструктором с параметрами). Аргументов может быть несколько -и они могут иметь значения по умолчанию, Таким образом, конструктор может -быть перегружен. - -```c++ -class Time { -public: - Time(int hour, int minute, int second) - : hour_(hour), minute_(minute), second_(second) {} -private: - int hour_, minute_, second_; -}; -``` - -Если конструктор имеет у всех аргументов значение по умолчанию, то такой -конструктор перегружает конструктор по умолчанию. - -```c++ -class Time { -public: - Time(int hour = 0, int minute = 0, int second = 0) - : hour_(hour), minute_(minute), second_(second) {} -private: - int hour_, minute_, second_; -}; -``` - -Для создания объекта класса необходим вызов конструктора. Синтаксис вызова констуктора: - -```c++ -Time t1(1, 1, 1); -Time t2{1, 1, 1}; -Time t3 = {1, 1, 1} -Time t4 = Time{1, 1, 1}; -Time t5 = Time(1, 1, 1); -``` - -Аналогично для всех его вариантов перегрузки. - -### Конструктор по умолчанию - -Конструктор по умолчанию представляет собой конструктор без аргументов. -Часто для простых класов конструктор имеет пустое тело. Удобно использовать -значение по умолчанию для инициализации. - -Синтаксис: `() {}` - -Часто имеет пустое тело для тривиальных случаев. - -Если не определен ни один конструктор, то компилятор самостоятельно сгенерирует -данный конструктор. - -```c++ -class DefaultCtor { -public: - DefaultCtor() {} -private: - int value = 0; -}; -``` - -Вызов конструктора: - -```c++ -DefaultCtor obj; -DefaultCtor obj2{}; -DefaultCtor obj3 = {}; -DefaultCtor obj4 = DefaultCtor{}; -DefaultCtor obj5 = DefaultCtor(); -``` -- во всех этих случаях вызовется только конструктор по умолчанию один раз - -### Конструктор копирования - -Конструктор копирования необходим для создания копии объекта из объекта того -же типа. Представляет собой конструктор, принимающий в качестве аргументов -константную ссылку того же типа, что и сам класс. - -Синтаксис: `(const & ) {}` - -```c++ -class Time { -public: - Time(const Time& other) - : hour_(other.hour_), minute_(other.minute_), second_(other.second_) {} -private: - int hour_, minute_, second_; -}; -``` - -Поля другого объекта того же класса доступны внутри методов класса даже если они -приватные. - -Вызывается конструктор копирования: - -- при передаче в функцию по значению -- при возврате объекта соответствующего значения по значению -- при конструировании одного объекта из другого - -```c++ -Time t; -Time t1 = t; // copy ctor -Time t2(t); // copy ctor -Time t3{t}; // copy ctor -``` - -### Указатель на себя `this` - -Внутри класса, в методах, в том числе конструкторах, можно получить указатель на -себя (объект класса, который вызывает данный метод) с помощью ключевого слова `this`. - -Можно использовать `this`, как в качестве значения по умолчанию, так и в списке -инициализации. - -```c++ -class Self { -public: - Self() : self(this) {}; - Self* GetPtr() { return self; } - Self& GetRef() { return *this; } -private: - Self* self = this; -}; -``` - -Можно считать что указатель на себя передается первым неявным аргументом в конструкторы, -методы и операторы класса. - -Через указатель можно явно обращаться к полям класса, но как правило, так не делают - -```c++ -// inside methods -this->self; -``` - -### Копирующий оператор присваивания - -Оператор присвания необходим при присваивании одного созданного объекта другому. -Если один из объектов не создан, то он не будет вызываться, а будет вызываться -конструктор копирования, даже если в инструкции есть `=`. - -Как правило, оператор возвращает ссылку на себя (экземпляр текущего класса), что -позволяет испоьзовать цепочку из операторов `=`. Для этого необходимо вернуть из -оператора разыменованный указатель на себя `return *this;`. - -Синтаксис: `& operator=(const & ) {}` - -Поскольку язык не запрещает присвоить объект самому себе, как правило, в копирующем -операторе присваивания выполняют проверку на самоприсваивание. Особенно это -критично для классов владеющих ресурсами (выделяющих память), что может привести -к **UB** - -```c++ -class Time { -public: - Time& operator=(const Time& other) { - if (this == &other) { - return *this; - } - hour_ = other.hour_; - minute_ = other.minute_; - second_ = other.second_; - return *this; - } -private: - int hour_, minute_, second_; -}; -``` - -Вызов оператора: - -```c++ -Time t1, t2, t3; -t1 = t2; // copy assignment -t1 = t1; // copy assignment -t1 = t2 = t3; // copy assignment -auto t4 = t1; // copy ctor (not a copy assignment!) -``` - -### Деструктор - -Особый метод, вызываемый перед разрушением объекта, когда заканчивается время -жизни объекта. - -Синтаксис: `~() {}` - -Если в конструкторе выполнялось ручное выделение ресурсов, то в деструкторе -необходимо обязательно освободить ресурсы. Иначе деструктор остается тривиальным -и генерируется компилятором по умолчанию. - -Деструкторы вызываются в обратном порядке по отношению к конструируемым объектам -при выходе из области видимости. Последний сконструированный объект, будет разрушен -первым. - -### Конструктор преобразования - -Конструктором преобразования называется конструктор, принимающий один аргумент -другого произвольного типа. Данный конструктор разрешает неявное преобразование -из указанного типа в тип класса. - -```c++ -class Integer { -private: - int value; -public: - Integer(int v) : value(v) {} - Integer(char c) : value(static_cast(c)) {} -}; -``` - -Таким образом, если функция принимает пользовательский класс, а класс имеет -конструктор преобразования от другого типа, то в функцию можно передать -непосредственно этот другой тип, произойдет неявное преобразование с помощью -соответствующего конструктора: - -```c++ -int DoSomething(Integer i) {} - -int main() { - Integer i{3}; - int value = 5; - char c = 'I'; - DoSomething(i); // OK - DoSomething(value); // OK - DoSomething(5); // OK - DoSomething(c); // OK - DoSomething('i'); // OK -} -``` - -**ВАЖНО** понимать, что при наличии конструктора присваивания из другого типа, -компилятор **НЕ** будет генеировать оператор присваивания из данного типа, его -необходимо определять самостоятельно. - -### Ключевое слово `explicit` - -Ключевое слово `explicit` используется как спецификатор перед именем конструктора -и позволяет запретить неявное преобразование и сообщает компилятору, что данный -конструктор можно вызывать только явно. - -```c++ -class Integer { -private: - int value; -public: - Integer(int v) : value(v) {} - Integer(char c) : value(static_cast(c)) {} - explicit Integer(double d) : value(static_cast(d)) {} -}; -``` - -Неявно такой конструктор вызвать нельзя: - -```c++ -//Integer i2 = 3.14; // compile error -Integer i3 = Integer{3.14}; // OK - -int DoSomething(Integer i) {} - -int main() { - double d = 3.14; - //DoSomething(d); // compile error - //DoSomething(3.14); // compile error - DoSomething(Integer{3.14}); // OK - DoSomething(Integer(3.14)); // OK -} -``` - -Также спецификатор `explicit` можно использовать с оператором преобразования, об этом -после знакомства с методами. - -### Конструктор от `std::initializer_list`(_C++11_) - -В C++11 появился контейнер список инициализации `std::initializer_list`, который -позволяет инициализировать класс набором элементов. Что вызывает неоднозначность -при наличии параметризированных конструкторов какой конструктор вызывать. - -Конструктор по умолчанию имеет приоритет перед конструктором от списка инициализации. - -Список инициализации имеет приоритет перед параметризированными конструкторами при -использовании `{}`. - -```c++ -class Vector { -public: - Vector() {}; - Vector(size_t count); - Vector(int a, int b); - Vector(std::initializer_list list); -private: - std::vector data; -}; -``` - -Вызов конструкторов: - -```c++ -Vector v = {1, 2, 3, 4, 5}; // ctor std::initializer_list -Vector v2{1, 2, 3}; // ctor std::initializer_list -Vector v3(10); // ctor Vector(size_t) -Vector v4{10}; // ctor std::initializer_list -Vector v5 = {10}; // ctor std::initializer_list -Vector v6(10, 20); // ctor Vector(int a, int b) -Vector v7{10, 20}; // ctor std::initializer_list -Vector v8 = {10, 20}; // ctor std::initializer_list -Vector v9 = 10; // ctor Vector(size_t) implicit cast -Vector v10; // default ctor -Vector v11{}; // default ctor -Vector v12 = {}; // default ctor -``` - -### Делегирующий конструктор (_C++11_) - -Делегирующий конструктор - конструктор, который на месте списка инициализации -использует другой конструктор данного класса. В таком случае можно указать только -один целевой конструктор, дополнительно списки инициализации указать нельзя. - -```c++ -class Time { -public: - Time(int hour, int minute, int second) - : hour_(hour), minute_(minute), second_(second) {} - Time(int hour) : Time(hour, 0, 0) {} -private: - int hour_, minute_, second_; -}; -``` - -Делегирующий конструктор **НЕ** может быть рекурсивным. - -### Ключевое слово `default` (_С++11_) - -С помощью ключевого слова `default` можно явно попросить компилятор сгенерировать -конструктор (деструктор), указав после сигнатуры `= default`. Это более выразительно, -чем писать `{}` для конструктора по умолчанию. Рекомендуется к использованию. - -```c++ -class Value { -public: - Value(int x) : x_(x) {} - Value() = default; - Value(const Value& other) = default; - Value(Value&& other) = default; - Value& operator=(const Value& other) = default; - Value& operator=(Value&& other) = default; - ~Value() = default; -private: - int x = 0; -}; -``` - -### Ключевое слово `delete` (_С++11_) - -С помощью ключевого слова `delete` можно явно попросить компилятор удалить функцию -(запретить её использование), указав после сигнатуры `= delete`. Это более выразительно, -чем прятать конструкторы в приватную область класса. Рекомендуется к использованию. - -Можно использовать не только для конструкторов, деструкторов, но и для любых методов, -операторов, шаблонных функций, функций вне классов. - -```c++ -class Value { -public: - Value(int x) : x_(x) {} - Value() = delete; - Value(const Value&) = delete; - Value& operator=(const Value&) = delete; -private: - int x = 0; -}; -``` - -Например, если класс не подразумевает сравнения на равенство или других операторов -можно явно указать для них `delete`. - -### Методы - -Внутри класса можно определять функции, которые могут работать с полями класса, в том -числе закрытыми. Данные функции называются методы. - -Синтаксис аналогичен определению обычным функциям. - -Публичный метод можно вызвать через операторы `.` для экземпляра и `->` для указателяю. - -Приватные методы, можно вызывать внутри класса. - -Можно вызывать методы в списках инициализации. Например, метод, который будет -контролировать допустимость значения или выполнять дополнительные преобразования. - -### Определение методов вне класса - -Методы можно объявить внутри класса, а определить снаружи класса. Содержимое -класса имеет свою область видимости. Для определения снаружи класса перед именем -конструктора, метода, оператора используется имя класса и оператор разрешения области -видимости `::` - -```c++ -class Time { -public: - Time(); - Time(int hours, int minutes, int seconds = 0); - int GetHours(); - void SetHours(int hours); -private: - int hours_ = 0; - int minutes_ = 0; - int seconds_ = 0; -}; - -Time::Time() = default; -Time::Time(int hours, int minutes, int seconds) - : hours_(hours), minutes_(minutes), seconds_(seconds) {} - -int Time::GetHours() { return hours; } -void Time::SetHours(int hours) { hours_ += hours; } -``` - -Аргументы методов, имеющие значения по умолчанию указываются только при объявлении, -при определении нельзя указать значения по умолчанию - -### CV-квалификация методов - -Методы могут иметь CV-квалификацию. Методы, которые не изменяют полей класса, а только -предоставляют информацию о них следует помечать квалификатором `const` после сигнатуры -и перед телом метода: - -```c++ -class Size { -public: - size_t GetSize() const { return size; }; - void AddSize(size_t size) { size_ += size; }; -private: - size_t size_ = 0; -}; -``` - -Методы помеченные квалификатором `const` можно вызывать у константных объектов класса. -Компилятор отслеживает, что в данном методе нет измененеий полей класса. - -Методы можно перегрузить только по квалификатору `const`. - -Не изменяет поля класса и может быть вызван для константного объекта: - -```c++ -int Class::foo() const; -``` - -Может изменять поля класса и может быть вызван для `volatile` объекта: - -```c++ -int Class::foo() volatile; -``` - -Может быть вызван как для `const`, так и для `volatile` объекта, так и для -`const volatile` объекта: - -```c++ -int Class::foo() const volatile; -``` - -### Оператор преобразования - -В классе возможно определить оператор преобразования, который позволяет преобразовывать -пользовательский класс в другой тип. - -Синтаксис: ` operator () const {}` - -- `` - можно запретить неявное преобразование -- `` - тип к которому выполняется приведение - -Рекомендуется помечать `const` поскольку данный оператор не должен менять полей класса -и вызываться от констант данного класса. - -Как правило рекомендуется запрещать неявное преобразование к типу (использовать -`explicit`), поскольку можно обнаружить много неожиданных мест в коде, где неявно -произведено преобразование. - -Исключением обычно является оператор `bool` для удобства использования в условиях. - -### Перегрузка операторов внутри класса - -Поскольку первым аргументом неявно передается ключевое слово `this`, то перегрузка -бинарных операторов внутри класса имеет один аргумент: - -```c++ -Class& operator+=(const Class& other); -Class& operator-=(const Class& other); -Class& operator*=(const Class& other); -Class& operator/=(const Class& other); -``` - -Операторы арифмесстических операций часто переопределяют на основе работы присваивающих -операторов: - -```c++ -Class operator+(const Class& other) const { - Class result = *this; // copy ctor - result += other; // operator += - return result; -} -``` - -Операторы префиксного и постфиксного инкремента/декремента переопределяются -следующим образом: - -```c++ -Class& operator++(); // ++obj -Class operator++(int); // obj++ -Class& operator--(); // --obj -Class operator--(int); // obj-- -``` -- постфиксный оператор возвращает копию, поэтому у возвращаемого значения нет `&` - -### Перегрузка операторов вне класса - -Операторы можно перегрузить вне класса, тогда сигнатура перегружаемого оператора -пишется в привычной манере. Но для реализации таких операторов у класса должны быть -методы задающие и считывающие значение полей (геттеры и сеттеры). Бывает, что их нет, -тогда перегрузить класс не получится или получится на основе определенных операторов -составного присваивания внутри класса. - -Перегрузка инкремента и декремента вне класса: - -```c++ -Class& operator++(const Class& obj); // ++obj -Class operator++(const Class& obj, int); // obj++ -Class& operator--(const Class& obj); // --obj -Class operator--(const Class& obj, int); // obj-- -``` - -### Ключевое слово `friend` - -Внутри класса с помощью ключевого слова `friend` (_friend declaration_) можно -объявить дружественную функцию, класс или дружественный метод другого класса. - -Сущности объявленные дружественными будут иметь доступ к `private` и `protected` -полям класса. - -Дружественность работает в одностороннем порядке. - -```c++ -friend void SomeMethod(int); -friend struct SomeStruct; -friend class SomeClass; -friend OtherClass; // C++11 -friend int OtherClass::Method(); -``` - -### Ключевое слово `mutable` - -Спецификатор типа `mutable` разрешает изменять поле класса, объявленное с ним, -даже в константных методах и для константных объектов. - -Например, это может быть поле представляющее собой счетчик операций и необходимо его -изменять даже в константном методе. - -Также может использоваться в лямбда-выражениях \ No newline at end of file diff --git a/04_week/04_class.pdf b/04_week/04_class.pdf deleted file mode 100644 index 50a488d9..00000000 Binary files a/04_week/04_class.pdf and /dev/null differ diff --git a/04_week/tasks/phasor/phasor.cpp b/04_week/tasks/phasor/phasor.cpp index 3ec1b9ad..21dfa5ed 100644 --- a/04_week/tasks/phasor/phasor.cpp +++ b/04_week/tasks/phasor/phasor.cpp @@ -1,10 +1,206 @@ - +#include +#include +#include struct ExpTag {}; struct DegTag {}; struct AlgTag {}; +double areDoublesEqual(const double a, const double b, const double eps = 1e-12) { + return std::abs(a - b) < eps; +} class Phasor { - +public: + Phasor(); + Phasor(const double m, const double phi); + Phasor(const double m, const double phi, const ExpTag& tag); + Phasor(const double m, const double deg, const DegTag& tag); + Phasor(const double x, const double y, const AlgTag& tag); + void SetPolar(const double m, const double phi); + void SetCartesian(const double x, const double y); + double Magnitude() const; + double Abs() const; + double Phase() const; + double Angle() const; + double PhaseDeg() const; + double AngleDeg() const; + double Real() const; + double Imag() const; + Phasor Conj() const; + Phasor Inv() const; + bool operator==(const Phasor& other) const; + bool operator!=(const Phasor& other) const; + Phasor operator-() const; + Phasor& operator+=(const Phasor& other); + Phasor& operator-=(const Phasor& other); + Phasor& operator/=(const Phasor& other); + Phasor& operator*=(const Phasor& other); +private: + double m_m = .0; + double m_phi = .0; + double m_degToRad(const double deg) const; + double m_radToDeg(const double deg) const; + void m_normalizeMagnitude(); + void m_normalizePhase(); + void m_normalizePhasor(); }; + +double Phasor::m_degToRad(const double deg) const { + return deg * M_PI / 180; +} + +double Phasor::m_radToDeg(const double rad) const { + return rad * 180 / M_PI; +} + +void Phasor::m_normalizeMagnitude() { + if (m_m < 0) { + m_m = std::abs(m_m); + m_phi += (m_phi > 0) ? -M_PI : M_PI; + } +} + +void Phasor::m_normalizePhase() { + m_phi = std::atan2(std::sin(m_phi), std::cos(m_phi)); + + if (m_phi <= -M_PI) { + m_phi = M_PI; + } +} + +void Phasor::m_normalizePhasor() { + m_normalizeMagnitude(); + m_normalizePhase(); +} + +void Phasor::SetCartesian(const double x, const double y) { + m_m = sqrt(pow(x, 2) + pow(y, 2)); + m_phi = atan2(y, x); + m_normalizePhasor(); +} + +Phasor::Phasor() {} +Phasor::Phasor(const double m, const double phi = 0) : m_m(m), m_phi(phi) { + m_normalizePhasor(); + +} +Phasor::Phasor(const double m, const double phi, const ExpTag&) : Phasor(m, phi) {} +Phasor::Phasor(const double m, const double deg, const DegTag&) : Phasor(m, m_degToRad(deg)) {} +Phasor::Phasor(const double x, const double y, const AlgTag&) { + SetCartesian(x, y); +} + +void Phasor::SetPolar(const double m, const double phi) { + m_m = m; + m_phi = phi; + m_normalizePhasor(); +} + +double Phasor::Magnitude() const { + return m_m; +} + +double Phasor::Abs() const { + return m_m; +} + +double Phasor::Phase() const { + return m_phi; +} + +double Phasor::Angle() const { + return m_phi; +} + +double Phasor::PhaseDeg() const { + return m_radToDeg(m_phi); +} + +double Phasor::AngleDeg() const { + return m_radToDeg(m_phi); +} + +double Phasor::Real() const { + return m_m * cos(m_phi); +} + +double Phasor::Imag() const { + return m_m * sin(m_phi); +} + +Phasor Phasor::Conj() const { + return Phasor(m_m, -m_phi); +} + +Phasor Phasor::Inv() const { + return Phasor(1 / m_m, -m_phi); +} + +bool Phasor::operator==(const Phasor& other) const { + return areDoublesEqual(m_m, other.m_m) && areDoublesEqual(m_phi, other.m_phi); +} + +bool Phasor::operator!=(const Phasor& other) const { + return !(*this == other); +} + +Phasor Phasor::operator-() const { + return Phasor(-m_m, m_phi); +} + +Phasor& Phasor::operator+=(const Phasor& other) { + SetCartesian(Real() + other.Real(), Imag() + other.Imag()); + return *this; +} + +Phasor& Phasor::operator-=(const Phasor& other) { + SetCartesian(Real() - other.Real(), Imag() - other.Imag()); + return *this; +} + +Phasor& Phasor::operator*=(const Phasor& other) { + SetPolar(m_m * other.m_m, m_phi + other.m_phi); + return *this; +} + +Phasor& Phasor::operator/=(const Phasor& other) { + SetPolar(m_m / other.m_m, m_phi - other.m_phi); + return *this; +} + +std::ostream& operator<<(std::ostream& os, const Phasor& phasor) { + return ( + os << std::fixed << std::setprecision(3) << phasor.Magnitude() + << "*e(j*" << phasor.PhaseDeg() << ") [" << phasor.Real() + << " + j*" << phasor.Imag() << "]" << std::endl + ); +} + +Phasor operator+(const Phasor& lhs, const Phasor& rhs) { + return Phasor(lhs.Real() + rhs.Real(), lhs.Imag() + rhs.Imag(), AlgTag {}); +} + +Phasor operator-(const Phasor& lhs, const Phasor& rhs) { + return Phasor(lhs.Real() - rhs.Real(), lhs.Imag() - rhs.Imag(), AlgTag {}); +} + +Phasor operator*(const Phasor& lhs, const Phasor& rhs) { + return Phasor(lhs.Abs() * rhs.Abs(), lhs.Phase() + rhs.Phase()); +} + +Phasor operator/(const Phasor& lhs, const Phasor& rhs) { + return Phasor(lhs.Abs() / rhs.Abs(), lhs.Phase() - rhs.Phase()); +} + +Phasor MakePhasorCartesian(const double x, const double y) { + return Phasor(x, y, AlgTag {}); +} + +Phasor MakePhasorPolar(const double m, const double phi) { + return Phasor(m, phi, ExpTag {}); +} + +Phasor MakePhasorPolarDeg(const double m, const double deg) { + return Phasor(m, deg, DegTag {}); +} \ No newline at end of file diff --git a/04_week/tasks/queue/queue.cpp b/04_week/tasks/queue/queue.cpp index 2a9f8493..b21cb484 100644 --- a/04_week/tasks/queue/queue.cpp +++ b/04_week/tasks/queue/queue.cpp @@ -1,6 +1,162 @@ #include - +#include +#include +#include class Queue { - +public: + Queue() = default; + Queue(std::stack stack); + Queue(std::vector vector); + Queue(const std::initializer_list& values); + Queue(const size_t size); + void Push(const int x); + bool Pop(); + int& Back(); + int Back() const; + int& Front(); + int Front() const; + bool Empty() const; + size_t Size() const; + void Clear(); + void Swap(Queue& other); + bool operator==(const Queue& other) const; + bool operator!=(const Queue& other) const; +private: + int m_null_value = 0; + std::vector m_input{}; + std::vector m_output{}; + std::vector m_mergeVectors(const auto& input, const auto& output) const; + void m_pushInputBack(); }; + +Queue::Queue(std::stack stack) { + while (!stack.empty()) { + m_output.push_back(stack.top()); + stack.pop(); + } +} + +Queue::Queue(std::vector vector) : m_output(std::move(vector)) { + std::reverse(m_output.begin(), m_output.end()); +} + +Queue::Queue(const std::initializer_list& ilist): m_input(ilist) {} + +Queue::Queue(const size_t size) { + m_input.reserve(size); + m_output.reserve(size); +} + +void Queue::Push(const int x) { + m_input.push_back(x); +} + +bool Queue::Pop() { + if (Empty()) { + return false; + } + + if (m_output.empty()) { + while (!m_input.empty()) { + m_pushInputBack(); + } + } + m_output.pop_back(); + return true; +} + +int& Queue::Back() { + if (Empty()) + return m_null_value; + + return m_input.empty() ? m_output.front() : m_input.back(); +} + +int Queue::Back() const { + if (Empty()) + return m_null_value; + + return m_input.empty() ? m_output.front() : m_input.back(); +} + +int& Queue::Front() { + if (Empty()) + return m_null_value; + + return m_output.empty() ? m_input.front() : m_output.back(); +} + +int Queue::Front() const { + if (Empty()) + return m_null_value; + + return m_output.empty() ? m_input.front() : m_output.back(); +} + +bool Queue::Empty() const { + return m_input.empty() && m_output.empty(); +} + +size_t Queue::Size() const { + return m_input.size() + m_output.size(); +} + +void Queue::Clear() { + m_input.clear(); + m_output.clear(); +} + +void Queue::Swap(Queue& other) { + m_input.swap(other.m_input); + m_output.swap(other.m_output); +} + +std::vector Queue::m_mergeVectors(const auto& input, const auto& output) const { + std::vector merged{}; + merged.reserve(input.size() + output.size()); + + for (int i = output.size() - 1; i >= 0; --i) { + merged.push_back(output[i]); + } + + for (size_t i = 0; i < input.size(); ++i) { + merged.push_back(input[i]); + } + + return merged; +} + +void Queue::m_pushInputBack() { + m_output.push_back(m_input.back()); + m_input.pop_back(); +} + +bool Queue::operator==(const Queue& other) const { + if (this == &other) { + return true; + } else if (Size() != other.Size()) { + return false; + } + + const size_t out_size = m_output.size(); + const size_t other_out_size = other.m_output.size(); + const size_t total = Size(); + + for (size_t i = 0; i < total; ++i) { + const int& a = (i < out_size) + ? m_output[out_size - 1 - i] + : m_input[i - out_size]; + + const int& b = (i < other_out_size) + ? other.m_output[other_out_size - 1 - i] + : other.m_input[i - other_out_size]; + + if (a != b) return false; + } + return true; +} + +bool Queue::operator!=(const Queue& other) const { + return !(*this == other); +} \ No newline at end of file diff --git a/04_week/tasks/ring_buffer/ring_buffer.cpp b/04_week/tasks/ring_buffer/ring_buffer.cpp index e2b57ba2..db6805b5 100644 --- a/04_week/tasks/ring_buffer/ring_buffer.cpp +++ b/04_week/tasks/ring_buffer/ring_buffer.cpp @@ -1,6 +1,219 @@ #include +#include +#include class RingBuffer { - +public: + RingBuffer(const size_t size); + RingBuffer(const size_t size, const int val); + RingBuffer(const std::initializer_list ilist); + RingBuffer(const RingBuffer& other); + void Push(const int item); + bool TryPush(const int item); + void Pop(); + bool TryPop(int& pop_value); + int& Front(); + int Front() const; + int& Back(); + int Back() const; + bool Empty() const; + bool Full() const; + size_t Size() const; + size_t Capacity() const; + void Clear(); + void Resize(const size_t size); + std::vector Vector() const; + int& operator[](const size_t idx); + int operator[](const size_t idx) const; + RingBuffer& operator=(const RingBuffer& other); +private: + size_t m_size = 0; + size_t m_begin = 0; + size_t m_end = 0; + std::vector m_buffer{}; + size_t m_checkZeroSize(const size_t size) const; + void m_popCore(); + void m_pushBackCore(const int item); + void m_copyCore(const RingBuffer& other); + int m_last() const; }; + +size_t RingBuffer::m_checkZeroSize(const size_t size) const { + return (size == 0) ? 1 : size; +} + +int RingBuffer::m_last() const { + return (m_end == 0) ? Capacity() - 1 : m_end - 1; +} + +RingBuffer::RingBuffer(size_t size) { + m_buffer.reserve(m_checkZeroSize(size)); +} + +RingBuffer::RingBuffer(const size_t size, const int val) + : m_size(m_checkZeroSize(size)), + m_end(m_size), + m_buffer(m_size, val) +{} + +RingBuffer::RingBuffer(const std::initializer_list ilist) : m_buffer(ilist) { + if (m_buffer.empty()) { + m_buffer.reserve(1); + } + m_size = m_buffer.size(); + m_end = m_buffer.size(); +} + +void RingBuffer::m_copyCore(const RingBuffer& other) { + m_begin = other.m_begin; + m_end = other.m_end; + m_size = other.m_size; + m_buffer = other.m_buffer; + m_buffer.reserve(other.Capacity()); +} + +RingBuffer::RingBuffer(const RingBuffer& other) { + m_copyCore(other); +} + +void RingBuffer::m_pushBackCore(const int item) { + if (m_buffer.size() == Capacity()) { + m_buffer[m_end] = item; + if (m_size != m_buffer.size()) { + ++m_size; + } + else { + m_begin = (m_begin + 1) % Capacity(); + } + } + else { + m_buffer.push_back(item); + ++m_size; + } + m_end = (m_end + 1) % Capacity(); +} + +void RingBuffer::Push(const int item) { + m_pushBackCore(item); +} + +bool RingBuffer::TryPush(const int item) { + if (Full()) { + return false; + } + + m_pushBackCore(item); + return true; +} + +void RingBuffer::m_popCore() { + m_begin = (m_begin + 1) % Capacity(); + --m_size; +} + +void RingBuffer::Pop() { + if (Size() == 0) { + return; + } + + m_popCore(); +} + +bool RingBuffer::TryPop(int& pop_value) { + if (Size() == 0) { + return false; + } + pop_value = m_buffer[m_begin]; + m_popCore(); + return true; +} + +int& RingBuffer::Front() { + return m_buffer[m_last()]; +} + +int RingBuffer::Front() const { + return m_buffer[m_last()]; +} + +int& RingBuffer::Back() { + return m_buffer[m_begin]; +} + +int RingBuffer::Back() const { + return m_buffer[m_begin]; +} + +bool RingBuffer::Empty() const { + return Size() == 0; +} + +bool RingBuffer::Full() const { + return Capacity() == Size(); +} + +size_t RingBuffer::Size() const { + return m_size; +} + +size_t RingBuffer::Capacity() const { + return m_buffer.capacity(); +} + +void RingBuffer::Clear() { + m_buffer.clear(); + m_size = 0; +} + +std::vector RingBuffer::Vector() const { + std::vector vector_buffer; + vector_buffer.reserve(Size()); + + for (size_t i = 0; i < Size(); ++i) { + vector_buffer.push_back(m_buffer[(m_begin + i) % Capacity()]); + } + + return vector_buffer; +} + +void RingBuffer::Resize(const size_t size) { + size_t new_size = m_checkZeroSize(size); + + if (new_size == Capacity()) { + return; + } + + std::vector new_buffer; + new_buffer.reserve(new_size); + + for ( + size_t i = (Size() > new_size) ? Size() - new_size : 0; + new_buffer.size() != new_buffer.capacity() && i < Size(); + ++i + ) { + new_buffer.push_back(m_buffer[(m_begin + i) % Capacity()]); + } + + m_buffer = std::move(new_buffer); + m_begin = 0; + m_size = m_buffer.size(); + m_end = Size(); +} + +int& RingBuffer::operator[](const size_t idx) { + return m_buffer[(m_begin + idx) % Capacity()]; +} + +int RingBuffer::operator[](const size_t idx) const { + return m_buffer[(m_begin + idx) % Capacity()]; +} + +RingBuffer& RingBuffer::operator=(const RingBuffer& other) { + if (this == &other) { + return *this; + } + + m_copyCore(other); + return *this; +} diff --git a/04_week/tasks/stack/stack.cpp b/04_week/tasks/stack/stack.cpp index 222e4ffc..4a91ce60 100644 --- a/04_week/tasks/stack/stack.cpp +++ b/04_week/tasks/stack/stack.cpp @@ -1,6 +1,73 @@ #include +#include class Stack { - +public: + void Push(const int x); + bool Pop(); + int& Top(); + int Top() const; + bool Empty() const; + size_t Size() const; + void Clear(); + void Swap(Stack& other); + bool operator==(const Stack& other) const; + bool operator!=(const Stack& other) const; +private: + int m_null_value = 0; + std::vector m_stack{}; }; + +void Stack::Push(const int x) { + m_stack.push_back(x); +} + +bool Stack::Pop() { + if (Empty()) { + return false; + } + + m_stack.pop_back(); + return true; +} + +int Stack::Top() const { + if (Empty()) { + return m_null_value; + } + + return m_stack.back(); +} + +int& Stack::Top() { + if (Empty()) { + return m_null_value; + } + + return m_stack.back(); +} + +bool Stack::Empty() const { + return m_stack.empty(); +} + +size_t Stack::Size() const { + return m_stack.size(); +} + +void Stack::Clear() { + return m_stack.clear(); +} + +void Stack::Swap(Stack& other) { + m_stack.swap(other.m_stack); +} + +bool Stack::operator==(const Stack& other) const { + return m_stack == other.m_stack; +} + +bool Stack::operator!=(const Stack& other) const { + return m_stack != other.m_stack; +} diff --git a/05_week/05_memory_move.md b/05_week/05_memory_move.md new file mode 100644 index 00000000..5cffaf03 --- /dev/null +++ b/05_week/05_memory_move.md @@ -0,0 +1,1007 @@ +# Лекция 5. Память программы на C++. Семантика перемещения + +1. [Память программы (процесса)](#process_memory) + - [Стек](#stack) + - [Куча](#heap) + - [Сегмент BSS данных (Block Started by Symbol)](#bss_segment) + - [Сегмент данных (Data Segment)](#data_segment) + - [Сегмент данных для чтения (RODATA Segment)](#rodata_segment) + - [Сегмент кода программы (Text Segment) ](#text_segment) + - [Сегмент локальных переменных потока (Thread Local Segment)](#tls_segment) +1. [Продолжительность хранения (_Storage duration_)](#storage_duration) + - [Static storage duration](#static_storage_duration) + - [Automatic storage duration](#automatic_storage_duration) + - [Dynamic storage duration](#dynamic_storage_duration) + - [Thread storage duration](#thread_storage_duration) +1. [Связывание (_linkage_)](#linkage) + - [Внешнее связывание (_external linkage_)](#external_linkage) + - [Внутренее связывание (_internal linkage_)](#internal_linkage) + - [Отсутствие связывания (_no linkage_)](#no_linkage) + - [Модульное связывания (_module linkage_) C++20](#module_linkage) +1. [Ключевое слово `static`](#static) + - [Вне класса. Глобальная статическая переменная](#global_static_var) + - [Вне класса. Локальная статическая переменная](#local_static_var) + - [Вне класса. Глобальная статическая функция](#global_static_function) + - [Внутри класса. Статическое поле](#static_field_in_class) + - [Внутри класса. Статический метод](#static_function_in_class) +1. [Initialization Order Fiasco](#initialization_order_fiasco) +1. [Ключевое слово `inline`](#inline) +1. [Работа с динамической памятью](#cpp_dynamic_memory) + - [Операторы `new` и `delete`](#new_delete) + - [Операторы `new[]` и `delete[]`](#new_delete_array) + - [Переопределение операторов `new` и `delete`](#new_delete_overloading) +1. [Ключевое слово `noexcept`](#noexcept) +1. [Категория выражений ([value categories](https://en.cppreference.com/w/cpp/language/value_category.html))](#value_categories) + - [rvalue-ссылка](#rvalue_reference) + - [Связывание ссылок](#reference_binding) + - [Перегрузка функции по ссылке](#function_overloading_by_reference) + - [Квалификация метода по виду ссылки ](#reference_qualifiers_class_method) + - [Temporary materialization (С++17)](#temporary_materialization) +1. [Семантика перемещения](#move_semantics) + - [Функция `std::move`](#std_move) + - [Правило нуля (_Rule of Zero_)](#rule_of_zero) + - [Правило пяти (_Rule of Five_)](#rule_of_five) + - [Правила для специальных методов](#rules_for_specific_methods) +1. [Copy Elision](#copy_elision) + - [Return Value Optimization (RVO)](#rvo) + - [Named Return Value Optimization (NRVO)](#nrvo) + + +## Память программы (процесса) + +Программа имеет виртуальное адресное пространство. При запуске программа +не полностью загружается в оперативную память. Операционная система (ОС) +настраивает таблицы страниц и выделяет области памяти для программы. +Таблицы страниц служат для отображения виртуальных адресов программы на C++ +в физические адреса оперативной памяти. + +В языке C++ упрощенно можно выделить следующие сегменты памяти: + +- **автоматический** - представлен стеком, служит для автоматического выделения + и освобождения памяти под локальные переменные, аргументы функции. +- **статический** - для глобальных и статических переменных, существует всю жизнь + программы +- **динамический** - представлен кучей, позволяет получать память в использование + во время выполнения программы и требует управления выделением и освобождением + памяти +- **потоковый** - для специально помеченных локальных переменных потока, существует + всю жизнь потока + +Память процесса можно разделить на следующие сегменты: + +- Stack (Стек) +- TLS (Thread local segment) +- Heap (Куча) +- BSS (Block Started by Symbol) +- Data Segment +- RODATA Segment (read only) +- Text Segment (Code) + +### Стек + +Стек имеет ограниченный размер, который зависит от ОС и её настроек. По умолчанию +размер составляет 1-8 Мб (Windows, Linux). Стек организован по принципу LIFO + +Выделяемая память на стеке **НЕ** инициализируется по умолчанию. + +При вызове функции на стеке формируется стек фрейм, в котором размещаются +параметры функции, локальные переменные, адрес возврата, сохраненные регистры. + +Компилятор **НЕ** гарантирует, что переменные на стеке будут лежать в порядке +объявления. + +При выходе из области видимости вызываются деструкторы объектов в порядке +обратном созданию + +При выходе из функции указатель текущего фрейма стека (stack pointer) просто +сдвигается, тем самым память автоматически считается свободной. + +Переполнение стека приводит к ошибке stack overflow -> segmentation fault. + +К переполнению может приводить как размещение слишком больших массивов, так +и глубокий рекурсивный вызов функий. + +### Куча + +Динамическая память (куча) представляет собой участок памяти, требующий ручного +управления. Динамическая память выделяется во время выполнения программы и имеет +продолжительность хранения, пока не будет освобождена. + +Куча медленнее стека поскольку при выделении может происходить запрос к +менеджеру памяти ОС (системный вызов) во время выполнения программы. + +### Сегмент BSS данных (Block Started by Symbol) + +Данный сегмент хранит глобальные и статические переменные, которые не имеют +явной инициализации или инициализированы нулем. + +Загрузчик ОС при запуске программы выделяет память под эти переменные и зануляет +её, поэтому они не занимают места в бинарном файле, что позволяет экономить место. + +### Сегмент данных (Data Segment) + +Данный сегмент хранит глобальные и статические переменные, которые явно +инициализированы ненулевыми значениями. Переменные занимают место в бинарном +файле + +### Сегмент данных для чтения (RODATA Segment) + +Как следует из названия в данном сегменте лежат данные для чтения. Поэтому +попытка изменения данных приводит к ошибкам компиляции или к UB, падению +программы при попытке изменить константу. Продолжительность хранения данного +сегмента статическая - все время жизни программы + +В данном сегменте лежат именованные константы, строковые литералы. +Строковые литералы могут лежать в единственном экземпляре. + +### Сегмент кода программы (Text Segment) + +В данном сегменте располагается машинный код программы. В данном сегменте +записаны функции, методы и точка входа в программу `main`. Как правило, +данный сегмент предназначен только для чтения и является разделяемым +(_shared_) между потоками. + +### Сегмент локальных переменных потока (Thread Local Segment) + +В данном сегменте располагаются локальные переменные потока. Для таких переменных +при объявлении используется ключевое слово `thread_local` перед типом. У каждого +потока свои копии переменных, а их время жизни соответствует времени жизни потока. + +## Продолжительность хранения (_Storage duration_) + +В языке C++ с сегментами памяти тесно связано понятие storage duration. + +Продолжительность хранения (storage duration) - период, в течение которого +зарезервирована память для объекта. + +Продолжительность хранения это свойство объекта, которое определяет минимальный +потенциальный срок жизни выделенной памяти под объект. + +В языке C++ выделены следующие виды продолжительности хранения + +- static +- automatic +- dynamic +- thread + +### Static storage duration + +Static storage duration - память выделяется и освобождается независимо от области +видимости объекта и продолжает существовать все время выполнение процесса. + +Соответствует сегментам памяти `.bss`, `.data`, `.rodata` + +Глобальные, статические переменные, в том числе объявленные внутри `namespace` +имеют static storage duration + +Создается до входа в `main` (для локальных статических переменных при первом +входе) + +Порядок инициализации между единицами трансляции **НЕ** определен. Следовательно, +некорректно инициализировать переменную со статическим storage duration, переменной +с аналогичным storage duration из другой единицы трансляции (файла) + +```c++ +int global; +int global_init = 18; +static int static_value = 10; +extern int value; + +namespace utils { + const double IN_TO_CM = 2.54; +} + +int& get_static() { + static int instance; + return &instance; +} +``` + +### Automatic storage duration + +Automatic storage duration - память выделяется при входе в область видимости и +освобождается при выходе из области видимости, причем вызываются деструкторы. + +Представлен стеком. + +```c++ +void function(int a, double b, const char* name) { + std::string info(name); + { + double num = a * b; + info += std::to_string(num); + } + std::cout << info << std::endl; +} +``` +- переменные a, b, name, info, num - располагаются на стеке + +### Dynamic storage duration + +Dynamic storage duration - управление памяти осуществляется вручную. + +Представлен кучей + +```c++ +int* ptr = new int(5); +int* arr = new int[5]{1, 2, 3}; +delete ptr; +delete[] arr; +``` + +### Thread storage duration + +Thread storage duration - память выделяется при запуске потока и освобождается +при его завершении. + +У каждого потока своя копия + +```c++ +thread_local int value = 18; +``` + +## Связывание (_linkage_) + +Связывание определяет видимость идентификаторов между единицами трансляции. +Связывание не относится к памяти напрямую, а затрагивает связывание имён на +этапе линковки + +### Внешнее связывание (_external linkage_) + +**External linkage** - внешнее связывание означает, что идентификатор (имя) +виден в других единицах трансляции. По умолчанию для сущностей C++ в глобальной +области видимости. + +### Внутренее связывание (_internal linkage_) + +**Internal linkage** - внутреннее связывание, означает, что идентификатор (имя) +виден только в текущей единице трансляции. Для `static` переменных, `const` +переменных, имен шаблонных параметров. + +Таким образом `const` в заголовочном файле, включаемый в разные `.cpp` файлы, +представлен копией в каждой единице трансляции. + +Для внешнего связывания `const` переменной используется ключевое слово `extern` + +Для внешнего связывания константы в C++17 используется ключевое слово +`inline constexpr` + +### Отсутствие связывания (_no linkage_) + +**No linkage** - отсутствует связывание, означает, что идентификатор (имя) виден +только в области видимости. Для параметров функций, имен шаблонных параметров, +локальных переменных. + +### Модульное связывания (_module linkage_) C++20 + +В языке C++20 появились модули `module` и вместе с ними появился новый вид связывания + +**Module linkage** (C++20) - модульное связывания означает, что идентификатор (имя) +виден в пределах модуля и не виден извне. + +Модуль может включать несколько единиц трансляции. + +## Ключевое слово `static` + +Ключевое слово `static` применяется к идентификатору переменной или +функции и имеет разные смыслы в зависимости от контекста использования. + +### Вне класса. Глобальная статическая переменная + +**Глобальная переменная**, помеченная ключевым словом `static`, имеет +статическую продолжительность хранения (static storage duration) и +внутреннее связывание (internal linkage), то есть видна только в текущей +единице трансляции. Что позволяет скрыть реализацию в .cpp файле и избежать +конфликтов **ODR** (_One Definition Rule_) + +```c++ +static int value = 18; +``` + +**НО** если определить её в `.h` файле, то каждый включающий её `.cpp` +файл будет иметь копию, видную только в пределах данного файла. + +### Вне класса. Локальная статическая переменная + +**Локальная статическая переменная** (внутри функции) имеет _static +storage duration_ и область видимости внутри функции, а также +инициализируется только **один раз** при первом вызове функции. + +```c++ +void f() { + static int counter = 0; + ++counter; + std::cout << "f() call count = " << counter << std::endl; +} +``` +- **НЕ** видна вне функции + +Как правило используется в качестве кэша, счетчика, синглтона +(singleton), при ленивой инициализации (lazy initialization) + +### Вне класса. Глобальная статическая функция + +**Глобальная функция**, помеченная ключевым словом `static`, также +имеет внутреннее связывание и не видна из других единиц трансляции +(.cpp файлов) + +```c++ +static void inner_helper(); +``` + +В современном C++ заменяют `static` в данном контексте на размещение +глобальной функции или переменной в безымянном пространстве имен +`namespace` + +### Внутри класса. Статическое поле + +**Переменная внутри класса**, объявленная с ключевым словом `static`, имеет +статическую продолжительность хранения (static storage duration) и внешнее +связывание (external linkage), если нет inline. + +Статическая переменная класса принадлежит классу, а не конкретному экземпляру. + +Доступ к статическому полю принято осуществлять посредством имени класса и оператора +`::`. Также доступно через экземпляр класса и оператор `.` + +Статическое поле является объявлением. Требует определения только в одном `.cpp`, +поэтому инициализация в теле класса вызывает ошибку **ODR violation**. + +```c++ +// .h +class C { + static int counter; // declaration + // static int counter = 0; // compile error +}; +// .cpp +int C::counter = 0; // definition +``` + +Инициализация `static` поля внутри класса разрешена для интегральных констант. + +**НО** для получения адреса константного статического поля необходимо определение +**ВНЕ** класса, даже если значение проинициализировано. + +```c++ +// .h +class C { + static const int multipliyer = 2; // declaration + // static const double PI = 3.14; // error double is not integral +}; +// .cpp +const int C::multipliyer; // definition +const int* ptr = &C::multipliyer; // work after definition +``` + +Инициализация `static` поля внутри класса разрешена для `constexpr` полей (C++11). + +```c++ +// .h +class C { + static constexpr int multipliyer = 2; // declaration + static constexpr double PI = 3.141592653589793; //ok +}; +// .cpp +constexpr int C::multipliyer; // definition +const int* ptr = &C::multipliyer; // work after definition +``` +- Но для получения адреса необходимо определение **ВНЕ** класса до C++17. + Начиная с C++17 `constexpr` по умолчанию является `inline` и определение + **ВНЕ** класса **НЕ** требуется + +Инициализация `static` поля внутри класса разрешена для `inline static` полей (C++17). +Является определением, определение **ВНЕ** класса **НЕ** требуется, разрешено +в заголовке. + +```c++ +class C { + inline static int multipliyer = 2; + inline static std::string default_name = "name"; +}; +``` + +### Внутри класса. Статический метод + +**Функция внутри класса**, объявленная с ключевым словом `static` не привязана +к какому-либо экземпляру (объекту) класса, а логически принадлежит всему классу. +Следовательно, внутри функции нет указателя на объект `this`. + +```c++ +struct A { + static int f(); +}; +``` + +Внутри функции можно обращаться только к `static` полям, к обычным нельзя + +Вызвать функцию можно с указанием принадлежности классу, используя оператор `::` + +```c++ +int value = A::f(); +``` + +## Initialization Order Fiasco + +Initialization Order Fiasco - Ошибка порядка статической инициализации, +которая возникает, когда один объект со _static storage duration_ использует +для инициализации другой объект из другого файла с аналогичным _storage duration_, +поскольку порядок инициализации между единицами трансляции не определен. + +Следующий код вызывает **UB**: + +```c++ +// a.cpp +extern int y; +int x = y + 1; + +// b.cpp +int y = 10; +``` +- порядок инициализации внутри одной единцы трансляции сверху вниз + +## Ключевое слово `inline` + +Ключевое слово `inline` означает, что можно иметь несколько определений, +если они одинаковы. + +Для переменных (C++17) это значит, что это одно логическое определение и можно +размещать в заголовке. + +Таким образом, использование `inline static` используется для вспомогательных +функций, определяемых в заголовочном файле. + +## Работа с динамической памятью + +При выполнении операции выделения памяти процесс обращается к ОС, ОС находит +свободные физические кадры памяти и обновляет таблицу страниц, чтобы отобразить +виртуальный адрес (возвращаемый в программе) на этот физический кадр. Если +физическая память заканчивается, ОС перемещает старые страницы на диск (подкачка), +помечая запись и загружает новые. Если запрошенная по адресу переменная не +находится в таблице страниц, происходит _page fault_ и ОС загружает нужную страницу +обратно в память. Поэтому работа с динамической памятью медленнее, чем со стеком. + +Выделенная процессу память будет считаться занятой, пока с помощью операции +освобождения памяти она не будет явно освобождена и отдана ОС. Если динамическую +память не освободить, то она будет освобождена только по факту завершения процесса + +При работе с динамической памятью могут возникать следующие проблемы: + +- Утечки памяти (memory leak) - когда выделенная память не освобождается и уже + не используется. Такая память будет считаться ОС занятой до тех пор, пока не + завершится весь процесс (программа). + +- Двойное удаление (double free) - когда производится вызов оператора освобождения + памяти дважды для одного указателя, что приведет к ошибке и падению программы + +- Висячий указатель (dangling pointer) - когда указатель, указывает на область + памяти, которая уже была освобождена (удалена), но сам указатель не был обнулен, + либо это другой указатель на ту же область памяти. + +### Операторы `new` и `delete` + +Для выделения динамической памяти используется оператор `new`, а для освобождения +оператор `delete`. + +Синтаксис: `* = new ;` +Синтаксис с инициализацией: +- `* = new ();` +- `* = new {};` + +```c++ +int* ptr = new int; +int* ptr_init = new int(5); +delete ptr; +delete ptr_init; +``` + +### Операторы `new[]` и `delete[]` + +Также есть соответствующая версии операторов для работы массивами `new[]`, +`delete[]`. Обязательно необходимо освобождать память с использованием парного +оператора. + +Синтаксис: `* = new [];` +Синтаксис с инициализацией аналогичен созданию обычных массивов. + +```c++ +int* arr = new int[10]; +delete[] arr; +``` + +### Переопределение операторов `new` и `delete` + +Глобальный оператор `new`: + +```c++ +void* operator new(std::size_t); +void operator delete(void*) noexcept; +``` + +Оператор `new` внутри класса: + +```c++ +struct Struct { + static void* operator new(std::size_t); + static void operator delete(void*) noexcept; +}; +``` + +Для вызова глобальных операторов в таком случае можно воспользоваться +оператором разрешения области видимости `::operator new` , `::operator delete` + +```c++ +static void* Struct::operator new(std::size_t bytes) { + std::cerr << "allocate: " << bytes << " bytes\n"; + return ::operator new(bytes); +} +static void Struct::operator delete(void* ptr, std::size_t bytes) noexcept { + std::cerr << "deallocate: " << bytes << " bytes\n"; + ::operator delete (ptr); +} +``` + +Оператор placement `new` переопределить нельзя: + +```c++ +void* operator new(std::size_t, void*) noexcept; +``` + +## Ключевое слово `noexcept` + +Ключевое слово `noexcept` является спецификатором функции (в том числе метода, +оператора) и оператором, проверяющим на этапе компиляции может ли выражение +выбрасывать исключение `noexcept()`. + +Спецификатор `noexcept` пишется после сигнатуры функции и перед `{`. +Спецификатор сообщает компилятору, что код внутри не выбрасывает исключений, +и позволяет ему выполнить оптимизации (убрать код для раскрутки стека). + +Если исключение всё же будет выброшено из помеченной функции, то вызовется +`std::terminate`, который завершит процесс. + +Хорошим тоном считается писать `noexcept` там, где действительно код не может +выбросить исключение. + +Правила использования: + +- Деструкторы по умолчанию `noexcept` +- Функции освобождения памяти (`free`, `delete`) должны быть `noexcept` +- Функции обмена (`swap`) должны быть `noexcept` +- Хорошими кандидатами на использование `noexcept` являются `const` методы +- Move-операции должны быть `noexcept`, когда это возможно +- **НЕ** следует использовать `noexcept(false)` без веской причины + +## Категория выражений ([value categories](https://en.cppreference.com/w/cpp/language/value_category.html)) + +Помимо типа каждое выражение в C++ характеризуется категорией. + +Классификация до C++11: + +- lvalue - имеет адрес, может быть слева от присваивания `=` +- rvalue - временное значение, справа от присваивания `=` + +Современная классификация (C++11) построена на двух свойствах: + +- Идентичность (I)- параметр, по которому можно понять ссылаются ли два объекта + на одну и ту же сущность (например, наличие адреса в памяти) +- Перемещаемость (M) - возможно ли переместить объект, отдать владение ресурсом + +В основу классификации легли три первичных категории + +- lvalue (locator/left value) +- xvalue (expiring value) - временный материализованный объект (с истекающим временем жизни) +- prvalue (pure rvalue) + +```text + glvalue(I) rvalue(M) + / \ / \ +lvalue(I~M) xvalue(IM) prvalue(~IM) +``` + +### rvalue-ссылка + +В C++11 вводится rvalue-ссылка `&&`. + +Основные отличия rvalue-ссылка `&&` от lvalue-ссылки `&`: + +- можно проинициализировать только rvalue выражением +- при возврате из функции `&&` возвращается rvalue выражение. +- продлевает жизнь временным объектам + +Во всем остальном ведет себя также как ссылка. +Сам по себе идентификатор, объявленный rvalue-ссылкой, является lvalue. + +```c++ +int l = 18; +int&& r = l; // compilation error (l is not rvalue) +int&& x = 18; // ok +int&& y = x; //compilation error (x is not rvalue) +int&& m = std::move(l); // ok +int&& sc = static_cast(x); // ok +m = 0; // ok l = 0, m = 0 +``` + +В языке присутствует базовое неявное приведение _lvalue-to-prvalue_ + +```c++ +int x, y; +x = x + 1; // lvalue = prvalue +y = x; // lvalue = lvalue to prvalue +y = std::move(x); // lvalue = xvalue +``` + +### Связывание ссылок + +Rvalue-ссылка **НЕ** может быть связана с **lvalue** +Lvalue-ссылка **НЕ** может быть связана с **rvalue** + +```c++ +int x = 1; +int&& y = x * 3; //ok +int&& b = x; //fail, not rvalue + +int& c = x * 3; // fail, not lvalue; +const int& d = x * 3; //ok, lifetime prolongation for const ref + +int&& e = y; // fail, not rvalue +int& f = y; // ok +``` + +### Перегрузка функции по ссылке + +Функция может быть перегружен по типу принимаемой ссылке: + +```c++ +int foo(int& v); // 1 +int foo(int&& v); // 2 +int foo(const int& v); // 3 +int foo(const int&& v); // 4 + +int x = 1; + +foo(x); // -> 1 +foo(1); // -> 2 + +const int y = 1; + +foo(y); // -> 3 +foo(std::move(y)); // -> 4 +``` + +### Квалификация метода по виду ссылки + +Метод может быть перегружен, по факту вызова для **lvalue** или **rvalue** ссылки. + +Для этого после закрывающей скобки аргументов добавляется квалификатор **&** или **&&** +Таким образом, можно определить различное поведение при вызове функции от временного +объекта `&&` и постоянного `&` + +```c++ +struct S { + int foo() &; + int foo() &&; +}; +``` + +Оператор присваивания можно пометить одним амперсандом `&` и не создавать версию +с `&&`, чтобы к временному объекту нельзя было присваивать, тогда будет +недоступно присваивание к временному объекту, что достаточно логично + +```c++ +struct S { + S() = default; + S& operator=(const S& other) &; +}; + +S a; +S() = a; // compile error +``` + +### Temporary materialization (С++17) + +Материализация временного объекта (_temporary materialization_) представляет собой +неявное преобразование из _prvalue_ в _xvalue_. + +Материализация временного объекта происходит: + +- при связывании ссылки с чисто временным объектом (категории prvalue) +- при доступе к нестатическому полю класса, prvalue объекта +- при вызове нестатических методов от prvalue объекта (поскольку в метод неявно + передается указатель `this`) +- при выполнении преобразования array-to-pointer или обращению по индексу `[]` + к prvalue массиву +- при инициализации объекта `std::initializer_list` посредством списка инициализации + в фигурных скобках +- когда prvalue появляется в отбрасывающем результат выражении (игнорируем результат + prvalue выражения, возвращающего тип какого-либо класса) + +1. Связывание ссылки с чисто временным объектом + +```c++ +const int& r1 = 10; // prvalue 10 is materialized to get cref +int&& r2 = 5 + 3; // prvalue from expr '5+3' is materialized to get && + +void foo(const std::string&); +foo("hello"); // std::string materializes from "hello" to get cref +``` + +2. Доступ к нестатическому полю класса, prvalue объекта + +```c++ +struct Point { int x = 0, int y = 0}; +int val = Point{3, 4}.x; // Point{3,4} is materialized to access.x; +``` + +3. Вызов нестатического метода от rvalue объекта + +```c++ +struct Logger { + void log() const { std::cout << "Logging\n"; } +}; +Logger().log(); // Logger() is materialized to call log(), need this. +std::string("hello").length(); // the same case +``` + +4. Обращение к prvalue массиву `[]` или при приобразовании array-to-pointer + +```c++ +void bar(const int*); +bar((int[]){10, 20}); + +int* p1 = (int[3]){1, 2, 3};. +int val = ((int[]){10, 20, 30})[1]; +``` + +5. Инициализация объекта `std::initializer_list` + +```c++ +#include +void func(std::initializer_list lst) {} +func({1, 2, 3}); +``` + +6. Когда prvalue в discarded-value expression + +```c++ +struct Widget { + Widget() { std::cout << "Created\n"; } + ~Widget() { std::cout << "Destroyed\n"; } +}; +Widget(); // materialized to the end of expression ; +``` + +Материализации временного объекта **НЕ** возникает, когда используется prvalue того же +типа (при `direct-initialization` или `copy-initialization`). Такие объекты создаются +напрямую из инициализирующего prvalue. Это является гарантированным _copy elision_, +начиная с C++17 + +## Семантика перемещения + +Чтобы избежать излишнего копирования, когда создается объект из объекта, который +больше не нужен, в языке реализована семантика перемещения (move-семантика). + +Семантика перемещения позволяет забрать у другого объекты ресурсы, которыми +он владеет. Причем, объект, у которого забрали данные, должен остаться в +консистентном состоянии, то есть после перемещения из него данных, его +состоянии было корректным (часто дефолтным, нулевым) и им можно было продолжать +пользоваться. + +Начиная с C++11 у класса появляется перемещающий конструктор и перемещающий +оператор присваивания. + +```c++ +Class(Class&& other); +Class& operator=(Class&& other); +``` + +Компилятор генерирует move-конструктор и move оператор присваивания, вызывая +соответствующие move операции для полей класса. + +Для примитивных типов перемещающие методы выполняют **копирование**. + +Владение ресурсом подразумевает, например указатель на динамическую память внутри +класса и выделение памяти в конструкторе и освобождение в деструкторе. Пусть +экземпляр класса создается на стеке, а данные в динамической памяти. Копирование +по умолчанию подразумевает копирование значений всех полей, в случае указателя +скопируются не данные, а указатель (адрес на выделенную память). В такой ситуации +возникает проблема двойного удаления по указателю, так как два объекта имеют +одинаковые указатели на одну память и при выходе из области видимости объектов в +деструкторе будет двойное освобождение памяти по указателю. + +Для такого класса переопределяется конструктор копирования и оператор присваивания +копированием, чтобы данные в динамической памяти действительно копировались: + +```c++ +class String { +public: + String(size_t size) : size_(size), cap_(size), data_(new char[size + 1]) { + std::fill_n(data, size + 1, '\0'); + } + String(const String& other) : + size_(other.size_), + cap_(other.size_), + data_(new char[other.size_ + 1]) { + std::strcpy(data_, other.data_); + } + String& operator=(const String& other) { + if (this != &other) { + delete[] data; + size = other.size; + cap_ = other.size; + data_ = new char[size + 1]; + std::strcpy(data_, other.data_); + } + return *this; + } + ~String() { delete[] data_; } + + size_t cap_; + size_t size_; + char* data_; +}; +``` + +Когда объект больше не нужен, а его данные нужны, или как минимум нужна выделенная +память, чтобы не выделять её снова, можно переместить ресурс (память, файловый +дескриптор, сокет и т.д.) из одного объекта в другой, а не копировать их. +Это обеспечивает значительный прирост производительности, особенно для объектов, +владеющих "тяжелыми" ресурсами. + +Но объект из которого забирают ресурс должен остаться в консистентном состоянии +и его можно было использовать + +```c++ + String(String&& other) noexcept + : cap_(other.cap_), size_(other.size_), data_(other.data_) { + // необходимо оставить other в валидном состоянии + other.cap_ = 0; + other.size_ = 0; + other.data_ = nullptr; + } + String& operator=(String&& other) noexcept { + if (this != &other) { + // очищаем свои данные + delete[] data_; + // копируем + cap_ = other.cap_; + size_ = other.size_; + // забираем данные + data_ = other.data_; + // необходимо оставить other в валидном состоянии + other.cap_ = 0; + other.size_ = 0; + other.data_ = nullptr; + } + return *this; + } +``` + +Вызвать данные конструкторы можно следующим образом: + +```c++ +String str(1000); +auto str_move = std::move(str); // call move ctor + +String str_other(10); +String str_moved(100); +str_moved = std::move(str_other); // call move assignment +``` + +### Функция `std::move` + +Функция `std::move` необходима для того, чтобы при вызове функции от объекта +перенаправить компилятор на использование версии с rvalue-ссылкой. + +Функция действует на этапе компиляции и всё что она делает, это возвращает +rvalue-ссылку на переданный ей объект + +```c++ +std::string str = "I will be moved"; +auto str_copy = str; +auto str_moved = std::move(str); // call move ctor for string +std::cout << str_copy == str_moved) << std::endl; // 1 (true) +std::cout << str.empty() << std::endl; // 1 (true) +``` + +### Правило нуля (_Rule of Zero_) + +"Правило нуля" - принцип программирования на C++, который гласит, что класс +**НЕ** должен определять ни один из пяти специальных методов, если их можно +автоматически сгенерировать компилятором: + +- деструктор +- конструктор копирования +- оператор присваивания копированием +- конструктор перемещения +- оператор присваивания перемещением + +Если ваш класс **НЕ** управляет ресурсами (памятью), а его данные — это простые типы +или классы, которые сами управляют ресурсами (например, `std::string`, `std::vector`, +умные указатели), то вам **НЕ** нужно писать свои специальные методы. + +Класс **НЕ** должен самостоятельно управлять ресурсами, если это не класс управления +ресурсами. + +### Правило пяти (_Rule of Five_) + +"Правило пяти" - принцип программирования на C++, который гласит, что если +пользовательский класс управляет ресурсами (память, файловые дескрипторы) и +определяет хотя бы один из пяти специальных методов, то он **должен явно +определить все пять**: + +- деструктор +- конструктор копирования +- оператор присваивания копированием +- конструктор перемещения +- оператор присваивания перемещением + +Такой подход позволяет избежать ошибок управления памятью (висячие указатели, +двойное удаление). + +До возникновения семантики перемещения в C++11 использовали "Правило трёх". + +### Правила для специальных методов + +Компилятор генерирует специальные методы самостоятельно в зависимости от условий. + +Деструктор по умолчанию создается: + +- default если нет ни одного пользовательского деструктора + +Конструктор копирования (аналогично копирующее присваивание): + +- delete если его нет, но есть пользовательское перемещение или move ctor +- иначе default, если его нет (но если при этом есть деструктор или парный + копирующий метод, то deprecated) + +Перемещающий конструктор (аналогично перемещающее присваивание) + +- delete если его нет, но при этом есть парный перемещающий метод или + copy ctor, или copy operator=, или dtor (при наследовании если деструктор + объявлен virtual) +- иначе default, если его нет + +## Copy Elision + +Copy elision (пропуск копирования) в C++ — это оптимизация компилятора, +устраняющая создание временных объектов при возврате из функций или передаче по +значению, что исключает вызов конструкторов копирования/перемещения. + +Начиная с C++17 компилятор обязан применять _copy elision_ при инициализации +объекта результатом функции, возвращающей тот же тип, что повышает +производительность и делает код более эффективным. + +Оптимизация позволяет не тратить время на копирование/перемещения, а деструкторы +для временных объектов не вызываются. + +### Return Value Optimization (RVO) + +**Return Value Optimization (RVO)** - оптимизация компилятора, позволяющая +не создавать локальный объект, который используется в качестве возвращаемого +значения, а конструировать возвращаемый объект на месте вызова функции. Такая +оптимизация позволяет устранить лишний вызов конструктора копирования или +перемещения. + +RVO применима, если возвращаемое значение prvalue-выражение, то есть временный +объект. + +Начиная с _C++17_ RVO - это не оптимизация, а правило, которое должно выполняться +компилятором. + +### Named Return Value Optimization (NRVO) + +**Named Return Value Optimization (NRVO)** - оптимизация компилятора, аналогичная +RVO, но действующая для локального lvalue-объекта, имеющего имя, и возвращаемого +из функции. + +На месте вызова функции вставляется инициализация объекта, принимающего +возвращаемое значение функции. В аргументы функции добавляется указатель на +данный объект и все вычисления возвращаемого значения выполняются над объектом +под указателем. + +NRVO может быть применена только когда тип возвращаемого объекта и тип +возвращаемого значения сигнатуры функции точно совпадают + +По этой причине **НЕ** следует возвращаемое значение оборачивать в `std::move()`, +так как тип возвращаемого значения не будет совпадать и, следовательно, +оптимизация не будет применена. diff --git a/05_week/05_memory_move.pdf b/05_week/05_memory_move.pdf new file mode 100644 index 00000000..4bb2c3b5 Binary files /dev/null and b/05_week/05_memory_move.pdf differ diff --git a/05_week/CMakeLists.txt b/05_week/CMakeLists.txt new file mode 100644 index 00000000..cdf912be --- /dev/null +++ b/05_week/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.14) # Минимальная требуемая версия CMake +project(05_week) # Необходим для инициализации cmake + +set(CMAKE_CXX_STANDARD 20) # Версия стандарта C++ +set(CMAKE_CXX_STANDARD_REQUIRED ON) # Гарантирует использование указанной версии стандарта + +set(EXAMPLES_DIR examples) # Определим переменную с именем директории +set(TASKS_DIR tasks) + +add_subdirectory(tasks) + +# Создать исполняемый файл для каждого примера +if (BUILD_EXAMPLES_05_WEEK) + +endif() \ No newline at end of file diff --git a/05_week/examples/storage_duration.cpp b/05_week/examples/storage_duration.cpp new file mode 100644 index 00000000..38079f4a --- /dev/null +++ b/05_week/examples/storage_duration.cpp @@ -0,0 +1,127 @@ +#include +#include + +int global_zero; // bss +int global_init = 18; // data +const int global_const = 10; // rodata + +int global_zero_2; // bss +int global_init_2 = 18; // data +const int global_const_2 = 10; // rodata + +static int static_zero; +static int static_init = 18; + +const char* str_a = "hello"; +const char* str_b = "hello"; + +thread_local int tlocal_var = 10; + +void local_var(int func_arg, const int func_arg_const, int& ref, const int& ref_const) { + std::cout << __func__ << ':' << std::endl; + int local = 18; + const int local_const = 10; + std::cout << "addr func_arg = \t" << &func_arg << std::endl; + std::cout << "addr func_arg_const = \t" << &func_arg_const << std::endl; + std::cout << "addr ref = \t\t" << &ref << std::endl; + std::cout << "addr ref_const = \t" << &ref_const << std::endl; + std::cout << "addr local = \t\t" << &local << std::endl; + std::cout << "addr local_const = \t" << &local_const << std::endl; + const int local_const_2 = 20; + int local_two = 2; + std::cout << "addr local_const_2 = \t" << &local_const_2 << std::endl; + std::cout << "addr local_two = \t" << &local_two << std::endl; + std::cout << std::endl; +} + +void same_literals() { + std::cout << __func__ << ':' << std::endl; + const char* str_c = "hello"; + + std::cout << "addr str_a = \t" << &str_a << std::endl; + std::cout << "addr str_b = \t" << &str_b << std::endl; + std::cout << "addr str_c = \t" << &str_c << std::endl; + std::cout << std::endl; +} + +class Static { +public: + static int* get_static_class_var() { + return &static_class_var; + } +private: + static int static_class_var; +}; + +int Static::static_class_var = 0; + +void static_var() { + std::cout << __func__ << ':' << std::endl; + static int static_local_var = 10; + + std::cout << "addr static_zero = \t\t" << &static_zero << std::endl; + std::cout << "addr static_init = \t\t" << &static_init << std::endl; + std::cout << "addr static_local_var = \t" << &static_local_var << std::endl; + std::cout << "addr static_class_var = \t" << Static::get_static_class_var() << std::endl; + std::cout << std::endl; +} + +void dynamic_var() { + std::cout << __func__ << ':' << std::endl; + + int* dynamic_var = new int; + *dynamic_var = 10; + int* dynamic_var_init = new int(10); + int* dynamic_arr = new int[10]{}; + int* dynamic_var_init2 = new int{2}; + std::cout << "addr dynamic_var = \t" << dynamic_var << std::endl; + std::cout << "addr dynamic_var_init = " << dynamic_var_init << std::endl; + std::cout << "addr dynamic_arr = \t" << dynamic_arr << std::endl; + std::cout << "addr dynamic_var_init2 = " << dynamic_var_init2 << std::endl; + delete dynamic_var; + delete dynamic_var_init; + delete[] dynamic_arr; + delete dynamic_var_init2; + std::cout << std::endl; +} + +void thread_var() { + std::cout << __func__ << ':' << std::endl; + int local_var; + std::cout << "addr tlocal_var = \t" << &tlocal_var << std::endl; + std::cout << "addr global_zero = \t" << &global_zero << std::endl; + std::cout << "addr local_var = \t" << &local_var << std::endl; + std::cout << std::endl; +} + +int main() { + std::cout << "global var:" << std::endl; + std::cout << "addr global_zero = " << &global_zero << std::endl; + std::cout << "addr global_zero_2 = " << &global_zero_2 << std::endl; + std::cout << "addr global_init = " << &global_init << std::endl; + std::cout << "addr global_init_2 = " << &global_init_2 << std::endl; + std::cout << "addr global_const = " << &global_const << std::endl; + std::cout << "addr global_const_2 = " << &global_const_2 << std::endl; + std::cout << std::endl; + + std::cout << "func:" << std::endl; + std::cout << "addr func local_var = " << (void*)&local_var << std::endl; + std::cout << "addr func main = " << (void*)&main << std::endl; + std::cout << "addr func static class = " << (void*)&Static::get_static_class_var << std::endl; + std::cout << std::endl; + + std::cout << "local var main:" << std::endl; + int main_var = 0; + const int main_var_const = 0; + std::cout << "addr main_var = " << &main_var << std::endl; + std::cout << "addr main_var_const = " << &main_var_const << std::endl; + std::cout << std::endl; + + local_var(main_var, main_var_const, main_var, main_var_const); + same_literals(); + static_var(); + dynamic_var(); + thread_var(); +} + + diff --git a/05_week/tasks/CMakeLists.txt b/05_week/tasks/CMakeLists.txt new file mode 100644 index 00000000..c06cbe6e --- /dev/null +++ b/05_week/tasks/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(tracer) +add_subdirectory(string_view) +add_subdirectory(cow_string) +add_subdirectory(simple_vector) \ No newline at end of file diff --git a/05_week/tasks/cow_string/CMakeLists.txt b/05_week/tasks/cow_string/CMakeLists.txt new file mode 100644 index 00000000..d234c642 --- /dev/null +++ b/05_week/tasks/cow_string/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_cow_string test.cpp) \ No newline at end of file diff --git a/05_week/tasks/cow_string/README.md b/05_week/tasks/cow_string/README.md new file mode 100644 index 00000000..edb1a1cd --- /dev/null +++ b/05_week/tasks/cow_string/README.md @@ -0,0 +1,74 @@ +[cow]: https://en.wikipedia.org/wiki/Copy-on-write + +# Коровья строка + +Существуют разные подходы к управлению данными при копировании. Можно выделить +следующие семантики: + +- value-семантика - при копировании объекта происходит глубокое копирование (deep copy), + все значения полностью объекта полностью копируются (реализовано в большинстве классов + стандартной библиотеки C++). +- reference-семантика - при копировании объекта его содержимое не копируется, + а разделяется между копиями посредством ссылки или указателя. Также существует подход, + который называют поверхностное копирование (shallow copy), когда происходит + копирование только верхнего уровня оригинального объекта, а ссылки на внутренние + части объекта остаются общими (реализовано по умолчанию в некоторых других языках). +- [cow]-семантика (copy-on-write) - при копировании объекта создается shallow copy, + что позволяет использовать объект на чтение без дополнительных затрат ресурсов, но + при изменении объекта создается настоящая глубокая копия и изменения вносятся уже в копию. + Таким образом, копирование совершается только при внесении изменений, что в + определенных сценариях использования объекта позволяет увеличить производительность + и уменьшить затраты на ресурсы. + +Cow-семантика применяется в реализации строк Яндекса и фреймворка Qt. + +Необходимо реализовать класс `CowString`, который представляет собой упрощенную +реализацию строки с copy-on-write семантикой. + +Класс предоставляет следующий функционал: + +- Конструктор по умолчанию — создает пустую строку +- Конструктор от `const char*` +- Конструктор от `std::string` +- Конструктор копирования — увеличивает счетчик ссылок, не копирует данные +- Оператор присваивания копированием — увеличивает счетчик ссылок, не копирует данные +- Конструктор перемещения +- Оператор присваивания перемещением +- Деструктор + +Методы **НЕ** вызывающие копирования: + +- Метод `Size` - возвращает длину строки без учета терминирующего нуля `\0` +- Метод `ToCstr` - возвращает указатель на данные +- Метод `ToString` - возвращает `std::string` +- Оператор `[]` - доступ к символу для чтения +- Оператор неявного преобразования к C-строке + +Методы, обеспечивающие модификацию на собственной копии данных: + +- Оператор `[]` - доступ к символу для записи +- Метод `Append` - добавляет строку из C-строки или `std::string` +- Метод `Substr` - принимает позицию и количество символов (по умолчанию от начала + до конца строки), возвращает соответствующую подстроку. Если позиция начала + превышает длину, то возвращаем пустую строку. +- Метод `Clear` - очистка строки + +Для реализации строки удобно вынести все необходимые поля в отдельную структуру +и кроме этого хранить в ней счетчик ссылок `ref_count`. + +Правильно поддерживая `ref_count` всегда будет известно, когда нужно удалять +данные строки или когда нужно превращать shallow-copy в deep-copy. + +## Примечание + +- **Запрещено** использовать `std::string` в реализации и умные указатели +- Рекомендуется определять методы вне класса +- При необходимости вспомогательные методы реализуются в закрытой части класса +- Тесты оператор `[]` не проверяют на индекс вне диапазона + +## Проблемы реализации + +- В предполагаемой реализации имеется следующая проблема: при использовании + модифицирующей версии оператора `[]` пользователь может сохранить ссылку + и модифицировать символ позже, что нарушит cow-семантику. Для упрощения + решать её не требуется. В Qt используют `QCharRef` для решения этой проблемы. diff --git a/05_week/tasks/cow_string/cow_string.cpp b/05_week/tasks/cow_string/cow_string.cpp new file mode 100644 index 00000000..34d59738 --- /dev/null +++ b/05_week/tasks/cow_string/cow_string.cpp @@ -0,0 +1,6 @@ +#include +#include + +class CowString { + +}; diff --git a/05_week/tasks/cow_string/test.cpp b/05_week/tasks/cow_string/test.cpp new file mode 100644 index 00000000..bf54b279 --- /dev/null +++ b/05_week/tasks/cow_string/test.cpp @@ -0,0 +1,519 @@ +#include + +#include "cow_string.cpp" + +TEST(CowStringTest, DefaultConstructor) { + CowString s; + EXPECT_EQ(s.Size(), 0); + EXPECT_STREQ(s.ToCstr(), ""); + EXPECT_TRUE(s.ToString().empty()); +} + +TEST(CowStringTest, ConstructorFromCString) { + CowString s("Hello"); + EXPECT_EQ(s.Size(), 5); + EXPECT_STREQ(s.ToCstr(), "Hello"); + EXPECT_EQ(s.ToString(), "Hello"); +} + +TEST(CowStringTest, ConstructorFromStdString) { + std::string str = "World"; + CowString s(str); + EXPECT_EQ(s.Size(), 5); + EXPECT_STREQ(s.ToCstr(), "World"); + EXPECT_EQ(s.ToString(), "World"); +} + +TEST(CowStringTest, ConstructorFromEmptyCString) { + CowString s(""); + EXPECT_EQ(s.Size(), 0); + EXPECT_STREQ(s.ToCstr(), ""); + EXPECT_TRUE(s.ToString().empty()); +} + +TEST(CowStringTest, CopyConstructorCOWSemantics) { + CowString s1("Hello"); + CowString s2 = s1; + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, MoveConstructor) { + CowString s1("Hello"); + const char* original_ptr = s1.ToCstr(); + + CowString s2 = std::move(s1); + + EXPECT_EQ(s2.ToCstr(), original_ptr); + EXPECT_STREQ(s2.ToCstr(), "Hello"); + + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s1.ToCstr(), ""); +} + +TEST(CowStringTest, CopyAssignmentCOWSemantics) { + CowString s1("Hello"); + CowString s2; + + s2 = s1; + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, CopyAssignment_SelfAssignment) { + CowString s("Hello"); + const char* original_ptr = s.ToCstr(); + + s = s; + + EXPECT_EQ(s.ToCstr(), original_ptr); + EXPECT_STREQ(s.ToCstr(), "Hello"); +} + +TEST(CowStringTest, MoveAssignment) { + CowString s1("Hello"); + CowString s2; + + const char* original_ptr = s1.ToCstr(); + + s2 = std::move(s1); + + EXPECT_EQ(s2.ToCstr(), original_ptr); + EXPECT_STREQ(s2.ToCstr(), "Hello"); + + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s1.ToCstr(), ""); +} + +TEST(CowStringTest, ReadOperationsNoCopy) { + CowString s1("Hello World"); + CowString s2 = s1; + + const char* original_ptr = s1.ToCstr(); + + size_t size1 = s1.Size(); + size_t size2 = s2.Size(); + + const char* cstr1 = s1.ToCstr(); + const char* cstr2 = s2.ToCstr(); + + std::string str1 = s1.ToString(); + std::string str2 = s2.ToString(); + + const CowString& s1_cref = s1; + const CowString& s2_cref = s2; + char ch1 = s1_cref[0]; + char ch2 = s2_cref[0]; + + EXPECT_EQ(s1.ToCstr(), original_ptr); + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + EXPECT_EQ(size1, 11); + EXPECT_EQ(size2, 11); + EXPECT_STREQ(cstr1, "Hello World"); + EXPECT_STREQ(cstr2, "Hello World"); + EXPECT_EQ(str1, "Hello World"); + EXPECT_EQ(str2, "Hello World"); + EXPECT_EQ(ch1, 'H'); + EXPECT_EQ(ch2, 'H'); +} + +TEST(CowStringTest, WriteOperationCausesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr1_before = s1.ToCstr(); + const char* ptr2_before = s2.ToCstr(); + + EXPECT_EQ(ptr1_before, ptr2_before); + + s2[0] = 'J'; + + const char* ptr1_after = s1.ToCstr(); + const char* ptr2_after = s2.ToCstr(); + + EXPECT_NE(ptr1_after, ptr2_after); + EXPECT_EQ(ptr1_before, ptr1_after); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); + EXPECT_STREQ(s2.ToCstr(), "Jello"); +} + +TEST(CowStringTest, WriteOperationMultipleCopies) { + CowString original("Hello"); + CowString copy1 = original; + CowString copy2 = original; + CowString copy3 = original; + + EXPECT_EQ(original.ToCstr(), copy1.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy2.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy3.ToCstr()); + + copy2[1] = 'a'; + + EXPECT_NE(original.ToCstr(), copy2.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy1.ToCstr()); + EXPECT_EQ(original.ToCstr(), copy3.ToCstr()); + + EXPECT_STREQ(original.ToCstr(), "Hello"); + EXPECT_STREQ(copy1.ToCstr(), "Hello"); + EXPECT_STREQ(copy2.ToCstr(), "Hallo"); + EXPECT_STREQ(copy3.ToCstr(), "Hello"); +} + +TEST(CowStringTest, AppendFromCStringCausesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr1_before = s1.ToCstr(); + const char* ptr2_before = s2.ToCstr(); + EXPECT_EQ(ptr1_before, ptr2_before); + + s1.Append(" World"); + + const char* ptr1_after = s1.ToCstr(); + const char* ptr2_after = s2.ToCstr(); + + EXPECT_NE(ptr1_after, ptr2_after); + EXPECT_EQ(ptr2_before, ptr2_after); + + EXPECT_STREQ(s1.ToCstr(), "Hello World"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, AppendFromStdString) { + CowString s("Hello"); + std::string world = " World"; + + s.Append(world); + + EXPECT_STREQ(s.ToCstr(), "Hello World"); + EXPECT_EQ(s.Size(), 11); +} + +TEST(CowStringTest, AppendToEmptyString) { + CowString s; + s.Append("Hello"); + + EXPECT_STREQ(s.ToCstr(), "Hello"); + EXPECT_EQ(s.Size(), 5); +} + +TEST(CowStringTest, AppendMultipleTimes) { + CowString s("Hello"); + + s.Append(", "); + s.Append("World"); + s.Append("!"); + + EXPECT_STREQ(s.ToCstr(), "Hello, World!"); + EXPECT_EQ(s.Size(), 13); +} + +TEST(CowStringTest, AppendChainedMultipleTimes) { + CowString s("Hello"); + + s.Append(", ").Append("World").Append("!"); + + EXPECT_STREQ(s.ToCstr(), "Hello, World!"); + EXPECT_EQ(s.Size(), 13); +} + +TEST(CowStringTest, AppendEmptyStringNoCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* original_ptr = s1.ToCstr(); + + s1.Append(""); + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s1.ToCstr(), original_ptr); + + EXPECT_STREQ(s1.ToCstr(), "Hello"); +} + +TEST(CowStringTest, SubstrDoesNotModifyOriginal) { + CowString s("Hello World"); + CowString copy = s; + + CowString sub = s.Substr(0, 5); + + EXPECT_EQ(s.ToCstr(), copy.ToCstr()); + EXPECT_NE(s.ToCstr(), sub.ToCstr()); + + EXPECT_STREQ(s.ToCstr(), "Hello World"); + EXPECT_STREQ(sub.ToCstr(), "Hello"); +} + +TEST(CowStringTest, SubstrDefaultParameters) { + CowString s("Hello World"); + + CowString sub1 = s.Substr(); + EXPECT_STREQ(sub1.ToCstr(), "Hello World"); + + CowString sub2 = s.Substr(6); + EXPECT_STREQ(sub2.ToCstr(), "World"); + + CowString sub3 = s.Substr(0, 5); + EXPECT_STREQ(sub3.ToCstr(), "Hello"); + + CowString sub4 = s.Substr(0, CowString::npos); + EXPECT_STREQ(sub4.ToCstr(), "Hello World"); +} + +TEST(CowStringTest, Substr_FullString) { + CowString s("Hello World"); + + CowString sub = s.Substr(0, s.Size()); + + EXPECT_STREQ(sub.ToCstr(), "Hello World"); + EXPECT_EQ(sub.Size(), 11); +} + +TEST(CowStringTest, Substr_Partial) { + CowString s("Hello World"); + + CowString sub = s.Substr(6, 5); + + EXPECT_STREQ(sub.ToCstr(), "World"); + EXPECT_EQ(sub.Size(), 5); +} + +TEST(CowStringTest, SubstrCountGreaterThanAvailable) { + CowString s("Hello"); + + CowString sub = s.Substr(2, 10); + + EXPECT_STREQ(sub.ToCstr(), "llo"); + EXPECT_EQ(sub.Size(), 3); +} + +TEST(CowStringTest, SubstrFromEmptyString) { + CowString se; + se.Clear(); + + CowString sub1 = se.Substr(0, CowString::npos); + EXPECT_STREQ(sub1.ToCstr(), ""); + EXPECT_EQ(sub1.Size(), 0); + + CowString sub2 = se.Substr(0, 5); + EXPECT_STREQ(sub2.ToCstr(), ""); + EXPECT_EQ(sub2.Size(), 0); +} + +TEST(CowStringTest, SubstrInvalidPos) { + CowString s("Hello"); + + auto sub1 = s.Substr(10); + EXPECT_STREQ(sub1.ToCstr(), ""); + EXPECT_EQ(sub1.Size(), 0); + + auto sub2 = s.Substr(6, CowString::npos); + EXPECT_STREQ(sub2.ToCstr(), ""); + EXPECT_EQ(sub2.Size(), 0); +} + +TEST(CowStringTest, ClearCreatesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr2_before = s2.ToCstr(); + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + + s1.Clear(); + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s2.ToCstr(), ptr2_before); + + EXPECT_STREQ(s1.ToCstr(), ""); + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s2.ToCstr(), "Hello"); + EXPECT_EQ(s2.Size(), 5); +} + +TEST(CowStringTest, ClearEmptyString) { + CowString s; + CowString copy = s; + + s.Clear(); + + EXPECT_STREQ(s.ToCstr(), ""); + EXPECT_EQ(s.Size(), 0); + EXPECT_STREQ(copy.ToCstr(), ""); + EXPECT_EQ(copy.Size(), 0); +} + +TEST(CowStringTest, OperatorConstCharStar) { + CowString s("Hello"); + + const char* cstr = s; + + EXPECT_STREQ(cstr, "Hello"); +} + +TEST(CowStringTest, IndexOperatorRead) { + CowString s("Hello"); + + const CowString& s_cref = s; + EXPECT_EQ(s_cref[0], 'H'); + EXPECT_EQ(s_cref[1], 'e'); + EXPECT_EQ(s_cref[4], 'o'); +} + +TEST(CowStringTest, IndexOperatorWriteCausesCopy) { + CowString s1("Hello"); + CowString s2 = s1; + + const char* ptr1_before = s1.ToCstr(); + const char* ptr2_before = s2.ToCstr(); + EXPECT_EQ(ptr1_before, ptr2_before); + + s1[0] = 'J'; + + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s2.ToCstr(), ptr2_before); + + EXPECT_STREQ(s1.ToCstr(), "Jello"); + EXPECT_STREQ(s2.ToCstr(), "Hello"); +} + +TEST(CowStringTest, ImplicitConversionInComparisons) { + CowString s("Hello"); + + EXPECT_STREQ(s, "Hello"); + EXPECT_STRNE(s, "World"); +} + +TEST(CowStringTest, MultipleOperations) { + CowString s1("Hello"); + CowString s2 = s1; + CowString s3 = s2; + + EXPECT_EQ(s1.ToCstr(), s2.ToCstr()); + EXPECT_EQ(s2.ToCstr(), s3.ToCstr()); + + s2.Append(" World"); + + EXPECT_EQ(s1.ToCstr(), s3.ToCstr()); + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + + s1[0] = 'J'; + + EXPECT_NE(s1.ToCstr(), s2.ToCstr()); + EXPECT_NE(s1.ToCstr(), s3.ToCstr()); + EXPECT_NE(s2.ToCstr(), s3.ToCstr()); + + EXPECT_STREQ(s1.ToCstr(), "Jello"); + EXPECT_STREQ(s2.ToCstr(), "Hello World"); + EXPECT_STREQ(s3.ToCstr(), "Hello"); +} + +TEST(CowStringTest, MemoryManagementDestructor) { + CowString* s1 = new CowString("Hello"); + CowString* s2 = new CowString(*s1); + + const char* s1_ptr = s1->ToCstr(); + const char* s2_ptr = s2->ToCstr(); + + EXPECT_EQ(s1_ptr, s2_ptr); + + delete s1; + + EXPECT_STREQ(s2->ToCstr(), "Hello"); + + delete s2; +} + +TEST(CowStringTest, AssignmentChain) { + CowString a("A"); + CowString b("B"); + CowString c("C"); + + a = b = c; + + EXPECT_EQ(a.ToCstr(), b.ToCstr()); + EXPECT_EQ(b.ToCstr(), c.ToCstr()); + EXPECT_EQ(a.ToCstr(), c.ToCstr()); + + EXPECT_STREQ(a.ToCstr(), "C"); + EXPECT_STREQ(b.ToCstr(), "C"); + EXPECT_STREQ(c.ToCstr(), "C"); +} + +TEST(CowStringTest, LargeStringCOWBenefits) { + + std::string large(10000, 'A'); + CowString s1(large); + + std::vector copies(10); + for (auto& copy : copies) { + copy = s1; + } + + for (const auto& copy : copies) { + EXPECT_EQ(s1.ToCstr(), copy.ToCstr()); + } + + copies[5][0] = 'B'; + + EXPECT_NE(s1.ToCstr(), copies[5].ToCstr()); + + for (size_t i = 0; i < copies.size(); ++i) { + if (i != 5) { + EXPECT_EQ(s1.ToCstr(), copies[i].ToCstr()); + } + } + + EXPECT_EQ(s1.ToCstr()[0], 'A'); + EXPECT_EQ(copies[5].ToCstr()[0], 'B'); + + for (size_t i = 1; i < 100; ++i) { + EXPECT_EQ(s1.ToCstr()[i], 'A'); + EXPECT_EQ(copies[5].ToCstr()[i], 'A'); + } +} + +TEST(CowStringTest, EmptyStringsShareData) { + CowString s1; + CowString s2; + + EXPECT_STREQ(s1.ToCstr(), ""); + EXPECT_EQ(s1.Size(), 0); + EXPECT_STREQ(s2.ToCstr(), ""); + EXPECT_EQ(s2.Size(), 0); + + CowString s3 = s1; + EXPECT_STREQ(s3.ToCstr(), ""); + EXPECT_EQ(s3.Size(), 0); +} + +TEST(CowStringTest, FindMethod) { + CowString s("Hello World"); + + EXPECT_EQ(s.Find("World"), 6); + EXPECT_EQ(s.Find("Hello"), 0); + EXPECT_EQ(s.Find("Universe"), CowString::npos); + EXPECT_EQ(s.Find('o'), 4); + EXPECT_EQ(s.Find('x'), CowString::npos); + EXPECT_EQ(s.Find(""), 0); +} + +TEST(CowStringTest, EmptyMethod) { + CowString s1("Hello"); + EXPECT_FALSE(s1.Empty()); + + CowString s2; + EXPECT_TRUE(s2.Empty()); + + CowString s3(""); + EXPECT_TRUE(s3.Empty()); + + s1.Clear(); + EXPECT_TRUE(s1.Empty()); +} diff --git a/05_week/tasks/simple_vector/CMakeLists.txt b/05_week/tasks/simple_vector/CMakeLists.txt new file mode 100644 index 00000000..44ad31db --- /dev/null +++ b/05_week/tasks/simple_vector/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_simple_vector test.cpp) \ No newline at end of file diff --git a/05_week/tasks/simple_vector/README.md b/05_week/tasks/simple_vector/README.md new file mode 100644 index 00000000..6b7a4c47 --- /dev/null +++ b/05_week/tasks/simple_vector/README.md @@ -0,0 +1,66 @@ +# Вектор чисел + +Необходимо реализовать класс `SimpleVector`, представляющий упрощенную реализацию +контейнера `std::vector` для целочисленных элементов типа `int` в динамической +памяти. + +Класс предоставляет следующий функционал: + +- Конструктор по умолчанию +- Конструктор, принимающий размер вектора и заполняющий его нулями +- Конструктор, принимающий список инициализации `std::initializer_list`, + что позволит писать `SimpleVector v = {1, 3, 5}` +- Конструктор копирования +- Конструктор перемещения +- Операторы присваивания копированием +- Оператор присваивания перемещением +- Деструктор +- Метод `Swap` - принимает другой вектор и меняет содержимое текущего вектора с ним местами +- Операторы индексирования `[]`- позволяет изменять содержимое для неконстантного вектора +- Метод `Size` - возвращает число элементов в векторе +- Метод `Capacity` - возвращает текущее число выделенных ячеек памяти под вектор. +- Метод `Empty` - возвращает `true`, если вектор пуст +- Метод `Data` - прямой доступ к памяти, не позволяющий вносить изменения +- Метод `PushBack` который вставляет элемент в конец вектора. +- Метод `PopBack` - удаляет последний элемент вектора. При этом изменяется только размер, + выделенную память изменять не нужно. +- Метод `Insert` - принимает позицию (или `const int*`) и элемент, вставляет элемент перед + указанной позицией, возвращает указатель на вставленный элемент, позволяющий вносить изменения. + При вставке элементы контейнера, начиная с указанной позиции до конца вектора смещаются + на одну позицию. Если передана некорректная позиция, вставка не происходит, возвращается + указатель за последний элемент контейнера. +- Метод `Erase` - принимает позицию (или `const int*`), удаляет элемент в указанной позиции + и возвращает указатель на элемент, который был следующим за удаляемым. + Элементы после указанной позиции сдвигаются. Если передана некорректная позиция, удаление + не производится. +- Метод `Clear` - очистить вектор. При этом, как правило, изменяется только размер, память + очищать не нужно и выполнять релокации тоже. +- Метод `Resize` - принимает размер и значение (по умолчанию 0), изменяет размер массива на + заданный. Если переданный размер совпадает с текущим, то без изменений. Если меньше, то + изменяет размер. Если больше, то при необходимости производит релокацию и заполняет элементы + заданным значением +- Метод `Reserve` - принимает новое значение вместимости, позволяя зарезервировать место в + векторе. Если текущий `Capacity` не меньше переданного, то метод не должен ничего делать. + В противном случае выполните релокацию в массив размера заданной вместимости. +- Поддержка работы range-based for для контейнера. В данном случае для простоты + допустимо возвращать указатели на первый элемент и за последний, концепция итераторов + будет обсуждаться позже. +- Операторы сравнения на равенство и неравенство, учитывающие размер и поэлементное + сравнение элементов + +При добавлении элемента, если память, выделенная для вектора, заполнена, то выполните +релокацию: выделите массив вдвое большего размера, скопируйте элементы туда, +после чего удалите старый массив. В этом случае вместимость должна увеличиться вдвое. +Для вектора, сконструированного с помощью конструктора по умолчанию, вместимость == 0, +при добавлении элемента вместимость становится 1. + +## Примечание + +- **Запрещено** использовать стандартные контейнеры (`std::vector`, умные указатели) +- Устройство вектора обсуждалось в конце третьей лекции +- Для поддержки range-based for необходимы методы `begin`, `end` или внешние функции + `begin`, `end`, принимающие заданную коллекцию, поэтому допустимо, чтобы они не + соответствовали стайлгайду. Если стайлгайд не хочется +- Для совместимости с алгоритмами стандартной библиотеки **STL** может потребоваться + `swap`, ситуация аналогичная, но поскольку требуется внутри класса `Swap`, достаточно + реализовать внешнюю функцию, вызывающую метод `Swap` контейнера diff --git a/05_week/tasks/simple_vector/simple_vector.cpp b/05_week/tasks/simple_vector/simple_vector.cpp new file mode 100644 index 00000000..9b2ea971 --- /dev/null +++ b/05_week/tasks/simple_vector/simple_vector.cpp @@ -0,0 +1,5 @@ + + +class SimpleVector { + +}; \ No newline at end of file diff --git a/05_week/tasks/simple_vector/test.cpp b/05_week/tasks/simple_vector/test.cpp new file mode 100644 index 00000000..9bf16ed4 --- /dev/null +++ b/05_week/tasks/simple_vector/test.cpp @@ -0,0 +1,397 @@ +#include + +#include "simple_vector.cpp" + + +TEST(SimpleVectorTest, DefaultConstructor) { + SimpleVector v; + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 0); + EXPECT_TRUE(v.Empty()); +} + +TEST(SimpleVectorTest, SizeConstructor) { + SimpleVector v(5); + EXPECT_EQ(v.Size(), 5); + EXPECT_EQ(v.Capacity(), 5); + EXPECT_FALSE(v.Empty()); + + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], 0); + } +} + +TEST(SimpleVectorTest, SizeValueConstructor) { + SimpleVector v(3, 42); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v.Capacity(), 3); + + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], 42); + } +} + +TEST(SimpleVectorTest, InitializerListConstructor) { + SimpleVector v = {1, 2, 3, 4, 5}; + EXPECT_EQ(v.Size(), 5); + EXPECT_EQ(v.Capacity(), 5); + + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i + 1)); + } +} + +TEST(SimpleVectorTest, CopyConstructor) { + SimpleVector original = {1, 2, 3}; + SimpleVector copy(original); + + EXPECT_EQ(copy.Size(), original.Size()); + EXPECT_EQ(copy.Capacity(), original.Capacity()); + + EXPECT_EQ(original, copy); + + copy[0] = 100; + EXPECT_EQ(original[0], 1); +} + +TEST(SimpleVectorTest, MoveConstructor) { + SimpleVector original = {1, 2, 3}; + size_t original_size = original.Size(); + size_t original_capacity = original.Capacity(); + const int* original_data = original.Data(); + + SimpleVector moved(std::move(original)); + + EXPECT_EQ(moved.Size(), original_size); + EXPECT_EQ(moved.Capacity(), original_capacity); + EXPECT_EQ(moved.Data(), original_data); + + EXPECT_EQ(original.Size(), 0); + EXPECT_EQ(original.Capacity(), 0); + EXPECT_EQ(original.Data(), nullptr); +} + + +TEST(SimpleVectorTest, CopyAssignment) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {4, 5}; + + v2 = v1; + + EXPECT_EQ(v2.Size(), v1.Size()); + EXPECT_EQ(v2.Capacity(), v1.Size()); + + EXPECT_EQ(v2, v1); +} + +TEST(SimpleVectorTest, SelfCopyAssignment) { + SimpleVector v1 = {1, 2, 3}; + auto cap = v1.Capacity(); + + v1 = v1; + + EXPECT_EQ(v1.Capacity(), cap); + EXPECT_EQ(v1.Size(), 3); + EXPECT_EQ(v1[0], 1); +} +TEST(SimpleVectorTest, MoveAssignment) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2; + + const int* v1_data = v1.Data(); + size_t v1_size = v1.Size(); + size_t v1_capacity = v1.Capacity(); + + v2 = std::move(v1); + + EXPECT_EQ(v2.Size(), v1_size); + EXPECT_EQ(v2.Capacity(), v1_capacity); + EXPECT_EQ(v2.Data(), v1_data); + + EXPECT_EQ(v1.Size(), 0); + EXPECT_EQ(v1.Capacity(), 0); + EXPECT_EQ(v1.Data(), nullptr); +} + +TEST(SimpleVectorTest, PushBack) { + SimpleVector v; + + v.PushBack(1); + EXPECT_EQ(v.Size(), 1); + EXPECT_EQ(v.Capacity(), 1); + EXPECT_EQ(v[0], 1); + + v.PushBack(2); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(v.Capacity(), 2); + EXPECT_EQ(v[1], 2); + + v.PushBack(3); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v.Capacity(), 4); + EXPECT_EQ(v[2], 3); +} + +TEST(SimpleVectorTest, EmptyPopBack) { + SimpleVector v; + EXPECT_NO_THROW(v.PopBack()); + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 0); +} + +TEST(SimpleVectorTest, PopBack) { + SimpleVector v = {1, 2, 3, 4}; + + v.PopBack(); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v.Capacity(), 4); + + v.PopBack(); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v.Capacity(), 4); + + v.PopBack(); + v.PopBack(); + EXPECT_EQ(v.Size(), 0); + EXPECT_TRUE(v.Empty()); + EXPECT_EQ(v.Capacity(), 4); + + v.PopBack(); + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 4); +} + +TEST(SimpleVectorTest, Insert) { + SimpleVector v = {1, 3, 4}; + + int* inserted = v.Insert(v.Data() + 1, 2); + EXPECT_EQ(v.Size(), 4); + EXPECT_EQ(*inserted, 2); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i + 1)); + } + + v.Insert(v.Data(), 0); + EXPECT_EQ(v.Size(), 5); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i)); + } + + v.Insert(v.Data() + v.Size(), 5); + EXPECT_EQ(v.Size(), 6); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i)); + } + + int* result = v.Insert(v.Data() - 1, 100); + EXPECT_EQ(v.Size(), 6); + for (size_t i = 0; i < v.Size(); ++i) { + EXPECT_EQ(v[i], static_cast(i)); + } + EXPECT_EQ(result, v.Data() + v.Size()); +} + +TEST(SimpleVectorTest, Erase) { + SimpleVector v = {1, 2, 3, 4, 5}; + + int* ptr = v.Erase(v.Data() + 1); + EXPECT_EQ(v.Size(), 4); + EXPECT_EQ(*ptr, 3); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 3); + EXPECT_EQ(v[2], 4); + EXPECT_EQ(v[3], 5); + + v.Erase(v.Data()); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v[0], 3); + + ptr = v.Erase(v.Data() + v.Size() - 1); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(ptr, v.Data() + v.Size()); + EXPECT_EQ(v[1], 4); + + ptr = v.Erase(v.Data() + v.Size() + 1); + EXPECT_EQ(ptr, v.Data() + v.Size()); +} + +TEST(SimpleVectorTest, Resize) { + SimpleVector v = {1, 2, 3}; + + v.Resize(2); + EXPECT_EQ(v.Size(), 2); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 2); + + v.Resize(4); + EXPECT_EQ(v.Size(), 4); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 2); + EXPECT_EQ(v[2], 0); + EXPECT_EQ(v[3], 0); + + v.Resize(6, 42); + EXPECT_EQ(v.Size(), 6); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 2); + EXPECT_EQ(v[2], 0); + EXPECT_EQ(v[3], 0); + EXPECT_EQ(v[4], 42); + EXPECT_EQ(v[5], 42); +} + +TEST(SimpleVectorTest, Reserve) { + SimpleVector v; + + v.Reserve(10); + EXPECT_EQ(v.Capacity(), 10); + EXPECT_EQ(v.Size(), 0); + + SimpleVector expected = {1, 2, 3}; + v = {1, 2, 3}; + v.Reserve(5); + EXPECT_EQ(v.Capacity(), 5); + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v, expected); + + v.Reserve(2); + EXPECT_EQ(v.Capacity(), 5); +} + +TEST(SimpleVectorTest, Clear) { + SimpleVector v = {1, 2, 3, 4, 5}; + + v.Clear(); + EXPECT_EQ(v.Size(), 0); + EXPECT_EQ(v.Capacity(), 5); + EXPECT_TRUE(v.Empty()); +} + +TEST(SimpleVectorTest, Swap) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {4, 5}; + + const int* v1_data = v1.Data(); + const int* v2_data = v2.Data(); + size_t v1_size = v1.Size(); + size_t v2_size = v2.Size(); + size_t v1_cap = v1.Capacity(); + size_t v2_cap = v2.Capacity(); + + v1.Swap(v2); + + EXPECT_EQ(v1.Size(), v2_size); + EXPECT_EQ(v1.Data(), v2_data); + EXPECT_EQ(v1.Capacity(), v2_cap); + EXPECT_EQ(v2.Size(), v1_size); + EXPECT_EQ(v2.Data(), v1_data); + EXPECT_EQ(v2.Capacity(), v1_cap); +} + +TEST(SimpleVectorTest, ExternalSwap) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {4, 5}; + + swap(v1, v2); + + EXPECT_EQ(v1.Size(), 2); + EXPECT_EQ(v2.Size(), 3); +} + +TEST(SimpleVectorTest, RangeBasedFor) { + SimpleVector v = {1, 2, 3, 4, 5}; + int sum = 0; + for (int x : v) { + sum += x; + } + EXPECT_EQ(sum, 15); + + const SimpleVector& cv = v; + sum = 0; + for (int x : cv) { + sum += x; + } + EXPECT_EQ(sum, 15); +} + +TEST(SimpleVectorTest, EqualityOperators) { + SimpleVector v1 = {1, 2, 3}; + SimpleVector v2 = {1, 2, 3}; + SimpleVector v3 = {1, 2}; + SimpleVector v4 = {1, 2, 4}; + + EXPECT_TRUE(v1 == v2); + EXPECT_FALSE(v1 != v2); + + EXPECT_FALSE(v1 == v3); + EXPECT_TRUE(v1 != v3); + + EXPECT_FALSE(v1 == v4); + EXPECT_TRUE(v1 != v4); +} + +TEST(SimpleVectorTest, AccessMethods) { + SimpleVector v = {10, 20, 30}; + const SimpleVector& cv = v; + + EXPECT_EQ(*v.Data(), 10); + EXPECT_EQ(*cv.Data(), 10); + + EXPECT_EQ(v[0], 10); + EXPECT_EQ(cv[1], 20); + + v[2] = 40; + EXPECT_EQ(v[2], 40); +} + +TEST(SimpleVectorTest, ComplexOperations) { + SimpleVector v; + + v.PushBack(1); + v.PushBack(2); + v.PushBack(3); + + v.Insert(v.Data() + 1, 99); + v.Erase(v.Data() + 2); + + EXPECT_EQ(v.Size(), 3); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[1], 99); + EXPECT_EQ(v[2], 3); + + v.Resize(5, 100); + EXPECT_EQ(v.Size(), 5); + EXPECT_EQ(v[3], 100); + EXPECT_EQ(v[4], 100); + + v.Reserve(20); + EXPECT_EQ(v.Capacity(), 20); +} + +TEST(SimpleVectorTest, WorkAfterMoveCtor) { + SimpleVector original = {1, 2, 3}; + SimpleVector moved(std::move(original)); + + moved.PushBack(17); + EXPECT_EQ(moved.Size(), 4); + EXPECT_EQ(moved[3], 17); + + original.PushBack(17); + EXPECT_EQ(original.Size(), 1); + EXPECT_EQ(original[0], 17); +} + +TEST(SimpleVectorTest, WorkAfterMoveAssignment) { + SimpleVector original = {1, 2, 3}; + SimpleVector moved = {4, 5}; + moved = std::move(original); + + moved.PushBack(17); + EXPECT_EQ(moved.Size(), 4); + EXPECT_EQ(moved[3], 17); + + original.PushBack(17); + EXPECT_EQ(original.Size(), 1); + EXPECT_EQ(original[0], 17); +} \ No newline at end of file diff --git a/05_week/tasks/string_view/CMakeLists.txt b/05_week/tasks/string_view/CMakeLists.txt new file mode 100644 index 00000000..2660fd76 --- /dev/null +++ b/05_week/tasks/string_view/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_string_view test.cpp) \ No newline at end of file diff --git a/05_week/tasks/string_view/README.md b/05_week/tasks/string_view/README.md new file mode 100644 index 00000000..660599cc --- /dev/null +++ b/05_week/tasks/string_view/README.md @@ -0,0 +1,42 @@ +[std::string_view]: https://en.cppreference.com/w/cpp/string/basic_string_view.html + +# Представление строки + +Необходимо реализовать класс `StringView`, представляющий упрощенную реализацию +[std::string_view]. В отличие от `std::string` данный класс не владеет строкой +(ресурсами), а хранит лишь размер и указатель на начало строки. Это позволяет +быстро создавать представление строки, копировать и создавать подстроки. Важно +понимать, что поскольку `StringView` не владеет строкой, а лишь наблюдает за +оригинальной строкой, то при изменении оригинала представление тоже изменится. +При этом сам класс `StringView` не может изменять строку, за которой наблюдает. + +Класс предоставляет следующий функционал: + +- Конструктор от `std::string`, позиции начала подстроки (по умолчанию 0) и длину + наблюдаемой подстроки (по умолчанию аналог `std::string::npos`). Длину подстроки + следует обрезать, если она превышает длину строки. Если начало превышает длину, + то следует создать `StringView` с параметрами по умолчанию +- Конструктор от C-строки +- Конструктор от C-строки и длины +- Оператор `[]` - доступ на чтение символа +- Метод `Data` - возвращает указатель на начало наблюдаемой строки +- Метод `Front` - доступ на чтение к первому символу. +- Метод `Back` - доступ на чтение к последнему символу. +- Методы `Size`, `Length` - возвращают длину наблюдаемой строки +- Метод `Empty` - проверяет на пустоту +- Метод `RemovePrefix` - убирает заданное количество символов из начала представления +- Метод `RemoveSuffix` - убирает заданное количество символов с конца представления +- Метод `Substr` - может принимать позицию начала поиска и количество элементов и + возвращает `StringView`. В случае, когда подстрока начала поиска превышает длину строки, + следует вернуть пустое представление +- Метод `Find` - принимает символ или `StringView` и позицию начала поиска (по умолчанию 0), + возвращает позицию начала совпадения элемента (или аналог `std::string::npos`) +- Метод `ToString` - создает `std::string` на основе представления + +## Примечание + +- **Запрещено** использовать `std::string_view` в реализации +- Передавать `std::string` по значению в первый конструктор очень плохая идея +- Получить длину C-строки можно с помощью функции `strlen`. +- Не забудьте про константность методов + diff --git a/05_week/tasks/string_view/string_view.cpp b/05_week/tasks/string_view/string_view.cpp new file mode 100644 index 00000000..438c4536 --- /dev/null +++ b/05_week/tasks/string_view/string_view.cpp @@ -0,0 +1,7 @@ +#include +#include + + +class StringView { + +}; \ No newline at end of file diff --git a/05_week/tasks/string_view/test.cpp b/05_week/tasks/string_view/test.cpp new file mode 100644 index 00000000..2388fbf0 --- /dev/null +++ b/05_week/tasks/string_view/test.cpp @@ -0,0 +1,349 @@ +#include + +#include "string_view.cpp" + +#include + +void ExpectEmpty(const StringView& sv) { + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); + EXPECT_EQ(sv.Length(), 0); + EXPECT_EQ(sv.Data(), nullptr); +} + +TEST(StringViewTest, DefaultConstructor) { + StringView sv; + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); + EXPECT_EQ(sv.Length(), 0); + EXPECT_EQ(sv.Data(), nullptr); +} + +TEST(StringViewTest, CtorStdString) { + std::string str = "Hello, World!"; + StringView sv(str); + + EXPECT_FALSE(sv.Empty()); + EXPECT_EQ(sv.Size(), str.size()); + EXPECT_EQ(sv.Length(), str.size()); + EXPECT_EQ(sv.Data(), str.data()); + EXPECT_EQ(sv.ToString(), str); +} + +TEST(StringViewTest, CtorStdStringWithPos) { + std::string str = "Hello, World!"; + + StringView sv(str, 7); + EXPECT_EQ(sv.Size(), 6); + EXPECT_EQ(sv.ToString(), "World!"); + + StringView sv2(str, 0); + EXPECT_EQ(sv2.ToString(), str); +} + +TEST(StringViewTest, CtorStdStringWithPosAndCount) { + std::string str = "Hello, World!"; + + StringView sv1(str, 0, 5); + EXPECT_EQ(sv1.ToString(), "Hello"); + + StringView sv2(str, 7, 5); + EXPECT_EQ(sv2.ToString(), "World"); + + StringView sv3(str, 7, 100); + EXPECT_EQ(sv3.ToString(), "World!"); + + StringView sv4(str, 7, StringView::npos); + EXPECT_EQ(sv4.ToString(), "World!"); +} + +TEST(StringViewTest, CtorStdStringWithInvalidPos) { + std::string str = "Hello"; + + EXPECT_NO_THROW(StringView(str, 10)); + EXPECT_NO_THROW(StringView(str, 6)); + + auto sv = StringView(str, 10); + ExpectEmpty(sv); +} + +TEST(StringViewTest, CtorCString) { + const char* cstr = "Hello, World!"; + StringView sv(cstr); + + EXPECT_FALSE(sv.Empty()); + EXPECT_EQ(sv.Size(), std::strlen(cstr)); + EXPECT_EQ(sv.ToString(), cstr); + EXPECT_EQ(sv.Data(), cstr); +} + +TEST(StringViewTest, CtorCStringNullptr) { + StringView sv(static_cast(nullptr)); + ExpectEmpty(sv); +} + +TEST(StringViewTest, CtorCStringAndLength) { + const char* cstr = "Hello, World!"; + + StringView sv1(cstr, 5); + EXPECT_EQ(sv1.Size(), 5); + EXPECT_EQ(sv1.ToString(), "Hello"); + + StringView sv2(cstr + 7, 5); + EXPECT_EQ(sv2.Size(), 5); + EXPECT_EQ(sv2.ToString(), "World"); +} + +TEST(StringViewTest, CtorCStringAndLengthNullptr) { + StringView sv1(nullptr, 0); + EXPECT_TRUE(sv1.Empty()); + + EXPECT_NO_THROW(StringView(nullptr, 5)); + auto sv = StringView(nullptr, 5); + ExpectEmpty(sv); +} + +TEST(StringViewTest, AccessMethods) { + std::string str = "Hello"; + StringView sv(str); + + EXPECT_EQ(sv[0], 'H'); + EXPECT_EQ(sv[1], 'e'); + EXPECT_EQ(sv[4], 'o'); + + EXPECT_EQ(sv.Front(), 'H'); + EXPECT_EQ(sv.Back(), 'o'); +} + +TEST(StringViewTest, RemovePrefix) { + std::string str = "Hello, World!"; + StringView sv(str); + + sv.RemovePrefix(7); + EXPECT_EQ(sv.ToString(), "World!"); + EXPECT_EQ(sv.Size(), 6); + + sv.RemovePrefix(5); + EXPECT_EQ(sv.ToString(), "!"); + EXPECT_EQ(sv.Size(), 1); + + sv.RemovePrefix(10); + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); +} + +TEST(StringViewTest, RemoveSuffix) { + std::string str = "Hello, World!"; + StringView sv(str); + + sv.RemoveSuffix(7); + EXPECT_EQ(sv.ToString(), "Hello,"); + EXPECT_EQ(sv.Size(), 6); + + sv.RemoveSuffix(5); + EXPECT_EQ(sv.ToString(), "H"); + EXPECT_EQ(sv.Size(), 1); + + sv.RemoveSuffix(10); + EXPECT_TRUE(sv.Empty()); + EXPECT_EQ(sv.Size(), 0); +} + +TEST(StringViewTest, Substr) { + std::string str = "Hello, World!"; + StringView sv(str); + + StringView sub1 = sv.Substr(0, 5); + EXPECT_EQ(sub1.ToString(), "Hello"); + + StringView sub2 = sv.Substr(7, 5); + EXPECT_EQ(sub2.ToString(), "World"); + + StringView sub3 = sv.Substr(7); + EXPECT_EQ(sub3.ToString(), "World!"); + + StringView sub4 = sv.Substr(7, 100); + EXPECT_EQ(sub4.ToString(), "World!"); + + StringView sub5 = sv.Substr(13, 5); + EXPECT_TRUE(sub5.Empty()); + EXPECT_EQ(sub5.ToString(), ""); +} + +TEST(StringViewTest, SubstrInvalidPos) { + std::string str = "Hello"; + StringView sv(str); + + auto substr = sv.Substr(6); + ExpectEmpty(substr); +} + +TEST(StringViewTest, FindChar) { + std::string str = "Hello, World!"; + StringView sv(str); + + EXPECT_EQ(sv.Find('H'), 0); + EXPECT_EQ(sv.Find('o'), 4); + EXPECT_EQ(sv.Find('!'), 12); + EXPECT_EQ(sv.Find('z'), StringView::npos); + + EXPECT_EQ(sv.Find('o', 5), 8); + EXPECT_EQ(sv.Find('l', 3), 3); + + EXPECT_EQ(sv.Find('H', 13), StringView::npos); +} + +TEST(StringViewTest, FindStringView) { + std::string str = "Hello, World! Hello again!"; + StringView sv(str); + + EXPECT_EQ(sv.Find(StringView("Hello")), 0); + EXPECT_EQ(sv.Find(StringView("World")), 7); + EXPECT_EQ(sv.Find(StringView("again")), 20); + EXPECT_EQ(sv.Find(StringView("nonexistent")), StringView::npos); + + EXPECT_EQ(sv.Find(StringView("")), 0); + + EXPECT_EQ(sv.Find(StringView("Hello"), 1), 14); + EXPECT_EQ(sv.Find(StringView("!"), 13), 25); + + EXPECT_EQ(sv.Find(StringView("Hello, World! Hello again! And more")), StringView::npos); +} + +TEST(StringViewTest, Find_EmptyView) { + StringView sv; + + EXPECT_EQ(sv.Find('a'), StringView::npos); + EXPECT_EQ(sv.Find(StringView("test")), StringView::npos); + + StringView empty_target(""); + EXPECT_EQ(sv.Find(empty_target), StringView::npos); +} + +TEST(StringViewTest, ToString) { + std::string str = "Hello, World!"; + StringView sv1(str); + EXPECT_EQ(sv1.ToString(), str); + + const char* cstr = "Test"; + StringView sv2(cstr); + EXPECT_EQ(sv2.ToString(), "Test"); + + StringView sv3 = sv1.Substr(7, 5); + EXPECT_EQ(sv3.ToString(), "World"); + + StringView sv4; + EXPECT_EQ(sv4.ToString(), ""); +} + +TEST(StringViewTest, ObservesOriginalString) { + std::string str = "Hello"; + StringView sv(str); + + const char* original_data = sv.Data(); + + str[0] = 'J'; + str.append(", World!"); + + EXPECT_EQ(sv.Data(), original_data); + EXPECT_EQ(sv.ToString(), "Jello"); +} + +TEST(StringViewTest, WorksWithStringLiterals) { + StringView sv1 = "Hello"; + EXPECT_EQ(sv1.ToString(), "Hello"); + + StringView sv2("World"); + EXPECT_EQ(sv2.ToString(), "World"); + + std::string result = sv1.ToString() + ", " + sv2.ToString(); + EXPECT_EQ(result, "Hello, World"); +} + +TEST(StringViewTest, NposConstant) { + EXPECT_EQ(StringView::npos, static_cast(-1)); + EXPECT_GT(StringView::npos, 1000000); // Очень большое число +} + +TEST(StringViewTest, IteratorLikeUsage) { + std::string str = "Hello"; + StringView sv(str); + + int count_l = 0; + for (size_t i = 0; i < sv.Size(); ++i) { + if (sv[i] == 'l') { + ++count_l; + } + } + EXPECT_EQ(count_l, 2); + + bool found = false; + for (size_t i = 0; i < sv.Size(); ++i) { + if (sv[i] == 'o') { + found = true; + break; + } + } + EXPECT_TRUE(found); +} + +TEST(StringViewTest, MemorySafety) { + std::string str = "Hello"; + StringView sv1(str, 5); + EXPECT_TRUE(sv1.Empty()); + EXPECT_EQ(sv1.ToString(), ""); + + std::string temp("Temporary"); + StringView sv2(temp); + EXPECT_EQ(sv2.ToString(), "Temporary"); + + StringView sv3("Hello, World!"); + StringView sv4 = sv3.Substr(7); + StringView sv5 = sv4.Substr(0, 5); + EXPECT_EQ(sv5.ToString(), "World"); + + EXPECT_EQ(sv3.Data() + 7, sv4.Data()); + EXPECT_EQ(sv4.Data(), sv5.Data()); +} + +TEST(StringViewTest, ComplexScenario) { + std::string log_line = "[INFO] 2024-01-15 10:30:00 User 'admin' logged in from 192.168.1.1"; + StringView sv(log_line); + + size_t bracket_end = sv.Find(']'); + StringView level = sv.Substr(1, bracket_end - 1); + EXPECT_EQ(level.ToString(), "INFO"); + + size_t pos = bracket_end + 2; + size_t data_end = sv.Find(' ', pos); + StringView datetime = sv.Substr(pos, data_end - pos); + EXPECT_EQ(datetime.ToString(), "2024-01-15"); + + size_t user_start = sv.Find('\'', data_end); + size_t user_end = sv.Find('\'', user_start + 1); + StringView username = sv.Substr(user_start + 1, user_end - user_start - 1); + EXPECT_EQ(username.ToString(), "admin"); + + size_t ip_start = sv.Find("from") + 5; + StringView ip = sv.Substr(ip_start); + EXPECT_EQ(ip.ToString(), "192.168.1.1"); + + ip.RemovePrefix(4); + ip.RemoveSuffix(2); + EXPECT_EQ(ip.ToString(), "168.1"); +} + +TEST(StringViewTest, PerformanceCharacteristics) { + std::string large_string(1000000, 'A'); + StringView sv(large_string); + + EXPECT_EQ(sv.Size(), 1000000); + EXPECT_EQ(sv.Data(), large_string.data()); + + StringView substr = sv.Substr(500000, 1000); + EXPECT_EQ(substr.Size(), 1000); + EXPECT_EQ(substr.Data(), large_string.data() + 500000); + + std::string copy = sv.ToString(); + EXPECT_EQ(copy.size(), 1000000); +} \ No newline at end of file diff --git a/05_week/tasks/tracer/CMakeLists.txt b/05_week/tasks/tracer/CMakeLists.txt new file mode 100644 index 00000000..89739e1f --- /dev/null +++ b/05_week/tasks/tracer/CMakeLists.txt @@ -0,0 +1 @@ +add_gtest_asan(test_tracer test.cpp) \ No newline at end of file diff --git a/05_week/tasks/tracer/README.md b/05_week/tasks/tracer/README.md new file mode 100644 index 00000000..a1809d93 --- /dev/null +++ b/05_week/tasks/tracer/README.md @@ -0,0 +1,43 @@ +# Трассировщик + +Необходимо реализовать класс `Tracer`, подсчитывающий вызовы своих конструкторов, +операторов присваивания и деструктора. + +В качестве данных внутри класса должна храниться имя `std::string` и идентификатор. + +В классе присутствуют следующие счетчики, доступные извне: + +- `count` - общее количество созданных объектов (используется для генерации id) +- `default_ctor` - количество вызовов конструктора по умолчанию +- `str_ctor` - количество вызовов конструктора от строки +- `copy_ctor` - количество вызовов конструктора копирования +- `move_ctor` - количество вызовов конструктора перемещения +- `copy_assign` - количество вызовов оператора копирующего присваивания +- `move_assign` - количество вызовов оператора перемещающего присваивания +- `dtor` - количество вызовов деструктора +- `alive` - количество живых объектов в данный момент + +Правила изменения идентификатора (id): + +- присваивается при создании и **НЕ** меняется в течение жизни объекта +- каждый новый объект получает уникальный id (увеличивающийся счетчик) +- при использовании операторов не изменяется + +Класс предоставляет следующий функционал: + +- Конструктор по умолчанию - создает объект с именем `obj_{id}` ("obj_1") +- Конструктор от строки `std::string` - создает объект с именем `{name}_{id}` +- Конструктор копирования - копирует имя, но создает id +- Конструктор перемещения - перемещает имя, но создает id +- Оператор присваивания копированием - копирует имя, не изменяет id +- Оператор перемещения копированием - перемещает имя, не изменяет id +- Деструктор - изменяет свои счетчики +- Метод `Id` - возвращает идентификатор объекта +- Метод `Name` - возвращает константную ссылку на имя. +- Метод `Data` - возвращает указатель на данные строки. +- Метод `ResetStats` - сбрасывает на `0` все счетчики + +## Примечание + +- Для удобства отладки можно написать функции, выводящие на экран статистики + и логирующие действия. diff --git a/05_week/tasks/tracer/test.cpp b/05_week/tasks/tracer/test.cpp new file mode 100644 index 00000000..4db2a9c8 --- /dev/null +++ b/05_week/tasks/tracer/test.cpp @@ -0,0 +1,370 @@ +#include + +#include + +#include "tracer.cpp" + + +class TracerTest : public ::testing::Test { +protected: + void SetUp() override { + Tracer::ResetStats(); + } +}; + +TEST_F(TracerTest, DefaultConstructor) { + { + Tracer obj; + EXPECT_EQ(obj.Id(), 1); + EXPECT_EQ(obj.Name(), "obj_1"); + EXPECT_NE(obj.Data(), nullptr); + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::default_ctor, 1); + EXPECT_EQ(Tracer::alive, 1); + } + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::default_ctor, 1); + EXPECT_EQ(Tracer::str_ctor, 0); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::dtor, 1); + EXPECT_EQ(Tracer::alive, 0); +} + +TEST_F(TracerTest, StringConstructor) { + { + Tracer obj("Hello"); + EXPECT_EQ(obj.Id(), 1); + EXPECT_EQ(obj.Name(), "Hello_1"); + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::alive, 1); + } + + EXPECT_EQ(Tracer::count, 1); + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::dtor, 1); + EXPECT_EQ(Tracer::alive, 0); +} + +TEST_F(TracerTest, CopyConstructor) { + Tracer original("Original"); + int original_id = original.Id(); + const char* original_ptr = original.Data(); + std::string original_name = original.Name(); + + Tracer copy = original; + + EXPECT_NE(original_id, copy.Id()); + EXPECT_EQ(copy.Id(), 2); + EXPECT_EQ(copy.Name(), original_name); + + EXPECT_NE(original_ptr, copy.Data()); + + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::copy_ctor, 1); + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, MoveConstructor) { + Tracer original("OriginalWithLongString"); + int original_id = original.Id(); + const char* original_ptr = original.Data(); + std::string original_name = original.Name(); + + Tracer moved = std::move(original); + + EXPECT_NE(original_id, moved.Id()); + EXPECT_EQ(moved.Id(), 2); + + EXPECT_EQ(moved.Name(), original_name); + EXPECT_TRUE(original.Name().empty()); + EXPECT_EQ(moved.Data(), original_ptr); + + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::move_ctor, 1); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, CopyAssignment) { + Tracer a("Apple"); + Tracer b("BananaBombaOuououou"); + + int a_id = a.Id(); + int b_id = b.Id(); + const char* a_ptr_before = a.Data(); + const char* b_ptr = b.Data(); + + a = b; + + EXPECT_EQ(a.Id(), a_id); + EXPECT_EQ(b.Id(), b_id); + EXPECT_EQ(a.Name(), b.Name()); + EXPECT_NE(a.Data(), b_ptr); + EXPECT_NE(a.Data(), a_ptr_before); + + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::copy_assign, 1); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, MoveAssignment) { + Tracer a("Apple"); + Tracer b("BananaBombaFlySoGreat"); + + int a_id = a.Id(); + int b_id = b.Id(); + const char* b_ptr_before = b.Data(); + std::string b_name = b.Name(); + + a = std::move(b); + + EXPECT_EQ(a.Id(), a_id); + EXPECT_EQ(b.Id(), b_id); + + EXPECT_EQ(a.Name(), b_name); + EXPECT_TRUE(b.Name().empty()); + EXPECT_EQ(a.Data(), b_ptr_before); + + EXPECT_EQ(Tracer::move_assign, 1); + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, SelfAssignmentCopy) { + Tracer obj("Test"); + std::string original_name = obj.Name(); + const char* original_ptr = obj.Data(); + int original_id = obj.Id(); + + obj = obj; + + EXPECT_EQ(obj.Id(), original_id); + EXPECT_EQ(obj.Name(), original_name); + EXPECT_EQ(obj.Data(), original_ptr); + + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, SelfAssignmentMove) { + Tracer obj("Test"); + std::string original_name = obj.Name(); + const char* original_ptr = obj.Data(); + int original_id = obj.Id(); + + obj = static_cast(obj); + + EXPECT_EQ(obj.Id(), original_id); + EXPECT_EQ(obj.Data(), original_ptr); + + EXPECT_EQ(Tracer::move_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); +} + +TEST_F(TracerTest, VectorPushBackCopy) { + std::vector vec; + vec.reserve(3); + + Tracer obj("Item"); + const char* obj_ptr = obj.Data(); + + vec.push_back(obj); + + EXPECT_EQ(vec[0].Name(), obj.Name()); + EXPECT_NE(vec[0].Data(), obj_ptr); + + EXPECT_EQ(Tracer::copy_ctor, 1); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, VectorPushBackMove) { + std::vector vec; + vec.reserve(3); + + Tracer obj("LongNameToAvoidSSO"); + const char* obj_ptr = obj.Data(); + std::string obj_name = obj.Name(); + + vec.push_back(std::move(obj)); + + EXPECT_EQ(vec[0].Data(), obj_ptr); + EXPECT_EQ(vec[0].Name(), obj_name); + EXPECT_TRUE(obj.Name().empty()); + + EXPECT_EQ(Tracer::move_ctor, 1); + EXPECT_EQ(Tracer::alive, 2); +} + +TEST_F(TracerTest, VectorEmplaceBack) { + std::vector vec; + vec.reserve(3); + + vec.emplace_back("Hello"); + + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::alive, 1); +} + +TEST_F(TracerTest, VectorReallocation) { + std::vector vec; + + vec.emplace_back("First"); // 1 ctor + vec.emplace_back("Second"); // 1 ctor + realloc (1 move) + vec.emplace_back("Third"); // 1 ctor + realloc (2 move) + + EXPECT_EQ(Tracer::str_ctor, 3); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 3); // 1 + 2 move when realloc + EXPECT_EQ(Tracer::alive, 3); +} + +TEST_F(TracerTest, VectorWithReserve) { + std::vector vec; + vec.reserve(5); + + vec.emplace_back("First"); + vec.emplace_back("Second"); + vec.emplace_back("Third"); + vec.emplace_back("Fourth"); + vec.emplace_back("Fifth"); + + EXPECT_EQ(Tracer::str_ctor, 5); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::alive, 5); +} + +Tracer CreateTracer() { + return Tracer("Hello"); // RVO +} + +TEST_F(TracerTest, ReturnValueOptimization) { + Tracer obj = CreateTracer(); + + EXPECT_EQ(Tracer::str_ctor, 1); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); +} + +TEST_F(TracerTest, MultipleObjectsConsistency) { + { + Tracer a("A"); + Tracer b = a; // copy ctor + Tracer c = std::move(a); // move ctor + + std::vector vec; + vec.reserve(2); + vec.push_back(b); + vec.push_back(std::move(c)); + + a = b; // copy assignment + b = std::move(vec[0]); // move assignment + } + + int total_created = Tracer::default_ctor + Tracer::str_ctor + Tracer::copy_ctor + + Tracer::move_ctor; + + EXPECT_EQ(total_created, Tracer::dtor); + EXPECT_EQ(Tracer::alive, 0); + EXPECT_EQ(Tracer::count, total_created); +} + +TEST_F(TracerTest, DataMethodReturnsValidPointer) { + Tracer obj("Test"); + const char* ptr = obj.Data(); + + EXPECT_NE(ptr, nullptr); + EXPECT_EQ(ptr[0], 'T'); + EXPECT_EQ(ptr[1], 'e'); + EXPECT_EQ(ptr[2], 's'); + EXPECT_EQ(ptr[3], 't'); + + Tracer copy = obj; + EXPECT_NE(obj.Data(), copy.Data()); + EXPECT_STREQ(obj.Data(), copy.Data()); +} + +TEST_F(TracerTest, ResetStatsWorks) { + Tracer a("A"); + Tracer b = a; + + EXPECT_GT(Tracer::count, 0); + EXPECT_GT(Tracer::alive, 0); + + Tracer::ResetStats(); + + EXPECT_EQ(Tracer::count, 0); + EXPECT_EQ(Tracer::default_ctor, 0); + EXPECT_EQ(Tracer::str_ctor, 0); + EXPECT_EQ(Tracer::copy_ctor, 0); + EXPECT_EQ(Tracer::move_ctor, 0); + EXPECT_EQ(Tracer::copy_assign, 0); + EXPECT_EQ(Tracer::move_assign, 0); + EXPECT_EQ(Tracer::dtor, 0); + EXPECT_EQ(Tracer::alive, 0); + + EXPECT_FALSE(a.Name().empty()); + EXPECT_FALSE(b.Name().empty()); +} + +TEST_F(TracerTest, ChainedAssignments) { + Tracer a("A"); + Tracer b("B"); + Tracer c("C"); + + a = b = c; + + EXPECT_NE(a.Name().find("C_"), std::string::npos); + EXPECT_NE(b.Name().find("C_"), std::string::npos); + EXPECT_NE(c.Name().find("C_"), std::string::npos); + + EXPECT_EQ(a.Id(), 1); + EXPECT_EQ(b.Id(), 2); + EXPECT_EQ(c.Id(), 3); + + EXPECT_EQ(Tracer::copy_assign, 2); +} + +TEST_F(TracerTest, TemporaryObjectInExpression) { + Tracer a("Start"); + a = Tracer("Temp"); + + EXPECT_EQ(Tracer::str_ctor, 2); + EXPECT_EQ(Tracer::move_assign, 1); +} + +TEST_F(TracerTest, StdSwapUsesMoveOperations) { + Tracer a("ThisNameRequireLongStringToAvoidSSO"); + Tracer b("LongStringToAvoidSSO"); + std::string name_a = a.Name(); + std::string name_b = b.Name(); + + const char* a_ptr_before = a.Data(); + const char* b_ptr_before = b.Data(); + int a_id = a.Id(); + int b_id = b.Id(); + + std::swap(a, b); + + EXPECT_EQ(a.Name(), name_b); + EXPECT_EQ(b.Name(), name_a); + + EXPECT_EQ(a.Id(), a_id); + EXPECT_EQ(b.Id(), b_id); + + EXPECT_EQ(a.Data(), b_ptr_before); + EXPECT_EQ(b.Data(), a_ptr_before); + + // std::swap uses move construction and move assignment + EXPECT_EQ(Tracer::move_ctor + Tracer::move_assign, 3); +} diff --git a/05_week/tasks/tracer/tracer.cpp b/05_week/tasks/tracer/tracer.cpp new file mode 100644 index 00000000..2ccfb417 --- /dev/null +++ b/05_week/tasks/tracer/tracer.cpp @@ -0,0 +1,6 @@ +#include + + +class Tracer { + +}; \ No newline at end of file diff --git a/cmake/TestSolution.cmake b/cmake/TestSolution.cmake index acc27ee1..692da330 100644 --- a/cmake/TestSolution.cmake +++ b/cmake/TestSolution.cmake @@ -11,6 +11,16 @@ function(add_gtest TARGET) ) endfunction() +function(add_gtest_asan TARGET) + add_psds_executable(${TARGET} ${ARGN}) + target_compile_options(${TARGET} PRIVATE -g -fsanitize=address,undefined) + target_link_libraries(${TARGET} PRIVATE GTest::gtest GTest::gtest_main) + target_link_options(${TARGET} PRIVATE -fsanitize=address,undefined) + set_target_properties(${TARGET} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_TASKS_DIR} + ) +endfunction() + function(add_example NAME) if(BUILD_EXAMPLES) add_psds_executable(${NAME} ${ARGN}) diff --git a/grading/deadlines.json b/grading/deadlines.json index d9138f50..630f54c1 100644 --- a/grading/deadlines.json +++ b/grading/deadlines.json @@ -131,5 +131,26 @@ "max_score": 400, "deadline": "2025-12-26 23:59", "description": "Комплексная амплитуда" + }, + + "tracer": { + "max_score": 100, + "deadline": "2026-02-16 23:59", + "description": "Трассировщик" + }, + "string_view": { + "max_score": 200, + "deadline": "2026-02-16 23:59", + "description": "Представление строки" + }, + "cow_string": { + "max_score": 300, + "deadline": "2026-02-16 23:59", + "description": "Коровья строка" + }, + "simple_vector": { + "max_score": 400, + "deadline": "2026-02-16 23:59", + "description": "Простой вектор" } } \ No newline at end of file