江門做公司網(wǎng)站百度競價排名公式
1 🍑字段規(guī)則🍑
消息的字段可以?下??種規(guī)則來修飾:
singular
:消息中可以包含該字段零次或?次(不超過?次)。 proto3 語法中,字段默認使?該規(guī)則。repeated
:消息中可以包含該字段任意多次(包括零次),其中重復(fù)值的順序會被保留。可以理解為定義了?個數(shù)組。
比如:
syntax = "proto3";
package contacts;
message PeopleInfo
{string name = 1; int32 age = 2; repeated string phone_numbers = 3;
}
2 🍑消息類型的定義與使用🍑
2.1 🍎定義🍎
在單個
.proto
?件中可以定義多個消息體,且?持定義嵌套類型的消息(任意多層)。每個消息體中的字段編號可以重復(fù)。
嵌套寫法:
syntax = "proto3";
package contacts;
message PeopleInfo
{string name = 1; int32 age = 2; message Phone {string number = 1;}
}
?嵌套寫法:
syntax = "proto3";
package contacts;
message Phone
{string number = 1;
}
message PeopleInfo
{string name = 1; int32 age = 2;
}
2.2 🍎使用🍎
消息類型可作為字段類型使?:
syntax = "proto3";
package contacts;
message PeopleInfo
{string name = 1; int32 age = 2; message Phone {string number = 1; }repeated Phone phone = 3;
}
可導(dǎo)?其他 .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 ?件導(dǎo)?進來 !!!
message PeopleInfo
{string name = 1; int32 age = 2; // 引?的?件聲明了package,使?消息時,需要? ‘命名空間.消息類型’ 格式 repeated phone.Phone phone = 3;
}
注:在 proto3 ?件中可以導(dǎo)? proto2 消息類型并使?它們,反之亦然。
3 🍑通訊錄版本v2🍑
contacts.proto:
syntax = "proto3";
package contacts;// 聯(lián)系?
message PeopleInfo
{string name = 1; // 姓名int32 age = 2; // 年齡message Phone {string number = 1; // 電話號碼}repeated Phone phone = 3; // 電話
}
// 通訊錄
message Contacts
{repeated PeopleInfo contacts = 1;
}
接著進??次編譯:
protoc --cpp_out=. contacts.proto
3.1 🍎通訊錄v2的寫入實現(xiàn)🍎
main.cc:
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
/*** 新增聯(lián)系?*/
void AddPeopleInfo(PeopleInfo *people_info_ptr)
{cout << "-------------新增聯(lián)系?-------------" << endl;cout << "請輸?聯(lián)系?姓名: ";string name;getline(cin, name);people_info_ptr->set_name(name);cout << "請輸?聯(lián)系?年齡: ";int age;cin >> age;people_info_ptr->set_age(age);cin.ignore(256, '\n');//遇到'\n'結(jié)束,或者接受到的數(shù)據(jù)大于256for (int i = 1;; i++){cout << "請輸?聯(lián)系?電話" << i << "(只輸?回?完成電話新增): ";string number;getline(cin, number);if (number.empty()){break;}PeopleInfo_Phone *phone = people_info_ptr->add_phone();phone->set_number(number);}cout << "-----------添加聯(lián)系?成功-----------" << endl;
}
int main(int argc, char *argv[])
{// GOOGLE_PROTOBUF_VERIFY_VERSION 宏: 驗證沒有意外鏈接到與編譯的頭?件不兼容的庫版// 本。如果檢測到版本不匹配,程序?qū)⒅?。注意,每個 .pb.cc ?件在啟動時都會?動調(diào)?此宏。在使// ? C++ Protocol Buffer 庫之前執(zhí)?此宏是?種很好的做法,但不是絕對必要的。GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc != 2){cerr << "Usage: " << argv[0] << " CONTACTS_FILE" << endl;return -1;}Contacts contacts;// 先讀取已存在的 contactsfstream input(argv[1], ios::in | ios::binary);if (!input){cout << argv[1] << ": File not found. Creating a new file." << endl;}else if (!contacts.ParseFromIstream(&input)){cerr << "Failed to parse contacts." << endl;input.close();return -1;}// 新增?個聯(lián)系?AddPeopleInfo(contacts.add_contacts());// 向磁盤?件寫?新的 contactsfstream output(argv[1], ios::out | ios::trunc | ios::binary);if (!contacts.SerializeToOstream(&output)){cerr << "Failed to write contacts." << endl;input.close();output.close();return -1;}input.close();output.close();google::protobuf::ShutdownProtobufLibrary();return 0;
}
注意點代碼中有解釋。
驗證:
查看?進制?件:
3.2 🍎通訊錄v2的讀取實現(xiàn)🍎
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace contacts;
/*** 打印聯(lián)系?列表*/
void PrintfContacts(const Contacts &contacts)
{for (int i = 0; i < contacts.contacts_size(); ++i){const PeopleInfo &people = contacts.contacts(i);cout << "------------聯(lián)系?" << i + 1 << "------------" << endl;cout << "姓名:" << people.name() << endl;cout << "年齡:" << people.age() << endl;int j = 1;for (const PeopleInfo_Phone &phone : people.phone()){cout << "電話" << j++ << ": " << phone.number() << endl;}}
}
int main(int argc, char *argv[])
{GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc != 2){cerr << "Usage: " << argv[0] << "CONTACTS_FILE" << endl;return -1;}// 以?進制?式讀取 contactsContacts contacts;fstream input(argv[1], ios::in | ios::binary);if (!contacts.ParseFromIstream(&input)){cerr << "Failed to parse contacts." << endl;input.close();return -1;}// 打印 contactsPrintfContacts(contacts);input.close();google::protobuf::ShutdownProtobufLibrary();return 0;
}
驗證:
另?種驗證?法--decode
我們可以? protoc -h
命令來查看 ProtoBuf 為我們提供的所有命令 option。其中 ProtoBuf 提供?個命令選項 --decode
,表?從標準輸?中讀取給定類型的?進制消息,并將其以?本格式寫?標準輸出。 消息類型必須在 .proto
?件或?qū)?的?件中定義。
4 🍑enum 類型🍑
4.1 🍎定義規(guī)則🍎
語法?持我們定義枚舉類型并使?。在.proto
?件中枚舉類型的書寫規(guī)范為:
- 枚舉類型名稱使?駝峰命名法,?字??寫。 例如: MyEnum
- 常量值名稱全?寫字?,多個字?之間? _ 連接。例如: ENUM_CONST = 0;
我們可以定義?個名為 PhoneType 的枚舉類型,定義如下:
enum PhoneType
{MP = 0; // 移動電話TEL = 1; // 固定電話
}
要注意枚舉類型的定義有以下?種規(guī)則:
- 0 值常量必須存在,且要作為第?個元素。這是為了與 proto2 的語義兼容:第?個元素作為默認值,且值為 0。
- 枚舉類型可以在消息外定義,也可以在消息體內(nèi)定義(嵌套)。
- 枚舉的常量值在 32 位整數(shù)的范圍內(nèi),但因負值?效因?不建議使?(與編碼規(guī)則有關(guān))。
4.2 🍎注意事項🍎
將兩個 具有相同枚舉值名稱 的枚舉類型放在單個 .proto
?件下測試時,編譯后會報錯:某某某常量已經(jīng)被定義!所以這?要注意:
- 同級(同層)的枚舉類型,各個枚舉類型中的常量不能重名。
- 單個
.proto
?件下,最外層枚舉類型和嵌套枚舉類型,不算同級。 - 多個
.proto
?件下,若?個?件引?了其他?件,且每個?件都未聲明 package,每個 proto ?件中的枚舉類型都在最外層,算同級。 - 多個
.proto
?件下,若?個?件引?了其他?件,且每個?件都聲明了 package,不算同級。
5 🍑Any 類型🍑
字段還可以聲明為 Any 類型,可以理解為泛型類型。使?時可以在 Any 中存儲任意消息類型。Any 類型的字段也? repeated
來修飾。
Any 類型是 google 已經(jīng)幫我們定義好的類型,在安裝 ProtoBuf 時,其中的 include ?錄下查找所有g(shù)oogle 已經(jīng)定義好的 .proto
?件。
此時我們可以再升級通訊錄版本:
.proto
文件:
syntax = "proto3";
package contacts;
import "google/protobuf/any.proto"; // 引? any.proto ?件
// 地址
message Address
{string home_address = 1; // 家庭地址string unit_address = 2; // 單位地址
}
// 聯(lián)系?
message PeopleInfo
{string name = 1; // 姓名int32 age = 2; // 年齡message Phone {string number = 1; // 電話號碼enum PhoneType {MP = 0; // 移動電話TEL = 1; // 固定電話}PhoneType type = 2; // 類型}repeated Phone phone = 3; // 電話google.protobuf.Any data = 4;
}
// 通訊錄
message Contacts
{repeated PeopleInfo contacts = 1;
}
使用protoc編譯器編譯后:
// 新?成的 Address 類
class Address final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;void CopyFrom(const Address& from);using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;void MergeFrom( const Address& from) {Address::MergeImpl(*this, from);}// string home_address = 1;void clear_home_address();const std::string& home_address() const;template <typename ArgT0 = const std::string&, typename... ArgT>void set_home_address(ArgT0&& arg0, ArgT... args);std::string* mutable_home_address();PROTOBUF_NODISCARD std::string* release_home_address();void set_allocated_home_address(std::string* home_address);// string unit_address = 2;void clear_unit_address();const std::string& unit_address() const;template <typename ArgT0 = const std::string&, typename... ArgT>void set_unit_address(ArgT0&& arg0, ArgT... args);std::string* mutable_unit_address();PROTOBUF_NODISCARD std::string* release_unit_address();void set_allocated_unit_address(std::string* unit_address);
};
// 更新的 PeopleInfo 類
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{
public:// .google.protobuf.Any data = 4;bool has_data() const;void clear_data();const ::PROTOBUF_NAMESPACE_ID::Any& data() const;PROTOBUF_NODISCARD ::PROTOBUF_NAMESPACE_ID::Any* release_data();::PROTOBUF_NAMESPACE_ID::Any* mutable_data();void set_allocated_data(::PROTOBUF_NAMESPACE_ID::Any* data);
};
上述的代碼中,對于 Any 類型字段:設(shè)置和獲取:獲取?法的?法名稱與?寫字段名稱完全相同。設(shè)置?法可以使? mutable_
?法,返回值為Any類型的指針,這類?法會為我們開辟好空間,可以直接對這塊空間的內(nèi)容進?修改。
之前講過,我們可以在 Any 字段中存儲任意消息類型,這就要涉及到任意消息類型 和 Any 類型的互轉(zhuǎn)。這部分代碼就在 Google為我們寫好的頭?件 any.pb.h
中。對 any.pb.h 部分代碼展?:
class PROTOBUF_EXPORT Any final : public ::PROTOBUF_NAMESPACE_ID::Message
{bool PackFrom(const ::PROTOBUF_NAMESPACE_ID::Message& message) {...}bool UnpackTo(::PROTOBUF_NAMESPACE_ID::Message* message) const {...}template<typename T> bool Is() const {return _impl_._any_metadata_.Is<T>();}
};
上述代碼中:
- 使?
PackFrom()
?法可以將任意消息類型轉(zhuǎn)為 Any 類型。 - 使?
UnpackTo()
?法可以將 Any 類型轉(zhuǎn)回之前設(shè)置的任意消息類型。 - 使?
Is()
?法可以?來判斷存放的消息類型是否為 typename T。
6 🍑oneof 類型🍑
如果消息中有很多可選字段, 并且將來同時只有?個字段會被設(shè)置, 那么就可以使? oneof
加強這個?為,也能有節(jié)約內(nèi)存的效果。
注意:
- 可選字段中的字段編號,不能與?可選字段的編號沖突。
- 不能在
oneof
中使?repeated
字段。 - 將來在設(shè)置
oneof
字段中值時,如果將 oneof 中的字段設(shè)置多個,那么只會保留最后?次設(shè)置的成員,之前設(shè)置的oneof
成員會?動清除。
contacts.pb.h 更新的部分代碼展?:
// 更新的 PeopleInfo 類
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{enum OtherContactCase {kQq = 5,kWeixin = 6,OTHER_CONTACT_NOT_SET = 0,};// string qq = 5;bool has_qq() const;void clear_qq();const std::string& qq() const;template <typename ArgT0 = const std::string&, typename... ArgT>void set_qq(ArgT0&& arg0, ArgT... args);std::string* mutable_qq();PROTOBUF_NODISCARD std::string* release_qq();void set_allocated_qq(std::string* qq);// string weixin = 6;bool has_weixin() const;void clear_weixin();const std::string& weixin() const;template <typename ArgT0 = const std::string&, typename... ArgT>void set_weixin(ArgT0&& arg0, ArgT... args);std::string* mutable_weixin();PROTOBUF_NODISCARD std::string* release_weixin();void set_allocated_weixin(std::string* weixin);void clear_other_contact();OtherContactCase other_contact_case() const;
};
上述的代碼中,對于 oneof
字段:
- 會將
oneof
中的多個字段定義為?個枚舉類型。 - 設(shè)置和獲取:對
oneof
內(nèi)的字段進?常規(guī)的設(shè)置和獲取即可,但要注意只能設(shè)置?個。如果設(shè)置多個,那么只會保留最后?次設(shè)置的成員。 - 清空
oneof
字段:clear_
?法 - 獲取當前設(shè)置了哪個字段:
_case
?法
7 🍑map 類型🍑
語法?持創(chuàng)建?個關(guān)聯(lián)映射字段,也就是可以使? map 類型去聲明字段類型,格式為:
map<key_type, value_type> map_field = N;
要注意的是:
- key_type 是除了 float 和 bytes 類型以外的任意標量類型。 value_type 可以是任意類型。
- map 字段不可以?
repeated
修飾。 - map 中存?的元素是?序的。
contacts.pb.h 更新的部分代碼展?:
// 更新的 PeopleInfo 類
class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message
{// map<string, string> remark = 7;int remark_size() const;void clear_remark();const ::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >&remark() const;::PROTOBUF_NAMESPACE_ID::Map< std::string, std::string >*mutable_remark();
};
上述的代碼中,對于Map類型的字段:
- 清空map:
clear_
?法。 - 設(shè)置和獲取:獲取?法的?法名稱與?寫字段名稱完全相同。設(shè)置?法為
mutable_
?法,返回值為Map類型的指針,這類?法會為我們開辟好空間,可以直接對這塊空間的內(nèi)容進?修改。
8 🍑默認值🍑
反序列化消息時,如果被反序列化的?進制序列中不包含某個字段,反序列化對象中相應(yīng)字段時,就會設(shè)置為該字段的默認值。不同的類型對應(yīng)的默認值不同:
- 對于字符串,默認值為空字符串。
- 對于字節(jié),默認值為空字節(jié)。
- 對于布爾值,默認值為 false。
- 對于數(shù)值類型,默認值為 0。
- 對于枚舉,默認值是第?個定義的枚舉值, 必須為 0。
- 對于消息字段,未設(shè)置該字段。它的取值是依賴于語?。
- 對于設(shè)置了 repeated 的字段的默認值是空的( 通常是相應(yīng)語?的?個空列表 )。對于 消息字段 、 oneof字段 和 any字段 ,C++ 和 Java 語?中都有 has_ ?法來檢測當前字段是否被設(shè)置。
9 🍑更新消息🍑
9.1 🍎更新規(guī)則🍎
如果現(xiàn)有的消息類型已經(jīng)不再滿?我們的需求,例如需要擴展?個字段,在不破壞任何現(xiàn)有代碼的情況下更新消息類型?常簡單。遵循如下規(guī)則即可:
- 禁?修改任何已有字段的字段編號。
- 若是移除?字段,要保證不再使?移除字段的字段編號。正確的做法是保留字段編號(reserved),以確保該編號將不能被重復(fù)使?。不建議直接刪除或注釋掉字段。
- int32, uint32, int64, uint64 和 bool 是完全兼容的。可以從這些類型中的?個改為另?個,?不破壞前后兼容性。若解析出來的數(shù)值與相應(yīng)的類型不匹配,會采?與 C++ ?致的處理?案(例如,若將 64 位整數(shù)當做 32 位進?讀取,它將被截斷為 32 位)。
- sint32 和 sint64 相互兼容但不與其他的整型兼容。
- string 和 bytes 在合法 UTF-8 字節(jié)前提下也是兼容的。
- bytes 包含消息編碼版本的情況下,嵌套消息與 bytes 也是兼容的。
- fixed32 與 sfixed32 兼容, fixed64 與 sfixed64兼容。
- enum 與 int32,uint32, int64 和 uint64 兼容(注意若值不匹配會被截斷)。但要注意當反序列化消息時會根據(jù)語?采?不同的處理?案:例如,未識別的 proto3 枚舉類型會被保存在消息中,但是當消息反序列化時如何表?是依賴于編程語?的。整型字段總是會保持其的值。
- oneof:
-
- 將?個單獨的值更改為 新 oneof 類型成員之?是安全和?進制兼容的。
-
- 若確定沒有代碼?次性設(shè)置多個值那么將多個字段移??個新 oneof 類型也是可?的。
-
- 將任何字段移?已存在的 oneof 類型是不安全的。
9.2 🍎保留字段 reserved🍎
如果通過 刪除 或 注釋掉 字段來更新消息類型,未來的??在添加新字段時,有可能會使?以前已經(jīng)存在,但已經(jīng)被刪除或注釋掉的字段編號。將來使?該 .proto
的舊版本時的程序會引發(fā)很多問題:數(shù)據(jù)損壞、隱私錯誤等等。
確保不會發(fā)?這種情況的?種?法是:使? reserved
將指定字段的編號或名稱設(shè)置為保留項 。當我們再使?這些編號或名稱時,protocol buffer 的編譯器將會警告這些編號或名稱不可?。
9.3 🍎未知字段🍎
未知字段:解析結(jié)構(gòu)良好的 protocol buffer 已序列化數(shù)據(jù)中的未識別字段的表??式。例如,當舊程序解析帶有新字段的數(shù)據(jù)時,這些新字段就會成為舊程序的未知字段。
本來,proto3 在解析消息時總是會丟棄未知字段,但在 3.5 版本中重新引?了對未知字段的保留機制。所以在 3.5 或更?版本中,未知字段在反序列化時會被保留,同時也會包含在序列化的結(jié)果中。
9.4 🍎前后兼容性🍎
- 向前兼容:?模塊能夠正確識別新模塊?成或發(fā)出的協(xié)議。
- 向后兼容:新模塊也能夠正確識別?模塊?成或發(fā)出的協(xié)議。
前后兼容的作?:當我們維護?個很龐?的分布式系統(tǒng)時,由于你?法同時 升級所有 模塊,為了保證在升級過程中,整個系統(tǒng)能夠盡可能不受影響,就需要盡量保證通訊協(xié)議的“向后兼容”或“向前兼容”。
10 🍑選項 option🍑
.proto
?件中可以聲明許多選項,使? option
標注。選項能影響 proto 編譯器的某些處理?式。
10.1 🍎常用選項列舉🍎
optimize_for
: 該選項為?件選項,可以設(shè)置 protoc 編譯器的優(yōu)化級別,分別為SPEED
、CODE_SIZE
、LITE_RUNTIME
。受該選項影響,設(shè)置不同的優(yōu)化級別,編譯 .proto ?件后?成的代碼內(nèi)容不同。-
SPEED
: protoc 編譯器將?成的代碼是?度優(yōu)化的,代碼運?效率?,但是由此?成的代碼編譯后會占?更多的空間。 SPEED 是默認選項。
-
CODE_SIZE
: proto 編譯器將?成最少的類,會占?更少的空間,是依賴基于反射的代碼來實現(xiàn)序列化、反序列化和各種其他操作。但和 SPEED 恰恰相反,它的代碼運?效率較低。這種?式適合?在包含?量的.proto?件,但并不盲?追求速度的應(yīng)?中。
-
LITE_RUNTIM
E : ?成的代碼執(zhí)?效率?,同時?成代碼編譯后的所占?的空間也是?常少。這是以犧牲Protocol Buffer提供的反射功能為代價的,僅僅提供 encoding+序列化 功能,所以我們在鏈接 BP 庫時僅需鏈接libprotobuf-lite,??libprotobuf。這種模式通常?于資源有限的平臺,例如移動?機平臺中。
option optimize_for = LITE_RUNTIME;
allow_alias
: 允許將相同的常量值分配給不同的枚舉常量,?來定義別名。該選項為枚舉選項。
舉個例?:
enum PhoneType
{option allow_alias = true;MP = 0;TEL = 1;LANDLINE = 1; // 若不加 option allow_alias = true; 這??會編譯報錯
}