友元
在程序里,有些私有属性也能让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为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++中,前缀形式和后缀形式的++
运算符是通过运算符重载来实现的,它们使用不同的函数签名来区分。下面我会解释为什么不同的函数签名能够达到这个效果。
-
前缀形式的
++
运算符重载函数签名为MyInteger& operator++()
:- 返回类型是一个引用(
MyInteger&
),表示该运算符重载函数直接在原对象上进行自增操作,并返回增加后的对象的引用。 - 函数的形参列表为空,因为前缀形式不需要接收额外的参数。
- 返回类型是一个引用(
-
后缀形式的
++
运算符重载函数签名为MyInteger operator++(int)
:- 返回类型是一个新的
MyInteger
对象(非引用),表示该运算符重载函数在原对象的副本上进行自增操作,并返回增加之前的副本对象。 - 函数的形参列表中包含一个额外的 int 参数(此参数没有实际用途),用于区分前缀和后缀形式的运算符重载函数。
- 返回类型是一个新的
通过在函数签名中加入int参数(可能是任何类型的参数),可以实现与前缀形式的运算符进行区分,以便编译器能够正确选择调用哪个重载函数。
值得注意的是,后缀形式中的int参数是一个惯例,并没有实际作用,因为C++规定后缀形式的重载运算符必须具有该参数。这是为了能够和前缀形式的重载运算符区分开来,从而实现一致性和灵活性。
所以,operator++()
和operator++(int)
是两个不同的函数,它们根据函数签名的不同来区分前缀和后缀形式的++
运算符重载。这样,你就可以根据需要在变量前面或后面使用++
运算符。
问:什么是重载运算符?
答:重载运算符是C++语言的一个重要特性,它允许程序员为自定义类型定义操作符的行为。通过重载运算符,你可以以自定义的方式对对象进行运算,使其适应你的特定需求。以下是一些常用的重载运算符及其相应的函数签名:
-
算术运算符:
- 加法运算符:
+
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;
- 加法运算符:
-
比较运算符:
- 等于运算符:
==
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;
- 等于运算符:
-
赋值运算符:
- 简单赋值运算符:
=
ReturnType& operator=(const ClassName& obj);
- 简单赋值运算符:
-
递增递减运算符:
- 前置递增运算符:
++
ReturnType& operator++();
- 后置递增运算符:
++
(额外的int参数用于区分前置和后置形式)
ReturnType operator++(int);
- 前置递减运算符:
--
ReturnType& operator--();
- 后置递减运算符:
--
(额外的int参数用于区分前置和后置形式)
ReturnType operator--(int);
- 前置递增运算符:
-
索引访问运算符:
- 下标访问运算符:
[]
ReturnType operator[](int index) const;
- 下标访问运算符:
-
函数调用运算符:
- 函数调用运算符:
()
ReturnType operator()(Args... args);
- 函数调用运算符:
-
其他运算符:
- 成员访问运算符:
->
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;
}