C++奇迹之旅:深入理解赋值运算符重载

news2024/11/20 20:19:27

请添加图片描述

文章目录

  • 📝赋值运算符重载
  • 🌠 运算符重载
    • 🌉特性
  • 🌠 赋值运算符重载
  • 🌠传值返回:
  • 🌠传引用赋值:
    • 🌉两种返回选择
    • 🌉赋值运算符只能重载成类的成员函数不能重载成全局函数
  • 🚩总结


📝赋值运算符重载

🌠 运算符重载

运算符重载是C++中的一个重要特性,他允许我们为自定义的类型定义自己的运算符行为。通过运算符重载,我们可以使用与内置数据类型相同的语法来操作自定义类型,从而提高代码的可读性和可维护性

还是我们熟悉的日期函数:

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	// 给缺省值
	int _year = 1;
	int _month = 1;
	int _day = 1;
};

然后我们定义两个日期对象d1和d2:

int main()
{
	Date d1(2024, 2, 17);
	Date d2(2024, 6, 27);
	
	return 0;
}

当你想要比较两个对象d1和d2的数据是否一样,这是通常的比较方法:
创建一个专门的比较函数来比较两个Date对象是否相同。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	bool isSame(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 2, 17);
	Date d2(2024, 6, 27);
	
	if (d1.isSame(d2))
	{
		cout << "d1 and d2 are the same date" << endl;
	}
	else
	{
		cout << "d1 and d2 are different dates" << endl;
	}

	return 0;
}

很明显的可以看出这是个比较函数,能不能直接通过像内置类型那样d1==d2来比较相同呀,因此运算符重载就来了:
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通函数类似

函数名字为:关键字operator后面接需要重载的运算符号。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	bool isSame(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1,const Date& d2)
{
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2024, 2, 17);
	Date d2(2024, 6, 27);
	
	//d1.isSame(d2)
	if (d1 == d2)
	{
		cout << "d1 and d2 are the same date" << endl;
	}
	else
	{
		cout << "d1 and d2 are different dates" << endl;
	}

	return 0;
}

这样使用运算符重载是不是直接可以使用d1==d2,方便了,但是这里有个注意点:此时bool operator==(const Date& d1,const Date& d2)这个函数我是写在全局变量中,因此private也去掉,才能够访问。

这样一来,安全性降低了,可读性升高了,有点得不偿失,运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
在这里插入图片描述
补充:两种调用方式直接写和显示调用是一样的:

int main()
{
	Date d1(2024, 2, 17);
	Date d2(2024, 6, 27);

	// 显式调用
	operator==(d1, d2);
	
	// 直接写,装换调用,编译会转换成operator==(d1, d2);
	d1 == d2;

	return 0;
}

两者的call指令是一样的:
在这里插入图片描述
bool operator==(const Date& d1, const Date& d2) 重载成全局,无法访问私有成员,解决办法有三种:

  1. 使用 getter 和 setter 函数的方案:
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    int GetYear() { return _year; }
    int GetMonth() { return _month; }
    int GetDay() { return _day; }

    void SetYear(int year) { _year = year; }
    void SetMonth(int month) { _month = month; }
    void SetDay(int day) { _day = day; }

    bool operator==(const Date& d) const
    {
        return _year == d._year && _month == d._month && _day == d._day;
    }

private:
    int _year;
    int _month;
    int _day;
};
  1. 使用友元函数的方案:
    • 这种方式可以直接访问私有成员变量,不需要额外的 getter 和 setter 函数。
    • 但是,将 operator== 函数声明为友元函数会破坏类的封装性,需要谨慎使用。
class Date
{
    friend bool operator==(const Date& d1, const Date& d2);

public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }

private:
    int _year;
    int _month;
    int _day;
};

bool operator==(const Date& d1, const Date& d2)
{
    return d1._year == d2._year 
   		&& d1._month == d2._month 
    	&& d1._day == d2._day;
}
  1. 重载为成员函数的方案:
    • 这是最常见和推荐的方式。
