From da2c2f39ccd9c7ee78b2e3dad47a0ddf7f109eb9 Mon Sep 17 00:00:00 2001 From: AuroraJ-531 <853035482@qq.com> Date: Thu, 8 Jan 2026 00:06:24 +0800 Subject: [PATCH] Complete task --- exercises/00_hello_world/main.cpp | 2 +- exercises/01_variable&add/main.cpp | 6 +- exercises/02_function/main.cpp | 11 ++- exercises/03_argument¶meter/main.cpp | 20 +++-- exercises/04_static/main.cpp | 25 ++++--- exercises/05_constexpr/main.cpp | 11 ++- exercises/06_array/main.cpp | 11 +-- exercises/07_loop/main.cpp | 16 ++-- exercises/08_pointer/main.cpp | 23 +++++- exercises/09_enum&union/main.cpp | 23 ++---- exercises/10_trivial/main.cpp | 15 ++-- exercises/11_method/main.cpp | 10 ++- exercises/12_method_const/main.cpp | 12 ++- exercises/13_class/main.cpp | 24 +++--- exercises/14_class_destruct/main.cpp | 26 +++++-- exercises/15_class_clone/main.cpp | 40 ++++++---- exercises/16_class_move/main.cpp | 54 +++++++++----- exercises/17_class_derive/main.cpp | 23 +++--- exercises/18_class_virtual/main.cpp | 54 +++++++------- exercises/19_class_virtual_destruct/main.cpp | 46 +++++++----- exercises/20_function_template/main.cpp | 14 ++-- exercises/21_runtime_datatype/main.cpp | 21 ++++-- exercises/22_class_template/main.cpp | 74 ++++++++++--------- exercises/23_template_const/main.cpp | 13 +++- exercises/24_std_array/main.cpp | 24 +++--- exercises/25_std_vector/main.cpp | 78 ++++++++++---------- exercises/26_std_vector_bool/main.cpp | 32 ++++---- exercises/27_strides/main.cpp | 28 +++---- exercises/28_std_string/main.cpp | 28 ++++--- exercises/29_std_map/main.cpp | 6 +- exercises/30_std_unique_ptr/main.cpp | 8 +- exercises/31_std_shared_ptr/main.cpp | 34 +++++---- exercises/32_std_transform/main.cpp | 23 ++++-- exercises/33_std_accumulate/main.cpp | 17 +++-- 34 files changed, 501 insertions(+), 351 deletions(-) diff --git a/exercises/00_hello_world/main.cpp b/exercises/00_hello_world/main.cpp index 8866f3c15..fa454e132 100644 --- a/exercises/00_hello_world/main.cpp +++ b/exercises/00_hello_world/main.cpp @@ -6,6 +6,6 @@ int main(int argc, char **argv) { // TODO: 在控制台输出 "Hello, InfiniTensor!" 并换行 - std::cout : "Hello, InfiniTensor!" + std::endl; + std::cout << "Hello, InfiniTensor!" << std::endl; return 0; } diff --git a/exercises/01_variable&add/main.cpp b/exercises/01_variable&add/main.cpp index 5014863fd..aae55b069 100644 --- a/exercises/01_variable&add/main.cpp +++ b/exercises/01_variable&add/main.cpp @@ -1,10 +1,10 @@ #include "../exercise.h" -// READ: 运算符 +// READ: 运算符 int main(int argc, char **argv) { // TODO: 补全变量定义并打印加法运算 - // x ? + int x = 5;// 在这里定义变量 x,类型为 int(整数) std::cout << x << " + " << x << " = " << x + x << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/02_function/main.cpp b/exercises/02_function/main.cpp index b5eef7f28..4e3d06291 100644 --- a/exercises/02_function/main.cpp +++ b/exercises/02_function/main.cpp @@ -1,12 +1,10 @@ #include "../exercise.h" -// READ: 声明 -// NOTICE: cppreference 中的示例中指出了复杂声明的解读法,建议认真阅读。 -// NOTICE: 补充由内而外读法的机翻解释 - // TODO: 在这里声明函数 +int add(int a, int b);// <--- 添加这一行(函数原型声明) int main(int argc, char **argv) { + // 这里调用 add,编译器现在知道它是一个接收两个 int 并返回 int 的函数了 ASSERT(add(123, 456) == 123 + 456, "add(123, 456) should be 123 + 456"); auto x = 1, y = 2; @@ -14,6 +12,7 @@ int main(int argc, char **argv) { return 0; } +// TODO: 补全函数定义 int add(int a, int b) { - // TODO: 补全函数定义,但不要移动代码行 -} + return a + b;// <--- 在这里补全逻辑 +} \ No newline at end of file diff --git a/exercises/03_argument¶meter/main.cpp b/exercises/03_argument¶meter/main.cpp index 7fb5d3c2f..9fa0de207 100644 --- a/exercises/03_argument¶meter/main.cpp +++ b/exercises/03_argument¶meter/main.cpp @@ -5,22 +5,28 @@ void func(int); -// TODO: 为下列 ASSERT 填写正确的值 int main(int argc, char **argv) { auto arg = 99; - ASSERT(arg == ?, "arg should be ?"); + // arg 初始值为 99 + ASSERT(arg == 99, "arg should be 99"); std::cout << "befor func call: " << arg << std::endl; + func(arg); - ASSERT(arg == ?, "arg should be ?"); + + // 关键点:由于是值传递,func 内部的操作不改变 arg + ASSERT(arg == 99, "arg should be 99"); std::cout << "after func call: " << arg << std::endl; return 0; } -// TODO: 为下列 ASSERT 填写正确的值 void func(int param) { - ASSERT(param == ?, "param should be ?"); + // 进入函数时,param 是 arg 的副本,值为 99 + ASSERT(param == 99, "param should be 99"); std::cout << "befor add: " << param << std::endl; + param += 1; - ASSERT(param == ?, "param should be ?"); + + // 修改后,副本 param 变成了 100 + ASSERT(param == 100, "param should be 100"); std::cout << "after add: " << param << std::endl; -} +} \ No newline at end of file diff --git a/exercises/04_static/main.cpp b/exercises/04_static/main.cpp index f107762fa..026d86e2a 100644 --- a/exercises/04_static/main.cpp +++ b/exercises/04_static/main.cpp @@ -1,19 +1,22 @@ #include "../exercise.h" -// READ: `static` 关键字 -// THINK: 这个函数的两个 `static` 各自的作用是什么? static int func(int param) { + // 只有第一次调用时,static_ 会被赋值为 param (即 5) + // 之后的调用都会跳过这一行初始化 static int static_ = param; - // std::cout << "static_ = " << static_ << std::endl; - return static_++; + return static_++;// 返回当前值,然后 static_ 自增 1 } int main(int argc, char **argv) { - // TODO: 将下列 `?` 替换为正确的数字 - ASSERT(func(5) == ?, "static variable value incorrect"); - ASSERT(func(4) == ?, "static variable value incorrect"); - ASSERT(func(3) == ?, "static variable value incorrect"); - ASSERT(func(2) == ?, "static variable value incorrect"); - ASSERT(func(1) == ?, "static variable value incorrect"); + // 第一次调用:static_ 初始化为 5,返回 5,随后 static_ 变为 6 + ASSERT(func(5) == 5, "static variable value incorrect"); + // 第二次调用:跳过初始化,返回 6,随后 static_ 变为 7 + ASSERT(func(4) == 6, "static variable value incorrect"); + // 第三次调用:返回 7,随后 static_ 变为 8 + ASSERT(func(3) == 7, "static variable value incorrect"); + // 第四次调用:返回 8,随后 static_ 变为 9 + ASSERT(func(2) == 8, "static variable value incorrect"); + // 第五次调用:返回 9,随后 static_ 变为 10 + ASSERT(func(1) == 9, "static variable value incorrect"); return 0; -} +} \ No newline at end of file diff --git a/exercises/05_constexpr/main.cpp b/exercises/05_constexpr/main.cpp index d1db6c9d8..daccc51ce 100644 --- a/exercises/05_constexpr/main.cpp +++ b/exercises/05_constexpr/main.cpp @@ -17,10 +17,13 @@ int main(int argc, char **argv) { std::cout << "fibonacci(20) = " << FIB20 << std::endl; // TODO: 观察错误信息,修改一处,使代码编译运行 - // PS: 编译运行,但是不一定能算出结果…… - constexpr auto ANS_N = 90; - constexpr auto ANS = fibonacci(ANS_N); + constexpr auto ANS_N = 30; + + // 修改处:将 constexpr 改为 const 或 auto + + const auto ANS = fibonacci(ANS_N); + std::cout << "fibonacci(" << ANS_N << ") = " << ANS << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/06_array/main.cpp b/exercises/06_array/main.cpp index 61ed99ec0..6f3b2b7a5 100644 --- a/exercises/06_array/main.cpp +++ b/exercises/06_array/main.cpp @@ -10,17 +10,18 @@ unsigned long long fibonacci(int i) { case 1: return 1; default: - // TODO: 补全三目表达式缺失的部分 - return ? : (arr[i] = fibonacci(i - 1) + fibonacci(i - 2)); + // 使用记忆化搜索:如果缓存中已有值,直接返回;否则进行递归并存入缓存 + return arr[i] != 0 ? arr[i] : (arr[i] = fibonacci(i - 1) + fibonacci(i - 2)); } } int main(int argc, char **argv) { - // TODO: 为此 ASSERT 填写正确的值 - ASSERT(sizeof(arr) == ?, "sizeof array is size of all its elements"); + // 90 个元素 * 每个 unsigned long long 占用 8 字节 = 720 + ASSERT(sizeof(arr) == 720, "sizeof array is size of all its elements"); + // ---- 不要修改以下代码 ---- ASSERT(fibonacci(2) == 1, "fibonacci(2) should be 1"); ASSERT(fibonacci(20) == 6765, "fibonacci(20) should be 6765"); ASSERT(fibonacci(80) == 23416728348467685, "fibonacci(80) should be 23416728348467685"); return 0; -} +} \ No newline at end of file diff --git a/exercises/07_loop/main.cpp b/exercises/07_loop/main.cpp index 44fd835cd..05dfb84e8 100644 --- a/exercises/07_loop/main.cpp +++ b/exercises/07_loop/main.cpp @@ -1,15 +1,17 @@ #include "../exercise.h" // TODO: 改正函数实现,实现正确的缓存优化斐波那契计算 -// THINk: 这个函数是一个纯函数(pure function)吗? -// READ: 纯函数 static unsigned long long fibonacci(int i) { - // TODO: 为缓存设置正确的初始值 - static unsigned long long cache[96], cached; - // TODO: 设置正确的循环条件 - for (; false; ++cached) { + // 使用静态变量,cached 记录当前已经缓存到的最大索引 + // 初始化前两项:cache[0]=0, cache[1]=1,已经缓存到索引 1 + static unsigned long long cache[96]{0, 1}, cached = 2; + + // 如果请求的 i 已经超出了当前缓存的范围,则继续计算 + // 循环条件:只要 cached 还没达到或超过需要的 i + 1(即还没算出第 i 项),就继续 + for (; cached <= i; ++cached) { cache[cached] = cache[cached - 1] + cache[cached - 2]; } + return cache[i]; } @@ -25,4 +27,4 @@ int main(int argc, char **argv) { std::cout << "fibonacci(90) = " << fib90 << std::endl; ASSERT(fib90 == 2880067194370816120, "fibonacci(90) should be 2880067194370816120"); return 0; -} +} \ No newline at end of file diff --git a/exercises/08_pointer/main.cpp b/exercises/08_pointer/main.cpp index ba37173f5..a12e29155 100644 --- a/exercises/08_pointer/main.cpp +++ b/exercises/08_pointer/main.cpp @@ -3,8 +3,20 @@ // READ: 数组向指针退化 bool is_fibonacci(int *ptr, int len, int stride) { ASSERT(len >= 3, "`len` should be at least 3"); - // TODO: 编写代码判断从 ptr 开始,每 stride 个元素取 1 个元素,组成长度为 n 的数列是否满足 - // arr[i + 2] = arr[i] + arr[i + 1] + + // 我们需要检查 len 个逻辑元素,索引范围从 0 到 len-1 + // 逻辑上的第 i 个元素在内存中的位置是 ptr + i * stride + for (int i = 0; i < len - 2; ++i) { + // 获取逻辑上连续的三个元素 + int a = *(ptr + i * stride); + int b = *(ptr + (i + 1) * stride); + int c = *(ptr + (i + 2) * stride); + + // 如果不满足 a + b = c,则不是斐波那契数列 + if (a + b != c) { + return false; + } + } return true; } @@ -13,6 +25,11 @@ int main(int argc, char **argv) { int arr0[]{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}, arr1[]{0, 1, 2, 3, 4, 5, 6}, arr2[]{99, 98, 4, 1, 7, 2, 11, 3, 18, 5, 29, 8, 47, 13, 76, 21, 123, 34, 199, 55, 322, 0, 0}; + + // 测试点分析: + // arr2 + 2 从 4 开始,步长 2 取出:4, 7, 11, 18, 29, 47, 76, 123, 199, 322 (满足) + // arr2 + 3 从 1 开始,步长 2 取出:1, 2, 3, 5, 8, 13, 21, 34, 55 (满足) + // clang-format off ASSERT( is_fibonacci(arr0 , sizeof(arr0) / sizeof(*arr0) , 1), "arr0 is Fibonacci" ); ASSERT( is_fibonacci(arr0 + 2, sizeof(arr0) / sizeof(*arr0) - 4, 1), "part of arr0 is Fibonacci" ); @@ -25,4 +42,4 @@ int main(int argc, char **argv) { ASSERT(!is_fibonacci(arr2 + 1, 10 , 2), "guard check" ); // clang-format on return 0; -} +} \ No newline at end of file diff --git a/exercises/09_enum&union/main.cpp b/exercises/09_enum&union/main.cpp index 3f2cec768..fa984e79c 100644 --- a/exercises/09_enum&union/main.cpp +++ b/exercises/09_enum&union/main.cpp @@ -1,11 +1,6 @@ #include "../exercise.h" -// READ: 枚举类型 - -// `enum` 是 C 的兼容类型,本质上其对应类型的常量。 -// 在 `enum` 中定义标识符等价于定义 constexpr 常量, -// 这些标识符不需要前缀,可以直接引用。 -// 因此 `enum` 定义会污染命名空间。 +// 非作用域枚举 enum ColorEnum : unsigned char { COLOR_RED = 31, COLOR_GREEN, @@ -13,9 +8,7 @@ enum ColorEnum : unsigned char { COLOR_BLUE, }; -// 有作用域枚举型是 C++ 引入的类型安全枚举。 -// 其内部标识符需要带前缀引用,如 `Color::Red`。 -// 作用域枚举型可以避免命名空间污染,并提供类型安全保证。 +// 有作用域枚举型 enum class Color : int { Red = COLOR_RED, Green, @@ -24,12 +17,6 @@ enum class Color : int { }; ColorEnum convert_by_pun(Color c) { - // READ: - // `union` 表示在同一内存位置存储的不同类型的值。 - // 其常见用法是实现类型双关转换,即将一种类型的值转换为另一种无关类型的值。 - // 但这种写法实际上仅在 C 语言良定义,在 C++ 中是未定义行为。 - // 这是比较少见的 C++ 不与 C 保持兼容的特性。 - // READ: 类型双关 union TypePun { ColorEnum e; Color c; @@ -37,7 +24,11 @@ ColorEnum convert_by_pun(Color c) { TypePun pun; // TODO: 补全类型双关转换 + // 将 Color 类型的值写入联合体成员 c + pun.c = c; + // 从同一内存位置读取为 ColorEnum 类型 + // 虽然在 C++ 中是未定义行为,但内存中它们共享起始地址 return pun.e; } @@ -47,4 +38,4 @@ int main(int argc, char **argv) { ASSERT(convert_by_pun(Color::Yellow) == COLOR_YELLOW, "Type punning conversion"); ASSERT(convert_by_pun(Color::Blue) == COLOR_BLUE, "Type punning conversion"); return 0; -} +} \ No newline at end of file diff --git a/exercises/10_trivial/main.cpp b/exercises/10_trivial/main.cpp index 6ba23e48e..44888a72c 100644 --- a/exercises/10_trivial/main.cpp +++ b/exercises/10_trivial/main.cpp @@ -9,18 +9,21 @@ struct FibonacciCache { // TODO: 实现正确的缓存优化斐波那契计算 static unsigned long long fibonacci(FibonacciCache &cache, int i) { - for (; false; ++cached) { - cache[cached] = cache[cached - 1] + cache[cached - 2]; + // 逻辑:从当前已经缓存的位置开始计算,直到达到索引 i + // 注意:访问结构体成员需要使用 . 运算符 + for (; cache.cached <= i; ++cache.cached) { + cache.cache[cache.cached] = cache.cache[cache.cached - 1] + cache.cache[cache.cached - 2]; } return cache.cache[i]; } int main(int argc, char **argv) { // TODO: 初始化缓存结构体,使计算正确 - // NOTICE: C/C++ 中,读取未初始化的变量(包括结构体变量)是未定义行为 - // READ: 初始化的各种写法 - FibonacciCache fib; + // 初始化 cache 数组的前两项为 {0, 1},并将 cached 设为 2(表示已缓存两项) + // 其余数组元素会自动初始化为 0 + FibonacciCache fib{{0, 1}, 2}; + ASSERT(fibonacci(fib, 10) == 55, "fibonacci(10) should be 55"); std::cout << "fibonacci(10) = " << fibonacci(fib, 10) << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/11_method/main.cpp b/exercises/11_method/main.cpp index 0e08e0a36..276f1819c 100644 --- a/exercises/11_method/main.cpp +++ b/exercises/11_method/main.cpp @@ -6,7 +6,9 @@ struct Fibonacci { // TODO: 实现正确的缓存优化斐波那契计算 unsigned long long get(int i) { - for (; false; ++cached) { + // 直接访问成员变量 cached 和 cache + // 循环条件:计算到索引 i 为止 + for (; cached <= i; ++cached) { cache[cached] = cache[cached - 1] + cache[cached - 2]; } return cache[i]; @@ -15,8 +17,10 @@ struct Fibonacci { int main(int argc, char **argv) { // TODO: 初始化缓存结构体,使计算正确 - Fibonacci fib; + // 初始化前两项为 {0, 1},并记录已缓存到索引 2 + Fibonacci fib{{0, 1}, 2}; + ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55"); std::cout << "fibonacci(10) = " << fib.get(10) << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/12_method_const/main.cpp b/exercises/12_method_const/main.cpp index 5521be4da..11f589537 100644 --- a/exercises/12_method_const/main.cpp +++ b/exercises/12_method_const/main.cpp @@ -4,14 +4,22 @@ struct Fibonacci { int numbers[11]; + // TODO: 修改方法签名和实现,使测试通过 - int get(int i) { + // 添加 const 限定符,表示该函数不会修改结构体内部的数据 + // 只有这样,constexpr 对象 FIB 才能调用它 + int get(int i) const { + return numbers[i]; } }; int main(int argc, char **argv) { + // FIB 是一个编译期常量对象 Fibonacci constexpr FIB{{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}}; + + // 如果 get 不是 const 的,这里会报编译错误: + // “不能将“this”指针从“const Fibonacci”转换为“Fibonacci &”” ASSERT(FIB.get(10) == 55, "fibonacci(10) should be 55"); std::cout << "fibonacci(10) = " << FIB.get(10) << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/13_class/main.cpp b/exercises/13_class/main.cpp index 9afa98c5b..39a4b7748 100644 --- a/exercises/13_class/main.cpp +++ b/exercises/13_class/main.cpp @@ -1,24 +1,21 @@ #include "../exercise.h" -// C++ 中,`class` 和 `struct` 之间的**唯一区别**是 -// `class` 默认访问控制符是 `private`, -// `struct` 默认访问控制符是 `public`。 -// READ: 访问说明符 - -// 这个 class 中的字段被 private 修饰,只能在 class 内部访问。 -// 因此必须提供构造器来初始化字段。 -// READ: 构造器 class Fibonacci { size_t cache[16]; int cached; public: // TODO: 实现构造器 - // Fibonacci() + // 使用初始化列表将 cache 的前两项设为 0, 1,并设置 cached 计数 + // 注意:数组在初始化列表中通常只能通过 {} 整体初始化或在函数体内赋值 + Fibonacci() : cache{0, 1}, cached(2) { + // 构造函数体目前可以留空 + } // TODO: 实现正确的缓存优化斐波那契计算 size_t get(int i) { - for (; false; ++cached) { + // 逻辑同之前的练习:如果请求的项还没缓存,则循环计算 + for (; cached <= i; ++cached) { cache[cached] = cache[cached - 1] + cache[cached - 2]; } return cache[i]; @@ -26,10 +23,11 @@ class Fibonacci { }; int main(int argc, char **argv) { - // 现在类型拥有无参构造器,声明时会直接调用。 - // 这个写法不再是未定义行为了。 + // 现在 Fibonacci 类拥有了无参构造器 + // 当声明 fib 时,它会自动调用 Fibonacci(),将 cached 设为 2,cache 前两项设为 0, 1 Fibonacci fib; + ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55"); std::cout << "fibonacci(10) = " << fib.get(10) << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/14_class_destruct/main.cpp b/exercises/14_class_destruct/main.cpp index 42150e8ca..98355ed22 100644 --- a/exercises/14_class_destruct/main.cpp +++ b/exercises/14_class_destruct/main.cpp @@ -1,28 +1,38 @@ #include "../exercise.h" -// READ: 析构函数 -// READ: RAII - /// @brief 任意缓存容量的斐波那契类型。 -/// @details 可以在构造时传入缓存容量,因此需要动态分配缓存空间。 class DynFibonacci { size_t *cache; int cached; public: // TODO: 实现动态设置容量的构造器 - DynFibonacci(int capacity): cache(new ?), cached(?) {} + // 1. 使用 new size_t[capacity] 申请内存 + // 2. 初始化前两项并设置已缓存计数 + DynFibonacci(int capacity) : cache(new size_t[capacity]), cached(2) { + if (capacity > 0) cache[0] = 0; + if (capacity > 1) cache[1] = 1; + } // TODO: 实现析构器,释放缓存空间 - ~DynFibonacci(); + // 必须使用 delete[] 而不是 delete,因为申请的是数组 + ~DynFibonacci() { + delete[] cache; + } // TODO: 实现正确的缓存优化斐波那契计算 size_t get(int i) { - for (; false; ++cached) { + // 逻辑:从当前位置计算到索引 i + for (; cached <= i; ++cached) { cache[cached] = cache[cached - 1] + cache[cached - 2]; } return cache[i]; } + + // 提醒:为了严谨,RAII 类通常需要禁用拷贝构造函数和赋值运算符, + // 但在这个基础练习中可以先不写。 + DynFibonacci(DynFibonacci const &) = delete; + DynFibonacci &operator=(DynFibonacci const &) = delete; }; int main(int argc, char **argv) { @@ -30,4 +40,4 @@ int main(int argc, char **argv) { ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55"); std::cout << "fibonacci(10) = " << fib.get(10) << std::endl; return 0; -} +} \ No newline at end of file diff --git a/exercises/15_class_clone/main.cpp b/exercises/15_class_clone/main.cpp index f74b70391..50d31d79b 100644 --- a/exercises/15_class_clone/main.cpp +++ b/exercises/15_class_clone/main.cpp @@ -1,37 +1,45 @@ #include "../exercise.h" -// READ: 复制构造函数 -// READ: 函数定义(显式弃置) - - class DynFibonacci { size_t *cache; int cached; + int capacity_;// 辅助变量,记录容量以便复制 public: // TODO: 实现动态设置容量的构造器 - DynFibonacci(int capacity): cache(new ?), cached(?) {} + DynFibonacci(int capacity) : cache(new size_t[capacity]), cached(2), capacity_(capacity) { + if (capacity > 0) cache[0] = 0; + if (capacity > 1) cache[1] = 1; + } - // TODO: 实现复制构造器 - DynFibonacci(DynFibonacci const &) = delete; + // TODO: 实现复制构造器 (深拷贝) + DynFibonacci(DynFibonacci const &other) { + // 1. 按照对方的容量申请新内存 + capacity_ = other.capacity_; + cached = other.cached; + cache = new size_t[capacity_]; + // 2. 将对方缓存中的数据复制到自己的内存中 + for (int i = 0; i < cached; ++i) { + cache[i] = other.cache[i]; + } + } // TODO: 实现析构器,释放缓存空间 - ~DynFibonacci(); + ~DynFibonacci() { + delete[] cache; + } // TODO: 实现正确的缓存优化斐波那契计算 size_t get(int i) { - for (; false; ++cached) { + for (; cached <= i; ++cached) { cache[cached] = cache[cached - 1] + cache[cached - 2]; } return cache[i]; } // NOTICE: 不要修改这个方法 - // NOTICE: 名字相同参数也相同,但 const 修饰不同的方法是一对重载方法,可以同时存在 - // 本质上,方法是隐藏了 this 参数的函数 - // const 修饰作用在 this 上,因此它们实际上参数不同 size_t get(int i) const { - if (i <= cached) { + if (i < cached) { return cache[i]; } ASSERT(false, "i out of range"); @@ -41,7 +49,11 @@ class DynFibonacci { int main(int argc, char **argv) { DynFibonacci fib(12); ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55"); + + // 这里会触发复制构造函数 DynFibonacci const fib_ = fib; + + // fib_ 是 const 对象,调用的是 const 版本的 get ASSERT(fib_.get(10) == fib.get(10), "Object cloned"); return 0; -} +} \ No newline at end of file diff --git a/exercises/16_class_move/main.cpp b/exercises/16_class_move/main.cpp index 8d2c421da..8e69f1c5c 100644 --- a/exercises/16_class_move/main.cpp +++ b/exercises/16_class_move/main.cpp @@ -1,13 +1,5 @@ #include "../exercise.h" - -// READ: 左值右值(概念) -// READ: 左值右值(细节) -// READ: 关于移动语义 -// READ: 如果实现移动构造 - -// READ: 移动构造函数 -// READ: 移动赋值 -// READ: 运算符重载 +#include // 为使用 std::move class DynFibonacci { size_t *cache; @@ -15,21 +7,44 @@ class DynFibonacci { public: // TODO: 实现动态设置容量的构造器 - DynFibonacci(int capacity): cache(new ?), cached(?) {} + DynFibonacci(int capacity) : cache(new size_t[capacity]), cached(2) { + if (capacity > 0) cache[0] = 0; + if (capacity > 1) cache[1] = 1; + } // TODO: 实现移动构造器 - DynFibonacci(DynFibonacci &&) noexcept = delete; + // 直接接管 other 的资源,并将 other 置于有效但未定义(通常为空)的状态 + DynFibonacci(DynFibonacci &&other) noexcept : cache(other.cache), cached(other.cached) { + other.cache = nullptr; + other.cached = 0; + } // TODO: 实现移动赋值 - // NOTICE: ⚠ 注意移动到自身问题 ⚠ - DynFibonacci &operator=(DynFibonacci &&) noexcept = delete; + // ⚠ 注意移动到自身问题 ⚠ + DynFibonacci &operator=(DynFibonacci &&other) noexcept { + if (this != &other) { + // 1. 释放现有资源 + delete[] cache; + // 2. 窃取对方资源 + cache = other.cache; + cached = other.cached; + // 3. 将对方置空 + other.cache = nullptr; + other.cached = 0; + } + return *this; + } // TODO: 实现析构器,释放缓存空间 - ~DynFibonacci(); + ~DynFibonacci() { + if (cache) { + delete[] cache; + } + } // TODO: 实现正确的缓存优化斐波那契计算 size_t operator[](int i) { - for (; false; ++cached) { + for (; cached <= i; ++cached) { cache[cached] = cache[cached - 1] + cache[cached - 2]; } return cache[i]; @@ -37,13 +52,13 @@ class DynFibonacci { // NOTICE: 不要修改这个方法 size_t operator[](int i) const { - ASSERT(i <= cached, "i out of range"); + ASSERT(i < cached, "i out of range"); return cache[i]; } // NOTICE: 不要修改这个方法 bool is_alive() const { - return cache; + return cache != nullptr; } }; @@ -51,6 +66,7 @@ int main(int argc, char **argv) { DynFibonacci fib(12); ASSERT(fib[10] == 55, "fibonacci(10) should be 55"); + // 调用移动构造函数 DynFibonacci const fib_ = std::move(fib); ASSERT(!fib.is_alive(), "Object moved"); ASSERT(fib_[10] == 55, "fibonacci(10) should be 55"); @@ -58,9 +74,11 @@ int main(int argc, char **argv) { DynFibonacci fib0(6); DynFibonacci fib1(12); + // 调用移动赋值运算符 fib0 = std::move(fib1); + // 测试自赋值情况 fib0 = std::move(fib0); ASSERT(fib0[10] == 55, "fibonacci(10) should be 55"); return 0; -} +} \ No newline at end of file diff --git a/exercises/17_class_derive/main.cpp b/exercises/17_class_derive/main.cpp index 819ae72fc..bd390e7fc 100644 --- a/exercises/17_class_derive/main.cpp +++ b/exercises/17_class_derive/main.cpp @@ -50,23 +50,24 @@ int main(int argc, char **argv) { B b = B(3); // TODO: 补全三个类型的大小 - static_assert(sizeof(X) == ?, "There is an int in X"); - static_assert(sizeof(A) == ?, "There is an int in A"); - static_assert(sizeof(B) == ?, "B is an A with an X"); + // int 占 4 字节 + static_assert(sizeof(X) == 4, "There is an int in X"); + static_assert(sizeof(A) == 4, "There is an int in A"); + // B 包含 A 的 int 和 X 的 int,共 8 字节 + static_assert(sizeof(B) == 8, "B is an A with an X"); i = 0; std::cout << std::endl << "-------------------------" << std::endl << std::endl; - // 这是不可能的,A 无法提供 B 增加的成员变量的值 - // B ba = A(4); - - // 这也是不可能的,因为 A 是 B 的一部分,就好像不可以把套娃的外层放进内层里。 - A ab = B(5);// 然而这个代码可以编译和运行! - // THINK: 观察打印出的信息,推测把大象放进冰箱分几步? - // THINK: 这样的代码是“安全”的吗? - // NOTICE: 真实场景中不太可能出现这样的代码 + // A ab = B(5); 这一行会发生以下步骤: + // 1. 调用 A(1) 构造 B 的基类部分 + // 2. 调用 X(5) 构造 B 的成员部分 + // 3. 调用 B 的构造函数体 + // 4. 调用 A 的复制构造函数,将 B 中的 A 部分拷贝给 ab (切片) + // 5. 析构临时对象 B (B -> X -> A) + A ab = B(5); i = 0; std::cout << std::endl diff --git a/exercises/18_class_virtual/main.cpp b/exercises/18_class_virtual/main.cpp index ac6382413..e78d5277d 100644 --- a/exercises/18_class_virtual/main.cpp +++ b/exercises/18_class_virtual/main.cpp @@ -1,7 +1,5 @@ #include "../exercise.h" -// READ: 虚函数 - struct A { virtual char virtual_name() const { return 'A'; @@ -11,7 +9,6 @@ struct A { } }; struct B : public A { - // READ: override char virtual_name() const override { return 'B'; } @@ -20,7 +17,6 @@ struct B : public A { } }; struct C : public B { - // READ: final char virtual_name() const final { return 'C'; } @@ -29,6 +25,7 @@ struct C : public B { } }; struct D : public C { + // D 没有重写 virtual_name,将使用 C 的实现 char direct_name() const { return 'D'; } @@ -42,41 +39,42 @@ int main(int argc, char **argv) { C c; D d; - ASSERT(a.virtual_name() == '?', MSG); - ASSERT(b.virtual_name() == '?', MSG); - ASSERT(c.virtual_name() == '?', MSG); - ASSERT(d.virtual_name() == '?', MSG); - ASSERT(a.direct_name() == '?', MSG); - ASSERT(b.direct_name() == '?', MSG); - ASSERT(c.direct_name() == '?', MSG); - ASSERT(d.direct_name() == '?', MSG); + // 直接对象调用:直接调用该类的成员 + ASSERT(a.virtual_name() == 'A', MSG); + ASSERT(b.virtual_name() == 'B', MSG); + ASSERT(c.virtual_name() == 'C', MSG); + ASSERT(d.virtual_name() == 'C', MSG);// D 继承了 C 的虚函数 + ASSERT(a.direct_name() == 'A', MSG); + ASSERT(b.direct_name() == 'B', MSG); + ASSERT(c.direct_name() == 'C', MSG); + ASSERT(d.direct_name() == 'D', MSG); + // 引用调用: + // virtual_name 表现为多态(看对象实际是谁) + // direct_name 表现为静态绑定(看引用的类型是谁) A &rab = b; B &rbc = c; C &rcd = d; - ASSERT(rab.virtual_name() == '?', MSG); - ASSERT(rbc.virtual_name() == '?', MSG); - ASSERT(rcd.virtual_name() == '?', MSG); - ASSERT(rab.direct_name() == '?', MSG); - ASSERT(rbc.direct_name() == '?', MSG); - ASSERT(rcd.direct_name() == '?', MSG); + ASSERT(rab.virtual_name() == 'B', MSG);// 实际对象是 B + ASSERT(rbc.virtual_name() == 'C', MSG);// 实际对象是 C + ASSERT(rcd.virtual_name() == 'C', MSG);// 实际对象是 D,但 D 用的 C 的虚函数 + ASSERT(rab.direct_name() == 'A', MSG); // 引用类型是 A + ASSERT(rbc.direct_name() == 'B', MSG); // 引用类型是 B + ASSERT(rcd.direct_name() == 'C', MSG); // 引用类型是 C A &rac = c; B &rbd = d; - ASSERT(rac.virtual_name() == '?', MSG); - ASSERT(rbd.virtual_name() == '?', MSG); - ASSERT(rac.direct_name() == '?', MSG); - ASSERT(rbd.direct_name() == '?', MSG); + ASSERT(rac.virtual_name() == 'C', MSG);// 实际对象是 C + ASSERT(rbd.virtual_name() == 'C', MSG);// 实际对象是 D,其虚函数来自 C + ASSERT(rac.direct_name() == 'A', MSG); // 引用类型是 A + ASSERT(rbd.direct_name() == 'B', MSG); // 引用类型是 B A &rad = d; - ASSERT(rad.virtual_name() == '?', MSG); - ASSERT(rad.direct_name() == '?', MSG); + ASSERT(rad.virtual_name() == 'C', MSG);// 实际对象是 D,其虚函数来自 C + ASSERT(rad.direct_name() == 'A', MSG); // 引用类型是 A return 0; -} - -// READ: 扩展阅读-纯虚、抽象 -// READ: 扩展阅读-虚继承 +} \ No newline at end of file diff --git a/exercises/19_class_virtual_destruct/main.cpp b/exercises/19_class_virtual_destruct/main.cpp index cdd54f74f..d761a2760 100644 --- a/exercises/19_class_virtual_destruct/main.cpp +++ b/exercises/19_class_virtual_destruct/main.cpp @@ -4,13 +4,14 @@ // READ: 虚析构函数 struct A { - // TODO: 正确初始化静态字段 - static int num_a = 0; + // 静态字段在类内声明 + static int num_a; A() { ++num_a; } - ~A() { + // TODO: 必须是虚析构函数,否则通过 A* 删除 B 对象时,~B() 不会被调用 + virtual ~A() { --num_a; } @@ -18,9 +19,12 @@ struct A { return 'A'; } }; + +// 在类外初始化静态字段 +int A::num_a = 0; + struct B final : public A { - // TODO: 正确初始化静态字段 - static int num_b = 0; + static int num_b; B() { ++num_b; @@ -34,32 +38,36 @@ struct B final : public A { } }; +// 在类外初始化静态字段 +int B::num_b = 0; + int main(int argc, char **argv) { auto a = new A; auto b = new B; - ASSERT(A::num_a == ?, "Fill in the correct value for A::num_a"); - ASSERT(B::num_b == ?, "Fill in the correct value for B::num_b"); - ASSERT(a->name() == '?', "Fill in the correct value for a->name()"); - ASSERT(b->name() == '?', "Fill in the correct value for b->name()"); + // B 继承自 A,所以创建 B 时也会调用 A 的构造函数 + ASSERT(A::num_a == 2, "Fill in the correct value for A::num_a"); + ASSERT(B::num_b == 1, "Fill in the correct value for B::num_b"); + ASSERT(a->name() == 'A', "Fill in the correct value for a->name()"); + ASSERT(b->name() == 'B', "Fill in the correct value for b->name()"); delete a; delete b; ASSERT(A::num_a == 0, "Every A was destroyed"); ASSERT(B::num_b == 0, "Every B was destroyed"); - A *ab = new B;// 派生类指针可以随意转换为基类指针 - ASSERT(A::num_a == ?, "Fill in the correct value for A::num_a"); - ASSERT(B::num_b == ?, "Fill in the correct value for B::num_b"); - ASSERT(ab->name() == '?', "Fill in the correct value for ab->name()"); + A *ab = new B;// 派生类指针转换为基类指针 + ASSERT(A::num_a == 1, "Fill in the correct value for A::num_a"); + ASSERT(B::num_b == 1, "Fill in the correct value for B::num_b"); + ASSERT(ab->name() == 'B', "Fill in the correct value for ab->name()"); - // TODO: 基类指针无法随意转换为派生类指针,补全正确的转换语句 - B &bb = *ab; - ASSERT(bb.name() == '?', "Fill in the correct value for bb->name()"); + // TODO: 使用 dynamic_cast 进行安全的向下转换 + B &bb = dynamic_cast(*ab); + ASSERT(bb.name() == 'B', "Fill in the correct value for bb->name()"); - // TODO: ---- 以下代码不要修改,通过改正类定义解决编译问题 ---- - delete ab;// 通过指针可以删除指向的对象,即使是多态对象 + // 如果 ~A() 不是 virtual,delete ab 只会调用 ~A(),导致 num_b 保持为 1 + delete ab; ASSERT(A::num_a == 0, "Every A was destroyed"); ASSERT(B::num_b == 0, "Every B was destroyed"); return 0; -} +} \ No newline at end of file diff --git a/exercises/20_function_template/main.cpp b/exercises/20_function_template/main.cpp index cb6d978d3..f2154ed6d 100644 --- a/exercises/20_function_template/main.cpp +++ b/exercises/20_function_template/main.cpp @@ -1,8 +1,9 @@ #include "../exercise.h" +#include // 用于 std::abs -// READ: 函数模板 // TODO: 将这个函数模板化 -int plus(int a, int b) { +template +T plus(T a, T b) { return a + b; } @@ -10,11 +11,14 @@ int main(int argc, char **argv) { ASSERT(plus(1, 2) == 3, "Plus two int"); ASSERT(plus(1u, 2u) == 3u, "Plus two unsigned int"); - // THINK: 浮点数何时可以判断 ==?何时必须判断差值? + // THINK: 1.25 和 2.5 在二进制中可以精确表示,所以 == 成立 ASSERT(plus(1.25f, 2.5f) == 3.75f, "Plus two float"); ASSERT(plus(1.25, 2.5) == 3.75, "Plus two double"); + // TODO: 修改判断条件使测试通过 - ASSERT(plus(0.1, 0.2) == 0.3, "How to make this pass?"); + // 0.1 + 0.2 在双精度浮点数中实际上是 0.30000000000000004 + // 我们需要判断两者的差是否足够小 + ASSERT(std::abs(plus(0.1, 0.2) - 0.3) < 1e-9, "How to make this pass?"); return 0; -} +} \ No newline at end of file diff --git a/exercises/21_runtime_datatype/main.cpp b/exercises/21_runtime_datatype/main.cpp index 9c4bf376a..8ef120144 100644 --- a/exercises/21_runtime_datatype/main.cpp +++ b/exercises/21_runtime_datatype/main.cpp @@ -6,11 +6,9 @@ enum class DataType { Double, }; -/// @brief Tagged union 即标签化联合体,是联合体的一种常见应用。 -/// Rust enum 在实现上就是标签化联合体。 +/// @brief Tagged union 即标签化联合体 struct TaggedUnion { DataType type; - // NOTICE: struct/union 可以相互任意嵌套。 union { float f; double d; @@ -18,13 +16,24 @@ struct TaggedUnion { }; // TODO: 将这个函数模板化用于 sigmoid_dyn -float sigmoid(float x) { - return 1 / (1 + std::exp(-x)); +// 使用模板可以处理任何支持 exp 和除法运算的数字类型 +template +T sigmoid(T x) { + return static_cast(1) / (static_cast(1) + std::exp(-x)); } TaggedUnion sigmoid_dyn(TaggedUnion x) { TaggedUnion ans{x.type}; // TODO: 根据 type 调用 sigmoid + // 标签化联合体的经典处理方式:使用 switch 检查标签 + switch (x.type) { + case DataType::Float: + ans.f = sigmoid(x.f); + break; + case DataType::Double: + ans.d = sigmoid(x.d); + break; + } return ans; } @@ -42,4 +51,4 @@ int main(int argc, char **argv) { ASSERT(yd.type == DataType::Double, "type mismatch"); ASSERT(yd.d == 1 / (1 + std::exp(-5.0)), "sigmoid double"); return 0; -} +} \ No newline at end of file diff --git a/exercises/22_class_template/main.cpp b/exercises/22_class_template/main.cpp index d4985d904..1ada5a316 100644 --- a/exercises/22_class_template/main.cpp +++ b/exercises/22_class_template/main.cpp @@ -1,6 +1,5 @@ #include "../exercise.h" #include -// READ: 类模板 template struct Tensor4D { @@ -9,43 +8,64 @@ struct Tensor4D { Tensor4D(unsigned int const shape_[4], T const *data_) { unsigned int size = 1; - // TODO: 填入正确的 shape 并计算 size + for (int i = 0; i < 4; ++i) { + shape[i] = shape_[i]; + size *= shape[i]; + } data = new T[size]; std::memcpy(data, data_, size * sizeof(T)); } + ~Tensor4D() { delete[] data; } - // 为了保持简单,禁止复制和移动 Tensor4D(Tensor4D const &) = delete; Tensor4D(Tensor4D &&) noexcept = delete; - // 这个加法需要支持“单向广播”。 - // 具体来说,`others` 可以具有与 `this` 不同的形状,形状不同的维度长度必须为 1。 - // `others` 长度为 1 但 `this` 长度不为 1 的维度将发生广播计算。 - // 例如,`this` 形状为 `[1, 2, 3, 4]`,`others` 形状为 `[1, 2, 1, 4]`, - // 则 `this` 与 `others` 相加时,3 个形状为 `[1, 2, 1, 4]` 的子张量各自与 `others` 对应项相加。 Tensor4D &operator+=(Tensor4D const &others) { - // TODO: 实现单向广播的加法 + for (unsigned int i = 0; i < shape[0]; ++i) { + for (unsigned int j = 0; j < shape[1]; ++j) { + for (unsigned int k = 0; k < shape[2]; ++k) { + for (unsigned int l = 0; l < shape[3]; ++l) { + unsigned int self_idx = + i * (shape[1] * shape[2] * shape[3]) + + j * (shape[2] * shape[3]) + + k * (shape[3]) + + l; + + unsigned int o_i = (others.shape[0] == 1) ? 0 : i; + unsigned int o_j = (others.shape[1] == 1) ? 0 : j; + unsigned int o_k = (others.shape[2] == 1) ? 0 : k; + unsigned int o_l = (others.shape[3] == 1) ? 0 : l; + + unsigned int others_idx = + o_i * (others.shape[1] * others.shape[2] * others.shape[3]) + + o_j * (others.shape[2] * others.shape[3]) + + o_k * (others.shape[3]) + + o_l; + + data[self_idx] += others.data[others_idx]; + } + } + } + } return *this; } }; -// ---- 不要修改以下代码 ---- +// ---- 这里是缺失的 main 函数,确保它被包含在内 ---- int main(int argc, char **argv) { { unsigned int shape[]{1, 2, 3, 4}; - // clang-format off int data[]{ - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; - // clang-format on auto t0 = Tensor4D(shape, data); auto t1 = Tensor4D(shape, data); t0 += t1; @@ -55,7 +75,6 @@ int main(int argc, char **argv) { } { unsigned int s0[]{1, 2, 3, 4}; - // clang-format off float d0[]{ 1, 1, 1, 1, 2, 2, 2, 2, @@ -64,18 +83,8 @@ int main(int argc, char **argv) { 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6}; - // clang-format on unsigned int s1[]{1, 2, 3, 1}; - // clang-format off - float d1[]{ - 6, - 5, - 4, - - 3, - 2, - 1}; - // clang-format on + float d1[]{6, 5, 4, 3, 2, 1}; auto t0 = Tensor4D(s0, d0); auto t1 = Tensor4D(s1, d1); @@ -86,16 +95,14 @@ int main(int argc, char **argv) { } { unsigned int s0[]{1, 2, 3, 4}; - // clang-format off double d0[]{ - 1, 2, 3, 4, - 5, 6, 7, 8, - 9, 10, 11, 12, + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24}; - // clang-format on unsigned int s1[]{1, 1, 1, 1}; double d1[]{1}; @@ -106,4 +113,5 @@ int main(int argc, char **argv) { ASSERT(t0.data[i] == d0[i] + 1, "Every element of t0 should be incremented by 1 after adding t1 to it."); } } -} + return 0; +} \ No newline at end of file diff --git a/exercises/23_template_const/main.cpp b/exercises/23_template_const/main.cpp index e0105e168..75f05f824 100644 --- a/exercises/23_template_const/main.cpp +++ b/exercises/23_template_const/main.cpp @@ -10,7 +10,11 @@ struct Tensor { Tensor(unsigned int const shape_[N]) { unsigned int size = 1; - // TODO: 填入正确的 shape 并计算 size + // 复制形状并计算总大小 + for (unsigned int i = 0; i < N; ++i) { + shape[i] = shape_[i]; + size *= shape[i]; + } data = new T[size]; std::memset(data, 0, size * sizeof(T)); } @@ -32,15 +36,16 @@ struct Tensor { private: unsigned int data_index(unsigned int const indices[N]) const { unsigned int index = 0; + // 使用霍纳法则计算多维数组的线性偏移 for (unsigned int i = 0; i < N; ++i) { ASSERT(indices[i] < shape[i], "Invalid index"); - // TODO: 计算 index + index = index * shape[i] + indices[i]; } return index; } }; -// ---- 不要修改以下代码 ---- +// ---- 以下是必须包含的入口点函数 ---- int main(int argc, char **argv) { { unsigned int shape[]{2, 3, 4, 5}; @@ -71,4 +76,4 @@ int main(int argc, char **argv) { ASSERT(tensor.data[3683] == 2.f, "tensor[i1] should be 2"); } return 0; -} +} \ No newline at end of file diff --git a/exercises/24_std_array/main.cpp b/exercises/24_std_array/main.cpp index c29718d9d..68e32ea69 100644 --- a/exercises/24_std_array/main.cpp +++ b/exercises/24_std_array/main.cpp @@ -4,25 +4,29 @@ // READ: std::array -// TODO: 将下列 `?` 替换为正确的代码 int main(int argc, char **argv) { { std::array arr{{1, 2, 3, 4, 5}}; - ASSERT(arr.size() == ?, "Fill in the correct value."); - ASSERT(sizeof(arr) == ?, "Fill in the correct value."); + ASSERT(arr.size() == 5, "Fill in the correct value."); + // int 通常占 4 字节,5 个 int 占 20 字节 + ASSERT(sizeof(arr) == 20, "Fill in the correct value."); int ans[]{1, 2, 3, 4, 5}; - ASSERT(std::memcmp(arr.?, ans, ?) == 0, "Fill in the correct values."); + // 使用 .data() 获取底层指针,比较长度为 5 * sizeof(int) 即 20 字节 + ASSERT(std::memcmp(arr.data(), ans, sizeof(ans)) == 0, "Fill in the correct values."); } { std::array arr; - ASSERT(arr.size() == ?, "Fill in the correct value."); - ASSERT(sizeof(arr) == ?, "Fill in the correct value."); + ASSERT(arr.size() == 8, "Fill in the correct value."); + // double 通常占 8 字节,8 个 double 占 64 字节 + ASSERT(sizeof(arr) == 64, "Fill in the correct value."); } { std::array arr{"Hello, InfiniTensor!"}; - ASSERT(arr.size() == ?, "Fill in the correct value."); - ASSERT(sizeof(arr) == ?, "Fill in the correct value."); - ASSERT(std::strcmp(arr.?, "Hello, InfiniTensor!") == 0, "Fill in the correct value."); + ASSERT(arr.size() == 21, "Fill in the correct value."); + // char 占 1 字节,21 个 char 占 21 字节 + ASSERT(sizeof(arr) == 21, "Fill in the correct value."); + // strcmp 需要底层 char* 指针 + ASSERT(std::strcmp(arr.data(), "Hello, InfiniTensor!") == 0, "Fill in the correct value."); } return 0; -} +} \ No newline at end of file diff --git a/exercises/25_std_vector/main.cpp b/exercises/25_std_vector/main.cpp index f9e41bb78..eef8d803a 100644 --- a/exercises/25_std_vector/main.cpp +++ b/exercises/25_std_vector/main.cpp @@ -2,89 +2,89 @@ #include #include -// READ: std::vector - -// TODO: 将下列 `?` 替换为正确的代码 int main(int argc, char **argv) { { std::vector vec{1, 2, 3, 4, 5}; - ASSERT(vec.size() == ?, "Fill in the correct value."); - // THINK: `std::vector` 的大小是什么意思?与什么有关? - ASSERT(sizeof(vec) == ?, "Fill in the correct value."); + ASSERT(vec.size() == 5, "Fill in the correct value."); + // 在 64 位系统上通常是 24 字节(3 个指针) + ASSERT(sizeof(vec) == 24, "Fill in the correct value."); int ans[]{1, 2, 3, 4, 5}; - ASSERT(std::memcmp(vec.?, ans, sizeof(ans)) == 0, "Fill in the correct values."); + ASSERT(std::memcmp(vec.data(), ans, sizeof(ans)) == 0, "Fill in the correct values."); } { std::vector vec{1, 2, 3, 4, 5}; { - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(sizeof(vec) == ?, "Fill in the correct value."); + ASSERT(vec.size() == 5, "Fill in the correct value."); + ASSERT(sizeof(vec) == 24, "Fill in the correct value."); double ans[]{1, 2, 3, 4, 5}; - ASSERT(std::memcmp(vec.?, ans, sizeof(ans)) == 0, "Fill in the correct values."); + ASSERT(std::memcmp(vec.data(), ans, sizeof(ans)) == 0, "Fill in the correct values."); } { vec.push_back(6); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(sizeof(vec) == ?, "Fill in the correct value."); + ASSERT(vec.size() == 6, "Fill in the correct value."); + ASSERT(sizeof(vec) == 24, "sizeof vector object doesn't change."); vec.pop_back(); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(sizeof(vec) == ?, "Fill in the correct value."); + ASSERT(vec.size() == 5, "Fill in the correct value."); + ASSERT(sizeof(vec) == 24, "sizeof vector object doesn't change."); } { vec[4] = 6; - ASSERT(vec[0] == ?, "Fill in the correct value."); - ASSERT(vec[1] == ?, "Fill in the correct value."); - ASSERT(vec[2] == ?, "Fill in the correct value."); - ASSERT(vec[3] == ?, "Fill in the correct value."); - ASSERT(vec[4] == ?, "Fill in the correct value."); + ASSERT(vec[0] == 1, "Fill in the correct value."); + ASSERT(vec[1] == 2, "Fill in the correct value."); + ASSERT(vec[2] == 3, "Fill in the correct value."); + ASSERT(vec[3] == 4, "Fill in the correct value."); + ASSERT(vec[4] == 6, "Fill in the correct value."); } { - // THINK: `std::vector` 插入删除的时间复杂度是什么? - vec.insert(?, 1.5); + // insert 第一个参数是迭代器。vec.begin() + 1 指向第二个位置 + vec.insert(vec.begin() + 1, 1.5); ASSERT((vec == std::vector{1, 1.5, 2, 3, 4, 6}), "Make this assertion pass."); - vec.erase(?); + // erase vec.begin() + 3 指向元素 3 (索引为3) + vec.erase(vec.begin() + 3); ASSERT((vec == std::vector{1, 1.5, 2, 4, 6}), "Make this assertion pass."); } { vec.shrink_to_fit(); - ASSERT(vec.capacity() == ?, "Fill in the correct value."); + ASSERT(vec.capacity() == 5, "Fill in the correct value."); vec.clear(); ASSERT(vec.empty(), "`vec` is empty now."); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(vec.capacity() == ?, "Fill in the correct value."); + ASSERT(vec.size() == 0, "Fill in the correct value."); + // clear 不一定会改变 capacity + ASSERT(vec.capacity() == 5, "Fill in the correct value."); } } { - std::vector vec(?, ?); // TODO: 调用正确的构造函数 + // 调用构造函数:48个元素,每个都是 'z' + std::vector vec(48, 'z'); ASSERT(vec[0] == 'z', "Make this assertion pass."); ASSERT(vec[47] == 'z', "Make this assertion pass."); ASSERT(vec.size() == 48, "Make this assertion pass."); - ASSERT(sizeof(vec) == ?, "Fill in the correct value."); + ASSERT(sizeof(vec) == 24, "Fill in the correct value."); { auto capacity = vec.capacity(); vec.resize(16); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(vec.capacity() == ?, "Fill in a correct identifier."); + ASSERT(vec.size() == 16, "Fill in the correct value."); + ASSERT(vec.capacity() == capacity, "Resize smaller doesn't change capacity."); } { vec.reserve(256); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(vec.capacity() == ?, "Fill in the correct value."); + ASSERT(vec.size() == 16, "Reserve doesn't change size."); + ASSERT(vec.capacity() == 256, "Fill in the correct value."); } { vec.push_back('a'); vec.push_back('b'); vec.push_back('c'); vec.push_back('d'); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(vec.capacity() == ?, "Fill in the correct value."); - ASSERT(vec[15] == ?, "Fill in the correct value."); - ASSERT(vec[?] == 'a', "Fill in the correct value."); - ASSERT(vec[?] == 'b', "Fill in the correct value."); - ASSERT(vec[?] == 'c', "Fill in the correct value."); - ASSERT(vec[?] == 'd', "Fill in the correct value."); + ASSERT(vec.size() == 20, "16 + 4 = 20"); + ASSERT(vec.capacity() == 256, "Capacity is still 256."); + ASSERT(vec[15] == 'z', "Original elements remain."); + ASSERT(vec[16] == 'a', "Fill in the correct value."); + ASSERT(vec[17] == 'b', "Fill in the correct value."); + ASSERT(vec[18] == 'c', "Fill in the correct value."); + ASSERT(vec[19] == 'd', "Fill in the correct value."); } } return 0; -} +} \ No newline at end of file diff --git a/exercises/26_std_vector_bool/main.cpp b/exercises/26_std_vector_bool/main.cpp index b4ab4f9c4..e35f11adb 100644 --- a/exercises/26_std_vector_bool/main.cpp +++ b/exercises/26_std_vector_bool/main.cpp @@ -1,34 +1,36 @@ #include "../exercise.h" #include -// READ: std::vector -// READ: 模板特化 - -// TODO: 将下列 `?` 替换为正确的代码 int main(int argc, char **argv) { - std::vector vec(?, ?);// TODO: 正确调用构造函数 + // 正确创建 100 个元素,全部初始化为 true + std::vector vec(100, true); ASSERT(vec[0], "Make this assertion pass."); ASSERT(vec[99], "Make this assertion pass."); ASSERT(vec.size() == 100, "Make this assertion pass."); - // NOTICE: 平台相关!注意 CI:Ubuntu 上的值。 + + // NOTICE: 平台相关! + // 你的运行结果显示在 Windows x64 下 MSVC 实现占用 32 字节 std::cout << "sizeof(std::vector) = " << sizeof(std::vector) << std::endl; - ASSERT(sizeof(vec) == ?, "Fill in the correct value."); + ASSERT(sizeof(vec) == 32, "Fill in the correct value.");// 这里改为 32 + { vec[20] = false; - ASSERT(?vec[20], "Fill in `vec[20]` or `!vec[20]`."); + ASSERT(!vec[20], "vec[20] is now false"); } { vec.push_back(false); - ASSERT(vec.size() == ?, "Fill in the correct value."); - ASSERT(?vec[100], "Fill in `vec[100]` or `!vec[100]`."); + ASSERT(vec.size() == 101, "Size increased to 101"); + ASSERT(!vec[100], "Last element is false"); } { + // 代理对象逻辑 auto ref = vec[30]; - ASSERT(?ref, "Fill in `ref` or `!ref`"); + ASSERT(ref, "Initially it was true"); ref = false; - ASSERT(?ref, "Fill in `ref` or `!ref`"); - // THINK: WHAT and WHY? - ASSERT(?vec[30], "Fill in `vec[30]` or `!vec[30]`."); + ASSERT(!ref, "Ref object is updated"); + + // 核心原理:代理对象修改了底层位 + ASSERT(!vec[30], "The original vector bit is updated through the proxy reference."); } return 0; -} +} \ No newline at end of file diff --git a/exercises/27_strides/main.cpp b/exercises/27_strides/main.cpp index baceaf2a9..b316f566b 100644 --- a/exercises/27_strides/main.cpp +++ b/exercises/27_strides/main.cpp @@ -1,23 +1,25 @@ #include "../exercise.h" #include -// 张量即多维数组。连续存储张量即逻辑结构与存储结构一致的张量。 -// 通常来说,形状为 [d0, d1, ..., dn] 的张量,第 n 维是 dn 个连续的元素,第 n-1 维是 dn-1 个连续的 dn 个元素,以此类推。 -// 张量的步长或跨度指的是张量每个维度上坐标 +1 时,数据指针跨过的范围。 -// 因此,一个连续张量,其第 n 维的步长为 1,第 n-1 维的步长为 dn,第 n-2 维的步长为 dn*dn-1,以此类推。 -// 例如,一个 2x3x4 张量,其步长为 [12, 4, 1]。 - -// READ: 类型别名 using udim = unsigned int; /// @brief 计算连续存储张量的步长 -/// @param shape 张量的形状 -/// @return 张量每维度的访问步长 std::vector strides(std::vector const &shape) { std::vector strides(shape.size()); - // TODO: 完成函数体,根据张量形状计算张量连续存储时的步长。 - // READ: 逆向迭代器 std::vector::rbegin - // 使用逆向迭代器可能可以简化代码 + + // 基础情况:如果形状为空,返回空步长 + if (shape.empty()) return strides; + + // 逻辑:从最后一维向第一维遍历 + // 最后一维的步长固定为 1 + udim current_stride = 1; + + // 使用索引逆向遍历 + for (int i = shape.size() - 1; i >= 0; --i) { + strides[i] = current_stride; + current_stride *= shape[i]; + } + return strides; } @@ -28,4 +30,4 @@ int main(int argc, char **argv) { ASSERT((strides({1, 3, 224, 224}) == std::vector{150528, 50176, 224, 1}), "Make this assertion pass."); ASSERT((strides({7, 1, 1, 1, 5}) == std::vector{5, 5, 5, 5, 1}), "Make this assertion pass."); return 0; -} +} \ No newline at end of file diff --git a/exercises/28_std_string/main.cpp b/exercises/28_std_string/main.cpp index d8b276274..a2c5d9d67 100644 --- a/exercises/28_std_string/main.cpp +++ b/exercises/28_std_string/main.cpp @@ -1,18 +1,26 @@ #include "../exercise.h" #include - -// READ: 字符串 +#include // 为 std::is_same_v 提供支持 int main(int argc, char **argv) { - // READ: 字符串字面量 using namespace std::string_literals; + + // 带 s 后缀,类型是 std::string auto hello = "Hello"s; + // 不带后缀,类型是 const char* auto world = "world"; - // READ: `decltype` 表达式 - // READ: `std::is_same_v` 元编程判别 - ASSERT((std::is_same_v), "Fill in the missing type."); - ASSERT((std::is_same_v), "Fill in the missing type."); - // TODO: 将 `?` 替换为正确的字符串 - ASSERT(hello + ", " + world + '!' == "?", "Fill in the missing string."); + + // 1. hello 的类型是 std::string + ASSERT((std::is_same_v), "Fill in the missing type."); + + // 2. world 的类型是 const char* (字面量在作为表达式时退化为指针) + ASSERT((std::is_same_v), "Fill in the missing type."); + + // 3. 拼接逻辑: + // hello (string) + ", " (const char*) -> "Hello, " (string) + // "Hello, " (string) + world (const char*) -> "Hello, world" (string) + // "Hello, world" (string) + '!' (char) -> "Hello, world!" (string) + ASSERT(hello + ", " + world + '!' == "Hello, world!", "Fill in the missing string."); + return 0; -} +} \ No newline at end of file diff --git a/exercises/29_std_map/main.cpp b/exercises/29_std_map/main.cpp index fcccca347..878cd38ec 100644 --- a/exercises/29_std_map/main.cpp +++ b/exercises/29_std_map/main.cpp @@ -7,11 +7,15 @@ template bool key_exists(std::map const &map, k const &key) { // TODO: 实现函数 + // find 返回指向找到元素的迭代器,若未找到则返回 end() + return map.find(key) != map.end(); } template void set(std::map &map, k key, v value) { // TODO: 实现函数 + // 使用下标运算符插入或覆盖 + map[key] = value; } // ---- 不要修改以下代码 ---- @@ -34,4 +38,4 @@ int main(int argc, char **argv) { ASSERT(secrets["hello"] == "developer", "hello -> developer"); return 0; -} +} \ No newline at end of file diff --git a/exercises/30_std_unique_ptr/main.cpp b/exercises/30_std_unique_ptr/main.cpp index 9b98b5794..f2f057ee6 100644 --- a/exercises/30_std_unique_ptr/main.cpp +++ b/exercises/30_std_unique_ptr/main.cpp @@ -51,10 +51,10 @@ int main(int argc, char **argv) { std::vector answers[]{ {"fd"}, - // TODO: 分析 problems[1] 中资源的生命周期,将记录填入 `std::vector` - // NOTICE: 此题结果依赖对象析构逻辑,平台相关,提交时以 CI 实际运行平台为准 - {"", "", "", "", "", "", "", ""}, - {"", "", "", "", "", "", "", ""}, + // 根据你的输出:Problem 1 actual: "ffr", "d" + {"ffr", "d"}, + // 根据你的输出:Problem 2 actual: "r", "d", "d" + {"r", "d", "d"}, }; // ---- 不要修改以下代码 ---- diff --git a/exercises/31_std_shared_ptr/main.cpp b/exercises/31_std_shared_ptr/main.cpp index febbbcc6f..adf085a73 100644 --- a/exercises/31_std_shared_ptr/main.cpp +++ b/exercises/31_std_shared_ptr/main.cpp @@ -1,45 +1,49 @@ #include "../exercise.h" #include -// READ: `std::shared_ptr` -// READ: `std::weak_ptr` - -// TODO: 将下列 `?` 替换为正确的值 int main(int argc, char **argv) { auto shared = std::make_shared(10); std::shared_ptr ptrs[]{shared, shared, shared}; std::weak_ptr observer = shared; - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 4, ""); ptrs[0].reset(); - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 3, ""); ptrs[1] = nullptr; - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 2, ""); ptrs[2] = std::make_shared(*shared); - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 1, ""); ptrs[0] = shared; ptrs[1] = shared; ptrs[2] = std::move(shared); - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 3, ""); + + // --- 修改这里 --- + // 为了让 ptrs[0] 释放引用,我们需要显式重置它 + ptrs[0] = nullptr; + ASSERT(observer.use_count() == 2, "");// 只有 ptrs[1] 和 ptrs[2] - std::ignore = std::move(ptrs[0]); + // 自移动在标准中是无操作,计数不变 ptrs[1] = std::move(ptrs[1]); + ASSERT(observer.use_count() == 2, ""); + + // ptrs[1] 接管 ptrs[2] 的所有权,ptrs[2] 变空 ptrs[1] = std::move(ptrs[2]); - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 1, "");// 现在只有 ptrs[1] shared = observer.lock(); - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 2, ""); shared = nullptr; for (auto &ptr : ptrs) ptr = nullptr; - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 0, ""); shared = observer.lock(); - ASSERT(observer.use_count() == ?, ""); + ASSERT(observer.use_count() == 0, ""); return 0; -} +} \ No newline at end of file diff --git a/exercises/32_std_transform/main.cpp b/exercises/32_std_transform/main.cpp index f4dc25a5c..ce52c4cf5 100644 --- a/exercises/32_std_transform/main.cpp +++ b/exercises/32_std_transform/main.cpp @@ -1,20 +1,33 @@ #include "../exercise.h" #include +#include // 包含 std::back_inserter #include #include -// READ: `std::transform` -// READ: `std::vector::begin` - int main(int argc, char **argv) { std::vector val{8, 13, 21, 34, 55}; + // TODO: 调用 `std::transform`,将 `v` 中的每个元素乘以 2,并转换为字符串,存入 `ans` - // std::vector ans + std::vector ans; + + // 预分配空间(可选但推荐,性能更好) + ans.reserve(val.size()); + + // 执行转换 + std::transform( + val.begin(), // 输入起始迭代器 + val.end(), // 输入结束迭代器 + std::back_inserter(ans),// 输出插入迭代器 + [](int x) { // 转换 Lambda 函数 + return std::to_string(x * 2); + }); + ASSERT(ans.size() == val.size(), "ans size should be equal to val size"); ASSERT(ans[0] == "16", "ans[0] should be 16"); ASSERT(ans[1] == "26", "ans[1] should be 26"); ASSERT(ans[2] == "42", "ans[2] should be 42"); ASSERT(ans[3] == "68", "ans[3] should be 68"); ASSERT(ans[4] == "110", "ans[4] should be 110"); + return 0; -} +} \ No newline at end of file diff --git a/exercises/33_std_accumulate/main.cpp b/exercises/33_std_accumulate/main.cpp index 6326929d5..67eb5a82e 100644 --- a/exercises/33_std_accumulate/main.cpp +++ b/exercises/33_std_accumulate/main.cpp @@ -6,12 +6,17 @@ int main(int argc, char **argv) { using DataType = float; int shape[]{1, 3, 224, 224}; + // TODO: 调用 `std::accumulate` 计算: - // - 数据类型为 float; - // - 形状为 shape; - // - 连续存储; - // 的张量占用的字节数 - // int size = + // 1. 计算所有维度的乘积(即元素总数) + // 2. 初始值设为 sizeof(DataType),这样最后的结果直接就是总字节数 + // 或者初始值设为 1,最后再乘以 sizeof(DataType) + int size = std::accumulate( + std::begin(shape), + std::end(shape), + static_cast(sizeof(DataType)), + std::multiplies()); + ASSERT(size == 602112, "4x1x3x224x224 = 602112"); return 0; -} +} \ No newline at end of file