4.Proto 3 语法详解

打印 上一主题 下一主题

主题 984|帖子 984|积分 2952

proto 3 语法详解


在语法详解部分, 依旧使用 项目推进 的方式完成讲授, 这个部分会对通讯录进行多次升级, 使用 2.x表示升级的版本, 最终版将会升级为如下内容:


  • 不再打印接洽人的序列化结果, 而是将通讯录序列化后并写入文件中.
  • 从文件中将通讯录解析出来, 并进行打印
  • 新增接洽人属性, 共包括: 姓名, 年事, 电话信息, 地点, 其他接洽方式, 备注.
字段规则

   除了通例字段, 我们能否还可以给字段设置其他规则, 好比我想要一个数组范例的字段.
  

  • singular: 消息中可以包罗该字段零次或一次, proto3语法中, 字段默认使用该规则
  • repeated: 消息中可以包罗该字段任意多次(包括0次), 此中重复值的顺序会被保存. 可以明白为界说了一个数组.
  更新contacts.proto, PeopleInfo消息中新增phone字段, 表示一个接洽人有多个号码, 可将其设置为repeated, 写法如下:

  消息范例的界说与使用

   1.在单个.proto文件中可以界说多个消息体, 且支持界说嵌套范例的消息(任意多层), 每个消息体中的字段编号可以重复
2.消息范例可作为字段范例使用


3.可导入其他.proto文件的消息并使用


  创建通讯录2.0版本

   下面我们来实现通讯录2.0版本, 向文件中写入通讯录列表

进行编译生成

再次查看生成的.h文件





  write.cc通讯录2.0的写入实现
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void AddPeopleInfo(contacts2::PeopleInfo* people){
  6.     cout << "------------------新增联系人----------------" << endl;
  7.     cout << "请输入联系人姓名: ";
  8.     string name;
  9.     getline(cin, name);
  10.     people->set_name(name);
  11.     cout << "请输入联系人年龄: ";
  12.     int age;
  13.     cin >> age;
  14.     people->set_age(age);
  15.     cin.ignore(256, '\n');
  16.     for (int i = 0;; i++){
  17.         cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
  18.         string number;
  19.         getline(cin, number);
  20.         if (number.empty()) {
  21.             break;
  22.         }
  23.         contacts2::PeopleInfo_Phone* phone = people->add_phone();
  24.         phone->set_number(number);
  25.     }
  26.     cout << "-----------添加联系人成功-----------------" << endl;
  27. }
  28. int main()
  29. {
  30.     contacts2::Contacts contacts;
  31.     // 读取本地已存在的通讯录文件
  32.     fstream input("contacts.bin", ios::in | ios::binary);
  33.     if (!input.is_open()) {
  34.         cout << "contacts.bin not find, create new file!" << endl;
  35.     } else if (!contacts.ParseFromIstream(&input)) {
  36.         cerr << "parse error!" << endl;
  37.         input.close();
  38.         return -1;
  39.     }
  40.     // 向通讯录中添加一个联系人
  41.     AddPeopleInfo(contacts.add_contacts());
  42.     // 将通讯录写入本地文件中
  43.     fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
  44.     if (!contacts.SerializeToOstream(&output)) {
  45.         cerr << "write error!" << endl;
  46.         input.close();
  47.         output.close();
  48.         return -1;
  49.     }
  50.     cout << "write success!" << endl;
  51.     input.close();
  52.     output.close();
  53.     return 0;
  54. }
复制代码
  运行:

    先容在Linux查看二进制文件的工具: hexdump: 可以将二进制文件进行转换, 转换为对应的ASCII值, 以及八进制, 十进制, 十六进制的格式.

  read.cc通讯录2.0的读取实现
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void PrintContacts(contacts2::Contacts& contacts) {
  6.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  7.         cout << "------------------联系人" << i + 1 << "----------------------" << endl;
  8.         const contacts2::PeopleInfo& people = contacts.contacts(i);
  9.         cout << "联系人姓名: " << people.name() << endl;
  10.         cout << "联系人年龄: " << people.age() << endl;
  11.         for (int j = 0; j < people.phone_size(); ++j) {
  12.             const contacts2::PeopleInfo_Phone& phone = people.phone(j);
  13.             cout << "联系人电话" << j + 1 << ":" << phone.number() << endl;
  14.         }
  15.     }
  16. }
  17. int main()
  18. {
  19.     contacts2::Contacts contacts;
  20.     // 读取本地已存在的通讯录文件
  21.     fstream input("contacts.bin", ios::in | ios::binary);
  22.     if (!contacts.ParseFromIstream(&input)) {
  23.         cerr << "parse error!" << endl;
  24.         input.close();
  25.         return -1;
  26.     }
  27.     // 打印通讯录列表
  28.     PrintContacts(contacts);
  29.     return 0;
  30. }
复制代码
  运行:

    我们也可以从标准输入中读取给定范例的二进制消息, 可以将其内容以文本的格式输出到标准输出上.

有什么用呢? 我们之前存的contacts.bin存的是二进制序列, 想要看的话就需要写一个read.cc, 再通过打印的方式来查看内容是什么. 我们如今可以使用–decode命令, 使用一行命令就可以进行二进制消息的打印.
  enum范例

   proto3语法还支持我们界说罗列范例的字段, 在.proto文件中罗列范例的誊写规范为:
罗列范例名称: 使用驼峰定名法, 首字母大写. 例如: MyEnum
常量值名称: 全大写字母, 多个字母之间用 _ 毗连. 例如: ENUM_CONST = 0;
    我们可以界说一个PhoneType的罗列范例, 界说如下:

要注意罗列的界说有以下几种规则:
1.0值常量必须存在, 且要作为第一个元素. 这是为了与proto2的语义兼容: 第一个元素作为默认值, 且值为0.
2.罗列范例可以在消息外界说, 也可以在消息体内界说(嵌套).
3.罗列的常量值在32位整数的范围内. 但因负值无效因而不建议使用(与编码规则有关).
4.同级的罗列范例, 如果有相同的常量名称, 是会报错的.


什么是同级(同层)的罗列范例?
单个.proto文件下, 最外层罗列范例和嵌套罗列范例, 不算同级.

不会编译报错

