C++之运算符重载系列深入学习:从入门到精通!

news2024/11/24 10:04:45

为什么需要对运算符进行重载

C++预定义中的运算符的操作对象只局限于基本的内置数据类型,但是对于我们自定义的类型是没有办法操作的。但是大多时候我们需要对我们定义的类型进行类似的运算,这个时候就需要我们对这么运算符进行重新定义,赋予其新的功能,以满足自身的需求。比如:

class Complex
{
public:
	Complex(double real = 0, double image = 0)
		: _real(real)
		, _image(image)
	{
	}
private:
	double _real;
	double _image;
};
void test()
{
	Complex c1(1, 2), c2(3, 4);
	Complex c3 = c1 + c2;//编译出错
}

为了使对用户自定义数据类型的数据的操作与内置数据类型的数据的操作形式一致,C++提供了运算符的重载,通过把C++中预定义的运算符重载为类的成员函数或者友元函数,使得对用户的自定义数据类型的数据(对象)的操作形式与C++内部定义的类型的数据一致。

运算符重载的实质就是函数重载函数多态。运算符重载是一种形式的 C++ 多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式,运算符函数形式:

返回类型 operator 运算符(参数表)
{
	//...
}

运算符重载的规则

运算符是一种通俗、直观的函数,比如:int x = 2 + 3; 语句中的 “+” 操作符,系统本身就提供了很多个重载版本:

int operator+(int, int);
double operator+(double, double);

但并不是所有的运算符都可以重载。可以重载的运算符有:

在这里插入图片描述

运算符重载还具有以下规则:

1、为了防止用户对标准类型进行运算符重载,C++规定重载的运算符的操作对象必须至少有一个是自定义类型或枚举类型

2、重载运算符之后,其优先级和结合性还是固定不变的。

3、重载不会改变运算符的用法,原来有几个操作数、操作数在左边还是在右边,这些都不会改变。

4、重载运算符函数不能有默认参数,否则就改变了运算符操作数的个数。

5、重载逻辑运算符(&&,||)后,不再具备短路求值特性。

6、不能臆造一个并不存在的运算符,如@、$等

运算符重载的形式

运算符重载的形式有三种:

1、采用普通函数的重载形式

2、采用成员函数的重载形式

3、采用友元函数的重载形式

以普通函数形式重载

在上面的例子中,Complex对象无法执行加法操作,接下来我们重载+运算符。由于之前的定义中Complex的成员都设置成了private成员,所以不能访问,我们需要在类中添加2个get函数,获取其值。

class Complex
{
public:
	Complex(double real = 0, double image = 0)
		: _real(real)
		, _image(image)
	{
	}

public:
	double getReal() const
	{
		return _real;
	}
	double getImage() const
	{
		return _image;
	}
	
private:
	double _real;
	double _image;
};

Complex operator+(const Complex& lhs, const Complex& rhs)
{
	return Complex(lhs.getReal() + rhs.getReal(), lhs.getImage() +
		rhs.getImage());
}

void test()
{
	Complex c1(1, 2), c2(3, 4);
	Complex c3 = c1 + c2;//此时编译通过
}

以成员函数形式重载

成员函数形式的运算符声明和实现与成员函数类似,首先应当在类定义中声明该运算符,声明的具体形式为:

返回类型 operator 运算符(参数列表);

既可以在类定义的同时定义运算符函数使其成为 inline 型,也可以在类定义之外定义运算符函数,但要使用作用域限定符::,类外定义的基本格式为:

返回类型 类名::operator 运算符(参数列表)
{
	//...
}

注意:用成员函数重载双目运算符时,左操作数无须用参数输入,而是通过隐含的 this 指针传入。回到 Complex 的例子,如果以成员函数形式进行重载,则不需要定义get函数:

class Complex
{
public:
	//...
	Complex operator+(const Complex & rhs)
	{
		return Complex(_real + rhs._real, _image + rhs._image);
	}
};

以友元函数形式重载

如果以友元函数形式进行重载,同样不需要定义get函数:

class Complex{
	//...
	friend Complex operator+(const Complex &lhs, const Complex &rhs);
};

Complex operator+(const Complex &lhs, const Complex &rhs){
	return Complex(lhs._real + rhs._real, lhs._image + rhs._image);
}

运算符重载可以改变运算符内置的语义,如以友元函数形式定义的加操作符:

Complex operator+(const Complex &lhs,const Complex &rhs){
	return complex(lhs._real - rhs._real, lhs._image - rhs._image);
}

明明是加操作符,但函数内却进行的是减法运算,这是合乎语法规则的,不过却有悖于人们的直觉思维,会引起不必要的混乱。因此,除非有特别的理由,尽量使重载的运算符与其内置的、广为接受的语义保持一致。

特殊运算符的重载

复合赋值运算符

复合赋值运算符推荐以成员函数的形式进行重载,包括这些(+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=),因为对象本身会发生变化。

class Complex
{
public:
	//对于复合赋值运算符,对象本身发生了改变,推荐使用成员函数形式
	Complex &operator+=(const Complex &rhs){
		cout << "Complex &operator+=(const Complex &)" << endl;
		_dreal += rhs._dreal;
		_dimag += rhs._dimag;
		return *this;
	}

	Complex &operator-=(const Complex &rhs){
		cout << "Complex &operator-=(const Complex &)" << endl;
		_dreal -= rhs._dreal;
		_dimag -= rhs._dimag;
		return *this;
	}
};

自增自减运算符

自增运算符++和自减运算符–推荐以成员函数形式重载,分别包含两个版本,即运算符前置形式(如 ++x)和运算符后置形式(如 x++),这两者进行的操作是不一样的。因此,当我们在对这两个运算符进行重载时,就必须区分前置和后置形式。

C++根据参数的个数来区分前置和后置形式。如果按照通常的方法(成员函数不带参数)来重载++/–运算符,那么重载的就是前置版本。要对后置形式进行重载,就必须为重载函数再增加一个int类型的参数,该参数仅仅用来告诉编译器这是一个运算符后置形式,在实际调用时不需要传递实参

class Complex
{
public:
	//...
	//前置形式
	Complex& operator++()
	{
		++_real;
		++_image;
		return *this;
	}
	//后置形式
	Complex operator++(int) //int作为标记,并不传递参数
	{
		Complex tmp(*this);
		++_real;
		++_image;
		return tmp;
	}
};
void test()
{
	int a = 3;
	int b = 4;
	(++a);//表达式的值与a的值,需要进行区分,对于重载前置++与后置++是有一定参考价值的
	(a++)
}

赋值运算符

对于赋值运算符=,只能以成员函数形式进行重载,我们已经在类和对象中讲过了,就不再赘述,大家可以翻看前面的内容。

函数调用运算符

我们知道,普通函数执行时,有一个特点就是无记忆性,一个普通函数执行完毕,它所在的函数栈空间就会被销毁,所以普通函数执行时的状态信息,是无法保存下来的,这就让它无法应用在那些需要对每次的执行状态信息进行维护的场景。大家知道,我们学习了成员函数以后,有了对象的存在,对象执行某些操作之后,只要对象没有销毁,其状态就是可以保留下来的,但在函数作为参数传递时,会有障碍。为了解决这个问题,C++引入了函数调用运算符。函数调用运算符的重载形式只能是成员函数形式,其形式为:

返回类型 类名::operator()(参数列表){
	//...
}

在定义 () 运算符的语句中,第一对小括号总是空的,因为它代表着我们定义的运算符名,第二对小括号就是函数参数列表了,它与普通函数的参数列表完全相同。对于其他能够重载的运算符而言,操作数个数都是固定的,但函数调用运算符不同,它的参数是根据需要来确定的, 并不固定。

接下来,我们来看一个例子:

class FunctionObject
{
public:
	FunctionObject(int count = 0) : _count(count)
	{
	}
	void operator()(int x)
	{
		++_count;
		cout << " x = " << x << endl;
	}
	int operator()(int x, int y)
	{
		++_count;
		return x + y;
	}
	int _count;//函数对象的状态
};
void test()
{
	FunctionObject fo;
	int a = 3, b = 4;
	fo(a);
	cout << fo(a, b) << endl;
}

从例子可以看出,一个类如果重载了函数调用operator(),就可以将该类对象作为一个函数使用。对于这种重载了函数调用运算符的类创建的对象,我们称为函数对象(Function Object)。函数也是一种对象,这是泛型思考问题的方式。

下标访问运算符

下标访问运算符 [] 通常用于访问数组元素,它是一个二元运算符,如 arr[ idx ] 可以理解成 arr 是左操作数,idx 是右操作数。对下标访问运算符进行重载时,只能以成员函数形式进行,如果从函数的观点来看,语句 arr[idx] ;可以解释为 arr.operator[] (idx) ;,因此下标访问运算符的重载形式如下:

返回类型 &类名::operator[](参数类型);
返回类型 &类名::operator[](参数类型) const;

下标运算符的重载函数只能有一个参数,不过该参数并没有类型限制,任何类型都可以。如果类中未重载下标访问运算符,编译器将会给出其缺省定义,在其表达对象数组时使用。

class CharArray
{
public:
	CharArray(size_t size = 10)
		: _size(size)
		, _array(new char[_size]())
	{
	}
	char& operator[](size_t idx)
	{
		if (idx < _size)
		{
			return _array[idx];
		}
		else
		{
			static char nullchar = '\0';
			return nullchar;
		}
	}
	const char& operator[](int idx) const//针对的是const对象
	{
		if (idx < _size)
		{
			return _array[idx];
		}
		else
		{
			static char nullchar = '\0';
			return nullchar;
		}
	}
	~CharArray()
	{
		delete[] _array;
	}
private:
	size_t _size;
	char* _array;
};

我们之前使用过的std::string同样也重载了下标访问运算符,这也是为什么它能像数组一样去访问元素的原因。

成员访问运算符

成员访问运算符包括箭头访问运算符->和解引用运算符*,我们先来看箭头运算符->.

箭头运算符只能以成员函数的形式重载,其返回值必须是一个指针或者重载了箭头运算符的对象。来看下例子:

class Data
{
public:
	int getData() const
	{
		return _data;
	}
private:
	int _data;
};

class MiddleLayer
{
public:
	MiddleLayer(Data* pdata)
		: _pdata(pdata)
	{}
	//返回值是一个指针
	Data* operator->()
	{
		return _pdata;
	}
	Data& operator*()
	{
		return *_pdata;
	}
	~MiddleLayer()
	{
		delete _data;
	}
private:
	Data* _pdata;
};

class ThirdLayer
{
public:
	ThirdLayer(MiddleLayer* ml)
		: _ml(ml)
	{
	}
	//返回一个重载了箭头运算符的对象
	MiddleLayer& operator->()
	{
		return *_ml;
	}
	~ThirdLayer()
	{
		delete _ml;
	}
private:
	MiddleLayer* _ml;
};

void test()
{
	MiddleLayer ml(new Data());
	cout << ml->getData() << endl;
	cout << (ml.operator->())->getData() << endl;
	cout << (*ml).getData() << endl;
	ThirdLayer tl(new MiddleLayer(new Data()));
	cout << tl->getData() << endl;
	cout << ((tl.operator->()).operator->())->getData() << endl;
}

输入输出流运算符

在之前的例子中,我们如果想打印一个对象时,常用的方法是通过定义一个 print 成员函数来完成,但使用起来不太方便。我们希望打印一个对象,与打印一个整型数据在形式上没有差别(如下例子),那就必须要重载 << 运算符。

void test()
{
	int a = 1, b = 2;
	cout << a << b << endl;
	Point pt1(1, 2), pt2(3, 4);
	cout << pt1 << pt2 << endl;
}

从上面的形式能看出,cout 是左操作数,a 或者 pt1 是右操作数,那输入输出流能重载为成员函数形式吗?我们假设是可以的,由于非静态成员函数的第一个参数是隐含的 this 指针,代表当前对象本身,这与其要求是冲突的,因此 >> 和 << 不能重载为成员函数,只能是非成员函数,如果涉及到要对类中私有成员进行访问,还得将非成员函数设置为类的友元函数。

class Point
{
public:
	//...
	friend ostream& operator<<(ostream& os, const Point& rhs);
	friend istream& operator>>(istream& is, Point& rhs);
private:
	int _ix;
	int _iy;
};

ostream& operator<<(ostream& os, const Point& rhs)
{
	os << "(" << rhs._ix
		<< "," << rhs._iy
		<< ")";
	return os;
}

istream& operator>>(istream& is, Point& rhs)
{
	is >> rhs._ix;
	is >> rhs._iy;
	return is;
}

通常来说,重载输出流运算符用得更多一些。同样的,输入流运算符也可以进行重载,如上。

总结

对于运算符重载时采用的形式的建议:

1、所有的一元运算符,建议以成员函数重载

2、运算符 = () [] -> * ,必须以成员函数重载

