【ProtoBuf】Proto3 “密契” 精研:通讯录 2.x 版本的类型实战续篇

王者杯·14天创作挑战营·第5期 10w+人浏览 540人参与


在这里插入图片描述

引言

语法详解部分,依旧使⽤ 项⽬推进 的⽅式完成教学。这个部分会对通讯录进⾏多次升级,使⽤ 2.x表⽰升级的版本,最终将会升级如下内容:

  • 不再打印联系⼈的序列化结果,⽽是将通讯录序列化后并写⼊⽂件中。
  • 从⽂件中将通讯录解析出来,并进⾏打印。
    •- 新增联系⼈属性,共包括:姓名、年龄、电话信息、地址、其他联系⽅式、备注。

一、字段规则

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

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

更新 contacts.protoPeopleInfo消息中新增 phone_numbers 字段,表⽰⼀个联系⼈有多个号码,可将其设置为 repeated,写法如下:

syntax = "proto3";
package contants;


//定义联系人message
message PeopleInfo{
    string name = 1;            //姓名
    int32 age = 2;              //年龄
    repeated string phone = 3;  // 电话号码,可以有多个
}

二、消息类型的定义与使用

2.1 定义

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

更新 contacts.proto,我们可以将 phone_number 提取出来,单独成为⼀个消息:

// -------------------------- 嵌套写法 -------------------------
syntax = "proto3";
package contacts;
message PeopleInfo {
    string name = 1;
    int32 age = 2;
    //repeated string phone_numbers = 3;
	message Phone {
		string number = 1;
		}
}
// -------------------------- ⾮嵌套写法 -------------------------
syntax = "proto3";
package contacts;

message Phone {
	string number = 1;
}

message PeopleInfo {
	string name = 1;
	int32 age = 2;
}

2.2 使用

消息类型可作为字段类型使用

contacts.proto

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 消息类型并使⽤它们,反之亦然。

2.3 创建通讯录 2.0 版本

通讯录 2.x 的需求是向⽂件中写⼊通讯录列表,以上我们只是定义了⼀个联系⼈的消息,并不能存放通讯录列表,所以还需要在完善⼀下 contacts.proto (终版通讯录 2.0):

syntax = "proto3";
package contants;


//定义联系人message
message PeopleInfo{
    string name = 1;                    //姓名
    int32 age = 2;                      //年龄
    
    message Phone{
        string number = 1;              //电话号码
    }
    repeated Phone phones = 3;          //电话号码列表
}

//通讯录
message contacts{
    repeated PeopleInfo contacts = 1;   //联系人列表
}

编译后⽣成的 contacts.pb.hcontacts.pb.cc会将在快速上⼿的⽣成⽂件覆盖掉。

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

在这里插入图片描述
上述的例⼦中:

  • 每个字段都有⼀个clear_⽅法,可以将字段重新设置回 empty 状态。
  • 每个字段都有设置和获取的⽅法, 获取⽅法的⽅法名称与⼩写字段名称完全相同。但如果是消息类型的字段,其设置⽅法为mutable_⽅法,返回值为消息类型的指针,这类⽅法会为我们开辟好空间,可以直接对这块空间的内容进⾏修改。
  • 对于使⽤ repeated 修饰的字段,也就是数组类型,pb 为我们提供了 add_ ⽅法来新增⼀个值,并且提供了_size⽅法来判断数组存放元素的个数。

2.3.1 通讯录 2.0 的写入实现

write.cc(通讯录 2.0)

#include <iostream>
#include <fstream> //用于文件读写
#include "contacts.pb.h"

using namespace std;

void AddpeopleInfo(contacts2::PeopleInfo * people){
    cout << "-------------新增联系人-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');  //清除缓冲区
    
    for(int i = 0;;i++){
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if(number.empty()){
            break;
        }
        contacts2::PeopleInfo_Phone* phone = people->add_phones();
        phone->set_number(number);  
    }
}

int main()
{
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);  // 修正:使用 |
    if(!input){
        cout << "contacts.bin not find, create new file!" << endl;
    }else if(!contacts.ParseFromIstream(&input)){   //反序列失败
        cerr << "parse error!" << endl;  
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddpeopleInfo(contacts.add_contacts()); //实现写入逻辑,通讯录中插入了一个对象
    

    // 将通讯录写入本地文件中
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output)) {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}

  • fstream input(“contacts.bin”, ios::in | ios::binary); (其中覆盖写 trunc、二进制写binary。)
  • cin.ignore(256, ‘\n’); //清除缓冲区

