【网络编程】序列化与反序列化

news2024/11/19 4:24:32

文章目录

  • 一、网络协议
  • 二、序列化和反序列化
    • 1. 结构化数据
    • 2. 序列化和反序列化
  • 三、网络版计算器
    • 1. 协议定制
    • 2. 客户端处理收到的数据
    • 3. 整体代码


一、网络协议

网络协议 是通信计算机双方必须共同遵从的一组约定,为了使数据在网络上能够从源地址到目的地址,网络通信的参与方必须遵循相同的规则,因此我们将这套规则称为协议,协议最终需要通过计算机语言的方式表示出来。


二、序列化和反序列化

1. 结构化数据

如果需要传输的数据是字符串,那么我们可以直接将这个字符串发送到网络当中。但是如果我们传输的是一些结构化的数据,此时就不能直接将这些数据发送到网络当中。

如果服务器将这些结构化的数据单独一个一个发送到网络当中,那么服务器从网络当中获取这些数据时也只能一个一个获取,并且结构化的数据往往具有特定的格式和规范,例如数据库表格或者特定的数据模型。如果直接将这些数据发送到网络,服务端可能需要处理大量复杂的格式转换和数据清洗工作,而且还有可能会影响数据的正确性。所以客户端最好把这些结构化的数据打包后统一发送到网络当中,此时服务端每次从网络当中获取到的就是一个完整的请求数据。

常见的打包方式:

  1. 将结构化的数据组合成一个字符串:
    客户端可以按照某种方式将这些结构化的数据组合成一个字符串,然后将该字符串发送到网络当中,当服务器接收到这个字符串时,以相同的方式将这个字符串进行解析。就可以从这个字符串中提取出这些结构化的数据。

  2. 定制结构体 + 序列化与反序列化
    客户端可以定制一个结构体,将需要交互的信息定义到这个结构体当中。客户端发送数据时先对数据进行序列化,服务端接收到数据后在对其进行反序列化,此时服务端就能得到客户端发送过来的结构体了。


2. 序列化和反序列化

  • 序列化: 将对象的状态信息转换为可以存储或传输的形式的过程。
  • 反序列化: 将字节序列恢复为的过程。

OSI七层模型中表示层的作用就是,实现设备固有数据格式和网络标准数据格式的转换。其中设备固有的数据格式指的是数据在应用层上的格式,而网络标准数据格式则指的是序列化之后可以进行网络传输的数据格式。

💕 序列化和反序列化的目的:

序列化的目的是方便网络数据的发送和接收,无论何种类型的数据,经过序列化后都变成了二进制序列,此时底层在进行网络传输时看到的都是二进制序列。

序列化后的二进制序列只有在网络传输时能够被底层识别,上层应用是无法识别序列化后的二进制数据的,因此需要将从网络中获取到的数据进行反序列化,将二进制序列的数据转换成应用层能够识别的数据格式。

在这里插入图片描述


三、网络版计算器

1. 协议定制

💕 请求数据和响应数据

这里我们实现一个请求结构体和一个响应结构体。

  • 请求数据结构体中包含两个操作数和一个操作符,操作数表示需要进行计算的两个数,操作符表示是要进行 +-*/ 中的哪一个操作。
  • 响应数据结构体需要包含一个计算结果和一个状态码,表示本次计算的结果和计算状态。

状态码所表示的含义:

  • 0 表示计算成功
  • 1 表示出现除0错误
  • 2 表示模0错误
  • 3 表示非法计算

因为协议定制好以后要被客户端和服务端同时遵守,所以需要将它写到一个头文件中,同时被客户端和服务端包含该头文件。

// 请求
class Request
{
public:
	Request()
	{}
	Request(int x, int y, char op):_x(x), _y(y), _op(op)
	{}
public:	
	int _x;
	int _y;
	char _op;
};

// 响应
class Response
{
public:
	Response()
	{}
	Response(int result, int code):_result(result), _code(code)
	{}
public:
	int _result;
	int _code;
};

💕 自定义序列化和反序列化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