3、运算符 += -= /= *= %= ^= &= != >>= <<= 建议以成员函数形式重载

4、其它二元运算符,建议以非成员函数重载

类型转换

前面介绍过对普通变量的类型转换,比如说 int 型转换为 long 型,double 型转换为 int 型,接下来我们要讨论下类对象与其他类型的转换。转换的方向有:

1、由其他类型向自定义类型转换

2、由自定义类型向其他类型转换

由其它类型向自定义类型转换

由其他类型向定义类型转换是由构造函数来实现的,只有当类中定义了合适的构造函数时,转换才能通过。这种转换,一般称为隐式转换。下面,我们通过一个例子进行说明:

class Point
{
public:
	Point(int ix = 0, int iy = 0)
		: _ix(ix)
		, _iy(iy)
	{}
	//...
	friend ostream& operator<<(ostream& os, const Point& rhs);
private:
	int _ix;
	int _iy;
};
ostream& operator<<(ostream& os, const Point& rhs)
{
	os << "(" << rhs._ix
		<< "," << rhs._iy
		<< ")";
	return os;
}
void test()
{
	Point pt = 1;//隐式转换
	cout << "pt = " << pt << endl;
}

这种隐式转换有时候用起来是挺好的,比如,我们以前学过的std::string,当执行:

std::string s1 = "hello,world";

该语句时,这里其实是有隐式转换的,但该隐式转换的执行很自然,很和谐。而上面把一个 int 型数据直接赋值给一个 Point 对象,看起来就是比较诡异的,难以接受,所以这里我们是不希望发生这样的隐式转换的。那怎么禁止隐式转换呢,比较简单,只需要在相应构造函数前面加上 explicit 关键字就能解决。

由自定义类型向其它类型转换

由自定义类型向其他类型的转换是由类型转换函数完成的,这是一个特殊的成员函数。它的形式如下:

operator 目标类型()
{
	//...
}

类型转换函数具有以下的特征:

1、必须是成员函数;

2、参数列表中没有参数;

3、没有返回值,但在函数体内必须以return语句返回一个目标类型的变量。

我们来看一个例子:

class Fraction
{
public:
	Fraction(double numerator, double denominator)
		: _numerator(numerator)
		, _denominator(denominator)
	{
	}
	operator double()
	{
		return _numerator / _denominator;
	}
	operator Point()
	{
		return Point(_numerator, _denominator);
	}
private:
	double _numerator;
	double _denominator;
};
void test()
{
	Fraction f(2, 4);
	cout << "f = " << f << endl;
	double x = f + 1.11;
	cout << "x = " << x << endl;
	double y = f;
}

补充:类作用域

作用域可以分为类作用域、类名的作用域以及对象的作用域几部分内容。在类中定义的成员变量和成员函数的作用域是整个类,这些名称只有在类中(包含类的定义部分和类外函数实现部分)是可见的,在类外是不可见的,因此,可以在不同类中使用相同的成员名。另外,类作用域意味着不能从外部直接访问类的任何成员,即使该成员的访问权限是 public ,也要通过对象名来调用,对于 static 成员函数,要指定类名来调用。

如果发生 “屏蔽” 现象,类成员的可见域将小于作用域,但此时可借助 this 指针或 “类名::” 形式指明所访问的是类成员,这有些类似于使用 :: 访问全局变量。例如:

#include <iostream>
using std::cout;
using std::endl;

int num = 1;

namespace test
{
	int num = 20;
	class Example
	{
	public:
		void print(int num) const
		{
			cout << "形参num = " << num << endl;
			cout << "数据成员num = " << this->num << endl;
			cout << "数据成员num = " << Example::num << endl;
			cout << "命名空间中num = " << test::num << endl;
			cout << "全局变量num = " << ::num << endl;
		}
	private:
		int num;
	};
}//end of namespace test

和函数一样,类的定义没有生存期的概念,但类定义有作用域和可见域。使用类名创建对象时,首要的前提是类名可见,类名是否可见取决于类定义的可见域,该可见域同样包含在其作用域中,类本身可被定义在3种作用域内,这也是类定义的作用域。

全局作用域

在函数和其他类定义的外部定义的类称为全局类,绝大多数的 C++ 类是定义在该作用域中,我们在前面定义的所有类都是在全局作用域中,全局类具有全局作用域。

类作用域

