【Protobuf】Protobuf 语法介绍

news2024/11/14 17:16:40

Protobuf 语法介绍

  • 一、 字段规则
  • 二、消息类型的定义与使用
    • 1、练习——序列化后并写入文件
    • 2、练习——从文件中反序列化后打印输出
  • 三、enum 类型
    • 1、 定义规则
    • 2、 定义时注意事项
    • 3、查看枚举类的操作方法
    • 4、 实际使用
  • 四、Any 类型
    • 1、 介绍
    • 2、查看Any类的操作方法
    • 3、 实际使用
  • 五、 oneof 类型
    • 1、介绍
    • 2、查看oneof类的操作方法
    • 3、 实际使用
  • 六、map 类型
    • 1、介绍
    • 2、查看Map类的操作方法
    • 3、 实际使用
  • 七、总结

在前文中,我们已经对Protobuf 进行了基本的使用,本次这里我们继续通过项目升级的方式来学习Protobuf

本次升级如下内容:

  • 不再打印联系人的序列化结果,而是将通讯录序列化后并写入文件中。

一、 字段规则

消息的字段可以用下面几种规则来修饰:

  • singular :用于表示一个字段在消息中最多出现一次, proto3 语法中,字段默认使用该规则,但是在proto3语法中不可显示加上该规则。

singular 字段在序列化和反序列化中的行为:

  • 序列化时:如果一个 singular 字段从未被设置过,那么在序列化时,这个字段不会被编码到序列化的二进制流中。这意味着序列化的结果不会包含未设置的 singular 字段。
  • 反序列化时:如果在反序列化的过程中,序列化数据中没有找到对应的 singular 字段的编码值,那么 protobuf 会使用该字段的默认值来填充该字段。默认值通常是对于标量类型的零值(例如整数的默认值是 0,字符串的默认值是空字符串等)。
  • repeated :消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留,可以理为定义了一个数组。

由于联系人可能有多个号码,于是我们使用数组来进行存储号码,对应的.proto文件如下:

syntax = "proto3";
package contacts;

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

编译这个文件,生成C++ 头/源文件,我们继续看其内部生成的方法,这里我们只关注「数组」的操作方法(其他类型的方法已经在前一章节讲解过了)。

在这里插入图片描述

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

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

  1. 这里我们可以将 phone 提取出来,单独成为一个消息:
syntax = "proto3";
package contact;

message Phone
{
	string phone_number = 1;
}

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

编译这个文件,生成C++ 头/源文件,我们继续看其内部生成的方法,这里我们只关注「自定义对象」的操作方法。

在这里插入图片描述

  1. 这里我们还可以将 phone 进行嵌套在People中:
syntax = "proto3";
package contact;

message PeopleInfo
{
	string name = 1;
	int32 age = 2;
	message Phone
	{
		string phone_number = 1;
	}
	repeated Phone phone = 3;
}

使用嵌套,生成的操作方法和在外面单独定义phone 是没有区别,唯一的区别在于生成的类名前面会加一个 :父作用域的名称和下划线

在这里插入图片描述

  1. 除了这两种方式以外我们还可以将这个phone消息定义在另外一个文件中,然后在本文件中进行导入。
// contact2.proto 
syntax = "proto3";
package contact;
// 引入其他的proto文件
import "phone.proto";


message PeopleInfo
{
	string name = 1;
	int32 age = 2;
	// 引入的文件声明了package,使用消息时,需要用 ‘命名空间.消息类型’ 格式
	repeated phone.Phone phone = 3;
}

// phone.proto
syntax = "proto3";
package phone;

message Phone
{
	string phone_number = 1;
}

此时我们的编译指令就要需要写两个文件

protoc --cpp_out=. contact2.proto phone.proto 

此时会生成两个 头/源文件
在这里插入图片描述

总结,上述的例子中:

  • 每个字段都有一个 clear_ 方法,可以将字段重新设置回初始状态。
  • 每个字段都有设置和获取的方法, 获取方法的方法名称与小写字段名称完全相同,pb的内置类型有set 方法, 自定义消息类型没有。
  • 如果是自定义消息类型的字段,其设置方法为 mutable_ 方法,返回值为消息类型的指针,这类方法会为我们开辟好空间,可以直接对这块空间的内容进行修改。
  • 对于使用 repeated 修饰的字段,也就是数组类型,pb 为我们提供了 add_方法来新增一个值,也是会为我们开辟好空间,可以直接对这块空间的内容进行修改。
  • 对于使用 repeated 修饰的字段还会提供了 _size 方法来判断数组存放元素的个数。

1、练习——序列化后并写入文件

这里我们来实现将通讯录序列化后并写入文件中:

  • proto文件
syntax = "proto3";
package contact;

// 电话信息
message Phone
{
	string phone_number = 1;
}

// 联系人信息
message PeopleInfo
{
	string name = 1;
	int32 age = 2;
	// 可能有多个电话
	repeated Phone phone = 3;
}

// 通讯录信息
message Contacts
{
	repeated PeopleInfo peopleinfo = 2;
}
  • write源文件
#include <iostream>
#include <fstream>
#include <filesystem>
#include "contact2.pb.h"

using namespace std;
namespace fs = std::filesystem;


