类和对象(2)(重点)

news2024/12/29 8:45:09

 个人主页:Jason_from_China-CSDN博客

所属栏目:C++系统性学习_Jason_from_China的博客-CSDN博客

所属栏目:C++知识点的补充_Jason_from_China的博客-CSDN博客

类的默认成员函数

概念概述

  • 默认成员函数就是用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下 6 个默认成员函数,需要注意的是这 6 个中最重要的是前 4 个,最后两个取地址重载不重要,我们稍微了解一下即可。其次就是 C++11 以后还会增加两个默认成员函数,移动构造和移动赋值,这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习。

包括

构造函数

概念概述

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象 (我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前 Stack 和 Date 类中写的 Init 函数的功能,构造函数自动调用的特点就完美的替代的了 Init。

构造函数的特点:

  1. 函数名与类名相同。
  2. 无返回值。(返回值啥都不需要给,也不需要写 void,不要纠结,C++ 规定如此)
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载(因为需要不同的数据,不同的初始化,会使开发更加简单)。
  5. 如果类中没有显式定义构造函数,则 C++ 编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  6. 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。
  7. 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决,初始化列表,我们下个章节再细细讲解。
  8. 简单的说构造函数就是用来初始化的函数,而且默认构造函数会自动调用

注意:

C++ 把类型分成内置类型 (基本类型) 和自定义类型。内置类型就是语言提供的原生数据类型,如:int/char/double/ 指针等,自定义类型就是我们使用 class/struct 等关键字自己定义的类型。

构造函数的实现

不带参数构造函数

//.h

///默认构造函数的使用
class MyClass
{
public:
	//默认构造函数
	MyClass()//类名相同,没有参数,叫做无参数构造函数
	{
		_year = 1999;
		_month = 12;
		_day = 1;
		cout << "默认构造函数的自动调用:" ;
		cout << _year << "/" << _month << "/" << _day << endl;
	}

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

//.cpp
int main()
{
	MyClass d1;
	return 0;
}

此时我们发现这里在初始化的时候自动调用无参数构造函数(也就是默认构造函数),当然这里初始化成功

带参数构造函数

//.h
class MyClass1
{
public:
	MyClass1(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "带参数构造函数的调用:";
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

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

//.cpp
int main()
{
	//不带参数构造函数
	MyClass d1;

	//带参数构造函数
	MyClass1 d2(2024, 9, 14);//带参构造函数这里是需要传递一个参数

	return 0;
}

半缺省构造函数

//.h
//半缺省构造函数(从右往左进行赋值)
class MyClass2
{
public:
	MyClass2(int year, int month, int day = 28)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "半缺省构造函数(从右往左进行赋值):";
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private://私有变量,只能类里面的函数进行访问
	int _year;
	int _month;
	int _day;
};

//.cpp
int main()
{
	//不带参数构造函数
	MyClass d1;
	
	//带参数构造函数
	MyClass1 d2(2024, 9, 14);

	//半缺省构造函数(从右往左进行赋值),赋值的时候,从左往右哦,就是为了和构造函数的缺省参数进行分开
	MyClass2 d3(2000, 2);
	return 0;
}

半缺省构造参数(从右往左进行赋值),赋值的时候,从左往右哦,就是为了和构造函数的缺省参数进行分开

全缺省构造函数

//全缺省构造函数(从右往左进行赋值)
class MyClass2
{
public:
	MyClass2(int year, int month, int day = 28)
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "半缺省构造参数(从右往左进行赋值):";
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private://私有变量,只能类里面的函数进行访问
	int _year;
	int _month;
	int _day;
};

int main()
{
	//不带参数构造函数
	MyClass d1;
	
	//带参数构造函数
	MyClass1 d2(2024, 9, 14);

	//半缺省构造函数(从右往左进行赋值)
	MyClass2 d3(2000, 2);

	//全缺省构造函数
	MyClass3 d4;
	return 0;
}

默认构造函数

无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。

总结:

简单的说就是,在创建对象初始化的时候,会自动调用的函数,带参数构造函数不是默认构造函数,是需要调用的时候传参的。默认构造函数在调用创建对象的时候是不需要传参的。上面我们的代码很清晰了。

构造函数注意事项

对于自定义类型来说,构造函数往往是需要自己实现的。因为自定义类型的初始化可能较为复杂,需要根据特定的逻辑来进行成员变量的初始化等操作。例如,一个包含多个成员变量且有特定初始化需求的自定义类,通常需要定义构造函数来确保对象在创建时被正确初始化。

 

对于内置类型来说是不需要的。内置类型如 int、char、double 等,在创建对象时如果不进行显式初始化,它们会被自动赋予默认值(对于数值类型通常为 0,对于指针类型通常为 nullptr 等)。当然,如果需要特定的初始值,也可以在定义变量时进行显式初始化,但一般情况下不需要专门为内置类型定义构造函数。

析构函数(~)

概念概述

析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前 Stack 实现的 Destroy 功能,而像 Date 没有 Destroy,其实就是没有资源需要释放,所以严格说 Date 是不需要析构函数的。

析构函数的特点:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。(这里跟构造类似,也不需要加 void)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,系统会自动调用析构函数。
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用他的析构函数。
  6. 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
  7. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如 Date;如果默认生成的析构就可以用,也就不需要显式写析构,如 MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如 Stack。
  8. 一个局部域的多个对象,C++规定后定义的先析构。
  9. 简单的说,析构函数就是函数的销毁,是编译器会自动调用的销毁

什么时候需要析构函数

当有资源申请的时候,就需要进行析构函数,析构函数是和构造函数一样的,内置类型不需要析构,自定义类型需要进行析构,也需要自己写析构函数,但是调用的时候是会自己调用的。

像如下图:此时开辟了空间,产生了资源的使用,所以就需要析构函数,如果在main函数调用结束的时候没有进行销毁资源,就会导致资源的泄露,这是很麻烦的。所以此时我们需要进行析构函数。

什么时候不需要析构函数 

1,没有资源申请的时候

2,内置类型不做处理

3,两个栈实现队列的时候,内置类型不做处理,自定义类型会调用栈的析构(可以在队列的析构里面写一行,cout

但是这里有一个特殊情况,就是两个栈实现队列下面有其他资源

这里的指针需要析构,因为指向一个空间,需要把这个资源释放了

析构函数的使用

书写,其实就是C语言的函数的销毁

调用,main函数在结束的时候,会自动调用析构函数,因为这里有资源的申请

多个对象的时候,析构顺序

当有资源需要进行析构的时候,你没有写内容,但是定义了析构函数,也是会调用的,只是没有析构成功(析构函数的自动调用:当一个对象的生命周期结束时,如果该对象的类定义了析构函数,那么析构函数会被自动调用。这是由C++运行时环境保证的。如果没有定义析构函数,那么不会有任何特别的函数被调用来处理对象的销毁。)

规定来说哦,后定义的先析构

所以这里是st2先析构,st1后析构

析构函数调用的时候-注意事项

  1. 规定来说哦,后定义的先析构
  2. 类的析构函数调用一般是按照调用的相反顺序进行调用,但是需要注意static对象的存在,因为static改变了对象的生存作用域,需要等待程序结束的时候才会释放对象
  3. 全局变量先于局部对象进行构造。

    (在 C++ 中,在程序启动时,全局变量的构造在进入main函数之前完成

    也就是说,先构造全局变量,然后程序的执行流程进入main函数,再构造main函数中的局部变量等。)

  4. 局部对象按照出现的顺序进行构造,无论是否为static。(简单的说,也就是在main,调用Func函数,Func函数会按照后定义的先析构顺序进行析构)
  5. 题型精讲:

    全局变量优先局部进行构造,所以往往最后进行析构。(C)
    局部变量先构造的后析构。(A B)
    被静态修饰的变量因为static改变了对象的生存作用域,需要等待程序结束的时候才会释放对象(D)

拷贝构造函数

概念概述

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。

同时C++规定了,这里是规定,传值传参必须调用拷贝构造

拷贝构造函数的特点

  1. 拷贝构造函数是构造函数的一个重载。
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
  3. C++ 规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
  4. 若未显式定义拷贝构造,编译器会生成自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝 / 浅拷贝 (一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
  5. 像 Date 这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显式实现拷贝构造。像 Stack 这样的类,虽然也都是内置类型,但是 _a 指向了资源,编译器自动生成的拷贝构造完成的值拷贝 / 浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝 (对指向的资源也进行拷贝)。像 MyQueue 这样的类型内部主要是自定义类型 Stack 成员,编译器自动生成的拷贝构造会调用 Stack 的拷贝构造,也不需要我们显式实现 MyQueue 的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
  6. 传值返回会产生一个临时对象调用拷贝构造,传值引用返回,返回的是返回对象的别名 (引用),没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回

拷贝函数的使用

拷贝构造的实现其实是很简单,这里先举出实例讲解,之后会进行解释,主要就是传参的时候,如果传递的是传值传参,那么需要用引用进行接收,不然会导致死循环从而导致崩溃

下面的的代码里面首先我们实现声明和实现的分离

//.h
//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//.cpp
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();
	return 0;
}

运行代码,可以清楚的看见,两个拷贝构造都实现成功,这两种形式,看看你喜欢哪一种拷贝构造实现形式

拷贝构造的过程

//.h

//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}


//.cpp
void Func(Date& d)
{
	cout << "拷贝构造函数的调用" << endl;
	d.print();
}
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);
	return 0;
}

这里我们观察拷贝构造是如何完成的

第一步,类类型的,传值传参

第二步,返回函数

全过程图解

在这个例子中,void Func(Date& d)中的&可以写也可以不写,但加上引用可以避免不必要的拷贝构造函数调用,提高效率。

如果写成void Func(Date d),在调用Func函数时,会调用拷贝构造函数将实参对象拷贝一份传递给函数参数d,可能会有一定的性能开销,尤其是当Date类比较复杂时。

如果写成void Func(Date& d),则是通过引用传递参数,不会调用拷贝构造函数,只是传递了对象的引用,效率更高。综上所述,从性能角度考虑,建议加上引用符号&。

所以可以进行与优化,我们函数依旧采取引用接收,减少拷贝

拷贝构造函数的原理解释

首先我们需要明确一点就是,C++在传值传参的时候会调用拷贝构造

什么是传值传参,顾名思义肯定是传递数值。

如果我们传值传参,但是接收的话不采取引用接收,就会导致一直情况

拷贝构造和指针

class MyClass
{
public:
	MyClass();
	MyClass(MyClass* M);
	//打印函数的实现
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
//构造函数
MyClass::MyClass()
{
	_year = 1999;
	_month = 2;
	_day = 28;
	cout << "MyClass构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现
MyClass::MyClass(MyClass* M)
{
	_year = M->_year;
	_month = M->_month;
	_day = M->_day;
}

int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);

	//拷贝构造和指针的联合实现
	MyClass M1;
	MyClass M2 = M1;
	M2.print();
	return 0;
}

拷贝构造函数需要加上conts和不加const的区别

这里是有一点难度的

接下来 我们给出一个代码,但是这个代码是一个典型的错误代码:此时我们发现是报错的

这里解决报错的原因需要在拷贝构造函数上面加上const,因为这里存在权限放大的行为,之前我们就说过权限可以缩小,但是不能放大。

这里就产生了权限放大的问题,所以我们需要解决权限放大的问题

接下来我们上正确的代码

class MyClass
{
public:
	MyClass(MyClass* M);
	MyClass(int year, int month, int day);
	MyClass(const MyClass& M);
	//打印函数的实现
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
//构造函数
MyClass::MyClass(int year = 1999, int month = 2, int day = 28)
{
	_year = year;
	_month = month;
	_day = day;
	cout << "MyClass构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}
//拷贝构造,和指针的联合实现,这里就会导致这里只是一个普通的构造函数
//MyClass::MyClass(MyClass* M)
//{
//	_year = M->_year;
//	_month = M->_month;
//	_day = M->_day;
//}
//拷贝构造的实现
MyClass::MyClass(const MyClass& M)
{
	_year = M._year;
	_month = M._month;
	_day = M._day;
}


//拷贝构造函数的实现
class Date
{
public:
	//构造函数的实现
	Date();
	//拷贝构造的实现
	Date(const Date& d);

	//打印函数的实现
	void print();
private:
	int _year;
	int _month;
	int _day;
};

//构造函数的实现
Date::Date()
{
	_year = 2000;
	_month = 2;
	_day = 28;
	cout << "构造函数的实现";
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

//拷贝构造的实现
//Date::Date(Date* const this, Date& d)
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl << endl;
}

void Func(Date& d)
{
	cout << "拷贝构造函数的调用" << endl;
	d.print();
}

Date F()
{
	Date d1;
	return d1;
}

MyClass f()
{
	MyClass M1(12, 12, 12);
	return M1;
}
int main()
{
	//构造函数的实现
	Date d1;
	
	//拷贝构造的实现:1
	Date d2(d1);
	d2.print();

	//拷贝构造的实现:2
	Date d3 = d2;
	d3.print();


	//传值传参的调用
	Func(d3);

	拷贝构造和指针的联合实现
	MyClass M1(2000, 1, 1);//初始化2000
	MyClass M2;//默认初始化 1999
	MyClass M3 = M1;
	//M2.print();
	M3.print();

	//const在拷贝构造函数里面的使用
	cout << "拷贝构造const的实现"<<endl;
	MyClass M4 = f();
	Date d4 = F();

	return 0;
}

运算符重载

概念概述

  • 当运算符被用于类类型的对象时,C++ 语言允许我们通过运算符重载的形式指定新的含义。C++ 规定类类型对象使用运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
  • 运算符重载是具有特殊名字的函数,他的名字是由 operator 和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
  • 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
  • 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的 this 指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
  • 不能通过连接语法中没有的符号来创建新的操作符:比如 operator@。
  • .*   ::   sizeof   ?:   . 注意以上 5 个运算符不能重载。(选择题里面常考,大家要记一下)
  • 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y)。
  • 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如 Date 类重载 operator - 就有意义,但是重载 operator * 就没有意义(因为:日期之间的乘法运算在现实生活中没有一个直观且被广泛接受的含义。不像减法运算可以表示两个日期之间的时间间隔,乘法运算很难找到一个符合日期概念的自然解释)。
  • 重载 ++ 运算符时,有前置 ++ 和后置 ++,运算符重载函数名都是 operator++,无法很好的区分。C++ 规定,后置 ++ 重载时,增加一个 int 形参,跟前置 ++ 构成函数重载,方便区分。
  • 重载 <<(流插入) 和 >>(流提取) 时,需要重载为全局函数,因为重载为成员函数,this 指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象 <<cout,不符合使用习惯和可读性。重载为全局函数把 ostream/istream 放到第一个形参位置就可以了,第二个形参位置当类类型对象。

一元二元三元运算符:

一、一元运算符

 
  1. 算术运算符:
    • 正号(+):例如+a,一般对数值类型起作用,通常意义不大,因为数值默认就是正的。
    • 负号(-):如-b,用于取负值。
  2. 逻辑运算符:
    • 逻辑非(!):例如!c,对布尔值进行取反操作,如果操作数为 true,则结果为 false;如果操作数为 false,则结果为 true
  3. 自增自减运算符:
    • 前置自增(++a):先将变量的值加 1,然后再使用变量的值。
    • 后置自增(a++):先使用变量的值,然后再将变量的值加 1。
    • 前置自减(--a):先将变量的值减 1,然后再使用变量的值。
    • 后置自减(a--):先使用变量的值,然后再将变量的值减 1。
  4. 地址运算符:
    • 取地址符(&):例如&d,用于获取变量的内存地址。
  5. 间接寻址运算符:
    • 解引用符(*):如果有一个指针变量 p*p 用于访问指针所指向的对象。
 

二、二元运算符

 
  1. 算术运算符:
    • 加法(+):如 a + b,用于数值相加或字符串连接(对于 C++ 中的 std::string 对象)。
    • 减法(-):如 a - b,用于数值相减。
    • 乘法(*):如 a * b,用于数值相乘。
    • 除法(/):如 a / b,用于数值相除。
    • 取余(%):如 a % b,用于求整数除法的余数。
  2. 位运算符:
    • 按位与(&):如 a & b,对两个操作数的每一位进行与操作。
    • 按位或(|):如 a | b,对两个操作数的每一位进行或操作。
    • 按位异或(^):如 a ^ b,对两个操作数的每一位进行异或操作。
    • 左移(<<):如 a << b,将 a 的二进制表示向左移动 b 位。
    • 右移(>>):如 a >> b,将 a 的二进制表示向右移动 b 位。
  3. 关系运算符:
    • 等于(==):如 a == b,判断两个操作数是否相等。
    • 不等于(!=):如 a!= b,判断两个操作数是否不相等。
    • 大于(>):如 a > b,判断 a 是否大于 b
    • 小于(<):如 a < b,判断 a 是否小于 b
    • 大于等于(>=):如 a >= b,判断 a 是否大于等于 b
    • 小于等于(<=):如 a <= b,判断 a 是否小于等于 b
  4. 逻辑运算符:
    • 逻辑与(&&):如 a && b,当且仅当两个操作数都为 true 时,结果为 true
    • 逻辑或(||):如 a || b,当至少一个操作数为 true 时,结果为 true
  5. 赋值运算符:
    • 简单赋值(=):如 a = b,将 b 的值赋给 a
    • 复合赋值(如 +=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=):例如 a += b 相当于 a = a + b
 

三、三元运算符(条件运算符)

 

条件运算符(?:),例如 a? b : c,如果 a 为 true,则结果为 b;如果 a 为 false,则结果为 c

内置类型和自定义类型

内置类型(简单类型):

一般情况下系统对于内置类型,直接就有对应的指令,会自动识别进行比较,核心就是比较,这里就是i和j直接进行比较 存到ret里面返回

自定义类型(按照你自己的比较方式,按照自己的需求,进行比较):

1,自定义类型不能直接转换成指令,就不能直接进行比较。尤其是像日期类里面的,年 月 日的比较,更是无法直接进行比较,你是需要返回更大的日期,还是更小的,你是需要加减,多少进位一次,这些都不是编译器决定的,是右你自己决定的。编译器不能直接进行识别。

2,内置类型比较简单,可以直接转换识别,但是自定义类型就不是

3,为了可以自己对自定义类型进行比较,语法结构就是:operator ➕运算符

自定义类型运算符重载的使用

原因:

日期类的比较日期,这里会存在一个问题,就是类外面无法访问私有变量,所以这里我们有三种解决办法

  • get函数
  • 重载为类的成员函数
  • 友元函数

这里我们以重载为成员函数为例子进行举例,至于经常用到的友元函数,类的第三章我们会讲到:

这里还有一个关键点,我们直接上代码(这里发现报错)

  

原因:可以放到类里面,重载为成员函数,但是直接拷贝放到类里面,会导致报错,因为隐藏了this指针,隐藏的this指针会指向第一个参数

  

解决:

但是此时调用,从全局调用,变成了成员函数的调用,调用方式发生了改变

此时d1传给了this,d2传给了d

试验一下 发现没有问题

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给
	Date(Date& d);
	void print();
	bool operator<(const Date& d);

	//比较日期大小(重载为成员函数)
private:
	int _year;
	int _month;
	int _day;
};

//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{
	//比较年
	if (this->_year < d._year)
	{
		return true;
	}
	else
	{
		if (this->_year == d._year && this->_month < d._month)//比较月
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日
		{
			return true;
		}
	}
	return false;
}



//.cpp测试文件
//运算符重载,比较大小
int main()
{
	//构造和拷贝构造函数的使用
	Date d1(1000, 2, 1);
	d1.print();
	Date d2 = d1;
	d2.print();

	//比较年月日
	Date d3(999, 1, 1);
	bool ret1 = d1.operator<(d3);
	cout << ret1 << endl;

	Date d4(1100, 1, 1);
	bool ret2 = d1.operator<(d4);
	cout << ret2 << endl;
	return 0;
}

重载成员函数至少有一个类类型的形参

前置++和后置++的区分

在C++里面,d++和++d,在汇编层,其实都是一样的,都是,operater++,所以是无法是分辨的

所以我们需要在书写的时候做出分辨

这里重载的函数,函数名是一样的

前置++,和后置++,运算符的复用

前置++,直接进行复用

后置++

1,保存之前的数值(拷贝下来)

2,然后进行++

3,最后返回++之前的数值

总结

注意,

在 C++ 中,后置 ++ 运算符重载函数的参数必须是 int 类型,这是 C++ 语言的规定,不能使用 double 或其他类型。

这样规定的原因主要是为了保持语言的一致性和可识别性。编译器通过参数为 int 来区分前置 ++ 和后置 ++ 的重载版本。如果允许使用其他类型,会使编译器难以确定到底是哪种 ++ 操作,增加了语言的复杂性和不确定性。

传递 int 类型参数通常只是作为一个占位符,实际在函数实现中一般不会用到这个参数的值,它的存在仅仅是为了满足语法要求以区分前置和后置自增操作。所以一般情况下,都是默认传递 int 类型参数,而不是 0 或者 1 这样的特定值,并且也不能传递 double 等其他类型。

代码的实现:

这里声明一下,上面图片的讲解的代码,是实现+=的,所以可以直接使用this+=1,这里并没有实现+=的函数重载,所以这里我们就会跑到对象里面实现day的+1

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给
	Date(Date& d);
	void print();
	bool operator<(const Date& d);
	Date& operator++();
	Date& operator++(int);
	//比较日期大小(重载为成员函数)
private:
	int _year;
	int _month;
	int _day;
};


//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{
	//比较年
	if (this->_year < d._year)
	{
		return true;
	}
	else
	{
		if (this->_year == d._year && this->_month < d._month)//比较月
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日
		{
			return true;
		}
	}
	return false;
}

