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页面时往往需要 “双击” 或者 “捏开” 来放大页面看清页面的具体内容…...

NRF52832的DFU
开发环境: Winsodw:10 nRF5_SDK:17.1.0 1 工具安装 1.1 gcc-arm-none-eabi Downloads | GNU Arm Embedded Toolchain Downloads – Arm Developer 下载“gcc-arm-none-eabi-10.3-2021.10-win32.exe”,接提示安装。注意安装完…...

开源WebRTC库放大器模式在采集桌面图像时遇到的DPI缩放与内存泄漏问题排查
目录 1、在非100%的显示比例下放大器采集到的桌面图像不全问题 1.1、通过manifest文件禁止系统对软件进行缩放 1.2、调用SetThreadDpiAwarenessContext函数,禁止系统对目标线程中的窗口进行缩放 1.3、使用winver命令查看Windows的年月版本 2、使用放大器模式遇…...

敲黑板!java反射机制和原理
获取Class对象:首先,你需要获取表示要操作的类的Class对象。可以使用以下三种方式之一来获取Class对象: Class.forName()方法:使用类的全限定名获取Class对象,例如:Class<? Class<?> clazz MyC…...

【大数据工具】HBase 集群搭建与基本使用
HBase 集群搭建 HBase 安装包下载地址:https://archive.apache.org/dist/hbase/ 安装 HBase 的前提: ZooKeeper 集群 OKHadoop 集群 OK 1. HBase 集群安装 1. 将 HBase 软件包上传至 Hadoop0 解压并重命名 使用 FileZilla 将 hbase-1.3.1-bin.tar.g…...

【Java】数组详解
文章目录 一、数组的基本认识1.1 数组的概念1.2数组的创建与初始化1.3 数组的使用 二、数组的类型 — 引用类型2.1 JVM 内存分布2.2 什么是引用类型2.3 基本类型变量与引用类型变量的区别2.4 Java 中的 null 三、数组的应用3.1 保存数据3.2 函数参数3.3 函数返回值 一、数组的基…...

NumPy库的学习
本文主要记录的是笔者在B站自学Numpy库的学习笔记。 引入numpy库 import numpy as np矩阵的创建 创建一个二行三列的矩阵。 array np.array([[1,2,3],[2,3,4]])查看array的行数、形状、元素数量 print("number of dim:",array.ndim) print("shape:"…...

CentOS安装IRIS
最近电脑提搞了,可以无顾虑创建虚拟机了,试一下在Linux安装IRIS,适用CentOS7.6上安装Intersystem公司的IRIS数据库,资料基本是空白,分享一下。 首先安装解压软件unzip和libicu,最小化安装的缺,…...

华为OD机试真题 JavaScript 实现【最多几个直角三角形】【2023Q1 100分】
一、题目描述 有 N 条线段,长度分别为 a[1]-a[n]。 现要求你计算这 N 条线段最多可以组合成几个直角三角形,每条线段只能使用一次,每个三角形包含三条线段。 二、输入描述 第一行输入一个正整数 T (1< T< 100) ,表示有…...

vue3中的reactive、ref、toRef和toRefs
目录 reactivereactive的实现原理使用reactive的注意事项 refref的实现原理使用ref的注意事项 toRef和toRefsref和reactive的使用比较 reactive reactive用于创建响应式对象,它返回一个对象的响应式代理。即:它返回的对象以及其中嵌套的对象都会通过 Pr…...

数字图像处理与Python实现-图像增强经典算法汇总
图像增强经典算法汇总 文章目录 图像增强经典算法汇总1、像素变换2、图像逆变换3、幂律变换4、对数变换5、图像均衡化6、对比度受限自适应直方图均衡(CLAHE)7、对比度拉伸8、Sigmoid校正9、局部对比度归一化10、总结本文将对图像增强经典算法做一个简单的汇总。图像增强的经典…...