Skip to content

卢瑟们的作业展示,答案讲解,以及一些C++知识

License

Notifications You must be signed in to change notification settings

Adttil/Loser-HomeWork

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

72 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Loser-HomeWork

卢瑟们的作业展示

提交pr不应当更改当前README,请将作业提交到src\群友提交中,比如你要提交第一个作业:

你应当在src\群友提交\第一题中创建一个自己的.md.cpp文件,文件名以QQ群名命名

答题的一般要求如下(题目额外要求也自行注意看):

  1. 不更改main函数,不得使其不运行(意思别捞偏门)。
  2. 自行添加代码,在满足第一点的要求下,要能成功编译运行并与 给出运行结果一致

01实现管道运算符

日期:2023/7/21 出题人:mq白

给出代码:

int main(){
    std::vector v{1, 2, 3};
    std::function f {[](const int& i) {std::cout << i << ' '; } };
    auto f2 = [](int& i) {i *= i; };
    v | f2 | f;
}

运行结果

1 4 9

难度:一星

群友提交

答题者:andyli

#include <algorithm>
#include <vector>
#include <functional>
#include <iostream>

template <typename R, typename F>
auto operator|(R&& r, F&& f) {
    for (auto&& x: r)
        f(x);
    return r;
}
int main() {
    std::vector v{1, 2, 3};
    std::function f{[](const int& i) { std::cout << i << ' '; }};
    auto f2 = [](int& i) { i *= i; };
    v | f2 | f;
}

很常规,没啥问题。

答题者:mq松鼠

#include<iostream>
#include <vector>
#include <functional>

auto operator | (std::vector<int>&& v,std::function<void(const int&)> f){
    for(auto&i:v){
        f(i);
    }
    return v;
}
auto operator | (std::vector<int>& v,std::function<void(int&)> f){
    for(auto&i:v){
        f(i);
    }
    return v;
}
int main(){
    std::vector v{1, 2, 3};
    std::function f {[](const int& i) {std::cout << i << '\n'; } };
    auto f2 = [](int& i) {i *= i; };
    v | f2 | f;
}

评价:闲的没事多写个重载,裱起来。


标准答案

template<typename U, typename F>
    requires std::regular_invocable<F, U&>//可加可不加,不会就不加
std::vector<U>& operator|(std::vector<U>& v1, F f) {
    for (auto& i : v1) {
        f(i);
    }
    return v1;
}

不使用模板

std::vector<int>& operator|(std::vector<int>& v1, const std::function<void(int&)>& f) {
    for (auto& i : v1) {
        f(i);
    }
    return v1;
}

不使用范围for,使用C++20简写函数模板:

std::vector<int>& operator|(auto& v1, const auto& f) {
    std::ranges::for_each(v1, f);
    return v1;
}

各种范式无非就是这些改来改去了,没必要再写。



02实现自定义字面量_f

日期:2023/7/22 出题人:mq白

给出代码:

int main(){
    std::cout << "乐 :{} *\n"_f(5);
    std::cout << "乐 :{0} {0} *\n"_f(5);
    std::cout << "乐 :{:b} *\n"_f(0b01010101);
    std::cout << "{:*<10}"_f("卢瑟");
    std::cout << '\n';
    int n{};
    std::cin >> n;
    std::cout << "π:{:.{}f}\n"_f(std::numbers::pi_v<double>, n);
}

运行结果:

乐 :5 *
乐 :5 5 *
乐 :1010101 *
卢瑟******
6
π:3.141593

6为输入,决定π的小数点后的位数,可自行输入更大或更小数字。 提示:C++11用户定义字面量C++20format库。 难度:二星

群友提交

答题者:andyli

#include <format>
#include <iostream>
#include <string_view>
#include <string>

namespace impl {
    struct Helper {
        const std::string_view s;
        Helper(const char* s, std::size_t len): s(s, len) {}
        template <typename... Args>
        std::string operator()(Args&&... args) const {
            return std::vformat(s, std::make_format_args(std::forward<Args>(args)...));
        }
    };
} // namespace impl
impl::Helper operator""_f(const char* s, std::size_t len) noexcept {
    return {s, len};
}