多个.proto文件下, 若一个文件引入了其他文件, 且每个文件都未声明package, 每个proto文件中的罗列范例都在最外层, 算同级



多个.proto文件下, 若一个文件引入了其他文件, 且每个文件都声明了package, 不算同级.


此时就可以正常生成了

  升级通讯录至2.1版本

   更新contacts.proto, 新增罗列字段并使用, 更新内容如下:

进行编译

查看对应.h文件
  


  更新write.cc(通讯录2.1)
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void AddPeopleInfo(contacts2::PeopleInfo* people){
  6.     cout << "------------------新增联系人----------------" << endl;
  7.     cout << "请输入联系人姓名: ";
  8.     string name;
  9.     getline(cin, name);
  10.     people->set_name(name);
  11.     cout << "请输入联系人年龄: ";
  12.     int age;
  13.     cin >> age;
  14.     people->set_age(age);
  15.     cin.ignore(256, '\n');
  16.     for (int i = 0;; i++){
  17.         cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
  18.         string number;
  19.         getline(cin, number);
  20.         if (number.empty()) {
  21.             break;
  22.         }
  23.         contacts2::PeopleInfo_Phone* phone = people->add_phone();
  24.         phone->set_number(number);
  25.         cout << "请输入该电话类型(1. 移动电话      2.固定电话): ";
  26.         int type;
  27.         cin >> type;
  28.         cin.ignore(256, '\n');
  29.         switch(type) {
  30.             case 1:
  31.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
  32.                 break;
  33.             case 2:
  34.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
  35.                 break;
  36.             default:
  37.                 cout << "选择有误! " << endl;
  38.                 break;
  39.         }
  40.     }
  41.     cout << "-----------添加联系人成功-----------------" << endl;
  42. }
  43. int main()
  44. {
  45.     contacts2::Contacts contacts;
  46.     // 读取本地已存在的通讯录文件
  47.     fstream input("contacts.bin", ios::in | ios::binary);
  48.     if (!input.is_open()) {
  49.         cout << "contacts.bin not find, create new file!" << endl;
  50.     } else if (!contacts.ParseFromIstream(&input)) {
  51.         cerr << "parse error!" << endl;
  52.         input.close();
  53.         return -1;
  54.     }
  55.     // 向通讯录中添加一个联系人
  56.     AddPeopleInfo(contacts.add_contacts());
  57.     // 将通讯录写入本地文件中
  58.     fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
  59.     if (!contacts.SerializeToOstream(&output)) {
  60.         cerr << "write error!" << endl;
  61.         input.close();
  62.         output.close();
  63.         return -1;
  64.     }
  65.     cout << "write success!" << endl;
  66.     input.close();
  67.     output.close();
  68.     return 0;
  69. }
复制代码
更新read.cc(通讯录2.1)
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void PrintContacts(contacts2::Contacts& contacts) {
  6.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  7.         cout << "------------------联系人" << i + 1 << "----------------------" << endl;
  8.         const contacts2::PeopleInfo& people = contacts.contacts(i);
  9.         cout << "联系人姓名: " << people.name() << endl;
  10.         cout << "联系人年龄: " << people.age() << endl;
  11.         for (int j = 0; j < people.phone_size(); ++j) {
  12.             const contacts2::PeopleInfo_Phone& phone = people.phone(j);
  13.             cout << "联系人电话" << j + 1 << ":" << phone.number();
  14.             cout << "   (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
  15.         }
  16.     }
  17. }
  18. int main()
  19. {
  20.     contacts2::Contacts contacts;
  21.     // 读取本地已存在的通讯录文件
  22.     fstream input("contacts.bin", ios::in | ios::binary);
  23.     if (!contacts.ParseFromIstream(&input)) {
  24.         cerr << "parse error!" << endl;
  25.         input.close();
  26.         return -1;
  27.     }
  28.     // 打印通讯录列表
  29.     PrintContacts(contacts);
  30.     return 0;
  31. }
复制代码
  运行结果:

  Any范例

   proto3语法还支持我们界说Any的字段, 可以明白为泛型范例, 使用时可以在Any中存储任意消息范例. Any范例的字段也可以用repeated来修饰.
Any范例是google已经帮我们界说好的范例, 在安装Protobuf时, 此中的include目次下已经界说好了.proto文件.
进行cat查看该文件



都是我们熟悉的语法, 阐明我们要学的Any就是一个message消息体
  升级通讯录至2.2版本

   通讯录2.2版本会新增接洽人的地点信息, 我们可以使用any范例的字段来存储地点信息.
更新contacts.proto(通讯录2.2), 更新内容如下:

进行编译

查看生成的.h文件


怎样将我们的any范例去存放通讯录的地点信息?
打开any.proto文件编译后的Any.h文件


  更新write.cc(通讯录2.2)
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void AddPeopleInfo(contacts2::PeopleInfo* people){
  6.     cout << "------------------新增联系人----------------" << endl;
  7.     cout << "请输入联系人姓名: ";
  8.     string name;
  9.     getline(cin, name);
  10.     people->set_name(name);
  11.     cout << "请输入联系人年龄: ";
  12.     int age;
  13.     cin >> age;
  14.     people->set_age(age);
  15.     cin.ignore(256, '\n');
  16.     for (int i = 0;; i++){
  17.         cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
  18.         string number;
  19.         getline(cin, number);
  20.         if (number.empty()) {
  21.             break;
  22.         }
  23.         contacts2::PeopleInfo_Phone* phone = people->add_phone();
  24.         phone->set_number(number);
  25.         cout << "请输入该电话类型(1. 移动电话      2.固定电话): ";
  26.         int type;
  27.         cin >> type;
  28.         cin.ignore(256, '\n');
  29.         switch(type) {
  30.             case 1:
  31.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
  32.                 break;
  33.             case 2:
  34.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
  35.                 break;
  36.             default:
  37.                 cout << "选择有误! " << endl;
  38.                 break;
  39.         }
  40.     }
  41.     contacts2::Address address;
  42.     cout << "请输入联系人家庭地址: ";
  43.     string home_address;
  44.     getline(cin, home_address);
  45.     address.set_home_address(home_address);
  46.     cout << "请输入联系人单位地址: ";
  47.     string unit_address;
  48.     getline(cin, unit_address);
  49.     address.set_unit_address(unit_address);
  50.     people->mutable_data()->PackFrom(address);
  51.     cout << "-----------添加联系人成功-----------------" << endl;
  52. }
  53. int main()
  54. {
  55.     contacts2::Contacts contacts;
  56.     // 读取本地已存在的通讯录文件
  57.     fstream input("contacts.bin", ios::in | ios::binary);
  58.     if (!input.is_open()) {
  59.         cout << "contacts.bin not find, create new file!" << endl;
  60.     } else if (!contacts.ParseFromIstream(&input)) {
  61.         cerr << "parse error!" << endl;
  62.         input.close();
  63.         return -1;
  64.     }
  65.     // 向通讯录中添加一个联系人
  66.     AddPeopleInfo(contacts.add_contacts());
  67.     // 将通讯录写入本地文件中
  68.     fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
  69.     if (!contacts.SerializeToOstream(&output)) {
  70.         cerr << "write error!" << endl;
  71.         input.close();
  72.         output.close();
  73.         return -1;
  74.     }
  75.     cout << "write success!" << endl;
  76.     input.close();
  77.     output.close();
  78.     return 0;
  79. }