makefile

write: write.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf

.PHONY:clean
clean:
	rm -f write

2.3.2 hexdump工具

hexdump 工具,将我们的二进制文件进行一个转换为对应的ASCII码值,以及八进制十六机制。

  • -C选项,表示我们将每个二进制字节输出为我们规范的16进制和对应的ASCII码值。

contact.bin 是本地生成的二进制文件,存放我们的数值

在这里插入图片描述

2.3.3 通讯录 2.0 的读取实现

read.cc

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;

void PrintContacts(contacts2::Contacts& contacts){

    
    for(int i = 0; i < contacts.contacts_size(); i++){
        cout << "---------------联系人" << i+1 << "---------------" << endl;
        const contacts2::PeopleInfo& people = contacts.contacts(i);
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        for(int j = 0; j < people.phones_size(); j++){
            const contacts2::PeopleInfo_Phone& phone = people.phones(j);
            cout << "联系人电话" << j + 1 << ":" << phone.number();
        }
    }
}

int main(){
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if(!contacts.ParseFromIstream(&input)){
        cerr << "parse error!" << endl;
        input.close();
        return -1; 
    }
    
    //打印通讯录列表
    PrintContacts(contacts);
    return 0;
}

makefile

all: write read

write:write.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf
read:read.cc contacts.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf

.PHONY:clean
clean:
	rm -f write read

在这里插入图片描述

2.3.4 验证方式-decode

我们可以⽤protoc -h命令来查看 ProtoBuf 为我们提供的所有命令 option。

其中 ProtoBuf 提供⼀个命令选项 --decode ,表⽰从标准输⼊中读取给定类型的⼆进制消息,并将其以⽂本格式写⼊标准输出。 消息类型必须在 .proto ⽂件或导⼊的⽂件中定义。
在这里插入图片描述

三、enum类型

3.1 定义

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

枚举类型名称:

  • 使⽤驼峰命名法,⾸字⺟⼤写。 例如: MyEnum

常量值名称:

  • 全⼤写字⺟,多个字⺟之间⽤ _ 连接。例如: ENUM_CONST = 0;

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

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

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

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

3.2 定义时注意

将两个 ‘具有相同枚举值名称’ 的枚举类型放在单个.proto文件下测试时,编译后会报错:某某某常量已经被定义!

所以这里要注意:

  • 同级(同层)的枚举类型,各个枚举类型中的常量不能重名。
  • 单个 .proto 文件下,最外层枚举类型和嵌套枚举类型,不算同级。
  • 多个 .proto 文件下,若一个文件引入了其他文件,且每个文件都未声明 package,每个proto文件中的枚举类型都在最外层,算同级。
  • 多个 .proto 文件下,若一个文件引入了其他文件,且每个文件都声明了 package,不算同级
// ---------------------- 情况1:同级枚举类型包含相同枚举值名称--------------------
enum PhoneType {
    MP = 0;  // 移动电话
    TEL = 1; // 固定电话
}

enum PhoneTypeCopy {
    MP = 0;  // 移动电话 // 编译后报错:MP 已经定义
}

// ---------------------- 情况2:不同级枚举类型包含相同枚举值名称-------------------
enum PhoneTypeCopy {
    MP = 0;  // 移动电话 // 用法正确
}

message Phone {
    string number = 1; // 电话号码
    enum PhoneType {
        MP = 0;  // 移动电话
        TEL = 1; // 固定电话
    }
}

// ---------------------- 情况3:多文件下都未声明package--------------------
// phone1.proto
import "phone2.proto"
enum PhoneType {
    MP = 0;  // 移动电话 // 编译后报错:MP 已经定义
    TEL = 1; // 固定电话
}

// phone2.proto
enum PhoneTypeCopy {
    MP = 0;  // 移动电话
}

// ---------------------- 情况4:多文件下都声明了package--------------------
// phone1.proto
import "phone2.proto"
package phone1;
enum PhoneType {
    MP = 0;  // 移动电话 // 用法正确
    TEL = 1; // 固定电话
}

// phone2.proto
package phone2;
enum PhoneTypeCopy {
    MP = 0;  // 移动电话
}

3.3 升级通讯录至 2.1 版本

更新 contacts.proto (通讯录 2.1),新增枚举字段并使用,更新内容如下:

syntax = "proto3";
package contacts2;