请求序列化

在这里插入图片描述
请求反序列化

在这里插入图片描述

响应序列化

在这里插入图片描述

响应反序列化

在这里插入图片描述


💕 添加报头和去除报头

为了保证服务器在接收时能够接收到一个完整的报文,我们需要给已经序列化完成的字符串添加报头。此报头来标识服务器读取到的数据是否是完整的,因此我们在序列化后的字符串前面添加要发送的字符串的长度,以便于服务器读取到完整的报文。

添加报头

在这里插入图片描述

去除报头

在这里插入图片描述

去除报头


2. 客户端处理收到的数据

当客户端收到已经反序列化完成之后的数据后,需要将其分隔到数组里面,然后在转换为结构化的数据类型进行计算。

服务器收到去除字符串报头后的结果,需要将其分隔并反序列化

在这里插入图片描述
客户端收到去除字符串报头后的结果,需要将其分隔并反序列化

在这里插入图片描述


3. 整体代码

💕 Sock.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "Err.hpp"

using namespace std;
static const int gbacklog = 32;
static const int defaultfd = -1;

class Sock
{
public:
	Sock():_sock(defaultfd)
	{}

	Sock(int sock):_sock(sock)
	{}
	
	// 创建套接字
	void Socket()
	{
		_sock = socket(AF_INET, SOCK_STREAM, 0);
		if(_sock < 0)
		{
			logMessage(Fatal, "socket error, code: %d, errstring: %s", errno, strerror(errno));
			exit(SOCKET_ERR);
		}
	}