复制代码
更新read.cc(通讯录2.2)
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void PrintContacts(contacts2::Contacts& contacts) {
  6.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  7.         cout << "------------------联系人" << i + 1 << "----------------------" << endl;
  8.         const contacts2::PeopleInfo& people = contacts.contacts(i);
  9.         cout << "联系人姓名: " << people.name() << endl;
  10.         cout << "联系人年龄: " << people.age() << endl;
  11.         for (int j = 0; j < people.phone_size(); ++j) {
  12.             const contacts2::PeopleInfo_Phone& phone = people.phone(j);
  13.             cout << "联系人电话" << j + 1 << ":" << phone.number();
  14.             cout << "   (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
  15.         }
  16.         if (people.has_data() && people.data().Is<contacts2::Address>()) {
  17.             contacts2::Address address;
  18.             people.data().UnpackTo(&address);
  19.             if (!address.home_address().empty()) {
  20.                 cout << "联系人家庭地址: " << address.home_address() << endl;
  21.             }
  22.             if (!address.unit_address().empty()) {
  23.                 cout << "联系人单位地址: " << address.unit_address() << endl;
  24.             }
  25.         }
  26.     }
  27. }
  28. int main()
  29. {
  30.     contacts2::Contacts contacts;
  31.     // 读取本地已存在的通讯录文件
  32.     fstream input("contacts.bin", ios::in | ios::binary);
  33.     if (!contacts.ParseFromIstream(&input)) {
  34.         cerr << "parse error!" << endl;
  35.         input.close();
  36.         return -1;
  37.     }
  38.     // 打印通讯录列表
  39.     PrintContacts(contacts);
  40.     return 0;
  41. }
复制代码
  编译运行:

    为什么用Any范例, 而不用void* 范例代替呢? 如果大家经历过, 记忆应该能更深刻一点.
不知道你们有没有调试过瓦解过的程序, 调试出来了就是因为使用了范例转换错误的, 所以没有范例查抄, 是很不安全的事. 当然原因不止这一点, 尚有一些原因(可以AI搜一下), 但我讲的这一点是比力重要的原因.

  oneof范例

   如果消息中有很多可选字段, 而且未来同时只有一个字段会被设置, 那么就可以使用oneof加强这个行为, 也能有节约内存的结果.
  升级通讯录至2.3版本

   通讯录2.3版本想新增接洽人的其他接洽方式, 好比qq或者微信号二选一, 我们就可以使用oneof字段来加强多选一这个行为.
更新contacts.proto, 更新内容如下:

注意:
1.可选字段中的字段编号, 不能与非可选字段的编号冲突.
2.不能在oneof中使用repeated字段.
3.未来在设置oneof字段的时间, 如果你set了多个字段, 那么只会保存最后一次set的成员.
    进行编译后查看.h文件




  更新write.cc, 更内容如下:
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void AddPeopleInfo(contacts2::PeopleInfo* people){
  6.     cout << "------------------新增联系人----------------" << endl;
  7.     cout << "请输入联系人姓名: ";
  8.     string name;
  9.     getline(cin, name);
  10.     people->set_name(name);
  11.     cout << "请输入联系人年龄: ";
  12.     int age;
  13.     cin >> age;
  14.     people->set_age(age);
  15.     cin.ignore(256, '\n');
  16.     for (int i = 0;; i++){
  17.         cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
  18.         string number;
  19.         getline(cin, number);
  20.         if (number.empty()) {
  21.             break;
  22.         }
  23.         contacts2::PeopleInfo_Phone* phone = people->add_phone();
  24.         phone->set_number(number);
  25.         cout << "请输入该电话类型(1. 移动电话      2.固定电话): ";
  26.         int type;
  27.         cin >> type;
  28.         cin.ignore(256, '\n');
  29.         switch(type) {
  30.             case 1:
  31.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
  32.                 break;
  33.             case 2:
  34.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
  35.                 break;
  36.             default:
  37.                 cout << "选择有误! " << endl;
  38.                 break;
  39.         }
  40.     }
  41.     contacts2::Address address;
  42.     cout << "请输入联系人家庭地址: ";
  43.     string home_address;
  44.     getline(cin, home_address);
  45.     address.set_home_address(home_address);
  46.     cout << "请输入联系人单位地址: ";
  47.     string unit_address;
  48.     getline(cin, unit_address);
  49.     address.set_unit_address(unit_address);
  50.     people->mutable_data()->PackFrom(address);
  51.     cout << "请选择要添加的其他联系方式(1. qq       2. 微信号):";
  52.     int other_contact;
  53.     cin >> other_contact;
  54.     cin.ignore(256, '\n');
  55.     if (1 == other_contact) {
  56.         cout << "请输入联系人qq号: ";
  57.         string qq;
  58.         getline(cin, qq);
  59.         people->set_qq(qq);
  60.     } else if (2 == other_contact) {
  61.         cout << "请输入联系人微信号: ";
  62.         string wechat;
  63.         getline(cin, wechat);
  64.         people->set_wechat(wechat);
  65.     } else {
  66.         cout << "选择有误, 未成功设置其他联系方式! " << endl;
  67.     }
  68.     cout << "-----------添加联系人成功-----------------" << endl;
  69. }
  70. int main()
  71. {
  72.     contacts2::Contacts contacts;
  73.     // 读取本地已存在的通讯录文件
  74.     fstream input("contacts.bin", ios::in | ios::binary);
  75.     if (!input.is_open()) {
  76.         cout << "contacts.bin not find, create new file!" << endl;
  77.     } else if (!contacts.ParseFromIstream(&input)) {
  78.         cerr << "parse error!" << endl;
  79.         input.close();
  80.         return -1;
  81.     }
  82.     // 向通讯录中添加一个联系人
  83.     AddPeopleInfo(contacts.add_contacts());
  84.     // 将通讯录写入本地文件中
  85.     fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
  86.     if (!contacts.SerializeToOstream(&output)) {
  87.         cerr << "write error!" << endl;
  88.         input.close();
  89.         output.close();
  90.         return -1;
  91.     }
  92.     cout << "write success!" << endl;
  93.     input.close();
  94.     output.close();
  95.     return 0;
  96. }