void addPeopleInfo(contact::Contacts& mycontact)
{
	contact::PeopleInfo* people = mycontact.add_peopleinfo();

	cout << "-------------新增联系人-------------" << endl;
	// 添加姓名
	string name;
	cout << "请输入联系人姓名:";
	getline(cin, name);
	people->set_name(name);

	// 添加年龄
	int age;
	cout << "请输入联系人年龄:";
	cin >> age;
	// 清除age 输入以后的\n
	cin.ignore();

	// 添加手机号码
	for (int i = 1; ; ++i)
	{
		cout << "请输入联系人电话" << i << "(ctrl + d 退出): ";
		string phone_number;
		if (cin >> phone_number)
		{
			contact::Phone* phone = people->add_phone();
			phone->set_phone_number(phone_number);
		}
		else
		{
			break;
		}
	}
	cout << "\n-----------添加联系人成功-----------" << endl;
}

int main()
{
	GOOGLE_PROTOBUF_VERIFY_VERSION;

	fs::path file_path("contacts.data");
	ofstream ofs;
	if (!fs::exists(file_path))
	{
		// 文件不存在创建文件
		ofs.open(file_path, ios::out | ios::binary);
	}
	else
	{
		// 文件存在打开文件
		ofs.open(file_path, ios::app | ios::binary);
	}

	if (!ofs.is_open())
	{
		perror("文件打开失败!");
		return 1;
	}

	//  到这里文件肯定是已经存在的并且可以正常打开

	// 创建通讯录
	contact::Contacts mycontact;
	ifstream ifs(file_path, ios::in | ios::binary);

	// 文件不为空文件
	if (ifs.peek() != EOF)
	{
		// 读取文件
		if (!mycontact.ParseFromIstream(&ifs))
		{
			cerr << "反序列化失败" << endl;
			return 1;
		}
	}

	// 添加新的联系人
	addPeopleInfo(mycontact);

	// 将通讯录中的数据持久化到文件中
	mycontact.SerializeToOstream(&ofs);

	ofs.close();
	google::protobuf::ShutdownProtobufLibrary();
	cout << "程序退出" << endl;
	return 0;
}
  • Makefile
PROTOSRC = contact2.proto
PROTOUT = contact2.pb.h contact2.pb.cc

write.out:write.cpp $(PROTOUT)
	g++ -o $@ $^ -std=c++17 -g -lprotobuf

$(PROTOUT):$(PROTOSRC)
	protoc --cpp_out=. $(PROTOSRC)

.PHONY:clean
clean:
	rm -f write.out contact2.pb*

这里解释一下两段特殊的代码

  • GOOGLE_PROTOBUF_VERIFY_VERSION;
  • GOOGLE_PROTOBUF_VERIFY_VERSION宏,验证没有意外链接到与编译的头文件不兼容的库版本。如果检测到版本不匹配,程序将中止。
  • 注意,每个 .pb.cc文件在启动时都会自动调用此宏。在使用 C++ Protocol Buffer 库之前执行此宏是一种很好的做法,但不是绝对必要的。
  • google::protobuf::ShutdownProtobufLibrary();
  • 在程序结束时调用 ShutdownProtobufLibrary(),为了删除 Protocol Buffer
    库分配的所有全局对象。对于大多数程序来说这是不必要的,因为该过程无论如何都要退出,并且操作系统将负责回收其所有内存。
  • 但是,如果你使用了内存泄漏检查程序,该程序需要释放每个最后对象,或者你正在编写可以由单个进程多次加载和卸载的库,那么你可能希望强制使用Protocol Buffers 来清理所有内容。

运行程序

在这里插入图片描述

我们使用hexdump命令来查看这个二进制文件中的内容

在这里插入图片描述

2、练习——从文件中反序列化后打印输出

  • read源文件
#include <iostream>
#include <fstream>
#include <filesystem>
#include "contact2.pb.h"

using namespace std;
namespace fs = std::filesystem;


void showContact(contact::Contacts& mycontact)
{
	int sz = mycontact.peopleinfo_size();
	for (int i = 0; i < sz; ++i)
	{
		cout << "---------------联系人" << i + 1 << "-----------------\n";
		const ::contact::PeopleInfo& peopleinfo = mycontact.peopleinfo(i);
		cout << "\t姓名 : " << peopleinfo.name() << endl;
		cout << "\t年龄 : " << peopleinfo.age() << endl;
		for (int j = 0; j < peopleinfo.phone_size(); ++j)
		{
			cout << "\t电话" << j + 1 << " : " << peopleinfo.phone(j).phone_number() << endl;
		}
		cout << "-------------------------END-----------------\n";
	}
}

int main()
{
	GOOGLE_PROTOBUF_VERIFY_VERSION;
	fs::path file_path("contacts.data");
	if (!fs::exists(file_path))
	{
		cerr << "文件不存在" << endl;
		return 1;
	}
	ifstream ifs(file_path, ios::in | ios::binary);
	if (!ifs.is_open())
	{
		perror("文件打开失败");
		return 1;
	}

	// 反序列化
	contact::Contacts mycontact;
	if (!mycontact.ParseFromIstream(&ifs))
	{
		cerr << "解析失败" << endl;
		return 1;
	}

	showContact(mycontact);
	google::protobuf::ShutdownProtobufLibrary();
	cout << "程序退出" << endl;
	return 0;
}
  • Makefile
PROTOSRC = contact2.proto
PROTOUT = contact2.pb.h contact2.pb.cc

all:write.out read.out

write.out:write.cpp $(PROTOUT)
	g++ -o $@ $^ -std=c++17 -g -lprotobuf

