从零开始c++精讲:第二篇——类和对象

文章目录

  • 一、类的定义
  • 二、类的访问限定符及封装
  • 三、类的作用域
  • 四、类的实例化
  • 五、类对象模型
    • 5.1计算对象的大小
    • 5.2结构体内存对齐规则
  • 六、this指针
    • 6.1简介
    • 6.2 this指针的特性
  • 七、类的6个默认函数
    • 7.1构造函数
    • 7.2析构函数
    • 7.3拷贝构造函数
    • 7.4赋值运算符重载
      • 7.4.1运算符重载
      • 7.4.2赋值运算符重载
  • 八、const成员
  • 九、取地址及const取地址操作符重载
  • 九、知识实战:日期类简单实现
  • 十、构造函数补充
    • 10.1构造函数体赋值
    • 10.2初始化列表
    • 10.3explicit关键字
  • 十一、static成员
  • 十二、友元
  • 十三、内部类
  • 十四、拷贝对象时的一些编译器优化


一、类的定义

class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号

class为定义类的关键字,ClassName为类的名字{}中为类的主体,注意类定义结束时后面分号不能省略
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者
成员函数。

类的两种定义方式

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内
    联函数处理。
    在这里插入图片描述
  2. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
    在这里插入图片描述
    一般情况下,更期望采用第二种方式。

二、类的访问限定符及封装

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选
择性的将其接口提供给外部的用户使用

在这里插入图片描述

【面试题】
问题:C++中struct和class的区别是什么?
答:其他方式也可以的,主要看公司要求。一般都是加个前缀或者后缀标识区分就行。比特就业课
解答:C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来
定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类
默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别,后序给大
家介绍。

我这里写一个简单的栈的对象,代码不完全,只是做教学使用
我们要定义一个栈对象,可以在对象名Stack前面加一个class,你也可以在Stack前面加一个struct

但是要注意,struct默认成员都是公有的public。但是class则默认成员都是私有的

如果采用了class,而且你不对成员的权限加说明,你是都访问不了成员的,如下图:
在这里插入图片描述

我们这里再加一下权限,就可以访问public的成员了,但是private的成员依然不可以访问

在这里插入图片描述

三、类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。

class Person
{
	public:
		void PrintPersonInfo();
	private:
		char _name[20];
		char _gender[3];
		int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " "<< _gender << " " << _age << endl;
}

像上面这种void Person::PrintPersonInfo(),一般是长函数声明和定义分离
分离后前面要指明是哪个类域

如果是比较短的函数,你可以直接定义在类里面,这种函数默认是inline的

四、类的实例化

用类类型创建对象的过程,称为类的实例化

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
    有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个
    类,来描述具体学生信息。

  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

class Date {
	public:
		void Init(int year, int month, int day)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year;//这里默认前面加个下划线,方便函数里面使用的(不是硬性要求,但建议这样做)
		int _month;
		int _day;
};

int main()
{
	//类和对象的关系是一对多
	//举个例子:狗——边牧
	//狗是一个类,边牧是一个对象

	Date d;//Date对象的实例化,定义出一个实体d
	d.Init(2024, 1, 7);

	cout << sizeof(d) << endl;//打印12,即3个int型成员变量year、month、day的大小
	//成员函数的大小是不计算的
	//不同对象调用类的函数,这个函数地址都是一样的,即不同对象调用的类函数是同一个函数
	
	//需要注意的是,不同对象的成员变量是不一样的,这点和类函数是不同的
	return 0;
}

在这里插入图片描述

五、类对象模型

5.1计算对象的大小

class Date {
	public:
		void Init(int year, int month, int day)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year;//这里默认前面加个下划线,方便函数里面使用的(不是硬性要求,但建议这样做)
		int _month;
		int _day;
};

class Empty1 {//一个空类

};

class Empty2 {//一个无成员变量的类
	public:
		void func()
		{

		}
};

int main()
{
	//类和对象的关系是一对多
	//举个例子:狗——边牧
	//狗是一个类,边牧是一个对象

	Date d;//Date对象的实例化,定义出一个实体d
	d.Init(2024, 1, 7);

	cout << sizeof(d) << endl;//打印12,即3个int型成员变量year、month、day的大小
	//成员函数的大小是不计算的
	//不同对象调用类的函数,这个函数地址都是一样的,即不同对象调用的类函数是同一个函数
	//需要注意的是,不同对象的成员变量是不一样的,这点和类函数是不同的
	cout << sizeof(Date) << endl;//类大小也是12


	//空类、无成员变量的类,对象大小为1字节,这1字节不存储有效数据
	//因为成员函数不算在对象大小里面,只要没有成员变量,大小都是1
	Empty1 e1;
	cout << sizeof(e1) << endl;//打印1

	Empty2 e2;
	cout << sizeof(e2) << endl;//打印1
	return 0;
}