复制代码
更新read.cc, 更内容如下:
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void PrintContacts(contacts2::Contacts& contacts) {
  6.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  7.         cout << "------------------联系人" << i + 1 << "----------------------" << endl;
  8.         const contacts2::PeopleInfo& people = contacts.contacts(i);
  9.         cout << "联系人姓名: " << people.name() << endl;
  10.         cout << "联系人年龄: " << people.age() << endl;
  11.         for (int j = 0; j < people.phone_size(); ++j) {
  12.             const contacts2::PeopleInfo_Phone& phone = people.phone(j);
  13.             cout << "联系人电话" << j + 1 << ":" << phone.number();
  14.             cout << "   (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
  15.         }
  16.         if (people.has_data() && people.data().Is<contacts2::Address>()) {
  17.             contacts2::Address address;
  18.             people.data().UnpackTo(&address);
  19.             if (!address.home_address().empty()) {
  20.                 cout << "联系人家庭地址: " << address.home_address() << endl;
  21.             }
  22.             if (!address.unit_address().empty()) {
  23.                 cout << "联系人单位地址: " << address.unit_address() << endl;
  24.             }
  25.         }
  26.         switch(people.other_contact_case()) {
  27.             case contacts2::PeopleInfo::OtherContactCase::kQq:
  28.                 cout << "联系人qq: " << people.qq() << endl;
  29.                 break;
  30.             case contacts2::PeopleInfo::OtherContactCase::kWechat:
  31.                 cout << "联系人微信号: " << people.wechat() << endl;
  32.                 break;
  33.             default:
  34.                 break;
  35.         }
  36.     }
  37. }
  38. int main()
  39. {
  40.     contacts2::Contacts contacts;
  41.     // 读取本地已存在的通讯录文件
  42.     fstream input("contacts.bin", ios::in | ios::binary);
  43.     if (!contacts.ParseFromIstream(&input)) {
  44.         cerr << "parse error!" << endl;
  45.         input.close();
  46.         return -1;
  47.     }
  48.     // 打印通讯录列表
  49.     PrintContacts(contacts);
  50.     return 0;
  51. }
复制代码
  编译运行结果:

  map范例

   语法支持创建一个关联映射字段, 也就是可以使用map范例去声明字段范例
要注意的是:
1.key_type是除了float和bytes范例以外的任意标量范例, value_type可以是任意范例.
2.map字段不可以用repeated修饰
3.map中存入的元素是无序的
  升级通讯录至2.4版本

   通讯录2.4版本想新增接洽人的备注信息, 我们可以使用map范例字段来存储备注信息
更新contacts.proto, 更新内容如下:

进行编译后查看.h文件


  更新write.cc, 更新内容如下:
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void AddPeopleInfo(contacts2::PeopleInfo* people){
  6.     cout << "------------------新增联系人----------------" << endl;
  7.     cout << "请输入联系人姓名: ";
  8.     string name;
  9.     getline(cin, name);
  10.     people->set_name(name);
  11.     cout << "请输入联系人年龄: ";
  12.     int age;
  13.     cin >> age;
  14.     people->set_age(age);
  15.     cin.ignore(256, '\n');
  16.     for (int i = 0;; i++){
  17.         cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
  18.         string number;
  19.         getline(cin, number);
  20.         if (number.empty()) {
  21.             break;
  22.         }
  23.         contacts2::PeopleInfo_Phone* phone = people->add_phone();
  24.         phone->set_number(number);
  25.         cout << "请输入该电话类型(1. 移动电话      2.固定电话): ";
  26.         int type;
  27.         cin >> type;
  28.         cin.ignore(256, '\n');
  29.         switch(type) {
  30.             case 1:
  31.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
  32.                 break;
  33.             case 2:
  34.                 phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
  35.                 break;
  36.             default:
  37.                 cout << "选择有误! " << endl;
  38.                 break;
  39.         }
  40.     }
  41.     contacts2::Address address;
  42.     cout << "请输入联系人家庭地址: ";
  43.     string home_address;
  44.     getline(cin, home_address);
  45.     address.set_home_address(home_address);
  46.     cout << "请输入联系人单位地址: ";
  47.     string unit_address;
  48.     getline(cin, unit_address);
  49.     address.set_unit_address(unit_address);
  50.     people->mutable_data()->PackFrom(address);
  51.     cout << "请选择要添加的其他联系方式(1. qq       2. 微信号):";
  52.     int other_contact;
  53.     cin >> other_contact;
  54.     cin.ignore(256, '\n');
  55.     if (1 == other_contact) {
  56.         cout << "请输入联系人qq号: ";
  57.         string qq;
  58.         getline(cin, qq);
  59.         people->set_qq(qq);
  60.     } else if (2 == other_contact) {
  61.         cout << "请输入联系人微信号: ";
  62.         string wechat;
  63.         getline(cin, wechat);
  64.         people->set_wechat(wechat);
  65.     } else {
  66.         cout << "选择有误, 未成功设置其他联系方式! " << endl;
  67.     }
  68.     for (int i = 0;; i++) {
  69.         cout << "请输入备注" << i + 1 << "标题(只输入回车完成备注新增):";
  70.         string remark_key;
  71.         getline(cin, remark_key);
  72.         if (remark_key.empty()) {
  73.             break;
  74.         }
  75.         cout << "请输入备注" << i + 1 << "内容: ";
  76.         string remark_value;
  77.         getline(cin, remark_value);
  78.         people->mutable_remark()->insert({remark_key, remark_value});
  79.     }
  80.     cout << "-----------添加联系人成功-----------------" << endl;
  81. }
  82. int main()
  83. {
  84.     contacts2::Contacts contacts;
  85.     // 读取本地已存在的通讯录文件
  86.     fstream input("contacts.bin", ios::in | ios::binary);
  87.     if (!input.is_open()) {
  88.         cout << "contacts.bin not find, create new file!" << endl;
  89.     } else if (!contacts.ParseFromIstream(&input)) {
  90.         cerr << "parse error!" << endl;
  91.         input.close();
  92.         return -1;
  93.     }
  94.     // 向通讯录中添加一个联系人
  95.     AddPeopleInfo(contacts.add_contacts());
  96.     // 将通讯录写入本地文件中
  97.     fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
  98.     if (!contacts.SerializeToOstream(&output)) {
  99.         cerr << "write error!" << endl;
  100.         input.close();
  101.         output.close();
  102.         return -1;
  103.     }
  104.     cout << "write success!" << endl;
  105.     input.close();
  106.     output.close();
  107.     return 0;
  108. }