read.out:read.cpp $(PROTOUT)
	g++ -o $@ $^ -std=c++17 -g -lprotobuf

$(PROTOUT):$(PROTOSRC)
	protoc --cpp_out=. $(PROTOSRC)

.PHONY:clean
clean:
	rm -f write.out read.out contact2.pb*

执行结果

在这里插入图片描述


其实我们也可以使用下面的指令也可以直接查看序列化之后的文件内容:

在这里插入图片描述

这个MESSAGE_TYPE 就是我们序列化的对象的在.proto文件中的类型

protoc --decode=contact.Contacts contact2.proto  < contacts.data

在这里插入图片描述


三、enum 类型

1、 定义规则

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

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

例如下面的定义:

enum Gender
{
	MALE = 0;
	FEMALE = 1;
}

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

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

2、 定义时注意事项

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

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

3、查看枚举类的操作方法

我们更新.proto文件,然后进行编译查看protobuf给我们生成的方法有那些:

  • proto文件
syntax = "proto3";
package contact;

// 电话信息
message Phone
{
	string phone_number = 1;
}

// 联系人信息
message PeopleInfo
{
	// ----------------------- 新增
	// 性别
	enum Gender
	{
		MALE = 0;
		FEMALE = 1;
		SECRECY = 2;
	}

	string name = 1;
	int32 age = 2;
	// 可能有多个电话
	repeated Phone phone = 3;
	// 联系人的性别
	Gender gender = 4;
}

// 通讯录信息
message Contacts
{
	repeated PeopleInfo peopleinfo = 2;
}

当我们编译完这份代码以后,protobuf会给我们生成两部分代码:

  • 对于在.proto文件中定义的枚举类型,编译生成的代码中会含有与之对应的枚举类型校验枚举值是否有效的方法 _IsValid获取枚举值名称的方法 _Name,将枚举值名称转化为枚举类型的方法_Parse。(这些都是全局的方法)。
    在这里插入图片描述

  • 对于使用了枚举类型的字段,包含设置获取字段的方法,已经清空字段的方法clear_

在这里插入图片描述

4、 实际使用

  • write 文件 (这里我们只更改了addPeopleInfo 这个函数)
void addPeopleInfo(contact::Contacts& mycontact)
{
	contact::PeopleInfo* people = mycontact.add_peopleinfo();

	cout << "-------------新增联系人-------------" << endl;
	// 添加姓名
	string name;
	cout << "请输入联系人姓名:";
	getline(cin, name);
	people->set_name(name);

	// 添加年龄
	int age;
	cout << "请输入联系人年龄:";
	cin >> age;
	// 清除age 输入以后的\n
	cin.ignore();

	// 添加手机号码
	for (int i = 1; ; ++i)
	{
		cout << "请输入联系人电话" << i << "(quit表示退出): ";
		string phone_number;
		cin >> phone_number;
		if (phone_number != "quit")
		{
			contact::Phone* phone = people->add_phone();
			phone->set_phone_number(phone_number);
		}
		else
		{
			break;
		}
	}
	cin.ignore();
	cout << "\n请输入联系人的性别 (0.男性 1.女性 2.保密) : ";
	int sex = 0;
	cin >> sex;
	switch (sex)
	{
	case 0:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_MALE);
		break;
	case 1:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_FEMALE);
		break;
	default:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_SECRECY);
		break;
	}
	cout << "\n-----------添加联系人成功-----------" << endl;
}
  • read 源文件(这里我们只更改了showContact 这个函数)
void showContact(contact::Contacts& mycontact)
{
	int sz = mycontact.peopleinfo_size();
	for (int i = 0; i < sz; ++i)
	{
		cout << "---------------联系人" << i + 1 << "-----------------\n";
		const ::contact::PeopleInfo& peopleinfo = mycontact.peopleinfo(i);
		cout << "\t姓名 : " << peopleinfo.name() << endl;
		cout << "\t年龄 : " << peopleinfo.age() << endl;
		for (int j = 0; j < peopleinfo.phone_size(); ++j)
		{
			cout << "\t电话" << j + 1 << " : " << peopleinfo.phone(j).phone_number() << endl;
		}
		cout << "联系人的性别" << peopleinfo.Gender_Name(peopleinfo.gender()) << '\n';
		cout << "-------------------------END-----------------\n";
	}
}

编译之后运行,新增一个联系人以后,我们使用read 进行解析,可以看到,第一次我们序列化的结果aclie也带上了枚举类型,这是为什么呢?

在这里插入图片描述

实际上这可以用之前的修饰规则来解释,在proto3语法中每个字段默认都是有singular规则修饰的,因此被改规则修饰的字段在序列化的时候如果没有设置值,那么该字段不会被序列化,同理,如果在该字段的反序列化序列中找不到对应字段的编码值,那么该字段会被使用当前类型的默认值来进行填充,而我们的aclie的反序列化数据中是没有枚举数据类型的,因此在反序列化的时候,对于aclie的枚举字段则是用枚举类型的默认值来填充的,而当前枚举类型的默认值就是MALE,因此我们才看到aclie数据在枚举字段显示的是MALE;

四、Any 类型

1、 介绍

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

Any 类型是 google 已经帮我们定义好的类型,在安装 ProtoBuf 时,其中的 include 目录下查找所有google 已经定义好的 .proto 文件,因此当我们们需要使用Any类型时需要导入Any.proto文件

import "google/protobuf/any.proto";

例如我的安装目录:

ls /usr/local/protobuf/include/google/protobuf/

在这里插入图片描述

打开any.proto文件,可以看出Any是一个Message对象

在这里插入图片描述

2、查看Any类的操作方法

打开any.pb.h查看any中的的方法:

在这里插入图片描述

bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message);
bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const ;
bool Is() const 

接下来我们继续为通讯录中添加联系人的家庭住址

  • proto 文件
syntax = "proto3";
package contact;
import "google/protobuf/any.proto";

// 电话信息
message Phone
{
	string phone_number = 1;
}

message Address
{
	string home = 1;
	string company = 2; 
}

// 联系人信息
message PeopleInfo
{
	// 性别
	enum Gender
	{
		MALE = 0;
		FEMALE = 1;
		SECRECY = 2;
	}

	string name = 1;
	int32 age = 2;
	// 可能有多个电话
	repeated Phone phone = 3;
	// 联系人的性别
	Gender gender = 4;
	// 联系人的住址
	google.protobuf.Any data = 5;
}

// 通讯录信息
message Contacts
{
	repeated PeopleInfo peopleinfo = 2;
}

编译然后继续查看protobuf给我们生成的方法有那些:

可以看到,在包含Any类型的类中增加了下面的方法:
在这里插入图片描述

3、 实际使用

  • write 文件 (这里我们只更改了addPeopleInfo 这个函数)
void addPeopleInfo(contact::Contacts& mycontact)
{
	contact::PeopleInfo* people = mycontact.add_peopleinfo();

	cout << "-------------新增联系人-------------" << endl;
	// 添加姓名
	string name;
	cout << "请输入联系人姓名:";
	getline(cin, name);
	people->set_name(name);

	// 添加年龄
	int age;
	cout << "请输入联系人年龄:";
	cin >> age;
	people->set_age(age);
	// 清除age 输入以后的\n
	cin.ignore();

	// 添加手机号码
	for (int i = 1; ; ++i)
	{
		cout << "请输入联系人电话" << i << "(quit表示退出): ";
		string phone_number;
		cin >> phone_number;
		if (phone_number != "quit")
		{
			contact::Phone* phone = people->add_phone();
			phone->set_phone_number(phone_number);
		}
		else
		{
			break;
		}
	}
	cin.ignore();
	cout << "\n请输入联系人的性别 (0.男性 1.女性 2.保密) : ";
	int sex = 0;
	cin >> sex;
	switch (sex)
	{
	case 0:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_MALE);
		break;
	case 1:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_FEMALE);
		break;
	default:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_SECRECY);
		break;
	}
	cin.ignore();
	cout << " 请输入联系人家庭住址 : ";
	string addr;
	getline(cin, addr);
	contact::Address address;
	address.set_home(addr);
	cout << " 请输入联系人公司住址 : ";
	getline(cin, addr);
	address.set_company(addr);
	google::protobuf::Any* data = people->mutable_data();
	data->PackFrom(address);
	cout << "\n-----------添加联系人成功-----------" << endl;
}
  • read 源文件(这里我们只更改了showContact 这个函数)
void showContact(contact::Contacts& mycontact)
{
	int sz = mycontact.peopleinfo_size();
	for (int i = 0; i < sz; ++i)
	{
		cout << "---------------联系人" << i + 1 << "-----------------\n";
		const ::contact::PeopleInfo& peopleinfo = mycontact.peopleinfo(i);
		cout << "\t姓名 : " << peopleinfo.name() << endl;
		cout << "\t年龄 : " << peopleinfo.age() << endl;
		for (int j = 0; j < peopleinfo.phone_size(); ++j)
		{
			cout << "\t电话" << j + 1 << " : " << peopleinfo.phone(j).phone_number() << endl;
		}
		cout << "联系人的性别 : " << peopleinfo.Gender_Name(peopleinfo.gender()) << '\n';
		if (peopleinfo.has_data() && peopleinfo.data().Is<contact::Address>())
		{
			contact::Address address;
			peopleinfo.data().UnpackTo(&address);
			cout << "联系人的家庭地址 : " << address.home() << endl;
			cout << "联系人的公司地址 : " << address.company() << endl;
		}
	}
}

在这里插入图片描述

五、 oneof 类型

1、介绍

如果消息中有很多可选字段, 并且将来同时只有一个字段会被设置, 那么就可以使用 oneof 加强这个行为,也能有节约内存的效果,有些类似于C语言中的union联合体。

在 Protobuf 中,oneof 字段在序列化时会记录当前设置的字段类型,这有助于在反序列化时恢复正确的类型。

这里我们新增联系人的其他联系方式,比如qq或者微信号二选一,我们就可以使用 oneof 字段来加强多选一这个行为。
oneof 字段定义的格式为: oneof 字段名 { 字段1; 字段2; ... }
更新 contacts.proto ,更新内容如下:

syntax = "proto3";
package contact;
import "google/protobuf/any.proto";

// 电话信息
message Phone
{
	string phone_number = 1;
}

message Address
{
	string home = 1;
	string company = 2; 
}

// 联系人信息
message PeopleInfo
{
	// 性别
	enum Gender
	{
		MALE = 0;
		FEMALE = 1;
		SECRECY = 2;
	}

	string name = 1;
	int32 age = 2;
	// 可能有多个电话
	repeated Phone phone = 3;
	// 联系人的性别
	Gender gender = 4;
	// 联系人的住址
	google.protobuf.Any data = 5;
	// 其他联系方式
	oneof other_contact
	{
		string qq = 6;
		string wechat = 7;
	}
}