在这里插入图片描述

5.2结构体内存对齐规则

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

【面试题】

  1. 结构体怎么对齐? 为什么要进行内存对齐?
  2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?
  3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

六、this指针

6.1简介

class Date {
	public:
		void Init(int year, int month, int day)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year;//这里默认前面加个下划线,方便函数里面使用的(不是硬性要求,但建议这样做)
		int _month;
		int _day;
	public:
		void Print() {//这里Print函数虽然没有参数,但实际调用会处理成Print(Date* this)
			//this是一个形参,就是当前调用这个函数的对象
			cout << _year << "-" << _month << "-" << _day;
			//这里会默认处理成
			//cout << this->_year << "-" << this->_month << "-" << this->_day;
		}
};

class Empty1 {//一个空类

};

class Empty2 {//一个无成员变量的类
	public:
		void func()
		{

		}
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2023, 1, 1);
	d2.Init(2024, 1, 3);
	d1.Print();//这里Print函数虽然没有参数,但实际调用会处理成d1.Print(&d1)
	cout << endl;
	d2.Print();
}

在这里插入图片描述
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏
的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”
的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编
译器自动完成。

【面试题】

  1. this指针存在哪里?

答:this指针是个形参,它和函数的局部变量一样,存在栈帧上面

  1. this指针可以为空吗?下面两段代码运行结果是什么

2答:在这里插入图片描述
上图左边代码解释:

class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
	int _a;
};
int main()
{
	
	A* p = nullptr;
	p->Print();//可以正常运行,打印Print()
	//指针用->,对象用.
	//成员函数的地址不在对象中
	//找成员函数,本质就是找成员函数的地址,
	//这个找地址是在编译链接阶段找的,通过函数名去找的
	
	//成员变量存在对象中
	p->_a=1;//报错,这里访问nullptr的_a,就有问题了,
	//_a是属于对象的,和前面成员函数不属于对象不一样
	
	return 0;
}

在这里插入图片描述

上图右边代码解释:

class A
{
public:
	void Print()
	{
		cout << _a << endl;//如果是空指针进来,相当于this._a变成了nullptr._a,就会报错
		cout << "Print()" << endl;
	}
	int _a;
};
int main()
{
	
	A* p = nullptr;
	p->Print();//可以正常运行,打印Print()
	//指针用->,对象用.
	//成员函数的地址不在对象中
	//找成员函数,本质就是找成员函数的地址,
	//这个找地址是在编译链接阶段找的,通过函数名去找的
	

	//p->_a=1;
	//成员变量存在对象中
	return 0;
}

在这里插入图片描述
为什么第一次没有报错,这里报错了?
因为我们第一次访问了Print这个成员函数,并没有用this调用东西。
而我们这次要访问this._a,但是我们这里p是一个nullptr传参过去,不调用成员变量就不会报错,但是你用nullptr调用成员变量就会有问题了。

6.2 this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给
    this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传
    递,不需要用户传递

七、类的6个默认函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员
函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

class Date {};

在这里插入图片描述

7.1构造函数

概念:
对于以下Date类:

class Date
{
public:
	void Init(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	private:
		int _year;
		int _month;
		int _day;
};
int main()
{
	Date d1;
	d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置
信息,未免有点麻烦,那能否在对象创建时,就将信息传递进去呢?

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证
每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次

在这里插入图片描述

class Date
{
public:
	// 1.无参构造函数
	Date()
	{}
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//构造函数特性:
	//1.函数名和类名相同
	//2.无返回值
	//3.对象实例化时编译器自动调用对应的构造函数
	//4.构造函数可以重载


	private:
		int _year;
		int _month;
		int _day;
};
int main()
{
	Date d1;//调用无参构造函数
	
	Date d2(2024,1,8);//调用带参构造函数(相当于是一个初始化)
	return 0;
}

当然,如果有时忘了初始化,但是又想每次都默认有一个初始化,我们可以用到缺省参数

也就是把这里的Date()构造函数填一些默认参数变成Date(int year=1, int month=1, int day=1)
在这里插入图片描述

ps:这里的Date()和Date(int year=1, int month=1, int day=1)构成重载
重载就是函数名相同,参数名不同。

这里如果无参调用Date()会有歧义,
因为编译器不知道你是要用无参构造函数Date()
还是没有写参数的Date(int year=1, int month=1, int day=1)
在这里插入图片描述

class Date
{
public:
	
	// 带参构造函数
	Date(int year=1, int month=1, int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//构造函数特性:
	//1.函数名和类名相同
	//2.无返回值
	//3.对象实例化时编译器自动调用对应的构造函数
	//4.构造函数可以重载
	void Print() {
		cout << _year <<"-"<< _month <<"-"<< _day<<endl;
	}

	private:
		int _year;
		int _month;
		int _day;

};
int main()
{
	Date d1;//调用无参构造函数
	d1.Print();

	Date d2(2024,1,8);//调用带参构造函数(相当于是一个初始化)
	d2.Print();

	Date d3(2024, 1);
	d3.Print();

	return 0;
}

在这里插入图片描述


class Stack {
	public:
		Stack(size_t capacity=3) {
			_a = (int*)malloc(sizeof(int)*capacity);
			if (nullptr == _a) {
				perror("malloc申请失败");
				return;
			}
			_capacity = capacity;
			_size = 0;
		}

	private:
		int* _a;
		int _capacity;
		int _size;
};

int main()
{
	Stack s;
	
	return 0;
}

上面这段代码,如果你不写构造函数,默认不会初始化
在这里插入图片描述

所以,大家在用的时候,最好还是要记得初始化一下。

默认生成的构造函数,会处理自定义类型(调用自定义类型的构造或者赋一个默认值),但是内置类型处理不处理取决于编译器。建议不管是什么类型,你都初始化一下,防止编译器没有给你初始化后面报错。
在这里插入图片描述
一般情况都需要我们自己写构造函数。如果成员是自定义类型,或者声明时给了缺省值,可以考虑不写构造函数,让编译器自己生成构造函数

在这里插入图片描述

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;
	int _month;
	int _day;
};
int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
	// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
	// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

在这里插入图片描述

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述
这里Date创建对象d,由于我们自己没有写构造函数,所以编译器自动生成了一个。
对于我们的内置类型的int的_year、_month、_day我们编译器都没有初始化
但是对于我们的自定义类型的_t,我们编译器是默认把它初始化成了0

在这里插入图片描述
在这里插入图片描述
这里我们写了一个无参的构造函数,还有一个全缺省的构造函数。你可以理解为可以不传参就调用的构造函数就是默认构造函数。所以这里就产生歧义,编译器无法通过了。

ps:三种默认构造函数:
在这里插入图片描述

解决办法:你可以把全缺省改成半缺省,或者写一个

7.2析构函数

概念:
在这里插入图片描述
注意:析构函数是进行资源的清理,不是资源的销毁!

特性:
在这里插入图片描述

class Date
{
public:
	Date(int year=1 , int month = 1, int day = 1)//构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day;
	}
	~Date()//析构函数,类名前加一个波浪号~
	{
		cout << "~Date()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
// 以下测试函数能通过编译吗?
int main()
{
	Date d1;
}

析构函数会在对象生命周期结束后自动调用,我这里打印一下做个示例
在这里插入图片描述

这个析构函数的作用体现在哪了呢?
比如说我们的,如果我们用完一个指针,而那个指针指向的malloc出来的空间,我们很多时候会忘了destroy它,这样就会造成内存泄露。
内存泄露是不会报错的,这就导致有时其实代码有问题,但是我们没发现。

但如果我们写了一个析构函数,就会在对象生命周期结束默认进行资源的清理

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	// 其他方法...
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

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


class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

在这里插入图片描述

7.3拷贝构造函数

概念:

在这里插入图片描述
特征:
拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式

  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

class Date
{
public:
	Date(int year, int month, int day) {//构造函数
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d) {//拷贝构造函数(构造函数的一个重载)
	//拷贝构造函数的参数必须有且仅有一个同类型对象的引用
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print() {
		cout << _year << "-" << _month << "-" << _day;
	}
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 2;
};

int main()
{
	Date d1(2024,1,11);
	Date d2(d1);//用d1来拷贝构造d2,这里会调用拷贝构造函数
	d2.Print();
	return 0;
}

在这里插入图片描述
关于拷贝构造第二点特征:为什么使用传值方式编译器直接报错,会引发无穷递归调用?

如果我现在使用传值,那我现在d2要调用拷贝构造
在这里插入图片描述
但是调用这个函数之前,你要先传参。但是d1传参给d的时候,传参又会引发一个新的拷贝构造。
(简单来说,d2要拷贝构造d1,但是d1传参过去又创造了新的拷贝构造d…)

而如果你是用引用来做参数:
这里d2会调用拷贝构造,然后d1传参给引用dd,而引用dd就是d1的别名,所以不会再往下调用拷贝构造了,这里就不会出现无穷递归了。
在这里插入图片描述

ps:普通传值拷贝的缺点:
我们在传一些较大的自定义类型时,有时候是传的原先变量的类型。
这种方式又称为“浅拷贝”(也就是值拷贝),也就是参数改变不影响原先变量。
ps:深拷贝是指拷贝指向的资源,让我有和你一样大的空间,一样的值。

class Date
{
public:
	Date(int year,int month,int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() {
		cout << _year << "-" << _month << "-" << _day;
	}
private:
	// 基本类型(内置类型)
	int _year=1970 ;
	int _month=1 ;
	int _day =2;
};

void func(Date d)
{//该种方式又称为“浅拷贝”
	d.Print();
}

int main()
{
	Date d1(2024,1,11);
	d1.Print();
	return 0;
}

在这里插入图片描述
对于Date类,直接传值拷贝好像没啥问题。但是如果你遇到一些特别的类,比如栈类

举个例子:现在我们调用一个Date类,和一个栈类
然后对象d1和对象st1分别调用了func1和func2
在这里插入图片描述
对于Date类,把三个int型的参数传过去就传过去了,然后函数结束以此销毁。
在这里插入图片描述
但是对于栈类(stack),栈有指针类型的_a,也有int型的_top和_capacity
传值调用先复制过去
在这里插入图片描述

但是这里有区别的是,我们值拷贝的_a指向一块空间,和我们原先的_a指向是同一块空间。

在这里插入图片描述
然后c++是有析构函数的,析构函数会在出了作用域自动调用
在出了func2这个作用域,析构函数会把st中的_a指向的空间释放一次
在这里插入图片描述
然后出了main函数的作用域,析构函数又会把st1中的_a指向区域再释放一次
在这里插入图片描述

就相当于一块空间被释放了两次,但是谁又能保证,第一次释放后,有没有其他程序用了那块空间。但是你第二次释放,相当于把别人的空间释放掉了。这就很离谱了。
所以,对于栈这种类,你用浅拷贝(传值)就很有问题。

解决办法:

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

对于栈类,我们的拷贝构造对于自定义类型_a(指针)就是要进行深拷贝,深拷贝是指拷贝指向的资源,让我有和你一样大的空间,一样的值。对于_a的拷贝就是再申请一块同样大小的空间,放同样的数据进去。

而对于内置类型的_top和_capacity就直接浅拷贝即可。
在这里插入图片描述
这样的话,在函数调用结束,自动调用析构函数时,两次释放的是不同空间。就不会造成对同一个空间释放两次的情况了。

拷贝构造函数典型调用场景:
1.使用已存在对象创建新对象
2.函数参数类型为类类型对象
3.函数返回值类型为类类型对象
简言之:一般自己要开空间的基本都要自己写深拷贝

ps:拷贝构造和赋值拷贝的辨析

7.4赋值运算符重载

7.4.1运算符重载

对于内置类型,可以使用运算符,比如"=="。但是自定义类型就无法直接比较(编译器也不知道你的自定义类型的比较规则),自定义类型的比较,只能你自己来写比较函数了。

而不同人写的同一个功能的函数,可能函数的命名又是不同的。
为了统一起来方便辨认,推出了运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

函数原型:返回值类型 operator操作符(参数列表)
即,operate+运算符 作为函数名,这样就很容易知道不同人写的函数是相做什么了。

比如下面代码中 bool operate==很容易就知道是一个判断相等的函数

class Date
{
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)
{//operate==很容易就知道是一个判断相等的函数
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}
int main()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout << (d1 == d2) << endl;
}

在这里插入图片描述
上面代码main函数中,d1==d2,实际编译器会自动给你调operate= =(d1,d2)这个函数

所以这里运算符重载的好处就体现出来了,你写一个运算符重载的函数,然后你可以调operater+运算符来调用这个函数。更方便的是你写完运算符重载函数就可以直接写算符,编译器会帮你自动调用这个运算符重载函数。

ps:运算符重载只能有一个参数,因为对象调用这个运算符函数,
然后参数是另一个运算符作用的对象

7.4.2赋值运算符重载

在这里插入图片描述

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	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;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023, 1, 14);
	Date d2(2022, 1, 1);

	//一个已存在的对象去拷贝初始化另一个对象
	Date d3(d1);//拷贝构造
	d3.print();

	cout << endl;

	//两个已存在的对象拷贝
	 d2 = d3;//赋值重载,这里两个自定义类型的复制调用了Date& operator=(const Date& d)
	d2.print();

}

在这里插入图片描述
如果我们没有写赋值运算符重载,编译器也会默认生成一个
可以看到,我把赋值运算符重载那块代码注释掉,代码也是可以通过的
在这里插入图片描述
这里区分自定义类型的比较==,如果你要比较自定义类型必须写一个比较的重载函数,不然编译器不知道你的比较规则是什么。

另外,也不是所有的都可以不写operator=,
和拷贝构造类似,内置类型值拷贝,自定义类型调用它的赋值。
比如Date MyQueue可以不写operator=,默认生成的operator=即可用
但是Stack必须自己实现operator=,实现深拷贝。


八、const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改

在这里插入图片描述
上面这段代码,我们用const Date创建了d1,就意味着d1是不能被修改了
但是我们d1.Print()又调用了Print()函数,会传一个const Date*类型的d1地址过去

这里Print()函数表面是没有参数的,但实际是有一个Date* this的参数在里面
传参的时候你把一个Const Date变成了Date,这就相当于把权限给放大了。
权限只能缩小,不能放大!
ps:权限可以缩小意味着不管是const或者非const修饰的,都可以调用const修饰的

但你const修饰的,调用非const修饰的这样就会报错了。

那我们要修改就要把Print函数也加一个const,但由于前面有返回类型和作用域限制符,已经比较乱了,所以是规定把const放在函数后面

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

九、取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

class Date
{
public:
	Date* operator&()
	{
		return this;

	}
	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!

九、知识实战:日期类简单实现

我们这里写一个简单的日期加天数得到新日期的运算符重载

Date.h文件

#pragma once
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);
	Date(const Date& d);
	Date& operator=(const Date& d);
	void print();
	// 获取某年某月的天数
	int GetMonthDay(int year, int month);
	bool operator<(const Date& y);
	bool operator>(const Date& y);
	bool operator<=(const Date& y);
	bool operator>=(const Date& y);
	bool operator==(const Date& d);
	bool operator!=(const Date& y);
	Date& operator+=(int day);
	Date& operator-=(int day);
	int operator-(const Date& d);
	Date operator++(int);
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp文件

#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"
#include<assert.h>


Date::Date(int year , int month , int day)
{
		_year = year;
		_month = month;
		_day = day;
		if (_year < 1 || _month>12 || _day<1 || _day>GetMonthDay(_year,_month)) {
			/*assert(false);*/
			this->print();
			cout << "日期非法" << endl;
		}
}

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

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

void Date:: print() {
	cout << _year << "-" << _month << "-" << _day;
}



bool Date::operator!=(const Date& y) {
	return !(*this == y);
}

Date& Date::operator=(const Date & y)
{
	_year = y._year;
	_month = y._month;
	_day = y._day;
	return *this;
}

bool Date::operator>=(const Date& y)
{
	return *this > y || *this == y;
}


bool Date::operator<(const Date& y)
{
	return !(*this >= y);
}

bool Date::operator<=(const Date& y)
{
	return !(*this > y);
}

bool Date::operator>(const Date& y)
{
	if (this->_year > y._year) {
		return true;
	}
	if (this->_month > y._month) {
		return true;
	}
	if (this->_day > y._day) {
		return true;
	}
	return false;
}
	// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
	{
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31 };
		int day = days[month];
		if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			day += 1;
		}
		return day;
}
Date& Date::operator+=(int day) {
		_day += day;
		while (_day > GetMonthDay(_year, _month)) {
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month == 13) {
				_year++;
				_month = 1;
			}
		}
		return *this;//this是一个指针,指向当前对象,对它解引用才是当前对象。
}