复制代码
更新read.cc, 更新内容如下:
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. void PrintContacts(contacts2::Contacts& contacts) {
  6.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  7.         cout << "------------------联系人" << i + 1 << "----------------------" << endl;
  8.         const contacts2::PeopleInfo& people = contacts.contacts(i);
  9.         cout << "联系人姓名: " << people.name() << endl;
  10.         cout << "联系人年龄: " << people.age() << endl;
  11.         for (int j = 0; j < people.phone_size(); ++j) {
  12.             const contacts2::PeopleInfo_Phone& phone = people.phone(j);
  13.             cout << "联系人电话" << j + 1 << ":" << phone.number();
  14.             cout << "   (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
  15.         }
  16.         if (people.has_data() && people.data().Is<contacts2::Address>()) {
  17.             contacts2::Address address;
  18.             people.data().UnpackTo(&address);
  19.             if (!address.home_address().empty()) {
  20.                 cout << "联系人家庭地址: " << address.home_address() << endl;
  21.             }
  22.             if (!address.unit_address().empty()) {
  23.                 cout << "联系人单位地址: " << address.unit_address() << endl;
  24.             }
  25.         }
  26.         switch(people.other_contact_case()) {
  27.             case contacts2::PeopleInfo::OtherContactCase::kQq:
  28.                 cout << "联系人qq: " << people.qq() << endl;
  29.                 break;
  30.             case contacts2::PeopleInfo::OtherContactCase::kWechat:
  31.                 cout << "联系人微信号: " << people.wechat() << endl;
  32.                 break;
  33.             default:
  34.                 break;
  35.         }
  36.         if (people.remark_size()) {
  37.             cout << "备注信息: " << endl;
  38.         }
  39.         for (auto it = people.remark().cbegin(); it != people.remark().cend(); it++){
  40.             cout << "   " << it->first << ": " << it->second << endl;
  41.         }
  42.     }
  43. }
  44. int main()
  45. {
  46.     contacts2::Contacts contacts;
  47.     // 读取本地已存在的通讯录文件
  48.     fstream input("contacts.bin", ios::in | ios::binary);
  49.     if (!contacts.ParseFromIstream(&input)) {
  50.         cerr << "parse error!" << endl;
  51.         input.close();
  52.         return -1;
  53.     }
  54.     // 打印通讯录列表
  55.     PrintContacts(contacts);
  56.     return 0;
  57. }
复制代码
  编译运行结果:

