【ProtoBuf】proto3语法

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

前言

我们依旧使用项目推进的方式去认识proto3语法,这个部分会对通讯录进行多次升级,使用2.x 表示升级的版本,最终将会升级如下内容:

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

字段规则

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

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

更新 contacts.proto , PeopleInfo 消息中新增 phone_numbers 字段,表示一个联系人有多个号码,可将其设置为 repeated,写法如下:

// 定义联系人message
message PeopleInfo(){
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

    repeated string phone_numbers = 3; 
}

下面还可以定义消息体,比如电话信息包含多个电话、电话号呢类型等,下面定义消息体可以解决这个问题。

消息类型的定义与使用

定义

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

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


// -------------------------- 嵌套写法 -------------------------
// 定义联系人message
message PeopleInfo(){
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

    message Phone{
   
   
    string  numbers = 1;
    }
}

// -------------------------- 非嵌套写法 -------------------------
message Phone{
   
   
    string  numbers = 1;
}

// 定义联系人message
message PeopleInfo(){
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄
 
}

使用

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

contacts.proto

// -------------------------- 嵌套写法 -------------------------
syntax = "proto3";
package contacts2;

// 定义联系人message
message PeopleInfo(){
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

    message Phone{
   
   
    string  numbers = 1;
    }

    repeated Phone phone = 3; //使用
}

// -------------------------- 非嵌套写法 -------------------------
syntax = "proto3";
package contacts2;

message Phone{
   
   
    string  numbers = 1;
}

// 定义联系人message
message PeopleInfo(){
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

    repeated Phone phone = 3; 、、使用
}
  • 可导入其他 .proto 文件的消息并使用

例如 Phone 消息定义在 phone.proto 文件中:

syntax = "proto3";
package phone;

message Phone{
   
   
    string  numbers = 1;
} 

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

syntax = "proto3";
package contacts2;

import "phone.proto"; // 使用 import 将 phone.proto 文件导入进来 !!!

// 定义联系人message
message PeopleInfo(){
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

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

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

创建通讯录 2.0 版本

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

syntax = "proto3";
package contacts2;


// 定义联系人message
message PeopleInfo{
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

    message Phone{
   
   
    string  numbers = 1;
    }

    repeated Phone phone = 3; // 电话信息
}

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

}

接着进行一次编译:

protoc --cpp_out=. contacts.proto

contacts.pb.h 更新的部分代码展示:
在这里插入图片描述

上述的例子中:

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

通讯录 2.0 的写入实现

write.cc (通讯录 2.0)

#include <iostream>
#include <fstream>
#include <limits>  // 用于处理输入缓冲区的清理

// 引入protobuf自动生成的头文件,包含通讯录数据结构定义
#include "contacts.pb.h"

using namespace std;


//@brief 是 Doxygen 的标签,表示 “简要说明”。
//@param 是 Doxygen 的标签,表示 “函数参数说明”。

/**
 * @brief 向联系人信息对象中添加数据
 * @param people 指向待填充数据的PeopleInfo对象的指针
 */
void AddPeopleInfo(contacts2::PeopleInfo* people){
   
   
    cout << "-------------新增联系人-------------" << endl;
    
    // 读取联系人姓名
    cout << "请输入联系人姓名: ";
    string name;
    getline(cin, name);  // 使用getline可读取包含空格的完整姓名
    people->set_name(name);  // 调用protobuf生成的setter方法设置姓名
    
    // 读取联系人年龄
    cout << "请输入联系人年龄: ";
    int age;
    cin >> age;  // 读取整数年龄
    people->set_age(age);  // 设置年龄
    
    // 清理输入缓冲区中的残留换行符
    // numeric_limits<streamsize>::max()确保清空所有剩余字符
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    
    // 循环读取多个电话号码,直到用户输入空行为止
    for(int i=0; ; i++){
   
   
        cout << "请输入联系人电话: " << i+1 << "(只输入回车完成电话新增): ";
        string number;
        getline(cin, number);  // 读取电话号码
        
        // 如果用户输入空行,则退出循环,结束电话号码输入
        if(number.empty()){
   
   
            break;
        }
        
        // 调用protobuf生成的add_phone()方法添加新电话
        // 该方法会返回一个指向新创建的Phone对象的指针
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_numbers(number);  // 设置电话号码
    }

    cout << "-------------添加联系人成功-------------" << endl;
}