Date& Date::operator-=(int day) {
	_day -= day;
	while (_day <= 0) {
		--_month;
		if (_month == 0) {
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

int Date::operator-(const Date& d)
{
	//假设左大右小
	int flag = 1;
	Date max = *this;
	Date min = d;

	if (*this < d)//左小右大
	{
		max = d;
		min = *this;
		flag = -1;
	}

	int n = 0;
	while (min != max) {
		min++;
		n++;
	}
	return n * flag;
}


测试用例

int main()
{
	Date d1(2023, 10, 24);
	d1.print();
	
	cout << endl;

	Date d2(2100, 3, 4);
	d2.print();

	cout << endl;
	cout << d2 - d1 << endl;

}

在这里插入图片描述

十、构造函数补充

10.1构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

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

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

10.2初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

class Date
{
public:
	Date(int year, int month, int day, int ret)
		: _year(year)
		, _month(month)
		, _day(day)
		, _ret(ret)
		,_n(1)
	{
	}
private:
	int _year;
	int _month;
	int _day;
	int& _ret;//引用必须使用初始化列表初始化
	const int _n;//const修饰的成员变量也必须在初始化列表初始化
};
int main()
{
	Date d1(2024, 1, 16,0);//对象整体定义
	//每个成员变量在哪里定义?——初始化列表中
}

在这里插入图片描述

需要注意引用、const修饰的成员变量、自定义类型成员(没有默认构造函数)必须在初始化列表初始化

也可以一部分初始化在函数体内,也可以一部分初始化在初始化列表中。

class Date
{
public:
	Date(int year, int month, int day, int ret)
		:_ret(ret)
		,_n(1)
	{	//剩下3个成员没有在初始化列表中显示写出来定义,
		//但它也会定义,只是内置类型默认给的是随机值
		//如果是自定义类型成员会去调用它的默认构造函数
		//函数体内初始化
		_year = year;
		_month=month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
	int& _ret;//引用必须使用初始化列表初始化
	const int _n;//const修饰的成员变量也必须在初始化列表初始化
};

在这里插入图片描述

class Time
{
public:
	Time(int hour = 0)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int day)
	{}
private:
	int _day;
	Time _t;
	//没有写默认构造函数,但对于自定义类型也会默认去调用它的默认构造函数
	//ps:不是说我们不写构造函数,编译器自动生成的就是默认构造
	//默认构造函数有三种——1.无参、2.全缺省、3.默认生成的
};

int main()
{
	Date d(1);
}

在这里插入图片描述

初始化列表解决的问题:
1.必须在定义的地方显示初始化,比如:引用、const、没有默认构造的自定义成员
2.有些自定义成员想要显示初始化(不想用默认的初始化)

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

这里为什么第二个打印的是随机值?
因为我们什么是先是a2,所以我们在初始化的时候也是先初始化a2,而a2是用a1进行初始化的(此时a1还未初始化,a1还是个随机值),所以a2是随机值。

然后是用传参过来的1初始化a1,a1=1

ps:建议什么和初始化顺序保持一致,不然会出现理解问题,我上面这个代码是为了举例,正常写肯定不建议这样写。

10.3explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用

也就是说,你那个单参数的构造函数中,参数是什么类型,什么类型就可以转那个自定义类型。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	int _a = 0;
};
int main()
{
	A aa1(1);//正常构造
	
	A aa2 = 2;//内置类型对象,隐式转换成自定义类型对象
	//相当于先构造A(2),再把aa3=A(2)
	//但是注意!只要发生类型转换,都会产生临时变量,上面说的的A(2)其实就是临时变量

	return 0;
}

在这里插入图片描述
可以看到上面代码也是可以调试运行的

但是用explicit修饰构造函数,将会禁止构造函数的隐式转换。

在这里插入图片描述
可以看到,在单参数的构造函数前加一个explicit关键字,就不再支持转换了

十一、static成员

在这里插入图片描述

面试题:实现一个类,计算程序中创建出了多少个类对象

class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() { --_scount; }
	static int GetACount() { return _scount; }//静态成员函数特点:没有this指针,可以不通过对象调用
private:
	static int _scount;//类里面声明
	//相当于这个类的专属全局变量,static修饰的成员变量,不属于某个具体对象,它属于所有对象
};

int A::_scount = 0;//类外面定义

int main() {
	A a1;
	A a2;
	//static修饰的函数可以通过对象访问
	cout << a1.GetACount() << endl;
	
	//也可以通过类名访问
	cout << A::GetACount()<<endl;

	//还可以用匿名对象进行访问
	//匿名对象格式:类名()
	cout << A().GetACount() << endl;
	//但这里需要注意的是,匿名对象本身也是一个对象,在你使用匿名对象调用函数时,对象个数也会随之+1
	return 0;
}

在这里插入图片描述

在这里插入图片描述
静态成员函数不可以调用非静态的成员函数(也不可以访问其他静态的成员函数),
因为静态的没有this指针

但非静态成员函数可以调用静态成员函数。

十二、友元

友元这个东西简单来说就是,对于一个类里面私有的成员,你可以进行一些声明,让一些友元函数可以用这些私有的成员。

就像你家的私有的东西别人不能用,但是你可以声明给你的好友,好朋友是可以用你的东西的。

class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
	ostream& operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day << endl;
		return _cout;
	}
