C++类的运算符重载.md

news2025/7/12 7:34:21

11.8 类的运算符重载

运算符重载是一种形式的C++多态

运算符重载将充值该的概念扩展到运算符上,允许赋予C++运算符多种含义。

C++允许将运算符重载扩展到用户定义类型,例如,允许使用+将两个对象相加。

11.8.1 操作符重载定义

要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:

operator op(argument-list)

例如,operator+()重载+运算符,operator*()重载*运算符。op必须是有效的C++运算符,不能虚构一个新的符号。例如,不能有operator@()这样的函数,因为C++中没有@运算符。然而,operatoe函数将重载[]运算符,因为[]是数组索引运算符。

11.8.2 调用重载的操作符

11.8.2.1 使用重载的操作符(常用)

假设有一个Salesperson类,并为它定义了一个operator+()成员函数,以重载+运算符,以便能够将两个Salesperson对象的销售额相加,如果district2, sid, and sara都是Salesperson类的对象,便可以编写这样的等式。

district2 = sid + sara;

编译器发现,操作数是Salesperson类对象,因此使用相应的运算符函数替换上述运算符。

district2 = sid.operator+(sara);

该函数将隐式地调用sid(因为它调用了方法),而显式地使用sara对象(因为他被作为参数传递)

11.8.2.2 使用operatorop()函数名

//比如这个加法
total = morefixing.operator+(total);

11.8.2.3 是否可以实现连加(or 连减、连乘…)

t4 = t1 + t2 + t3; // valid?
t4 = t1.operator+(t2 + t3); // valid?
t4 = t1.operator+(t2.operator+(t3)); // valid? YES

11.8.3 重载限制

重载的运算符(有些例外情况)不必是成员函数,但必须至少有一个操作数是用户定义的类型,以下是函数重载地限制:

1.重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。因此,不能将减法运算符(-)重载为计算两个double值的和,而是他们的差。虽然这种限制会对创造性有所影响,但可以确保程序正常运行。

2.使用运算符时不能违反运算符原来的句法规则。例如,不能将求模运算符(%)重载成为一个操作数。

int x;
Time shiva;
% x; // invalid for modulus operator
% shiva; // invalid for overloaded operator

3.不能修改运算符的优先级。因此,如果将加号运算符 重载为两个类相加,则新的运算符与原来的加号具有相同的优先级。

4.不能创建新运算符。例如,不能定义operator**()函数来表示求幂。

5.不能重载以下运算符:

在这里插入图片描述

6.表11.1中的大多数运算符都可以通过成员或非成员函数进行重载,但下面的运算符只能通过成员函数进行重载:

在这里插入图片描述

7.本章不介绍这里列出的所有运算符,但附录E对本书正文中没有介绍的运算符进行了总结。

11.8.4 举例

11.8.4.1 源代码

#pragma once
// mytime0.h -- Time class before operator overloading
#ifndef MYTIME0_H_
#define MYTIME0_H_
class Time
{
private:
	int hours;
	int minutes;
public:
	Time();
	Time(int h, int m = 0);
	void AddMin(int m);
	void AddHr(int h);
	void Reset(int h = 0, int m = 0);
	Time operator+(const Time& t) const;
	Time operator-(const Time& t) const;
	Time operator*(double n) const;
	void Show() const;
};
#endif
// mytime0.cpp -- implementing Time methods
#include <iostream>
#include "mytime0.h"
Time::Time()
{
	hours = minutes = 0;
}
Time::Time(int h, int m)
{
	hours = h;
	minutes = m;
}
void Time::AddMin(int m)
{
	minutes += m;
	hours += minutes / 60;
	minutes %= 60;
}
void Time::AddHr(int h)
{
	hours += h;
}
void Time::Reset(int h, int m)
{
	hours = h;
	minutes = m;
}
Time Time::operator+(const Time& t) const
{
	Time sum;
	sum.minutes = minutes + t.minutes;
	sum.hours = hours + t.hours + sum.minutes / 60;
	sum.minutes %= 60;
	return sum;
}
Time Time::operator-(const Time& t) const
{
	Time diff;
	int tot1, tot2;
	tot1 = t.minutes + 60 * t.hours;
	tot2 = minutes + 60 * hours;
	diff.minutes = (tot2 - tot1) % 60;
	diff.hours = (tot2 - tot1) / 60;
	return diff;
}
Time Time::operator*(double mult) const
{
	Time result;
	long totalminutes = hours * mult * 60 + minutes * mult;
	result.hours = totalminutes / 60;
	result.minutes = totalminutes % 60;
	return result;
}
void Time::Show() const
{
	std::cout << hours << " hours, " << minutes << " minutes";
}
// usetime0.cpp -- using the first draft of the Time class
// compile usetime0.cpp and mytime0.cpp together
#include <iostream>
#include "mytime0.h"