	// 绑定端口号 和 ip地址
	void Bind(const uint16_t& port)
	{
		struct sockaddr_in local;
		local.sin_addr.s_addr = INADDR_ANY; // 绑定任意IP地址
		local.sin_port = htons(port); // 绑定端口号
		local.sin_family = AF_INET;

		if(bind(_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
		{
			logMessage(Fatal, "bind error, code: %d, errstring: %s", errno, strerror(errno));
			exit(BIND_ERR);
		}
	}

	// 监听
	void Listen()
	{
		if(listen(_sock, gbacklog) < 0)
		{
			logMessage(Fatal, "listen error, code: %d, errstring: %s", errno, strerror(errno));
			exit(LISTEN_ERR);
		}
	}

	// 获取连接
	int Accept(string* clientip, uint16_t* clientport)
	{
		struct sockaddr_in temp;
		socklen_t len = sizeof(temp);

		int sock = accept(_sock, (struct sockaddr*)&temp, &len);
		if(sock < 0)
		{
			logMessage(Warning, "accept error, code: %d, errstring: %s", errno, strerror(errno));
		}
		else
		{
			*clientip = inet_ntoa(temp.sin_addr);
			*clientport = ntohs(temp.sin_port);
		}
		return sock;
	}

	// 客户端和服务器建立连接
	int Connect(const string& serverip, const uint16_t& serverport)
	{
		struct sockaddr_in server;
		memset(&server, 0, sizeof(server));
		server.sin_family = AF_INET;
		server.sin_port = htons(serverport);
		server.sin_addr.s_addr = inet_addr(serverip.c_str());
		
		return connect(_sock, (struct sockaddr*)&server, sizeof(server));
	}

	int Fd()
	{
		return _sock;
	}

	void Close()
	{
		if(_sock != defaultfd)
			close(_sock);
	}
	~Sock()
    {}

private:
	int _sock;
};

💕 Protocol.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>
#include "Util.hpp"
// 给网络版本计算器定制协议

// #define MYSELF 1

// 给网络版本计算器定制协议
namespace protocol_ns
{
	#define SEP " "
    #define SEP_LEN strlen(SEP) // 注意不能写成 sizeof
    #define HEADER_SEP "\r\n"
    #define HEADER_SEP_LEN strlen("\r\n")

	// 添加报头后 —— "长度"\r\n""_x _op _y"\r\n 
	string AddHeader(const string& str)
	{
		std::cout << "AddHeader 之前:\n"
                  << str << std::endl;

		string s = to_string(str.size());
		s += HEADER_SEP;
		s += str;
		s += HEADER_SEP;

		std::cout << "AddHeader 之后:\n"
                  << s << std::endl;

		return s;
	}

	// 去除报头 "长度"\r\n""_x _op _y"\r\n —— _x _op _y
	string RemoveHeader(const std::string &str, int len)
    {
		std::cout << "RemoveHeader 之前:\n"
                  << str << std::endl;

		string res = str.substr(str.size() - HEADER_SEP_LEN - len, len);
		return res;

		std::cout << "RemoveHeader 之后:\n"
                  << res << std::endl;
    }

	int ReadPackage(int sock, string& inbuffer, string*package)
	{
		std::cout << "ReadPackage inbuffer 之前:\n"
                  << inbuffer << std::endl;

		// 边读取
		char buffer[1024];
		ssize_t s = recv(sock, buffer, sizeof(buffer - 1), 0);

		if(s <= 0)
			return -1;
		buffer[s] = 0;
		inbuffer += buffer;

		std::cout << "ReadPackage inbuffer 之中:\n"
                  << inbuffer << std::endl;

		// 边读取边分析, "7"\r\n""10 + 20"\r\n
		auto pos = inbuffer.find(HEADER_SEP); // 查找 \r\n
		if(pos == string::npos)
			return 0;
		
		string lenStr = inbuffer.substr(0, pos); // 获取头部字符串
		int len = Util::toInt(lenStr);

		int targetPackageLen = lenStr.size() + len + 2 * HEADER_SEP_LEN; // 获取到的完整字符串的长度
		if(inbuffer.size() < targetPackageLen)
			return 0;
		*package = inbuffer.substr(0, targetPackageLen);
		inbuffer.erase(0, targetPackageLen);		
		
		std::cout << "ReadPackage inbuffer 之后:\n"
                  << inbuffer << std::endl;

		return len; // 返回有效载荷的长度
	}

	// Request && Response都要提供序列化和反序列化功能——自己手写

	// 请求
	class Request
    {
	public:
		Request()
		{}
		Request(int x, int y, char op):_x(x), _y(y), _op(op)
		{}
		
		// 协议的序列化 struct -> string
		bool Serialize(string* outStr)
		{
			*outStr = "";
#ifdef MYSELF
			string x_string = to_string(_x);
			string y_string = to_string(_y);

			// 手动序列化
			*outStr = x_string + SEP + _op + SEP + y_string;

			std::cout << "Request Serialize:\n"
                      << *outStr << std::endl;
#else
			Json::Value root; // Value 是一种万能对象, 可以接受任意的kv类型
			root["x"] = _x;
			root["y"] = _y;
			root["op"] = _op;

			// Json::FastWriter writer; // Writer 是用来序列化的, struct -> string
			Json::StyledWriter writer;
			*outStr = writer.write(root);
#endif
			return true;
		}

		// 协议的反序列化 string -> struct
		bool Deserialize(const string& inStr)
		{
#ifdef MYSELF
			// 将inStr分隔到数组里面 -> [0]=>10 [1]=>+ [2]=>20
			vector<string> result;
			Util::StringSplit(inStr, SEP, &result);
			if(result.size() != 3)
				return false;
			if(result[1].size() != 1)
				return false;
			
			_x = Util::toInt(result[0]);
			_y = Util::toInt(result[2]);
			_op = result[1][0];
#else
			Json::Value root;
			Json::Reader reader; // Reader是用来进行反序列化的
			reader.parse(inStr, root);

			_x  = root["x"].asInt();
			_y = root["y"].asInt();
			_op = root["op"].asInt();
#endif
			return true;
		}
		~Request()
		{}
	
	public:	
		int _x; // 操作数 _x
		int _y; // 操作数 _y
		char _op;// 操作符 _op
	};

	// 响应
	class Response
	{
	public:
		Response()
		{}
		Response(int result, int code):_result(result), _code(code)
		{}

		bool Serialize(string* outStr)
		{
			*outStr = "";
#ifdef MYSELF
			string res_string = to_string(_result);
			string code_string = to_string(_code);

			// 手动序列化
			*outStr = res_string + SEP + code_string;

			std::cout << "Response Serialize:\n"
                      << *outStr << std::endl;
#else
			Json::Value root;
			root["result"] = _result;
			root["code"] = _code;

			// Json::FastWriter writer;
			Json::StyledWriter writer;
			*outStr = writer.write(root);
#endif
			return true;
		}

		bool Deserialize(const string& inStr)
		{
#ifdef MYSELF
			// 将inStr分隔到数组里面 -> [0]=>10 [1]=>+ [2]=>20
			vector<string> result;
			Util::StringSplit(inStr, SEP, &result);
			if(result.size() != 2)
				return false;
			
			_result = Util::toInt(result[0]);
			_code = Util::toInt(result[1]);
#else
			Json::Value root;
			Json::Reader reader;
			reader.parse(inStr, root);
			_result = root["result"].asInt();
			_code = root["code"].asInt();
#endif
			Print();
			return true;
		}

		void Print()
        {
            std::cout << "_result: " << _result << std::endl;
            std::cout << "_code: " << _code << std::endl;
        }

	public:
		int _result;
		int _code;
	};
}

💕 CalculatorClient.cc

#include "Sock.hpp"
#include "Protocol.hpp"

#include <iostream>
#include <string>

using namespace std;
using namespace protocol_ns;

static void usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

enum
{
    LEFT,
    OPER,
    RIGHT
};


Request ParseLine(const std::string &line)
{
    std::string left, right;
    char op;
    int status = LEFT;

    int i = 0;
    while(i < line.size())
    {
        switch (status)
        {
        case LEFT:
            if (isdigit(line[i]))
                left.push_back(line[i++]);
            else
                status = OPER;
            break;
        case OPER:
            op = line[i++];
            status = RIGHT;
            break;
        case RIGHT:
            if (isdigit(line[i]))
                right.push_back(line[i++]);
            break;
        }
    }

    Request req;
    req._x = std::stoi(left);
    req._y = std::stoi(right);
    req._op = op;

    return req;
}

int main(int argc, char* argv[])
{
	if(argc != 3)
	{
		usage(argv[0]);
		exit(USAGE_ERR);
	}

	string serverip = argv[1];
	uint16_t serverport = atoi(argv[2]);

	Sock sock;
	sock.Socket();

	int n = sock.Connect(serverip, serverport);
	if(n != 0) return 1;

	string buffer;
	while(true)
	{
		cout << "Enter# ";
		string line;
		getline(cin, line);

		Request req = ParseLine(line);
		cout << "test:" << req._x << req._op << req._y << endl;

		// 序列化
		string sendString;
		req.Serialize(&sendString);

		// 添加报头
		sendString = AddHeader(sendString);

		// send
		send(sock.Fd(), sendString.c_str(), sendString.size(), 0);

		// 获取响应
		string package;
		int n = 0;
	START:
		n = ReadPackage(sock.Fd(), buffer, &package);
		if(n == 0)
			goto START;
		else if(n < 0)
			break;
		else
		{}

		// 去掉报头
		package = RemoveHeader(package, n);

		// 反序列化
		Response resp;
		resp.Deserialize(package);

		cout << "result: " << resp._result << "[code: " << resp._code << "]" << endl;
	}

	sock.Close();
	return 0;
}

💕 TcpServer.hpp

#pragma once
#include <iostream>
#include <functional>
#include <cstring>
#include <pthread.h>
#include "Sock.hpp"
#include "Protocol.hpp"

using namespace std;

namespace tcpserver_ns
{
	using namespace protocol_ns;
	using func_t = function<Response(const Request&)>;
	
	class TcpServer;
	class ThreadData
	{
	public:
		ThreadData(int sock, string ip, uint16_t port, TcpServer* tsvrp)
			:_sock(sock), _ip(ip), _port(port), _tsvrp(tsvrp)
		{}
		~ThreadData()
		{}
	public:
		int _sock;
		string _ip;
		uint16_t _port;
		TcpServer *_tsvrp;
	};

	class TcpServer
	{
	public:
		TcpServer(func_t func, uint16_t port)
		:_func(func), _port(port)
		{}

		// 初始化服务器
		void InitServer()
		{
			_listensock.Socket();
			_listensock.Bind(_port);
			_listensock.Listen();
			logMessage(Info, "init server done, listensock: %d, errstring: %s", errno, strerror(errno));
		}

		// 运行服务器
		void Start()
		{
			while(true)
			{
				string clientip;
				uint16_t clientport;

				int sock = _listensock.Accept(&clientip, &clientport);
				if(sock < 0) continue;
				logMessage(Debug, "get a new client, client info : [%s:%d]", clientip.c_str(), clientport);

				pthread_t tid; // 创建多线程
				ThreadData *td = new ThreadData(sock, clientip, clientport, this);
				pthread_create(&tid, nullptr, ThreadRoutine, td);
			}
		}

		static void* ThreadRoutine(void* args)
		{
			pthread_detach(pthread_self());
			ThreadData *td = static_cast<ThreadData *>(args);

			td->_tsvrp->ServiceIO(td->_sock, td->_ip, td->_port);
			logMessage(Debug, "thread quit, client quit ...");
			delete td;
			return nullptr;
		}

		// 服务器对客户端的数据进行IO处理
		void ServiceIO(int sock, const std::string &ip, const uint16_t &port)
		{
			string inbuffer;
			while(true)
			{
				// 保证自己读到一个完整的字符串报文 "7"\r\n""10 + 20"\r\n
				string package;
				int n = ReadPackage(sock, inbuffer, &package);
				if(n == -1)
					break;
				else if(n == 0)
					continue;
				else
				{
					// 已经得到了一个"7"\r\n""10 + 20"\r\n
					// 1. 提取有效载荷
					package = RemoveHeader(package, n);

					// 2. 已经读到了一个完整的string
					Request req;
					req.Deserialize(package);

					// 3. 直接提取用户的请求数据
					Response resp = _func(req); // 业务逻辑
					
					// 4. 给用户返回响应——序列化
					string send_string;
					resp.Serialize(&send_string);

					// 5. 添加报头
					send_string = AddHeader(send_string);

					// 6. 发送
					send(sock, send_string.c_str(), send_string.size(), 0);
				}
			}
			close(sock);
		}

		~TcpServer()
		{}
	private:
		uint16_t _port; // 端口号
		Sock _listensock; // 监听套接字
		func_t _func;
	};
}

💕 CalculatorServer.cc

#include "TcpServer.hpp"
#include <memory>
using namespace tcpserver_ns;

Response calculate(const Request &req)
{
    // 走到这里,一定保证req是有具体数据的!

    Response resp(0, 0);
    switch (req._op)
    {
    case '+':
        resp._result = req._x + req._y;
        break;
    case '-':
        resp._result = req._x - req._y;
        break;
    case '*':
        resp._result = req._x * req._y;
        break;
    case '/':
        if (req._y == 0)
            resp._code = 1;
        else
            resp._result = req._x / req._y;
        break;
    case '%':
        if (req._y == 0)
            resp._code = 2;
        else
            resp._result = req._x % req._y;
        break;
    default:
        resp._code = 3;
        break;
    }

    return resp;
}

int main()
{
	uint16_t port = 8080;
	unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port));
	tsvr->InitServer();
	tsvr->Start();
	return 0;
}