private:
	int _year;
	int _month;
	int _day;
};

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在
类的内部声明,声明时需要加friend关键字。

在这里插入图片描述
但友元这个东西也有不好的地方,本来私有的东西别人是禁止访问的,结果你搞了个友元让别人可以随便改,它是破坏了封装的,也增加了耦合,不宜多用

十三、内部类

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上图中B就是一个普通类,它只是受A的类域和访问限定符限制
如果你直接访问B,是不能定义B类的对象的
在这里插入图片描述
你必须指明内部类是属于哪个类的,编译器才能找到那个内部类
可以看到,我们指明了内部类B是属于A类的,这里就不再报错了。
在这里插入图片描述
当然了,如果你把内部类置为private,那这个内部类只有你自己能用,别人就用不了了。

另外,内部类天生就是外部类的友元,它是可以调用内部类的私有成员的。
在这里插入图片描述
这就有点像,外部类是老爸,内部类是儿子,对于家里的私有东西,儿子是可以用老爸的。

十四、拷贝对象时的一些编译器优化

先来看构造的一些补充知识点

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;
}
int main()
{
	A aa1(1);//构造
	A aa2(aa1);//拷贝构造
	
	A aa3 = aa1;//也是拷贝构造
	//一个已经存在的对象拷贝初始化另一个要创建的对象是拷贝构造

	aa2 = aa3;//赋值拷贝
	//两个已经存在的对象,才是赋值拷贝
	return 0;
}

