Drogon网络库URL转发过程源码解析

2023-11-08

Drogon是C++开发的后端服务框架,在看示例代码时,简短的语法一下是真的美,于是打算花点时间学习其中的实现细节。看到request解析到响应的业务处理部分,有个地方处理很是巧妙,跟Qt的信号槽机制神似。
请求到后端服务后,首先做HTTP的协议解析,然后根据URL获取访问路径及请求参数,查找匹配的业务处理接口,接口调用拿到响应结果返回给客户端。查找匹配业务接口的过程,库中是这样的处理:

  1. 定义了一套处理接口的注册机制。
    a. 指定匹配的URL模式,URL中所有可填充参数的部分都用占位符表示,类似Python的Print函数格式化字符;
    b. 接收的HTTP方法等,用于描述请求到达该接口的前置过滤条件;
    c. 请求处理暴露出去的接口,也就是实现业务处理的地方;
    示例代码如下:
	    METHOD_ADD(CustomCtrl::hello, //业务处理接口
	               "/{userName}", //URL
	               Get,           //按方法过滤
	               "CustomHeaderFilter");  // path is /customctrl/{arg1}
	    METHOD_LIST_END
  1. 实现了URL解析,并从URL检出所需参数的方法;并且可以将检出参数(string类型)转换为与业务处理接口相匹配的参数类型。
    对于URL的解析和匹配过程,采取的方法是将位置参数替换为带小括号的正则表达式模式字符串,与请求中的URL做正则匹配,若能匹配中,则从匹配结果的组集合中获取各组所包含的字符串。字符串到参数类型的转换,采用了两种方案:
    1. 将字符串转换字符串流,通过流输出操作符,自动转为对应类型的参数;
    2. 基本的数据类型,int、double等通过stoi的接口调用进行转换。
      关键代码段如下:
// SFINAE技法确定当前类型是否有">>"定义
    template <typename T>
    struct CanConvertFromStringStream
    {
      private:
        using yes = std::true_type;
        using no = std::false_type;
        template <typename U>
        static auto test(U *p, std::stringstream &&ss)
            -> decltype((ss >> *p), yes()); //构造的校验结构,通常为表达式
        template <typename>
        static no test(...);
      public:
        static constexpr bool value =
            std::is_same<decltype(test<T>(nullptr, std::stringstream())),
                         yes>::value;
    };

	// 能够通过流输出操作获取,则通过如下方式
    template <typename T>
    typename std::enable_if<CanConvertFromStringStream<T>::value, void>::type
    getHandlerArgumentValue(T &value, std::string &&p)
    {
        if (!p.empty())
        {
            std::stringstream ss(std::move(p));
            ss >> value;
        }
    }
	//  重载同名方法,通过stoi调用来实现

当然其中要解决:
1) 怎么将url的位置参数与接口对应索引位置关联起来?如何拿到要转换的参数类型?
2) 参数指针是如何保存的?在参数转换完成后又是如何调用的?
针对上述问题,关键代码如下:

// 定义类模板,模板参数为函数指针类型,其函数签名与业务处理接口一致;
template <typename FUNCTION>
class HttpBinder : public HttpBinderBase
{
  public:
      // 通过构造函数的入参类型推导FUNCTION的模板实参类型
    HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
    {
        static_assert(traits::isHTTPFunction,
                      "Your API handler function interface is wrong!");
        handlerName_ = DrClassMap::demangle(typeid(FUNCTION).name());
    }


  private:
    FUNCTION func_; // 接口函数以成员变量方式保存,待匹配参数匹配转换完成后调用
    