class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    // bool operator==(Date* this, const Date& d2)
    // 这里需要注意的是,左操作数是this,指向调用函数的对象
    bool operator==(const Date& d) 
    {
        return _year == d._year 
        	&& _month == d._month 
        	&& _day == d._day;
    }

private:
    int _year;
    int _month;
    int _day;
};

这里我们本节主要学习第三种重载为成员函数的方案:bool operator==(const Date& d) 这里需要注意的是,左操作数是this,指向调用函数的对象。
这里的参数使用const修饰,确保传入的原对象不被修改

函数的调用方法:

int main()
{
	Date d1(2024, 2, 17);
	Date d2(2024, 6, 27);
	
	//显示调用
	d1.operator==(d2);

	//转换调用,等价与d1.operator==(d2);
	d1 == d2;

	
	cout << d1.operator==(d2) << endl;
	cout << (d1 == d2) << endl;
	//注意这里的()括号,因为符号优先级问题
	return 0;
}

在这里插入图片描述

对于自定义对象我们可以使用运算符,返回值是根据运算符来决定,加减返回int类型,判断大小,使用bool类型:一个类要重载哪些运算符是看需求,看重载有没有价值和意义

🌉特性

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有一个类类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
    藏的this
  5. .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出
    现。
    这里主要注意的是.*,看看这个代码:
class OB
{
public:
	void func()
	{
		cout << "void func" << endl;
	}
};

typedef void(OB::*ptrFunc)();//成员函数指针类型

int main()
{
	//函数指针
	//void(*ptr)();

	//成员函数要加&才能取到函数指针
	ptrFunc fp = &OB::func;//定义函数指针fp指向func

	OB temp;//定义OB类对象temp

	(temp.*fp)();

	return 0;
}

首先这是普通函数指针的定义:

//函数指针
void(*ptr)();

其次是成员函数指针类型:

typedef void(OB::*ptrFunc)();//成员函数指针类型

在这个代码中,typedef void(OB::*ptrFunc)() 定义了一个新的类型 ptrFunc,它是一个指向 OB 类的成员函数的指针类型。

  1. void(OB::*)()

    • 这是一个函数指针类型,它指向一个返回值为 void 且没有参数的成员函数。
    • OB::* 表示这个函数指针是指向 OB 类的成员函数。
  2. typedef void(OB::*ptrFunc)();

    • 使用 typedef 关键字定义了一个新的类型 ptrFunc
    • ptrFunc 就是这个指向 OB 类成员函数的指针类型的别名。

这样定义之后,我们就可以使用 ptrFunc 这个类型来声明指向 OB 类成员函数的指针变量了。

//成员函数要加&才能取到函数指针
	ptrFunc fp = &OB::func;//定义函数指针fp指向func

main() 函数中,我们使用 &OB::func 获取了 OB 类的 func() 成员函数的地址,并将其赋值给 ptrFunc 类型的变量 fp

	OB temp;//定义OB类对象temp
	(temp.*fp)();

然后,我们创建了一个 OB 类的对象 temp。最后,使用 (temp.*fp)(); 语法调用了 temp 对象的 func() 成员函数。这里的 .* 运算符用于通过成员函数指针调用成员函数。

🌠 赋值运算符重载

在这里插入图片描述

上节我们学了拷贝构造来进行数据的复制:一个已经存在的对象,拷贝给另一个要创建初始化的对象

Date d1(2024, 4, 20);
// 拷贝构造
// 一个已经存在的对象,拷贝给另一个要创建初始化的对象
Date d2(d1);
Date d3 = d1;

当然那还有赋值拷贝/赋值运算符重载也可以进行复制:
一个已经存在的对象,拷贝赋值给另一个已经存在的对象

Date d1(2024, 4, 20);
d1 = d4;

在这里插入图片描述

这里是单个赋值,能不能连续像内置类型那样赋值?
	int i, j, k;
	i = j = k = 1;

