1.类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
![](https://i-blog.csdnimg.cn/direct/690880b381d641da8d964805f2177e37.png)
2.构造函数
2.1引入
在使用类的时候,比如对于下面的Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,过于麻烦,而且,我们常常会忘记初始化和销毁,这可能导致对象中的变量变成随机值
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<<"/"<<_month <<"/"<< _day<< endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
//这里忘记了初始化,打印出来的就是三个随机值
return 0;
}
甚至可能导致运行崩溃,比如使用定义的栈,但忘记初始化和销毁的时候
class Stack
{
public:
void Init()
{
a = nullptr;
top = capacity = 0;
}
void Push(int x)
{
if (top == capacity)
{
int newcapacity = capacity == 0 ? 4 : capacity * 2;
a = (int*)realloc(a, sizeof(int) * newcapacity);
}
a[top++] = x;
}
int Top()
{
return a[top - 1];
}
private:
int* a;
int top;
int capacity;
};
int main()
{
Stack st;
st.Push(1);
//这里没有初始化就进行了压栈操作,会导致运行崩溃
st.Push(2);
st.Push(3);
st.Push(4);
return 0;
}
为了解决以上问题,能否在对象创建时,就将信息设置进去呢?因此,C++引入了构造函数。
2.2概念
class Date
{
public:
Date()//构造函数
{
_year = 1;
_month = 1;
_day = 1;
}
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<<"/"<<_month <<"/"<< _day<< endl;
}
private:
int _year;
int _month;
int _day;
};
2.3特性
其特征如下:1. 函数名与类名相同。2. 无返回值。(不需要写void)3. 对象实例化时编译器 自动调用对应的构造函数。4. 构造函数可以重载,就是说我们可以写多个构造函数,提供多种初始化方式。5.当我们不主动去写构造函数,即类中没有显式定义构造函数时,C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
2.3.1 Date类
class Date
{
public:
//Date()// 1.无参构造函数
//{
// _year = 1;
// _month = 1;
// _day = 1;
//}
//Date(int year, int month, int day)// 2.带参构造函数
//{
// _year = year;
// _month = month;
// _day = day;
//}
Date(int year=1, int month=1, int day=1)
//利用缺省参数,上述两个函数还可以合并起来,而且不仅仅合并了,调用还更灵活了
//注意该函数不能与第一个函数共同存在
//在语法上,两函数构成函数重载,无参数和全缺省带参数,但调用的时候可能会存在歧义,存在二义性问题
{
_year = year;
_month = month;
_day = day;
}
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<<"/"<<_month <<"/"<< _day<< endl;
}
private:
int _year;//声明
int _month;
int _day;
};
int main()
{
Date d1;//可以不传参数
//Date d3();//通过无参构造函数创建对象时,对象后面不用跟括号
没有参数不能加(),否则会出问题,编译器分不清楚这里是要定义一个对象还是函数的声明
Date d2(2024,8,20);//可以传3个参数
//这里不会有歧义,函数声明中()里面给的应该是类型而不是实际的参数值
//一个类提供一个全缺省的构造函数是很棒的事情,它使得初始化很灵活
//构造函数也在公共代码区,也有this指针
//lea:取地址的意思
Date d3(2024);//可以传一个参数
d1.Print();
d2.Print();
d3.Print();
return 0;
}
2.3.2 Stack类
class Stack
{
public:
Stack()
//既然构造函数是为了初始化,我们可以直接将Init函数的代码拷贝过来吗?
{
a = nullptr;
top = capacity = 0;
}
void Init()
{
a = nullptr;
top = capacity = 0;
}
void Push(int x)
{
if (top == capacity)//扩容问题
{
cout << top << "扩容" << endl;
size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
a = (int*)realloc(a, sizeof(int) * newcapacity);
capacity = newcapacity;
}
a[top++] = x;
}
int Top()
{
return a[top - 1];
}
void Pop()
{
assert(top > 0);
--top;
}
void Destroy()
{
free(a);
a = nullptr;
top = capacity = 0;
}
bool Empty()
{
return top == 0;
}
private:
int* a;
int top;
int capacity;
};
既然构造函数是为了初始化,那我们是不是可以直接将Init函数的代码拷贝到Stack这个构造函数中?
答案是可以的,但这样很不便利。
int main()
{
Stack st;//这里就直接调用构造函数了
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
while (!st.Empty())
{
cout << st.Top()<<" ";
st.Pop();
}
cout << endl;
st.Destroy();
///
Stack st1;
//局部变量属于栈帧里面的,建立栈帧时就会把函数中的变量等所需空间开辟出来
for (int i = 0; i < 1000; i++)
{
st1.Push(i);
}
while (!st1.Empty())
{
cout << st1.Top() << " ";
st1.Pop();
}
cout << endl;
st1.Destroy();
return 0;
}
这里对st1进行操作时,已知要压栈1000个数据,但是要是按照前面的写法,入栈时要不断地扩容,不停地使用realloc函数,扩容到后面,大概率都是异地扩容,开更大的空间,拷贝数据,释放旧空间,这是一种很挫的做法。
//也可以使用代码查看异地扩容了多少次
void Push(int x)
{
if (top == capacity)
{//后面大概率都是异地扩容,开更大的空间,拷贝数据,释放旧空间
size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
if (tmp == a)
{
cout << capacity << "原地扩容" << endl;
}
else
{
cout << capacity << "异地扩容" << endl;
}
a = tmp;
capacity = newcapacity;
}
a[top++] = x;
}
所以我们要对Stack构造函数进行改进
Stack(size_t n = 4)//借助缺省参数
{
cout << "Stack(size_t n = 4)" << endl;
//这个用来查看构造函数是否是在对象定义出来时就调用了
if (n == 0)
{
a = nullptr;
top = capacity = 0;
}
else
{
a = (int*)malloc(sizeof(int) * n);
if (a == nullptr)
{
perror("realloc fail");
exit(-1);
//exit(-1) 让程序退出,以-1的方式,-1就是一种异常的方式退出
//正常方式的退出都是0
}
top = 0;
capacity = n;
}
}
//这样的话已知要压栈1000个数据时,就可以直接这样定义
Stack st1(1000);
注意:C++有规定,构造函数是必须要调用的。
2.3.3内置类型和自定义类型
关于 编译器生成的默认成员函数, 已知: 不实现构造函数的情况下,编译器会 生成默认的构造函数。
2.3.3.1内置类型
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
//没有对_year进行操作,_year就是1
_month = month;
_day = day;
}
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout <<_year<<"/"<<_month <<"/"<< _day<< endl;
}
private:
int _year=1; //声明
int _month=1;
int _day=1;
//在C++11中,声明处支持给缺省值,这里不是初始化,声明不会开辟空间
//这是声明给的缺省值,这样默认生成的构造函数就可以用这个缺省值进行初始化
//这个缺省值可以理解为就是用来补内置类型的坑
};
2.3.3.2自定义类型
对于自定义类型的成员才会处理,会去调用这个成员的默认构造函数
2.3.3.3特例:用栈实现队列
// 两个栈实现一个队列
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
//Date* _ptr;
//int _size;
};
2.3.4编译生成的默认构造的特点
构造函数,也就是默认成员函数,我们不写,编译器会自动生成
编译生成的默认构造的特点:
1、我们不写才会生成,我们写了任意一个构造函数就不会生成了
2、内置类型的成员不会处理(C++11,声明支持给缺省值)
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即: 内置类型成员变量在类中声明时可以给默认值。3、自定义类型的成员才会处理,会去调用这个成员的默认构造函数
所以一般情况都需要我们自己写构造函数,来决定初始化方式,成员变量全是自定义类型时,可以考虑不写构造函数
总结:不传参就可以调用的就是默认构造函数
2.3.4.1注意
1.
这样写会报错,因为它既不是无参构造函数,也不是全缺省构造函数,而且因为我们写了构造函数,所以编译器不会生成默认构造函数。
2.
class Stack
{
public:
Stack(size_t n)
{
//...
}
private:
int* a;
int top;
int capacity;
};
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
//注意:这里不能传参 Stack _pushst(1);是错误的
};
int main()
{
MyQueue mq;
return 0;
}
这里也会报错,因为调用mq的构造函数时,会去调用mq中两个成员的默认构造函数,但它们没有默认构造函数,所以会报错。
3.析构函数
3.1 概念
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
~Date()//Date类中其实没有什么资源需要清理
//年月日是属于对象的,对象本身的数据不需要清理,对象是在栈帧里面,栈帧出了作用域就自动销毁了
{
cout << "~Date()" << endl;
}
void Print()
{
cout <<_year<<"/"<<_month <<"/"<< _day<< endl;
}
private:
int _year;//声明
int _month;
int _day;
};
int main()
{
Date d1;
//对象定义在这里不需要我们去销毁,对象在栈帧中,栈帧结束,它就跟着销毁了
//出了作用域清栈帧就直接带走了
//对象出了作用域、生命周期到了以后,会自动调用析构函数,完成对象中资源的清理工作。
//~ 在C语言中是按位取反的意思
return 0;
}
调用函数会把栈帧拉开,栈帧开多大呢?编译器提前编译时就已经算好了,栈帧结束就销毁了。
而堆上的空间不手动释放就会导致内存泄露。
3.2 特性
析构函数是特殊的成员函数,其 特征如下:1. 析构函数名是在类名前加上字符 ~。2. 无参数无返回值类型。3. 一个类 只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意: 析构函数不能重载,因为它没有参数4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
3.2.1
3.2.2
比如栈,这样就不用去写Destroy了
清理资源,如:malloc的、fopen的,把动态申请的、显式地申请的去释放一下
class Stack
{
~Stack()
{
cout << "~Stack()" << endl;
free(a);
a = nullptr;
top = capacity = 0;
}
private:
int* a;
int top;
int capacity;
}
int main()
{
Date d1;
Date d2;
Stack st1;
Stack st2;
//先调用st2的析构,后定义的先析构
//因为在栈帧里面,C++有规定,这些数据需要后进先出
return 0;
}
同时要注意:析构函数调用顺序为后定义的对象先调用析构函数
3.2.3总结
默认成员函数最大的优势:实例化对象时会自动调用
3.2.4使用默认析构函数的一种情况
// 两个栈实现一个队列
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
//Date* _ptr;
//int _size;
};
int main()
{
MyQueue mq;
return 0;
}
4.拷贝构造函数
4.1引入
日常学习中,除了有初始化的需求,还有拷贝的需求。比如把当前对象拷贝一份出来。那么如何拷贝呢?是直接拷贝吗? (赋值重载本质也是一种拷贝)
4.1.1什么情况下会出现拷贝?
void func1(Date d)//将d2拷贝给了d
//实际上这里如果没有显式地写拷贝构造,就会调用默认的拷贝构造
{
d.Print();
}
int main()
{
Date d2(2024, 8, 23);
func1(d2);//这里就是拷贝
//调用时传过去,按照C语言的理解是直接拷贝
//传值传参,传了以后开空间出来,把12byte的数据依次拷贝过去,类似memcpy
//C语言允许结构体拷贝
return 0;
}
但这里会出现问题,当对Date类型的对象进行拷贝时,一切正常。
但对Stack类型的对象进行拷贝时,就会出现问题。
class Stack
{
public:
//...
~Stack()
{
cout << "~Stack()" << endl;
free(a);
a = nullptr;
top = capacity = 0;
}
private:
int* a;
int top;
int capacity;
};
void func2(Stack s)
{
}
int main()
{
Stack s1;
func2(s1);
//调用建立func2的栈帧,然后进行传参,传值传参是一种值拷贝或者说是浅拷贝
return 0;
}
4.1.2为什么会崩溃呢?
实际上是在析构函数崩溃的。C语言中也有这个问题,它叫做浅拷贝问题,值拷贝(浅拷贝)会把每个byte依次拷贝过去,C语言做得就是这样的直接拷贝,默认情况下C语言还不会出问题,因为C语言不会自动去调用析构,所以在C语言中注意这个场景就不会出问题,但C++中会自动调用析构,比如上述代码,func2先结束,出了作用域,func2优先去调用析构函数,就会把a指向的空间给释放,虽然置空了,但置空的是func2中的s对象的a,不会影响main函数中的s1对象的a,而且它还会变成野指针,因为它指向的空间已经被释放了,然后回到main栈帧,还会调用析构函数,所以这块空间就会释放两次,第二次调用析构时就会崩溃。相当于两个对象同时去调用了Destroy,也因此它对于Date类是没有问题的,而对于Stack类就会出现问题。
4.1.3解决方案(Date类)
1.使用引用
void func2(Stack& s)//一个对象不会析构两次
//只是别名,不会析构
{
}
int main()
{
Stack s1;
func2(s1);
return 0;
}
但这里也存在问题,比如在func2函数中进行Push操作,那么就会影响s1。
我们更期望程序不会崩溃的前提下,s的改变,比如插入一些数据,不影响s1。
2.拷贝构造函数(Date类)
C++认定这样一个问题,用同一个对象去拷贝另外一个对象时,定义了一个函数来进行解决,这个函数就叫做拷贝构造函数,它是专门完成拷贝的一个构造函数。
class Date
{
public:
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//...
}
int main()
{
Date d2(2024, 8, 23);
Date d3(d2);//d3是d2的拷贝
//Stack s2(s1);//s2是s1的拷贝
return 0;
}
4.1.3.1新的问题
然而此时又会出现新的问题,这是为什么?
4.1.3.2原因
Date(Date d)
//这里调用拷贝构造,调用函数的第一件事情是传参,但是如果传参使用传值的方式会出现问题
//C++认为传参就是一个拷贝构造,还没有调用上这个函数时就要先传参,传参又会形成一个新的拷贝构造
//而新的拷贝构造又要传参,传参又要形成一个新的拷贝构造,如此无限循环{
_year = d._year;
_month = d._month;
_day = d._day;
}
为什么传值传参要调用拷贝构造,而不像C语言传结构体直接进行浅拷贝的方式一样?
原因:函数传参,如果是内置类型,直接传参没有问题,但自定义类型不能直接传,而是要去调用一个函数去传,这个函数就叫拷贝构造,调用函数可以完成深拷贝。
比如上述Stack这样的类,如果按照浅拷贝的方式,会有析构两次的问题,也就是浅拷贝的问题。传参调用拷贝构造是为了解决C语言留下的坑。
C++规定,传值传参必须要去调用拷贝构造完成拷贝,不能像C语言那样直接拷贝
C++规定,自定义类型传参这种,它是一种对象间的拷贝初始化,需要调用拷贝构造才能完成这个过程
所以正确的方法是:
class Date
{
public:
Date(Date& d)
//这里必须是引用,否则会出现一个问题:无穷递归
//引用是别名,引用的底层是指针,所以引用不涉及拷贝的问题,也因此指针也可以解决这个问题
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//...
}
int main()
{
Date d2(2024, 8, 23);
//以下两种写法是等价的,都是拷贝构造
Date d3(d2);
//Date d3 = d2;
//这样写也是拷贝构造
return 0;
}
就比如上面的func1函数,虽然没有自定义类型,但其实也会调用拷贝构造函数,也因为使用了引用,所以这里不会出现无穷递归的问题。
同时使用指针也可以解决问题,但是使用起来不够舒服,而且这个不算是显式定义拷贝构造,编译器会生成默认拷贝构造
class Date
{
public:
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
//...
}
int main()
{
Date d2(2024, 8, 23);
Date d3(&d2);
//Date d3(d2);
return 0;
}
4.1.4解决方案(Stack类)
class Stack
{
public:
//Stack s2(s1);
Stack(Stack& s)
{//深拷贝,深一个层次的拷贝,这里要保证两个对象的a指向的是不同的空间
a = (int*)malloc(sizeof(int) * s.capacity);
if (a == nullptr)
{
perror("malloc申请空间失败!");
return;
}
top = s.top;
capacity = s.capacity;
memcpy(a, s.a, sizeof(int) * s.capacity);
}
//...
private:
int* a;
int top;
int capacity;
};
4.1.5总结
拷贝构造最重要的特性是,用当前类型的对象去拷贝初始化另一个对象,严格来说,分为两类拷贝,一类如Date类这样直接拷贝,另外一类就是如Stack类这样的,我们需要自己书写,来完成深拷贝,必须使用引用传参,而不能使用传值传参,传值传参会引发无穷递归
4.2概念
拷贝构造函数: 只有单个形参,该形参是 对本类类型对象的引用(一般常用const修饰),在使用 已存在的类类型对象创建新对象时由编译器 自动调用。在创建对象时,它用来创建一个与已存在对象一模一样的新对象。
4.3特性
拷贝构造函数也是 特殊的成员函数,其 特征如下:1. 拷贝构造函数 是构造函数的一个重载形式。2. 拷贝构造函数的 参数只有一个且 必须是同类型对象的引用,使用 传值方式编译器会直接报错,因为会引发 无穷递归调用。
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数将对象按 内存存储按 字节序完成拷贝,这种拷贝叫做 浅拷贝,或者 值拷贝。注意:在编译器生成的 默认拷贝构造函数中, 内置类型是 按照字节方式直接拷贝的,而自定义类型是 调用其拷贝构造函数完成拷贝的。int main() { // 我们不写,编译默认生成的拷贝构造,跟之前的构造函数特性是不一样的 // 1、内置类型, 完成值拷贝(浅拷贝) 一定程度兼容了C语言 // 2、自定义的类型,调用它的拷贝构造 Date d1(2024,8,25); Date d2=d1; return 0; }
总结:Date类不需要我们实现拷贝构造,默认生成的拷贝构造就可以使用
而Stack类需要我们自己实现深拷贝的拷贝构造,使用默认生成的会出问题
那么什么时候可以使用默认的拷贝构造呢?
class MyQueue { private: Stack _pushst; Stack _popst; }; int main() { //MyQueue对于默认生成的几个函数都非常受用 MyQueue mq1; MyQueue mq2 = mq1; return 0; }
拷贝构造函数 典型调用场景:1.使用已存在对象创建新对象2.函数参数类型为类类型对象3.函数返回值类型为类类型对象注意:为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
4.3.1补充
引用传参前最好加上const
//Date d2(d1);
class Date
{
public:
Date(const Date& d)
//一般来说,引用的形参
//只要不是要改变这个形参,或是不做输出型参数的话,都会加const缩小权限,防止它被修改(误伤)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//...
}
因为有时可能会写错,为了避免这种情况
//Date d2(d1);
class Date
{
public:
Date(Date& d)
{
d._year=_year;
d._month=_month;
d._day=_day;
}
//整个写反了,相当于把d2拷贝给了d1
//而本意是想把d1拷贝给d2
//但是代码是不会报错的,所以为了避免这种情况发生,要加上const
}
5.赋值运算符重载
5.1运算符重载的引入
5.1.1问题一
要想对两个日期对象比较大小,如何比较呢?
int main()
{
//要对两个日期对象比较大小,如何比较?
Date d1(2023, 7, 25);
Date d2(2023, 7, 26);
// d1 < d2;
//日期能不能像这样使用运算符比较大小呢?
//当然是不能的,它们是自定义类型
return 0;
}
5.1.2问题二
先抛开上述问题,我们来思索一下,内置类型为什么能知道运算符的使用规则?
int main()
{
int i = 1, j = 10;
i < j;
//内置类型为什么知道怎么使用运算符呢?
//因为内置类型是语言原生定义的,而且内置类型都是一些比较简单的类型
//如整型、浮点型等,因为是语言原生定义的,所以它们知道比较规则
//所以在这里可以直接支持比较
//编译器只是个工具人,它要去围绕祖师爷本贾尼实现他的想法
return 0;
}
5.1.3问题一(续)
我们接着来看第一个问题,如何比较两个日期对象的大小?
一种方式就是写一个专门用于比较日期对象的函数。
bool DateLess(const Date& x1, const Date& x2)
{
if (x1._year < x2._year)
{
return true;
}
else if (x1._year == x2._year && x1._month < x2._month)
{
return true;
}
else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
{
return true;
}
else
{
return false;
}
}
int main()
{
Date d1(2023, 7, 25);
Date d2(2023, 7, 26);
cout << DateLess(d1,d2) << endl;
//这样比较时就可以直接调用函数来比较
return 0;
}
5.1.3.1注意
因为函数中调用的成员变量是私有的,所以上述代码实际会报错,这个问题需要专门解决一下。
class Date
{
public:
//...
private:
int _year;//声明
int _month;
int _day;
};
一、将私有的成员变量置为公有的,这也是最暴力的一种解决办法
二、在Date类中额外定义几个函数,如
class Date
{
public:
//...
GetYear()
{
return _year;//在Java中常会使用这种方式
//在自己的函数中直接使用这个成员函数就可以了
}
private:
int _year;//声明
int _month;
int _day;
};
三、把比较日期对象的函数写进Date类中,使之由全局函数变为成员函数
5.1.4问题三
但这里还存在一些问题,比如比较日期对象这个函数的命名,不同人的命名习惯都有所不同,会产生千奇百怪的函数名,可能会使得他人看不懂函数的作用,甚至自己写完代码很久以后,都可能会遗忘函数的意义
bool DateLess(const Date& x1, const Date& x2);
bool riqixiaoyu(const Date& x1, const Date& x2);
bool Compare1(const Date& x1, const Date& x2);
5.1.5解决方案
所以为了解决这些问题,增加代码的可读性,C++引入了运算符重载。
//增加一个特殊的函数,叫做运算符重载
//重载就有重新定义的意思,重新定义这里的运算符重载
//编译器不知道你的自定义类型怎么比,但是写代码的自己是知道的
bool operator<(const Date& x1, const Date& x2)
{
if (x1._year < x2._year)
{
return true;
}
else if (x1._year == x2._year && x1._month < x2._month)
{
return true;
}
else if (x1._year == x2._year && x1._month == x2._month && x1._day < x2._day)
{
return true;
}
else
{
return false;
}
}
int main()
{
Date d1(2023, 7, 25);
Date d2(2023, 7, 26);
// cout << d1 < d2 << endl;
//这里编译报错的原因是,流插入运算符的优先级比‘<’高
//所以会先走cout<<d1,走完之后这里的返回值和后面的又对不上,所以这里要加括号
cout << (d1 < d2) << endl;//引入运算符重载后,这里就可以这样写
//这里本质被转换成了下面这个特殊的成员函数
cout << (operator<(d1,d2)) << endl;
//可以像这样显式调用,但一般不需要,编译器会自动转换
//C++规定operator加上'<'就是函数名
//这个函数是一个特殊的成员函数,相当于对运算符、运算方式的重新定义
return 0;
}
这样自定义类型就也可以像内置类型一样,支持这些运算符了,使用起来会很方便。
int main()
{
Date d1(2023, 7, 25);
Date d2(2023, 7, 26);
int i = 1, j = 10;
//C++会去分辨是自定义类型还是内置类型
i < j;//这里会转换成相关指令 cmp
d1 < d2;//这里会去调用相关函数
//不写运算符重载,自定义类型是不支持比较的
return 0;
}
运算符重载真正的意义是,虽然使用普通的函数也可以完成上述的操作,但是使用运算符重载后,会让可读性变强
5.2运算符重载
C++为了 增强代码的可读性引入了 运算符重载,运算符重载是 具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。函数名字为:关键字 operator后面接 需要重载的运算符符号。函数原型: 返回值类型 operator操作符(参数列表)
5.2.1注意
一、不能通过连接其他符号来创建 新的操作符:比如operator@
二、重载操作符必须 有一个类类型参数,也就是 自定义类型参数bool operator+(int& x1, int& x2) { //... } //比如这里,想把两个整型的加法规则更改掉就是不允许的 //因为重载操作符必须有一个类类型参数
三、用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能去改变其含义
四、作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this此时我们再回到之前的问题一(续)的注意,在C++中,可以选择第三种方式,把 比较日期对象的函数写进Date类中,使之成为 成员函数。但这里直接把 operator<函数完全照搬地写进Date类中是行不通的。class Date {public: // d1 < d2 // d1.operator<(d2) bool operator<(const Date& d)//d1是this,d2是d { if (_year < d._year) { return true; } else if (_year == d._year && _month < d._month) { return true; } else if (_year == d._year && _month == d._month && _day < d._day) { return true; } else { return false; } } //... private: int _year; int _month; int _day; } int main() { Date d1(2023, 7, 25); Date d2(2023, 7, 26); cout << (d1 < d2) << endl; //编译器先去全局找,再去类中找,找到以后再去转换成下面的样子 cout << (d1.operator<(d2)) << endl; return 0; }
五、不能改变 操作符的 操作数个数,一个操作符是几个操作数,那么重载的时候就有几个参数,但有一个参数this是省略掉的有些操作符是 两个操作数,比如+、-、<
还有些操作符是 一个操作数,比如++
六、.* ::(域作用限定符) sizeof ?:(三目操作符) . 注意:以上这5个运算符 不能重载。* 会作为两种重载,乘以、解引用