在这里插入图片描述

一些优化:
连续的构造+构造=构造
构造+拷贝构造=构造
拷贝构造+拷贝构造=拷贝构造

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a = aa._a;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

void f1(A aa)
{}

A f2()
{
	A aa;
	return aa;
}
int main()
{
	A aa1 = 1;//不同类型之间赋值
	//第一步:先用1构造一个临时对象,
	//第二步:再用临时对象拷贝构造aa1
	//第三步:编译器优化——在同一个表达式中,构造+拷贝构造=构造

	//优化的三种情况:
	//连续的构造+构造=构造
	//构造+拷贝构造=构造
	//拷贝构造+拷贝构造=拷贝构造
	cout << "--------" << endl;

	const A& aa2 = 2;
	//先用2构造一个临时对象,然后给临时对象取别名aa2(不会调用拷贝构造)
	cout << "--------" << endl;

	A aa3;//构造
	f1(aa3);//拷贝构造
	cout << "--------" << endl;

	f1(A(2));//构造+拷贝构造=构造
	cout << "--------" << endl;
	
	A aa4 = f2();//拷贝构造+拷贝构造=拷贝构造
	cout << "--------" << endl;

	// 一个表达式中,连续拷贝构造+赋值重载->无法优化
	aa1 = f2();
	cout << "--------" << endl;
	return 0;
}

