protobuf学习日记 | 认识protobuf中的类型

目录

前言

一、标量数据类型

二、protobuf中的 “数组” 

三、特殊类型

1、枚举类型

(1)类型讲解 

(2)升级通讯录

2、Any类型

(1)类型讲解 

(2)升级通讯录

3、oneof类型

(1)类型讲解

(2)升级通讯录

4、map类型

(1)类型讲解

(2)升级通讯录


前言

        本文为protobuf系列的第二期,本文主要介绍protobuf中的数据类型,初步认识这些数据类型后,在来通过这些类型来不断完善我们通讯录的小项目;

一、标量数据类型

        protobuf中将类型分为标量数据类型与特殊类型(枚举等),下面为常见的标量数据类型,与在C++中对应类型;

.proto typeNotesC++ type
doubledouble
floatfloat
int32使用变长编码。负数的编码效率较低,若字段可能为负值,应用 sint32 代替。int32
int64使用变长编码。负数的编码效率较低,若字段可能为负值,应用 sint64 代替。int64
uint32使用变长编码。uint32
uint64使用变长编码。uint64
sint32使用变长编码。符号整型。负值的编码效率高于常规的 int32 类型。int32
sint64使用变长编码。符号整型。负值的编码效率高于常规的 int64 类型。int64
fixed32定长4字节。若值常大于 2^28 则会比 uint32 更高效。uint32
fixed64定长8字节。若值常大于 2^56 则会比 uint64 更高效。uint64
sfixed32定长4字节。int32
sfixed64定长8字节。int64
boolbool
string包含 UTF-8 和 ASCII 编码的字符串,长度不能超过 2^32 。string
bytes可包含任意的字节序列但长度不能超过 2^32 。string

        上述资料来自 protobuf 官网。如下图所示;protobuf官网

        这里对上述表格内容进行解释补充;

1、所谓变长编码指的是protobuf在序列化中,不是按照固定字节进行序列化,而采用根据具体数据变化而不定长的编码。

2、定长编码则与变长为相反概念,为固定字节编码。

二、protobuf中的 “数组” 

        假若我们想让message中字段中有数组类型的字段我们应该如何处理呢?实际上,这里是通过字段规则来控制的,我们通过给字段声明额外属性来达到数组的效果;

singular:消息中包含该字段零次或一次。默认使用该字段。

repeated:消息中可以包含该字段任意多次(包括零次),其中重复值的顺序会被保留。可以理解为定义了⼀个数组。

        我们继续完善通讯录小项目(没看上篇文章的不要急,这里仍可以看懂),我们分析一下我们PeopleInfo结构中所需字段,我们需要一个联系人姓名,一个联系人年龄,接着我们需要联系人电话,此时我们会发现联系人电话可能不止一个,所以我们应该将电话号码设置成一个类似数组的字段,这里由于后面每个电话可能还有电话类型,如座机、移动电话等,所以这里我们将电话单独放在一个message中;具体代码如下;

syntax="proto3";
package contacts;
// 非嵌套
message Phone
{string phone_num = 1;
}message PeopleInfo
{string name = 1;  // 姓名int32 age = 2;  // 年龄// 可嵌套// message Phone// {//     string PhoneNum = 1;// }repeated Phone phones = 3; // 电话
}

        注意上述中,我们展示message可以进行嵌套,甚至可以写在别的proto文件中,我们通过import 导入那个文件就可以使用了,这里就不展示了;

        我们通过protoc编译器编译这个proto文件,如下所示;

        我们查看.h文件来看一看proto帮我们生成序列化、反序列化相关方法;如下所示;

        由于环境原因,我的机器上会显示报错,但实际上没有问题的,如上图,我们可以找到一个contacts的命名空间,这个命名空间就是我们在proto文件通过package生成的命名空间;

        仔细翻阅,我们就能找到两个类,一个是Phone。一个是PeopleInfo,这两个就是由我们在proto文件中的message通过proto编译器编译来的;一般来说,都会给每个message中每个字段生成一个查询字段的接口,和一个设置字段的接口,查询字段以字段名为接口名,设置字段前带前缀set;我们继续阅读代码;

 

        上面为message中三个普通字段的相关接口,我们仔细想一下,其中phone_num为嵌套message中的字段,这个嵌套message是一个数组,那么这个数组又如何的接口呢?实际上,我们也可以找到,只不过我把他单独拎出来了,如下图;

        我单独标出来了四个接口,其中第一个接口就是数组中元素个数;第二个接口是我们传入数组的一个下标,该接口返回该下标下的地址,我们这个地址我们再仔细观察发现就是Phone*的,也就是这个数组存储数据的类型,我们通过这个地址可更改对应下标下元素的值;第三个接口是查询指定下标下的元素值;第四个接口与第二个接口类似,我们不关心将值加入哪个下标下,我们就指向增加要给元素,此时调用该接口返回一个地址,我们将数据放在这个地址下即可;

        基于上面的proto代码,我们现在想实现一个写程序和一个读程序,读程序要求我们从用户输入中获取联系人的姓名,年龄。电话号等信息;然后将该信息通过protobuf进行序列化,存入文件中;我们的读程序则是从文件中读取程序,然后打印出来;代码如下;

        首先更新proto文件,我们引入一个message叫做contact,该message中只有一个字段,就是PeopleInfo,如下所示(记得重新编译proto文件哦);

