c++ 反射
转至:https://cloud.tencent.com/developer/article/1524775
本文不讨论完整的C++反射技术,只讨论 结构体 (struct) 的 字段 (field) 反射,及其在序列化/反序列化代码生成上的应用。
正文开始于 § 静态反射 部分,其他部分都是铺垫,可以略读。
打包后的代码可以通过 archived.zip下载,每个
.cc
文件上都有对应的编译、运行脚本,或者可以通过run_all.sh
脚本运行所有代码。
1. 背景
很多人喜欢把程序员称为 码农,程序员也经常嘲讽自己每天都在 搬砖。这时候,大家会想:能否构造出一些 更好的工具,代替我们做那些无意义的 体力劳动 呢?
在实际 C++ 项目中,我们经常需要实现一些与外部系统交互的 接口 —— 外部系统传入 JSON 参数,我们的程序处理后,再以 JSON 的格式传回外部系统。这个过程就涉及到了两次数据结构的转换:
- 输入的 JSON 转换为 C++ 数据结构(反序列化 deserialization)
- C++ 数据结构 转换为 输出的 JSON(序列化 serialization)
如果传输的 JSON 数据 格式 (schema) 非常繁多、比较复杂,那么序列化/反序列化的代码也会变得非常复杂 —— 需要处理 结构嵌套、可选字段、输入合法性检查 等问题。如果为每个 JSON 数据结构都 人工手写 一套序列化/反序列化代码,那么 工作量 会特别大。
例如,chromium/headless 的 devtools 相关接口里就定义了 33 个 领域模型 (domain model),每个模型有自己的格式,其中又包含了许多字段。
懒惰是程序员的天性:
- “勤奋” 的程序员选择 § 人工手写 序列化/反序列化 代码
- “懒惰” 的程序员选择
- 构建代码生成器
(例如 protobuf、chromium/mojo)
- 或 § 编译器生成 序列化/反序列化 代码
代码生成器虽然功能强大,但依赖复杂,不易于和已有系统集成。所以本文主要讨论如何用 C++ 14 提供的 元编程 (metaprogramming) 技巧(C++ 11 也支持),让编译器帮你写代码。
2. 目标
- 基于 C++ 原生语法,不需要引入第三方库
- 支持 非侵入式 (nonintrusive) 接口,能直接应用到已有代码上
- 提供 声明式 (declarative) 的方法,只需要声明格式,不需要写逻辑语句
- 不会带来 额外的运行时开销,能达到和手写代码一样的运行时效率
基于 nlohmann的C++JSON库,给定两个C++结构体:SimpleStruct
和 NestedStruct
:
struct SimpleStruct {bool bool_;int int_;double double_;std::string string_;std::unique_ptr<bool> optional_; };struct NestedStruct {SimpleStruct nested_;std::vector<SimpleStruct> vector_; };
NestedStruct::nested_
为嵌套对象,NestedStruct::vector_
为嵌套的对象数组SimpleStruct::optional_
为可选字段;由于 `std::optional` 需要 C++ 17 支持,所以我们使用 `std::unique_ptr` 表示 可选字段- 针对 可选字段 的 JSON 序列化/反序列化 扩展代码,见 `optional_json.h`(参考:How do I convert third-party types? | nlohmann/json)
一般接口的业务处理,往往包括三部分:
- 解析输入(字符串到 JSON 对象的转换 + JSON 对象到领域模型的 反序列化)
- 处理业务逻辑(实际需要我们写的代码)
- 转储输出(领域模型到 JSON 对象的 序列化 + JSON 对象到字符串的转换)
// input json json_input = json::parse("{"" \"_nested\": {"" \"_bool\": false,"" \"_int\": 0,"" \"_double\": 0,"" \"_string\": \"foo\""" },"" \"_vector\": [{"" \"_bool\": true,"" \"_int\": 1,"" \"_double\": 1,"" \"_string\": \"bar\","" \"_optional\": true"" },{"" \"_bool\": true,"" \"_int\": 2,"" \"_double\": 2.0,"" \"_string\": \"baz\","" \"_optional\": false"" }]""}"); NestedStruct nested = json_input.get<NestedStruct>();// use nested.nested_.string_ += " in nested struct";// output json json_output = json(nested); std::string string_output = json_output.dump(2);
- 对于 JSON 对象和字符串之间的转换,主流的JSON 库都实现了:
- 调用
json::parse
从字符串得到输入 JSON 对象 - 调用
json::dump
将 JSON 对象转为用于输出的字符串
- 调用
- 而 JSON 对象和 C++ 结构体之间的转换,需要我们实现:
- 通过反序列化,调用
json::get()
得到NestedStruct nested
- 通过序列化,使用
nested
构造输出 JSON 对象
- 通过反序列化,调用
3. 实现
实现从 C++ 结构体到 JSON 的序列化/反序列化操作,需要用到以下信息:
- 结构体有哪些字段
bool_
/int_
/double_
/string_
/optional_
nested_
/vector_
- 每个字段在结构体中的什么位置
&SimpleStruct::bool_
/&SimpleStruct::int_
/&SimpleStruct::double_
/&SimpleStruct::string_
/&SimpleStruct::optional_
&NestedStruct::nested_
/&NestedStruct::vector_
- 每个字段在JSON 中对应的名称是什么
"_bool"
/"_int"
/"_double"
/"_string"
/"_optional"
"_nested"
/"_vector"
- 每个字段如何从 C++ 到 JSON 进行类型映射
bool
对应Boolean
int
对应Number(Integer)
double
对应Number
string
对应String
vector
对应Array
SimpleStruct
/NestedStruct
对应Object
- 必选字段缺失 或 字段类型与 JSON 数据 类型不匹配,则抛出异常
- 可选字段(例如
optional_
)缺失,则跳过检查
对于很多支持 反射 (reflection) 的语言,JSON 的解析者 可以通过反射接口,查询到SimpleStruct
/NestedStruct
所有的 字段信息。
尽管 C++ 支持 运行时类型信息 (RTTI, run-time type information),但无法得到所有上述信息,所以需要 SimpleStruct 的定义者 把这些信息告诉 JSON 的解析者。
于是,我们用以下几种方法实现:
- 人工手写 序列化/反序列化 代码
- 动态反射
- 静态反射
- 编译器生成 序列化/反序列化 代码
4. 人工手写 序列化/反序列化 代码
代码链接
实现序列化/反序列化最简单的方法,就是通过 人工编写 代码:
void to_json(nlohmann::json& j, const SimpleStruct& value) {j["_bool"] = value.bool_;j["_int"] = value.int_;j["_double"] = value.double_;j["_string"] = value.string_;j["_optional"] = value.optional_; }void from_json(const nlohmann::json& j, SimpleStruct& value) {j.at("_bool").get_to(value.bool_);j.at("_int").get_to(value.int_);j.at("_double").get_to(value.double_);j.at("_string").get_to(value.string_);if (j.find("_optional") != j.cend()) {j.at("_optional").get_to(value.optional_);} }void to_json(nlohmann::json& j, const NestedStruct& value) {j["_nested"] = value.nested_;j["_vector"] = value.vector_; }void from_json(const nlohmann::json& j, NestedStruct& value) {j.at("_nested").get_to(value.nested_);j.at("_vector").get_to(value.vector_); }
- 在
to_json
/from_json
中包含了 所有字段 的 位置、名称、映射方法:- 使用
j[name] = field
序列化 - 使用
j.at(name).get_to(field)
反序列化 - 针对可选字段检查字段是否存在,不存在则跳过
- 使用
- nlohmann 的 C++ JSON 库能处理 结构嵌套:
j = value.nested_
会调用void to_json(json& j, const SimpleStruct& value)
序列化SimpleStruct
j.get_to(value.nested_)
会调用void from_json(const json& j, SimpleStruct& value)
反序列化SimpleStruct
- nlohmann 的 C++ JSON 库基于 C++ 原生的 异常处理(
throw-try-catch
):- 如果字段不存在,函数
json::at
抛出异常 - 如果字段实际类型和 JSON 输入类型不匹配,函数
json::get_to
抛出异常
- 如果字段不存在,函数
手写 to_json
/from_json
需要写 2 份类似的代码:
- 一方面,需要复制粘贴,导致 代码冗余
- 另一方面,两份代码逻辑不是对称的(需要特殊处理 可选字段),不易于统一编写
5. 动态反射
“崇尚偷懒”的 Google 的工程师,为 chromium/base::Value 构建了一套基于 动态反射 (dynamic reflection) 的反序列化机制,实现统一的 JSON数据和C++结构体转换。(参考:chromium/base::JSONValueConverter)
核心原理 是:利用 适配器模式 (adapter pattern) 和 策略模式 (strategy pattern),定义 接口 (interface) 抹除具体字段转换操作的类型,通过 运行时多态 (runtime polymorphism) 调用接口进行实际的转换操作。
Talk is cheap, show me the code —— 代码链接
首先,为不同 字段类型 定义一个通用的转换接口 ValueConverter<FieldType>
,用于存储实际的 C++ 类型与 JSON 类型的转换操作(仅关联操作的字段类型,抹除具体转换操作的类型):
template <typename FieldType> using ValueConverter =std::function<void(FieldType* field, const std::string& name)>;
- 参数
field
表示字段的值,name
是字段的名称 - 原始代码将
ValueConverter
定义为接口;本文为了化简,直接使用std::function
(关于使用接口的讨论,参考:回调 vs 接口)
然后,为不同类型的 结构体 定义一个通用的转换接口 FieldConverterBase<StructType>
,用于存储结构体内所有字段的转换操作(仅关联结构体的类型,抹除操作的字段类型):
template <typename StructType> class FieldConverterBase {public:virtual ~FieldConverterBase() = default;virtual void operator()(StructType* obj) const = 0; };
接着,通过 FieldConverter <StructType, FieldType>
将上边两个接口 承接 起来,用于存储 结构体 的 字段类型 的实际转换操作(类似于 double dispatch),同时关联上具体某个字段的位置和名称(实现 FieldConverterBase 接口,调用 ValueConverter 接口):
template <typename StructType, typename FieldType> class FieldConverter : public FieldConverterBase<StructType> {public:FieldConverter(const std::string& name,FieldType StructType::*pointer,ValueConverter<FieldType> converter): field_name_(name),field_pointer_(pointer),value_converter_(converter) {}void operator()(StructType* obj) const override {return value_converter_(&(obj->*field_pointer_), field_name_);}private:std::string field_name_;FieldType StructType::*field_pointer_;ValueConverter<FieldType> value_converter_; };
- 构造时传递 字段名称
field_name_
- 字段的 成员指针 (member pointer)(即字段位置)
field_pointer_
- 字段的映射方法
value_converter_
- 在
operator()
转换时调用 :value_converter_.operator()
,传入当前结构体中字段的值和字段的名称;其中结构体obj
字段的值通过obj->*field_pointer_
得到
最后,针对 结构体 定义一个存储 所有字段 信息(名称、位置、映射方法)的容器StructValueConverter<StructType>
,并提供 注册 字段信息的接口(有哪些字段)RegisterField
和执行所有转换操作的接口 operator()
(仅关联结构体的类型,利用 FieldConverterBase 抹除操作的字段信息):
template <class StructType> class StructValueConverter {public:template <typename FieldType>void RegisterField(FieldType StructType::*field_pointer,const std::string& field_name,ValueConverter<FieldType> value_converter) {fields_.push_back(std::make_unique<FieldConverter<StructType, FieldType>>(field_name, field_pointer, std::move(value_converter)));}void operator()(StructType* obj) const {for (const auto& field_converter : fields_) {(*field_converter)(obj);}}private:std::vector<std::unique_ptr<FieldConverterBase<StructType>>> fields_; };
使用样例代码链接
具体使用时,只需要两步:
- 构造
converter
对象,调用RegisterField
动态绑定字段信息(名称、位置、映射方法) - 调用
converter(&simple)
对所有注册了的字段 进行转换
// setup converter (partial) auto int_converter = [](int* field, const std::string& name) {std::cout << name << ": " << *field << std::endl; }; auto string_converter = [](std::string* field, const std::string& name) {std::cout << name << ": " << *field << std::endl; };StructValueConverter<SimpleStruct> converter; converter.RegisterField(&SimpleStruct::int_, "int",ValueConverter<int>(int_converter)); converter.RegisterField(&SimpleStruct::string_, "string",ValueConverter<std::string>(string_converter));// use converter SimpleStruct simple{2, "hello dynamic reflection"}; converter(&simple);// output: // int: 2 // string: hello dynamic reflection
基于动态反射的开源库:
- https://github.com/fnc12/sqlite_orm
- https://github.com/billyquith/ponder
- https://github.com/rttrorg/rttr
6. 静态反射
实际上,实现序列化/反序列化所需要的信息(有哪些字段,每个字段的位置、名称、映射方法),在 编译时 (compile-time) 就已经确定了 —— 没必要在 运行时 (runtime) 动态构建 converter
对象。所以,我们可以利用 静态反射 (static reflection) 的方法,把这些信息告诉 编译器,让它帮我们 生成代码。
核心原理 是:利用 访问者模式 (visitor pattern),使用 元组 std::tuple
记录结构体所有的字段信息,通过 编译时多态 (compile-time polymorphism) 针对具体的 字段类型 进行转换操作。
Talk is cheap, show me the code —— 代码链接
首先,
定义一个StructSchema<StructType>
函数模板 (function template),返回所有字段信息(默认返回空元组):
template <typename T> inline constexpr auto StructSchema() {return std::make_tuple(); }
然后,
提供以下两个宏:
DEFINE_STRUCT_SCHEMA
和 DEFINE_STRUCT_FIELD
定义结构体字段信息(有哪些、位置、名称)
隐藏 StructSchema
和 std::tuple
的实现细节:
#define DEFINE_STRUCT_SCHEMA(Struct, ...) \template <> \inline constexpr auto StructSchema<Struct>() { \using _Struct = Struct; \return std::make_tuple(__VA_ARGS__); \}#define DEFINE_STRUCT_FIELD(StructField, FieldName) \std::make_tuple(&_Struct::StructField, FieldName)
StructSchema
返回元组的结构是:((& field1, name1), (& field2, name2), …)
DEFINE_STRUCT_SCHEMA
定义了 结构体Struct
有哪些字段DEFINE_STRUCT_FIELD
定义了每个 字段 的 位置、名称using _Struct = Struct
提供了一种宏内数据接力的方法,让下一个宏能获取上一个宏的数据
最后,
提供 ForEachField<StructType>
函数,从对应的StructSchema<StructType>
取出记录结构体 StructType
所有字段信息 的元组,然后遍历这个元组,从中取出 每个字段的位置、名称,作为参数调用转换函数 fn
:
template <typename T, typename Fn> inline constexpr void ForEachField(T&& value, Fn&& fn) {constexpr auto struct_schema = StructSchema<std::decay_t<T>>();detail::ForEachTuple(struct_schema, [&value, &fn](auto&& field_schema) {fn(value.*(std::get<0>(std::forward<decltype(field_schema)>(field_schema))),std::get<1>(std::forward<decltype(field_schema)>(field_schema)));}); }
fn
接受的参数分别为:字段的值和名称(field_value, field_name)
- 字段的值通过
value.*field_pointer
得到,其中field_pointer
是成员指针
- 字段的值通过
ForEachTuple
的实现中还用到了 静态断言 (static assert) 检查,具体见 代码- 检查
StructSchema
是否定义了字段信息 - 检查每个字段的信息 是否都包含了位置和名称
- 检查
使用样例代码链接
具体使用时,也是需要两步:
- 使用下面两个参数静态定义字段信息(名称、位置)
DEFINE_STRUCT_SCHEMA
和DEFINE_STRUCT_FIELD
- 调用
ForEachField
并传入 映射方法(泛型 functor 或泛型 lambda 表达式),对所有字段调用这个函数
// define schema (partial) DEFINE_STRUCT_SCHEMA(SimpleStruct,DEFINE_STRUCT_FIELD(int_, "int"),DEFINE_STRUCT_FIELD(string_, "string"));// use ForEachTuple ForEachField(SimpleStruct{1, "hello static reflection"},[](auto&& field, auto&& name) {std::cout << name << ": "<< field << std::endl;});// output: // int: 1 // string: hello static reflection
静态反射过程中,最核心 的地方:传入 ForEachField
的可调用对象 fn
,通过 编译时多态 针对不同 字段类型 选择不同的转换操作:
- 针对
int
类型字段,ForEachField
调用fn(simple.int_, "int")
- 针对
std::string
类型字段,ForEachField
调用fn(simple.string_, "string")
2019/2/19 补充 如果需要针对不同类型使用不同的操作,可以考虑 重载 lambda 表达式(提案 p0051r3): ForEachField(SimpleStruct{1, "hello static reflection"}, overload( [](int field, const char* name) { std::cout << "i " << name << ": " << field << std::endl; }, [](const std::string& field, const char* name) { std::cout << "s " << name << ": " << field.c_str() << std::endl; })); 2019/1/11 补充(by fredwyan) C++ 11 不支持 泛型 lambda 表达式,可以使用 泛型 functor 代替传入
ForEachField
的可调用对象,从而实现 编译时多态: struct GenericFunctor { // ... data members template <typename Field, typename Name> void operator()(Field&& field, Name&& name) { std::cout << name << ": " << field << std::endl; } }; // use ForEachTuple ForEachField(SimpleStruct{1, "hello static reflection"}, GenericFunctor{/* ... context data */});
最后 ForEachField(SimpleStruct{...}, [](...) { ... });
经过 内联 (inline) 后,生成的代码非常简单:
{SimpleStruct simple{1, "hello static reflection"};std::cout << "int" << ": " << simple.int_ << std::endl;std::cout << "string" << ": " << simple.string_ << std::endl; }
基于静态反射的开源库:
- https://github.com/qicosmos/iguana
使用编译时静态反射,相对于运行时动态反射,有许多优点:
7. 编译器生成 序列化/反序列化 代码
代码链接
基于 ForEachField
,我们可以实现 通用 的结构体序列化/反序列化函数:
template <typename T> struct adl_serializer<T, std::enable_if_t<::has_schema<T>>> {template <typename BasicJsonType>static void to_json(BasicJsonType& j, const T& value) {ForEachField(value, [&j](auto&& field, auto&& name) {j[name] = field;});}template <typename BasicJsonType>static void from_json(const BasicJsonType& j, T& value) {ForEachField(value, [&j](auto&& field, auto&& name) {// ignore missing field of optionalif (::is_optional_v<decltype(field)> &&j.find(name) == j.end())return;j.at(name).get_to(field);});} };
- 和§ 人工手写 序列化/反序列化 代码的代码类似:
- 使用
j[name] = field
序列化 - 使用
j.at(name).get_to(field)
反序列化 - 针对可选字段检查字段是否存在,不存在则跳过(C++ 17 还可以使用
if constexpr
实现选择性编译)
- 使用
- 关于如何使用
nlohmann::adl_serializer
扩展自定义类型的序列化/反序列化操作,参考 How do I convert third-party types? | nlohmann/json - 使用的两个简单的变量模板(variable template),具体见代码
has_schema
检查是否定义了:StructSchema
is_optional_v
检查字段类型是不是可选参数
对于需要进行序列化/反序列化的自定义结构体,我们只需要使用下面这两个参数声明 其字段信息即可 —— 不需要为每个结构体写一遍 to_json
/from_json
逻辑了:
DEFINE_STRUCT_SCHEMA
和 DEFINE_STRUCT_FIELD
DEFINE_STRUCT_SCHEMA(SimpleStruct,DEFINE_STRUCT_FIELD(bool_, "_bool"),DEFINE_STRUCT_FIELD(int_, "_int"),DEFINE_STRUCT_FIELD(double_, "_double"),DEFINE_STRUCT_FIELD(string_, "_string"),DEFINE_STRUCT_FIELD(optional_, "_optional"));DEFINE_STRUCT_SCHEMA(NestedStruct,DEFINE_STRUCT_FIELD(nested_, "_nested"),DEFINE_STRUCT_FIELD(vector_, "_vector"));
于是,编译器就可以生成和 § 人工手写 序列化/反序列化 代码 一致的代码了。
图片来源:Declarative Programming And The Web
8. 写在最后
不依赖于第三方库,只需要简单的声明,没有额外的运行时开销 —— 这就是 现代 C++ 元编程。
掌握 C++ 元编程,自己打造工具,解放生产力,告别搬砖的生活!
本文来源:腾讯技术工程(ID:Tencent_TEG)
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 软考高级信息系统项目管理师备考经验分享
超详细的信息系统项目管理师备考经验分享,让你一次通过! 信息系统项目管理师作为软考高级资格考试,每年通过率20%不到,所以报考这个考试的朋友,一定要好好复习,争取一次考过。 软考考试说难也不难,简单不算不上,但用心复习一般都能有百分之八十的机率能过。 所以在这里…...
2024/5/8 17:26:49 - http实现浏览器端大文件分片上传
需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在500M内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以501M来进行限制。第一步:前端修改由于项目使用的是BJUI前端框架,并没有使用框架本身的文件上传控件,而使用的基于jQuery的Up…...
2024/5/9 2:38:30 - ZOJ 1789 The Suspects(经典并查集)
The Suspects题目传送门~Time Limit: 2000 msMemory Limit: 65536 KBSevere acute respiratory syndrome (SARS), an atypical pneumonia of unknown aetiology, was recognized as a global threat in mid-March 2003. To minimize transmission to others, the best strategy…...
2024/5/5 12:11:49 - vue $emit、$on、$refs简介
1、$emit触发当前实例上的事件。附加参数都会传给监听器回调ex: 子组件调用父组件的方法并传递数据 注意:子组件标签中的时间也不区分大小写要用“-”隔开子组件:<template><button @click="emitEvent">点击我</button> </template> <s…...
2024/5/3 7:47:32 - RocketMQ docker
1. 安装 Namesrv docker run -d -p 9876:9876 -v F:/testing/rocketmq/logs:/root/logs -v F:/testing/rocketmq/namesrv/store:/root/store --name rmqnamesrv -e "MAX_POSSIBLE_HEAP=100000000" rocketmqinc/rocketmq:4.4.0 sh mqnamesrv2. 安装 broker 服务器创建…...
2024/4/24 12:21:39 - 全球顶级的五大网络组织,中国首次上榜,他们到底牛到什么程度?
在我心里,“黑客”是一个神奇的人物,“黑客”组织更是一个超级神秘的团队。他们拥有世界上最先进的计算机技术。今天让我来冒险揭开全球顶级的五大网络组织,中国也是首次上榜,那么他们的技术他们到底牛到什么程度?笔者来揭秘一下。1、匿名者(澳大利亚)创立时间2003年,总…...
2024/5/3 7:35:25 - JavaFX实验——疫情高危人群识别
疫情期间,人员流动很容易额外添加疫情防范的难度,请根据以下信息来完成模拟扫码测温界面的代码编写。 设计一个名为Migrant的流动人员类,包括下述内容:①一个名为name的String类型私有数据域,以及对应的get/set方法; ②一个名为areaFrom的int类型私有数据域,以及对应的get…...
2024/5/3 7:02:32 - 微信认证授权详细代码示例
微信认证授权详细代码示例 背景 公司有个H5页面分享到微信之后不显示图片,且样式跟其他应用的分享卡片存在差异.后来发现是因为没有调用微信统一的分享接口.下面就是我踩坑踩出来的心得. 开发前必须知道的东西要有一个公众号,有了公众号就会有appId和appsecret. 要有一个外网可…...
2024/5/8 2:54:05 - ftp上传工具下载,6款最值得推荐的Windows端ftp上传工具下载软件
第一款:iis7服务器管理软件 它是一款免费而且专业的ftp多站点管理软件,而且可以实现ftp定时备份的功能。除此之外,它还是一款强大的服务器集成管理器,可以批量管理windows及linux服务器及vps。 下载地址:iis7服务器批量管理 效果图:第二款:FileZilla 这是一个完全免费、开…...
2024/5/2 23:24:00 - dfuse for EOSIO 架构剖析:架构总览
自从我们发布了开源和二进制程序“ dfuse for EOSIO”以来,许多人请我们来解释它其中所包含的基础架构。 Alex 会带你了解“ dfuse for EOSIO”的17个微服务以及它们各自的作用。 每一个微服务都会有专门的视频讲解,所以请关注我们,跟进我们的更新!dfuse for EOSIO 架构剖析…...
2024/4/16 16:16:57 - 15、16周实验
15.嗜睡猫问题 题目描述: 众所周知,TT家里有一只魔法喵。这只喵十分嗜睡。 一睡就没有白天黑夜。喵喵一天可以睡多次!!每次想睡多久就睡多久 喵睡觉的时段是连续的,即一旦喵喵开始睡觉了, 就不能被打扰,不然喵会咬人哒 可以假设喵喵必须要睡眠连续不少于 A 个小时,即一…...
2024/5/3 3:44:17 - ftp工具,小白也轻松学会的ftp工具,服务器工具
我认为IIS7服务器管理工具是最好用的,我一直都在自己用,功能比较强大,FTP用于在FTP服务器和FTP客户端之间上传和下载文件,它最优秀的功能就是可以批量管理,可以将文件从一个主机传输到另一个主机。 IIS7服务管理器,它最优秀的功能就是可以批量管理,适用于Windows系统,Li…...
2024/5/3 7:16:08 - VS2017 MFC项目设置printf输出到Console窗口
开发VC程序时经常通过printf打印一些调试信息,仅在VS2017测试通过,其它版本没试过一、打开VS工程。二、项目名称上点击鼠标右键选择 属性,打开项目的属性页。三、在配置属性中,生成事件->生成后事件在命令行的右边空白处添加“editbin /SUBSYSTEM:CONSOLE $(OUTDIR)\$(P…...
2024/5/3 5:33:02 - 实现算法过程中的小经验——关于fflush(stdin)的使用
关于fflush(stdin)的使用 #include <stdio.h> #include <stdlib.h>int main() {int i;for(;;) {fputs("Please input an integer: ", stdout);scanf("%d", &i);printf("%d\n", i);}return 0; } 这个程序首先会提示用户输入一个整…...
2024/4/24 12:21:41 - 2.19 货币兑换-添加操作
2.19.1 添加启动2.19.2启动结果2.19.3设置输入兑换金额页面2.19.4设置初始金额2.19.5 设置持有货币页面2.19.6 设置持有货币属性2.19.7 设置持有货币成功2.19.8 设置兑换货币2.19.9 设置兑换货币2.19.10 设置兑换货币成功2.19.11 设置兑换2.19.12 点击兑换,页面切换成功2.19.1…...
2024/5/3 2:09:40 - Linux系统中的文件权限管理(1) ---查看、读取、文件保留权限(umask)、改变文件的权限、所有人和所有组
一、文件权限查看及读取1.文件权限文件权限存在的意义系统最底层安全设定方法之一保证文件可以被可用的用户做相应操作2.权限查看ls -l file ##查看文件权限ls -ld dir ##查看目录权限3.权限的读取文件的属性被叫做文件的元数据(meta data)一种元数据…...
2024/5/3 11:47:54 - 奇偶法解决哲学家进餐问题
奇偶法解决哲学家进餐问题前言 前言 最近在写操作系统的课设,其中有一道关于进程同步的哲学家进餐问题,课件上的做法有两种,一种是“尝试法”,尝试是否有“哲学家”占用资源,另一种则是AND信号量,保证一次拿完两根筷子。 解决此类可能引起死锁问题的方法很多,这里采用奇…...
2024/4/24 12:21:35 - 程序运行报错“AttributeError: module scipymisc has no attribute imread”的解决办法
运行程序报错 AttributeError: module ‘scipy.misc’ has no attribute ‘imread’解决办法:因为安装的scipy版本过高,需要重新安装scipy.pip install scipy==1.2.1安装好之后再运行就不会报错啦~...
2024/4/24 12:21:34 - JAVA对SQlite数据库的操作
java项目十 JDBC数据库操作 项目学习内容:数据库的连接和增删改查操作 一、数据库操作准备 (一)sqlites数据安装与调试 1、解压 sqlite-dll-win64-x64-3210000 sqlite-tools-win32-x86-3210000 (win10 可以不解压) 2、将 sqlite-tools-win32-x86-3210000 文件夹所在路径添…...
2024/5/7 0:29:15 - TCP三次握手原理
在众多的网络协议中,TCP协议占据着举足轻重的地位,你知道什么是TCP协议吗?一、TCP协议TCP(Transmission Control Protoco)协议属于计算机网络体系中的运输层。运输层的任务是负责向主机中应用层进程之间的通信提供通用的数据传输服务。所以可以通俗理解TCP协议就是进程间数据…...
2024/4/24 12:21:32
最新文章
- 数据库选择小结
第一次测试 1. (单选题, 2分)【单选题】在E-R模型中,实体间的联系用( C)图标来表示。 A. 矩形B. 直线C. 菱形D. 椭圆 2. (单选题, 2分)【单选题】设R是一个关系模式,如果R中的每个属性都是不可分解的,则称R属于&…...
2024/5/9 5:13:05 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/5/7 10:36:02 - 正交投影的矩阵(基变换与过渡矩阵的例子)
在 n n n维欧几里得空间 V V V中,有子空间 W W W. 如果用自然基 ( e i ) 1 ≤ i ≤ n (\mathbf{e}_i)_{1\leq i \leq n} (ei)1≤i≤n,设 W s p a n ( w 1 , … , w d ) ( 0 < d < n ) W\mathrm{span}(w_1, \ldots, w_d)\; (0< d < n) W…...
2024/5/6 19:09:46 - C++ //练习 11.14 扩展你在11.2.1节练习(第378页)中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名和生日。
C Primer(第5版) 练习 11.14 练习 11.14 扩展你在11.2.1节练习(第378页)中编写的孩子姓到名的map,添加一个pair的vector,保存孩子的名和生日。 环境:Linux Ubuntu(云服务器&#x…...
2024/5/5 8:38:52 - WKWebView的使用
一、简介 在iOS中,WKWebView是WebKit框架提供的一个用于展示网页内容的控件,相比UIWebView有更好的性能和功能。 以下是在iOS中使用WKWebView的基本步骤: 1.1 导入WebKit框架 import WebKit1.2 创建WKWebView实例 let webView WKWebVie…...
2024/5/8 18:01:10 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/5/8 6:01:22 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/5/7 9:45:25 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/5/4 23:54:56 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/5/9 4:20:59 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/5/4 23:54:56 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/5/4 23:55:05 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/5/4 23:54:56 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/5/7 11:36:39 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/5/4 23:54:56 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/5/6 1:40:42 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/5/4 23:54:56 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/5/8 20:48:49 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/5/7 9:26:26 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/5/4 23:54:56 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/5/8 19:33:07 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/5/5 8:13:33 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/5/8 20:38:49 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/5/4 23:54:58 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/5/6 21:42:42 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/5/4 23:54:56 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57