您的位置:首页 > 科技 > 能源 > 【protobuf】ProtoBuf——proto3语法详解、字段规则、消息类型的定义与使用、通讯录的写入和读取功能实现

【protobuf】ProtoBuf——proto3语法详解、字段规则、消息类型的定义与使用、通讯录的写入和读取功能实现

2024/10/5 19:12:04 来源:https://blog.csdn.net/Crocodile1006/article/details/141269019  浏览:    关键词:【protobuf】ProtoBuf——proto3语法详解、字段规则、消息类型的定义与使用、通讯录的写入和读取功能实现

文章目录

  • ProtoBuf
    • 5. proto3语法详解
      • 5.1 字段规则
      • 5.2 消息类型的定义与使用

ProtoBuf

在这里插入图片描述

  

5. proto3语法详解

  在语法详解部分,依旧通过项目推进的方式开展教学。此部分会对通讯录多次升级,用 2.x 表示升级的版本,最终将完成以下内容的升级:

  (1)不再打印联系人的序列化结果,而是把通讯录序列化后写入文件。

  (2)从文件中解析出通讯录,并予以打印。

  (3)新增联系人属性,涵盖:姓名、年龄、电话信息、地址、其他联系方式、备注。

  

5.1 字段规则

  消息的字段能够运用以下几种规则加以修饰:

  singular :消息中能够包含该字段零次或者一次(不超过一次)。在 proto3 语法里,字段默认采用该规则。

  我们在 Protobuf 定义的消息结构中,对于被标记为 singular 规则的字段,在一条消息里,这个字段可以不出现(即零次),也可以出现一次,但最多只能出现一次。

  

  repeated :消息中能够包含该字段任意多次(包含零次),其中重复值的顺序会得以保留。

  这意味着在我们定义的消息结构中,对于被标记为 “repeated” 规则的字段,在一条消息里,它可以出现零次、一次或者多次,没有数量上的限制。可以理解为定义了一个数组。

  

   接下来我们就可以使用repeated字段在通讯录中添加我们的电话字段了:

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

  
  protoc编译文件:

protoc --cpp_out=. contacts.proto

  
  查看contacts.pb.h文件,这就是自动生成的有关repeated的函数:

在这里插入图片描述

  
  有关repeated类型的函数,下面以phone_numbers为例(自动生成的函数名和我们设置的字段名相关):

  
  获取相关函数:

  int phone_numbers_size() const;:这个函数用于获取 phone_numbers 字段中元素的数量。

  const std::string& phone_numbers(int index) const;:通过指定索引 index,获取 phone_numbers 中对应位置的元素,返回的是常量引用,意味着不能通过这个返回值修改元素。

  
  清空相关函数:

  void clear_phone_numbers();:该函数用于清空 phone_numbers 字段中的所有元素。

  
  修改相关函数:

  std::string* mutable_phone_numbers(int index);:通过索引获取 phone_numbers 中对应位置元素的指针,可通过这个指针修改对应位置的元素。

  void set_phone_numbers(int index, const std::string& value);:通过指定索引和一个字符串常量值,设置 phone_numbers 中对应位置的元素。

  void set_phone_numbers(int index, std::string&& value);:通过指定索引和一个右值引用的字符串,设置对应位置的元素。

  void set_phone_numbers(int index, const char* value);:通过指定索引和一个字符指针,设置对应位置的元素。

  void set_phone_numbers(int index, const char* value, size_t size);:通过指定索引、字符指针以及字符数量,设置对应位置的元素。

  
  添加相关函数:

  std::string* add_phone_numbers();:用于向 phone_numbers 字段添加一个新元素,并返回指向新添加元素的指针,以便进行后续修改。

  void add_phone_numbers(const std::string& value);:向 phone_numbers 字段添加一个字符串常量值。

  void add_phone_numbers(std::string&& value);:向 phone_numbers 字段添加一个右值引用的字符串。

  void add_phone_numbers(const char* value);:向 phone_numbers 字段添加一个通过字符指针表示的字符串。

  void add_phone_numbers(const char* value, size_t size);:向 phone_numbers 字段添加一个通过字符指针和字符数量表示的字符串。

  
  整体获取 / 修改相关函数:

  const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField < std::string> & phone_numbers() const;:获取整个 phone_numbers 字段的常量引用。

  ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField < std::string> * mutable_phone_numbers();:获取整个 phone_numbers 字段的可修改指针。

  