//前置++
Date& Date::operator++()
{
	this->_day += 1;
	return *this;
}
//后置++
Date& Date::operator++(int)
{
	Date tmp = *this;
	this->_day += 1;
	return tmp;
}


//.cpp测试文件
//运算符重载,比较大小
int main()
{
	//构造和拷贝构造函数的使用
	Date d1(1000, 2, 1);
	d1.print();
	Date d2 = d1;
	d2.print();

	//比较年月日
	Date d3(999, 1, 1);
	bool ret1 = d1.operator<(d3);
	cout << ret1 << endl;

	Date d4(1100, 1, 1);
	bool ret2 = d1.operator<(d4);
	cout << ret2 << endl;

	//前置++和后置++
	cout << "前置++和后置++" << endl;
	d4.print();//1100 ,1,1
	
	d4.operator++();
	d4.print();

	Date ret = d4.operator++(1);
	ret.print();
	d4.print();

	return 0;
}

赋值运算符重载

概念概述

赋值运算符重载的特点:

  1. 成员函数:赋值运算符重载必须定义为类的成员函数。
  2. 参数:建议将参数声明为const类型的类引用,以避免不必要的拷贝。
  3. 返回值:应有返回值,且建议为当前类类型的引用,这样可以支持连续赋值操作,并提高效率。