// 通讯录信息
message Contacts
{
	repeated PeopleInfo peopleinfo = 2;
}

注意:

  • oneof 中的变量隶属于上一个作用域,所以字段编号不能重复(oneof不会新增一个作用域!)
  • oneof 中的变量不能被repeated修饰

2、查看oneof类的操作方法

编译这个proto文件,查看内部生成的方法:

可以看到,protobuf为我们生成了一个枚举类:表示对应的字段名称

在这里插入图片描述

3、 实际使用

  • write 文件 (这里我们只更改了addPeopleInfo 这个函数)
void addPeopleInfo(contact::Contacts& mycontact)
{
	contact::PeopleInfo* people = mycontact.add_peopleinfo();

	cout << "-------------新增联系人-------------" << endl;
	// 添加姓名
	string name;
	cout << "请输入联系人姓名:";
	getline(cin, name);
	people->set_name(name);

	// 添加年龄
	int age;
	cout << "请输入联系人年龄:";
	cin >> age;
	people->set_age(age);
	// 清除age 输入以后的\n
	cin.ignore();

	// 添加手机号码
	for (int i = 1; ; ++i)
	{
		cout << "请输入联系人电话" << i << "(quit表示退出): ";
		string phone_number;
		cin >> phone_number;
		if (phone_number != "quit")
		{
			contact::Phone* phone = people->add_phone();
			phone->set_phone_number(phone_number);
		}
		else
		{
			break;
		}
	}
	cin.ignore();
	cout << "\n请输入联系人的性别 (0.男性 1.女性 2.保密) : ";
	int sex = 0;
	cin >> sex;
	switch (sex)
	{
	case 0:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_MALE);
		break;
	case 1:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_FEMALE);
		break;
	default:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_SECRECY);
		break;
	}
	cin.ignore();
	cout << " 请输入联系人家庭住址 : ";
	string addr;
	getline(cin, addr);
	contact::Address address;
	address.set_home(addr);
	cout << " 请输入联系人公司住址 : ";
	getline(cin, addr);
	address.set_company(addr);
	google::protobuf::Any* data = people->mutable_data();
	data->PackFrom(address);
flag:
	cout << "请输入其他的联系方式(0.qq 1.微信): ";
	int type = 0;
	cin >> type;
	cin.ignore();
	string number;
	switch (type)
	{
	case 0:
		cout << "请输入qq号码 : ";
		cin >> number;
		people->set_qq(number);
		break;
	case 1:
		cout << "请输入微信号码 : ";
		cin >> number;
		people->set_wechat(number);
		break;
	default:
		cout << "您的输入有误, 请重新输入\n";
		goto flag;
		break;
	}
	cout << "\n-----------添加联系人成功-----------" << endl;
}
  • read 源文件(这里我们只更改了showContact 这个函数)
void showContact(contact::Contacts& mycontact)
{
	int sz = mycontact.peopleinfo_size();
	for (int i = 0; i < sz; ++i)
	{
		cout << "---------------联系人" << i + 1 << "-----------------\n";
		const ::contact::PeopleInfo& peopleinfo = mycontact.peopleinfo(i);
		cout << "\t姓名 : " << peopleinfo.name() << endl;
		cout << "\t年龄 : " << peopleinfo.age() << endl;
		for (int j = 0; j < peopleinfo.phone_size(); ++j)
		{
			cout << "\t电话" << j + 1 << " : " << peopleinfo.phone(j).phone_number() << endl;
		}
		cout << "联系人的性别 : " << peopleinfo.Gender_Name(peopleinfo.gender()) << '\n';
		if (peopleinfo.has_data() && peopleinfo.data().Is<contact::Address>())
		{
			contact::Address address;
			peopleinfo.data().UnpackTo(&address);
			cout << "联系人的家庭地址 : " << address.home() << endl;
			cout << "联系人的公司地址 : " << address.company() << endl;
		}
		// 可以这样判断,但是如果种类很多代码不够简洁,这里我们采用 switch case
		// if (peopleinfo.has_qq())
		// {}
		// else if (peopleinfo.has_wechat())
		// {}

		switch (peopleinfo.other_contact_case())
		{
		case contact::PeopleInfo::kQq:
			cout << "qq号码 : " << peopleinfo.qq();
			break;
		case contact::PeopleInfo::kWechat:
			cout << "微信号码 : " << peopleinfo.wechat();
			break;
		default:
			break;
		}
		cout << "\n";
	}
}

在这里插入图片描述

六、map 类型

1、介绍

map 类型语法支持创建一个关联映射字段,也就是可以使用 map 类型去声明字段类型,格式为:map<key_type, value_type> map_field = N;
要注意的是:

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

利用这个特性我们在通讯录中新增联系人的备注信息,我们可以使用 map 类型的字段来存储备注信息。

  • proto文件如下:
syntax = "proto3";
package contact;
import "google/protobuf/any.proto";

// 电话信息
message Phone
{
	string phone_number = 1;
}

message Address
{
	string home = 1;
	string company = 2; 
}

