友元
在程序里,有些私有属性也能让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元的关键字为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<<endlclass 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,为NULLCPRINT(p1);Mydiv p2(p1);//拷贝p1到一个新的对象p2,但p1中指针为NULL,因此p2中指针的值也为NULLCPRINT(p1);CPRINT(p2);Mydiv p3(666);//构造p3并传值666,对象中指针new了一块区域用来存放CPRINT(p3);p1 = p3 = p2 = p2;//从右到左分别将p2赋值给p2,p3,p1,最终对象中指针全指向了NULLCPRINT(p1);CPRINT(p2);CPRINT(p3);Mydiv p4(666);//构造p4并传值666,对象中指针new了一块区域用来存放p1 = p2 = p4;//从右到左分别将p4赋值给p2,p1,通过深拷贝的方式将666赋值给p1,p2CPRINT(p1);CPRINT(p2);CPRINT(p4);Mydiv p5(p1);//将p1的值666拷贝给新构造的p5CPRINT(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;
}