int main() {
    std::cout << "乐 :{} *\n"_f(5);
    std::cout << "乐 :{0} {0} *\n"_f(5);
    std::cout << "乐 :{:b} *\n"_f(0b01010101);
    std::cout << "{:*<10}"_f("卢瑟");
    std::cout << '\n';
    int n{};
    std::cin >> n;
    std::cout << "π:{:.{}f}\n"_f(std::numbers::pi_v<double>, n);
}

标准答案

constexpr auto operator""_f(const char* fmt, size_t) {
    return[=]<typename... T>(T&&... Args) { return std::vformat(fmt, std::make_format_args(std::forward<T>(Args)...)); };
}


03实现print以及特化std::formatter

日期:2023/7/24 出题人:mq白

实现一个print,如果你做了上一个作业,我相信这很简单。 要求调用形式为:

print(格式字符串,任意类型和个数的符合格式字符串要求的参数)
struct Frac {
   int a, b;
};

给出自定义类型Frace,要求支持

Frac f{ 1,10 };
print("{}", f);// 结果为1/10

运行结果

1/10

禁止面相结果编程,使用宏等等方式,最多B(指评价),本作业主要考察和学习format库罢了。

提示: std::formatter

提交代码最好是网上编译了三个平台的截图,如:

图片

群友提交


标准答案

template<>
struct std::formatter<Frac>:std::formatter<char>{
    auto format(const auto& frac, auto& ctx)const{//const修饰是必须的
        return std::format_to(ctx.out(), "{}/{}", frac.a, frac.b);
    }
};
void print(std::string_view fmt,auto&&...args){
    std::cout << std::vformat(fmt, std::make_format_args(std::forward<decltype(args)>(args)...));
}

我们只是非常简单的支持了题目要求的形式,给std::formatter进行特化,如果要支持比如那些{:6}之类的格式化的话,显然不行,这涉及到更多的操作。 简单的特化以及std::formatter支持的形式可以参见文档。 一些复杂的特化,up之前也有写过,在Cookbook中;里面有对std::ranges::range,和std::tuple的特化,支持所有形式。



04给定模板类修改,让其对每一个不同类型实例化有不同ID

日期:2023/7/25 出题人:Maxy

#include<iostream>
class ComponentBase{
protected:
    static inline size_t component_type_count = 0;
};
template<typename T>
class Component : public ComponentBase{
public:
    //todo...
    //使用任意方式更改当前模板类,使得对于任意类型X,若其继承自Component

    //则X::component_type_id()会得到一个独一无二的size_t类型的id(对于不同的X类型返回的值应不同)
    //要求:不能使用std::type_info(禁用typeid关键字),所有id从0开始连续。
};
class A : public Component<A>
{};
class B : public Component<B>
{};
class C : public Component<C>
{};
int main()
{
    std::cout << A::component_type_id() << std::endl;
    std::cout << B::component_type_id() << std::endl;
    std::cout << B::component_type_id() << std::endl;
    std::cout << A::component_type_id() << std::endl;
    std::cout << A::component_type_id() << std::endl;
    std::cout << C::component_type_id() << std::endl;
}

运行结果

0
1
1
0
0
2

提交应当给出多平台测试结果,如图:

图片

群友提交


标准答案

template<typename T>
class Component : public ComponentBase{
public:
    static size_t component_type_id(){
        static size_t ID = component_type_count++;
        return ID;
    }
};


05实现scope_guard类型

日期:2023/7/29 出题人:mq白

要求实现 scope_guard 类型 ( 即支恃传入任意可调用类型 , 析构的时候同时调用 )。

#include <cstdio>
#include <iostream>
#include <functional>

struct X {
    X() { puts("X()"); }
    X(const X&) { puts("X(const X&)"); }
    X(X&&) noexcept { puts("X(X&&)"); }
    ~X() { puts("~X()"); }
};

int main() {
    {
        auto x = new X{};
        auto guard = scope_guard([&] {
            delete x;
            x = nullptr;
        });
    }
    puts("----------");
    {
        struct Test {
            void operator()(X*& x) {
                delete x;
                x = nullptr;
            }
        };
        auto x = new X{};
        Test t;
        auto guard = scope_guard(t, x);
    }
    puts("----------");
    {
        struct Test {
            void f(X*& x) {
                delete x;
                x = nullptr;
            }
        };
        auto x = new X{};
        Test t;
        auto guard = scope_guard{&Test::f, &t, x}; 
    }
}