//定义联系人message
message PeopleInfo{
    string name = 1;                    //姓名
    int32 age = 2;                      //年龄
    
    message Phone{
        string number = 1;              //电话号码
        enum PhoneType
        {
            MP =0; //移动电话
            TEL = 1; // 固定电话
        }
    }
    repeated Phone phones = 3;          //电话号码列表
}

//通讯录
message Contacts{
    repeated PeopleInfo contacts = 1;   //联系人列表
}

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

新生成的 PeopleInfo_Phone_PhoneType 枚举类

在这里插入图片描述
上述的代码中:

  • 对于在.proto文件中定义的枚举类型,编译生成的代码中会含有与之对应的枚举类型、校验枚举
    值是否有效的方法 _IsValid、以及获取枚举值名称的方法 _Name
  • 对于使用了枚举类型的字段,包含设置和获取字段的方法,已经清空字段的方法clear_

更新 write.cc (通讯录 2.1)

#include <iostream>
#include <fstream> //用于文件读写
#include "contacts.pb.h"

using namespace std;

void AddpeopleInfo(contacts2::PeopleInfo * people){
    cout << "-------------新增联系人-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');  //清除缓冲区
    
    for(int i = 0;;i++){
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if(number.empty()){
            break;
        }
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_number(number);  

        cout << "请输入该电话类型(1、移动电话 2、固定电话): ";
        int type;
        cin >> tyep;
        cin.ignore(256, '\n');
        switch(type){
            case 1:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
                break;
            case 2:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
                break;
            default:
                cout << "选择有误!" << endl;
                break;
        }
    }
}

int main()
{
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);  
    if(!input){
        cout << "contacts.bin not find, create new file!" << endl;
    }else if(!contacts.ParseFromIstream(&input)){   //反序列失败
        cerr << "parse error!" << endl;  
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddpeopleInfo(contacts.add_contacts()); //实现写入逻辑,通讯录中插入了一个对象
    

    // 将通讯录写入本地文件中
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output)) {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}


在这里插入图片描述

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;

void PrintContacts(contacts2::Contacts& contacts){
    for(int i = 0; i < contacts.contacts_size(); i++){
        cout << "---------------联系人" << i+1 << "---------------" << endl;
        const contacts2::PeopleInfo& people = contacts.contacts(i);
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        for(int j = 0; j < people.phone_size(); j++){
            const contacts2::PeopleInfo_Phone& phone = people.phone(j);
            cout << "联系人电话" << j + 1 << ":" << phone.number();
            // 联系人电话1:1313131(MP)
            cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }
    }
}

int main(){
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if(!contacts.ParseFromIstream(&input)){
        cerr << "parse error!" << endl;
        input.close();
        return -1; 
    }
    
    //打印通讯录列表
    PrintContacts(contacts);
    return 0;
}

四、Any类型

字段还可以声明为 Any 类型,可以理解为泛型类型。使⽤时可以在 Any 中存储任意消息类型。Any 类型的字段也⽤
repeated 来修饰。

Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include ⽬录下查找所有google 已经定义好的 .proto ⽂件。

/usr/local/protobuf/include/google/protobuf 目录下可以进行查看。

在这里插入图片描述

4.1 升级通讯录⾄ 2.2 版本

通讯录 2.2 版本会新增联系⼈的地址信息,我们可以使⽤any类型的字段来存储地址信息。

问题:我们的Any类型中存放我们的地址信息也是Address类。

更新 contacts.proto (通讯录 2.2),更新内容如下:
在这里插入图片描述

syntax = "proto3";

package contacts2;
import "google/protobuf/any.proto";

//地址类
message Address{
    string home_address = 1; //家庭住址
    string unit_address = 2; //单位地址
}

//定义联系人message
message PeopleInfo{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    message Phone{
    string number = 1;
        enum PhoneType{
            MP = 0; //移动电话
            TEL = 1; //固定电话
        }
    }
    repeated Phone phone = 3; //电话
    google.protobuf.Any data = 4;  //Any类型
}

//通讯录
message Contacts{
    repeated PeopleInfo contacts = 1;
}

contacts.pb.h 更新的部分代码展⽰:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Protocol Buffers Any 类型的三个核心方法:

PackFrom:可以将任意的信息类型,转化为我们的Any类型

// 示例:将 Address 对象打包到 Any 中
Address address;
address.set_home_address("北京市朝阳区");
people->mutable_data()->PackFrom(address);