       // 接口信息获取的Traits,主要用于提取接口回调敢兴趣的特性,比如参数个数,
       // 是否为类接口以及类名等。后面展开讲述;
	using traits = FunctionTraits<FUNCTION>;
    template <std::size_t Index>
    using nth_argument_type = typename traits::template argument<Index>;
    static const size_t argument_count = traits::arity;

//url中获取参数集解析逻辑如下,pathArguments中保存了所有的从路径中获取到string类型的参数。
//values为可变参数集,随着递归调用此次越来越深,该可变参数列表越长。
//初始个数为0,终止个数为响应处理接口的入参个数。
template <typename... Values, std::size_t Boundary = argument_count>
    typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run(
        std::deque<std::string> &pathArguments,
        const HttpRequestPtr &req,
        std::function<void(const HttpResponsePtr &)> &&callback,
        Values &&... values)
    {
        // Call this function recursively until parameter's count equals to the
        // count of target function parameters
        static_assert(
            BinderArgTypeTraits<nth_argument_type<sizeof...(Values)>>::isValid,
            "your handler argument type must be value type or const left "
            "reference type or right reference type");

	   //nth_argument_type能够获取到FUNCTION形参列表中,
	   // sizeof...(Values对应索引位置的参数类型。怎么获取到的后面再看。可
	   // 以把他当成特特殊的函数,入参为索引位置n,出参为数据类型。
        using ValueType =
            typename std::remove_cv<typename std::remove_reference<
                nth_argument_type<sizeof...(Values)>>::type>::type;
        ValueType value = ValueType();
        if (!pathArguments.empty())
        { 
            // 从参数集中获取首个,由于后面有弹出操作,所以是按序逐个获取的
            std::string v = std::move(pathArguments.front());
            pathArguments.pop_front();
            try
            {
                if (v.empty() == false)
                    // 这里就是根据实参类型调对应的转换接口了
                    getHandlerArgumentValue(value, std::move(v));
            }
            catch (const std::exception &e)
            {
                handleException(e, req, std::move(callback));
                return;
            }
        }
        else
        {
            try
            {
                value = req->as<ValueType>();
            }
            catch (const std::exception &e)
            {
                handleException(e, req, std::move(callback));
                return;
            }
            catch (...)
            {
                LOG_ERROR << "Exception not derived from std::exception";
                return;
            }
        }

	   // 将新解析的出来的value参数,作为入参追加道变参列表中
        run(pathArguments,
            req,
            std::move(callback),
            std::forward<Values>(values)...,
            std::move(value));
    }

    // sizeof...(Values) == Boundary,即从参数集中拿到了接口调用所需的所有参数
    template <typename... Values,
              std::size_t Boundary = argument_count,
              bool isCoroutine = traits::isCoroutine>
    typename std::enable_if<(sizeof...(Values) == Boundary) && !isCoroutine,
                            void>::type
    run(std::deque<std::string> &,
        const HttpRequestPtr &req,
        std::function<void(const HttpResponsePtr &)> &&callback,
        Values &&... values)
    {
        try
        {
            // 调用保存下来的FUNCTION函数
            callFunction(req, callback, std::move(values)...);
        }
        catch (const std::exception &except)
        {
            handleException(except, req, std::move(callback));
        }
        catch (...)
        {
            LOG_ERROR << "Exception not derived from std::exception";
            return;
        }
    }

至此,url中的参数集到接口实际参数之间的转换和调用过程就看完了。其中还有两个疑问点,FunctionTraits干了啥?假如调用接口为类的静态接口,callFunction又做了些什么?
首先来看FunctionTraits,关键代码如下:

// 通用模板定义
template <typename>
struct FunctionTraits;//此处省去非成员函数的特化模板

// 类成员函数的特化模板,通过模板匹配获取到了ClassType,ReturnType和形参列表Arguments
//还是没有看到获取第n个形参类型的实现。由于通过返回值和形参列表又重新构造了基类模板,所
//  以猜测肯定是在非成员函数特化模板中定义的。
template <typename ClassType, typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
    : FunctionTraits<ReturnType (*)(Arguments...)> //重新构造非成员函数模板,提取其他特性
{
    static const bool isClassFunction = true;
    static const bool isDrObjectClass =
        std::is_base_of<DrObject<ClassType>, ClassType>::value;
    using class_type = ClassType;
    static const std::string name()
    {
        return std::string("Class Function");
    }
};

// 就是这里,argument就是辅助模板
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (*)(Arguments...)>
{
    using result_type = ReturnType;

       // 借助标注库的tuple,获取指定索引位置的形参类型。其实也可以理解为另一个特殊函数的调用:
      //  入参为Index形参列表,输出为形参类型
    template <std::size_t Index>
    using argument =
        typename std::tuple_element<Index, std::tuple<Arguments...>>::type;
    static const std::size_t arity = sizeof...(Arguments);
    using class_type = void;
    using return_type = ReturnType;
    static const bool isHTTPFunction = false;
    static const bool isClassFunction = false;
    static const bool isDrObjectClass = false;
    static const bool isCoroutine = false;
    static const std::string name()
    {
        return std::string("Normal or Static Function");
    }
};

至此,获取第n个位置的形参问题已经解决了。再看第二个函数调用问题,关键代码如下:

  // 有了FunctionTraits,各种函数特性皆可认为已有了。这里只看类成员函数的场景
    template <typename... Values,
              bool isClassFunction = traits::isClassFunction,
              bool isDrObjectClass = traits::isDrObjectClass,
              bool isNormal = std::is_same<typename traits::first_param_type,
                                           HttpRequestPtr>::value>
    typename std::enable_if<isClassFunction && isDrObjectClass && isNormal,
                            typename traits::return_type>::type
    callFunction(const HttpRequestPtr &req, Values &&... values)
    {
        // 这里根据类型获取对象,可以理解为获取该类型的单例对象,至于该单例对象是
               //  如何获取管理的又是如何获取的,后面再展开看。至此我们已经看清了调用过程的全貌
        static auto objPtr =
            DrClassMap::getSingleInstance<typename traits::class_type>();
        return (*objPtr.*func_)(req, std::move(values)...);
    }

整个从url参数集到业务接口调用的过程我们已经撕完了,那么还有就是回调对象的注册管理问题了。
在Drogon库中可以通过继承HTTPController,增加业务处理接口,并注册到框架的方式来完成url到处理接口注册过程。看个典型代码:

class User : public drogon::HttpController<User>
{
  public:
    METHOD_LIST_BEGIN
    //use METHOD_ADD to add your custom processing function here;
    METHOD_ADD(User::getInfo, "/{id}", Get);                  //path is /api/v1/User/{arg1}
    METHOD_ADD(User::getDetailInfo, "/{id}/detailinfo", Get);  //path is /api/v1/User/{arg1}/detailinfo
    METHOD_ADD(User::newUser, "/{name}", Post);                 //path is /api/v1/User/{arg1}
    METHOD_LIST_END
    //your declaration of processing function maybe like this:
    void getInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void getDetailInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
    void newUser(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, std::string &&userName);
  public:
    User()
    {
        LOG_DEBUG << "User constructor!";
    }
};

#define METHOD_ADD(method, pattern, ...) \
    // 这里有一点宏定义使用的小技巧,变参是宏参数可以在函数调用时自动将部分参数
       //   检出用作构造函数的初始化列表。
    registerMethod(&method, pattern, {__VA_ARGS__}, true, #method)

想必getSingleInstance获取到的就是这里的User单例对象。METHOD_ADD自然是要创建HttpBinder对象并保存起来,然后在请求到来时根据path进行查找,然后就是刚讲完的那一套调用过程。HttpBinder的保存和查找过程不是我们所感兴趣的。我们感兴趣的是HttpController和getObject之间有什么关联。


template <typename T, bool AutoCreation = true>
class HttpController : public DrObject<T>, public HttpControllerBase
{namespace drogon
{
/**
 * @brief The base class for all drogon reflection classes.
 *
 */
class DROGON_EXPORT DrObjectBase
{
  public:
    /**
     * @brief Get the class name
     *
     * @return const std::string& the class name
     */
    virtual const std::string &className() const
    {
        static const std::string name{"DrObjectBase"};
        return name;
    }
    /**
     * @brief Return true if the class name is 'class_name'
     */
    virtual bool isClass(const std::string &class_name) const
    {
        return (className() == class_name);
    }
    virtual ~DrObjectBase()
    {
    }
};

/**
 * a class template to
 * implement the reflection function of creating the class object by class name
 */
template <typename T>
class DrObject : public virtual DrObjectBase
{
  public:
    virtual const std::string &className() const override
    {
        return alloc_.className();
    }
    static const std::string &classTypeName()
    {
        return alloc_.className();
    }
    virtual bool isClass(const std::string &class_name) const override
    {
        return (className() == class_name);
    }
  protected:
    // protect constructor to make this class only inheritable
    DrObject() = default;
    ~DrObject() override = default;
  private:
    class DrAllocator
    {
      public:
        DrAllocator()
        {
            // 构造函数中完成T的注册逻辑
            registerClass<T>();
        }
        const std::string &className() const
        {  
            // typeid(T).name()获取到T的类名,如"class test::MyClass"
            static std::string className =
                DrClassMap::demangle(typeid(T).name());
            return className;
        }
        template <typename D>
        typename std::enable_if<std::is_default_constructible<D>::value,
                                void>::type
        registerClass()
        {
            // 以类名为key,将创建单例的回调函数,向DrClassMap进行注册
            DrClassMap::registerClass(className(),
                                      []() -> DrObjectBase * { return new T; });
        }
        template <typename D>
        typename std::enable_if<!std::is_default_constructible<D>::value,
                                void>::type
        registerClass()
        {
        }
    };
    // use static val to register allocator function for class T;
    static DrAllocator alloc_; // 静态成员,该静态对象构造时完成T的注册
};
template <typename T>
typename DrObject<T>::DrAllocator DrObject<T>::alloc_;
}  // namespace drogon

// 注册到map表中
void DrClassMap::registerClass(const std::string &className,
                               const DrAllocFunc &func)
{
    LOG_TRACE << "Register class:" << className;
    getMap().insert(std::make_pair(className, func));
}

// 根据classname找到map表中单例对象,有则返回无则创建
const std::shared_ptr<DrObjectBase> &DrClassMap::getSingleInstance(
    const std::string &className)
{
    auto &mtx = internal::getMapMutex();
    auto &singleInstanceMap = internal::getObjsMap();
    {
        std::lock_guard<std::mutex> lock(mtx);
        auto iter = singleInstanceMap.find(className);
        if (iter != singleInstanceMap.end())
            return iter->second;
    }
    auto newObj = std::shared_ptr<DrObjectBase>(newObject(className));
    {
        std::lock_guard<std::mutex> lock(mtx);
        auto ret = singleInstanceMap.insert(
            std::make_pair(className, std::move(newObj)));
        return ret.first->second;
    }
}

// 调用注册时创建对象的回调函数,创建对象
DrObjectBase *DrClassMap::newObject(const std::string &className)
{
    auto iter = getMap().find(className);
    if (iter != getMap().end())
    {
        return iter->second();
    }
    else
        return nullptr;
}

DrObject的实现为反射机制的简单实现,即通过类名获取构造指定类型的对象,类名到类型的转换过程即为反射。这种机制在java中用的比较多,库作者自己简单实现了一套反射机制,主要做单例对象的管理用。
该部分代码,库作者对模板技术的应用也是看着很爽,算得上是模板应用的实例教程了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Drogon网络库URL转发过程源码解析 的相关文章

  • 静态只读字符串数组

    我在我的 Web 应用程序中使用静态只读字符串数组 基本上数组有错误代码 我将所有类似的错误代码保存在一个数组中并检查该数组 而不是检查不同常量字符串中的每个错误代码 like public static readonly string m
  • 使用 C# 登录《我的世界》

    我正在尝试为自己和一些朋友创建一个简单的自定义 Minecraft 启动器 我不需要启动 Minecraft 的代码 只需要登录的实际代码行 例如 据我所知 您过去可以使用 string netResponse httpGET https
  • 在c#中执行Redis控制台命令

    我需要从 Redis 控制台获取 客户端列表 输出以在我的 C 应用程序中使用 有没有办法使用 ConnectionMultiplexer 执行该命令 或者是否有内置方法可以查找该信息 CLIENT LIST是 服务器 命令 而不是 数据库
  • ComboBox DataBinding 导致 ArgumentException

    我的几个类对象 class Person public string Name get set public string Sex get set public int Age get set public override string
  • 如何在C(Linux)中的while循环中准确地睡眠?

    在 C 代码 Linux 操作系统 中 我需要在 while 循环内准确地休眠 比如说 10000 微秒 1000 次 我尝试过usleep nanosleep select pselect和其他一些方法 但没有成功 一旦大约 50 次 它
  • JNI 将 Char* 2D 数组传递给 JAVA 代码

    我想从 C 代码通过 JNI 层传递以下指针数组 char result MAXTEST MAXRESPONSE 12 12 8 3 29 70 5 2 42 42 在java代码中我写了以下声明 public static native
  • 使用 GCP 的数据存储区时如何区分代码是在模拟器中运行还是在 GKE 中运行

    按照中给出的说明进行操作后 我不确定是否遗漏了任何内容https cloud google com datastore docs tools datastore emulator https cloud google com datasto
  • 使用 LINQ to SQL 时避免连接超时的最佳实践

    我需要知道在 net 应用程序中使用 LINQ to SQL 时避免连接超时的最佳实践 特别是在返回时IQueryable
  • 在Linux中,找不到框架“.NETFramework,Version=v4.5”的参考程序集

    我已经设置了 Visual studio 来在我的 Ubuntu 机器上编译 C 代码 我将工作区 我的代码加载到 VS 我可以看到以下错误 The reference assemblies for framework NETFramewo
  • 告诉 Nancy 将枚举序列化为字符串

    Nancy 默认情况下在生成 JSON 响应时将枚举序列化为整数 我需要将枚举序列化为字符串 有一种方法可以通过创建来自定义 Nancy 的 JSON 序列化JavaScript 原始转换器 https github com NancyFx
  • 将 Long 转换为 DateTime 从 C# 日期到 Java 日期

    我一直尝试用Java读取二进制文件 而二进制文件是用C 编写的 其中一些数据包含日期时间数据 当 DateTime 数据写入文件 以二进制形式 时 它使用DateTime ToBinary on C 为了读取 DateTime 数据 它将首
  • 类型约束

    我有以下类层次结构 class Header IEnumerable
  • 使用valgrind进行GDB远程调试

    如果我使用远程调试gdb我连接到gdbserver using target remote host 2345 如果我使用 valgrind 和 gdb 调试内存错误 以中断无效内存访问 我会使用 target remote vgdb 启动
  • 如何在 C 中安全地声明 16 位字符串文字?

    我知道已经有一个标准方法 前缀为L wchar t test literal L Test 问题是wchar t不保证是16位 但是对于我的项目 我需要16位wchar t 我还想避免通过的要求 fshort wchar 那么 C 不是 C
  • C++ int 前面加 0 会改变整个值

    我有一个非常奇怪的问题 如果我像这样声明一个 int int time 0110 然后将其显示到控制台返回的值为72 但是当我删除前面的 0 时int time 110 然后控制台显示110正如预期的那样 我想知道两件事 首先 为什么它在
  • 保护 APK 中的字符串

    我正在使用 Xamarin 的 Mono for Android 开发一个 Android 应用程序 我目前正在努力使用 Google Play API 添加应用内购买功能 为此 我需要从我的应用程序内向 Google 发送公共许可证密钥
  • 高效列出目录中的所有子目录

    请参阅迄今为止所采取的建议的编辑 我正在尝试使用 WinAPI 和 C 列出给定目录中的所有目录 文件夹 现在我的算法又慢又低效 使用 FindFirstFileEx 打开我正在搜索的文件夹 然后我查看目录中的每个文件 使用 FindNex
  • Unity:通过拦截将两个接口注册为一个单例

    我有一个实现两个接口的类 我想对该类的方法应用拦截 我正在遵循中的建议Unity 将两个接口注册为一个单例 https stackoverflow com questions 1394650 unity register two inter
  • OpenGL:仅获取模板缓冲区而没有深度缓冲区?

    我想获取一个模板缓冲区 但如果可能的话 不要承受附加深度缓冲区的开销 因为我不会使用它 我发现的大多数资源表明 虽然模板缓冲区是可选的 例如 排除它以利于获得更高的深度缓冲区精度 但我还没有看到任何请求并成功获取仅 8 位模板缓冲区的代码
  • 可访问性不一致:参数类型的可访问性低于方法

    我试图在两个表单之间传递一个对象 基本上是对当前登录用户的引用 目前 我在登录表单中有一些类似的内容 private ACTInterface oActInterface public void button1 Click object s

随机推荐

  • 团队管理中的代码评审

    代码评审在软件项目管理中是经常组织的活动 通过代码评审的工作也确实给我们的团队带来很多的益处 简单谈谈代码评审的感受 你们的团队是否也在进行代码评审 Code Review 的相关工作呢 1 为什么要组织代码评审 组织代码评审其主要目的是保
  • uni-app跨端开发微信小程序之nodejs与后端通信并动态打包项目以适应多环境开发

    摘要 这篇文章主要的目的是分享一个可与后端接口通信的自动化脚本插件 实现不同环境下可打包成不同配置的微信小程序源码 全程靠命令行自动发起请求 修改配置文件 自动编译 解放双手不是梦 看官在阅读文章前可以思考这样一个场景 有一份代码需要支持本
  • Java与设计模式(3):抽象工厂模式

    一 定义 抽象工厂模式是一种创建型设计模式 它提供了一种将相关对象组合在一起创建的方式 而无需指定它们的具体类 在抽象工厂模式中 有一个抽象工厂接口 该接口定义了一组创建相关对象的方法 每个具体的工厂类都实现了这个接口 并负责创建一组相关的
  • Xshell 使用密钥连接服务器,每次都提示:SSH服务器拒绝了密码。请再试一次

    查了很多 原来问题出在这里 1 在用户身份验证 连接方法设置为public key 2 浏览 导入你服务端下载的密钥 确定 输入密钥密码 问题解决
  • 手写js物理引擎

    先来看效果 包括混沌小球碰撞 上抛 自由落体 滚动 想要手写游戏 这些都是最基础的内容 也是一些游戏库的底层原理 开始之前 先回忆一些物理和数学知识 1 自由落体 重力相关 2 非弹性碰撞 角度 速度 3 向量 标量 4 动量守恒 动能守恒
  • 你了解API测试吗?如何充分的测试一个API?

    什么是API API代表应用程序接口 API是软件系统中的中间层 负责数据源与用户看到的图形用户界面 GUI 之间的数据通信 换句话说 API是软件的业务层 它在表示层和数据层之间创建连接 API测试侧重于所谓的应用程序业务层 这意味着与标
  • 生成式对抗网络(GAN, Generaitive Adversarial Networks)总结

    最近要做有关图像生成的工作 也是小白 今天简单学习一些有关GAN的基础知识 很浅 入个门 大神勿喷 GAN目前确实是在深度学习领域最热门 最有前景的方向之一 近几年有关于GAN的论文非常非常之多 从2016年起关于GAN的论文是爆炸性的增长
  • android图片传输三方框架,Android ImageLoader第三方框架解析

    本文实例为大家分享了Android ImageLoader框架的使用方法 供大家参考 具体内容如下 1 准备工作 1 导入universal image loader 1 9 5 jar到项目中 2 创建MyApplication继承App
  • Swin-transformer 和其升级Cswin-transformer

    Swin Transformer Hierarchical Vision Transformer using Shifted Windows 论文解读 论文信息 概要 Swin transformer是微软今年三月25日公布的一篇利用tra
  • Verilog单周期CPU设计(超详细)

    下篇 Verilog流水线CPU设计 超详细 本篇完整工程下载链接 实验 单周期CPU 一 设计目的与目标 实验内容 实验要求 二 课程设计器材 硬件平台 软件平台 三 CPU逻辑设计总体方案 指令模块 MIPS指令格式 指令处理流程 数据
  • (优雅的使用)matlab脚本编程习题4

    优雅的使用 matlab脚本编程习题4 第四章数据可视化 虽然这种题 代码不会差的太多 但是我还是建议各位借鉴的时候 先理解我的代码 再自己重写一遍 写的时候尽量不要对着我的看 每个人的代码风格区别还是挺大的hhhh 上课的时候有看到同学用
  • matlab求数组上三角的和,第三周作业:判断上三角矩阵和求数组和

    第一个题 7 1 判断上三角矩阵 15 分 上三角矩阵指主对角线以下的元素都为0的矩阵 主对角线为从矩阵的左上角至右下角的连线 本题要求编写程序 判断一个给定的方阵是否上三角矩阵 输入格式 输入第一行给出一个正整数T 为待测矩阵的个数 接下
  • 计蒜客 T1044 最大数输出

    题目链接 https nanti jisuanke com t T1044 算法特工队QQ群 979618872 伸手党绕边 欢迎有良好基础的人加入 Created by Leo Lee on 2019 4 5 include
  • 年轻人正在双11掀起国潮热

    NEW 关注Tech逆向思维视频号 最新视频 男生的秋裤 女生的打底裤 哪个更抗冻 出品 盒饭财经 文 彻诺 当传统工艺走入现代商业 当传统文化遇上当代场景 郭艾薇的朋友圈已成了大型考古现场 你看 这是我朋友挖土时给自己拍的视频 这是另一个
  • iOS编译中pod报错CocoaPods could not find compatible versions for pod "XXXXX":

    pod install no repo update Analyzing dependencies CocoaPods could not find compatible versions for pod XXXXX In snapshot
  • 浅谈凝聚态物理的格林函数方法学习心得

    1 不必纠结单体格林函数 直接看多体格林函数 2 格林函数的定义也不必过多纠结 直接看无相互作用应用 这一章简单看看应用 这一章同样看应用 怎么用来算物理量 这一章简单看看应用 重点是RPA 重点是磁化率 运动方程解法在固体理论中讲的详细
  • 工业互联网这个领域 中国离世界第一还有多远?

    这个领域 中国离世界第一还有多远 试想一下 在一个大型工厂内 如果小到一颗螺丝钉的温度变化等数据都可以采集 分析 那就能大幅降低机器能耗 提升产品质量 甚至避免重大安全事故 这仅是工业互联网应用的冰山一角 互联网与工业的融合将为工业插上腾飞
  • Ubuntu下安装Chrome浏览器

    一 获取 deb安装包 终端输入 wget https dl google com linux direct google chrome stable current amd64 deb 二 安装 deb 使用指令 sudo dpkg i
  • 如何防止打开终端Conda默认激活基本环境

    当你安装好Anaconda 每次打开终端都会自动帮你激活基本环境 base 有时候确实自己不需要激活Conda环境 因为打开终端不一定要用到 Python 而且该项操作还会拖慢打开的终端的响应速度 十分烦人 经过网上查找方案 直接在终端输入
  • Drogon网络库URL转发过程源码解析

    Drogon是C 开发的后端服务框架 在看示例代码时 简短的语法一下是真的美 于是打算花点时间学习其中的实现细节 看到request解析到响应的业务处理部分 有个地方处理很是巧妙 跟Qt的信号槽机制神似 请求到后端服务后 首先做HTTP的协