连续赋值的本质是:从右向左开始,1赋值给k,k=1表达式返回值为左操作数k,接着j赋值给k,j=k表达式返回值为左操作数,再接着i就拿到了1,连续赋值完毕。

同理,自定义类型也是一样的,有两种方式传值和引用:

🌠传值返回:

Date operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

由于每次返回的都是左操作数,我们需要this,但是这是传值返回,返回的是this的临时拷贝,不是这个函数里局部变量*this

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	// d1 = d2 = d4;
	// ->d2 = d4
	// ->d1 = d2;
	Date operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2024, 4, 14);

	// 拷贝构造
	// 一个已经存在的对象,拷贝给另一个要创建初始化的对象
	Date d2(d1);
	Date d3 = d1;

	Date d4(2024, 5, 1);

	// 赋值拷贝/赋值重载
	// 一个已经存在的对象,拷贝赋值给另一个已经存在的对象
	d1 = d4;

	d1 = d2 = d4;

	return 0;
}

当从右向左开始,d4赋值给d2d2=d4表达式返回值为左操作数d2,这里的d2*this,但是传值返回,会生成一个临时的拷贝,返回的是Date *this,此时此刻,如果你一步一步调试,他会跳转到Date的构造函数里Date(const Date& d),然后刷新private的值,然后进行第二步的连续赋值d2赋值给d1,和上面的步骤是一样的。

🌠传引用赋值:

Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

这里按引用传递,无需再创造临时变量拷贝,再调用拷贝构造函数,当从右向左开始,d4赋值给d2d2=d4表达式返回值为左操作数d2,这里的d2*this,*this直接返回的是左操作数的d2的别名。

这里还需注意的一点是,我们可能会出现写错,避免不必要的操作:比如自赋值

d1=d1

处理自赋值问题主要有以下几个原因:

  1. 如果不检查自赋值的情况,当执行d1 = d1时,会进行不必要的赋值操作。这可能会造成性能的浪费,尤其是对于大型对象而言。
  2. 某些情况下,对象的成员变量可能依赖于其他成员变量的值。如果在赋值过程中,这些成员变量的值被覆盖,可能会导致对象处于不一致的状态,从而引发错误。

修改自赋值问题:使用地址来判断

Date& operator=(const Date& d)
{
	if (this != &d)//使用的是地址判断
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	return *this;
}

因此我们总结一下赋值运算符重载格式

  1. 参数类型:const T&,传递引用可以提高传参效率
  2. 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  3. 检测是否自己给自己赋值
  4. 返回*this :要复合连续赋值的含义

🌉两种返回选择

  1. 传值返回
Date func()
{
	Date d(2024, 4, 14);
	return d;
}

int main()
{
	const Date& ref = func();

	return 0;
}

选择传值,还是传引用呢?

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;

		_year = d._year;
		_month = d._month;
		_day = d._day;
	}


	// d1 = d2 = d4;
	// d2 = d4
	// d1 = d2
	// Date operator=(const Date& d)
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}

		return *this;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	~Date()
	{
		cout << "~Date()" << endl;
		_year = -1;
		_month = -1;
		_day = -1;
	}

private:
	int _year;
	int _month;
	int _day;
};

Date func()
{
	Date d(2024, 4, 14);
	return d;
}

int main()
{
	const Date ref = func();

	return 0;
}

在这里插入图片描述

  1. 传引用返回
Date& func()
{
	Date d(2024, 4, 14);
	return d;
}

int main()
{
	const Date& ref = func();

	return 0;
}

在这里插入图片描述

总结一下:返回对象是一个局部对象或者临时对象,出了当前func函数作用域,就析构销毁了,那么不能用引用返回用引用返回是存在风险的,因为引用对象在func函数栈帧已经销毁了
虽然引用返回可以减少一次拷贝,但是出了函数作用,返回对象还在,才能用引用返回

理解了func函数,那么operator=重载赋值函数返回选择哪种方式也是同样的方法:
*thisd2,在main函数传参的时候,this指针是存放栈空间的,当operator函数生命周期结束时,*this回到的是回到的是main函数的,也就是*this离开operator时生命周期未到,不会析构,因此按引用返回。
在这里插入图片描述