💕 Util.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>

using namespace std;

class Util
{
public:
	static bool StringSplit(const string&str, const string& sep, vector<string>* result)
	{
		size_t start = 0;
		while(start < str.size())
		{
			auto pos = str.find(sep, start);
			if(pos == string::npos) break;
			result->push_back(str.substr(start, pos - start));
			start = pos + sep.size();
		}

		if(start < str.size())
			result->push_back(str.substr(start));
		return true;
	}

	static int toInt(const string& s)
	{
		return atoi(s.c_str());
	}
};

💕 效果演示

在这里插入图片描述

整体源码


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

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

相关文章

CCF CSP认证 历年题目自练Day33

题目一 试题编号&#xff1a; 202212-1 试题名称&#xff1a; 现值计算 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 问题描述 评估一个长期项目的投资收益&#xff0c;资金的时间价值是一个必须要考虑到的因素。简单来说&#xff0c;假设…

华为DHCP配置实例

条件&#xff1a; R1为DHCP配置网关&#xff0c;S1为二层交换机 要求&#xff1a; PC1获取到vlan10地址&#xff0c;PC2获取vlan10地址&#xff0c;PC3获取vlan20地址 方法1&#xff1a; S1正常配置vlan10&#xff0c;20&#xff0c;配置与R1相连的1口为trunk口 R1的1口&a…

