【快速上手ProtoBuf】proto 3 语法详解

1 🍑字段规则🍑

消息的字段可以⽤下⾯⼏种规则来修饰:

  • singular :消息中可以包含该字段零次或⼀次(不超过⼀次)。 proto3 语法中,字段默认使⽤该规则。
  • repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。

比如:

syntax = "proto3";
package contacts;
message PeopleInfo 
{
  string name = 1; 
  int32 age = 2; 
  repeated string phone_numbers = 3;
}

2 🍑消息类型的定义与使用🍑

2.1 🍎定义🍎

在单个 .proto ⽂件中可以定义多个消息体,且⽀持定义嵌套类型的消息(任意多层)。每个消息体中的字段编号可以重复。

嵌套写法:

syntax = "proto3";
package contacts;
message PeopleInfo 
{
  string name = 1; 
  int32 age = 2; 
  message Phone 
  {
    string number = 1;
  }
}

⾮嵌套写法:

syntax = "proto3";
package contacts;
message Phone 
{
  string number = 1;
}
message PeopleInfo 
{
  string name = 1; 
  int32 age = 2; 
}

2.2 🍎使用🍎

消息类型可作为字段类型使⽤:

syntax = "proto3";
package contacts;
message PeopleInfo 
{
  string name = 1; 
  int32 age = 2; 
  message Phone 
  {
    string number = 1; 
  }
 repeated Phone phone = 3; 
}

可导⼊其他 .proto ⽂件的消息并使⽤
例如 Phone 消息定义在 phone.proto ⽂件中:

syntax = "proto3";
package phone;
message Phone 
{
  string number = 1; 
}

contacts.proto 中的 PeopleInfo 使⽤ Phone 消息:

syntax = "proto3";
package contacts;
import "phone.proto"; // 使⽤ import 将 phone.proto ⽂件导⼊进来 !!!
message PeopleInfo 
{
  string name = 1; 
  int32 age = 2; 
 // 引⼊的⽂件声明了package,使⽤消息时,需要⽤ ‘命名空间.消息类型’ 格式 
 repeated phone.Phone phone = 3; 
}

注:在 proto3 ⽂件中可以导⼊ proto2 消息类型并使⽤它们,反之亦然。


3 🍑通讯录版本v2🍑

contacts.proto:

syntax = "proto3";
package contacts;

// 联系⼈
message PeopleInfo 
{
  string name = 1; // 姓名
  int32 age = 2; // 年龄
  message Phone 
  {
    string number = 1; // 电话号码
  }
  repeated Phone phone = 3; // 电话
}
// 通讯录
message Contacts 
{
  repeated PeopleInfo contacts = 1;
}

接着进⾏⼀次编译:

 protoc --cpp_out=. contacts.proto

3.1 🍎通讯录v2的写入实现🍎

main.cc:

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
/**
 * 新增联系⼈
 */