至此, 我们2.4版本就正式完成了全部的需求.
  默认值

   反序列化消息时, 如果被反序列化的二进制序列中不包罗某个字段, 反序列化对象中相应字段时, 就会设置为该字段的默认值. 不同的范例对应的默认值不同:
  

  • 对于字符串, 默认值为空字符串
  • 对于字节, 默认值为空字节
  • 对于布尔值, 默认值为false
  • 对于数值范例, 默认值为0
  • 对于罗列, 默认值是第一个界说的罗列值, 必须为0
  • 对于消息字段, 如果未设置该字段, 它的取值是依赖于语言
  • 对于设置了repeated的字段的默认值是空的(通常是相应语言的一个空列表)
  • 对于消息字段, oneof字段和any字段, C++和java语言中都有hash_方法来检测当前字段是否被设置.
  更新消息

   反序列化消息时, 如果现有的消息范例已经不再满足我们的需求, 例如需要扩展一个字段, 在不破坏任何现有代码的环境下更新消息范例非常简单. 遵循如下规则即可:
  

  • 精致修改任何已有字段的字段编号.
  • 如果移除老字段, 要包管不再使用移除字段的字段编号. 正确的做法是保存字段编号(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范例是不安全的。

  保存字段reserved

   如果通过 删除 或 注释掉 字段来更新消息范例, 未来的用户在添加新字段时, 有可能会使用以前已经存在, 但已经被删除或注释掉的字段编号, 未来使用该.proto的旧版本时的程序会引发很多问题: 数据损坏, 隐私错误等等.
确保不会发生这种环境的一种方法是: 使用reserved将指定字段的编号或名称设置为保存项. 当我们再使用这些编号或名称时, protobuffer的编译器将会警告这些编号或名称不可用. 举个例子:
  1. message Message {
  2.   // 设置保留项
  3.   reserved 100, 101, 200 to 299;
  4.   reserved "field3", "field4";
  5.   // 注意:不要在⼀⾏ reserved 声明中同时声明字段编号和名称。
  6.   // reserved 102, "field5";
  7.   // 设置保留项之后,下⾯代码会告警
  8.   int32 field1 = 100; //告警:Field 'field1' uses reserved number 100
  9.   int32 field2 = 101; //告警:Field 'field2' uses reserved number 101
  10.   int32 field3 = 102; //告警:Field name 'field3' is reserved
  11.   int32 field4 = 103; //告警:Field name 'field4' is reserved
  12. }
复制代码
创建通讯录3.0版本

验证 错误字段 造成的数据损坏
   现模拟有两个服务, 他们各自使用一份通讯录.proto文件, 内容约定好了是千篇同等的. 唯一不同的是package的定名上
服务1: service 负责序列化通讯录对象, 并写入文件中.
服务2: client: 负责读取文件中的数据, 解析并打印出来.
    一段时间后, service更新了自己的.proto文件, 更新了某个字段, 并新增了一个字段, 新增的字段使用了被删除字段的字段编号, 并将新的序列化对象写进了文件, 但Client并没有更新自己的.proto文件. 根据结论, 可能会出现数据损坏的征象, 接下来就让我们来验证下这个结论.
  新增两个目次: service, client. 分别存放两个服务代码.

service目次下新增contacts.proto
  1. syntax ="proto3";
  2. package s_contacts;
  3. //联系人
  4. message PeopleInfo {
  5.   string name = 1;            //姓名
  6.   int32 age = 2;              //年龄
  7.   message Phone{
  8.     string number = 1;              //电话号码
  9.   }
  10.   repeated Phone phone=3;       //电话
  11. }
  12. //通讯录
  13. message Contacts{
  14.   repeated PeopleInfo contacts = 1;
  15. }
复制代码
client目次下新增contacts.proto
  1. syntax ="proto3";
  2. package c_contacts;
  3. //联系人
  4. message PeopleInfo {
  5.   string name = 1;            //姓名
  6.   int32 age = 2;              //年龄
  7.   message Phone{
  8.     string number = 1;              //电话号码
  9.   }
  10.   repeated Phone phone=3;       //电话
  11. }
  12. //通讯录
  13. message Contacts{
  14.   repeated PeopleInfo contacts = 1;
  15. }
复制代码
继承对service目次下新增service.cc, 复杂想文件中写通讯录消息, 内容如下:
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. using namespace s_contacts;
  6. /**
  7. * 新增联系⼈
  8. */
  9. void AddPeopleInfo(PeopleInfo *people_info_ptr)
  10. {
  11.     cout << "-------------新增联系⼈-------------" << endl;
  12.     cout << "请输⼊联系⼈姓名: ";
  13.     string name;
  14.     getline(cin, name);
  15.     people_info_ptr->set_name(name);
  16.     cout << "请输⼊联系⼈年龄: ";
  17.     int age;
  18.     cin >> age;
  19.     people_info_ptr->set_age(age);
  20.     cin.ignore(256, '\n');
  21.     for(int i = 1; ; i++) {
  22.         cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增): ";
  23.         string number;
  24.         getline(cin, number);
  25.         if (number.empty()) {
  26.             break;
  27.         }
  28.         PeopleInfo_Phone* phone = people_info_ptr->add_phone();
  29.         phone->set_number(number);
  30.     }
  31.     cout << "-----------添加联系⼈成功-----------" << endl;
  32. }
  33. int main(int argc, char *argv[])
  34. {
  35.     GOOGLE_PROTOBUF_VERIFY_VERSION;//: 验证没有意外链接到与编译的头⽂件不兼容的库版
  36. //本。如果检测到版本不匹配,程序将中⽌。注意,每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使
  37. //⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法,但不是绝对必要的。
  38.     if (argc != 2)
  39.     {
  40.         cerr << "Usage: " << argv[0] << " CONTACTS_FILE" << endl;
  41.         return -1;
  42.     }
  43.     Contacts contacts;
  44.    
  45.     // 先读取已存在的 contacts
  46.     fstream input(argv[1], ios::in | ios::binary);
  47.     if (!input) {
  48.         cout << argv[1] << ": File not found. Creating a new file." << endl;
  49.     }
  50.     else if (!contacts.ParseFromIstream(&input)) {
  51.         cerr << "Failed to parse contacts." << endl;
  52.         input.close();
  53.         return -1;
  54.     }
  55.     // 新增⼀个联系⼈
  56.     AddPeopleInfo(contacts.add_contacts());
  57.     // 向磁盘⽂件写⼊新的 contacts
  58.     fstream output(argv[1], ios::out | ios::trunc | ios::binary);
  59.     if (!contacts.SerializeToOstream(&output))
  60.     {
  61.         cerr << "Failed to write contacts." << endl;
  62.         input.close();
  63.         output.close();
  64.         return -1;
  65.     }
  66.     input.close();
  67.     output.close();
  68.     google::protobuf::ShutdownProtobufLibrary();
  69.     return 0;
  70. }
复制代码
service ⽬录下新增 makefile
  1. service:service.cc contacts.pb.cc
  2.         g++ -o $@ $^ -std=c++11 -lprotobuf
  3. .PHONY:clean
  4. clean:
  5.         rm -f service
复制代码
client ⽬录下新增 client.cc,负责向读出⽂件中的通讯录消息,内容如下
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. using namespace c_contacts;
  6. /**
  7. * 打印联系⼈列表
  8. */
  9. void PrintfContacts(const Contacts& contacts) {
  10.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  11.         const PeopleInfo& people = contacts.contacts(i);
  12.         cout << "------------联系⼈" << i+1 << "------------" << endl;
  13.         cout << "姓名:" << people.name() << endl;
  14.         cout << "年龄:" << people.age() << endl;
  15.         int j = 1;
  16.         for (const PeopleInfo_Phone& phone : people.phone()) {
  17.             cout << "电话" << j++ << ": " << phone.number() << endl;
  18.         }
  19.     }
  20. }
  21. int main(int argc, char* argv[]) {
  22.     GOOGLE_PROTOBUF_VERIFY_VERSION;//: 验证没有意外链接到与编译的头⽂件不兼容的库版
  23. //本。如果检测到版本不匹配,程序将中⽌。注意,每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使
  24. //⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法,但不是绝对必要的。
  25.     if (argc != 2) {
  26.         cerr << "Usage: " << argv[0] << "CONTACTS_FILE" << endl;
  27.         return -1;
  28.     }
  29.     // 以⼆进制⽅式读取 contacts
  30.     Contacts contacts;
  31.     fstream input(argv[1], ios::in | ios::binary);
  32.     if (!contacts.ParseFromIstream(&input)) {
  33.         cerr << "Failed to parse contacts." << endl;
  34.         input.close();
  35.         return -1;
  36.     }
  37.    
  38.     // 打印 contacts
  39.     PrintfContacts(contacts);
  40.     input.close();
  41.     google::protobuf::ShutdownProtobufLibrary();
  42.     return 0;
  43. }