5.2 消息类型的定义与使用

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

  
  单个 .proto 文件嵌套写法:

syntax = "proto3";
package contacts;// 定义联系⼈消息
message PeopleInfo {string name = 1; // 姓名int32 age = 2; // 年龄// 可以在消息字段中定义消息字段message Phone{ string number = 1;   }
}

  
  单个 .proto 文件非嵌套写法:

syntax = "proto3";
package contacts;message Phone{ string number = 1;   
}// 定义联系⼈消息
message PeopleInfo {string name = 1; // 姓名int32 age = 2; // 年龄
}

  
  多个 .proto 文件导入头文件写法:

  导入其它.proto文件:import path/xxx.proto,引入的文件声明了package,使用其类型时需要用 命名空间 . 消息类型 的格式。

// 这个是a.proto文件
syntax = "proto3";
package A;message Phone{ string number = 1;   
}
// 这个是contacts.proto文件
syntax = "proto3";
package contacts;import "a.proto";// 定义联系⼈消息
message PeopleInfo {string name = 1; // 姓名int32 age = 2; // 年龄// 引用包A,创建Phone类型消息字段,定义为phoneA.Phone phone = 3; // 使用类外定义
}

  
  我们的通讯录程序又得以再次的优化了,在 PeopleInfo 消息中:name:字符串类型,代表姓名。age:32 位整数类型,代表年龄。

  嵌套的 Phone 消息,其中包含 number 字段,为字符串类型,代表电话号码。phone:Phone 类型的重复字段,代表电话信息。

  在 Contacts 消息中:contacts:PeopleInfo 类型的重复字段,代表通讯录中的联系人信息。

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;
}

  
  编译再次生成我们所需要的 .h 和 .cc文件:

在这里插入图片描述

  

  通讯录 2.0 的写入实现:

#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');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;if (argc != 2){cerr << "Usage: " << argv[0] << " CONTACTS_FILE" << endl;return -1;}Contacts contacts;// 先读取已存在的 contactsfstream 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());// 向磁盘⽂件写⼊新的 contactsfstream 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;
}

  

  GOOGLE_PROTOBUF_VERIFY_VERSION 宏用于验证所链接的库版本与编译的头文件是否兼容。若检测到版本不匹配,程序会中止。每个 .pb.cc 文件启动时会自动调用此宏。在使用 C++ Protocol Buffer 库前执行该宏是良好做法。

  
  在程序结束时可调用 ShutdownProtobufLibrary() 来删除 Protocol Buffer 库分配的所有全局对象。对多数程序而言这并非必要,因程序退出时操作系统会回收内存。但如果使用内存泄漏检查程序,或编写可被单个进程多次加载和卸载的库,可能需要强制 Protocol Buffers 清理所有内容。

  

  Makefile:

write:write.cc contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:rm -f write

  

  接着就可以向我们指定的contacts.bin文件中写入信息了:

在这里插入图片描述
  

  查看二进制文件:

  hexdump 是 Linux 中的二进制文件查看工具,能把二进制文件转换成 ASCII、八进制、十进制、十六进制格式来查看。其中 -C 选项意味着每个字节会以十六进制形式和对应的 ASCII 字符显示。

在这里插入图片描述

  

  decode 是 ProtoBuf 中通过 protoc -h 命令可查看到的一个命令选项 --decode 。它的作用是从标准输入读取给定类型的二进制消息,并将其以文本格式输出到标准输出。而且,该消息的类型必须在 .proto 文件或导入的文件中有定义。

  具体用法如下: protoc --decode=MESSAGE_TYPE MESSAGE_TYPE所在文件
  

protoc --decode=contacts.Contacts contacts.proto < contacts.bin

在这里插入图片描述

  

  通讯录 2.0 的读取实现:

#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;}// 以⼆进制⽅式读取 contactsContacts contacts;fstream input(argv[1], ios::in | ios::binary);if (!contacts.ParseFromIstream(&input)){cerr << "Failed to parse contacts." << endl;input.close();return -1;}// 打印 contactsPrintfContacts(contacts);input.close();google::protobuf::ShutdownProtobufLibrary();return 0;
}

  

  Makefile:

all:write read
write:write.cc contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf
read:read.cc contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf.PHONY:clean
clean:rm -f write read

  

  我们可以读取到write写入文件的信息了:

在这里插入图片描述

            

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com