void AddPeopleInfo(PeopleInfo *people_info_ptr)
{
    cout << "-------------新增联系⼈-------------" << endl;
    cout << "请输⼊联系⼈姓名: ";
    string name;
    getline(cin, name);
    people_info_ptr->set_name(name);
    cout << "请输⼊联系⼈年龄: ";
    int age;
    cin >> age;
    people_info_ptr->set_age(age);
    cin.ignore(256, '\n');//遇到'\n'结束,或者接受到的数据大于256
    for (int i = 1;; i++)
    {
        cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增): ";
        string number;
        getline(cin, number);
        if (number.empty())
        {
            break;
        }
        PeopleInfo_Phone *phone = people_info_ptr->add_phone();
        phone->set_number(number);
    }
    cout << "-----------添加联系⼈成功-----------" << endl;
}
int main(int argc, char *argv[])
{
    // GOOGLE_PROTOBUF_VERIFY_VERSION 宏: 验证没有意外链接到与编译的头⽂件不兼容的库版
    // 本。如果检测到版本不匹配,程序将中⽌。注意,每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使
    // ⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法,但不是绝对必要的。
    GOOGLE_PROTOBUF_VERIFY_VERSION;
    if (argc != 2)
    {
        cerr << "Usage: " << argv[0] << " CONTACTS_FILE" << endl;
        return -1;
    }
    Contacts contacts;

    // 先读取已存在的 contacts
    fstream input(argv[1], ios::in | ios::binary);
    if (!input)
    {
        cout << argv[1] << ": File not found. Creating a new file." << endl;
    }
    else if (!contacts.ParseFromIstream(&input))
    {
        cerr << "Failed to parse contacts." << endl;
        input.close();
        return -1;
    }
    // 新增⼀个联系⼈
    AddPeopleInfo(contacts.add_contacts());
    // 向磁盘⽂件写⼊新的 contacts
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output))
    {
        cerr << "Failed to write contacts." << endl;
        input.close();
        output.close();
        return -1;
    }
    input.close();
    output.close();
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

注意点代码中有解释。

验证:
在这里插入图片描述
查看⼆进制⽂件:
在这里插入图片描述

3.2 🍎通讯录v2的读取实现🍎

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
/**
 * 打印联系⼈列表
 */
