类和对象(中)
文章目录
- 目录
- 1. 类的6个默认成员函数
- 2. 构造函数
- 3. 析构函数
- 4. 拷贝构造函数
- 5. 赋值运算符重载
- 5.1 运算符重载
- 5.2 赋值运算符重载
- 5.3 日期类实现
- 6. const成员函数
- 7. 取地址及const取地址操作符重载
目录
- 类的6个默认成员函数
- 构造函数
- 析构函数
- 拷贝构造函数
- 赋值运算符重载
- const成员函数
- 取地址及const取地址操作符重载
1. 类的6个默认成员函数
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
2. 构造函数
在写C语言代码时,大家可能会出现以下问题:
#include <iostream>
#include "Stack.h"using namespace std;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()
{//忘记调用Init,就使用Date d1;Date d2;d1.Print();d2.Print();Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);return 0;
}
因此,C++中用构造函数来解决这个问题。
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。构造函数的本质是要替代我们以前 Stack 和 Date 类中写的Init函数的功能,构造函数自动调用的特点就完美的替代了Init。
构造函数的特点:
- 函数名与类名相同。
- 无返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
- 对象实例化时系统会自动调用对应的构造函数。
//Stack.h#include <stdlib.h>class Stack
{
public:Stack();void Push(int x);private:int* _a;int _top;int _capacity;
};
//Stack.cpp#include "Stack.h"Stack::Stack()
{_a = (int*)malloc(sizeof(int) * 4);_top = 0;_capacity = 4;
}void Stack::Push(int x)
{//..._a[_top++] = x;
}
//Test.cpp#include <iostream>
#include "Stack.h"using namespace std;class Date
{
public:Date(){_year = 1;_month = 1;_day = 1;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;//年int _month;//月int _day;//日
};int main()
{Date d1;Date d2;d1.Print();d2.Print();Stack st1;st1.Push(1);st1.Push(2);st1.Push(3);return 0;
}
- 构造函数可以重载。(多个构造函数,有多种初始化方式)
#include <iostream>
#include "Stack.h"using namespace std;class Date
{
public://它们俩构成函数重载,但是无参调用时会存在歧义//Date()//{// _year = 1;// _month = 1;// _day = 1;//}//一般情况,建议每个类,都可以写一个全缺省的构造(好用)Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;//年int _month;//月int _day;//日
};int main()
{//Date d1();//err 无法跟函数声明区分开Date d1;d1.Print();Date d2(2024, 4, 2);d2.Print();Date d3(2024);d3.Print();Date d4(2024, 4);d4.Print();return 0;
}
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
//Stack.h#include <stdlib.h>class Stack
{
public:Stack(int n = 4);void Push(int x);private:int* _a;int _top;int _capacity;
};
//Stack.cpp#include "Stack.h"Stack::Stack(int n)
{_a = (int*)malloc(sizeof(int) * n);_top = 0;_capacity = n;
}
//Test.cpp#include <iostream>
#include "Stack.h"using namespace std;class A
{
public://A(int a)A(){_a = 0;cout << "A()" << endl;}private:int _a;
};class Date
{
public://我们没写,有没有构造函数?有->编译器自动生成//内置类型/基本类型 int/char/double.../指针//自定义类型 class/struct...//编译器自动生成的构造函数,对于内置类型成员变量,没有规定要不要做处理!(有些编译器会处理)// 对于自定义类型成员变量才会调用这个成员变量所属的自定义类型的不传参就可以调用的那个构造(也就是无参数或者全缺省的构造函数)(如果你自己写了一个有参数的构造函数,会编译报错;如果我们没有自己写构造函数,就用自动生成的无参构造;写了无参/全缺省构造函数,就用自己写的,然后再用上面的规则判断要不要做处理)void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;//年int _month;//月int _day;//日A _aa;
};//自动生成的构造函数意义何在?
//两个栈实现一个队列
class MyQueue
{
private:Stack _pushst;Stack _popst;
};int main()
{Date d1;d1.Print();MyQueue q;return 0;
}
- 无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成的那个叫默认构造,实际上无参构造函数、全缺省构造函数也是默认构造,总结一下就是不传实参就可以调用的构造就叫默认构造。
- 我们不写,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于自定义类型成员变量,要求调用这个成员变量的默认构造函数初始化(自定义成员变量不管是在编译器自动生成的构造函数,还是自己写的构造函数的情况下,都会调用它自己的默认构造函数)。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决,初始化列表,我们下个章节再细细讲解。
说明: C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的原生数据类型,如:int/char/double/指针 等,自定义类型就是我们使用 class/struct 等关键字自己定义的类型。
注意: C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
#include <iostream>using namespace std;class Time
{
public:private:int _hour = 1;int _minute;int _second;
};class Date
{
public://Date(int year = 1, int month = 1, int day = 1)//{// _year = year;// _month = month;// _day = day;//}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://默认生成构造函数//内置类型没有规定要处理(可处理,可不处理,看编译器)//给缺省值int _year = 1;int _month = 1;int _day = 1;//自定义类型调用默认构造函数Time _t;
};int main()
{//Date d1(2024, 4, 9);//d1.Print();Date d2;d2.Print();return 0;
}
#include <iostream>using namespace std;class Date
{
public:Date(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;
};int main()
{Date d2(2024, 4, 9);d2.Print();//2024-4-9return 0;
}
#include <iostream>using namespace std;class Date
{
public:Date(int year, int month, int day){}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://给缺省值int _year = 1;int _month = 1;int _day = 1;
};int main()
{Date d2(2024, 4, 9);d2.Print();//1-1-1return 0;
}
总结:
- 一般情况下,构造函数都需要我们自己显示的去实现
- 只有少数情况下可以让编译器自动生成构造函数(类似MyQueue,成员全是自定义类型)
3. 析构函数
和上面一样,在写C语言代码时,可能会忘记销毁,因此,C++中用析构函数来解决这个问题。
析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,比如局部对象是存在栈帧的,函数结束栈帧销毁,它就释放了,不需要我们管,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前 Stack 实现的 Destroy 功能,而像 Date 没有Destroy,其实就是没有资源需要释放,所以严格说 Date 是不需要析构函数的。
析构函数的特点:
- 析构函数名是在类名前加上字符 ~。
- 无参数(这就说明析构函数不能重载)无返回值。 (这里跟构造类似,也不需要加void)
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,系统会自动调用析构函数。
#include <iostream>using namespace std;typedef int DataType;class Stack
{
public:Stack(size_t capacity = 3){cout << "Stack(size_t capacity = 3)" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){//CheckCapacity();_array[_size] = data;_size++;}//其他方法...~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;
};int main()
{Stack st;//析构可以显示调用,这样就类似于 Destroy 了两次st.~Stack();return 0;
}
- 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理,自定义类型成员会调用他的析构函数。
- 还需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
- 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,如Date;如果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如Stack。
#include <iostream>using namespace std;typedef int DataType;class Stack
{
public:Stack(size_t capacity = 3){cout << "Stack(size_t capacity = 3)" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}void Push(DataType data){//CheckCapacity();_array[_size] = data;_size++;}//其他方法...~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;
};class MyQueue
{
private:Stack _st1;Stack _st2;int _size = 0;
};int main()
{MyQueue q;return 0;
}
- 一个局部域的多个对象,C++规定后定义的先析构。
4. 拷贝构造函数
如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,则此构造函数也叫做拷贝构造函数,也就是说拷贝构造是一个特殊的构造函数。
拷贝构造的特点:
- 拷贝构造函数是构造函数的一个重载。
- C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。
#include <iostream>using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date(Date& d){cout << "Date(Date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://给缺省值int _year = 1;int _month = 1;int _day = 1;
};void func(Date d)
{d.Print();
}int main()
{Date d2(2024, 4, 9);func(d2);return 0;
}
- 拷贝构造函数的第一个参数必须是类类型对象的引用,使用传值方式编译器直接报错,因为语法逻辑上会引发无穷递归调用。 拷贝构造函数也可以多个参数,但是第一个参数必须是类类型对象的引用,后面的参数必须有缺省值。
#include <iostream>using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}Date(const Date& d){//this->_year = d._year;_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://给缺省值int _year = 1;int _month = 1;int _day = 1;
};int main()
{Date d2(2024, 4, 9);d2.Print();//2024-4-9//下面这两种写法是等价的//拷贝构造:用同类型的对象拷贝初始化Date d3(d2);Date d4 = d2;//这也是拷贝构造d3.Print();d4.Print();return 0;
}
- 若未显式定义拷贝构造,编译器会自动生成拷贝构造函数。自动生成的拷贝构造对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的拷贝构造。
- 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的拷贝构造就可以完成需要的拷贝,所以不需要我们显示实现拷贝构造。像Stack这样的类,虽然也都是内置类型,但是_array指向了资源,编译器自动生成的拷贝构造完成的值拷贝/浅拷贝不符合我们的需求(st1和st2的_array指向的是同一块空间,同时,他会析构两次,第二次析构会出问题,因为第一次析构已经把那块空间释放掉了,第二次析构再去释放就会出错),所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的拷贝构造会调用Stack的拷贝构造,也不需要我们显示实现MyQueue的拷贝构造。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写拷贝构造,否则就不需要。
#include <iostream>using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}//Date(const Date& d)//{// //this->_year = d._year;// _year = d._year;// _month = d._month;// _day = d._day;//}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private://给缺省值int _year = 1;int _month = 1;int _day = 1;
};typedef int DataType;class Stack
{
public:Stack(size_t capacity = 3){cout << "Stack(size_t capacity = 3)" << endl;_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;}Stack(const Stack& st){_array = (DataType*)malloc(sizeof(DataType) * st._capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}memcpy(_array, st._array, sizeof(DataType) * st._size);_size = st._size;_capacity = st._capacity;}void Push(DataType data){//CheckCapacity();_array[_size] = data;_size++;}bool Empty(){return 0 == _size;}DataType Top(){return _array[_size - 1];}void Pop(){--_size;}//其他方法...~Stack(){cout << "~Stack()" << endl;if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}}private:DataType* _array;int _capacity;int _size;
};class MyQueue
{
private:Stack _st1;Stack _st2;int _size = 0;
};int main()
{Date d2(2024, 4, 9);Date d4 = d2;d4.Print();Stack st1(10);st1.Push(1);st1.Push(1);st1.Push(1);Stack st2 = st1;st2.Push(2);st2.Push(2);while (!st2.Empty()){cout << st2.Top() << " ";st2.Pop();}cout << endl;while (!st1.Empty()){cout << st1.Top() << " ";st1.Pop();}cout << endl;MyQueue q1;MyQueue q2(q1);return 0;
}
- 自定义类型传值返回会产生一个临时对象(临时对象具有常性)调用拷贝构造;传值引用返回,返回的是返回对象的别名(引用),没有产生拷贝,但是如果返回对象是一个当前函数局部域的局部对象,函数结束就销毁了,那么使用引用返回是有问题的,这时的引用相当于一个野引用,类似一个野指针一样。传引用返回可以减少拷贝,但是一定要确保返回对象,在当前函数结束后还在,才能用引用返回。
5. 赋值运算符重载
5.1 运算符重载
- C++为了增强代码的可读性引入了运算符重载:当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
- 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};//重载成全局,无法访问私有成员
//1、提供这些成员get和set
//2、友元 后面会讲
//3、重载为成员函数(一般使用这种)bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}int main()
{Date d3(2024, 4, 14);Date d4(2024, 4, 15);//显式调用operator==(d3, d4);//直接写,转换调用,编译器会转换成operator==(d3, d4);d3 == d4;return 0;
}
- 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}private:int _year;int _month;int _day;
};int main()
{Date d3(2024, 4, 14);Date d4(2024, 4, 15);//显式调用d3.operator==(d4);//转换调用 等价于d3.operator==(d4);d3 == d4;//优先在类里面找,然后在全局找,都找不到报错(如果类和全局都有,不会报错,调用类中的)return 0;
}
- 运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
- 不能通过连接语法中没有的符号来创建新的操作符:比如operator@。
- .* :: sizeof ?: . 注意以上5个运算符不能重载。(选择题里面常考,大家要记一下)
#include <iostream>using namespace std;class OB
{
public:void func(){cout << "void func()" << endl;}
};typedef void(OB::* PtrFunc)();//成员函数指针类型int main()
{//成员函数要加&才能取到函数指针PtrFunc fp = &OB::func;//定义成员函数指针fp指向函数funcOB temp;//定义OB类对象temp(temp.*fp)();return 0;
}
- 重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
- 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator+就没有意义。
- 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。
- 重载 << 和 >> 时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了 对象 << cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。
5.2 赋值运算符重载
赋值运算符重载是一个默认成员函数(不能重载为全局函数),用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。
赋值运算符重载的特点:
- 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引用,否则会传值传参会有拷贝。
- 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
- 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
- 像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显式实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_array指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显式实现MyQueue的赋值运算符重载。这里还有一个小技巧:如果一个类显式实现了析构并释放资源,那么他就需要显式写赋值运算符重载,否则就不需要。
#include <iostream>using namespace std;class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date& operator=(const Date& d){//检测是否自己给自己赋值if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 4, 14);//拷贝构造//一个已经存在的对象,拷贝给另一个要创建初始化的对象Date d2(d1);Date d3 = d1;Date d4(2024, 5, 1);//赋值拷贝/赋值重载//一个已经存在的对象,拷贝赋值给另一个已经存在的对象d1 = d4;d1 = d2 = d4;d1 = d1;return 0;
}
5.3 日期类实现
日期加天数:
日期减天数:
流插入:
//Date.h#include <iostream>
#include <assert.h>using namespace std;class Date
{//友元函数声明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);public:Date(int year = 1900, int month = 1, int day = 1);void Print();//直接定义在类里面,它默认是inline//频繁调用int GetMonthDay(int year, int month){assert(month > 0 && month < 13);static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if (2 == month && (0 == year % 4 && year % 100 != 0) || (0 == year % 400)){return 29;}else{return monthDayArray[month];}}bool CheckDate();bool operator< (const Date& d);bool operator<= (const Date& d);bool operator> (const Date& d);bool operator>= (const Date& d);bool operator== (const Date& d);bool operator!= (const Date& d);//d1 += 100Date& operator+=(int day);Date operator+(int day);//d1 -= 100Date& operator-=(int day);//d1 - 100Date operator-(int day);//d1 - d2int operator-(const Date& d);//++d1 -> d1.operator++()Date& operator++();//d1++ -> d1.operator++(1)//为了区分,构成重载,给后置++,强行增加了一个int形参//这里不需要写形参名,因为接收值是多少不重要,也不需要用//这个参数仅仅是为了跟前置++构成重载区分Date operator++(int);Date& operator--();Date operator--(int);//流插入//不建议,因为Date* this占据了第一个参数位置,使用d << cout 不符合习惯//void operator<<(ostream& out);private:int _year;int _month;int _day;
};//重载
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
//Date.cpp#include "Date.h"bool Date::CheckDate()
{if (_month < 1 || _month > 12|| _day < 1 || _day > GetMonthDay(_year, _month)){return false;}else{return true;}
}Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;if (!CheckDate()){cout << "日期非法" << endl;}
}void Date::Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}bool Date::operator< (const Date& d)
{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}else if (_month == d._month){return _day < d._day;}}return false;
}bool Date::operator<= (const Date& d)
{return *this < d || *this == d;
}bool Date::operator> (const Date& d)
{return !(*this <= d);
}bool Date::operator>= (const Date& d)
{return !(*this < d);
}bool Date::operator== (const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}bool Date::operator!= (const Date& d)
{return !(*this == d);
}//d1 += 50
//d1 += -50
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (13 == _month){++_year;_month = 1;}}return *this;
}//d1 + 50
//Date Date::operator+(int day)
//{
//
// Date tmp = *this;
// tmp._day += day;
//
// while (tmp._day > GetMonthDay(tmp._year, tmp._month))
// {
// tmp._day -= GetMonthDay(tmp._year, tmp._month);
// ++tmp._month;
//
// if (13 == tmp._month)
// {
// ++tmp._year;
// tmp._month = 1;
// }
// }
//
// return tmp;
//}Date Date::operator+(int day)
{Date tmp = *this;tmp += day;return tmp;
}//d1 -= 100
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day <= 0){--_month;if (0 == _month){_month = 12;_year--;}//借上一个月的天数_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator-(int day)
{Date tmp = *this;tmp -= day;return tmp;
}//++d1
Date& Date::operator++()
{*this += 1;return *this;
}//d1++
//能用前置++就用前置++,因为后置++消耗更大(需要两次拷贝构造)
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}Date& Date::operator--()
{*this -= 1;return *this;
}Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}//d1 - d2
int Date::operator-(const Date& d)
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}//void Date::operator<<(ostream& out)
//{
// out << _year << "年" << _month << "月" << _day << "日" << endl;
//}ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}istream& operator>>(istream& in, Date& d)
{cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "日期非法" << endl;}return in;
}
//Test.cpp#include "Date.h"void TestDate1()
{Date d1(2024, 4, 14);Date d2 = d1 + 30000;d1.Print();d2.Print();Date d3(2024, 4, 14);Date d4 = d3 - 5000;d3.Print();d4.Print();Date d5(2024, 4, 14);d5 += -5000;d5.Print();
}void TestDate2()
{Date d1(2024, 4, 14);Date d2 = ++d1;d1.Print();d2.Print();Date d3 = d1++;d1.Print();d3.Print();
}void TestDate3()
{Date d1(2024, 4, 14);Date d2(2034, 4, 14);int n = d1 - d2;cout << n << endl;n = d2 - d1;cout << n << endl;}void TestDate4()
{Date d1(2024, 4, 14);Date d2 = d1 + 30000;//operator<<(cout, d1)cout << d1;cout << d2;cin >> d1 >> d2;cout << d1 << d2;//operator<<想重载为成员函数,可以,但是用起来不符合正常逻辑,不建议这样处理,建议重载为全局函数//d1.operator<<(cout);//d1 << cout;
}int main()
{//提高输入输出的效率,一般在竞赛里面会有ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);//TestDate1();//TestDate2();//TestDate3();TestDate4();return 0;
}
6. const成员函数
- 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
- const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* const this
因此,Date类中不需要修改成员变量的成员函数最好都加上const
#include "Date.h"void TestDate5()
{const Date d1(2024, 4, 14);d1.Print();d1 + 100;Date d2(2024, 4, 25);d2.Print();d2 += 100;d1 < d2;d2 < d1;
}int main()
{TestDate5();return 0;
}
7. 取地址及const取地址操作符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,一般这两个函数编译器自动生成的就可以够我们用了,不需要去显式实现。除非一些很特殊的场景,比如我们不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址。
#include <iostream>using namespace std;class A
{
public://我们不实现,编译器会自己实现,我们实现了编译器就不会自己实现了//一般不需要我们自己实现//除非不想让别人取到这个类型对象的真实地址A* operator&(){return this;}const A* operator&() const{return this;}private:int _a1 = 1;int _a2 = 2;int _a3 = 3;
};int main()
{A aa1;const A aa2;cout << &aa1 << endl;cout << &aa2 << endl;return 0;
}
相关文章:
类和对象(中)
文章目录 目录1. 类的6个默认成员函数2. 构造函数3. 析构函数4. 拷贝构造函数5. 赋值运算符重载5.1 运算符重载5.2 赋值运算符重载5.3 日期类实现 6. const成员函数7. 取地址及const取地址操作符重载 目录 类的6个默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载cons…...
编程之路,从0开始:预处理详解(完结篇)
Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路! 我的博客:<但凡. 我的专栏:编程之路 这一篇预处理详解是我们C语言基础内容学习的最后一篇,也是我们的专栏ÿ…...
[chrome]黑色界面插件,PDF合并插件
Dark Reader_chrome插件下载,最新浏览器扩展,crx离线安装包 - 插件小屋 合并 PDF_chrome插件下载,最新浏览器扩展,crx离线安装包 - 插件小屋 下载的zip包解压成crx,然后把后缀名改为rar,然后解压,再导入解压的目录。...
【c语言】文件操作详解 - 从打开到关闭
文章目录 1. 为什么使用文件?2. 什么是文件?3. 如何标识文件?4. 二进制文件和文本文件?5. 文件的打开和关闭5.1 流和标准流5.1.1 流5.1.2 标准流 5.2 文件指针5.3 文件的打开和关闭 6. 文件的读写顺序6.1 顺序读写函数6.2 对比一组…...
AIGC--AIGC与人机协作:新的创作模式
AIGC与人机协作:新的创作模式 引言 人工智能生成内容(AIGC)正在以惊人的速度渗透到创作的各个领域。从生成文本、音乐、到图像和视频,AIGC使得创作过程变得更加快捷和高效。然而,AIGC并非完全取代了人类的创作角色&am…...
刷题日常(数据流中的中位数,逆波兰表达式求值,最长连续序列,字母异位词分组)
数据流中的中位数 描述 如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()…...
Redis突然变慢,有哪些原因?
目录 一、存在bigkey 二、如果Redis 实例设置了内存上限 maxmemory,有可能导致 Redis 变慢 三、开启了内存大页 四、使用了Swap 五、网络带宽过载 六、频繁短连接 一、存在bigkey 如果Redis实例中存储了 bigkey,那么在淘汰删除 bigkey 释放内存时&…...
Qt入门1——认识Qt的几个常用头文件和常用函数
1.头文件 ① #include <QPushButton>——“按钮”头文件; ② #include <QLabel>——“标签”头文件; ③ #include <QFont>——“字体”头文件; ④#include <QDebug>——输出相关信息; 2. 常用函数/类的基…...
Banana Pi BPI-CanMV-K230D-Zero 采用嘉楠科技 K230D RISC-V芯片设计
概述 Banana Pi BPI-CanMV-K230D-Zero 采用嘉楠科技 K230D RISC-V芯片设计,探索 RISC-V Vector1.0 的前沿技术,选择嘉楠科技的 Canmv K230D Zero 开发板。这款创新的开发板是由嘉楠科技与香蕉派开源社区联合设计研发,搭载了先进的勘智 K230D 芯片。 K230…...
CSS笔记(一)炉石传说卡牌设计1
目标 我要通过html实现一张炉石传说的卡牌设计 问题 其中必须就要考虑到各个元素的摆放,形状的调整来达到满意的效果。通过这个联系来熟悉一下CSS的基本操作。 1️⃣ 基本概念 在CSS里面有行元素,块元素,内联元素,常见的行元…...
怎么在宿主机上通过ssh连接虚拟机 VirtualBox 中的linux系统
通过 Xshell 连接 VirtualBox 中的 linux 虚拟机,您需要确保以下几个步骤都正确配置: 1. 配置 VirtualBox 网络 您需要将 VirtualBox 虚拟机的网络适配器设置为支持 SSH 连接的模式: 打开 VirtualBox,选择您的 Ubuntu 虚拟机&am…...
Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图
用户打开 PDF 文档时,他们会看到 PDF 的初始视图。默认情况下,打开 PDF 时不会显示书签面板或缩略图面板。在本文中,我们将演示如何设置文档属性,以便每次启动文件时都会打开书签面板或缩略图面板。 Spire.PDF for .NET 是一款独…...
241125学习日志——[CSDIY] [InternStudio] 大模型训练营 [17]
CSDIY:这是一个非科班学生的努力之路,从今天开始这个系列会长期更新,(最好做到日更),我会慢慢把自己目前对CS的努力逐一上传,帮助那些和我一样有着梦想的玩家取得胜利!!&…...
Centos 7 安装 Docker 最新版本
文章目录 一、卸载旧版本二、安装最新版本docker三、问题解决3.1 启动docker报错3.2 启动容器报错 一、卸载旧版本 #如果之前安装过旧版本的Docker,可以使用下面命令卸载 yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest …...
JavaScript的基础数据类型
一、JavaScript中的数组 定义 数组是一种特殊的对象,用于存储多个值。在JavaScript中,数组可以包含不同的数据类型,如数字、字符串、对象、甚至其他数组。数组的创建有两种常见方式: 字面量表示法:let fruits [apple…...
网络安全-------防止被抓包
1.Ios应用网络安全之https 安全套接字层 (Secure Socket Layer, SSL) 是用来实现互联网安全通信的最普遍的标准。Web 应用程序使用 HTTPS(基于 SSL 的 HTTP),HTTPS 使用数字证书来确保在服务器和客户端之间进行安全、加密的通信。在 SSL 连接…...
【Linux】认识进程以及进程的状态
目录 认识进程 基本概念 查看进程 父子进程 进程的状态 进程排队 运行状态 阻塞状态 挂起状态 僵尸进程 孤儿进程 认识进程 基本概念 有些教材上会说:正在运行的程序就是进程。这并没有错误,但是太过于笼统。现在我们深入到Linux底层来了解…...
Parker派克防爆电机在实际应用中的安全性能如何保证?
Parker防爆电机确保在实际应用中的安全性能主要通过以下几个方面来保证: 1.防爆外壳设计:EX系列电机采用强大的防爆外壳,设计遵循严格的防爆标准,能够承受内部可能发生的爆炸而不破损,利用间隙切断原理,防…...
11超全局变量php
超级全局变量是指在php任意脚本下都可以使用 PHP 超级全局变量列表: $GLOBALS:是PHP的一个超级全局变量组,在一个PHP脚本的全部作用域中都可以访问。 $_SERVER:$_SERVER 是一个PHP内置的超级全局变量,它是一个包含了诸如头信息(header)、路…...
路由器中继与桥接
一 . 背景 现在的路由器大多数已经开始支持多种网络连接模式,以下将以TP-Link迷你无线路由器为例进行展开介绍。在TP-Link迷你无线路由器上一般有AP(接入点)模式,Router(无线路由)模式,Repeate…...
[算法] 前缀函数与KMP算法
前缀函数 前缀函数 n x t [ i ] nxt[i] nxt[i]定义为 子串 s [ 1 … i ] s[1\dots i] s[1…i]最长的相等的真前缀与真后缀的长度。 计算前缀函数 scanf("%s",b1);lbstrlen(b1);int j0;nxt[1]0;for(int i2;i<lb;i){while(j&&b[j1]!b[i]) jnxt[j];if(b[j…...
电子学习中的关键游戏化元素
游戏化彻底改变了电子学习领域,提供了一种使学习具有吸引力、互动性和有效性的方法。通过将类似游戏的功能集成到教育平台中,教育工作者可以增强动力,提高知识记忆,并创造动态的学习体验。游戏化的关键要素为设计与学习者产生共鸣…...
通过端口测试验证网络安全策略
基于网络安全需求,项目中的主机间可能会有不同的网络安全策略,这当然是好的,但很多时候,在解决网络安全问题的时候,同时引入了新的问题,如k8s集群必须在主机间开放udp端口,否则集群不能正常的运…...
Mac配置maven环境及在IDEA中配置Maven
Mac配置maven环境及在IDEA中配置Maven 1. 介绍 Maven是一款广泛用于Java等JVM语言项目的工具,它以项目对象模型(POM)为基础进行项目管理,通过POM文件来定义项目信息和依赖关系。同时,它也是构建自动化工具࿰…...
TCP流套接字编程
TCP流套接字与UDP数据报套接字对比 API介绍 TCP协议核心的特点是面向字节流,是通过读取数据(单位字节)来完成通信。 TCP套接字与UDP套接字不同的点在于TCP是通过建立连接,使用输入流和输出流的方式完成通信。 ServerSocket 是…...
《硬件架构的艺术》笔记(七):处理字节顺序
介绍 本章主要介绍字节顺序的的基本规则。(感觉偏软件了,不知道为啥那么会放进《硬件架构的艺术》这本书)。 定义 字节顺序定义数据在计算机系统中的存储格式,描述存储器中的MSB和LSB的位置。对于数据始终以32位形式保存在存储器…...
IDEA2024如何创建Web项目以及配置Tomcat
在Web项目的开发过程中,Tomcat作为一款开源的Servlet容器,扮演着至关重要的角色。它不仅能够提供稳定的运行环境,还支持多种Java EE规范,为开发者提供了丰富的功能支持。因此,正确配置Tomcat服务器对于确保Web项目的顺…...
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
1. 谷歌浏览器安装及使用流程 1.1 准备篡改猴扩展程序包。 因为谷歌浏览器的扩展商城打不开,所以需要准备一个篡改猴压缩包。 其他浏览器只需打开扩展商城搜索篡改猴即可。 没有压缩包的可以进我主页下载。 也可直接点击下载:Chrome浏览器篡改猴(油猴…...
java学习记录12
ArrayList方法总结 构造方法 ArrayList() 构造一个初始容量为 10 的空列表。 ArrayList(int initialCapacity) 构造一个具有指定初始容量的空列表。 实例方法 add(int index, E element) 在此list中的指定位置插入指定元素。 ArrayList<Integer> array…...
网络协议之DNS
一、DNS概述 域名系统(Domain Name System,缩写:DNS)是互联网的一项服务。它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。DNS使用TCP和UDP端口53,通过递归查询请求的方式来…...
第02章_MySQL环境搭建(基础)
1. MySQL 的卸载 1.1 步骤1:停止 MySQL 服务 在卸载之前,先停止 MySQL8.0 的服务。按键盘上的 “Ctrl Alt Delete” 组合键,打开“任务管理器”对话 框,可以在“服务”列表找到“MySQL8.0” 的服务,如果现在“正在…...
反向代理模块
1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当于…...
【强化学习的数学原理】第05课-蒙特卡洛方法-笔记
学习资料:bilibili 西湖大学赵世钰老师的【强化学习的数学原理】课程。链接:强化学习的数学原理 西湖大学 赵世钰 文章目录 一、通过例子介绍蒙特卡洛二、 MC Basic 算法介绍三、MC Basic 算法例子例1:MC Baxic算法例2:episode le…...
Spring源码(十三):Spring全系列总结
Spring总结篇,不同于之前抽丝剥茧式地纵向深入源码,本次从横向的角度出发,希望可以带个读者一个完全不同的Spring视角。 2024年重置版,搞点不一样的东西。希望通过本篇的内容,将之前的文章全部给串起来。 相关前文: Spring Boot启动加载Spring Web请求处理流程Spring上…...
算法日记 33 day 动态规划(打家劫舍,股票买卖)
今天来看看动态规划的打家劫舍和买卖股票的问题。 上题目!!!! 题目:打家劫舍 198. 打家劫舍 - 力扣(LeetCode) 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金…...
从零开始打造个人博客:我的网页设计之旅
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 ✨特色专栏:…...
用python将一个扫描pdf文件改成二值图片组成的pdf文件
使用墨水屏读书现在似乎越来越流行,这确实有一定的好处,例如基本不发热,电池续航时间超长,基本不能游戏所以有利于沉浸式阅读,还有不知道是不是真的有用的所谓防蓝光伤害。但是,如果阅读的书籍是扫描图片组…...
Electron开发构建工具electron-vite(alex8088)添加VueDevTools(VitePlugin)
零、介绍 本文章的electron-vite指的是这个项目👉electron-vite仓库,electron-vite网站 本文章的VueDevTools指的是VueDevTools的Vite插件版👉https://devtools.vuejs.org/guide/vite-plugin 一、有一个用electron-vite创建的项目 略 二、…...
服务器数据恢复—raid5阵列热备盘上线失败导致EXT3文件系统不可用的数据恢复案例
服务器数据恢复环境: 两组分别由4块SAS硬盘组建的raid5阵列,两组阵列划分的LUN组成LVM架构,格式化为EXT3文件系统。 服务器故障: 一组raid5阵列中的一块硬盘离线。热备盘自动上线替换离线硬盘,但在热备盘上线同步数据…...
网络安全基础——网络安全法
填空题 1.根据**《中华人民共和国网络安全法》**第二十条(第二款),任何组织和个人试用网路应当遵守宪法法律,遵守公共秩序,遵守社会公德,不危害网络安全,不得利用网络从事危害国家安全、荣誉和利益,煽动颠…...
go-rod vs Selenium:自动化测试工具的比较与选择
自动化测试是软件开发过程中的关键环节,它能够帮助我们发现缺陷、验证功能并提高软件质量。随着Web技术的快速发展,市场上出现了多种自动化测试工具,其中Selenium和go-rod是两个备受关注的选择。本文将从多个维度对这两个工具进行比较&#x…...
Ubuntu20.04+ROS 进行机械臂抓取仿真:环境搭建(一)
目录 一、从官网上下载UR机械臂 二、给UR机械臂添加夹爪 三、报错解决 本文详细介绍如何在Ubuntu20.04ROS环境中为Universal Robots的UR机械臂添加夹爪。首先从官方和第三方源下载必要的软件包,包括UR机械臂驱动、夹爪插件和相关依赖。然后,针对gazeb…...
Pytorch微调深度学习模型
在公开数据训练了模型,有时候需要拿到自己的数据上微调。今天正好做了一下微调,在此记录一下微调的方法。用Pytorch还是比较容易实现的。 网上找了很多方法,以及Chatgpt也给了很多方法,但是不够简洁和容易理解。 大体步骤是&…...
PPT分享 | IBM集团业务流程架构顶层规划-订单到交付-销售到回款方案
PPT下载链接见文末~ IBM业务流程规划方法是一套结构化、体系化的流程设计理论,其企业流程框架(EPF)是一种用于企业业务流程架构设计梳理的方法论。 一、IBM业务流程规划方法的核心 IBM的BPM(业务流程管理)流程管理体…...
后端并发编程操作简述 Java高并发程序设计 六类并发容器 七种线程池 四种阻塞队列
目录 并发集合 1. ConcurrentHashMap: 2. CopyOnWriteArrayList: 3. CopyOnWriteArraySet: 4. BlockingQueue系列: 5. ConcurrentSkipListMap 和 ConcurrentSkipListSet: 6. ConcurrentLinkedDeque:…...
基于LLama_factory的Qwen2.5大模型的微调笔记
Qwen2.5大模型微调记录 LLama-facrotyQwen2.5 模型下载。huggingface 下载方式Modelscope 下载方式 数据集准备模型微调模型训练模型验证及推理模型导出 部署推理vllm 推理Sglang 推理 LLama-facroty 根据git上步骤安装即可,要求的软硬件都装上。 llama-factory运行…...
resnet50,clip,Faiss+Flask简易图文搜索服务
一、实现 文件夹目录结构: templates -----upload.html faiss_app.py 前端代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widt…...
亚信安全与飞书达成深度合作
近日,亚信安全联合飞书举办的“走近先进”系列活动正式走进亚信。活动以“安全护航信息化 共筑数字未来路”为主题,吸引了众多数字化转型前沿企业的近百位领导参会。作为“走近先进”系列的第二场活动,本场活动更加深入挖掘了数字化转型的基础…...
Spring框架特性及包下载(Java EE 学习笔记04)
1 Spring 5的新特性 Spring 5是Spring当前最新的版本,与历史版本对比,Spring 5对Spring核心框架进行了修订和更新,增加了很多新特性,如支持响应式编程等。 更新JDK基线 因为Spring 5代码库运行于JDK 8之上,所以Spri…...
.net 8使用hangfire实现库存同步任务
C# 使用HangFire 第一章:.net Framework 4.6 WebAPI 使用Hangfire 第二章:net 8使用hangfire实现库存同步任务 文章目录 C# 使用HangFire前言项目源码一、项目架构二、项目服务介绍HangFire服务结构解析HangfireCollectionExtensions 类ModelHangfireSettingsHttpAuthInfoUs…...