int main()
{
	using std::cout;
	using std::endl;
	Time weeding(4, 35);
	Time waxing(2, 47);
	Time total;
	Time diff;
	Time adjusted;
	cout << "weeding time = ";
	weeding.Show();
	cout << endl;
	cout << "waxing time = ";
	waxing.Show();
	cout << endl;
	cout << "total work time = ";
	total = weeding + waxing; // use operator+()
	total.Show();
	cout << endl;
	diff = weeding - waxing; // use operator-()
	cout << "weeding time - waxing time = ";
	diff.Show();
	cout << endl;
	adjusted = total * 1.5; // use operator*()
	cout << "adjusted work time = ";
	adjusted.Show();
	cout << endl;
	return 0;
}

11.8.4.2 运行结果

weeding time = 4 hours, 35 minutes
waxing time = 2 hours, 47 minutes
total work time = 7 hours, 22 minutes
weeding time - waxing time = 1 hours, 48 minutes
adjusted work time = 11 hours, 3 minutes

D:\Prj\C++\Function_OverLoading\Debug\Function_OverLoading.exe (进程 3524)已退出,代码为 0。
要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
按任意键关闭此窗口. . .

11.8.4.3 注意事项

不要返回指向局部变量或临时对象的引用。函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据。

11.8.5 重载运算符: 成员函数重载VS非成员函数重载

大多数运算符都可以通过成员或非成员函数来重载,非成员函数是友元函数,因此它可以直接访问对象的所有私有数据。

Time operator+(const Time & t) const; // member version
// nonmember version
friend Time operator+(const Time & t1, const Time & t2);

加法运算符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式地传递,另一个操作数作为函数参数显式地传递;对于友元版本来说,两个操作数都为参数来传递。

T1 = T2 + T3;
T1 = T2.operator+(T3); // member function
T1 = operator+(T2, T3); // nonmember function

记住,在定义运算符时,必须选择其中的一种格式,而不能同时选择这两种格式。因为这两种格式都与同一个表达式匹配,同时定义这两种格式将被视为二义性错误,导致编译错误。

那么哪种格式最好呢?对于某些运算符来说(如前所述),成员函数是唯一合法的选择。在其他情况下,这两种格式没有太大的区别。有时,根据类设计,使用非成员函数版本可能更好(尤其是为类定义类型转换时)

11.8.6 矢量类操作符重载

矢量(vector)是工程和物理中使用的一个术语,它是一个有大小和方向的量。

11.8.6.1 多种表示方式和类

可以用不同但等价的方式表示一个实体。

类非常适于在一个对象中表示实体的不同方面。首先在一个对象中存储多种表示方式;然后,编写这样的类函数,以便给一种表示方式赋值时,将自动给其他方式赋值。

11.8.6.2 为Vector类重载算数运算符

如果方法通过计算得到一个新的类对象,则应考虑是否可以使用类构造函数来完成这种工作。这样做不仅可以避免麻烦,而且可以确保新的对象时按照正确的方式创建的。

11.8.6.3 对已重载的运算符进行重载

在C++中,-运算符有两种含义;首先,使用两个操作数,它是减法运算符。减法运算是一个二元运算符,因为它有两个操作数。其次,使用一个操作数时(如-x),它是负号运算符。这种形式被称为一元运算符。即只有一个操作数。对于矢量来说,这两种操作都是有意义的,因此Vector类要有这两种操作。