🌉赋值运算符只能重载成类的成员函数不能重载成全局函数

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}

在这里插入图片描述

编译失败:
error C2801: “operator =”必须是非静态成员

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。
在这里插入图片描述
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

直接无法加载发生异常!
在这里插入图片描述
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
在这里插入图片描述


🚩总结

请添加图片描述

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

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

相关文章

【YOLOv8改进[Backbone]】使用MobileNetV3助力YOLOv8网络结构轻量化并助力涨点

目录 一 MobileNetV3 1 面向块搜索的平台感知NAS和NetAdapt 2 反向残差和线性瓶颈 二 使用MobileNetV3助力YOLOv8 1 整体修改 ① 添加MobileNetV3.py文件 ② 修改ultralytics/nn/tasks.py文件 ③ 修改ultralytics/utils/torch_utils.py文件 2 配置文件 3 训练 其他 …

在瑞芯微RV1126 Linux系统上调试WiFi的详细指南

目录标题 1. **系统和环境准备**2. **检查WiFi设备状态**3. **启用和禁用WiFi接口**4. **扫描可用的WiFi网络**5. **连接到WiFi网络**6. **查看当前的WiFi连接状态**7. **断开和重新连接WiFi**8. **管理WiFi网络配置**9. **使用iw工具进行高级WiFi调试**10. **故障排除和日志获…

C#基础|Debug程序调试学习和技巧总结

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 在程序的开发过程中&#xff0c;可能绝大部分时间是用来调试程序&#xff0c; 当完成了某个功能的编程&#xff0c;都需要调试一下程序&#xff0c;看编程是否存在问题。 01 为什么需要程序调试 无论是电气工程师还…

代码随想录阅读笔记-回溯【全排列 II】

题目 给定一个可包含重复数字的序列 nums &#xff0c;按任意顺序 返回所有不重复的全排列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,2]输出&#xff1a; [[1,1,2], [1,2,1], [2,1,1]] 示例 2&#xff1a; 输入&#xff1a;nums [1,2,3]输出&#xff1a;[[1,2,3],[1,…

七牛云配置,图片上传、查看的使用(备忘)

修改配置文档 修改新创建的空间的地区名 访问设置为 公开&#xff0c;不然会有访问时间限制 检查 上传和查看的链接是否正确。

centos linux 7.9安装php8.2.18不支持mysqli模块,如何解决?

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

钉钉对接T+生成总账凭证

客户介绍&#xff1a; 某餐饮连锁企业是一个专注于特色风味徽州菜的餐饮品牌&#xff0c;总部位于杭州市&#xff0c;其推出的各式特色徽菜深受市场的好评&#xff0c;在杭州本地的餐饮市场中有着很强的竞争力。公司ERP使用用友T系统&#xff0c;通过钉钉管理员工费用报销流程…

【精简改造版】大型多人在线游戏BrowserQuest服务器Golang框架解析(1)——功能清单

1.匿名登录 2.服务连接 3.新手引导 4.随机出生点 5.界面布局 6.玩法帮助 7.NPC会话 8.成就系统 9.成就达成 10.用户聊天 11.战斗&信息展示 12.药水使用 13.副本传送 14.玩家死亡 15.超时断开

【大语言模型+Lora微调】10条对话微调Qwen-7B-Chat并进行推理 (聊天助手)

代码&#xff1a;https://github.com/QwenLM/Qwen/tree/main 国内源安装说明&#xff1a;https://modelscope.cn/models/qwen/Qwen-7B-Chat/summary 通义千问&#xff1a;https://tongyi.aliyun.com/qianwen 一、环境搭建 下载源码 git clone https://github.com/QwenLM/Qwen…

浏览器渲染流程中的 9 个面试点