python中使用xml.dom.minidom模块读取解析xml文件

python中可以使用xml.dom.minidom模块读取解析xml文件 xml.dom.minidom模块应该是内置模块不用下载安装 对于一个xml文件来说比如这个xml文件的内容为如下 <excel version"1.0" author"huangzhihui"><table id"1"><colum id&qu…

ROS OpenCV库 示例

OpenCV库&#xff08;Open Source Computer Vision Library&#xff09;是一个基于BSD许可发行的跨平台开源计算机视觉库&#xff0c;可以运行在Linux、Windows和mac OS等操作系统上。OpenCV由一系列C函数和少量C类构成&#xff0c;同时提供C、Python、Ruby、MATLAB等语言的接口…

【Linux】shell运行原理及权限

主页点击直达&#xff1a;个人主页 我的小仓库&#xff1a;代码仓库 C语言偷着笑&#xff1a;C语言专栏 数据结构挨打小记&#xff1a;初阶数据结构专栏 Linux被操作记&#xff1a;Linux专栏 LeetCode刷题掉发记&#xff1a;LeetCode刷题 算法&#xff1a;算法专栏 C头疼…

【密码学】第二章 密码学的基本概念

1、密码学定义 密码编制学和密码分析学共同组成密码学 密码编制学&#xff1a;研究密码编制密码分析学&#xff1a;研究密码破译 2、密码体制的五个组成部分 明文空间M&#xff0c;全体明文的集合密文空间C&#xff0c;全体密文的集合密钥空间K&#xff0c;全体密钥的集合。…