void PrintfContacts(const Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
        const PeopleInfo &people = contacts.contacts(i);
        cout << "------------联系⼈" << i + 1 << "------------" << endl;
        cout << "姓名:" << people.name() << endl;
        cout << "年龄:" << people.age() << endl;
        int j = 1;
        for (const PeopleInfo_Phone &phone : people.phone())
        {
            cout << "电话" << j++ << ": " << phone.number() << endl;
        }
    }
}
int main(int argc, char *argv[])
{

    GOOGLE_PROTOBUF_VERIFY_VERSION;
    if (argc != 2)
    {
        cerr << "Usage: " << argv[0] << "CONTACTS_FILE" << endl;
        return -1;
    }
    // 以⼆进制⽅式读取 contacts
    Contacts contacts;
    fstream input(argv[1], ios::in | ios::binary);
    if (!contacts.ParseFromIstream(&input))
    {
        cerr << "Failed to parse contacts." << endl;
        input.close();
        return -1;
    }
    // 打印 contacts
    PrintfContacts(contacts);
    input.close();
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

验证:
在这里插入图片描述

另⼀种验证⽅法--decode
我们可以⽤ protoc -h 命令来查看 ProtoBuf 为我们提供的所有命令 option。其中 ProtoBuf 提供⼀个命令选项 --decode ,表⽰从标准输⼊中读取给定类型的⼆进制消息,并将其以⽂本格式写⼊标准输出。 消息类型必须在 .proto ⽂件或导⼊的⽂件中定义。

在这里插入图片描述


4 🍑enum 类型🍑

4.1 🍎定义规则🍎

语法⽀持我们定义枚举类型并使⽤。在.proto⽂件中枚举类型的书写规范为:

  • 枚举类型名称使⽤驼峰命名法,⾸字⺟⼤写。 例如: MyEnum
  • 常量值名称全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0;

我们可以定义⼀个名为 PhoneType 的枚举类型,定义如下:

enum PhoneType 
{
 MP = 0; // 移动电话
 TEL = 1; // 固定电话
}

要注意枚举类型的定义有以下⼏种规则:

  1. 0 值常量必须存在,且要作为第⼀个元素。这是为了与 proto2 的语义兼容:第⼀个元素作为默认值,且值为 0。
  2. 枚举类型可以在消息外定义,也可以在消息体内定义(嵌套)。
  3. 枚举的常量值在 32 位整数的范围内,但因负值⽆效因⽽不建议使⽤(与编码规则有关)。

4.2 🍎注意事项🍎

将两个 具有相同枚举值名称 的枚举类型放在单个 .proto ⽂件下测试时,编译后会报错:某某某常量已经被定义!所以这⾥要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
  • 单个 .proto ⽂件下,最外层枚举类型和嵌套枚举类型,不算同级。
  • 多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都未声明 package,每个 proto ⽂件中的枚举类型都在最外层,算同级。
  • 多个 .proto ⽂件下,若⼀个⽂件引⼊了其他⽂件,且每个⽂件都声明了 package,不算同级。

5 🍑Any 类型🍑

字段还可以声明为 Any 类型,可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。Any 类型的字段也⽤ repeated 来修饰。
Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include ⽬录下查找所有google 已经定义好的 .proto ⽂件。

在这里插入图片描述

此时我们可以再升级通讯录版本:
.proto文件:

syntax = "proto3";
package contacts;
import "google/protobuf/any.proto"; // 引⼊ any.proto ⽂件
// 地址
message Address
{
  string home_address = 1; // 家庭地址
  string unit_address = 2; // 单位地址
}
// 联系⼈
message PeopleInfo 
{
  string name = 1; // 姓名
  int32 age = 2; // 年龄
 message Phone 
 {
   string number = 1; // 电话号码
   enum PhoneType 
   {
     MP = 0; // 移动电话
     TEL = 1; // 固定电话
   }
   PhoneType type = 2; // 类型
 }
 repeated Phone phone = 3; // 电话
 google.protobuf.Any data = 4;
}
// 通讯录
message Contacts 
{
 repeated PeopleInfo contacts = 1;
}

使用protoc编译器编译后:

// 新⽣成的 Address 类
class Address final : public ::PROTOBUF_NAMESPACE_ID::Message 
{
public:
 using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
 void CopyFrom(const Address& from);
 using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
 void MergeFrom( const Address& from) 
 {
 	Address::MergeImpl(*this, from);
 }
 // string home_address = 1;
 void clear_home_address();
 const std::string& home_address() const;
 template <typename ArgT0 = const std::string&, typename... ArgT>
 void set_home_address(ArgT0&& arg0, ArgT... args);
 std::string* mutable_home_address();
 PROTOBUF_NODISCARD std::string* release_home_address();
 void set_allocated_home_address(std::string* home_address);
 // string unit_address = 2;
 void clear_unit_address();
 const std::string& unit_address() const;
 template <typename ArgT0 = const std::string&, typename... ArgT>
 void set_unit_address(ArgT0&& arg0, ArgT... args);
 std::string* mutable_unit_address();
 PROTOBUF_NODISCARD std::string* release_unit_address();
 void set_allocated_unit_address(std::string* unit_address);
};
// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message 
{
public:
 // .google.protobuf.Any data = 4;
 bool has_data() const;
 void clear_data();
 const ::PROTOBUF_NAMESPACE_ID::Any& data() const;
 PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_data();
 ::PROTOBUF_NAMESPACE_ID::Any* mutable_data();
 void set_allocated_data(::PROTOBUF_NAMESPACE_ID::Any* data);
};

上述的代码中,对于 Any 类型字段:设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法可以使⽤ mutable_ ⽅法,返回值为Any类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改。

之前讲过,我们可以在 Any 字段中存储任意消息类型,这就要涉及到任意消息类型 和 Any 类型的互转。这部分代码就在 Google为我们写好的头⽂件 any.pb.h 中。对 any.pb.h 部分代码展⽰:

class PROTOBUF_EXPORT Any final : public ::PROTOBUF_NAMESPACE_ID::Message 
{
 bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message) 
 {
 ...
 }
 bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const 
 {
 ...
 }
 template<typename T> bool Is() const 
 {
 return _impl_._any_metadata_.Is<T>();
 }
};

上述代码中:

  • 使⽤ PackFrom() ⽅法可以将任意消息类型转为 Any 类型。
  • 使⽤ UnpackTo() ⽅法可以将 Any 类型转回之前设置的任意消息类型。
  • 使⽤ Is() ⽅法可以⽤来判断存放的消息类型是否为 typename T。