一个类可以定义在另一类的定义中,这是所谓嵌套类或者内部类,举例来说,如果类 A 定义在类 B 中,如果 A 的访问权限是 public ,则 A 的作用域可认为和 B 的作用域相同,不同之处在于必须使用 B::A 的形式访问 A 的类名。当然,如果 A 的访问权限是 private ,则只能在类内使用类名创建该类的对象,无法在外部创建 A 类的对象。

class Line
{
public:
	Line(int x1, int y1, int x2, int y2);
	void printLine() const;
private:
	class Point
	{
	public:
		Point(int x = 0, int y = 0)
			: _x(x), _y(y)
		{
		}
		void print() const;
	private:
		int _x;
		int _y;
	};
private:
	Point _pt1;
	Point _pt2;
};
Line::Line(int x1, int y1, int x2, int y2)
	: _pt1(x1, y1)
	, _pt2(x2, y2)
{
}
void Line::printLine() const
{
	_pt1.print();
	cout << " ---> ";
	_pt2.print();
	cout << endl;
}
void Line::Point::print() const
{
	cout << "(" << _x
		<< "," << _y
		<< ")";
}

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

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

相关文章

智能家居中高性能联网通信方案,乐鑫ESP32-S3/C3无线Wi-Fi蓝牙应用

随着科技的飞速发展&#xff0c;智能家居已经不再是科幻小说中的概念&#xff0c;而是走进了千家万户的现实生活。 智能家居是广泛的系统性产品概念&#xff0c;以住宅为载体&#xff0c;运用物联网、网络通信和人工智能等技术&#xff0c;接收信号并判断&#xff0c;提供更加…

美股全线反弹,市场情绪回暖

一、市场概况 昨夜美股三大股指全线收涨&#xff0c;市场情绪明显回暖。道琼斯工业平均指数上涨1.76%&#xff0c;纳斯达克综合指数上涨2.87%&#xff0c;标普500指数则上涨2.3%。美债市场方面&#xff0c;美国十年期国债收益率上涨1.141%&#xff0c;报3.99%&#xff0c;两年…

[ Python ]使用Charles对Python程序发出的Get与Post请求抓包-解决Python程序报错问题

目录 一、前言 二、Charles 三、抓取Python请求 3.1 正常运行 3.2 程序报错 3.2.1 报错信息 3.2.2 解决方法 3.3 取消警告信息 四、总结 一、前言 在Python开发中&#xff0c;网络请求是常见的操作之一。无论是使用内置的urllib库还是第三方库requests&#xff0c;都可…

Java小白入门到实战应用教程-Scanner类及IO流讲解

Java小白入门到实战应用教程-Scanner类及IO流讲解 Scanner类 我们前面写的很多例子都是程序独自执行的&#xff0c;但是我们做编程写代码的目的是要实现能人和代码去交互的。 现在我们就来了解一个知识点&#xff0c;去实现最简单的人和程序的交互。 在java中通过Scanner类…

MySQL第3讲--数据类型和表的修改和删除

文章目录 前言数据类型数值类型整数类型浮点数和定点数 字符串类型字符类型&#xff1a;文本类型&#xff1a;二进制数据类型 日期和时间类型实例分析 表的操作添加字段修改字段删除字段修改表名删除表 DDL总结DDL数据库操作DDL表操作 前言 上一节在MySQL第2讲–关系型数据库以…

kubernetes 管理平台 Pod管理多容器 与嵌入式脚本

资源清单文件 模板与帮助信息 管理资源对象 多容器 Pod 管理多容器 Pod 自定义任务 容器保护策略 宽限期策略 Pod调度策略

CUDA编程从零到壹

如今&#xff0c;当我们谈论深度学习时&#xff0c;为了提高性能&#xff0c;我们通常会将其实现与使用 GPU 联系起来。 GPU&#xff08;图形处理单元&#xff09;最初设计用于加速图像、2D 和 3D 图形的渲染。然而&#xff0c;由于它们能够执行许多并行操作&#xff0c;它们的…

linux 源码部署polardb-x 错误汇总

前言 在linux 源码部署polardb-x 遇到不少错误&#xff0c;特在此做个汇总。 问题列表 CN 启动报错 Failed to init new TCP 详细错误如下 Caused by: Failed to init new TCP. XClientPool to my_polarx#267b21d8127.0.0.1:33660 now 0 TCP(0 aging), 0 sessions(0 runni…

【汇总】测开高频面试题