因为运算符重载是通过函数来实现的,所以只要运算符函数的参数不同,使用的运算符数量与相应的内置C++运算符相同,就可以多次重载同一个运算符。

11.8.6.4 对实现的说明

Vector对象可以只存储x和y分量,而返回矢量长度的magval()方法可以根据x和y的值来计算出长度,而不是查找对象中存储的这个值。这种方法改变了实现,但用户接口不变。将接口与实现分离是OOP的目标之一,这样允许对实现进行调整,而无需修改使用这个类的程序中的代码。

一种实现是只存储直角坐标,另一种实现是存储直角坐标和极坐标。这两种实现各有利弊,存储数据意味着对象将占据更多的内存,每次Vector对象被修改时,都需要更新直角坐标和极坐标表示;但查找数据的速度比较快。如果应用程序经常需要访问矢量的这两种表示,则第二种实现比较合适;如果只是偶尔使用极坐标,则另一种更好。可以在一个程序中使用一种实现,而在另一个程序中使用另一种实现,但他们的用户接口相同。

11.8.6.5 一个实例–使用Vector类模拟随机漫步

将一个人领到一个街灯柱下,这个人开始走动,但每一步的方向都是随机的。提出的问题是:这个人走到离灯柱50英尺处需要多少步,从矢量的角度看,这相当于不断将方向随机的矢量相加,直到长度超过50英尺。