6 🍑oneof 类型🍑

如果消息中有很多可选字段, 并且将来同时只有⼀个字段会被设置, 那么就可以使⽤ oneof 加强这个⾏为,也能有节约内存的效果。

注意:

  • 可选字段中的字段编号,不能与⾮可选字段的编号冲突。
  • 不能在 oneof 中使⽤ repeated 字段。
  • 将来在设置 oneof 字段中值时,如果将 oneof 中的字段设置多个,那么只会保留最后⼀次设置的成员,之前设置的 oneof 成员会⾃动清除。

contacts.pb.h 更新的部分代码展⽰:

// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message 
{
 enum OtherContactCase 
 {
 kQq = 5,
 kWeixin = 6,
 OTHER_CONTACT_NOT_SET = 0,
 };
 // string qq = 5;
 bool has_qq() const;
 void clear_qq();
 const std::string& qq() const;
 template <typename ArgT0 = const std::string&, typename... ArgT>
 void set_qq(ArgT0&& arg0, ArgT... args);
 std::string* mutable_qq();
 PROTOBUF_NODISCARD std::string* release_qq();
 void set_allocated_qq(std::string* qq);
  // string weixin = 6;
 bool has_weixin() const;
 void clear_weixin();
 const std::string& weixin() const;
 template <typename ArgT0 = const std::string&, typename... ArgT>
 void set_weixin(ArgT0&& arg0, ArgT... args);
 std::string* mutable_weixin();
 PROTOBUF_NODISCARD std::string* release_weixin();
 void set_allocated_weixin(std::string* weixin);
 void clear_other_contact();
 OtherContactCase other_contact_case() const;
};

上述的代码中,对于 oneof 字段:

  • 会将 oneof 中的多个字段定义为⼀个枚举类型。
  • 设置和获取:对 oneof 内的字段进⾏常规的设置和获取即可,但要注意只能设置⼀个。如果设置多个,那么只会保留最后⼀次设置的成员。
  • 清空 oneof 字段:clear_ ⽅法
  • 获取当前设置了哪个字段:_case ⽅法

7 🍑map 类型🍑

语法⽀持创建⼀个关联映射字段,也就是可以使⽤ map 类型去声明字段类型,格式为:

map<key_type, value_type> map_field = N;

要注意的是:

  • key_type 是除了 float 和 bytes 类型以外的任意标量类型。 value_type 可以是任意类型。
  • map 字段不可以⽤ repeated 修饰。
  • map 中存⼊的元素是⽆序的。

contacts.pb.h 更新的部分代码展⽰:

// 更新的 PeopleInfo 类
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message 
{
 // map<string, string> remark = 7;
 int remark_size() const;
 void clear_remark();
 const ::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >&
 remark() const;
 ::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >*
 mutable_remark();
};

上述的代码中,对于Map类型的字段:

  • 清空map: clear_ ⽅法。
  • 设置和获取:获取⽅法的⽅法名称与⼩写字段名称完全相同。设置⽅法为 mutable_ ⽅法,返回值为Map类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改。

8 🍑默认值🍑

反序列化消息时,如果被反序列化的⼆进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:

  • 对于字符串,默认值为空字符串。
  • 对于字节,默认值为空字节。
  • 对于布尔值,默认值为 false。
  • 对于数值类型,默认值为 0。
  • 对于枚举,默认值是第⼀个定义的枚举值, 必须为 0。
  • 对于消息字段,未设置该字段。它的取值是依赖于语⾔。
  • 对于设置了 repeated 的字段的默认值是空的( 通常是相应语⾔的⼀个空列表 )。对于 消息字段 、 oneof字段 和 any字段 ,C++ 和 Java 语⾔中都有 has_ ⽅法来检测当前字段是否被设置。

9 🍑更新消息🍑

