第九十九天学习记录:C++核心:类和对象Ⅴ(五星重要)友元&运算符重载

本文介绍了C++中的友元技术,包括全局函数、类和成员函数做友元的三种实现方式,以及运算符重载的概念和应用,如加号、左移、递增运算符的重载。同时,讨论了赋值运算符重载的重要性,防止浅拷贝问题,并展示了如何处理堆内存中的对象。最后,提到了关系运算符和函数调用运算符的重载,以及仿函数的概念。
摘要由CSDN通过智能技术生成

友元

在程序里,有些私有属性也能让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为frirend
友元的三种实现
1、全局函数做友元

#include<iostream>
using namespace std;
#include<string>

class MyHome
{
	//goodBoy全局函数是MyHome的好朋友,可以访问MyHome中私有成员
	friend void goodBoy(MyHome *myhome);

public:
	MyHome()
	{
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};

//全局函数
void goodBoy(MyHome *myhome)
{
	cout << "好朋友全局函数正在访问" << myhome->m_SittingRoom << endl;
	cout << "好朋友全局函数正在访问" << myhome->m_BedRoom << endl;
}

void test01()
{
	MyHome myhome;
	goodBoy(&myhome); 
}

int main()
{
	test01();
	return 0;
}

在这里插入图片描述

2、类做友元

#include<iostream>
using namespace std;
#include<string>

//类做友元
class AWei
{
	//JieGe是AWei的好朋友,可以访问AWei中私有的成员
	friend class JieGe;
public:
	AWei();
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};

class JieGe
{
public:
	JieGe();
	void visit();//参观函数,访问AWei中的属性
	AWei* awei;
};

//类外写成员函数
AWei::AWei() 
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

JieGe::JieGe()
{
	//创建一个AWei对象
	awei = new AWei;
}

void JieGe::visit()
{
	cout << "JieGe正在访问" << awei->m_SittingRoom << endl;
	cout << "JieGe正在访问" << awei->m_BedRoom << endl;
}

void test01()
{
	JieGe gg;
	gg.visit();
}

int main()
{
	test01();
	return 0;
}

在这里插入图片描述

3、成员函数做友元

#include<iostream>
using namespace std;
#include<string>

class AWei;

class JieGe
{
public:
	JieGe();
	void visit();//让visit函数可以访问AWei中私有成员
	void visit2();//让visit函数不可以访问AWei中私有成员
private:
	AWei* awei;
};

class AWei
{
	//告诉编译器JieGe类下的visit成员函数作为本类的好朋友,可以访问私有成员
	friend void JieGe::visit();

public:
	AWei();
	string m_SittingRoom;//客厅
private:
	string m_BedRoom;//卧室
};

//类外写成员函数
AWei::AWei()
{
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

JieGe::JieGe()
{
	//创建一个AWei对象
	awei = new AWei;
}

void JieGe::visit()
{
	cout << "visit正在访问" << awei->m_SittingRoom << endl;
	cout << "visit正在访问" << awei->m_BedRoom << endl;
}

void JieGe::visit2()
{
	cout << "visit2正在访问" << awei->m_SittingRoom << endl;
	//cout << "visit2正在访问" << awei->m_BedRoom << endl;//无法访问AWei私有
}

void test01()
{
	JieGe gg;
	gg.visit();
	gg.visit2();
}

int main()
{
	test01();
	return 0;
}

在这里插入图片描述

运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

加号运算符重载

作用:实现两个自定义数据类型相加的运算

#include<iostream>
using namespace std;

//加号运算符重载
//1、成员函数重载+号

class Person
{
public:
	int m_A;
	int m_B;
	Person()
	{
		cout << "Person的构造构造函数调用" << endl;
	}
	Person(const Person &p)
	{
		m_A = p.m_A;
		m_B = p.m_B;
		cout << "Person的拷贝构造函数调用" << endl;
	}
	//Person operator+(Person &p)
	//{
	//	Person tmp;
	//	tmp.m_A = this->m_A + p.m_A;
	//	tmp.m_B = this->m_B + p.m_B;
	//	return tmp;
	//}
};

//2、全局函数重载+号

Person operator+(Person &p1, Person &p2)
{
	Person tmp;
	tmp.m_A = p1.m_A + p2.m_A;
	tmp.m_B = p1.m_B + p2.m_B;
	return tmp;
}

//函数重载的版本
Person operator+(Person &p1, int num)
{
	Person tmp;
	tmp.m_A = p1.m_A + num;
	tmp.m_B = p1.m_B + num;
	return tmp;
}

int main()
{
	Person p1;
	p1.m_A = 10;
	p1.m_B = 10;
	Person p2;
	p2.m_A = 10;
	p2.m_B = 10;
	//成员函数重载本质调用
	//Person p3 = p1.operator+(p2);
	//全局函数重载本质调用
	//Person p3 = operator+(p1, p2);
	Person p3 = p1+p2;
	cout << "p3.m_A==" << p3.m_A << endl;
	cout << "p3.m_B==" << p3.m_B << endl;
	Person p4 = p3 + 10;
	cout << "p4.m_A==" << p4.m_A << endl;
	cout << "p4.m_B==" << p4.m_B << endl;
	//运算符重载也可以发生函数重载
	return 0;
}

在这里插入图片描述
注意:
1、内置的运算符无法重载
2、不要滥用

左移运算符重载

作用:可以输出自定义数据类型

#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

class Person
{
	friend ostream& operator<<(ostream &cout, Person& p);

public:
	Person(int a, int b)
	{
		m_A = a;
		m_B = b;
	}

private:

	//利用成员函数重载 左移运算符 p.operator<<(cout) 简化版本p<<cout
	//不会利用成员函数重载<<运算符,因为无法实现cout在左侧
	//void operator<<( cout )
	//{

	//}
	int m_A;
	int m_B;
};

//只能利用全局函数重载左移运算符
ostream& operator<<(ostream &cout, Person& p)//本质 operator<<(cout,p)简化cout<<p
{
	cout << "m_A=" << p.m_A << " m_B=" << p.m_B;
	return cout;
}

void test01()
{
	Person p(10,10);
	cout << p <<endl;
}

int main()
{
	test01();
	return 0;
}

在这里插入图片描述
对于这段代码中,为什么重载函数返回ostream& 之后,cout << p 后面就可以加<<endl;
是因为cout << p之后返回的就是一个cout的引用。虽然不能滥用重载,但是为了验证cout的可行性,甚至可以将代码写成如下:

#include<iostream>
using namespace std;

ostream& operator<<(ostream &out, ostream &cout2)
{
	out << "秀儿";
	return out;
}

int main()
{
	cout << cout << endl;
	return 0;
}

输出:在这里插入图片描述
原本cout<<cout<<endl;肯定是会报错的,但是我们通过重载函数让cout<<cout能够输出一个字符串,并返回一个cout引用。意味着cout<<cout之后依然能够继续接endl或者是其他的类型数据。个人猜想cout函数应该本来就能够接受各种有官方定义数据类型并返回一个cout。

总结:重载左移运算符配合友元可以实现输出自定义数据类型

递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

#include<iostream>
using namespace std;

class MyInteger
{
	friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
	MyInteger()
	{
		m_Num = 0;
	}

	//重载前置++运算符
	MyInteger& operator++()
	{
		m_Num++;
		return *this;
	}

	//重载后置++运算符
	//int代表占位参数,可以用于区分前置和后置递增
	MyInteger operator++(int)
	{
		//先返回结果
		MyInteger temp = *this;
		//后递增
		m_Num++;
		//最后将记录结果做返回
		return temp;
	}

private:
	int m_Num;
};

//重载<<运算符
ostream& operator<<(ostream& cout, MyInteger myint)
{
	cout << myint.m_Num;
	return cout;
}

void test01()
{
	MyInteger myint;
	cout << ++myint << endl;
}

void test02()
{
	MyInteger myint;
	cout << myint++ << endl;
	cout << myint << endl;
}

int main()
{
	//test01();
	test02();
	return 0;
}

问:operator++()为什么不带int就可以将++写在前面作为前置,而写成operator++(int)却能将++写在后面作为后缀?
答:
在C++中,前缀形式和后缀形式的++运算符是通过运算符重载来实现的,它们使用不同的函数签名来区分。下面我会解释为什么不同的函数签名能够达到这个效果。

  1. 前缀形式的++运算符重载函数签名为MyInteger& operator++()

    • 返回类型是一个引用(MyInteger&),表示该运算符重载函数直接在原对象上进行自增操作,并返回增加后的对象的引用。
    • 函数的形参列表为空,因为前缀形式不需要接收额外的参数。
  2. 后缀形式的++运算符重载函数签名为MyInteger operator++(int)

    • 返回类型是一个新的MyInteger对象(非引用),表示该运算符重载函数在原对象的副本上进行自增操作,并返回增加之前的副本对象。
    • 函数的形参列表中包含一个额外的 int 参数(此参数没有实际用途),用于区分前缀和后缀形式的运算符重载函数。

通过在函数签名中加入int参数(可能是任何类型的参数),可以实现与前缀形式的运算符进行区分,以便编译器能够正确选择调用哪个重载函数。

值得注意的是,后缀形式中的int参数是一个惯例,并没有实际作用,因为C++规定后缀形式的重载运算符必须具有该参数。这是为了能够和前缀形式的重载运算符区分开来,从而实现一致性和灵活性。

所以,operator++()operator++(int)是两个不同的函数,它们根据函数签名的不同来区分前缀和后缀形式的++运算符重载。这样,你就可以根据需要在变量前面或后面使用++运算符。

问:什么是重载运算符?
答:重载运算符是C++语言的一个重要特性,它允许程序员为自定义类型定义操作符的行为。通过重载运算符,你可以以自定义的方式对对象进行运算,使其适应你的特定需求。以下是一些常用的重载运算符及其相应的函数签名:

  1. 算术运算符:

    • 加法运算符:+
      ReturnType operator+(const ClassName& obj) const;
    • 减法运算符:-
      ReturnType operator-(const ClassName& obj) const;
    • 乘法运算符:*
      ReturnType operator*(const ClassName& obj) const;
    • 除法运算符:/
      ReturnType operator/(const ClassName& obj) const;
    • 取模运算符:%
      ReturnType operator%(const ClassName& obj) const;
  2. 比较运算符:

    • 等于运算符:==
      bool operator==(const ClassName& obj) const;
    • 不等于运算符:!=
      bool operator!=(const ClassName& obj) const;
    • 大于运算符:>
      bool operator>(const ClassName& obj) const;
    • 小于运算符:<
      bool operator<(const ClassName& obj) const;
    • 大于等于运算符:>=
      bool operator>=(const ClassName& obj) const;
    • 小于等于运算符:<=
      bool operator<=(const ClassName& obj) const;
  3. 赋值运算符:

    • 简单赋值运算符:=
      ReturnType& operator=(const ClassName& obj);
  4. 递增递减运算符:

    • 前置递增运算符:++
      ReturnType& operator++();
    • 后置递增运算符:++(额外的int参数用于区分前置和后置形式)
      ReturnType operator++(int);
    • 前置递减运算符:--
      ReturnType& operator--();
    • 后置递减运算符:--(额外的int参数用于区分前置和后置形式)
      ReturnType operator--(int);
  5. 索引访问运算符:

    • 下标访问运算符:[]
      ReturnType operator[](int index) const;
  6. 函数调用运算符:

    • 函数调用运算符:()
      ReturnType operator()(Args... args);
  7. 其他运算符:

    • 成员访问运算符:->
      ReturnType operator->();
    • 类型转换运算符:
      operator Type();

这只是一些常见的重载运算符示例,你可以根据自己的需要对其他运算符进行重载。重载运算符的语法和具体实现方式需要遵循一定的规则,确保符合预期的行为,并避免出现二义性或违反常规的语义。

赋值运算符重载

C++编译器至少给一个类添加4个函数
1、默认构造函数(无参,函数体为空)
2、默认析构函数(无参,函数体为空)
3、默认拷贝函数,对属性进行值拷贝
4、赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

#include<iostream>
using namespace std;

class Person
{

public:
	Person(int age)
	{
		m_Age = new int(age);
	}

	~Person()
	{
		if (m_Age != NULL)
		{
			delete m_Age;
			m_Age = NULL;
		}
	}

	//重载 赋值运算符
	Person& operator=(Person& p)
	{
		cout << "执行了重载赋值运算"  << endl;
		//编译器是提供的浅拷贝
		//m_Age=p.m_Age

		//应该先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
		if (m_Age != NULL)
		{
			if (m_Age == p.m_Age)
			{
				cout << "=两边是同一个对象" << endl;
				return *this;
			}
			else
			{
				cout << "delete了m_Age" << endl;
				delete m_Age;
				m_Age = NULL;
			}
		}
		//深拷贝
		m_Age = new int(*p.m_Age);
		return *this;
	}

	Person(const Person &p)
	{
		cout << "Person拷贝构造函数调用" << endl;
		//m_Age=p.m_Age;编译器默认实现就是这行代码
		m_Age = new int(*p.m_Age);
	}

	int* m_Age;
};

void test01()
{
	Person p1(18);
	Person p2(20);
	//Person p3 = Person(p2);//第一种方法
	//Person p3 = p2;//第二种方法
	Person p3(p2);//第三种方法
	Person p4(30);
	cout << "马上执行p2=p1" << endl;
	p2 = p1;
	cout << "马上执行p4=p3" << endl;
	p4 = p3;
	cout << "p1的年龄为:" << *p1.m_Age << endl;
	cout << "p2的年龄为:" << *p2.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
	cout << "p4的年龄为:" << *p4.m_Age << endl;
	p4 = p3 = p2 = p1;
	cout << "p1的年龄为:" << *p1.m_Age << endl;
	cout << "p2的年龄为:" << *p2.m_Age << endl;
	cout << "p3的年龄为:" << *p3.m_Age << endl;
	cout << "p4的年龄为:" << *p4.m_Age << endl;
	p3 = p3 = p3 = p3 = p2 = p3 = p3;
	cout << "p3的年龄为:" << *p4.m_Age << endl;
}

int main()
{
	test01();
	return 0;
}

在这里插入图片描述

针对之前学习的内容,自行进行了探索,探索代码以及输出如下:

#include<iostream>
using namespace std;
#define CPRINT(X) cout<<#X<<" value is:"<<X<<endl

class Mydiv
{
	friend ostream& operator<<(ostream& out, Mydiv& mydiv);
public:
	Mydiv();
	Mydiv(int num);
	Mydiv(const Mydiv& mydiv);
	~Mydiv();
	Mydiv& operator=(Mydiv& mydiv);
	Mydiv& operator++();
	Mydiv operator++(int);
	Mydiv& operator--();
	Mydiv operator--(int);

	Mydiv test02(Mydiv mydiv);
private:
	int *myinter;
};

Mydiv::Mydiv()
{
	cout << "build a NULL myinter" << endl;
	myinter = NULL;
}

Mydiv::Mydiv(int num)
{
	cout << "new Mydiv" << endl;
	myinter = new int(num);
}

Mydiv::Mydiv(const Mydiv& mydiv)
{
	if (mydiv.myinter == NULL)
	{
		cout << "copy NULL Mydiv" << endl;
		myinter = NULL;
	}
	else
	{
		cout << "copy Mydiv" << endl;
		myinter = new int(*mydiv.myinter);
	}
}

Mydiv::~Mydiv()
{
	cout << "delete Mydiv" << endl;
	if (myinter != NULL)
	{
		delete myinter;
		myinter = NULL;
	}
}

Mydiv& Mydiv::operator=(Mydiv& mydiv)
{
	if (myinter != NULL)
	{
		if (myinter == mydiv.myinter)
		{
			cout << "mydiv is equal" << endl;
			return *this;
		}
		else
		{
			cout << "rebuiding mydiv" << endl;
			delete myinter;
			myinter = NULL;
		}
		if (mydiv.myinter != NULL)
		{
			myinter = new int(*mydiv.myinter);
		}
	}
	else
	{
		if (mydiv.myinter==NULL)
		{
			cout << "mydiv is equal and NULL" << endl;
			return *this;
		}
		else
		{
			myinter = new int(*mydiv.myinter);
		}
	}
	return *this;
}

Mydiv& Mydiv::operator++()
{
	if (myinter != NULL)
	{
		(*myinter)++;
	}
	else
	{
		cout << "++ NULL fail!" << endl;
	}
	return *this;
}

Mydiv Mydiv::operator++(int)
{
	if (myinter != NULL)
	{
		Mydiv tmp(*this);
		(*myinter)++;
		return tmp;
	}
	else
	{
		cout << "NULL ++ fail!" << endl;
		return *this;
	}
}

Mydiv& Mydiv::operator--()
{
	if (myinter != NULL)
	{
		(*myinter)--;
	}
	else
	{
		cout << "NULL ++ fail!" << endl;
	}
	return *this;
}

Mydiv Mydiv::operator--(int)
{
	if (myinter != NULL)
	{
		Mydiv tmp(*this);
		(*myinter)--;
		return tmp;
	}
	else
	{
		cout << "NULL ++ fail!" << endl;
		return *this;
	}
}

Mydiv Mydiv::test02(Mydiv mydiv)
{
	cout << 3 << endl;
	return mydiv;
}

ostream& operator<<(ostream& out, Mydiv& mydiv)
{
	if (mydiv.myinter == NULL)
	{
		out << "NULL";
		return out;
	}
	out << *mydiv.myinter;
	return out;
}

void test01()
{
	Mydiv p1;//构造p1但因为没传参,对象中指针没有new,为NULL
	CPRINT(p1);
	Mydiv p2(p1);//拷贝p1到一个新的对象p2,但p1中指针为NULL,因此p2中指针的值也为NULL
	CPRINT(p1);
	CPRINT(p2);
	Mydiv p3(666);//构造p3并传值666,对象中指针new了一块区域用来存放
	CPRINT(p3);
	p1 = p3 = p2 = p2;//从右到左分别将p2赋值给p2,p3,p1,最终对象中指针全指向了NULL
	CPRINT(p1);
	CPRINT(p2);
	CPRINT(p3);
	Mydiv p4(666);//构造p4并传值666,对象中指针new了一块区域用来存放
	p1 = p2 = p4;//从右到左分别将p4赋值给p2,p1,通过深拷贝的方式将666赋值给p1,p2
	CPRINT(p1);
	CPRINT(p2);
	CPRINT(p4);
	Mydiv p5(p1);//将p1的值666拷贝给新构造的p5
	CPRINT(p5);
	CPRINT(++p5);
	CPRINT(++p3);
	CPRINT(++(++p4));
	CPRINT(--(--(--p1)));
	CPRINT(p3++);
	CPRINT(p2++);
	CPRINT(p2);
}

Mydiv test03(Mydiv pp)
{
	return pp;
}

int main()
{
	test01();
	cout << 1 << endl;//从这里到cout << 4 << endl;发现在调用a.test02时拷贝和析构被调用了两次
	Mydiv a(666);
	cout << 2 << endl;
	cout << &a << endl;
	cout << &(a.test02(a)) << endl;
	cout << 4 << endl;
	Mydiv cc(666);
	Mydiv* pp1 = &cc;
	Mydiv* pp2 = &(test03(cc));
	cout << pp1 << endl;
	cout << *pp1 << endl;
	cout << pp2 << endl;
	cout << *pp2 << endl;//*pp2的值为NULL证明了test03返回的对象在该语句执行完成后自动析构了
	return 0;
}

在这里插入图片描述

关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行对比操作

#include<iostream>
using namespace std;

//重载关系运算符

class Person
{
public:
	Person(string name, int age)
	{
		m_Name = name;
		m_Age = age;
	}

	//重载==号


	bool operator==(Person& p)
	{
		if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
		{
			return true;
		}
		return false;
	}

	bool operator!=(Person& p)
	{
		if (this->m_Name == p.m_Name&&this->m_Age == p.m_Age)
		{
			return false;
		}
		return true;
	}

	string m_Name;
	int m_Age;
};

void test01()
{
	Person p1("Tom", 18);
	Person p2("Tom", 18);
	if (p1 == p2)
	{
		cout << "p1和p2是相等的!" << endl;
	}
	else
	{
		cout << "p1和p2是不相等的!" << endl;
	}

	if (p1 != p2)
	{
		cout << "p1和p2是不相等的!" << endl;
	}
	else
	{
		cout << "p1和p2是相等的!" << endl;
	}
}

int main()
{
	test01();
	return 0;
}

函数调用运算符重载

1、函数调用运算符()也可以重载
2、由于重载后使用的方式非常像函数的调用,因此称为仿函数
3、仿函数没有固定的写法,非常灵活

#include<iostream>
using namespace std;
#include <string>

//函数调用运算符重载

class MyPrint
{
public:
	//重载函数调用运算符
	void operator()(string test)
	{
		cout << test << endl;
	}
};

void MyPrint02(string test)
{
	cout << test << endl;
}

void test01()
{
	MyPrint myPrint;
	myPrint("hello world");//仿函数
	MyPrint02("hello world");
}

class Myadd
{
public:
	int operator()(int num1, int num2)
	{
		return num1 + num2;
	}
};

void test02()
{
	Myadd myadd;
	int ret=myadd(100, 100);
	cout << "ret = " << ret << endl;

	//匿名函数对象
	cout << Myadd()(100, 100) << endl;
}

int main()
{
	test01();
	test02();
	return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值