编译器自动生成的赋值运算符:

  • 如果没有显式实现赋值运算符重载,编译器会提供一个默认实现。
  • 默认赋值运算符对内置类型成员变量执行值拷贝或浅拷贝。
  • 对自定义类型成员变量,会调用其赋值运算符重载函数。

特定情况下的赋值运算符重载:

  • 对于像Date这样只有内置类型成员的类,编译器自动生成的赋值运算符通常足够使用。
  • 对于像Stack这样包含指向资源的成员的类,需要自定义赋值运算符以实现深拷贝。
  • 对于像MyQueue这样包含自定义类型成员的类,如果这些成员的赋值运算符已经正确实现,通常不需要为MyQueue显式实现赋值运算符重载。

额外技巧:

  • 如果一个类显式实现了析构函数并释放资源,通常也需要显式实现赋值运算符重载,以确保资源被正确管理。

赋值运算符重载的使用以及注意事项

1,有返回值,建议写成const类类型的引用。因为C++规定类类型的传值传参,会调用拷贝构造,传递引用会减少拷贝(提高效率)

2,连续赋值的返回值

d3=d1;

返回值是d3,所以我们写代码的时候,要注意返回值,很可能是this

第一次赋值

第二次赋值

3,当返回的节点是d2,如何实现返回d2(用this,this本身指向的就是返回值)(这里注意:有返回值就支持连续赋值,但是返回的时候,需要注意)