9.1 🍎更新规则🍎

如果现有的消息类型已经不再满⾜我们的需求,例如需要扩展⼀个字段,在不破坏任何现有代码的情况下更新消息类型⾮常简单。遵循如下规则即可:

  • 禁⽌修改任何已有字段的字段编号。
  • 若是移除⽼字段,要保证不再使⽤移除字段的字段编号。正确的做法是保留字段编号(reserved),以确保该编号将不能被重复使⽤。不建议直接删除或注释掉字段。
  • int32, uint32, int64, uint64 和 bool 是完全兼容的。可以从这些类型中的⼀个改为另⼀个,⽽不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采⽤与 C++ ⼀致的处理⽅案(例如,若将 64 位整数当做 32 位进⾏读取,它将被截断为 32 位)。
  • sint32 和 sint64 相互兼容但不与其他的整型兼容。
  • string 和 bytes 在合法 UTF-8 字节前提下也是兼容的。
  • bytes 包含消息编码版本的情况下,嵌套消息与 bytes 也是兼容的。
  • fixed32 与 sfixed32 兼容, fixed64 与 sfixed64兼容。
  • enum 与 int32,uint32, int64 和 uint64 兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语⾔采⽤不同的处理⽅案:例如,未识别的 proto3 枚举类型会被保存在消息中,但是当消息反序列化时如何表⽰是依赖于编程语⾔的。整型字段总是会保持其的值。
  • oneof:
    • 将⼀个单独的值更改为 新 oneof 类型成员之⼀是安全和⼆进制兼容的。
    • 若确定没有代码⼀次性设置多个值那么将多个字段移⼊⼀个新 oneof 类型也是可⾏的。
    • 将任何字段移⼊已存在的 oneof 类型是不安全的。

9.2 🍎保留字段 reserved🍎

如果通过 删除 或 注释掉 字段来更新消息类型,未来的⽤⼾在添加新字段时,有可能会使⽤以前已经存在,但已经被删除或注释掉的字段编号。将来使⽤该 .proto 的旧版本时的程序会引发很多问题:数据损坏、隐私错误等等。

确保不会发⽣这种情况的⼀种⽅法是:使⽤ reserved 将指定字段的编号或名称设置为保留项 。当我们再使⽤这些编号或名称时,protocol buffer 的编译器将会警告这些编号或名称不可⽤。

9.3 🍎未知字段🍎

未知字段:解析结构良好的 protocol buffer 已序列化数据中的未识别字段的表⽰⽅式。例如,当旧程序解析带有新字段的数据时,这些新字段就会成为旧程序的未知字段。

本来,proto3 在解析消息时总是会丢弃未知字段,但在 3.5 版本中重新引⼊了对未知字段的保留机制。所以在 3.5 或更⾼版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果中。

9.4 🍎前后兼容性🍎

  • 向前兼容:⽼模块能够正确识别新模块⽣成或发出的协议。
  • 向后兼容:新模块也能够正确识别⽼模块⽣成或发出的协议。

前后兼容的作⽤:当我们维护⼀个很庞⼤的分布式系统时,由于你⽆法同时 升级所有 模块,为了保证在升级过程中,整个系统能够尽可能不受影响,就需要尽量保证通讯协议的“向后兼容”或“向前兼容”。


10 🍑选项 option🍑

.proto ⽂件中可以声明许多选项,使⽤ option 标注。选项能影响 proto 编译器的某些处理⽅式。