复制代码
client ⽬录下新增 makefile
  1. client:client.cc contacts.pb.cc
  2.         g++ -o $@ $^ -std=c++11 -lprotobuf
  3. .PHONY:clean
  4. clean:
  5.         rm -f client
复制代码
  编译运行结果:


  接下来对service目次下的contacts.proto文件进行更新: 删除age字段, 新增birthday字段, 新增的字段使用被删除字段的字段编号.
更新后的contacts.proto内容如下:
  1. syntax ="proto3";
  2. package s_contacts;
  3. //联系人
  4. message PeopleInfo {
  5.   string name = 1;            //姓名
  6.   // 删除年龄字段
  7.   // int32 age = 2;              //年龄
  8.   int32 birthday = 2;         //生日
  9.   message Phone{
  10.     string number = 1;              //电话号码
  11.   }
  12.   repeated Phone phone = 3;       //电话
  13. }
  14. //通讯录
  15. message Contacts {
  16.   repeated PeopleInfo contacts = 1;
  17. }
复制代码
编译文件.proto后, 还需要更新一下对应的service.cc
  1. #include <iostream>
  2. #include <fstream>
  3. #include "contacts.pb.h"
  4. using namespace std;
  5. using namespace s_contacts;
  6. /**
  7. * 新增联系⼈
  8. */
  9. void AddPeopleInfo(PeopleInfo *people_info_ptr)
  10. {
  11.     cout << "-------------新增联系⼈-------------" << endl;
  12.     cout << "请输⼊联系⼈姓名: ";
  13.     string name;
  14.     getline(cin, name);
  15.     people_info_ptr->set_name(name);
  16.     // cout << "请输⼊联系⼈年龄: ";
  17.     // int age;
  18.     // cin >> age;
  19.     // people_info_ptr->set_age(age);
  20.     // cin.ignore(256, '\n');
  21.     cout << "请输入联系人生日: ";
  22.     int birthday;
  23.     cin >> birthday;
  24.     people_info_ptr->set_birthday(birthday);
  25.     cin.ignore(256, '\n');
  26.     for(int i = 1; ; i++) {
  27.         cout << "请输⼊联系⼈电话" << i << "(只输⼊回⻋完成电话新增): ";
  28.         string number;
  29.         getline(cin, number);
  30.         if (number.empty()) {
  31.             break;
  32.         }
  33.         PeopleInfo_Phone* phone = people_info_ptr->add_phone();
  34.         phone->set_number(number);
  35.     }
  36.     cout << "-----------添加联系⼈成功-----------" << endl;
  37. }
  38. int main(int argc, char *argv[])
  39. {
  40.     GOOGLE_PROTOBUF_VERIFY_VERSION;//: 验证没有意外链接到与编译的头⽂件不兼容的库版
  41. //本。如果检测到版本不匹配,程序将中⽌。注意,每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使
  42. //⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法,但不是绝对必要的。
  43.     if (argc != 2)
  44.     {
  45.         cerr << "Usage: " << argv[0] << " CONTACTS_FILE" << endl;
  46.         return -1;
  47.     }
  48.     Contacts contacts;
  49.    
  50.     // 先读取已存在的 contacts
  51.     fstream input(argv[1], ios::in | ios::binary);
  52.     if (!input) {
  53.         cout << argv[1] << ": File not found. Creating a new file." << endl;
  54.     }
  55.     else if (!contacts.ParseFromIstream(&input)) {
  56.         cerr << "Failed to parse contacts." << endl;
  57.         input.close();
  58.         return -1;
  59.     }
  60.     // 新增⼀个联系⼈
  61.     AddPeopleInfo(contacts.add_contacts());
  62.     // 向磁盘⽂件写⼊新的 contacts
  63.     fstream output(argv[1], ios::out | ios::trunc | ios::binary);
  64.     if (!contacts.SerializeToOstream(&output))
  65.     {
  66.         cerr << "Failed to write contacts." << endl;
  67.         input.close();
  68.         output.close();
  69.         return -1;
  70.     }
  71.     input.close();
  72.     output.close();
  73.     google::protobuf::ShutdownProtobufLibrary();
  74.     return 0;
  75. }
复制代码
  编译运行:


这时问题便出现了, 我们发现输入的生日, 在反序列化时, 被设置到了使用了相同字段编号的年事上, 所以得出结论: 如果移除老字段, 要包管不再使用移除字段的字段编号, 不建议直接删除或注释掉字段, 如果直接删除或注释掉, 则会导致背面的开发职员不知道这个字段是否被删除, 那么正确的做法是保存字段编号(reserved), 以确保该编号将不能被重复使用
  正确service目次下的contacts.proto写法如下:
  1. syntax ="proto3";
  2. package s_contacts;
  3. //联系人
  4. message PeopleInfo {
  5.   reserved 2;
  6.   string name = 1;            //姓名
  7.   // 删除年龄字段
  8.   // int32 age = 2;              //年龄
  9.   int32 birthday = 4;         //生日
  10.   message Phone{
  11.     string number = 1;              //电话号码
  12.   }
  13.   repeated Phone phone = 3;       //电话
  14. }
  15. //通讯录
  16. message Contacts {
  17.   repeated PeopleInfo contacts = 1;
  18. }
复制代码
  不要忘记更新完contacts.proto要进行编译并重新编译程序, 运行结果:


发现tianqi的年事为0, 这是由于新增时未设置年事, 通过client程序反序列化时, 给年事字段设置了默认值0.
    根据以上例子, 有的同砚可能尚有一个疑问: 如果使用了reserved 2了, 那么service给tianqi设置的生日1210, client就没法读到了吗? 答案是可以的, 继承学习下面的位置字段即可发表答案.
  未知字段

   在通讯录3.0版本中, 我们想service目次下的contacts.proto新增了’生日’字段, 但对于client的代码并没有任何改动, 新增的’生日’字段在旧程序(client)中其实并没有丢失, 而是会作为旧程序的未知字段