int main()
{
   
   
    // 验证protobuf库版本是否与编译时一致,确保兼容性
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    // 创建通讯录主对象,用于存储所有联系人信息
    contacts2::Contacts contacts;

    // 尝试读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if(!input){
   
   
        // 文件不存在时提示将创建新文件
        cout << "contacts.bin  not find, creat new file! " << endl;
    }else if(!contacts.ParseFromIstream(&input)){
   
   
        // 解析文件失败时输出错误信息并退出
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

    // 关闭输入流,避免资源占用
    input.close();

    // 向通讯录中添加新联系人
    // add_contacts()方法会创建一个新的PeopleInfo对象并返回其指针
    AddPeopleInfo(contacts.add_contacts());

    // 打开文件用于写入更新后的通讯录数据
    // ios::out: 输出模式;ios::trunc: 若文件存在则清空;ios::binary: 二进制模式
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if(!contacts.SerializeToOstream(&output)){
   
   
        // 序列化并写入失败时处理
        cerr << "write error!" << endl;
        output.close();
        return -1;
    }

    // 提示写入成功并关闭输出流
    cout << "write success!" << endl;
    output.close();

    // 清理protobuf库分配的全局资源,适合在长时间运行的程序中使用
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

makefile

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

make之后,运行 write
在这里插入图片描述

查看二进制文件

在这里插入图片描述

解释:
hexdump:是Linux下的一个二进制文件查看工具,它可以将二进制文件转换为ASCII、八进制、十进制、十六进制格式进⾏查看。
-C: 表示每个字节显⽰为16进制和相应的ASCII字符

通讯录 2.0 的读取实现

read.cc (通讯录 2.0)

#include <iostream>
#include <fstream>

// 引入protobuf自动生成的通讯录数据结构定义头文件
#include "contacts.pb.h"

using namespace std;



//@brief 是 Doxygen 的标签,表示 “简要说明”。
//@param 是 Doxygen 的标签,表示 “函数参数说明”。

/**
 * @brief 打印通讯录中的所有联系人信息
 * @param contacts 常量引用,指向存储所有联系人数据的protobuf对象
 *                 使用const确保函数不会修改原始数据,提高安全性
 */
void PrintContacts(const contacts2::Contacts& contacts){
   
   
    // 遍历所有联系人,contacts_size()返回联系人总数
    for(int i = 0; i < contacts.contacts_size(); i++){
   
   
        cout << "--------------------联系人" << i+1 << "--------------------" << endl;
        // 通过contacts(i)获取第i个联系人的详细信息(protobuf生成的访问方法)
        const contacts2::PeopleInfo& people = contacts.contacts(i);
        
        // 打印联系人基本信息,调用protobuf生成的getter方法(name()、age())
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        
        // 遍历当前联系人的所有电话号码,phone_size()返回电话号码总数
        for(int j= 0; j < people.phone_size(); j++){
   
   
            // 通过phone(j)获取第j个电话号码对象
            const contacts2::PeopleInfo_Phone& phone = people.phone(j);
            cout << "联系人电话" << j+1 << ":" << phone.numbers() << endl;
        }
    }
}


int main(){
   
   
    // 创建protobuf通讯录对象,用于存储从文件读取的数据
    contacts2::Contacts contacts;

    // 尝试打开本地通讯录文件(二进制读模式)
    fstream input("contacts.bin", ios::in | ios::binary);
    
    // 检查文件是否成功打开
    if (!input.is_open()) {
   
     
        cerr << "无法打开文件 contacts.bin!" << endl;
        return -1;
    }
    // 从文件流中解析protobuf数据(反序列化过程)
    else if(!contacts.ParseFromIstream(&input)){
   
   
        cerr << "解析文件失败! 可能是文件格式错误或损坏" << endl;
        input.close();  // 出错时关闭文件流
        return -1;
    }
    
    // 关闭输入文件流(数据已读取完毕)
    input.close();
    
    // 调用打印函数,展示所有联系人信息
    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

make 后运行 read
在这里插入图片描述

另一种验证方法–decode
我们可以用 protoc -h 命令来查看 ProtoBuf 为我们提供的所有命令 option。
其中 ProtoBuf 提供一个命令选项 --decode ,表示从标准输入中读取给定类型的二进制消息,并将其以文本格式写入标准输出。 消息类型必须在 .proto 文件或导入的文件中定义。
在这里插入图片描述

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

这个命令是使用 Protocol Buffers(protobuf)工具protoc来解码二进制数据的指令,各部分含义如下:

protoc:protobuf 的官方命令行工具,用于处理.proto文件(协议定义文件)
--decode=contacts2.Contacts:指定解码模式,并指定要解码成的消息类型
contacts2是消息类型所在的包名(package)
Contacts是具体的消息类型名称
contacts.proto:协议定义文件,其中包含了contacts2.Contacts消息类型的结构定义
< contacts.bin:通过管道(pipe)将contacts.bin文件中的二进制数据作为输入传递给protoc

整个命令的作用是:使用contacts.proto中定义的contacts2.Contacts消息结构,来解码contacts.bin文件中的二进制数据,并将解码后的结果以人类可读的文本形式输出到控制台。

这通常用于调试 protobuf 数据,查看二进制文件中实际存储的内容。执行该命令前需要确保contacts.proto文件存在且正确定义了contacts2.Contacts消息类型。

在这里插入图片描述

enum 类型

定义规则

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

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

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


// -------------------------- 非嵌套写法 -------------------------
syntax = "proto3";
package test_enum;

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

message Phone{
   
   

}

// -------------------------- 嵌套写法 -------------------------
syntax = "proto3";
package test_enum;

message Phone{
   
     //主要这里要看清楚是嵌套再message里面,而不是enum

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

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

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

定义时注意

将两个 ‘具有相同枚举值名称’ 的枚举类型放在单个 .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 "phone1.proto";
enum PhoneType {
   
   
MP = 0; // 移动电话 // 编译后报错:MP 已经定义
TEL = 1; // 固定电话
}
// phone2.proto
enum PhoneTypeCopy {
   
   
MP = 0; // 移动电话
}
// ---------------------- 情况4:多文件下都声明了package--------------------
// phone1.proto
import "phone1.proto";
package phone1;
enum PhoneType {
   
   
MP = 0; // 移动电话 // 用法正确
TEL = 1; // 固定电话
}
// phone2.proto;
package phone2;
enum PhoneTypeCopy {
   
   
MP = 0; // 移动电话
}

升级通讯录至 2.1 版本

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

syntax = "proto3";
package contacts2;


// 定义联系人message
message PeopleInfo{
   
   
    string name = 1; // 名字
    int32 age = 2 ; // 年龄

    message Phone{
   
   
    string  numbers = 1;
        enum PhoneType{
   
   
            MP = 0; // 移动电话
            TEL = 1; // 固定电话
        }
    PhoneType type = 2; // 类型
    }

    repeated Phone phone = 3; // 电话信息
}

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

}

编译

 protoc --cpp_out=. contacts.proto

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

// 新生成的 PeopleInfo_Phone_PhoneType 枚举类
enum PeopleInfo_Phone_PhoneType : int {
   
   
    PeopleInfo_Phone_PhoneType_MP = 0,
    PeopleInfo_Phone_PhoneType_TEL = 1,

 PeopleInfo_Phone_PhoneType_PeopleInfo_Phone_PhoneType_INT_MIN_SENTINEL_DO_NOT_U
 SE_ = std::numeric_limits<int32_t>::min(),

 PeopleInfo_Phone_PhoneType_PeopleInfo_Phone_PhoneType_INT_MAX_SENTINEL_DO_NOT_U
 SE_ = std::numeric_limits<int32_t>::max()
};
// 更新的 PeopleInfo_Phone 类
class PeopleInfo_Phone final : public ::PROTOBUF_NAMESPACE_ID::Message {
   
   
public:
 typedef PeopleInfo_Phone_PhoneType PhoneType;
 static inline bool PhoneType_IsValid(int value) {
   
   
    return PeopleInfo_Phone_PhoneType_IsValid(value);
}
 template<typename T>
 static inline const std::string& PhoneType_Name(T enum_t_value) {
   
   ...}
 static inline bool PhoneType_Parse(
 ::PROTOBUF_NAMESPACE_ID::ConstStringParam name, PhoneType* value) {
   
   ...}

// .contacts.PeopleInfo.Phone.PhoneType type = 2;
 void clear_type();
 ::contacts::PeopleInfo_Phone_PhoneType type() const;
 void set_type(::contacts::PeopleInfo_Phone_PhoneType value);
}

上述的代码中:

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

更新 write.cc (通讯录 2.1)

#include <iostream>
#include <fstream>
#include <limits>  // 用于处理输入缓冲区的清理

// 引入protobuf自动生成的头文件,包含通讯录数据结构定义
#include "contacts.pb.h"

using namespace std;


//@brief 是 Doxygen 的标签,表示 “简要说明”。
//@param 是 Doxygen 的标签,表示 “函数参数说明”。

/**
 * @brief 向联系人信息对象中添加数据
 * @param people 指向待填充数据的PeopleInfo对象的指针
 */
void AddPeopleInfo(contacts2::PeopleInfo* people){
   
   
    cout << "-------------新增联系人-------------" << endl;
    
    // 读取联系人姓名
    cout << "请输入联系人姓名: ";
    string name;
    getline(cin, name);  // 使用getline可读取包含空格的完整姓名
    people->set_name(name);  // 调用protobuf生成的setter方法设置姓名
    
    // 读取联系人年龄
    cout << "请输入联系人年龄: ";
    int age;
    cin >> age;  // 读取整数年龄
    people->set_age(age);  // 设置年龄
    
    // 清理输入缓冲区中的残留换行符
    // cin.ignore(256,'\n');
    // numeric_limits<streamsize>::max()确保清空所有剩余字符
    cin.ignore(numeric_limits<streamsize>::max(), '\n');
    
    // 循环读取多个电话号码,直到用户输入空行为止
    for(int i=0; ; i++){
   
   
        cout << "请输入联系人电话: " << i+1 << "(只输入回车完成电话新增): ";
        string number;
        getline(cin, number);  // 读取电话号码
        
        // 如果用户输入空行,则退出循环,结束电话号码输入
        if(number.empty()){
   
   
            break;
        }
        
        // 调用protobuf生成的add_phone()方法添加新电话
        // 该方法会返回一个指向新创建的Phone对象的指针
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_numbers(number);  // 设置电话号码



        cout << "选择此电话号码类型(1、移动电话   2、固定电话): ";
        int type;
        cin >> type;
        // 清理输入缓冲区中的残留换行符
        // cin.ignore(256,'\n');
        // numeric_limits<streamsize>::max()确保清空所有剩余字符
        cin.ignore(numeric_limits<streamsize>::max(), '\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;
        }


    }

    



    cout << "-------------添加联系人成功-------------" << endl;
}

int main()
{
   
   
    // 验证protobuf库版本是否与编译时一致,确保兼容性
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    // 创建通讯录主对象,用于存储所有联系人信息
    contacts2::Contacts contacts;

    // 尝试读取本地已存在的通讯录文件
    fstream input("contacts.bin", ios::in | ios::binary);
    if(!input){
   
   
        // 文件不存在时提示将创建新文件
        cout << "contacts.bin  not find, creat new file! " << endl;
    }else if(!contacts.ParseFromIstream(&input)){
   
   
        // 解析文件失败时输出错误信息并退出
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

    // 关闭输入流,避免资源占用
    input.close();

    // 向通讯录中添加新联系人
    // add_contacts()方法会创建一个新的PeopleInfo对象并返回其指针
    AddPeopleInfo(contacts.add_contacts());

    // 打开文件用于写入更新后的通讯录数据
    // ios::out: 输出模式;ios::trunc: 若文件存在则清空;ios::binary: 二进制模式
    fstream output("contacts.bin", ios::out | ios::trunc | ios::binary);
    if(!contacts.SerializeToOstream(&output)){
   
   
        // 序列化并写入失败时处理
        cerr << "write error!" << endl;
        output.close();
        return -1;
    }

    // 提示写入成功并关闭输出流
    cout << "write success!" << endl;
    output.close();

    // 清理protobuf库分配的全局资源,适合在长时间运行的程序中使用
    google::protobuf::ShutdownProtobufLibrary();
    return 0;
}

更新 read.cc (通讯录 2.1)

#include <iostream>
#include <fstream>

// 引入protobuf自动生成的通讯录数据结构定义头文件
#include "contacts.pb.h"

using namespace std;



//@brief 是 Doxygen 的标签,表示 “简要说明”。
//@param 是 Doxygen 的标签,表示 “函数参数说明”。

/**
 * @brief 打印通讯录中的所有联系人信息
 * @param contacts 常量引用,指向存储所有联系人数据的protobuf对象
 *                 使用const确保函数不会修改原始数据,提高安全性
 */
void PrintContacts(const contacts2::Contacts& contacts){
   
   
    // 遍历所有联系人,contacts_size()返回联系人总数
    for(int i = 0; i < contacts.contacts_size(); i++){
   
   
        cout << "--------------------联系人" << i+1 << "--------------------" << endl;
        // 通过contacts(i)获取第i个联系人的详细信息(protobuf生成的访问方法)
        const contacts2::PeopleInfo& people = contacts.contacts(i);
        
        // 打印联系人基本信息,调用protobuf生成的getter方法(name()、age())
        cout << "联系人姓名:" << people.name() << endl;
        cout << "联系人年龄:" << people.age() << endl;
        
        // 遍历当前联系人的所有电话号码,phone_size()返回电话号码总数
        for(int j= 0; j < people.phone_size(); j++){
   
   
            // 通过phone(j)获取第j个电话号码对象
            const contacts2::PeopleInfo_Phone& phone = people.phone(j);
            cout << "联系人电话" << j+1 << ":" << phone.numbers();
            //希望格式 联系人电话1:12312312312 (MP)
            // phone.type()打印的格式是int类型也就是枚举的数字,用 phone.PhoneType_Name()将枚举的数字值转换为对应的名称字符串。
            cout << " (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
        }
    }
}


int main(){
   
   
    // 创建protobuf通讯录对象,用于存储从文件读取的数据
    contacts2::Contacts contacts;

    // 尝试打开本地通讯录文件(二进制读模式)
    fstream input("contacts.bin", ios::in | ios::binary);
    
    // 检查文件是否成功打开

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值