记得 08 年以前&#xff0c;打开网页的时候一个页面卡死整个浏览器凉凉。 这是因为当时浏览器是单进程架构&#xff0c;一个页面或者插件卡死&#xff0c;整个浏览器都会崩溃&#xff0c;非常影响用户体验。 经过了一代代工程师的设计&#xff0c;现代浏览器改成了多进程架构&…

C++:继承作业题

1. 关于以下菱形继承说法不正确的是&#xff08; &#xff09; &#xfeff;class B {public: int b;};class C1: public B {public: int c1;};class C2: public B {public: int c2;};class D : public C1, public C2 {public: int d;};A.D总共占了20个字节B.B中的内容总共在D…

2024物理学、电子电路与通信工程国际学术会议(ICPECCE2024)

2024物理学、电子电路与通信工程国际学术会议(ICPECCE2024) 会议简介 2024国际物理、电子电路与通信工程学术会议&#xff08;ICPECCE2024&#xff09;将在深圳隆重举行。本次会议旨在汇聚全球物理、电子电路、通信工程等领域的专家学者&#xff0c;共同探讨最新研究成果和…

《从零开始的Java世界》05异常处理

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

洛谷 P1131 [ZJOI2007] 时态同步

思路&#xff1a;树形DP 这道题总的来说有点贪心的味道&#xff0c;贪心在我们需要把这个时间点加到哪一条边上。 借用一下一位洛谷大佬的图&#xff1a; 其实这样看出来&#xff0c;如果说越靠近根的那条边加长&#xff0c;其实价值最小&#xff0c;所以我们需要尽量向靠近根…

AbstractQueuedSynchronizer 源码解析

AbstractQueuedSynchronizer 源码解析 文章目录 AbstractQueuedSynchronizer 源码解析一、CAS二、字段分析三、内部类 Node1、CLH 队列2、源码分析 四、内部类 ConditionObject1、字段分析2、方法分析1、await2、signal 五、方法分析1、独占式下的 AQS1、acquire 独占式获取资源…

14.基础乐理-音级、基本音级、变化音级

音级&#xff1a; 乐音体系中的每一个音&#xff0c;都叫 音级。 基本音级&#xff1a; 基本音级是 CDEFGAB 它们七个&#xff0c;在钢琴上使用白键展示的&#xff0c;没有任何升降号、没有任何重升重降号的。 变化音级&#xff1a; 除了 CDEFGAB 这七个音&#xff0c;都叫变化…

面向对象练习坦克大兵游戏

游戏玩家&#xff08;名称&#xff0c;生命值&#xff0c;等级&#xff09;&#xff0c;坦克&#xff0c;大兵类&#xff0c;玩家之间可以相互攻击&#xff0c;大兵拥有武器&#xff0c;用枪弹和反坦克炮弹&#xff0c;造成攻击不同&#xff0c;坦克攻击值固定&#xff0c;请设…

logisim 图解超前进位加法器原理解释

鄙人是视频作者&#xff0c;文件在视频简介的网盘链接。 找规律图解超前进位加法器与原理解释_哔哩哔哩_bilibili 一句话就是“把能导致进位到这个位置的情况全都穷举一遍。” 穷举情况看图中算式。 视频讲解比较啰嗦。

JavaFX--基础简介(1)

一、介绍 中文官网&#xff1a;JavaFX中文官方网站OpenJFX 是一个开源项目,用于桌面、移动端和嵌入式系统的下一代客户端应用程序平台。openjfx.cn是OpenJFX(JavaFX)的标准中文翻译网站&#xff0c;致力于方便开发者阅读官方文档和教程。https://openjfx.cn/ JavaFX 是一个开…

【一文配置好Python开发环境】Python创建虚拟环境,一键更换国内镜像源

一、使用Python自带的venv创建虚拟环境 首先&#xff0c;确保你的Python安装中包含了venv模块。你可以在命令行中运行以下命令来检查&#xff1a; python -m venv --help进入代码目录&#xff0c;创建一个新的虚拟环境。在命令行中运行以下命令&#xff1a; python -m venv …