// 联系人信息
message PeopleInfo
{
	// 性别
	enum Gender
	{
		MALE = 0;
		FEMALE = 1;
		SECRECY = 2;
	}

	string name = 1;
	int32 age = 2;
	// 可能有多个电话
	repeated Phone phone = 3;
	// 联系人的性别
	Gender gender = 4;
	// 联系人的住址
	google.protobuf.Any data = 5;
	// 其他联系方式
	oneof other_contact
	{
		string qq = 6;
		string wechat = 7;
	}
	// 备注信息
	map<string, string> remark = 8;
}

// 通讯录信息
message Contacts
{
	repeated PeopleInfo peopleinfo = 2;
}

2、查看Map类的操作方法

  • Map类的操作方法与unordered_map类似这里我们不再讲解

  • 在包含是Map字段的消息中会生成下面的的操作方法,(前面我们已经介绍过这里我们不在介绍)

在这里插入图片描述

3、 实际使用

  • write 文件 (这里我们只更改了addPeopleInfo 这个函数)
void addPeopleInfo(contact::Contacts& mycontact)
{
	contact::PeopleInfo* people = mycontact.add_peopleinfo();

	cout << "-------------新增联系人-------------" << endl;
	// 添加姓名
	string name;
	cout << "请输入联系人姓名:";
	getline(cin, name);
	people->set_name(name);

	// 添加年龄
	int age;
	cout << "请输入联系人年龄:";
	cin >> age;
	people->set_age(age);
	// 清除age 输入以后的\n
	cin.ignore();

	// 添加手机号码
	for (int i = 1; ; ++i)
	{
		cout << "请输入联系人电话" << i << "(quit表示退出): ";
		string phone_number;
		cin >> phone_number;
		if (phone_number != "quit")
		{
			contact::Phone* phone = people->add_phone();
			phone->set_phone_number(phone_number);
		}
		else
		{
			break;
		}
	}
	cin.ignore();
	cout << "\n请输入联系人的性别 (0.男性 1.女性 2.保密) : ";
	int sex = 0;
	cin >> sex;
	switch (sex)
	{
	case 0:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_MALE);
		break;
	case 1:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_FEMALE);
		break;
	default:
		people->set_gender(contact::PeopleInfo_Gender::PeopleInfo_Gender_SECRECY);
		break;
	}
	cin.ignore();
	cout << " 请输入联系人家庭住址 : ";
	string addr;
	getline(cin, addr);
	contact::Address address;
	address.set_home(addr);
	cout << " 请输入联系人公司住址 : ";
	getline(cin, addr);
	address.set_company(addr);
	google::protobuf::Any* data = people->mutable_data();
	data->PackFrom(address);
flag:
	cout << "请输入其他的联系方式(0.qq 1.微信): ";
	int type = 0;
	cin >> type;
	cin.ignore();
	string number;
	switch (type)
	{
	case 0:
		cout << "请输入qq号码 : ";
		cin >> number;
		people->set_qq(number);
		break;
	case 1:
		cout << "请输入微信号码 : ";
		cin >> number;
		people->set_wechat(number);
		break;
	default:
		cout << "您的输入有误, 请重新输入\n";
		goto flag;
		break;
	}
	cin.ignore();
	cout << "请输入备注 : ";
	string remark;
	getline(cin, remark);
	cout << "请输入备注信息 : ";
	string content;
	getline(cin, remark);
	google::protobuf::Map<string, string>* hash = people->mutable_remark();
	hash->insert({remark, content});

	cout << "\n-----------添加联系人-----------" << endl;
}
  • read文件 (这里我们只更改了addPeopleInfo 这个函数)
void showContact(contact::Contacts& mycontact)
{
	int sz = mycontact.peopleinfo_size();
	for (int i = 0; i < sz; ++i)
	{
		cout << "---------------联系人" << i + 1 << "-----------------\n";
		const ::contact::PeopleInfo& peopleinfo = mycontact.peopleinfo(i);
		cout << "\t姓名 : " << peopleinfo.name() << endl;
		cout << "\t年龄 : " << peopleinfo.age() << endl;
		for (int j = 0; j < peopleinfo.phone_size(); ++j)
		{
			cout << "\t电话" << j + 1 << " : " << peopleinfo.phone(j).phone_number() << endl;
		}
		cout << "联系人的性别 : " << peopleinfo.Gender_Name(peopleinfo.gender()) << '\n';
		if (peopleinfo.has_data() && peopleinfo.data().Is<contact::Address>())
		{
			contact::Address address;
			peopleinfo.data().UnpackTo(&address);
			cout << "联系人的家庭地址 : " << address.home() << endl;
			cout << "联系人的公司地址 : " << address.company() << endl;
		}
		// 可以这样判断,但是如果种类很多代码不够简洁,这里我们采用 switch case
		// if (peopleinfo.has_qq())
		// {}
		// else if (peopleinfo.has_wechat())
		// {}

		switch (peopleinfo.other_contact_case())
		{
		case contact::PeopleInfo::kQq:
			cout << "qq号码 : " << peopleinfo.qq();
			break;
		case contact::PeopleInfo::kWechat:
			cout << "微信号码 : " << peopleinfo.wechat();
			break;
		default:
			break;
		}
		cout << "\n";
		auto& hash = peopleinfo.remark();
		for (auto it = hash.begin(); it != hash.end(); ++i)
		{
			cout << "联系人备注 : " << it->first << endl;
			cout << "联系人备注信息 : " << it->second << endl;
		}
	}
}

在这里插入图片描述

七、总结

至此,我们的Protobuf 语法已经介绍完毕,我们现在来总结一下protobuf给我们生成的方法:

  • 内置标量类型