UpPackTo:可以将Any类型,转化为我们之前设置的任意信息类型

// 示例:从 Any 中解包出 Address 对象
Address address;
if (people->data().UnpackTo(&address)) {
    cout << "家庭地址: " << address.home_address() << endl;
}

Is方法:判断是否为T类型,Any存储了什么类型,用于判断类型使用

// 示例:检查 Any 中是否存储了 Address 类型
if (people->data().Is<Address>()) {
    cout << "存储的是 Address 类型" << endl;
}

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

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

更新 write.cc (通讯录 2.2)

#include <iostream>
#include <fstream> //用于文件读写
#include "contacts.pb.h"

using namespace std;

void AddpeopleInfo(contacts2::PeopleInfo * people){
    cout << "-------------新增联系人-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');  //清除缓冲区
    
    for(int i = 0;;i++){
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if(number.empty()){
            break;
        }
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_number(number);  

        cout << "请输入该电话类型(1、移动电话 2、固定电话): ";
        int type;
        cin >> tyep;
        cin.ignore(256, '\n');
        switch(type){
            case 1:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
                break;
            case 2:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
                break;
            default:
                cout << "选择有误!" << endl;
                break;
        }

        contacts2::Address address;
        cout << "请输入联系人家庭地址: " ;
        string home_address;
        getline(cin, home_address);
        address.set_home_address(home_address);
        cout << "请输入联系人单位地址: ";
        string unit_address;
        getline(cin, unit_address);
        address.set_unit_address(unit_address);

        //Address->Any
        people->mutable_data()->PackFrom(address);
    }
}

int main()
{
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);  
    if(!input){
        cout << "contacts.bin not find, create new file!" << endl;
    }else if(!contacts.ParseFromIstream(&input)){   //反序列失败
        cerr << "parse error!" << endl;  
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddpeopleInfo(contacts.add_contacts()); //实现写入逻辑,通讯录中插入了一个对象
    

    // 将通讯录写入本地文件中
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output)) {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}

mutable开辟一个空间,然后调用这个PackFromaddress类型转换为Any类型。

更新read.cc(通讯录 2.2)

在这里插入图片描述

  • has_data来判断我们有没有为这个data字段进行设置,如果设置了就返回true
  • 同时需要通过Is方法来判断是否是contacts2::Address方法。

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

5.1 升级通讯录⾄ 2.3 版本

通讯录 2.3 版本想新增联系⼈的其他联系⽅式,⽐如qq或者微信号⼆选⼀,我们就可以使⽤ oneof 字段来加强多选⼀这个⾏为。
oneof 字段定义的格式为:oneof 字段名 { 字段1; 字段2; … }

更新 contacts.proto (通讯录 2.3),更新内容如下:

其他联系方式:多选一

在这里插入图片描述

syntax = "proto3";

package contacts2;
import "google/protobuf/any.proto";

//地址类
message Address{
    string home_address = 1; //家庭住址
    string unit_address = 2; //单位地址
}

//定义联系人message
message PeopleInfo{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    message Phone{
    string number = 1;
        enum PhoneType{
            MP = 0; //移动电话
            TEL = 1; //固定电话
        }
    }
    repeated Phone phone = 3; //电话
    google.protobuf.Any data = 4;  //Any类型

    oneof other_contact{
        // repeated string qq = 5;  //不能使用 repeated
        string qq = 5;
        string wechat = 6;
    }
}

//通讯录
message Contacts{
    repeated PeopleInfo contacts = 1;
}

注意:

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

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

在这里插入图片描述
上述的代码中,对于 oneof 字段:

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

更新 write.cc (通讯录 2.3),更新内容如下:

在这里插入图片描述

#include <iostream>
#include <fstream> //用于文件读写
#include "contacts.pb.h"

using namespace std;