// contacts.proto文件
syntax="proto3";
package contacts;
// 非嵌套
message Phone
{string phone_num = 1;
}message PeopleInfo
{string name = 1;  // 姓名int32 age = 2;  // 年龄repeated Phone phones = 3; // 电话
}message Contact
{PeopleInfo people = 1;
}

        接着我们编写write程序,如下所示;

// write.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"#define FILE_NAME "./contact.bin"using namespace std;void AddPeopleInfo(contacts::Contact& con);int main()
{contacts::Contact con;// 1、获取文件中联系人相关信息,若有读进contact中fstream input(FILE_NAME, ios::in | ios::binary);if(!input){input.close();cout << "文件不存在, 已重新创建新文件!" << endl;}else if(!con.ParseFromIstream(&input)){input.close();cout << "反序列化失败, 程序退出!" << endl;exit(1);}input.close();// 2、向通讯录中添加联系人AddPeopleInfo(con);// 3、将通讯录重新写入文件中fstream output(FILE_NAME, ios::out | ios::trunc | ios::binary);if(!output){output.close();cout << "写文件时,文件打开失败, 程序退出!" << endl;exit(2);}else if(!con.SerializeToOstream(&output)){output.close();cout << "序列化失败, 程序退出!" << endl;exit(3);}cout << "写入成功!" << endl;output.close();return 0;
}void AddPeopleInfo(contacts::Contact& con)
{cout << "--------------- 添加联系人 ---------------" << endl;// 新增一个联系人contacts::PeopleInfo* people = con.add_people();// 从用户输入中获取联系人姓名string name;cout << "请输入联系人姓名# ";getline(cin, name);// 设置姓名people->set_name(name);// 获取联系人年龄cout << "请输入联系人年龄# ";int age;cin >> age;cin.ignore(256, '\n'); // 清空输入缓冲区中换行// 设置年龄people->set_age(age);// 获取联系人号码for(int i = 1; ; i++){string phone_num_str;cout << "联系人手机号" << i << "(只输入回车表示完成电话新增)# ";getline(cin, phone_num_str);if(phone_num_str.empty()) break;// 新增一个联系人号码类contacts::Phone* phone = people->add_phones();// 设置联系人号码类中的号码phone->set_phone_num(phone_num_str);}
}

        我们运行该程序,并输入数据,此时会生成一个二进制文件,其中存放的就是我们序列化后的结果;如下图所示;

        我们看不懂这个二进制序列,但是我们可以通过protoc编译器给我们提供的一个命令来解析这个二进制文件,具体用法如下图所示;

        补充一下,图上的第一个参数为我们要按照哪个message的格式进行解析,因此这个参数为指定的message,第二个参数为指定message所在文件;由于decode指令默认从标准输入获取数据,所以这里进行重定向,让这条指令从指定文件读取数据;最后结果与我们输入结果一模一样,接下来我们可以再编写一个read程序完成反序列化的工作,我们通过这个read程序进一步来验证write程序是否正确;