在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

详解Python web框架到底是怎么来的?

前言 咱都知道软件开发的架构有两种&#xff0c;分别是C/S架构与B/S架构&#xff0c;本质上都是借助socket实现网络通信&#xff0c;因此Django作为一个web框架本质上也是一个socket服务端&#xff0c;浏览器则是客户端&#xff0c;我们可以自己实现简易的web框架来更好的理解…

linux sudo指令提权

sudo指令 sudo 是在linux中用于以超级用户&#xff08;root&#xff09;权限执行命令的命令。它允许普通用户在执行特定命令时提升其权限&#xff0c;以完成需要超级用户权限的任务。sudo 的名称是 "superuser do" 的缩写。 格式 接受权限的用户登陆的主机 &#xff…

【MySQL】——关系数据库标准语言SQL(大纲)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

OceanBase集群扩缩容

​ OceanBase 数据库采用 Shared-Nothing 架构&#xff0c;各个节点之间完全对等&#xff0c;每个节点都有自己的 SQL 引擎、存储引擎、事务引擎&#xff0c;天然支持多租户&#xff0c;租户间资源、数据隔离&#xff0c;集群运行的最小资源单元是Unit&#xff0c;每个租户在每…

MCM备赛笔记——图论模型

Key Concept 图论是数学的一个分支&#xff0c;专注于研究图的性质和图之间的关系。在图论中&#xff0c;图是由顶点&#xff08;或节点&#xff09;以及连接这些顶点的边&#xff08;或弧&#xff09;组成的。图论的模型广泛应用于计算机科学、通信网络、社会网络、生物信息学…

如何在Docker下部署MinIO存储服务通过Buckets实现文件的远程上传

&#x1f4d1;前言 本文主要是Linux下通过Docker部署MinIO存储服务实现远程上传的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#…

Modern C++ 一个例子学习条件变量

目录 问题程序 施魔法让BUG浮出水面 条件变量注意事项 修改程序 问题程序 今天无意中看到一篇帖子&#xff0c;关于条件变量的&#xff0c;不过仔细看看发现它并达不到原本的目的。 程序如下&#xff0c;读者可以先想想他的本意&#xff0c;以及有没有问题&#xff1a; #…

洛谷P5732 【深基5.习7】杨辉三角(C语言)

入门递推题&#xff0c;就算你不是OIer也该知道的杨辉三角 同时这也是组合数的公式&#xff0c;很重要&#xff0c;因为常规组合数公式是阶乘运算会爆&#xff0c;而这个就不怎么会了 赋 arr[i][j]初值1&#xff0c;接下来就可以递推了 #include<stdio.h> int main() …

温度采样【通道选通】S9KEAZ128的PTA2和PTA3引脚无法拉高

