通过小例子快速入门c++
代码github地址:https://github.com/reganzm/learning-cxx
通过实现TODO部分,快速掌握C++基础知识。
1、hello world
c
int main(int argc, char **argv) {
// TODO: 在控制台输出 "Hello, InfiniTensor!" 并换行
std::cout << "Hello, InfiniTensor!" << std::endl;
return 0;
}
参考资料: std stream 流修饰符 format in c++20
2、Varible & add
c
int main(int argc, char **argv) {
// TODO: 补全变量定义并打印加法运算
// x ?
int x = 10;
std::cout << x << " + " << x << " = " << x + x << std::endl;
return 0;
}
参考资料:运算符
3、function
c
int add(int a, int b);
int main(int argc, char **argv) {
ASSERT(add(123, 456) == 123 + 456, "add(123, 456) should be 123 + 456");
auto x = 1, y = 2;
std::cout << x << " + " << y << " = " << add(x, y) << std::endl;
return 0;
}
int add(int a, int b) {
// TODO: 补全函数定义,但不要移动代码行
return a + b;
}
参考资料: declaration
4、argument & parameter
c
void func(int);
// TODO: 为下列 ASSERT 填写正确的值
int main(int argc, char **argv) {
auto arg = 99;
ASSERT(arg == 99, "arg should be ?");
std::cout << "befor func call: " << arg << std::endl;
func(arg);
ASSERT(arg == 99, "arg should be ?");
std::cout << "after func call: " << arg << std::endl;
return 0;
}
// TODO: 为下列 ASSERT 填写正确的值
void func(int param) {
ASSERT(param == 99, "param should be ?");
std::cout << "befor add: " << param << std::endl;
param += 1;
ASSERT(param == 100, "param should be ?");
std::cout << "after add: " << param << std::endl;
}
参考资料:参数的传递方式
5、static
c
static int func(int param) {
static int static_ = param;
// std::cout << "static_ = " << static_ << std::endl;
return static_++;
}
int main(int argc, char **argv) {
// TODO: 将下列 `?` 替换为正确的数字
ASSERT(func(5) == 5, "static variable value incorrect");
ASSERT(func(4) == 6, "static variable value incorrect");
ASSERT(func(3) == 7, "static variable value incorrect");
ASSERT(func(2) == 8, "static variable value incorrect");
ASSERT(func(1) == 9, "static variable value incorrect");
return 0;
}
参考资料:static关键字
6、常量表达式
c
constexpr unsigned long long fibonacci(int i) {
switch (i) {
case 0:
return 0;
case 1:
return 1;
default:
return fibonacci(i - 1) + fibonacci(i - 2);
}
}
int main(int argc, char **argv) {
constexpr auto FIB20 = fibonacci(20);
ASSERT(FIB20 == 6765, "fibonacci(20) should be 6765");
std::cout << "fibonacci(20) = " << FIB20 << std::endl;
// TODO: 观察错误信息,修改一处,使代码编译运行
// PS: 编译运行,但是不一定能算出结果……
auto ANS_N = 20;
auto ANS = fibonacci(ANS_N);
std::cout << "fibonacci(" << ANS_N << ") = " << ANS << std::endl;
return 0;
}
7、数组
c
unsigned long long arr[90]{0, 1};
unsigned long long fibonacci(int i) {
switch (i) {
case 0:
return 0;
case 1:
return 1;
default:
// TODO: 补全三目表达式缺失的部分
return arr[i] ? arr[i] : (arr[i] = fibonacci(i - 1) + fibonacci(i - 2));
}
}
int main(int argc, char **argv) {
// TODO: 为此 ASSERT 填写正确的值
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;
}
参考资料:数组
8、循环
c
static unsigned long long fibonacci(int i) {
// TODO: 为缓存设置正确的初始值
static unsigned long long cache[96]={0,1}, cached;
// TODO: 设置正确的循环条件
for (cached = 2; cached <= i; ++cached) {
cache[cached] = cache[cached - 1] + cache[cached - 2];
}
return cache[i];
}
// ---- 不要修改以下代码 ----
int main(int argc, char **argv) {
ASSERT(fibonacci(0) == 0, "fibonacci(0) should be 0");
ASSERT(fibonacci(1) == 1, "fibonacci(1) should be 1");
printf("fibonacci(2) = %zu\n", fibonacci(2));
ASSERT(fibonacci(2) == 1, "fibonacci(2) should be 1");
ASSERT(fibonacci(3) == 2, "fibonacci(3) should be 2");
ASSERT(fibonacci(10) == 55, "fibonacci(10) should be 55");
auto fib90 = fibonacci(90);
std::cout << "fibonacci(90) = " << fib90 << std::endl;
ASSERT(fib90 == 2880067194370816120, "fibonacci(90) should be 2880067194370816120");
return 0;
}
参考资料:纯函数
9、指针
c
bool is_fibonacci(int *ptr, int len, int stride) {
ASSERT(len >= 3, "`len` should be at least 3");
printf("Checking Fibonacci sequence with stride %d and length %d\n", stride, len);
// TODO: 编写代码判断从 ptr 开始,每 stride 个元素取 1 个元素,组成长度为 n 的数列
for (int i = 0; i < len - 2; i++) {
int idx1 = i * stride;
int idx2 = (i + 1) * stride;
int idx3 = (i + 2) * stride;
printf("Checking %d: %d + %d = %d\n", i, ptr[idx1], ptr[idx2], ptr[idx3]);
// 是否满足斐波那契数列的定义
if (ptr[idx1] + ptr[idx2] != ptr[idx3]) {
return false;
}
}
// 是否满足
// arr[i + 2] = arr[i] + arr[i + 1]
return true;
}
// ---- 不要修改以下代码 ----
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};
// 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" );
ASSERT(!is_fibonacci(arr1 , sizeof(arr1) / sizeof(*arr1) , 1), "arr1 is not Fibonacci");
ASSERT( is_fibonacci(arr1 + 1, 3 , 1), "part of arr1 is Fibonacci" );
ASSERT(!is_fibonacci(arr2 , sizeof(arr2) / sizeof(*arr2) , 1), "arr2 is not Fibonacci");
ASSERT( is_fibonacci(arr2 + 2, 10 , 2), "part of arr2 is Fibonacci" );
ASSERT( is_fibonacci(arr2 + 3, 9 , 2), "part of arr2 is Fibonacci" );
ASSERT(!is_fibonacci(arr2 + 3, 10 , 2), "guard check" );
ASSERT(!is_fibonacci(arr2 + 1, 10 , 2), "guard check" );
// clang-format on
return 0;
}
参考资料:数组向指针退化
10、枚举和联合体
c
// `enum` 是 C 的兼容类型,本质上其对应类型的常量。
// 在 `enum` 中定义标识符等价于定义 constexpr 常量,
// 这些标识符不需要前缀,可以直接引用。
// 因此 `enum` 定义会污染命名空间。
enum ColorEnum : unsigned char {
COLOR_RED = 31,
COLOR_GREEN,
COLOR_YELLOW,
COLOR_BLUE,
};
// 有作用域枚举型是 C++ 引入的类型安全枚举。
// 其内部标识符需要带前缀引用,如 `Color::Red`。
// 作用域枚举型可以避免命名空间污染,并提供类型安全保证。
enum class Color : int {
Red = COLOR_RED,
Green,
Yellow,
Blue,
};
ColorEnum convert_by_pun(Color c) {
// READ: <https://zh.cppreference.com/w/cpp/language/union>
// `union` 表示在同一内存位置存储的不同类型的值。
// 其常见用法是实现类型双关转换,即将一种类型的值转换为另一种无关类型的值。
// 但这种写法实际上仅在 C 语言良定义,在 C++ 中是未定义行为。
// 这是比较少见的 C++ 不与 C 保持兼容的特性。
// READ: 类型双关 <https://tttapa.github.io/Pages/Programming/Cpp/Practices/type-punning.html>
union TypePun {
ColorEnum e;
Color c;
};
TypePun pun;
// TODO: 补全类型双关转换
pun.c = c;
return pun.e;
}
int main(int argc, char **argv) {
ASSERT(convert_by_pun(Color::Red) == COLOR_RED, "Type punning conversion");
ASSERT(convert_by_pun(Color::Green) == COLOR_GREEN, "Type punning conversion");
ASSERT(convert_by_pun(Color::Yellow) == COLOR_YELLOW, "Type punning conversion");
ASSERT(convert_by_pun(Color::Blue) == COLOR_BLUE, "Type punning conversion");
return 0;
}
参考资料:枚举类型
11、struct
c
struct FibonacciCache {
unsigned long long cache[16];
int cached;
};
// TODO: 实现正确的缓存优化斐波那契计算
static unsigned long long fibonacci(FibonacciCache &cache, int 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: 初始化的各种写法 <https://zh.cppreference.com/w/cpp/language/initialization>
FibonacciCache fib;
fib.cached = 2;
fib.cache[0] = 0;
fib.cache[1] = 1;
printf("fibonacci(10) = %llu\n", fibonacci(fib, 10));
ASSERT(fibonacci(fib, 10) == 55, "fibonacci(10) should be 55");
std::cout << "fibonacci(10) = " << fibonacci(fib, 10) << std::endl;
return 0;
}
参考资料:Trivial type
12、method
c
struct Fibonacci {
unsigned long long cache[128];
int cached;
// TODO: 实现正确的缓存优化斐波那契计算
unsigned long long get(int i) {
for (; cached <= i; ++cached) {
cache[cached] = cache[cached - 1] + cache[cached - 2];
}
return cache[i];
}
};
int main(int argc, char **argv) {
// TODO: 初始化缓存结构体,使计算正确
Fibonacci fib;
fib.cached = 2;
fib.cache[0] = 0;
fib.cache[1] = 1;
ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55");
std::cout << "fibonacci(10) = " << fib.get(10) << std::endl;
return 0;
}
13、method const
c
struct Fibonacci {
int numbers[11];
// TODO: 修改方法签名和实现,使测试通过
int get(int i) const {
return numbers[i];
}
};
int main(int argc, char **argv) {
Fibonacci constexpr FIB{{0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55}};
ASSERT(FIB.get(10) == 55, "fibonacci(10) should be 55");
std::cout << "fibonacci(10) = " << FIB.get(10) << std::endl;
return 0;
}
参考资料:有cv限定符的成员函数
14、class
c
// C++ 中,`class` 和 `struct` 之间的**唯一区别**是
// `class` 默认访问控制符是 `private`,
// `struct` 默认访问控制符是 `public`。
// READ: 访问说明符 <https://zh.cppreference.com/w/cpp/language/access>
// 这个 class 中的字段被 private 修饰,只能在 class 内部访问。
// 因此必须提供构造器来初始化字段。
// READ: 构造器 <https://zh.cppreference.com/w/cpp/language/constructor>
class Fibonacci {
size_t cache[16];
int cached;
public:
// TODO: 实现构造器
// Fibonacci()
Fibonacci() : cache{0, 1}, cached(2) {}
// TODO: 实现正确的缓存优化斐波那契计算
size_t get(int i) {
for (; cached <= i; cached++) {
cache[cached] = cache[cached - 1] + cache[cached - 2];
}
return cache[i];
}
};
int main(int argc, char **argv) {
// 现在类型拥有无参构造器,声明时会直接调用。
// 这个写法不再是未定义行为了。
Fibonacci fib;
ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55");
std::cout << "fibonacci(10) = " << fib.get(10) << std::endl;
return 0;
}
15、析构函数
c
/// @brief 任意缓存容量的斐波那契类型。
/// @details 可以在构造时传入缓存容量,因此需要动态分配缓存空间。
class DynFibonacci {
size_t *cache;
int cached;
public:
// TODO: 实现动态设置容量的构造器
DynFibonacci(int capacity): cache(new size_t[capacity]{0,1}), cached(2) {}
// TODO: 实现析构器,释放缓存空间
~DynFibonacci() {
delete[] cache;
}
// TODO: 实现正确的缓存优化斐波那契计算
size_t get(int i) {
for (; cached <= i; cached++) {
cache[cached] = cache[cached - 1] + cache[cached - 2];
}
return cache[i];
}
};
int main(int argc, char **argv) {
DynFibonacci fib(12);
ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55");
std::cout << "fibonacci(10) = " << fib.get(10) << std::endl;
return 0;
}
16、拷贝构造函数
c
class DynFibonacci {
size_t *cache;
int cached;
public:
// TODO: 实现动态设置容量的构造器
DynFibonacci(int capacity): cache(new size_t[capacity]{0,1}), cached(2) {}
// TODO: 实现复制构造器
DynFibonacci(DynFibonacci const &other):cache(new size_t[other.cached]),cached(other.cached) {
for (int i = 0; i < cached; ++i) {
cache[i] = other.cache[i];
}
}
// TODO: 实现析构器,释放缓存空间
~DynFibonacci() {
delete[] cache;
}
// TODO: 实现正确的缓存优化斐波那契计算
size_t get(int i) {
for (; cached <= i; cached++) {
cache[cached] = cache[cached - 1] + cache[cached - 2];
}
return cache[i];
}
// NOTICE: 不要修改这个方法
// NOTICE: 名字相同参数也相同,但 const 修饰不同的方法是一对重载方法,可以同时存在
// 本质上,方法是隐藏了 this 参数的函数
// const 修饰作用在 this 上,因此它们实际上参数不同
const size_t get(int i) const {
if (i <= cached) {
return cache[i];
}
ASSERT(false, "i out of range");
}
};
int main(int argc, char **argv) {
DynFibonacci fib(12);
ASSERT(fib.get(10) == 55, "fibonacci(10) should be 55");
DynFibonacci const fib_ = fib;
ASSERT(fib_.get(10) == fib.get(10), "Object cloned");
return 0;
}
17、移动构造函数
c
class DynFibonacci {
size_t *cache;
int cached;
public:
// TODO: 实现动态设置容量的构造器
DynFibonacci(int capacity): cache(new size_t[capacity]{0,1}), cached(2) {}
// TODO: 实现移动构造器
// NOTICE: ⚠ 注意移动到自身问题 ⚠
DynFibonacci(DynFibonacci &&other) noexcept
: cache(other.cache), cached(other.cached) {
other.cache = nullptr; // 移动后,原对象不再拥有缓存
other.cached = 0; // 重置原对象的状态
}
// DynFibonacci(DynFibonacci &&) noexcept = delete;
// TODO: 实现移动赋值
// NOTICE: ⚠ 注意移动到自身问题 ⚠
DynFibonacci &operator=(DynFibonacci &&other) noexcept {
if (this != &other) {
delete[] cache;
cache = other.cache;
cached = other.cached;
other.cache = nullptr;
other.cached = 0;
}
return *this;
}
// TODO: 实现析构器,释放缓存空间
~DynFibonacci() {
delete[] cache;
}
// TODO: 实现正确的缓存优化斐波那契计算
size_t operator[](int i) {
for (; cached <= i; cached++) {
cache[cached] = cache[cached - 1] + cache[cached - 2];
}
return cache[i];
}
// NOTICE: 不要修改这个方法
size_t operator[](int i) const {
ASSERT(i <= cached, "i out of range");
return cache[i];
}
// NOTICE: 不要修改这个方法
bool is_alive() const {
return cache;
}
};
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");
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;
}
参考资料: 左值右值 左值右值细节 关于移动语义 如何实现移动构造 移动构造函数 移动赋值 运算符重载
18、继承
c
static int i = 0;
struct X {
int x;
X(int x_) : x(x_) {
std::cout << ++i << ". " << "X(" << x << ')' << std::endl;
}
X(X const &other) : x(other.x) {
std::cout << ++i << ". " << "X(X const &) : x(" << x << ')' << std::endl;
}
~X() {
std::cout << ++i << ". " << "~X(" << x << ')' << std::endl;
}
};
struct A {
int a;
A(int a_) : a(a_) {
std::cout << ++i << ". " << "A(" << a << ')' << std::endl;
}
A(A const &other) : a(other.a) {
std::cout << ++i << ". " << "A(A const &) : a(" << a << ')' << std::endl;
}
~A() {
std::cout << ++i << ". " << "~A(" << a << ')' << std::endl;
}
};
struct B : public A {
X x;
B(int b) : A(1), x(b) {
std::cout << ++i << ". " << "B(" << a << ", X(" << x.x << "))" << std::endl;
}
B(B const &other) : A(other.a), x(other.x) {
std::cout << ++i << ". " << "B(B const &) : A(" << a << "), x(X(" << x.x << "))" << std::endl;
}
~B() {
std::cout << ++i << ". " << "~B(" << a << ", X(" << x.x << "))" << std::endl;
}
};
int main(int argc, char **argv) {
X x = X(1);
A a = A(2);
B b = B(3);
// TODO: 补全三个类型的大小
static_assert(sizeof(X) == 4, "There is an int in X");
static_assert(sizeof(A) == 4, "There is an int in A");
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: 真实场景中不太可能出现这样的代码
i = 0;
std::cout << std::endl
<< "-------------------------" << std::endl
<< std::endl;
return 0;
}
参考资料:派生类
19、virtual关键字,实现动态分发
c
struct A {
virtual char virtual_name() const {
return 'A';
}
char direct_name() const {
return 'A';
}
};
struct B : public A {
// READ: override <https://zh.cppreference.com/w/cpp/language/override>
char virtual_name() const override {
return 'B';
}
char direct_name() const {
return 'B';
}
};
struct C : public B {
// READ: final <https://zh.cppreference.com/w/cpp/language/final>
char virtual_name() const final {
return 'C';
}
char direct_name() const {
return 'C';
}
};
struct D : public C {
char direct_name() const {
return 'D';
}
};
int main(int argc, char **argv) {
constexpr auto MSG = "Replace '?' with its correct name.";
A a;
B b;
C c;
D d;
ASSERT(a.virtual_name() == 'A', MSG);
ASSERT(b.virtual_name() == 'B', MSG);
ASSERT(c.virtual_name() == 'C', MSG);
ASSERT(d.virtual_name() == 'C', MSG);
ASSERT(a.direct_name() == 'A', MSG);
ASSERT(b.direct_name() == 'B', MSG);
ASSERT(c.direct_name() == 'C', MSG);
ASSERT(d.direct_name() == 'D', MSG);
A &rab = b;
B &rbc = c;
C &rcd = d;
ASSERT(rab.virtual_name() == 'B', MSG);
ASSERT(rbc.virtual_name() == 'C', MSG);
ASSERT(rcd.virtual_name() == 'C', MSG);
ASSERT(rab.direct_name() == 'A', MSG);
ASSERT(rbc.direct_name() == 'B', MSG);
ASSERT(rcd.direct_name() == 'C', MSG);
A &rac = c;
B &rbd = d;
ASSERT(rac.virtual_name() == 'C', MSG);
ASSERT(rbd.virtual_name() == 'C', MSG);
ASSERT(rac.direct_name() == 'A', MSG);
ASSERT(rbd.direct_name() == 'B', MSG);
A &rad = d;
ASSERT(rad.virtual_name() == 'C', MSG);
ASSERT(rad.direct_name() == 'A', MSG);
return 0;
}
参考资料: 虚函数 扩展阅读-纯虚、抽象 虚继承
20、class virtual实现动态分发
c
struct A {
// TODO: 正确初始化静态字段
inline static int num_a = 0;
A() {
++num_a;
}
virtual ~A() {
--num_a;
}
virtual char name() const {
return 'A';
}
};
struct B final : public A {
// TODO: 正确初始化静态字段
inline static int num_b = 0;
B() {
++num_b;
}
virtual ~B() {
--num_b;
}
char name() const final {
return 'B';
}
};
int main(int argc, char **argv) {
auto a = new A;
auto b = new B;
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 b;
delete a;
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 == 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 = dynamic_cast<B*>(ab);
ASSERT(bb->name() == 'B', "Fill in the correct value for bb->name()");
// TODO: ---- 以下代码不要修改,通过改正类定义解决编译问题 ----
delete ab;// 通过指针可以删除指向的对象,即使是多态对象
ASSERT(A::num_a == 0, "Every A was destroyed");
ASSERT(B::num_b == 0, "Every B was destroyed");
return 0;
}
21、函数模板、泛型
c
template <typename T>
T plus(T a, T b) {
return a + b;
}
int plus(int a, int b) {
return a + b;
}
int main(int argc, char **argv) {
ASSERT(plus(1, 2) == 3, "Plus two int");
ASSERT(plus(1u, 2u) == 3u, "Plus two unsigned int");
// THINK: 浮点数何时可以判断 ==?何时必须判断差值?
ASSERT(plus(1.25f, 2.5f) == 3.75f, "Plus two float");
ASSERT(plus(1.25, 2.5) == 3.75, "Plus two double");
// TODO: 修改判断条件使测试通过
printf("0.1 + 0.2 = %f\n", plus(0.1f, 0.2f));
ASSERT(plus(0.1, 0.2) - 0.3f < 0.0001f, "How to make this pass?");
return 0;
}
参考资料: 函数模板
22、运行时数据类型[union 实现]
c
enum class DataType {
Float,
Double,
};
/// @brief Tagged union 即标签化联合体,是联合体的一种常见应用。
/// c enum 在实现上就是标签化联合体。
struct TaggedUnion {
DataType type;
// NOTICE: struct/union 可以相互任意嵌套。
union {
float f;
double d;
};
};
// TODO: 将这个函数模板化用于 sigmoid_dyn
template <typename T>
T sigmoid(T x) {
return 1 / (1 + std::exp(-x));
}
TaggedUnion sigmoid_dyn(TaggedUnion x) {
TaggedUnion ans{x.type};
// TODO: 根据 type 调用 sigmoid
switch (x.type) {
case DataType::Float:
ans.f = sigmoid(x.f);
break;
case DataType::Double:
ans.d = sigmoid(x.d);
break;
default:
ASSERT(false, "Unsupported type");
}
return ans;
}
// ---- 不要修改以下代码 ----
int main(int argc, char **argv) {
TaggedUnion xf{DataType::Float};
xf.f = 5.f;
auto yf = sigmoid_dyn(xf);
ASSERT(yf.type == DataType::Float, "type mismatch");
ASSERT(yf.f == 1 / (1 + std::exp(-5.f)), "sigmoid float");
TaggedUnion xd{DataType::Double};
xd.d = 5.0;
auto yd = sigmoid_dyn(xd);
ASSERT(yd.type == DataType::Double, "type mismatch");
ASSERT(yd.d == 1 / (1 + std::exp(-5.0)), "sigmoid double");
return 0;
}
23、模板类
c
template<class T>
struct Tensor4D {
unsigned int shape[4];
T *data;
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 (int i = 0; i < 4; ++i) {
if (others.shape[i] != 1 && others.shape[i] != shape[i]) {
// 如果others的维度既不是1也不等于this的维度,则不兼容
return *this; // 或者抛出异常
}
}
// 遍历this的所有元素
for (unsigned int i0 = 0; i0 < shape[0]; ++i0) {
for (unsigned int i1 = 0; i1 < shape[1]; ++i1) {
for (unsigned int i2 = 0; i2 < shape[2]; ++i2) {
for (unsigned int i3 = 0; i3 < shape[3]; ++i3) {
// 计算this中当前元素的线性索引
unsigned int this_idx = i0 * (shape[1] * shape[2] * shape[3]) +
i1 * (shape[2] * shape[3]) +
i2 * shape[3] +
i3;
// 计算others中对应元素的索引(考虑广播)
unsigned int others_i0 = (others.shape[0] == 1) ? 0 : i0;
unsigned int others_i1 = (others.shape[1] == 1) ? 0 : i1;
unsigned int others_i2 = (others.shape[2] == 1) ? 0 : i2;
unsigned int others_i3 = (others.shape[3] == 1) ? 0 : i3;
unsigned int others_idx = others_i0 * (others.shape[1] * others.shape[2] * others.shape[3]) +
others_i1 * (others.shape[2] * others.shape[3]) +
others_i2 * others.shape[3] +
others_i3;
// 执行加法
data[this_idx] += others.data[others_idx];
}
}
}
}
return *this;
}
};
// ---- 不要修改以下代码 ----
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,
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;
for (auto i = 0u; i < sizeof(data) / sizeof(*data); ++i) {
ASSERT(t0.data[i] == data[i] * 2, "Tensor doubled by plus its self.");
}
}
{
unsigned int s0[]{1, 2, 3, 4};
// clang-format off
float d0[]{
1, 1, 1, 1,
2, 2, 2, 2,
3, 3, 3, 3,
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
auto t0 = Tensor4D(s0, d0);
auto t1 = Tensor4D(s1, d1);
t0 += t1;
for (auto i = 0u; i < sizeof(d0) / sizeof(*d0); ++i) {
ASSERT(t0.data[i] == 7.f, "Every element of t0 should be 7 after adding t1 to it.");
}
}
{
unsigned int s0[]{1, 2, 3, 4};
// clang-format off
double d0[]{
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};
auto t0 = Tensor4D(s0, d0);
auto t1 = Tensor4D(s1, d1);
t0 += t1;
for (auto i = 0u; i < sizeof(d0) / sizeof(*d0); ++i) {
ASSERT(t0.data[i] == d0[i] + 1, "Every element of t0 should be incremented by 1 after adding t1 to it.");
}
}
}
参考资料: 模板类
24、模板template和const
c
template<unsigned int N, class T>
struct Tensor {
unsigned int shape[N];
T *data;
Tensor(unsigned int const shape_[N]) {
unsigned int size = 1;
// TODO: 填入正确的 shape 并计算 size
for (int i = 0; i < N; ++i) {
shape[i] = shape_[i];
size *= shape[i];
}
data = new T[size];
std::memset(data, 0, size * sizeof(T));
}
~Tensor() {
delete[] data;
}
// 为了保持简单,禁止复制和移动
Tensor(Tensor const &) = delete;
Tensor(Tensor &&) noexcept = delete;
T &operator[](unsigned int const indices[N]) {
return data[data_index(indices)];
}
T const &operator[](unsigned int const indices[N]) const {
return data[data_index(indices)];
}
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 *= shape[i];
index += indices[i];
}
return index;
}
};
// ---- 不要修改以下代码 ----
int main(int argc, char **argv) {
{
unsigned int shape[]{2, 3, 4, 5};
auto tensor = Tensor<4, int>(shape);
unsigned int i0[]{0, 0, 0, 0};
tensor[i0] = 1;
ASSERT(tensor[i0] == 1, "tensor[i0] should be 1");
ASSERT(tensor.data[0] == 1, "tensor[i0] should be 1");
unsigned int i1[]{1, 2, 3, 4};
tensor[i1] = 2;
ASSERT(tensor[i1] == 2, "tensor[i1] should be 2");
ASSERT(tensor.data[119] == 2, "tensor[i1] should be 2");
}
{
unsigned int shape[]{7, 8, 128};
auto tensor = Tensor<3, float>(shape);
unsigned int i0[]{0, 0, 0};
tensor[i0] = 1.f;
ASSERT(tensor[i0] == 1.f, "tensor[i0] should be 1");
ASSERT(tensor.data[0] == 1.f, "tensor[i0] should be 1");
unsigned int i1[]{3, 4, 99};
tensor[i1] = 2.f;
ASSERT(tensor[i1] == 2.f, "tensor[i1] should be 2");
ASSERT(tensor.data[3683] == 2.f, "tensor[i1] should be 2");
}
return 0;
}
参考资料:模板非类型实参
25、std array
c
int main(int argc, char **argv) {
{
std::array<int, 5> arr{{1, 2, 3, 4, 5}};
ASSERT(arr.size() == 5, "Fill in the correct value.");
ASSERT(sizeof(arr) == 20, "Fill in the correct value.");
int ans[]{1, 2, 3, 4, 5};
ASSERT(std::memcmp(arr.data(), ans, sizeof(ans)) == 0, "Fill in the correct values.");
}
{
std::array<double, 8> arr;
ASSERT(arr.size() == 8, "Fill in the correct value.");
ASSERT(sizeof(arr) == 64, "Fill in the correct value.");
}
{
std::array<char, 21> arr{"Hello, InfiniTensor!"};
ASSERT(arr.size() == 21, "Fill in the correct value.");
ASSERT(sizeof(arr) == 21, "Fill in the correct value.");
ASSERT(std::memcmp(arr.data(), "Hello, InfiniTensor!",sizeof("Hello, InfiniTensor!")) == 0, "Fill in the correct value.");
ASSERT(std::strcmp(arr.data(), "Hello, InfiniTensor!") == 0, "Fill in the correct value.");
}
return 0;
}
参考资料: std:array
26、std vector
c
int main(int argc, char **argv) {
{
std::vector<int> vec{1, 2, 3, 4, 5};
ASSERT(vec.size() == 5, "Fill in the correct value.");
// THINK: `std::vector` 的大小是什么意思?与什么有关?
ASSERT(sizeof(vec) == 24, "Fill in the correct value.");
int ans[]{1, 2, 3, 4, 5};
ASSERT(std::memcmp(vec.data(), ans, sizeof(ans)) == 0, "Fill in the correct values.");
}
{
std::vector<double> vec{1, 2, 3, 4, 5};
{
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.data(), ans, sizeof(ans)) == 0, "Fill in the correct values.");
}
{
vec.push_back(6);
ASSERT(vec.size() == 6, "Fill in the correct value.");
ASSERT(sizeof(vec) == 24, "Fill in the correct value.");
vec.pop_back();
ASSERT(vec.size() == 5, "Fill in the correct value.");
ASSERT(sizeof(vec) == 24, "Fill in the correct value.");
}
{
vec[4] = 6;
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(vec.begin() + 1, 1.5);
ASSERT((vec == std::vector<double>{1, 1.5, 2, 3, 4, 6}), "Make this assertion pass.");
vec.erase(vec.begin() + 2);
ASSERT((vec == std::vector<double>{1, 1.5, 3, 4, 6}), "Make this assertion pass.");
}
{
vec.shrink_to_fit();
ASSERT(vec.capacity() == 5, "Fill in the correct value.");
vec.clear();
ASSERT(vec.empty(), "`vec` is empty now.");
ASSERT(vec.size() == 0, "Fill in the correct value.");
ASSERT(vec.capacity() == 5, "Fill in the correct value.");
}
}
{
std::vector<char> vec(48, 'z'); // TODO: 调用正确的构造函数
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) == 24, "Fill in the correct value.");
{
auto capacity = vec.capacity();
vec.resize(16);
ASSERT(vec.size() == 16, "Fill in the correct value.");
ASSERT(vec.capacity() == capacity, "Fill in a correct identifier.");
}
{
vec.reserve(256);
ASSERT(vec.size() == 16, "Fill in the correct value.");
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() == 20, "Fill in the correct value.");
ASSERT(vec.capacity() == 256, "Fill in the correct value.");
ASSERT(vec[15] == 'z', "Fill in the correct value.");
ASSERT(vec[12] == 'z', "Fill in the correct value.");
ASSERT(vec[13] == 'z', "Fill in the correct value.");
ASSERT(vec[14] == 'z', "Fill in the correct value.");
ASSERT(vec[15] == 'z', "Fill in the correct value.");
}
}
return 0;
}
参考资料: std::vector
27、std vector with bool
c
int main(int argc, char **argv) {
std::vector<bool> vec(100, true);// TODO: 正确调用构造函数
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 上的值。
std::cout << "sizeof(std::vector<bool>) = " << sizeof(std::vector<bool>) << std::endl;
ASSERT(sizeof(vec) == 32, "Fill in the correct value.");
{
vec[20] = false;
ASSERT(!vec[20], "Fill in `vec[20]` or `!vec[20]`.");
}
{
vec.push_back(false);
ASSERT(vec.size() == 101, "Fill in the correct value.");
ASSERT(!vec[100], "Fill in `vec[100]` or `!vec[100]`.");
}
{
auto ref = vec[30];
ASSERT(ref, "Fill in `ref` or `!ref`");
ref = false;
ASSERT(!ref, "Fill in `ref` or `!ref`");
// THINK: WHAT and WHY?
ASSERT(!vec[30], "Fill in `vec[30]` or `!vec[30]`.");
}
return 0;
}
参考资料: std::vector 模板特化
28、矩阵stride
c
// 张量即多维数组。连续存储张量即逻辑结构与存储结构一致的张量。
// 通常来说,形状为 [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]。
using udim = unsigned int;
/// @brief 计算连续存储张量的步长
/// @param shape 张量的形状
/// @return 张量每维度的访问步长
std::vector<udim> strides(std::vector<udim> const &shape) {
std::vector<udim> strides(shape.size());
// TODO: 完成函数体,根据张量形状计算张量连续存储时的步长。
// READ: 逆向迭代器 std::vector::rbegin <https://zh.cppreference.com/w/cpp/container/vector/rbegin>
// 使用逆向迭代器可能可以简化代码
udim stride = 1;
for (auto it = shape.rbegin(); it != shape.rend(); ++it)
{
strides[shape.size()- 1 - std::distance(shape.rbegin(),it)] = stride;
stride *= *it; // 更新步长
}
return strides;
}
// ---- 不要修改以下代码 ----
int main(int argc, char **argv) {
ASSERT((strides({2, 3, 4}) == std::vector<udim>{12, 4, 1}), "Make this assertion pass.");
ASSERT((strides({3, 4, 5}) == std::vector<udim>{20, 5, 1}), "Make this assertion pass.");
ASSERT((strides({1, 3, 224, 224}) == std::vector<udim>{150528, 50176, 224, 1}), "Make this assertion pass.");
ASSERT((strides({7, 1, 1, 1, 5}) == std::vector<udim>{5, 5, 5, 5, 1}), "Make this assertion pass.");
return 0;
}
参考资料: 类型别名
29、std string
c
int main(int argc, char **argv) {
// READ: 字符串字面量 <https://zh.cppreference.com/w/cpp/string/basic_string/operator%22%22s>
using namespace std::string_literals;
auto hello = "Hello"s;
auto world = "world";
// READ: `decltype` 表达式 <https://zh.cppreference.com/w/cpp/language/decltype>
// READ: `std::is_same_v` 元编程判别 <https://zh.cppreference.com/w/cpp/types/is_same>
ASSERT((std::is_same_v<decltype(hello), std::string>), "Fill in the missing type.");
ASSERT((std::is_same_v<decltype(world), const char*>), "Fill in the missing type.");
// TODO: 将 `?` 替换为正确的字符串
ASSERT(hello + ", " + world + '!' == "Hello, world!", "Fill in the missing string.");
return 0;
}
参考资料: 字符串
30、std map
c
template<class k, class v>
bool key_exists(std::map<k, v> const &map, k const &key) {
// TODO: 实现函数
return map.find(key) != map.end();
}
template<class k, class v>
void set(std::map<k, v> &map, k key, v value) {
// TODO: 实现函数
// 如果 key 已存在,则更新其值;如果不存在,则插入新的键值对
map[key] = value;
}
// ---- 不要修改以下代码 ----
int main(int argc, char **argv) {
using namespace std::string_literals;
std::map<std::string, std::string> secrets;
set(secrets, "hello"s, "world"s);
ASSERT(key_exists(secrets, "hello"s), "\"hello\" shoud be in `secrets`");
ASSERT(!key_exists(secrets, "foo"s), "\"foo\" shoud not be in `secrets`");
set(secrets, "foo"s, "bar"s);
set(secrets, "Infini"s, "Tensor"s);
ASSERT(secrets["hello"] == "world", "hello -> world");
ASSERT(secrets["foo"] == "bar", "foo -> bar");
ASSERT(secrets["Infini"] == "Tensor", "Infini -> Tensor");
set(secrets, "hello"s, "developer"s);
ASSERT(secrets["hello"] == "developer", "hello -> developer");
return 0;
}
参考资料: std::map std::unordered_map
31、unique_ptr
c
std::vector<std::string> RECORDS;
class Resource {
std::string _records;
public:
void record(char record) {
_records.push_back(record);
}
~Resource() {
RECORDS.push_back(_records);
}
};
using Unique = std::unique_ptr<Resource>;
Unique reset(Unique ptr) {
if (ptr) ptr->record('r');
return std::make_unique<Resource>();
}
Unique drop(Unique ptr) {
if (ptr) ptr->record('d');
return nullptr;
}
Unique forward(Unique ptr) {
if (ptr) ptr->record('f');
return ptr;
}
int main(int argc, char **argv) {
std::vector<std::string> problems[3];
drop(forward(reset(nullptr)));
problems[0] = std::move(RECORDS);
forward(drop(reset(forward(forward(reset(nullptr))))));
problems[1] = std::move(RECORDS);
drop(drop(reset(drop(reset(reset(nullptr))))));
problems[2] = std::move(RECORDS);
// ---- 不要修改以上代码 ----
std::vector<const char *> answers[]{
{"fd"},
// TODO: 分析 problems[1] 中资源的生命周期,将记录填入 `std::vector`
// NOTICE: 此题结果依赖对象析构逻辑,平台相关,提交时以 CI 实际运行平台为准
{"ffr", "d"},
{"r", "d", "d"},
};
// ---- 不要修改以下代码 ----
for (auto i = 0; i < 3; ++i) {
ASSERT(problems[i].size() == answers[i].size(), "wrong size");
for (auto j = 0; j < problems[i].size(); ++j) {
ASSERT(std::strcmp(problems[i][j].c_str(), answers[i][j]) == 0, "wrong location");
}
}
return 0;
}
参考资料: std::unique_ptr
32、shared_ptr
c
int main(int argc, char **argv) {
auto shared = std::make_shared<int>(10);
std::shared_ptr<int> ptrs[]{shared, shared, shared};
std::weak_ptr<int> observer = shared;
ASSERT(observer.use_count() == 4, "");
ptrs[0].reset();
ASSERT(observer.use_count() == 3, "");
ptrs[1] = nullptr;
ASSERT(observer.use_count() == 2, "");
ptrs[2] = std::make_shared<int>(*shared);
ASSERT(observer.use_count() == 1, "");
ptrs[0] = shared;
ptrs[1] = shared;
ptrs[2] = std::move(shared);
ASSERT(observer.use_count() == 3, "");
std::ignore = std::move(ptrs[0]);
ptrs[1] = std::move(ptrs[1]);
ptrs[1] = std::move(ptrs[2]);
ASSERT(observer.use_count() == 2, "");
shared = observer.lock();
ASSERT(observer.use_count() == 3, "");
shared = nullptr;
for (auto &ptr : ptrs) ptr = nullptr;
ASSERT(observer.use_count() == 0, "");
shared = observer.lock();
ASSERT(observer.use_count() == 0, "");
return 0;
}
参考资料: std::shared_ptr std::week_ptr
33、std transform
c
int main(int argc, char **argv) {
std::vector<int> val{8, 13, 21, 34, 55};
// TODO: 调用 `std::transform`,将 `v` 中的每个元素乘以 2,并转换为字符串,存入 `ans`
// std::vector<std::string> ans
std::vector<std::string> ans(val.size());
std::transform(val.begin(), val.end(), ans.begin(), [](int v) {
return std::to_string(v * 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;
}
参考资料: std::transform std::vector::begin
34、std accumulate
c
int main(int argc, char **argv) {
using DataType = float;
int shape[]{1, 3, 224, 224};
// TODO: 调用 `std::accumulate` 计算:
// - 数据类型为 float;
// - 形状为 shape;
// - 连续存储;
// 的张量占用的字节数
// int size =
int size = std::accumulate(shape, shape + 4, sizeof(DataType), [](int acc, int dim) {
return acc * dim;
});
ASSERT(size == 602112, "4x1x3x224x224 = 602112");
return 0;
}
参考资料: std::accumulate