11.8.6.5.1 code
#pragma once
//添加命名空间是为了复习之前命名空间的内容
// vect.h -- Vector class with <<, mode state
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
	class Vector
	{
	public:
		enum Mode { RECT, POL };
		// RECT for rectangular, POL for Polar modes
	private:
		double x; // horizontal value
		double y; // vertical value
		double mag; // length of vector
		double ang; // direction of vector in degrees
		Mode mode; // RECT or POL---Such a member is termed a state member because it describes the state an object is in.
		// private methods for setting values
		void set_mag();
		void set_ang();
		void set_x();
		void set_y();
	public:
		Vector();
		Vector(double n1, double n2, Mode form = RECT);
		void reset(double n1, double n2, Mode form = RECT);
		~Vector();
		double xval() const { return x; } // report x value
		double yval() const { return y; } // report y value
		double magval() const { return mag; } // report magnitude
		double angval() const { return ang; } // report angle
		void polar_mode(); // set mode to POL
		void rect_mode(); // set mode to RECT
		// operator overloading
		Vector operator+(const Vector& b) const;
		Vector operator-(const Vector& b) const;
		Vector operator-() const;
		Vector operator*(double n) const;
		// friends
		friend Vector operator*(double n, const Vector& a);
		friend std::ostream& operator<<(std::ostream& os, const Vector& v);
	};
} // end namespace VECTOR
#endif
// vect.cpp -- methods for the Vector class
#include <cmath>
#include "vect.h" // includes <iostream>
using std::sqrt;
using std::sin;
using std::cos;
using std::atan;
using std::atan2;
using std::cout;
namespace VECTOR
{
	// compute degrees in one radian
	const double Rad_to_deg = 45.0 / atan(1.0);
	// should be about 57.2957795130823
	// private methods
	// calculates magnitude from x and y
	void Vector::set_mag()
	{
		mag = sqrt(x * x + y * y);
	}
	void Vector::set_ang()
	{
		if (x == 0.0 && y == 0.0)
			ang = 0.0;
		else
			ang = atan2(y, x);
	}
	// set x from polar coordinate
	void Vector::set_x()
	{
		x = mag * cos(ang);
	}
	// set y from polar coordinate
	void Vector::set_y()
	{
		y = mag * sin(ang);
	}
	// public methods
	Vector::Vector() // default constructor
	{
		x = y = mag = ang = 0.0;
		mode = RECT;
	}
	// construct vector from rectangular coordinates if form is r
	// (the default) or else from polar coordinates if form is p
	Vector::Vector(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Incorrect 3rd argument to Vector() -- ";
			cout << "vector set to 0\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}
	// reset vector from rectangular coordinates if form is
	// RECT (the default) or else from polar coordinates if
	// form is POL
	void Vector::reset(double n1, double n2, Mode form)
	{
		mode = form;
		if (form == RECT)
		{
			x = n1;
			y = n2;
			set_mag();
			set_ang();
		}
		else if (form == POL)
		{
			mag = n1;
			ang = n2 / Rad_to_deg;
			set_x();
			set_y();
		}
		else
		{
			cout << "Incorrect 3rd argument to Vector() -- ";
			cout << "vector set to 0\n";
			x = y = mag = ang = 0.0;
			mode = RECT;
		}
	}
	Vector::~Vector() // destructor
	{
	}
	void Vector::polar_mode() // set to polar mode
	{
		mode = POL;
	}
	void Vector::rect_mode() // set to rectangular mode
	{
		mode = RECT;
	}
	// operator overloading
	// add two Vectors
	Vector Vector::operator+(const Vector& b) const
	{
		return Vector(x + b.x, y + b.y);
	}
	// subtract Vector b from a
	Vector Vector::operator-(const Vector& b) const
	{
		return Vector(x - b.x, y - b.y);
	}
	// reverse sign of Vector
	Vector Vector::operator-() const
	{
		return Vector(-x, -y);
	}
	// multiply vector by n
	Vector Vector::operator*(double n) const
	{
		return Vector(n * x, n * y);
	}
	// friend methods
	// multiply n by Vector a
	Vector operator*(double n, const Vector& a)
	{
		return a * n;//这里就会调用a的成员函数*
	}
	// display rectangular coordinates if mode is RECT,
	// else display polar coordinates if mode is POL
	std::ostream& operator<<(std::ostream& os, const Vector& v)
	{
		if (v.mode == Vector::RECT)
			os << "(x,y) = (" << v.x << ", " << v.y << ")";
		else if (v.mode == Vector::POL)
		{
			os << "(m,a) = (" << v.mag << ", "
				<< v.ang * Rad_to_deg << ")";
		}
		else
			os << "Vector object mode is invalid";
		return os;
	}
} // end namespace VECTOR
// randwalk.cpp -- using the Vector class
// compile with the vect.cpp file
#include <iostream>
#include <cstdlib> // rand(), srand() prototypes
#include <ctime> // time() prototype
#include "vect.h"
int main()
{
	using namespace std;
	using VECTOR::Vector;
	srand(time(0)); // seed random-number generator
	double direction;
	Vector step;
	Vector result(0.0, 0.0);
	unsigned long steps = 0;
	double target;
	double dstep;
	cout << "Enter target distance (q to quit): ";
	while (cin >> target)
	{
		cout << "Enter step length: ";
		if (!(cin >> dstep))
			break;
		while (result.magval() < target)
		{
			direction = rand() % 360;//rand()产生随机数的范围为0~RAND_MAX,RAND_MAX值最小为32767,最大为2147483647
			step.reset(dstep, direction, Vector::POL);
			result = result + step;
			steps++;
		}
		cout << "After " << steps << " steps, the subject "
			"has the following location:\n";
		cout << result << endl;
		result.polar_mode();
		cout << " or\n" << result << endl;
		cout << "Average outward distance per step = "
			<< result.magval() / steps << endl;
		steps = 0;
		result.reset(0.0, 0.0);
		cout << "Enter target distance (q to quit): ";
	}
	cout << "Bye!\n";
	cin.clear();
	while (cin.get() != '\n')
		continue;
	return 0;
}

这个程序用一个变量来表示位置(一个矢量),并报告到达只当距离处(用两种格式表示)所需的步数。

11.8.6.5.2 程序笔记

标准ANSI C库(C++也有)中有一个rand()函数,它返回一个从0到某个值之间的随机整数。上述程序使用求模操作数来获得一个0~359的角度值。rand()函数将一种算法用于一个初始种子值来获得随机数,该随机数将用作下一次函数调用的种子。这些数实际上是伪随机数,因为10次连续的调用通常将生成10个同样的随机数。然而,srand()函数允许覆盖默认的种子值,重新启动从某一个随机数序列,该程序使用time(0)的返回值来设置种子。time(0)函数返回当前时间,通常为从某一个日期开始的秒数(更广义的,time()接受time_t变量的地址,将时间放到该变量中,并返回它。将0用作地址参数,可以省略time_t变量声明)。因此,下面的语句在每次运行程序时,都将设置不同的种子,使随机输出看上去更为随机。

srand(time(0));

头文件cstdlib(以前为stdlib.h)包含了srand()和rand()的原型,而ctime(以前是time.h)包含了time()的原型。C++11使用头文件random中的函数提供了更强大的随机数支持。

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

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

相关文章

FutureTask-详解(二)-ThreadPollExecutor-并发编程(Java)

文章目录1 FutureTask1.1 简介1.2 成员变量1.3 构造方法1.4 主要执行流程分析1.4.1 run任务执行1.4.1.1 run方法1.4.1.2 set(result)方法1.4.1.3 setException(ex)方法1.4.1.4 finishCompletion()方法1.4.2 get() 执行流程1.4.2.1 get()方法1.4.2.2 awaitDone()方法1.4.2.3 rem…

C++获取计算机硬件信息(Linux)

C获取计算机硬件信息&#xff08;Windows&#xff09; https://blog.csdn.net/hhy321/article/details/121258036 C获取计算机硬件信息&#xff08;Linux&#xff09; https://blog.csdn.net/hhy321/article/details/127930470 “春赏百花秋望月&#xff0c;夏有凉风冬观雪。若…

InnoDB之Undo log写入和恢复

1. 前言 为了实现事务的回滚和MVCC&#xff0c;InnoDB设计了Undo log模块&#xff0c;简单来说就是在修改记录前先记下日志&#xff0c;以便之后能将记录恢复成修改前的样子。针对insert、delete、update这三种不同的操作&#xff0c;InnoDB设计了不同类型的undo log&#xff…

Redis 常见问题

一、 什么是Redis&#xff1f; Redis 是一个使用 C 语言写成的&#xff0c;开源的高性能key-value非关系缓存数据库&#xff1b;Redis的数据都基于缓存的&#xff0c;所以很快&#xff0c;每秒可以处理超过 10万次读写操作&#xff1b;Redis也可以实现数据写入磁盘中&#xff…

Spring源码里开天辟地的五个Bean,再介绍一个学习方法

准备工作 首先咱们还是来写一个最简单的例子&#xff1a; 用的还是 https://github.com/xiexiaojing/yuna 里的代码&#xff0c;只是标签和引用都换成了Spring原生的。 配置类就是配置了扫描路径&#xff1a; 在可以被扫描的包路径下定义了一个Bean对象。用了Component注解这…

Java#16(包装类和集合练习)

目录 基本数据类型对应的包装类: 一.添加学生对象并遍历 二.添加用户对象并判断是否存在 三.添加手机对象并返回要求的数据 基本数据类型对应的包装类: byte------>Byte short------->Short char------->Character int------->Integer long------->Long flo…

MRT(MODIS Reprojection Tool) 下载及安装教程

大家下载MODIS数据的时候&#xff0c;大多是hdf的格式数据。HDF数据包括11个波段的数据&#xff08;如下图&#xff09;&#xff0c;假如想要其中一个波段数据&#xff0c;我们需要批量提取&#xff0c;这时就要用到NASA提供的MODIS Reprojection Tool&#xff0c;此工具虽不能…

c++ 指针,new运算符

1、指针相关基础知识&#xff1a; 1.1、指针一般指向变量的地址&#xff0c;等同于变量名前加&&#xff0c;如下&#xff1a; int a 3; int* p; p &a; 1.2、 * 符号被称为间接值运算符或解除引用运算符&#xff0c;将其运用于指针&#xff0c;可以获取指针指向的值…

【Java八股文总结】之IO流

文章目录Java IO流一、IO基础知识1、字节流2、字符流3、字节缓冲流4、打印流5、随机访问流6、字节流和字符流的区别&#xff1f;二、IO设计模式1、装饰器模式2、适配器模式Q&#xff1a;适配器模式和装饰器模式的区别&#xff1f;3、工厂模式4、观察者模式三、IO模型详解&#…

柯桥留学日语培训机构有吗日本人平时都喝什么酒?

日本葡萄酒侍酒大师高野丰先生&#xff0c;带来一瓶在北海道发现的陈年白兰地。 那是2014年的事&#xff0c;高野先生去北海道十胜地区的一家葡萄酒厂考察&#xff0c;在一个多年未开启的葡萄酒储存库的角落里&#xff0c;发现了一只陈旧的酒桶&#xff0c;他很好奇地问酒厂的…

Javaweb filter过滤器 跟 listener监听器

Filter 权限控制&#xff1a;登录才能进数据库等 统一编码&#xff1a;统一各种编码 Filter快速入门 放行前&#xff0c;我们对request里的数据进行处理 处理完&#xff0c;然后放行&#xff0c;携带到对应的资源里去 放行后&#xff0c;对response的数据进行处理 //将re…

C/C++问题:一个指针的大小是多少?怎么理解指针变量的存在

目录 举例 一、 int a; int (*(*v)[])(); 二、 int func(); int (*func)(); 三、 const; int const a; const int a; int const*r; int *const r; 四、一个指针的大小是多少&#xff1f;&#xff08;补充&#xff09; 举例 一、 int a; 可以看到上面声明了一个变…

【MySQL基础】SQL语言的概述、组成及特点

目录 一、SQL的概述 二、SQL语言的组成 三、SQL语言的特点 语法特点&#xff1a; &#x1f49f; 创作不易&#xff0c;不妨点赞&#x1f49a;评论❤️收藏&#x1f499;一下 一、SQL的概述 SQL全称&#xff1a; Structured Query Language&#xff0c;是结构化查询语言&am…

数据结构先序序列创建二叉树

2022.11.19连发两回。 先序序列创建二叉树任务描述相关知识编程要求测试说明C/C代码任务描述 本关任务&#xff1a;利用先序遍历创建二叉树&#xff0c;并给出相应二叉树的中序遍历结果。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;1.二叉树的前序遍历&a…

win11系统下,迅雷启动后闪退的问题

win11系统&#xff0c;迅雷最新版本。 之前用迅雷一直好好的&#xff0c;某天要下东西突然发现点击就闪退&#xff0c;进程管理器里一点痕迹都没有。 启动就闪退&#xff0c;没有一点点痕迹。 因为之前是win10系统&#xff0c;一直用的好好的&#xff0c;考虑是不是win11系统…

创建自己数据集全套流程

目录 1、准备自己具有的数据集 2、标注数据----json格式 3、标注数据转为分割图----voc格式 4、增广数据集 5、分训练集以及验证集 1、准备自己具有的数据集 注意&#xff1a;数据集必须是统一的后缀格式&#xff0c;jpg或者png 2、标注数据----json格式 采用labelme标注…

前后端分离项目(vue+springboot)集成pageoffice实现在线编辑office文件

前后端分离项目下使用PageOffice原理图 集成步骤 前端 vue 项目 在您Vue项目的根目录下index.html中引用后端项目根目录下pageoffice.js文件。例如&#xff1a; <script type"text/javascript" src"http://localhost:8081/samples-springboot-back/pageof…

gom传奇引擎无限蜂功能插件安装图文教程

无限蜂功能插件安装教程 1. 百度搜索无限蜂官网,下载功能插件程序&#xff0c;请下载最新版&#xff01;2. 下载无限蜂功能插件&#xff0c;到桌面&#xff0c;并进行解压&#xff0c;如下图所示。 3. 打开无限蜂功能插件控制台(?.?.?)&#xff0c;进行账号注册。 4. 注册成…

InnoDB之Undo log格式

1. 前言 InnoDB有两大日志模块&#xff0c;分别是redo log和undo log。为了避免磁盘随机写&#xff0c;InnoDB设计了redo log&#xff0c;数据写入时只写缓冲页和redo log&#xff0c;脏页由后台线程异步刷盘&#xff0c;哪怕系统崩溃也能根据redo log恢复数据。但是我们漏了一…

STC51单片机32——液晶1602显示

//用LCD循环右移显示字符 //开发板上的跳帽连接Vcc #include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 sbit EP2^7; //使能信号位&#xff0c;将E位定义为P2.7引脚 sbit RSP2^6; //寄存器选择位&#…