Unity之ShaderGraph如何实现上下溶解

前言 我们经常在电影中见到的一个物体或者人物&#xff0c;从头上到脚下&#xff0c;慢慢消失的效果&#xff0c;我么今天就来体验一下这个上下溶解。 主要节点 Position节点&#xff1a;提供对网格顶点或片段的Position 的访问 Step节点&#xff1a;如果输入In的值大于或等…

openGauss学习笔记-101 openGauss 数据库管理-管理数据库安全-客户端接入之用SSH隧道进行安全的TCP/IP连接

文章目录 openGauss学习笔记-101 openGauss 数据库管理-管理数据库安全-客户端接入之用SSH隧道进行安全的TCP/IP连接101.1 背景信息101.2 前提条件101.3 操作步骤 openGauss学习笔记-101 openGauss 数据库管理-管理数据库安全-客户端接入之用SSH隧道进行安全的TCP/IP连接 101.…

【C++】手撕STL系列——stack,queue篇

前言 前面实现了string和vector&#xff0c;理所应当就该轮到stack和queue啦&#xff0c;本篇还会涉及到一个比较重要且听起来很厉害的概念——适配器模式 适配器模式 在之前数据结构初阶的学习过程中&#xff0c;我们学习的栈是由数组加上一些限制组成的容器&#xff0c;底…

SpringBoot面试题6:Spring Boot 2.X 有什么新特性?与 1.X 有什么区别?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Spring Boot 2.X 有什么新特性?与 1.X 有什么区别? Spring Boot是一种用于简化Spring应用程序开发的框架,它提供了自动配置、起步依赖和快速开…

7.继承与多态 对象村的优质生活