加油 &#xff01;&#xff01;&#xff01; &#x1f525; 谈谈对测试的理解 我认为测试是发现并及时解决问题&#xff1a;包括功能、性能、用户体验❤️等方面的验证 … 通过提前定位并修复缺陷&#xff0c;可以减少未来维护成本、保障软件质量、提升用户满意度❤️ … 我了…

大麦/猫眼抢票-狠货

大部分购买方式已迁移至手机端&#xff0c;专注研究移动端 小白操作–仅供学习 注意在帐号按权重的第三方账号设置解绑淘宝&#xff0c;否则有可能在抢票时候出现滑块&#xff0c;影响抢票,抢票优先选择大麦 ⚠️核心内容参考&#xff1a; 据悉&#xff0c;在购票环节&…

23_windows 使用sqlmap、kali使用sqlmap,SQL注入、sqlmap自动注入

sqlmap介绍 安装sqlmap 安装python环境 链接&#xff1a;https://pan.baidu.com/s/16QhhYCppSvuUikhKiOHNgg?pwd9LJY 提取码&#xff1a;9LJY C:\Users\leyilea> python // 测试python能不能用 >>> exit() // 退出 测试sqlmap是否可用 kali中运行sqlmap&#xff…

KAN网络简明教程

在不断发展的机器学习领域&#xff0c;最近一篇题为“KAN&#xff1a;柯尔莫哥洛夫-阿诺德网络”的研究论文在爱好者中引发了一波热潮。这种创新方法挑战了多层感知器 (MLP) 的传统观点&#xff0c;为神经网络架构提供了全新的视角。 NSDT工具推荐&#xff1a; Three.js AI纹理…

Qt 实战(9)窗体 | 9.2、QDialog

文章目录 一、QDialog1、基本概念2、常用特性2.1、模态与非模态2.2、数据交互 3、总结 前言&#xff1a; Qt框架中的QDialog类是一个功能强大且灵活的对话框控件&#xff0c;广泛应用于各种GUI&#xff08;图形用户界面&#xff09;应用程序中&#xff0c;用于处理用户输入、消…

map/multimap容器

一、 map基本概念 简介: map中所有元素都是pair pair中第一个元素为key(键值)&#xff0c;起到索引惟用&#xff0c;第二个元素为value(实值) 所有元素都会根据元素的键值自动排序 本质: map/multimap属于关联式容器&#xff0c;底层结构是用二叉树实现 优点: 可以根据ke…

欧拉系统离线安装界面ukui

1、官网下载安装镜像iso后&#xff0c;默认没有gui openEuler | 开源社区 | openEuler社区官网openEuler是一个开源、免费的 Linux 发行版平台&#xff0c;将通过开放的社区形式与全球的开发者共同构建一个开放、多元和架构包容的软件生态体系。同时&#xff0c;openEuler 也是…

数据结构与算法|算法总结|动态规划篇之子序列、子数组问题

首先我们要明确以下两个问题&#xff1a; 子序列&#xff1a;子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 子数组&#xff1a;子数组是数…

HTTP协议基础知识【后端 4】

HTTP协议基础知识 HTTP&#xff08;Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff09;是互联网上应用最为广泛的一种网络协议&#xff0c;它定义了客户端&#xff08;如浏览器&#xff09;与服务器之间数据传输的格式和规则。无论是浏览网页、在线购物还是使…

常见漏洞扫描工具!

AWVS 下载链接&#xff1a; 链接: https://pan.baidu.com/s/1rFq9iMV8oluumyylA22MFg?pwdmyc2 提取码: myc2 打开awvs安装⼯具&#xff0c;一路默认&#xff0c;不要修改文件安装路径&#xff0c;不然会失败&#xff1b; email和password是之后登录服务的账号密码&#xf…

算法日记day 34(动归之使用最小花费爬楼梯|不同路径2|整数拆分|不同的二叉搜索树)

一、使用最小花费爬楼梯 题目&#xff1a; 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。 请你计算并返…

工业设备数据采集方案的设计实施与应用-天拓四方

随着工业4.0时代的来临&#xff0c;工业设备数据采集成为了提升生产效率、优化能源管理、实现智能化决策的关键环节。本文将围绕工业设备数据采集的重要性、方案设计实施以及TDE工业网关的应用实践展开探讨&#xff0c;以期为相关企业提供一种清晰、专业且实操性强的数据采集方…