void AddpeopleInfo(contacts2::PeopleInfo * people){
    cout << "-------------新增联系人-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');  //清除缓冲区
    
    for(int i = 0;;i++){
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if(number.empty()){
            break;
        }
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_number(number);  

        cout << "请输入该电话类型(1、移动电话 2、固定电话): ";
        int type;
        cin >> tyep;
        cin.ignore(256, '\n');
        switch(type){
            case 1:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
                break;
            case 2:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
                break;
            default:
                cout << "选择有误!" << endl;
                break;
        }

        contacts2::Address address;
        cout << "请输入联系人家庭地址: " ;
        string home_address;
        getline(cin, home_address);
        address.set_home_address(home_address);
        cout << "请输入联系人单位地址: ";
        string unit_address;
        getline(cin, unit_address);
        address.set_unit_address(unit_address);

        //Address->Any
        people->mutable_data()->PackFrom(address);

        cout << "请选择要添加的其他联系方式(1、qq    2、微信号):";
        int other_contact;
        cin >> other_contact;
        cin.ignore(256,'\n');
          if (1 == other_contact) {
            cout << "请输入联系人qq号: ";
            string qq;
            getline(cin, qq);
            people->set_qq(qq);
        } else if (2 == other_contact) {
            cout << "请输入联系人微信号: ";
            string wechat;
            getline(cin, wechat);
            people->set_wechat(wechat);
        } else {
            cout << "选择有误,未成功设置其他联系方式!" << endl;
        }
    }
}

int main()
{
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);  
    if(!input){
        cout << "contacts.bin not find, create new file!" << endl;
    }else if(!contacts.ParseFromIstream(&input)){   //反序列失败
        cerr << "parse error!" << endl;  
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddpeopleInfo(contacts.add_contacts()); //实现写入逻辑,通讯录中插入了一个对象
    

    // 将通讯录写入本地文件中
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output)) {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}

更新 read.cc (通讯录 2.3),更新内容如下:
在这里插入图片描述

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;

void PrintContacts(contacts2::Contacts& contacts){
    for(int i = 0; i < contacts.contacts_size(); i++){
        cout << "---------------联系人" << i+1 << "---------------" << endl;
        const contacts2::PeopleInfo& people = contacts.contacts(i);
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        for(int j = 0; j < people.phone_size(); j++){
            const contacts2::PeopleInfo_Phone& phone = people.phone(j);
            cout << "联系人电话" << j + 1 << ":" << phone.number();
            // 联系人电话1:1313131(MP)
            cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }
        if(people.has_data() && people.data().Is<contacts2::Address()>()){
            contacts2::Address address;
            people.data().UppackTo(&address);
            if(!address.home_address().empty()){
                cout << "联系人家庭地址: " << address.home_address() << endl;
            }
            if(!address.unit_address().empty()){
                cout << "联系人单位地址: " << address.unit_address() << endl;
            }
        }
        
        switch(people.other_contact_case()){
            case contacts2::PeopleInfo::OtherContactCase::kQq;
                cout << "联系人qq:" << people.qq() << endl;
                break;
            case contacts2::PeopleInfo::OtherContactCase::kWechat;
                cout << "联系人微信号: " << people.wechat() << endl;
                break;
            default:
                break;
        }
    }
}

int main(){
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if(!contacts.ParseFromIstream(&input)){
        cerr << "parse error!" << endl;
        input.close();
        return -1; 
    }
    
    //打印通讯录列表
    PrintContacts(contacts);
    return 0;
}

在这里插入图片描述
OtherContactCase是一个枚举类型。

六、map类型

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

注意的是:

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

6.1 升级通讯录⾄ 2.4 版本

最后,通讯录 2.4 版本想新增联系⼈的备注信息,我们可以使⽤ map 类型的字段来存储备注信息。

更新 contacts.proto (通讯录 2.4),更新内容如下:
在这里插入图片描述

syntax = "proto3";

package contacts2;
import "google/protobuf/any.proto";

//地址类
message Address{
    string home_address = 1; //家庭住址
    string unit_address = 2; //单位地址
}

//定义联系人message
message PeopleInfo{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    message Phone{
    string number = 1;
        enum PhoneType{
            MP = 0; //移动电话
            TEL = 1; //固定电话
        }
    }
    repeated Phone phone = 3; //电话
    google.protobuf.Any data = 4;  //Any类型

    oneof other_contact{
        // repeated string qq = 5;  //不能使用 repeated
        string qq = 5;
        string wechat = 6;
    }

    map<stirng, stirng> remark = 7; //备注消息
}

//通讯录
message Contacts{
    repeated PeopleInfo contacts = 1;
}

contacts.pb.h 更新的部分代码展⽰:
在这里插入图片描述
上述的代码中,对于Map类型的字段:

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

更新 write.cc (通讯录 2.4),更新内容如下:

在这里插入图片描述

#include <iostream>
#include <fstream> //用于文件读写
#include "contacts.pb.h"

using namespace std;