1、问题记录&#xff1a;由18串温度采样修改成32串温度采样&#xff0c;增加一路adc采样&#xff0c;通过cd4051控制通道选通&#xff0c;代码中增加了相应的代码&#xff0c;发现增加的最后8路温度不能够控制&#xff0c;以24串为例&#xff0c;给温度传感器增加温度&#xff…

01-开始Rust之旅

1. 下载Rust 官方推荐使用 rustup 下载 Rust&#xff0c;这是一个管理 Rust 版本和相关工具的命令行工具。下载时需要连接互联网。 这边提供了离线安装版本。本人学习的机器环境为&#xff1a; ubuntu x86_64&#xff0c;因此选用第②个工具链&#xff1b; 1. rust-1.75.0-x86_…

CPU数据按行和按列读取性能差异浅析

改了一行代码&#xff0c;数组遍历耗时从10.3秒降到了0.5秒&#xff01; 两个简单的测试程序 定义一个同样大小的二维数组&#xff0c;然后循环遍历&#xff0c;对数组元素赋值。 array1.c 对数组按行进行访问 • array2.c 对数组按列进行访问、 编译运行&#xff0c;并用ti…

[小程序]页面的构建

一、视图容器组件 ①View 视图区域组件&#xff0c;类似于HTML中的div&#xff0c;可以用来按块实现页面布局效果&#xff0c;具体实例如下&#xff1a; <view class"dock"><view>A</view><view>B</view><view>C</view> &…

《Linux高性能服务器编程》笔记01

Linux高性能服务器编程 本文是读书笔记&#xff0c;如有侵权&#xff0c;请联系删除。 参考 Linux高性能服务器编程源码: https://github.com/raichen/LinuxServerCodes 豆瓣: Linux高性能服务器编程 文章目录 Linux高性能服务器编程第05章 Linux网络编程基础API5.1 socket…

JOSEF约瑟 零序过流继电器LGL-110/AC AC220V 0.01~9.99A 柜内安装

LGY 、LGL零序过电压继电器 系列型号 LGY-110零序过电压继电器&#xff1b; LGL-110零序过电压继电器&#xff1b; LGL-110/AC零序过电压继电器&#xff1b; LGL-110静态零序过电流继电器 &#xff11; 应用 LGL-110 型零序过电流继电器用作线路和电力设备的零序过电流保护。…

【Arduino】无法上传程序到开发板,报错 avrdude: ser_open(): can‘t set com-state for “\\.\COM6“

问题描述 在尝试将项目上传到Arduino板子时&#xff0c;尽管开发板已被正确连接&#xff0c;并且IDE中能够正常读取到开发板信息&#xff0c;但是上传过程中仍然出现了问题。 下面是IDE中显示的开发板信息&#xff1a; 当尝试上传程序时&#xff0c;控制台报错信息如下&#…

【迅搜19】扩展(二)TNTSearch和JiebaPHP方案

扩展&#xff08;二&#xff09;TNTSearch和JiebaPHP方案 搜索引擎系列的最后一篇了。既然是最后一篇&#xff0c;那么我们也轻松一点&#xff0c;直接来看一套非常有意思的纯 PHP 实现的搜索引擎及分词方案吧。这一套方案由两个组件组成&#xff0c;一个叫 TNTSearch &#xf…

【大数据Hive】hive 行列转换使用详解

目录 一、前言 二、使用场景介绍 2.1 使用场景1 2.2 使用场景2 三、多行转多列 3.1 case when 函数 语法一 语法二 操作演示 3.2 多行转多列操作演示 四、多行转单列 4.1 concat函数 语法 4.2 concat_ws函数 语法 4.3 collect_list函数 语法 4.4 collect_set函…

获取域控的方法

在域渗透中、作为渗透测试人员&#xff0c;获取域控的权限基本上可以获取整个内网的权限 1.高权限读取本地密码 当域管理员在域成员机器上登录进行工作的时候&#xff0c;会将明文密码保存在本地进行的lsass.exe&#xff0c;可以通过 mimikatz来读取到本地的明文密码。 priv…

MySQL基础笔记(9)事务

一.简介 所谓事务&#xff0c;是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或者撤销操作请求&#xff0c;即&#xff0c;这些操作要么同时成功&#xff0c;或者同时失败——OS中有原语不可分割的概念&…

蓝桥杯、编程考级、NOC、全国青少年信息素养大赛—scratch列表考点

1、小小情报员&#xff08;202309scratch四级24题&#xff09; 1.准备工作 &#xff08;1&#xff09;选择背景 Colorful City&#xff1b; &#xff08;2&#xff09;保留角色小猫&#xff0c;选择角色Ballerina。 2.功能实现 &#xff08;1&#xff09;角色小猫初始位置…