xxx();                  //获取字段(const 对象)
set_xxx();              //设置字段
clear_xxx();            //清除字段
mutable_xxx();          //获取字段的地址(诸如string、bytes等类型才会生成,其它内置类型不会);
  • 数组
xxx_size();               //获取数组元素个数;
xxx(index);               //获取第index个元素;
mutable_xxx(index);	      //获取第index个元素的;
clear_xxx();              //清理数组
add_xxx();                //新增一个元素(返回地址以便于修改)
  • message自定义类型
has_xxx();                 //判断该字段是否被设置
xxx();                     //获取该字段(const 对象)
mutable_xxx();             //获取该字段的地址;
clear_xxx();               //清理该字段
release_xxx();			   //释放所有权给外部进行管理,自己不再进行管理
  • enum枚举字段
// 注意:其中XXX为枚举类型名, yyy为字段名

// 三个全局方法:
XXX_IsValid(values);               //判断values是不是枚举常量
XXX_Name(values);                  //将枚举常量values变为字符串
XXX_Parse(StringName, values);     //将枚举字符串变为枚举值

// 包含枚举字段的消息内部生成的方法:
clear_yyy();                       //清理设置的枚举值
yyy();	                           //获取设置的枚举值
set_yyy();                         //设置枚举值
yyy_Name(values);                  //将枚举常量values变为字符串
yyy_Parse(StringName, values);     //将枚举字符串变为枚举值
  • Any字段
// Any类内部方法:
PackFrom(mes);         //将mes设置给Any字段
UnpackTo(mes);         //将Any字段还原成mes对象
template< class T>
bool Is();             //判断挡墙Any字段中是不是存的T类型的值;

// 包含Any字段的消息内部生成的方法:
has_xxx();             //Any字段是否被设置
clear_xxx();           //清理
xxx();                 //获取Any字段
mutable_xxx();         //获取any字段地址
release_xxx() 		   //释放所有权给外部进行管理,自己不再进行管理
  • oneof字段
// 注意:其中XXX为枚举类型名, yyy为字段名

// 包含oneof字段的消息内部生成的方法:
has_yyy();
yyy();
clear_yyy();
set_yyy();

clear_XXX();    //清理oneof字段里放的值
XXX_case();     //获取oneof字段使用的那个字段的枚举类型
  • map字段
// 包含map字段的类内部生成的字段 
clear_xxx();
xxx_size();
xxx();
mutable_xxx();
  • 常用序列化和反序列化方法:
// 常用序列化方法:
bool SerializeToOstream(ostream * output) const;     //将序列化结果放入流里面(标准流、文件流、字符串流);
bool SerializeToArray(void *data, int size) const;   //将序列化结果放入字节流里面
bool SerializeToString(string* output) const;        //将序列化结果放入字符串里面

// 常用反序列化方法:
bool ParseFromIstream(istream* input);               //从流里面读取反序列化结果;
bool ParseFromArray(const void* data, int size);     //从字节流里面读取反序列化结果;
bool ParseFromString(const string& data);            //从字符串中读取反序列化结果

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全审计技术原理与应用

网络安全审计概述 概念 定义:对网络信息系统的安全相关活动信息进行获取、记录、存储、分析和利用的工作 作用:建立“事后”安全保障措施,保存网络安全事件及行为信息,为网络安全事件分析提供线索及证据,以便发现潜在网络安全威胁行为,开展网络安全风险分析及管理 常…

热搜第一!网易云音乐“崩了”

昨日&#xff08;19日&#xff09;&#xff0c;网易云音乐发生故障&#xff0c;迅速登上热搜&#xff0c;引发了广泛关注。 当天下午&#xff0c;有用户反映网易云音乐官网无法访问。同时&#xff0c;网易云音乐App中的会员中心、创作者中心、商城等多个功能均出现异常&#x…

陕西文无文化与韩国RB娱乐签订2000亿韩元合作备忘录

近日&#xff0c;韩国知名制作人、延世大学教授、(株)RB Entertainment 公司社长尹钟豪先生在辰海资本&#xff08;亚州&#xff09;投资有限公司董事、国标舞世界冠军获得者王煜明先生的陪同下&#xff0c;对中国文化市场进行考察调研&#xff0c;先后在北京、西安等地与当地影…

一起学习LeetCode热题100道(47/100)