原来, proto3在解析消息时总是会抛弃未知字段, 但在3.5版本中重新引入了对未知字段的保存机制. 所以在3.5或更高版本中, 未知字段在反序列化时会被保存, 同时也会包罗在序列化的结果中.
    未知字段和自界说字段之间的关系图:

  升级通讯录3.1版本

验证未知字段

更新client.cc, 在这个版本中, 需要打印出未知字段的内容. 更新的代码如下:
  1. #include <iostream>
  2. #include <fstream>
  3. #include <google/protobuf/unknown_field_set.h>
  4. #include "contacts.pb.h"
  5. using namespace std;
  6. using namespace c_contacts;
  7. using namespace google::protobuf;
  8. /**
  9. * 打印联系⼈列表
  10. */
  11. void PrintfContacts(const Contacts& contacts) {
  12.     for (int i = 0; i < contacts.contacts_size(); ++i) {
  13.         const PeopleInfo& people = contacts.contacts(i);
  14.         cout << "------------联系⼈" << i+1 << "------------" << endl;
  15.         cout << "姓名:" << people.name() << endl;
  16.         cout << "年龄:" << people.age() << endl;
  17.         int j = 1;
  18.         for (const PeopleInfo_Phone& phone : people.phone()) {
  19.             cout << "电话" << j++ << ": " << phone.number() << endl;
  20.         }
  21.         // 打印未知字段
  22.         const Reflection* reflection = PeopleInfo::GetReflection();
  23.         const UnknownFieldSet& unknowSet = reflection->GetUnknownFields(people);
  24.         for (int j = 0; j < unknowSet.field_count(); ++j) {
  25.             const UnknownField& unknow_field = unknowSet.field(j);
  26.             cout << "未知字段" << j + 1 << ":"
  27.                  << "   字段编号: " << unknow_field.number()
  28.                  << " 类型:" << unknow_field.type();
  29.             switch (unknow_field.type()) {
  30.                 case UnknownField::Type::TYPE_VARINT:
  31.                     cout << " 值: " << unknow_field.varint() << endl;
  32.                     break;
  33.                 case UnknownField::Type::TYPE_LENGTH_DELIMITED:
  34.                     cout << " 值: " << unknow_field.length_delimited() << endl;
  35.                     break;
  36.             }
  37.         }
  38.     }
  39. }
  40. int main(int argc, char* argv[]) {
  41.     GOOGLE_PROTOBUF_VERIFY_VERSION;//: 验证没有意外链接到与编译的头⽂件不兼容的库版
  42. //本。如果检测到版本不匹配,程序将中⽌。注意,每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使
  43. //⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法,但不是绝对必要的。
  44.     if (argc != 2) {
  45.         cerr << "Usage: " << argv[0] << "CONTACTS_FILE" << endl;
  46.         return -1;
  47.     }
  48.     // 以⼆进制⽅式读取 contacts
  49.     Contacts contacts;
  50.     fstream input(argv[1], ios::in | ios::binary);
  51.     if (!contacts.ParseFromIstream(&input)) {
  52.         cerr << "Failed to parse contacts." << endl;
  53.         input.close();
  54.         return -1;
  55.     }
  56.    
  57.     // 打印 contacts
  58.     PrintfContacts(contacts);
  59.     input.close();
  60.     google::protobuf::ShutdownProtobufLibrary();
  61.     return 0;
  62. }
复制代码
  编译运行结果:

  前后兼容性

   根据上述的例子可以得出, pb是具有向前兼容的, 为了叙述方便, 把新增了 “生日” 属性的service称为 “新模块”; 未做变更的client称为 “老模块”
  

  • 向前兼容: 老模块可以或许正确识别新模块生成或发出的协议. 这时, 新增加的 “生日” 属性会被当做位置字段(pb 3.5版本及之后).
  • 向后兼容: 新模块也可以或许正确识别老模块生成或发出的协议.
    前后兼容的作用: 当我们维护一个很巨大的分布式系统时, 由于你无法同时升级全部模块, 为了包管在升级过程中, 整个系统可以或许尽可能不受影响, 就需要包管通讯协议的 “向前兼容” 或 “向后兼容”
  选项option

   .proto文件中可以声明许多选项, 使用 option标注. 选项能影响proto编译器的某些处理方式
举个例子:

进行编译

查看.h文件

自界说范例继承的Message
    我们加上这个option选项

进行编译, 查看.h文件

自界说范例继承的MessageLite, 这是什么意思呢? 请继承看背面
    option分为文件级, 消息级, 字段级 等等, 但并没有一种选项能作用于全部的范例
  常用选项列举
   optimize_for: 该选项为文件选项, 可以设置protoc编译器的优化级别, 分别为SPEED, CODE_SIZE, LITE_RUNTIME, 受该选项的影响, 设置不同的优化级别, 编译.proto文件后生成的代码内容不同
  

  • SPEED: protoc编译器将生成的代码是高度优化的, 代码运行效率高, 但是由此生成的代码编译后会占用更多的空间, SPEED是默认选项, 所以我们之前写的代码都是SPEED级别的
  • CODE_SIZE: proto编译器将生成最少的类, 会占用更少的空间, 但和SPEED恰恰相反, 它的代码运行效率较低, 这种方式适用在包罗大量的.proto文件, 但并不盲目追求速度的应用中.
  • LITE_RUNTIME: 生成的代码执行效率高, 同时生成代码编译后的所占用空间也是非常少, 这是以牺牲protobuf提供的反射功能为代价的(之前的图片可以看到, 自界说类是先继承message类, 再继承messagelite类. 所以这就没有message类所提供的能力, 好比说reflection, description), 仅仅提供encoding + 序列化功能, 所以我们在毗连BP库时仅需链接libprotobuf-lite, 而非libprotobuf, 这种模式通常用于资源有限的平台, 例如移动手机平台中
    allow_alias: 允许将相同的常量值分配给不同的罗列常量, 用来界说别名, 该选项为罗列选项.
举个例子:

    ProtoBuf允许自界说选项并使用, 该功能大部分场景用不到, 在这里不扩展讲解.

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

千千梦丶琪

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表