7.1 民法亲属篇&#xff1a;继承&#xff08;inheritance&#xff09; 了解继承 在设计继承时&#xff0c;你会把共同的程序代码放在某个类中&#xff0c;然后告诉其他的类说此类是它们的父类。当某个类继承另一个类的时候&#xff0c;也就是子类继承自父类。以Java的方式说&…

微信小程序框架---视图层逻辑层API事件

目录 前言 一、小程序框架介绍 1.响应的数据绑定 2.页面管理 3.基础组件 4.丰富的 API 二、视图层 View 1.WXML 数据绑定 列表渲染 条件渲染 模板 2.WXSS 尺寸单位 样式导入 内联样式 选择器 全局样式与局部样式 3.WXS 示例 注意事项 页面渲染 数据处理 …

VMtools安装Euler系统

前言 本文章针对刚安装好系统&#xff0c;未进行任何配置的&#xff0c;建议安装好系统先安装vmtools不然很多功能不能使用&#xff0c;也不能传文件 开机先等待开机自检 在下面这个地方输入用户名和密码&#xff08;在安装过程中设置的&#xff09; 成功登录 点击安装vm…

Go实现CORS(跨域)

引言 很多时候&#xff0c;需要允许Web应用程序在不同域之间&#xff08;跨域&#xff09;实现共享资源。本文将简介跨域、CORS的概念&#xff0c;以及如何在Golang中如何实现CORS。 什么是跨域 如果两个 URL 的协议、端口&#xff08;如果有指定的话&#xff09;和主机都相…

Maven打包添加本地工程jar包

前言 先吐槽几句,公司有一小组专门来做各个项目的测试环境以及打包上线的工作&#xff0c;我们称之为XX,这个XX并不是什么业务领导&#xff0c;也只是一个螺丝钉。这群人每天对上跪舔&#xff0c;对其他人爱搭不理&#xff0c;给人一种高高在上的感觉&#xff0c;之前的一个老…

二十七、【四种蒙版】

文章目录 图层蒙版剪贴蒙版快速蒙版矢量蒙版 图层蒙版 在当前图层加上蒙版&#xff0c;黑色画笔的可以让当前图层消失&#xff0c;白色的画笔可以让当前图层出现&#xff1a; 无论填充什么样的颜色&#xff0c;蒙板只有黑白灰三种颜色。模板最简单应用就是我们在插入图形的时候…

Ubuntu22.04安装及初始配置

前言 centos即将退出历史舞台&#xff0c;一代经典Centos7虽然经典&#xff0c;但后继无人。与此同时&#xff0c;ubuntu社区&#xff0c;无论是在桌面端、服务端还是容器生态等都是蓬勃发展。所以&#xff0c;我们正式的运行环境会以Ubuntu20.04为主。本文章定位为操作系统的…

【C++入门系列】——缺省参数,函数重载,引用和内联函数

​作者主页 &#x1f4da;lovewold少个r博客主页 ⚠️本文重点&#xff1a;c 缺省参数 引用与指针 &#x1f604;每日一言&#xff1a;青春就像一只容器&#xff0c;装满了不安躁动青涩与偶尔的疯狂。 目录 缺省参数 缺省函数的定义 缺省参数分类 函数重载 为什么C语言不支…

单片机判断语句与位运算的坑

一.问题描述 在我判断Oled的某点的值是否为1时,用到了如下判断语句 if(oled[x][y/8] &1<<(y%8)但是,当我将其改为如下的判断语句,代码却跑出BUG了 if((oled[x][y/8]&1<<(y%8))1)二.原因分析 1.if语句理解错误 首选让我们看看下面的代码运行结果 #inc…

Day1力扣打卡

打卡记录 最长相邻不相等子序列 I&#xff08;脑筋急转弯&#xff09; 链接 思路&#xff1a;形如 11100110001 要达到最大&#xff0c;必须在重复数字选出一个&#xff0c;即在111中取一个1&#xff0c;在00中取一个0&#xff0c;以此类推最终便得到最长相邻不相等子序列。 c…