注意:这里返回是可以用引用返回的,因为赋值运算符重载是两个已有的对象。出去作用域是依旧存在的(这里代码是传值返回,会增加拷贝)

4,这里还有一个问题,就是d1可以复制給給d1,所以为了防止自己給自己赋值 ,会进行判断

代码实现

//.h文件
//运算符重载,比较大小的实现,不在类里面实现
class Date
{
public:
	Date(int year = 1000, int month = 1, int day = 1);//声明给,定义不给
	Date(Date& d);
	void print();

	bool operator<(const Date& d);

	//比较日期大小(重载为成员函数)
	Date& operator++();
	Date& operator++(int);

	//赋值运算符重载(这里是可以加上const的,因为这里改变的是this不是d)
	Date& operator=(const Date& d);

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


//.cpp实现文件
#include"类和对象的通篇实现.h"
//构造函数的实现(全缺省构造函数的实现)
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
//拷贝构造的实现
Date::Date(Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
//打印函数的实现
void Date::print()
{
	cout << _year << "/" << _month << "/" << _day << endl;
}

//日期类的比较日期的大小,this-> < d.year 此时返回
bool Date::operator<(const Date& d)
{
	//比较年
	if (this->_year < d._year)
	{
		return true;
	}
	else
	{
		if (this->_year == d._year && this->_month < d._month)//比较月
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month && this->_day < d._day)//比较日
		{
			return true;
		}
	}
	return false;
}

//前置++
Date& Date::operator++()
{
	this->_day += 1;
	return *this;
}
//后置++
Date& Date::operator++(int)
{
	Date tmp = *this;
	this->_day += 1;
	return tmp;
}

//赋值运算符重载(这里是可以加上const的,因为这里改变的是this不是d)
Date& Date::operator=(const Date& d)
{
	//地址不一样才进行赋值
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}





//.cpp测试文件
//运算符重载,比较大小
int main()
{
	//构造和拷贝构造函数的使用
	Date d1(1000, 2, 1);
	d1.print();
	Date d2 = d1;
	d2.print();

	//比较年月日
	Date d3(999, 1, 1);
	bool ret1 = d1.operator<(d3);
	cout << ret1 << endl;

	Date d4(1100, 1, 1);
	bool ret2 = d1.operator<(d4);
	cout << ret2 << endl;

	//前置++和后置++
	cout << "前置++和后置++" << endl;
	d4.print();//1100 ,1,1
	
	d4.operator++();
	d4.print();

	Date ret = d4.operator++(1);
	ret.print();
	d4.print();


	//赋值运算符重载 
	cout << "赋值运算符重载" << endl;
	Date d5(1, 1, 1);
	d5.print();
	d5 = d4;//这样写也是对的
	//d5.operator=(d4);//这样写也是对的
	d5.print();
	 
	return 0;
}

赋值运算符重载什么时候需要自己实现

注意:默认的赋值运算符重载也会完成浅拷贝(有资源,就需要自己完成赋值运算符重载)

1,内置类型不指向什么资源,不需要自己实现,编译器自动生成的就可以实现

2,栈和深拷贝,都需要自己写,和拷贝构造非常相似

3,如果写了析构函数,那么赋值运算符重载就需要自己写

几个函数之间进行对比

1、构造一般都需要自己写,自己传参定义初始化

2、析构,构造时有资源申请(如malloc或者fopen)等,就需要显示写析构函数

3、拷贝构造和赋值重载,显示写了析构,内部管理资源,就需要显示实现深拷贝

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

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

相关文章

【CSS in Depth 2 精译_034】5.4 Grid 网格布局的显式网格与隐式网格(下)

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…

ACM MM24 | Hi3D: 3D生成领域再突破!新视角生成和高分辨率生成双SOTA(复旦智象等)

文章链接&#xff1a;https://arxiv.org/pdf/2409.07452 Github 链接&#xff1a;https://github.com/yanghb22-fdu/Hi3D-Official 亮点直击 本文提出了高分辨率图像到3D模型&#xff08;Hi3D&#xff09;&#xff0c;这是一种基于视频扩散的新范式&#xff0c;将单个图像重新定…

计算机毕业设计python+spark知识图谱房价预测系统 房源推荐系统 房源数据分析 房源可视化 房源大数据大屏 大数据毕业设计 机器学习

《PythonSpark知识图谱房价预测系统》开题报告 一、研究背景与意义 随着城市化进程的加速和房地产市场的不断发展&#xff0c;房价成为影响人们生活质量的重要因素之一。准确预测房价不仅有助于政府制定科学的房地产政策&#xff0c;还能为开发商提供市场参考&#xff0c;同时…

NLP-transformer学习:(7)evaluate实践

NLP-transformer学习&#xff1a;&#xff08;7&#xff09;evaluate 使用方法 打好基础&#xff0c;为了后面学习走得更远。 本章节是单独的 NLP-transformer学习 章节&#xff0c;主要实践了evaluate。同时&#xff0c;最近将学习代码传到&#xff1a;https://github.com/Mex…

c++类与对象一

C类与对象(一) 面向对象初步认识 在c语言中&#xff0c;编程是面向过程编程&#xff0c;注重求解问题列出过程&#xff0c;然后调用函数求解问题。 在日常生活中。我们经常会遇到面向过程的问题 手洗衣服就是面向过程 而C是基于面向对象的。关注的是对象&#xff0c;把事情…

SpringSecurity -- 入门使用

文章目录 什么是 SpringSesurity &#xff1f;细节使用方法 什么是 SpringSesurity &#xff1f; 在我们的开发中&#xff0c;安全还是有些必要的 用 拦截器 和 过滤器 写代码还是比较麻烦。 SpringSecurity 是 SpringBoot 的底层安全默认选型。一般我们需要认证和授权&#xf…

【Finetune】(三)、transformers之P-Tuning微调

文章目录 0、P-Tuning基本原理1、代码实战1.1、导包1.2、加载数据集1.3、数据集预处理1.4、创建模型1.5、P-tuning*1.5.1、配置文件1.5.2、创建模型 1.6、配置训练参数1.7、创建训练器1.8、模型训练1.9、模型推理 0、P-Tuning基本原理 P-Tuning的基本思想是在prompt-tuning的基…

Spring Boot管理用户数据

目录 学习目标前言Thymeleaf 模板JSON 数据步骤 1: 创建 Spring Boot 项目使用 Spring Initializr 创建项目使用 IDE 创建项目 步骤 2: 添加依赖步骤 3: 创建 Controller步骤 4: 新建index页面步骤 5: 运行应用程序 表单提交步骤 1: 添加 Thymeleaf 依赖在 Maven 中添加依赖 步…

Vue3.3新特性defineModel

defineModel的使用: defineModel选项可以帮我们省去很多麻烦 不仅需要上述操作&#xff0c;还需要进行一定的配置&#xff1a; 在vite.config.js中进行配置 defineModel是一个宏&#xff0c;所以不需要从vue中import导入&#xff0c;直接使用就可以了。这个宏可以用来声明一个…

Java之线程篇六

目录 CAS CAS伪代码 CAS的应用 实现原子类 实现自旋锁 CAS的ABA问题 ABA问题导致BUG的例子 相关面试题 synchronized原理 synchronized特性 加锁过程 相关面试题 Callable 相关面试题 JUC的常见类 ReentrantLock ReentrantLock 和 synchronized 的区别: 原…

Android 新增目录怎么加入git

工作中会遇到android系统源码系统增加第三方功能支持&#xff0c;需要增加目录&#xff0c;那么这个目录怎么提交到服务器上去呢&#xff1f;接下来我们就看下这个问题的解决 一服务器创建仓库 一个新的目录增加到仓库中&#xff0c;需要仓库有对应的仓库地址&#xff0c;这个让…

小记编程语言浮点精度问题

注意&#xff1a; 本文内容于 2024-09-15 20:21:12 创建&#xff0c;可能不会在此平台上进行更新。如果您希望查看最新版本或更多相关内容&#xff0c;请访问原文地址&#xff1a;小记编程语言浮点精度问题。感谢您的关注与支持&#xff01; 浮点数在计算机中不能精确表示所有…

docker启动mysql未读取my.cnf配置文件问题

描述 在做mysql主从复制配置两台mysql时&#xff0c;从节点的my.cnf配置为&#xff1a; [mysqld] datadir /usr/local/mysql/slave1/data character-set-server utf8 lower-case-table-names 1 # 主从复制-从机配置# 从服务器唯一 ID server-id 2 # 启用中继日志 relay-l…

【小沐学CAD】3ds Max常见操作汇总

文章目录 1、简介2、二次开发2.1 C 和 3ds Max C SDK2.2 NET 和 3ds Max .NET API2.3 3ds Max 中的 Python 脚本2.4 3ds Max 中的 MAXScript 脚本 3、快捷键3.1 3Dmax键快捷键命令——按字母排序3.2 3dmax快捷键命令——数字键3.3 3dmax功能键快捷键命令3.4 3Dmax常用快捷键——…

对网页聊天项目进行性能测试, 使用JMeter对于基于WebSocket开发的webChat项目的聊天功能进行测试

登录功能 包括接口的设置和csv文件配置 ​​​​​​ 这里csv文件就是使用xlsx保存数据, 然后在浏览器找个网址转成csv文件 注册功能 这里因为需要每次注册的账号不能相同, 所以用了时间函数来当用户名, 保证尽可能的给正确的注册数据, 时间函数使用方法如下 这里输入分钟, 秒…

肝内胆管癌中三级淋巴结构分布与临床预后的相关性研究|文献精析·24-09-22

小罗碎碎念 这篇文章是关于肝内胆管癌&#xff08;intrahepatic cholangiocarcinoma, iCCA&#xff09;中三级淋巴结构&#xff08;tertiary lymphoid structures, TLSs&#xff09;的分布、密度及其对临床结果的预测价值的研究。 作者类型作者姓名单位名称&#xff08;中文&a…

数据结构——串的模式匹配算法(BF算法和KMP算法)

算法目的&#xff1a; 确定主串中所含子串&#xff08;模式串&#xff09;第一次出现的位置&#xff08;定位&#xff09; 算法应用&#xff1a; 搜索引擎、拼写检查、语言翻译、数据压缩 算法种类&#xff1a; BF算法&#xff08;Brute-Force&#xff0c;又称古典的…

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解

【洛谷】P10417 [蓝桥杯 2023 国 A] 第 K 小的和 的题解 题目传送门 题解 CSP-S1 补全程序&#xff0c;致敬全 A 的答案&#xff0c;和神奇的预言家。 写一下这篇的题解说不定能加 CSP 2024 的 RP 首先看到 k k k 这么大的一个常数&#xff0c;就想到了二分。然后写一个判…

《深入理解JAVA虚拟机(第2版)》- 第13章 - 学习笔记【终章】

第13章 线程安全与锁优化 13.1 概述 面向过程的编程思想 将数据和过程独立分开&#xff0c;数据是问题空间中的客体&#xff0c;程序代码是用来处理数据的&#xff0c;这种站在计算机角度来抽象和解决问题的思维方式&#xff0c;称为面向对象的编程思想。 面向对象的编程思想…

一劳永逸:用脚本实现夸克网盘内容自动更新

系统环境&#xff1a;debian/ubuntu 、 安装了python3 原作者项目&#xff1a;https://github.com/Cp0204/quark-auto-save 感谢 缘起 我喜欢看电影追剧&#xff0c;会经常转存一些资源到夸克网盘&#xff0c;电影还好&#xff0c;如果是电视剧&#xff0c;麻烦就来了。 对于一…