// read.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"#define FILE_NAME "./contact.bin"using namespace std;void PrintContacts(contacts::Contact& con);int main()
{contacts::Contact con;// 1、 读取文件中数据fstream input(FILE_NAME, ios::in | ios::binary);if(!input){input.close();cout << "文件不存在, 已重新创建新文件!" << endl;}else if(!con.ParseFromIstream(&input)){input.close();cout << "反序列化失败, 程序退出!" << endl;exit(1);}input.close();// 2、打印contact内容PrintContacts(con);return 0;
}void PrintContacts(contacts::Contact& con)
{for(int i = 0; i < con.people_size(); i++){cout << "------------------- 联系人" << i + 1 << " -------------------" << endl;// 获取联系人信息contacts::PeopleInfo* people = con.mutable_people(i);cout << "联系人姓名: " << people->name() << endl;cout << "联系人年龄: " << people->age() << endl;for(int j = 0; j < people->phones_size(); j++){const contacts::Phone& phone = people->phones(j);cout << "联系人号码" << j + 1 << ": " << phone.phone_num() << endl;}}
}

        为了方便这两个文件的编译,我们编写了一份makefile文件,如下所示;

# makefile文件
.PHONY:all
all:read writeread:read.cc contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf
write:write.cc contacts.pb.ccg++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:rm -f write read

        我们运行read程序,结果如下所示;

        为了进一步验证我们再次调用write,接着再调用read,如下图所示;

        两个联系人都打印出来了,程序完成正确;

三、特殊类型

1、枚举类型

(1)类型讲解 

        protobuf为我们提供了一种与C语言C++类似的枚举类型;用法与C语言的枚举类型类似;如下所示;

syntax="proto3";enum PhoneType
{MP = 0;    // 分号分割TEL = 1;
}

注意:

1、第一个枚举值必须从0开始!

2、每个枚举值之间用分号分隔开

3、对于一个枚举字段来说,默认的枚举值为0

4、同级(同层)的枚举类型,各个枚举类型中的常量不能重名。(下面举例理解)

        上图中两个枚举类型的类型名不同,但是都一个MP,这种情况也属于同层;或者说同一个作用域的两个文件中,也不允许;比如我们再创建一个文件,文件中也有枚举值MP,当我们import这个文件后,同样也会编译报错;

(2)升级通讯录

        首先,我们给通讯录的每个号码增加一个号码类型,如移动电话或者固定电话;所以我们先更改proto文件,如下所示;

syntax="proto3";
package contacts;enum PhoneType
{MP = 0;   // 移动电话TEL = 1;  // 固定电话
}// 非嵌套
message Phone
{string phone_num = 1;   // 手机号PhoneType phone_type = 2;   // 手机号类型
}message PeopleInfo
{string name = 1;  // 姓名int32 age = 2;  // 年龄repeated Phone phones = 3; // 电话
}message Contact
{repeated PeopleInfo people = 1;
}

        我们再次给我们的通讯录代码的write程序和read程序进行修改,write程序增加让用户输入电话类型,read程序也会相应打印出对应程序;

// write.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"#define FILE_NAME "./contact.bin"using namespace std;void AddPeopleInfo(contacts::Contact& con);int main()
{contacts::Contact con;// 1、获取文件中联系人相关信息,若有读进contact中fstream input(FILE_NAME, ios::in | ios::binary);if(!input){input.close();cout << "文件不存在, 已重新创建新文件!" << endl;}else if(!con.ParseFromIstream(&input)){input.close();cout << "反序列化失败, 程序退出!" << endl;exit(1);}input.close();// 2、向通讯录中添加联系人AddPeopleInfo(con);// 3、将通讯录重新写入文件中fstream output(FILE_NAME, ios::out | ios::trunc | ios::binary);if(!output){output.close();cout << "写文件时,文件打开失败, 程序退出!" << endl;exit(2);}else if(!con.SerializeToOstream(&output)){output.close();cout << "序列化失败, 程序退出!" << endl;exit(3);}cout << "写入成功!" << endl;output.close();return 0;
}void AddPeopleInfo(contacts::Contact& con)
{cout << "--------------- 添加联系人 ---------------" << endl;// 新增一个联系人contacts::PeopleInfo* people = con.add_people();// 从用户输入中获取联系人姓名string name;cout << "请输入联系人姓名# ";getline(cin, name);// 设置姓名people->set_name(name);// 获取联系人年龄cout << "请输入联系人年龄# ";int age;cin >> age;cin.ignore(256, '\n'); // 清空输入缓冲区中换行// 设置年龄people->set_age(age);// 获取联系人号码for(int i = 1; ; i++){// 获取用户输入联系人电话string phone_num_str;cout << "联系人手机号" << i << "(只输入回车表示完成电话新增)# ";getline(cin, phone_num_str);if(phone_num_str.empty()) break;// 新增一个联系人号码类contacts::Phone* phone = people->add_phones();// 设置联系人号码类中的号码phone->set_phone_num(phone_num_str);// 获取用户输入电话类型cout << "该电话类型(1.移动电话 2.固定电话): ";int type = 0;cin >> type;cin.ignore(256, '\n');// 设置电话类型switch (type){case 1:phone->set_phone_type(contacts::PhoneType::MP);break;case 2:phone->set_phone_type(contacts::PhoneType::TEL);break;default:break;}}
}
// read.cc文件
#include <iostream>
#include <string>
#include <fstream>
#include "contacts.pb.h"#define FILE_NAME "./contact.bin"using namespace std;void PrintContacts(contacts::Contact& con);int main()
{contacts::Contact con;// 1、 读取文件中数据fstream input(FILE_NAME, ios::in | ios::binary);if(!input){input.close();cout << "文件不存在, 已重新创建新文件!" << endl;}else if(!con.ParseFromIstream(&input)){input.close();cout << "反序列化失败, 程序退出!" << endl;exit(1);}input.close();// 2、打印contact内容PrintContacts(con);return 0;
}void PrintContacts(contacts::Contact& con)
{for(int i = 0; i < con.people_size(); i++){cout << "------------------- 联系人" << i + 1 << " -------------------" << endl;// 获取联系人信息contacts::PeopleInfo* people = con.mutable_people(i);cout << "联系人姓名: " << people->name() << endl;cout << "联系人年龄: " << people->age() << endl;for(int j = 0; j < people->phones_size(); j++){// 获取电话号码信息const contacts::Phone& phone = people->phones(j);cout << "联系人号码" << j + 1 << ": " << phone.phone_num();// 获取电话类型信息cout << "(" << contacts::PhoneType_Name(phone.phone_type()) << ")" << endl;}}
}

        这里补充一个接口,接口名是枚举名+Name,也就是下面的PhoneType_Name,这个接口可以将枚举名转换成字段名;如下所示;

        具体运送结果如下所示;

        我们可以看到前面张三、李四我们并未为其增加电话类型,可以还是有默认值MP;

2、Any类型

(1)类型讲解 

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

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

        接下来我们通过这个字段继续完善我们的通讯录,我们给我们的通讯录增加一个地址字段,我们设置两个地址,一个是家庭地址,一个是单位地址,我们将这两个地址放到同一个message中,接着我们不想像存储手机号一样存储,而是在PeopleInfo中定义一个Any类型;

(2)升级通讯录

        我们根据上述重新再次更新我们的proto文件;如下所示;

syntax="proto3";
package contacts;// 使用any前必须引入文件
import "google/protobuf/any.proto";message Address
{string home_address = 1;string unit_address = 2;
}enum PhoneType
{MP = 0;   // 移动电话TEL = 1;  // 固定电话
}// 非嵌套
message Phone
{string phone_num = 1;   // 手机号PhoneType phone_type = 2;   // 手机号类型
}message PeopleInfo
{string name = 1;  // 姓名int32 age = 2;  // 年龄repeated Phone phones = 3; // 电话google.protobuf.Any data = 4; // 地址
}message Contact
{repeated PeopleInfo people = 1;
}

        接着我们依次升级write程序与read程序文件;

// write.cc文件// 创建地址类contacts::Address address;string home_address;cout << "联系人家庭地址: ";getline(cin, home_address);// 设置地址类中的家庭地址address.set_home_address(home_address);string unit_address;cout << "联系人单位地址: ";getline(cin, unit_address);// 设置地址类中的单位地址address.set_unit_address(unit_address);// 将Address类型转换成Any类型people->mutable_data()->PackFrom(address); 
// read.cc文件// 判断Any字段是否被设置以及判断Any字段中类型是否为Addressif(people->has_data() && people->data().Is<contacts::Address>()){contacts::Address address;// 将Any类型转换成Address类型people->data().UnpackTo(&address);if(!address.home_address().empty()){cout << "联系人家庭地址: " << address.home_address() << endl;}if(!address.unit_address().empty()){cout << "联系人单位地址: " << address.unit_address() << endl;}}

        这里涉及三个新接口,具体作用如下;

PackFrom: 可以将任意消息类型转为 Any 类型。

UnpackTo:将 Any 类型转回之前设置的任意消息类型。

Is:判断存放的消息类型是否为 typename T。

        测试运行结果如下;

3、oneof类型

(1)类型讲解

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

        简单来说,就是类似于C语言中的联合体,多个字段只有一个字段生效;

(2)升级通讯录

        我们给通讯录增加一个其他联系方式,如QQ或这微信,我们规定这两者之间只能选一个;我们根据这个重写proto文件,如下所示;

syntax="proto3";
package contacts;// 使用any前必须引入文件
import "google/protobuf/any.proto";message Address
{string home_address = 1;string unit_address = 2;
}enum PhoneType
{MP = 0;   // 移动电话TEL = 1;  // 固定电话
}// 非嵌套
message Phone
{string phone_num = 1;   // 手机号PhoneType phone_type = 2;   // 手机号类型
}message PeopleInfo
{string name = 1;  // 姓名int32 age = 2;  // 年龄repeated Phone phones = 3; // 电话google.protobuf.Any data = 4; // 地址oneof other_contact{string qq = 5;string wechat = 6;}
}message Contact
{repeated PeopleInfo people = 1;
}

        oneof字段内设置的字段属于外层message字段编号范围,故注意冲突问题;我们再次修改write与read程序代码,如下所示;

// write.cc文件
// 设置其他联系字段int other_contact = 0;cout << "请选择其他联系方式(1.qq 2.wechat): ";cin >> other_contact;cin.ignore(256, '\n');string qq;string wechat;switch(other_contact){case 1:cout << "请输入qq号: ";getline(cin, qq);people->set_qq(qq);break;case 2:cout << "请输入微信号: ";getline(cin, wechat);people->set_wechat(wechat);break;default:cout << "选择有误, 未成功设置其他联系方式" << endl;break;    }
// read.cc文件
// 判断其他联系方式字段是否被设置switch(people->other_contact_case()){case contacts::PeopleInfo::OtherContactCase::kQq:cout << "联系人qq: " << people->qq();break;case contacts::PeopleInfo::OtherContactCase::kWechat:cout << "联系人微信号: " << people->wechat();break;default:break;}

        

4、map类型

(1)类型讲解

        map类型就是与我们C++中map类似,就是建立一种映射;其格式为:

map<key_type, value_type> map_field = N;

        我们在使用时,仍有以下几点需要特别注意;

  • key_type是除了 float bytes 以外的任意 标量 数据类型。value_type可以是任意类型。
  • map字段不可以用 repeated 来修饰。
  • map中存入的数据是 无序 的。

(2)升级通讯录

        我们给我们的通讯录最后添加一个备注信息,我们希望这个备注信息呈现一种键值对的情况;我们首先更新我们的proto文件;如下所示;

// contacts.proto文件
syntax="proto3";
package contacts;// 使用any前必须引入文件
import "google/protobuf/any.proto";message Address
{string home_address = 1;string unit_address = 2;
}enum PhoneType
{MP = 0;   // 移动电话TEL = 1;  // 固定电话
}// 非嵌套
message Phone
{string phone_num = 1;   // 手机号PhoneType phone_type = 2;   // 手机号类型
}message PeopleInfo
{string name = 1;  // 姓名int32 age = 2;  // 年龄repeated Phone phones = 3; // 电话google.protobuf.Any data = 4; // 地址// 其他联系方式oneof other_contact{string qq = 5;string wechat = 6;}// 备注map<string, string> remarks = 7;
}message Contact
{repeated PeopleInfo people = 1;
}

        接着更新read和write程序,如下所示;

// write.cc文件// 设置备注字段for(int i = 0; ; i++){string key;cout << "请输入备注" << i + 1 << "标题(只输入回车表示完成备注): ";getline(cin, key);if(key.empty()) break;string value;cout << "请输入备注" << i + 1 << "内容: ";getline(cin, value);people->mutable_remarks()->insert({key, value});}
// read.cc文件
// 设置备注字段
for(int i = 0; ; i++)
{string key;cout << "请输入备注" << i + 1 << "标题(只输入回车表示完成备注): ";getline(cin, key);if(key.empty()) break;string value;cout << "请输入备注" << i + 1 << "内容: ";getline(cin, value);people->mutable_remarks()->insert({key, value});
}

        其中我们需要主要的是对于map来说,mutable系列接口的返回值就是C++map类型的指针;如下所示;

        运行测试结果也在下面贴出;

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

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

相关文章

LeetCode、2300. 咒语和药水的成功对数【中等,排序+二分】

文章目录 前言LeetCode、2300. 咒语和药水的成功对数【中等&#xff0c;排序二分】题目及类型思路及代码 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域…

elementUI+el-upload 上传、下载、删除文件以及文件展示列表自定义为表格展示

Upload 上传组件的使用 官方文档链接使用el-upload组件上传文件 具体参数说明&#xff0c;如何实现上传、下载、删除等功能获取文件列表进行file-list格式匹配代码 文件展示列表自定义为表格展示 使用的具体参数说明文件大小展示问题&#xff08;KB/MB&#xff09;文件下载代码…

【GitHub项目推荐--微软开源的可视化工具】【转载】

说到数据可视化&#xff0c;大家都很熟悉了&#xff0c;设计师、数据分析师、数据科学家等&#xff0c;都需要用各种方式各种途径做着数据可视化的工作.....当然许多程序员在工作中有时也需要用到一些数据可视化工具&#xff0c;如果工具用得好&#xff0c;就可以把原本枯燥凌乱…

svg矢量图标在wpf中的使用

在wpf应用程序开发中&#xff0c;为支持图标的矢量缩放&#xff0c;及在不同分辨率下界面中图标元素的矢量无损缩放&#xff0c;所以常常用到svg图标&#xff0c;那么如果完 美的将svg图标运用到wpf日常的项目开发中呢&#xff0c;这里分享一下我的个人使用经验和详细步骤。 步…

二叉树的基础概念及遍历

二叉树(Binary Tree)的基础 1、树的概念 1、树的概念 树是一种非线性的数据结构&#xff0c;是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合&#xff0c;将它称为树&#xff0c;是因为在形状上像一颗倒着的树&#xff0c;如下图所示就是一颗二叉…

electron-vite中的ipc通信

1. 概述 再electron中&#xff0c;进程间的通信通过ipcMain和ipcRenderer模块&#xff0c;这些通道是任意和双向的 1.1. 什么是上下文隔离进程 ipc通道是通过预加载脚本绑定到window对象的electron对象属性上的 2. 通信方式 2.1. ipcMain&#xff08;也就是渲染进程向主进…

R303 指纹识别模块硬件接口说明

1.外部接口尺寸图 2.USB通讯 3.串行通讯

VUE组件--动态组件、组件保持存活、异步组件

动态组件 有些场景可能会需要在多个组件之间进行来回切换&#xff0c;在vue中则使用<component :is"..."> 来实现组件间的来回切换 // App.vue <template><component :is"tabComponent"></component><button click"change…

【JavaEE进阶】 SpringBoot配置⽂件

文章目录 &#x1f340;配置⽂件的作⽤&#x1f334;SpringBoot配置⽂件&#x1f38b;配置⽂件的格式&#x1f384;properties配置⽂件&#x1f6a9;properties基本语法&#x1f6a9;读取配置⽂件&#x1f6a9;properties的缺点 &#x1f333;yml配置⽂件yml基本语法&#x1f6…

TestCaseAssiant使用说明

目录 说明 工具界面 功能描述 Xmind转测试用例 测试组件 测试用例 用例优先级 用例前提 用例操作步骤 用例期望结果 Excel测试用例转Testlink xml 用例模板 使用技巧: TestLink Xml转Excel测试用例 说明 本文为小编之前博文中介绍的工具使用说明 Xmind转Excel测…

python tkinter 最简洁的计算器按钮排列

代码如下&#xff0c;只要再加上按键绑定事件函数&#xff0c;计算器既可使用了。 import tkinter as tk from tkinter.ttk import Separator,Buttonif __name__ __main__:Buttons [[%,CE,C,←],[1/x,x,√x,],[7, 8, 9, x],[4, 5, 6, -],[1, 2, 3, ],[, 0, ., ]]root tk.T…

2024年阿里云优惠券和代金券领取,活动整理服务器价格表

2024阿里云优惠活动&#xff0c;免费领取阿里云优惠代金券&#xff0c;阿里云优惠活动大全和云服务器优惠价格表&#xff0c;阿里云ECS服务器优惠价99元一年起&#xff0c;轻量服务器优惠价61元一年&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云优惠券免费领取、优惠活…

GCC 内联汇编

LINUX下的汇编入门 AT&T风格 汇编 和GCC风格汇编 汇编代码的调试 前面写了三篇,是自我摸索三篇,摸着石头过河,有些或许是错误的细节,不必在意! 今天我们直接用GCC编译C语言代码,且在C语言里面内嵌AT&T风格的汇编! 前三篇大家了解即可,我们重点放在内嵌汇编里,简单快…

Java设计模式-单例模式(2)

大家好&#xff0c;我是馆长&#xff01;从今天开始馆长开始对java设计模式的创建型模式中的单例、原型、工厂方法、抽象工厂、建造者的单例模式进行讲解和说明。 单例模式&#xff08;Singleton&#xff09; 定义 某个类只能生成一个实例&#xff0c;该类提供了一个全局访问…

houdini rnn

1.3.RNN模型_哔哩哔哩_bilibili 此公式来自于吴恩达P1.3视频 按公式推测rnn内部结构,如有错误&#xff0c;敬请指正

解决系统开发中的跨域问题:CORS、JSONP、Nginx

文章目录 一、概述1.问题场景2.浏览器的同源策略3.解决思路 二、一点准备工作1.创建前端工程12.创建后端工程3.创建前端工程24.跨域问题 三、方法1&#xff1a;使用CORS四、方法2&#xff1a;JSONP五、方法3&#xff1a;Nginx1.安装和启动&#xff08;windows&#xff09;2.使用…

初识VUE

文章目录 Vue是什么1.创建一个Vue实例2.插值表达式{{ }}3.Vue的响应式特性4.开发者工具的安装 Vue是什么 概念&#xff1a;Vue是一个用于构建用户界面的渐进式框架 ①构建用户界面&#xff1a;基于数据渲染出用户看到的界面 ②渐进式&#xff1a;循序渐进 ③ 框架&#xff1…

6.4.4释放音频

6.4.4释放音频 许多Flash动画里的音乐或歌曲非常好听&#xff0c;能不能在没有源文件的情况下把里面的声音文件取出来呢&#xff1f;利用Swf2VideoConverter2可以轻松做到这一点。 1&#xff0e;单击“添加”按钮&#xff0c;在弹出的下拉菜单中选择“添加文件”&#xff0c;…

金蝶云星空表单插件获取单据体数据

文章目录 金蝶云星空表单插件获取单据体数据 金蝶云星空表单插件获取单据体数据 使用标识报错 var thisEntry this.View.Model.DataObject["FEntity"] as DynamicObjectCollection;应该使用实体属性 var thisEntry this.View.Model.DataObject["BillEntry&q…

搜索经典题——填充 9*9矩阵

题目&#xff1a;给定一个九行九列矩阵&#xff0c;填充矩阵元素&#xff0c;要求&#xff1a; 1、每一行每一列&#xff0c;每个小九宫格&#xff08;图片画粗的地方就是&#xff09;不能包含相同元素 2、每一行&#xff0c;每一列&#xff0c;每个小九宫格均会完整出现1-9的数…