10.1 🍎常用选项列举🍎

  • optimize_for : 该选项为⽂件选项,可以设置 protoc 编译器的优化级别,分别为 SPEEDCODE_SIZELITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译 .proto ⽂件后⽣成的代码内容不同。
    • SPEED : protoc 编译器将⽣成的代码是⾼度优化的,代码运⾏效率⾼,但是由此⽣成的代码编译后会占⽤更多的空间。 SPEED 是默认选项。
    • CODE_SIZE : proto 编译器将⽣成最少的类,会占⽤更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运⾏效率较低。这种⽅式适合⽤在包含⼤量的.proto⽂件,但并不盲⽬追求速度的应⽤中。
    • LITE_RUNTIME : ⽣成的代码执⾏效率⾼,同时⽣成代码编译后的所占⽤的空间也是⾮常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供 encoding+序列化 功能,所以我们在链接 BP 库时仅需链接libprotobuf-lite,⽽⾮libprotobuf。这种模式通常⽤于资源有限的平台,例如移动⼿机平台中。
option optimize_for = LITE_RUNTIME;
  • allow_alias : 允许将相同的常量值分配给不同的枚举常量,⽤来定义别名。该选项为枚举选项。
    举个例⼦:
enum PhoneType 
{
 option allow_alias = true;
 MP = 0;
 TEL = 1;
 LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/455326.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

二,几何相交---4,BO算法---(3)数据结构

数据结构分两块&#xff0c;一个是某一时间状态的局部相交线段。一个是事件队列&#xff0c;是某一时刻局部相交线段的集合。

【Java设计模式】十九、中介者模式

文章目录 1、中介者模式2、案例3、总结 1、中介者模式 如图&#xff1a; 同事类之间关联较多时&#xff0c;整体出现网状结构&#xff0c;耦合度极高。一个对象一变动&#xff0c;好多对象都得改。若变为右边的星形结构&#xff0c;则一个类的变动&#xff0c;只影响自身与中介…

Vulnhub - DevGuru

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog DevGuru 靶机下载地址&#xff1a;DevGuru: 1 ~ VulnHub 目录 0x01 信息收集 0x02 Web漏洞利用 - W…

Ansible管理主机的清单------------inventory

一、 Ansible组成 INVENTORY&#xff1a;Ansible管理主机的清单 /etc/ansible/hosts 需要管理的服务清单,(将你需要管理的主机 、地址 或者名字 写入此文件) MODULES&#xff1a;Ansible执行命令的功能模块&#xff0c;多数为内置核心模块&#xff0c;也可自定义 PLUGINS&…

掌握抽象基础之20个必备原则,看完你还不会,你打我

抽象基础之20个必备原则 1. 面向对象编程&#xff08;OOP&#xff09;中抽象原则背后的基本思想是什么&#xff1f;2.抽象和封装的区别&#xff1f;3.提供一个现实生活中说明抽象的例子4.在编程语言中如何实现抽象&#xff1f;5.定义抽象类6.提供一个抽象类的真实世界场景7.解释…

大数据赋能,能源企业的智慧转型之路

在数字洪流中&#xff0c;大数据已经成为推动产业升级的新引擎。特别是在能源行业&#xff0c;大数据的应用正引领着一场深刻的智慧转型。今天&#xff0c;我们就来探讨大数据如何在能源企业中发挥其独特的魅力&#xff0c;助力企业提效降本&#xff0c;实现绿色发展。 动态监控…

学生时期学习资源同步-1 第一学期结业考试题5

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载

Java推荐算法——特征加权推荐算法(以申请学校为例)

加权推荐算法 文章目录 加权推荐算法1.推荐算法的简单介绍2.加权推荐算法详细介绍3.代码实现4.总结 1.推荐算法的简单介绍 众所周知&#xff0c;推荐算法有很多种&#xff0c;例如&#xff1a; 1.加权推荐&#xff1a;分为简单的特征加权&#xff0c;以及复杂的混合加权。主要…

软考高级:系统工程方法(霍尔三维结构、切克兰德方法等)概念和例题

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

Python+Selenium 自动化 - 开发环境搭建

PythonSelenium 自动化 - 开发环境搭建 一、Python 下载与安装二、Geany 代码编辑工具下载与安装三、代码运行演示 一、Python 下载与安装 Python 官网&#xff1a;https://www.python.org/ 首先在官网下安装包。 建议选择稳定版本&#xff0c;我这里下载的是 3.12.2 版本。 …

C++Qt学习——添加资源文件

目录 1、创建好了文件之后&#xff0c;在左边空白处按下CtrlN&#xff0c;创建Qt 以及Qt Resource File 2、写入名称&#xff0c;点击下一步 3、可以发现已经创建好啦。 4、点击Add Prefix 5、写上前缀&#xff0c;最好加上斜杠 6、选择提前放好的图片或者icon 7、发…

2024最新修复版独立付费进群系统源码全开源

1、修复SQL表 2、修复支付文件 3、修复支付图标不显示 4、修复定位、分销逻辑、抽成逻辑 5、新增支持源支付、易支付的支付接口 6、修复官方微信、支付宝支付接口文件 本来早就可以完工的&#xff0c;被99ai群里的几个傻逼耽搁了&#xff0c;做好自己就行了&#xff0c;这…

Learn OpenGL 09 投光物+多光源

将光投射(Cast)到物体的光源叫做投光物(Light Caster)。 投光物主要分为点光源&#xff0c;聚光灯&#xff0c;平行光。 平行光 当一个光源处于很远的地方时&#xff0c;来自光源的每条光线就会近似于互相平行。不论物体和/或者观察者的位置&#xff0c;看起来好像所有的光都…

C++:vector类

vector的介绍及使用 1. vector 是表示可变大小数组的序列容器。 2. 就像数组一样&#xff0c; vector 也采用的连续存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c;它的大小是可以动态改变的…

解锁App推广新姿势:Xinstall二维码信息获取秘籍

在移动互联网时代&#xff0c;App推广已成为各大企业和开发者必不可少的一项工作。而在推广过程中&#xff0c;二维码作为一种便捷的下载和安装方式&#xff0c;被广泛应用于各种场景。然而&#xff0c;如何有效获取并利用这些二维码信息&#xff0c;却成为了一个令人头疼的问题…

AUTOSAR软件配置(3):MCAL下载安装

前言 所有的NXP软件的下载安装都是需要自己在官网去注册账号的 中文的NXP官方网址&#xff1a;恩智浦半导体官方网站 | NXP 半导体 注&#xff1a;本文指导安装教程将以S32K144平台为例展开。 下载 找到下载入口的指引 然后在左侧的导航栏找到AUTOSAR 然后选择4.2版本 在…

Python中的类【详谈】

零.前言&#xff1a; 本文适合对Python有浅浅了解的读者&#xff0c;并不能作为Python入门使用。 一.Python中类的属性、方法 在Python中有变量&#xff0c;有函数&#xff0c;例如下方&#xff1a; def IAmAFunction():print("I am a function")IAmVariable 25…

智能革新:思通数科开源AI平台在保险合同管理中的应用与优化

思通数科开源的多模态AI能力引擎平台是一个强大的工具&#xff0c;它结合了自然语言处理&#xff08;NLP&#xff09;、图像识别和语音识别技术&#xff0c;为企业提供自动化处理和分析文本、音视频和图像数据的能力。这个平台的开源性质意味着它可以被广泛地应用于各种业务场景…

idea Springboot 在线考试管理系统开发mysql数据库web结构java编程计算机网页

一、源码特点 springboot 在线考试管理系统是一套完善的完整信息系统&#xff0c;结合mvc框架和bootstrap完成本系统springboot spring mybatis &#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有…

这个学习Python的神仙网站,后悔没早点发现

Python 作为时下最流行的编程语言&#xff0c;很多初学者都将它作为自学编程的首选。不管是有编程经验的开发者&#xff0c;还是新手小白&#xff0c;在这个 AIGC 时代&#xff0c; Python 都可以带你探索新世界。 入门 Python 绝非难事&#xff0c;但如何让自己坚持学下去是如…