47.从前序与中序遍历序列构造二叉树(学习) 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,7], inorder [9…

nginx变量+rewrite相关功能+反向代理+openresty

目录 1、nginx变量 &#xff08;1&#xff09;内置变量 示例 &#xff08;2&#xff09;自定义变量 示例 2、nginx中的rewrite模块功能 &#xff08;1&#xff09;ngx_http_rewrite_module 模块指令 1&#xff09;if指令 示例 2&#xff09;set指令 示例​编辑 3&am…

代码随想录算法训练营day48:单调栈

目录 739. 每日温度 503.下一个更大元素II 分析&#xff1a; 42. 接雨水 本质&#xff1a; 暴力解法 分析&#xff1a; 双指针优化 单调栈 84.柱状图中最大的矩形 分析&#xff1a; 双指针&#xff1a; 单调栈 739. 每日温度 力扣题目链接(opens new window) 请根…

这个大佬一年连中五篇顶会!

1、SLAM/3DGS/三维点云/医疗图像/扩散模型/结构光/Transformer/CNN/Mamba/位姿估计 顶会论文指导 2、基于扩散模型的跨域鲁棒自动驾驶场景理解 3、基于环境信息的定位&#xff0c;重建与场景理解 4、轻量级高保真Gaussian Splatting 5、基于大模型与GS的 6D pose estimatio…

【Redis】Redis线程与IO模型—(三)

Redis线程与IO模型 一、Redis 单线程二、多路复用机制三、Redis 6.0 多线程特性四、IO 多线程配置 一、Redis 单线程 通常说 Redis 是单线程&#xff0c;主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的&#xff0c;其他功能&#xff0c;比如持久化、异步删除、集…

软件测试之全面质量管理

一.什么是TQM&#xff1f; 全面质量管理 英文&#xff1a;Total Quality Management TQM可以被定义为一种管理技术&#xff0c;用于改进与产品相关的过程、产品、服务和其他方法。 它关注的是整个业务&#xff0c;而不仅仅是一个特定的项目或过程。 二.TQM原则 以顾客为关注…

软考作弊率下降了78.68%!官方为了防止作弊做出了哪些努力?

01\软考违纪违规人数对比 2024年上半年软考&#xff08;机考&#xff09;共有52名考生被判违纪违规行为&#xff0c;其中浙江考区有9人&#xff0c;山东枣庄考区有10人&#xff0c;江苏考区有33人。 2023年下半年软考&#xff08;机考&#xff09;共有7名考生被判违纪违规行为…

磁盘格式化文件恢复:一文看懂数据恢复操作

当你意识到关键的硬盘已经被格式化&#xff0c;而且你不能获取里面的内容时&#xff0c;这会是非常令人沮丧的。这种情况可能是因为硬盘被不小心格式化&#xff0c;或者是你在试图修正一些问题、调整文件系统或者释放存储空间时&#xff0c;有意进行的格式化。无论具体情况是什…

【论文学习与撰写】论文中公式的编辑,Mathtype的使用,全文编号排版,智能截图识别公式,公式编号自动更新

1、准备工作 在word中安装mathtype插件&#xff0c; 2、插入公式 在想要插入公式的地方&#xff0c;点击右编号&#xff0c;在里面输入公式&#xff0c;保存&#xff0c;关闭&#xff0c;就会得到插入的公式。 3、公式编号的编辑&#xff0c;公式编号自动更新 要想插入的公式…

笔试练习day5

目录 游游的you题目解析解法方法一贪心方法二 腐烂的苹果题目解析例子1例子2解法多源BFS最短路径代码代码解析 JZ62 孩子们的游戏(圆圈中最后剩下的数)题目解析解法方法一模拟环形链表模拟数组模拟 方法二递推/递归/动态规划状态表示状态转移方程代码 感谢各位大佬对我的支持,如…

CORS error 302 Found

CORS error && 302 Found 场景 单点登录认证&#xff1a;访问A系统&#xff0c;在B系统登录认证 此处代码为A系统 controller ResponseBodyGetMapping("/idp/loginCheck")public void loginCheck(HttpServletRequest request, HttpServletResponse httpR…

基于vue框架的爱学习分享平台ud317(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,学科分类,交流答疑,论坛交流,学习资料 开题报告内容 基于Vue框架的爱学习分享平台 开题报告 一、项目背景与意义 随着互联网技术的飞速发展&#xff0c;知识的获取与传播方式正经历着前所未有的变革。在线教育平台逐渐成为满足…

【独立站搭建经验分享】B2C独立站如何搭建?怎么推广?

如果你的产品有C端属性&#xff0c;可能你就需要考虑建一个B2C独立站&#xff0c;用来满足访客的浏览和在线下单。那么B2C独立站应该怎么搭建&#xff0c;选择什么推广方式&#xff0c;这个前期最好有一些基本了解&#xff0c;本篇内容可以针对你最关心的问题进行讲解&#xff…

【文献】3D Gaussian Splatting for Real-Time Radiance Field Rendering

论文地址&#xff1a;https://arxiv.org/abs/2308.04079 项目&#xff1a; https://repo-sam.inria.fr/fungraph/3d-gaussian-splatting/ 代码&#xff1a; git clone https://github.com/graphdeco-inria/gaussian-splatting —recursive 一、文章概述 1.问题导向 辐射场…

《笑谈设计模式》 — 23种尝鲜版(未完待续......

引子&#xff1a;无论在平时开发过程中&#xff0c;还是深夜研读源码亦或者面试时都对遇到——设计模式。比如说Spring中的单例模式&#xff08;bean单例&#xff09;、工厂模式&#xff08;bean创建&#xff09;、代理模式&#xff08;动态代理&#xff09;、策略模式等。我们…

RTA-VRTE适配Orin

RTA-VRTE适配Orin sudo minicom -w -D /dev/ttyACM0 用户名 nvidia密码 123456底下的的rj45 ,对应的是eqos_0, (本次porting使用该接口)底下的的DEBUG(USB Micro-B)串口连接(本次porting使用该接口)上侧边的rj45,对应的是mgbe0_0#设置Orin的IP和默认网卡eqos_0

C语言:一维、二维数组详解

目录 一、数组的概念 二、一维数组创建和初始化 2.1 数组创建 2.2 数组的初始化 2.3 数组的类型 三、一维数组的使用 3.1 数组的下标 3.2 数组的输入与输出 四、⼀维数组在内存中的存储 五、sizeof计算数组元素个数 六、二维数组 6.1 二维数组的概念 6.2 二维数组…