C++核心编程——详解运算符重载
文章目录💬
- 一.运算符重载基础知识
- ①基本概念
- ②运算符重载的规则
- ③运算符重载形式
- ④运算符重载建议
- 二.常用运算符重载
- ①左移(<<)和右移(>>)运算符重载
- 1️⃣重载后函数参数是什么?
- 2️⃣重载的函数返回类型是什么?
- 3️⃣重载为哪种函数?
- 4️⃣代码实现
- ②赋值(=)运算符重载
- 知识卡片:
- ③关系运算符(== != > <)重载
- ④前置和后置(++/- -)重载
- 思考🤔
- ⑤类型转换(类型转换函数和转换构造函数)
- 1️⃣类型转换函数
- 2️⃣转换构造函数
- ⑥数组下标([])运算符重载
- ⑦指针运算符(*、 ->)重载
- 智能指针类
- ⑧函数调用()运算符重载——仿函数
一.运算符重载基础知识
C++的一大特性就是重载,重载使得程序更加简洁高效。在C++中不只函数可以重载,运算符也可以重载,运算符重载主要是面向对象之间的。
①基本概念
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
//对于基础数据类型,运算符可以有很好的效果
int a=20,b=20;
int c=a+b;
//对于自定义数据类型类创建的对象运算,是十分繁琐的
//例如:
class Maker
{
public://有参构造函数Maker(int id, int age){this->id = id;this->age = age;}public:int id;int age;
};
void test()
{Maker m1(1,19);Maker m2(2,20);Maker m3(3,17);m3=m1+m2;//err
//如果我们想要m1和m2相加赋值给m3,我们可以这样写
//m3.id=m1.id+m2.id;
//m3.age=m1.age+m2.age;
//这样写太繁琐,如果对象内部有很多成员变量,而且还是私有怎么办?
//C++用运算符重载来解决问题
}
运算符重载语法:
在C++中,使用operator关键字定义运算符重载。运算符重载语法格式如下:
函数的参数列表中参数个数取决于两个因素。
运算符是单目(一个参数)的双目(两个参数);
❤️运算符被定义为全局函数,对于单目运算符是一个参数,对于双目运算符是两个参数。
❤️被定义为类的成员函数,对于单目运算符没有参数,对于双目运算符是一个参数(因为类本身为左侧参数(this))。
这种定义很像一个普通的函数定义,只是函数的名字由关键字operator及其紧跟的运算符组成。差别仅此而已。它像任何其他函数一样也是一个函数,当编译器遇到适当的模式时,就会调用这个函数。
通过案例两个对象相加演示“+”运算符的重载:
class Maker
{
public:Maker(int id, int age){this->id = id;this->age = age;}
public:int id;int age;
};
Maker operator+(Maker &p1,Maker &p2)//2.编译器检查参数是否对应,第一个参数是加号的左边,第二参数是加号的右边
{Maker temp(p1.id + p2.id, p1.age + p2.age);return temp;
}
void test()
{Maker m1(1, 20);Maker m2(2, 22);Maker m3=m1 + m2;//1.编译器看到两个对象相加,那么编译器会去找有没有叫operator+的函数cout << "id:" << m3.id << " age:" << m3.age << endl;//多个对象相加Maker m4 = m1 + m2 + m3;cout << "id:" << m4.id << " age:" << m4.age << endl;
}
通过上面案例可以知道,重载运算符并没有改变其原来的功能,只是增加了针对自定义数据类型的运算功能,具有了更广泛的多态特征。
②运算符重载的规则
- 只能重载C++中已有的运算符,且不能创建新的运算符。例如,一个数的幂运算,试图重载“* *”为幂运算符,使用2 ** 4表示2^4是不可行的。
- 重载后运算符不能改变优先级和结合性,也不能改变操作数和语法结构。
- 运算符重载的目的是针对实际运算数据类型的需要,重载要保持原有运算符的语义,且要避免没有目的地使用运算符重载。例如,运算符“+”重载后实现相加的功能,而不会重载“+”为相减或者其他功能。
- 并非所有C++运算符都可以重载,可以重载的运算符如下图所示。其他运算符是不可以重载的,如“::”、“.”、“.*”、“?:”、sizeof、typeid等。
③运算符重载形式
运算符重载一般有三种形式:
- 重载为普通全局函数(不推荐,因为重载运算符为普通函数,只能访问类的公有成员)
- 重载为类的成员函数
- 将重载后的全局函数声明为类的友元函数
④运算符重载建议
- ✅下面的运算符只能通过类的成员函数进行重载。
=:赋值运算符
[]:下标运算符
() :函数调用运算符
->:通过指针访问类成员的运算符。
- ✅<< 和 >> 操作符最好通过类的友元函数进行重载
- ✅不要重载 && 和 || 操作符,因为无法实现短路规则
//短路规则
a&b&c只要a为假,整个式子就是假,b往后就短路了,不需要看
a|b|c只要a为真,整个式子就是真,b往后就短路了,不需要看
常规建议:
拓展:
二.常用运算符重载
①左移(<<)和右移(>>)运算符重载
C++输入输出标准库提供了“>>”和“<<”运算符执行输入、输出操作,但标准库只定义了基本数据类型的输入、输出操作,若要直接对类对象进行输入、输出,则需要在类中重载这两个运算符。
与其他运算符不同的是,输入、输出运算符只能重载成类的友元函数。“<<”和“>>”运算符重载的格式如下:
解释:
1️⃣重载后函数参数是什么?
①<<和>>是双目运算符,重载时函数需要两个参数,那么参数的类型是什么?要打印的对象类型显而易见,但cout和cin这两个对象的类型是什么呢?
cout 是 ostream 类的对象。cin是istream类的对象。ostream 、istream类、 cout 和cin都是在头文件 < iostream > 中声明的。
⚠️注意:重载的函数的参数都需要用引用,cout是ostream类的对象,cin是istream类的对象,ostream类和istream中的拷贝构造函数是私有的无法调用,如果不加引用,传参时会调用ostream类和istream类中的拷贝构造,编译会出现冲突。
2️⃣重载的函数返回类型是什么?
以cout为例,cin同理,重载的函数返回类型可以是void,但有缺点,一次只能打印一个对象,不能连续打印。
cout<<m1;✔️
cout<<m1<<m2<<m3;✖️等价于void<<m2<<m3
cout<<m1<<endl;✖️
解决方法就是返回对象为ostream &,要加引用(&),不加引用就调用ostream的拷贝构造函数,但ostream类的拷贝构造函数是私有的。
cout<<m1<<m2<<m3;等价于cout<<m2<<m3~cout<<m3;
3️⃣重载为哪种函数?
考虑到cout对象在<<运算符左边, 如果使用类的成员函数重载<<的话, 需要在ostream类中重载函数,因为ostream是标准库定义的类,我们不能更改库中代码,在左边不符合要求。
普通函数不能访问类的私有变量,直接pass掉。
所以我们选择将重载后的全局函数声明为类的友元函数。
4️⃣代码实现
class Maker
{//声明重载运算符的函数friend ostream& operator<<(ostream &out,Maker &m1);friend istream& operator>>(istream &in,Maker &m1);
public:int id;string Name;Maker(int a,int b,int c,string name){this->id=a;this->age=b;this->money=c;this->Name=name;}
private:int age;int money;
};
ostream& operator<<(ostream &out,Maker &m1)
{cout<<m1.id<<'|'<<m1.age<<'|'<<m1.money<<'|'<<m1.Name<<endl;return out;
}
istream& operator>>(istream &in,Maker &m1)
{in>>m1.id>>m1.age>>m1.money>>m1.Name;return in;
}
void test()
{Maker m1(100,18,5999,"强风吹拂");Maker m2(100,18,7999,"强风吹拂king");cout<<m1<<m2;cout<<"请重新为m1和m2对象输入数据:"<<endl;cin>>m1>>m2;cout<<m1<<m2;cout<<1000<<100.3142<<endl;//重载后基础类型,照样可以输入输出//因为左移和右移运算符基础类型本没有输入输出的能力//之所以有是因为左移和右移运算符输入输出基本数据在类内已经全部重载一遍了
}
②赋值(=)运算符重载
赋值符常常初学者的混淆。这是毫无疑问的,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用。
知识卡片:
1.编译器默认给类提供了一个默认的赋值运算符重载函数,默认的赋值运算符重载函数对成员变量进行了简单的赋值操作。
🔯例如:
class Maker
{
public:Maker(){id = 0;age = 0;}Maker(int id, int age){this->id = id;this->age = age;}
public:int id;int age;
};
void test()
{Maker m1(10, 20);Maker m2;m2 = m1;//赋值操作//默认的赋值运算符重载函数进行了简单的赋值操作cout << m2.id << " " << m2.age << endl;
}
2.区分赋值操作和拷贝构造函数,因为’=’在编程中是最基本的运算符,可以进行赋值操作,也能引起拷贝构造函数的调用。
区分的关键在于看对象有没有创建。
🔯例如:
class Student
{
public:Student(int id, int age){this->ID = id;this->Age = age;}void Printf(){cout << this->ID << this->Age << endl;}
private:int ID;int Age;
};
void test()
{Student s1(10, 20);//调用拷贝构造Student s2 = s1; s1.Printf();s2.Printf();//如果一个对象还没有被创建,则必须初始化,也就是调用构造函数//上述例子由于s2还没有初始化,所以会调用构造函数//由于s2是从已有的s1来创建的,所以只有一个选择//就是调用拷贝构造函数Student s3(101, 100);//这是赋值操作s3 = s1;s3.Printf();//由于s3已经创建,不需要再调用构造函数,这时候调用的是重载的赋值运算符
}
3.当类的成员变量有指针时,有可能会出现两个问题
一是在创建对象调用拷贝构造函数时,会出现浅拷贝问题,即一个对象的成员变量(指针)被拷贝给另一个对象的成员变量,然后两个对象的成员变量(指针)指向同一份空间,在析构函数调用时就会出现同一块空间释放2次。
解决方法深拷贝。
二是在赋值时,两个对象都已被创建,此时赋值会导致一个对象的成员变量(指针)会被覆盖,导致内存泄露。
解决方法重载=运算符。
重载=运算符解决上述的第二个问题:
class Student
{
public:Student(const char *name){pName = new char[strlen(name) + 1];strcpy(pName, name);}//防止浅拷贝Student(const Student &stu){pName = new char[strlen(stu.pName) + 1];strcpy(pName, stu.pName);}//重写赋值运算符重载函数Student &operator=(const Student &stu){//1.不能确定this->pName指向的空间是否能装下stu中的数据,所以先释放this->pName指向的空间if (this->pName != NULL){delete[] this->pName;this->pName = NULL;}//2.申请堆区空间,大小由stu决定this->pName = new char[strlen(stu.pName) + 1];//3.拷贝数据strcpy(this->pName, stu.pName);//4.返回对象本身return *this;} ~Student(){if (pName != NULL){delete[] pName;pName = NULL;}}void printStudent(){cout << "Name:" << pName << endl;}
public:char *pName;
};void test()
{Student s1("强风吹拂");Student s2("强风吹拂king");s1.printStudent();s2.printStudent();s1 = s2;//赋值操作s1.printStudent();s2.printStudent();
}
4.为什么operator=返回一个reference to *this ?
①为了实现连续赋值,赋值操作符必须返回一个引用指向操作符的左侧实参。这是你为class实现赋值操作符必须遵循的协议。这个协议不仅适用于标准的赋值形式,也适用于+=、-=、*=等等。
②如果不加引用&,对象以值的形式返回,会在返回处调用拷贝构造函数,产生一个新对象。
🔯例如:
void test()
{Student s1("a");Student s2("b");Student s3("c");s1 = s2 = s3;//s3赋值s2,s2赋值给s1//判断s3赋值给s2返回的是不是s2这个对象。cout << &(s2 = s3) << endl;cout << &s2 << endl;
}
- 一方面如果operator+返回类型不带引用,那么在返回的过程中表达式会调用拷贝构造函数,产生一个新的对象,就不是s2这个对象了。
- 另外一个方面:s1=s2=s3,赋值运算符本来的寓意,是s3赋值s2,s2赋值给s1,也就是说s2=s3这个表达式要返回s2这个对象,所以要返回引用。此处放对比图
③关系运算符(== != > <)重载
关系运算符(如“==”或“<”)也可以重载,关系运算符的重载函数返回值类型一般定义为bool类型,即返回true或false。关系运算符常用于条件判断中,重载关系运算符保留了关系运算符的原有含义。
🃏案例演示关系运算符重载:
class Student
{
private:string _id;double _score;
public:Student(string id, double score) : _id(id), _score(score){}void dis(){cout << "学号" << _id << "成绩" << _score << endl;}
//重载关系运算符friend bool operator==(const Student& st1, const Student& st2);friend bool operator!=(const Student& st1, const Student& st2);friend bool operator>(const Student& st1, const Student& st2);friend bool operator<(const Student& st1, const Student& st2);
};
bool operator==(const Student& st1, const Student& st2)
{return st1._score == st2._score;//重载“==”运算符
}
bool operator!=(const Student& st1, const Student& st2)
{return !(st1._score == st2._score);//重载“!=”运算符
}
bool operator>(const Student& st1, const Student& st2)
{return st1._score > st2._score;//重载“>”运算符
}
bool operator<(const Student& st1, const Student& st2)
{return st1._score<st2._score;//重载“<”运算符
}
void test()
{Student st1("22031202000", 101), st2("22031202001", 148);cout << "比较两名学生的成绩:" << endl;if (st1>st2)st1.dis();else if (st1<st2)st2.dis();elsecout << "两名学生成绩相同:" << endl;
}
关系运算符重载有以下几点使用技巧:
- 通常关系运算符都要成对地重载,例如重载了“>”运算符,就要重载“<”运算符,反之亦然。
- 通常情况下,“==”运算符具有传递性,例如:a= =b,b= =c,则a= =c成立。
- 可以把一个运算符的工作委托给另一个运算符,通过重载后的结果进行判断。例如,本例中重载“!=”运算符是在重载“==”运算符的基础上实现的。
④前置和后置(++/- -)重载
重载的++和- -运算符有点让人不知所措,因为前置和后置的运算符一样,都是++或- -,重载后的函数名都是operator++(- -),不便于区分,所以C++给我们提供了解决方法——占位参数。
即允许写一个增加了无用 int 类型形参的版本,编译器处理++或–前置的表达式时,调用参数个数正常的重载函数;处理后置表达式时,调用多出一个参数的重载函数。
重载形式:
例如:
class Maker
{friend ostream &operator<<(ostream &out, Maker &m);
public:Maker(int a){this->a = a;}//重载前置加加,重载后的函数参数个数正常0个Maker &operator++(){//返回类型是你自定义的类型,因为有++(m++)情况存在++this->a;//先完成递增操作return *this;//再返回当前对象}//重载后置加加,重载后的函数参数比正常情况多一个占位参数intMaker operator++(int)//占位参数,必须是int{//后置加加,先返回,在加加,//但一旦先返回,加加就操作不了,所以我们采取一个临时对象来延迟返回操作。//先保存旧值,然后加加,最后返回旧值Maker tmp(*this);//1.*this里面的值a是等于2++this->a;//这个对象的a等3return tmp;}
private:int a;
};ostream &operator<<(ostream &out, Maker &m)
{out << m.a << endl;return out;
}
void test()
{Maker m1(1);cout << m1 << endl;//1cout << ++m1 << endl;//2//++(++m1);cout << m1++ << endl;//2 这里返回的拷贝的tmp对象cout << m1 << endl;//3 这里打印的是++this->a的对象//同等条件下,优先使用前置加加,不需要产生新的对象和调用拷贝构造
}
对比前置++和后置++运算符的重载可以发现,后置++运算符的执行效率比前置的低。因为后置方式的重载函数中要多生成一个局部对象 tmp,而对象的生成会引发构造函数调用,需要耗费时间。同理,后置–运算符的执行效率也比前置的低。
思考🤔
为什么前置++运算符的返回值类型是Maker &,而后置++运算符的返回值类型是Maker?(Maker是你自定义的数据类型)
原因有二个方面:
- ①是因为运算符重载最好保持原运算符的用法。C++ 固有的前置++运算符的返回值本来就是操作数的引用,而后置++运算符的返回值则是操作数值修改前的复制品。
- ②后置++或- -,返回的是局部对象(默认情况下,局部对象的声明周期局限于所在函数的每次执行期间,只有当函数被调用的时候才存在),当函数执行完毕时,会立即释放分配给局部对象的存储空间。此时,对局部对象的引用就会指向不确定的内存。
⑤类型转换(类型转换函数和转换构造函数)
基本数据类型的数据可以通过强制类型转换操作符将数据转换成需要的类型,例如static_cast(3.14),这个表达式是将实型数据3.14转换成整型数据。对于自定义的类,C++提供了类型转换函数来实现自定义类与基本数据类型之间的转换。
1️⃣类型转换函数
对于自定义的类,C++提供了类型转换函数用来将类对象转换为基本数据类型。
类型转换函数也称为类型转换运算符重载函数,定义格式如下所示:
类型转换函数以operator关键字开头,这一点和运算符重载规律一致。从类型转换函数格式可以看出,在重载的数据类型名前不能指定返回值类型,返回值的类型由重载的数据类型名确定,且函数没有参数。由于类型转换函数的主体是本类的对象,因此只能将类型转换函数重载为类的成员函数。
✅示例:
class king
{
public:king(string id,const char*name){this->ID=id;Name = new char[strlen(name) + 1];strcpy(Name, name);}void Show(){cout<<"ID:"<<ID<<","<<"Name:"<<Name<<endl;}
operator char*()//类型转换运算符重载
{//如果要将对象转换为char*指针
//就直接将对象转换为自己的成员char*类型的Namereturn Name;
}
private:string ID;char *Name;
};
void test()
{king k1("2203120000","强风吹拂king");k1.Show();char *PK=k1;//调用类型转换函数cout<<PK<<endl;//通过调用重载的char*类型转换函数,将对象s1成功转换为了char*类型
}
将对象k1成功转换为char*类型
2️⃣转换构造函数
转换构造函数指的是构造函数只有一个参数,且参数不是本类的const引用。用转换构造函数不仅可以将一个标准类型数据转换为类对象,也可以将另一个类的对象转换为转换构造函数所在的类对象。转换构造函数的语法格式如下所示:
🃏案例演示转换构造函数:
三维坐标类转换为二维坐标类。
class Solid//三维坐标类
{friend class Point;//由于需要在Point类中访问Solid的成员变量//因此将Solid类声明为Point类的友元类。
public:Solid(int x,int y,int z) :x(x), y(y),z(z){}void Show(){cout<<"三维坐标"<<"("<<x<<","<<y<<","<<z<<")"<<endl;}
private:int x,y,z;
};
class Point//二维点类
{
public:Point(int x, int y) :x(x), y(y){}Point(const Solid &another)//定义转换构造函数{this->x=another.x;this->y=another.y;}void Show(){cout<<"二维坐标"<<"("<<x<<","<<y<<")"<<endl;}
private:int x,y;
};
void test()
{Point p1(1,1);p1.Show();Solid s1(2,2,2);s1.Show();cout<<"三维转换二维坐标"<<endl;p1=s1;p1.Show();
}
⑥数组下标([])运算符重载
在程序设计中,通常使用下标运算符“[]”访问数组或容器中的元素。为了在类中方便地使用“[]”运算符,可以在类中重载运算符“[]”。重载“[]”运算符有两个目的:
- “对象[下标]”的形式类似于“数组[下标]”,更加符合用户的编写习惯。
- 可以对下标进行越界检查。
重载下标运算符“[]”的语法格式如下所示:
上述格式中,“[]”运算符重载函数有且只有一个整型参数,表示下标值。重载下标运算符时一般把返回值指定为一个引用,因为数组下标既要做到读数据,也要做到写数据,所以要能当左右值。
✅案例演示:
class MyArray
{
public:MyArray();//无参构造函数MyArray(const MyArray &arr);//拷贝构造函数MyArray(int capacity, int val = 0);//有参构造参数,如果不指名初始值,默认为val=0。~MyArray();//析构函数//重写赋值运算符重载函数MyArray&operator=(const MyArray &m);//重写[]运算符,要能当左右值,左值可以放数据,右值可以读数据int &operator[](int index);//打印数据void PrintfMyArray();
private:int *pArray;//指向堆区空间,存储数据int mSize;//元素个数int mCapacity;//数组容量
};
//无参构造函数
MyArray::MyArray()
{this->mCapacity = 10;this->mSize = 0;this->pArray = new int[this->mCapacity];for (int i = 0; i < this->mCapacity; i++){this->pArray[i] = 0;}
}
//析构函数
MyArray::~MyArray()
{if (this->pArray != NULL){delete[] this->pArray;this->pArray = NULL;}
}
//拷贝构造函数
MyArray::MyArray(const MyArray &arr)
{this->mCapacity = arr.mCapacity;this->mSize = arr.mSize;//1.申请空间this->pArray = new int[arr.mCapacity];//2.拷贝数据for (int i = 0; i < this->mSize; i++){this->pArray[i] = arr.pArray[i];}
}
//有参构造函数,不指定初始值,则默认全部初始化为0
MyArray::MyArray(int capacity, int val)
{this->mCapacity = capacity;this->mSize = capacity;this->pArray = new int[capacity];for (int i = 0; i < this->mSize; i++){this->pArray[i] = val;}
}//重写赋值运算符重载函数
MyArray&MyArray::operator=(const MyArray &m)
{//1.释放原来的空间if (this->pArray != NULL){delete[] this->pArray;this->pArray = NULL;}this->mCapacity = m.mCapacity;this->mSize = m.mSize;//2.申请空间,大小由m决定this->pArray = new int[m.mCapacity];//3.拷贝数据for (int i = 0; i < this->mCapacity; i++){this->pArray[i] = m.pArray[i];}return *this;
}//要能当左右值
int &MyArray::operator[](int index)
{ static int x=-1;//数组下标检查if(mSize<index||index<0){cout<<"index越界"<<endl;return x;}else if (this->mSize ==index){ //当m.Size元素个数等于下标indexthis->mSize++;}//当else if执行说明此时要加入新数据,此时m.Size先要++,然后return this->pArray[index];返回的是左值。//上面两个if都不执行,说明只读取下标为index的元素,然后return this->pArray[index];返回的是右值。return this->pArray[index];
}
void MyArray::PrintfMyArray()
{for(int i=0;i<mSize;i++){cout<<pArray[i]<<" |";//调用[]运算符重载函数,输出数组类}
}
void test()
{MyArray arr;//无参构造函数for (int i = 0; i < 10; i++){arr[i] = i + 10;//调用[]运算符重载函数,初始化数组类}arr.PrintfMyArray();cout << endl;MyArray arr2(10, 66);//有参构造函数arr2.PrintfMyArray();cout << endl;MyArray arr3(arr2);//拷贝构造函数arr3.PrintfMyArray();cout << endl;MyArray arr4;arr4 = arr;//调用赋值运算符=的重载函数,arr赋值给arr4arr4.PrintfMyArray();cout << endl;arr2[6]=10086;//调用[]运算符,修改特定下标的值cout << arr2[6] << "|";cout << endl;cout << arr2[60] << "|";cout << endl;
}
⑦指针运算符(*、 ->)重载
智能指针类
智能指针(smart pointer)是存储指向动态分配(堆)对象指针的类,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。
智能指针的本质是使用引用计数的方式解决悬空指针的问题,通过重载“*”和“?>”运算符来实现。
为什么有智能指针类?
在平时我们写代码的时候,用new开辟出来的空间虽然我们知道要进行资源的回收,但可能会因为程序执行流的改变导致资源没有归还所导致的内存泄漏的问题,智能指针就帮助我们解决这个问题。
在学习引用计数、重载“*”和“?>”运算符之前,需要理解普通指针在资源访问中导致的指针悬空问题。
📝例如:
class king
{
public:king(string name) :Name(name){cout << "king构造函数调用" << endl;} ~king(){} void Show() { cout << "强风吹拂king的博客" << endl;}
private: string Name;
};
void test()
{king *pstr1 = new king("感谢支持我的博客");king *pstr2 = pstr1;king *pstr3 = pstr1;pstr1->Show();delete pstr1;pstr2->Show();
}
❗️问题:
指针pstr1、pstr2、pstr3共享同一个对象,若释放pstr1指向的对象,pstr2和pstr3仍然在使用该对象,将造成pstr2和pstr3无法访问资源,成为悬空指针,程序运行时出现异常。
为了解决悬空指针的问题,C++语言引入了引用计数的概念。引用计数是计算机科学中的一种编程技术,用于存储计算机资源的引用、指针或者句柄的数量。当引用计数为零时自动释放资源,使用引用计数可以跟踪堆中对象的分配和自动释放堆内存资源。
案例演示:
class king
{
public:king(string name) :Name(name){cout << "king构造函数调用" << endl;}~king(){}void Show(){cout << "感谢支持强风吹拂king的博客" << endl;}
private:string Name;
};
class Count//Count类用于存储指向同一资源的指针数量
{//声明SmartPtr智能指针类为Count类的友元类//因为SmartPtr要访问Count类成员friend class SmartPtr;
public://Count成员pking指向由SmartPtr类传过来king类对象的指针Count(king *pstr1) :pking(pstr1), count(1){cout << "Count类构造函数" << endl;}~Count(){cout << "Count类析构函数" << endl;delete pking;//释放king类指针}
private:king *pking;int count;
};
class SmartPtr//SmartPtr类用于对指向king类对象的指针实现智能管理
{
public://CountNum是成员对象指针,指向由king类指针初始化的Count类SmartPtr(king* pstr1) : CountNum(new Count(pstr1)){cout << "SmartPtr有参构造函数调用" << endl;}//拷贝构造函数SmartPtr(const SmartPtr& another) :CountNum(another.CountNum){++CountNum->count;//只要调用拷贝构造函数,就说明多一个指针指向king对象,所以count++cout << "Smartptr类拷贝构造函数" << endl;}//定义析构函数释放king类对象的资源,//当记录指向king类对象指针的数量count为0时,释放资源。~SmartPtr(){if (--CountNum->count == 0){delete CountNum;//释放指向成员对象Count的指针cout << "Smartptr类析构函数" << endl;}}//通过重载“*”和“->”运算符就可以指针的方式实现king类成员的访问。//->运算符重载,返回指向king类对象的指针。king *operator->(){return CountNum->pking;}//*运算符重载,返回king类对象。king &operator*(){return *CountNum->pking;}int disCount(){return CountNum->count;}
private://成员对象指针Count *CountNum;
};
void test()
{king *pstr1 = new king("感谢支持我的博客");SmartPtr pstr2 = pstr1;//调用有参构造函数,count不++,所以count的默认值才设为1(*pstr1).Show();SmartPtr pstr3 = pstr2;//调用拷贝构造函数,count++pstr2->Show();cout << "使用基类对象的指针数量:" << pstr2.disCount() << endl;
}
在使用智能指针申请king类对象存储空间后并没有使用delete释放内存空间。使用智能指针可以避免堆内存泄漏,只需申请,无须关注内存是否释放。通过重载“*”和“?>”运算符可以实现对象中成员的访问。
⑧函数调用()运算符重载——仿函数
1.类里有重载函数调用符号的类实例化的对象也叫仿函数。
2.仿函数的作用:
- 方便代码维护
- 方便有权限的调用函数。
- 作为算法的策略(仿函数在STL的算法中使用比较广泛。)
class Maker
{
public:Maker(){name = "强风吹拂king的博客";}void printMaker(){cout << "感谢支持"+name << endl;}//函数调用运算符()重载void operator()(const string str ){cout << str << endl;}//函数调用运算符()重载void operator()(int v1,int v2){cout << v1+v2 << endl;}
public:string name;
};void test()
{Maker func;func("感谢支持强风吹拂king的博客");//看起来像函数调用,其实func是对象func(10, 20);//像调用函数一样调用对象funcfunc.printMaker();
}
相关文章:

C++核心编程——详解运算符重载
文章目录💬 一.运算符重载基础知识①基本概念②运算符重载的规则③运算符重载形式④运算符重载建议 二.常用运算符重载①左移(<<)和右移(>>)运算符重载1️⃣重载后函数参数是什么?2️⃣重载的函数返回类型是什么?3️⃣重载为哪种…...

2023年前端面试汇总-CSS
1. CSS基础 1.1. CSS选择器及其优先级 对于选择器的优先级: 1. 标签选择器、伪元素选择器:1; 2. 类选择器、伪类选择器、属性选择器:10; 3. id 选择器:100; 4. 内联样式:1000&a…...

Java调用Pytorch实现以图搜图(附源码)
Java调用Pytorch实现以图搜图 设计技术栈: 1、ElasticSearch环境; 2、Python运行环境(如果事先没有pytorch模型时,可以用python脚本创建模型); 1、运行效果 2、创建模型(有则可以跳过…...

【EasyX】实时时钟
目录 实时时钟1. 绘制静态秒针2. 秒针的转动3. 根据实际时间转动4. 添加时针和分针5. 添加表盘刻度 实时时钟 本博客介绍利用EasyX实现一个实时钟表的小程序,同时学习时间函数的使用。 本文源码可从github获取 1. 绘制静态秒针 第一步定义钟表的中心坐标center&a…...

基于XC7Z100的PCIe采集卡(GMSL FMC采集卡)
GMSL 图像采集卡 特性 ● PCIe Gen2.0 X8 总线; ● 支持V4L2调用; ● 1路CAN接口; ● 6路/12路 GMSL1/2摄像头输入,最高可达8MP; ● 2路可定义相机同步触发输入/输出; 优势 ● 采用PCIe主卡与FMC子…...

Kibana:使用 Kibana 自带数据进行可视化(一)
在今天的练习中,我们将使用 Kibana 自带的数据来进行一些可视化的展示。希望对刚开始使用 Kibana 的用户有所帮助。 前提条件 如果你还没有安装好自己的 Elastic Stack,你可以参考如下的视频来开启 Elastic Stack 并进行下面的练习。你可以开通阿里云检…...

MySQL数据库基础 07
第七章 单行函数 1. 函数的理解1.1 什么是函数1.2 不同DBMS函数的差异1.3 MySQL的内置函数及分类 2. 数值函数2.1 基本函数2.2 角度与弧度互换函数2.3 三角函数2.4 指数与对数2.5 进制间的转换 3. 字符串函数4. 日期和时间函数4.1 获取日期、时间 4.2 日期与时间戳的转换 4.3 获…...
JVM | JVM垃圾回收
JVM | JVM垃圾回收 1、堆空间的基本结构2、内存分配和回收原则2.1、对象优先在 Eden 区分配2.2、大对象直接进入老年代2.3、长期存活的对象将进入老年代2.4、主要进行 gc 的区域2.5、空间分配担保3、死亡对象判断方法3.1、引用计数法3.2、可达性分析算法3.3、引用类型总结3.4、…...

avive零头撸矿
Avive 是一个透明的、自下而上替代自上而下的多元网络,旨在克服当前生态系统的局限性,实现去中心化社会。 aVive:一个基于 SBT 和市场的 deSoc,它使 dapps 能够与分散的位置 oracle 和 SBT 关系进行互操作。您的主权社交网络元宇宙…...

openGauss5.0之学习环境 Docker安装
文章目录 0.前言1. 准备软硬件安装环境1.1 软硬件环境要求1.2 修改操作系统配置1.2.1 关闭操作系统防火墙 1.3 设置字符集参数1.4 设置时区和时间(可选)关闭swap交换内存1.5 关闭RemoveIPC1.6 关闭HISTORY记录 2. 容器安装2. 1支持的架构和操作系统版本2…...

数据可视化大屏人员停留系统的开发实录(默认加载条件筛选、单击加载、自动刷新加载、异步加载数据)
项目需求 录入进入房间的相关数据;从进入时间开始计时,计算滞留房间的时间;定时刷新数据,超过30分钟的人数,进行红色告警; 实现流程 为了完整地实现上述需求,我们可以按照以下步骤开发&#…...

【Linux】-关于调试器gdb的介绍和使用
作者:小树苗渴望变成参天大树 作者宣言:认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧! 文章目录 前言一、Linux中的debug和release二、gdb的使用**1.进入调试****2.显示代码*…...
项目开发经验
hadoop 1.namenode中有专门的工作线程池用于处理与datanode的心跳信号 dfs.namenode.handler.count20 * log2(Clust 2.编辑日志存储路径 dfs.namenode.edits.dir 设置与镜像文件存储路径 dfs.namenode分开存放,可以达到提高并发 3.yarn参数调优,单个服…...

STM32——05-按键、时钟控制、中断复位 点亮LED灯
如何点亮一颗LED灯 编程实现点灯 常用的 GPIO HAL 库函数: void HAL_GPIO_Init ( GPIO_TypeDef * GPIOx , GPIO_InitTypeDef * GPIO_Init ); void HAL_GPIO_WritePin ( GPIO_TypeDef * GPIOx , uint16_t GPIO_Pin , GPIO_PinState PinState ); void HAL_GPIO_Togg…...
VBA下载二进制文件,文本读写
这里使用了vba如下两个对象: Microsoft.XMLHTTP:文件读写,可读写二进制,可指定编码,对于utf-8编码文本文件使用FSO的TextStream对象打开,读取到的内容可能会出现乱码,可以使用该对象打开;前期绑定添加引用…...
MongoDB结合Robo 3T 1.4.3的简单操作
MongoDB的简单操作结合Robo 3T 1.4.3工具进行查询。 常用的正则表达式 /* 29 */ 正则表达式 /\* [0-9]* \*/ "_id" : ObjectId("5f3d05cdfd2aa9a8a7"), 正则表达式 \"([^\"]*_id)\".*, 使用方法:查询结果去掉注释和不需要…...
【学习笔记】[AGC048D] Pocky Game
这是一个非平等博弈。但是只要求你判断胜负,本身也不是一道结论题,所以可以用 D P DP DP来解决。 结论:第一堆石子剩的越多,先手玩家获胜的概率越大。这直接引出了一个非常感性的结论:每次取石子时要么取一堆…...

Qgis中进行Shp和Excel属性连接实现百强县公共预算空间分析
前言 在之前的博文中,将2022的全国百强县一般公共预算收入的数据下载到了本地,博客原文地址:一种使用Java的快速将Web中表格转换成Excel的方法。对于不关注时空位置关系的一般分析,到此也就基本够用了。但是,如果站在全…...
ES6 新增的循环方法
在 ES6(ECMAScript 2015)中,新增了一些循环方法,这些方法可以帮助我们更方便地遍历数组、字符串、Set、Map 等数据结构。本文将介绍一些常用的 ES6 循环方法。 for…of 循环 for…of 循环是一种遍历可迭代对象的方法,…...

移动端事件300ms延迟解决
有移动端与PC端的项目开发,那么移动端和PC端开发上是存在差异的,比如 click 事件的300ms 延迟,即移动Web页面上的click事件响应都要慢上300ms,移动设备访问Web页面时往往需要 “双击” 或者 “捏开” 来放大页面看清页面的具体内容…...

wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
是否存在路径(FIFOBB算法)
题目描述 一个具有 n 个顶点e条边的无向图,该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序,确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数,分别表示n 和 e 的值(1…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...