运行结果

X()
~X()
----------
X()
~X()
----------
X()
~X()

群友提交


标准答案

使用类型擦除

struct scope_guard {
    std::function<void()>f;
    template<typename Func, typename...Args>requires std::invocable<Func, Args...>
    scope_guard(Func&& func, Args&&...args) :f{ [func = std::forward<Func>(func), ...args = std::forward<Args>(args)]() mutable {
            std::invoke(std::forward<std::decay_t<Func>>(func), std::forward<Args>(args)...);
        } }{}
    ~scope_guard() { f(); }
    scope_guard(const scope_guard&) = delete;
    scope_guard& operator=(const scope_guard&) = delete;
};

其实大家也可以考虑直接用std::bind,开销没测过

第一次构造是外面的默认构造;

第二次构造是初始化 lambda 捕获时复制构造;

第三次构造是从 lambda 初始化 std::function 时的移动构造;

第四次构造是调用f的复制构造。

第一个析构是 lambda 表达式的结果对象,里面因为 decay-copy 存了个 Xinvoke 的东西是 std::function 初始化时,通过移动构造创建的副本。


使用 std::tuple+std::apply

template<typename F, typename...Args>
    requires requires(F f, Args...args) { std::invoke(f, args...); }
struct scope_guard {
    F f;
    std::tuple<Args...>values;

    template<typename Fn, typename...Ts>
    scope_guard(Fn&& func, Ts&&...args) :f{ std::forward<Fn>(func) }, values{ std::forward<Ts>(args)... } {}
    ~scope_guard() {
        std::apply(f, values);
    }
    scope_guard(const scope_guard&) = delete;
};

template<typename F, typename...Args>//推导指引非常重要
scope_guard(F&&, Args&&...) -> scope_guard<std::decay_t<F>, std::decay_t<Args>...>;


06解释std::atomic初始化

日期:2023/8/2 出题人:mq白

#include <atomic>
int main() {
    std::atomic<int> n = 6;
    std::cout << n << '\n';
}

解释,为什么以上代码C++17 后可以通过编译, C++17 前不行?

图片

群友提交


标准答案

std::atomic<int> n = 6;中,由于6std::atomic<int>不是同一类型(但是这里其实有一个用户定义转换序列,你可以简单的认为6可以隐式转换)。

即调用转换构造函数:

constexpr atomic( T desired ) noexcept;

转换构造函数也会作为用户定义的转换序列中的一部分

6会调用转换构造函数,构造出一个临时的atomic对象用来直接初始化n,即

std::atomic<int> n(std::atomic<int>(6))

C++17之前 的版本,理所应当应该查找检测复制/移动 构造函数,满足要求才可以通过编译。但是:

atomic( const atomic& ) = delete;

实际上atomic的复制构造被删除(同时移动构造也被抑制生成了)。所以自然而然的不允许。

C++17的改动是:复制消除变为强制要求。 纯右值表达式作为构造对象的参数,不会再调用移动构造,也不会去检测,而是原位构造。

说句题外话,C++17后纯右值不可能再调用移动构造。没有移动构造或者复制构造不影响使用同类型纯右值初始化对象,如X x{X{}},即使移动/复制构造函数都被delete,也无所谓,code



07 new MyException

日期:2023/8/6 出题人:mq白

给出代码:

struct MyException :std::exception {
	const char* data{};
	MyException(const char* s) :data(s) { puts("MyException()"); }
	~MyException() { puts("~MyException()"); }
	const char* what()const noexcept { return data; }
};
void f2() {
	throw new MyException("new Exception异常....");
}
int main(){
    f2();
}

灵感来源自 java 人写 C++

main函数中自行修改代码,接取f2()函数抛出的异常(try catch)。

运行结果

MyException()
new Exception异常....
~MyException()

难度: 一星

某些IDE或者平台可能会将打印的异常信息标为红色放到第一行,即 new Exception异常.... 这句话也可能在第一行(一般终端运行不会,默认vs也无此功能)

群友提交


标准答案

About

卢瑟们的作业展示,答案讲解,以及一些C++知识

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 60.1%
  • TeX 16.9%
  • JavaScript 11.4%
  • CMake 6.2%
  • HTML 3.0%
  • Python 1.2%
  • Other 1.2%