void AddpeopleInfo(contacts2::PeopleInfo * people){
    cout << "-------------新增联系人-------------" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    people->set_age(age);
    cin.ignore(256, '\n');  //清除缓冲区
    
    for(int i = 0;;i++){
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if(number.empty()){
            break;
        }
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_number(number);  

        cout << "请输入该电话类型(1、移动电话 2、固定电话): ";
        int type;
        cin >> tyep;
        cin.ignore(256, '\n');
        switch(type){
            case 1:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
                break;
            case 2:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
                break;
            default:
                cout << "选择有误!" << endl;
                break;
        }

        contacts2::Address address;
        cout << "请输入联系人家庭地址: " ;
        string home_address;
        getline(cin, home_address);
        address.set_home_address(home_address);
        cout << "请输入联系人单位地址: ";
        string unit_address;
        getline(cin, unit_address);
        address.set_unit_address(unit_address);

        //Address->Any
        people->mutable_data()->PackFrom(address);

        cout << "请选择要添加的其他联系方式(1、qq    2、微信号):";
        int other_contact;
        cin >> other_contact;
        cin.ignore(256,'\n');
          if (1 == other_contact) {
            cout << "请输入联系人qq号: ";
            string qq;
            getline(cin, qq);
            people->set_qq(qq);
        } else if (2 == other_contact) {
            cout << "请输入联系人微信号: ";
            string wechat;
            getline(cin, wechat);
            people->set_wechat(wechat);
        } else {
            cout << "选择有误,未成功设置其他联系方式!" << endl;
        }

        for(int i = 0;; i++){
            cout << "请输入备注" << i + 1 << "标题(只输入回车完成备注新增):";
            stirng remark_key;
            getline(cin, remark_key);
            if(remark_key.empty()){
                break;
            }

            cout << "请输入备注" << i + 1 << "内容: ";
            string remark_value;
            getline(cin, remark_value);
            people->mutable_remark()->insert({remark_key, remark_value});
        }
       cout << "-----------添加联系⼈成功-----------" << endl;
    }
}

int main()
{
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);  
    if(!input){
        cout << "contacts.bin not find, create new file!" << endl;
    }else if(!contacts.ParseFromIstream(&input)){   //反序列失败
        cerr << "parse error!" << endl;  
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddpeopleInfo(contacts.add_contacts()); //实现写入逻辑,通讯录中插入了一个对象
    

    // 将通讯录写入本地文件中
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output)) {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}

更新 read.cc (通讯录 2.4),更新内容如下:

在这里插入图片描述

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;

void PrintContacts(contacts2::Contacts& contacts){
    for(int i = 0; i < contacts.contacts_size(); i++){
        cout << "---------------联系人" << i+1 << "---------------" << endl;
        const contacts2::PeopleInfo& people = contacts.contacts(i);
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        for(int j = 0; j < people.phone_size(); j++){
            const contacts2::PeopleInfo_Phone& phone = people.phone(j);
            cout << "联系人电话" << j + 1 << ":" << phone.number();
            // 联系人电话1:1313131(MP)
            cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }
        if(people.has_data() && people.data().Is<contacts2::Address()>()){
            contacts2::Address address;
            people.data().UppackTo(&address);
            if(!address.home_address().empty()){
                cout << "联系人家庭地址: " << address.home_address() << endl;
            }
            if(!address.unit_address().empty()){
                cout << "联系人单位地址: " << address.unit_address() << endl;
            }
        }
        
        switch(people.other_contact_case()){
            case contacts2::PeopleInfo::OtherContactCase::kQq;
                cout << "联系人qq:" << people.qq() << endl;
                break;
            case contacts2::PeopleInfo::OtherContactCase::kWechat;
                cout << "联系人微信号: " << people.wechat() << endl;
                break;
            default:
                break;
        }

        if(people.remark_size()){
            cout << "备注信息: " << endl;
        }
        for(auto it = people.remark().cbegin(); it != people.remark().cend();it++){
            cout <<" " << it->first << ": " << it->second << endl;
        }
    }
}

int main(){
    contacts2::Contacts contacts;
    // 读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if(!contacts.ParseFromIstream(&input)){
        cerr << "parse error!" << endl;
        input.close();
        return -1; 
    }
    
    //打印通讯录列表
    PrintContacts(contacts);
    return 0;
}

小结

本篇文章到此就暂告段落啦,希望能对大家的学习产生帮助,欢迎各位佬前来支持斧正!!!
在这里插入图片描述

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值