C++类和对象中篇

在这里插入图片描述

🐇

🔥博客主页: 云曦
📋系列专栏:[C++]

💨路漫漫其修远兮 吾将而求索
💛 感谢大家👍点赞 😋关注📝评论

文章目录

  • 📔前言
  • 📔1、类的六个默认成员函数
  • 📔2、构造函数
    • 📙2.1、概念
    • 📙2.2、特性
  • 📔3、析构函数
    • 📙3.1、概念
    • 📙3.2、特性
  • 📔4、拷贝构造函数
    • 📙4.1、概念
    • 📙4.2、特性
  • 📔5、运算符重载
    • 📙5.1、运算符重载
    • 📙5.2、赋值运算符重载
    • 📙5.3、前置++和后置++重载
  • 📔6、日期类的实现
    • 📙6.1、日期类比较运算符重载
      • 📄6.1.1、等于运算符重载
      • 📄6.1.2、不等于运算符重载
      • 📄6.1.3、大于运算符重载
      • 📄6.1.4、小于运算符重载
      • 📄6.1.5、大于等于运算符重载
      • 📄6.1.6、小于等于运算符重载
    • 📙6.2、日期类加天数的运算符重载
      • 📄6.2.1、日期+=天数
      • 📄6.2.2、日期+天数
      • 📄6.2.3、日期-=天数
      • 📄6.2.4、日期-天数
      • 📄6.2.5、GetMonthDay函数
    • 📙6.3、日期-日期运算符重载
    • 📙6.4、前置和后置++/- -运算符重载
      • 📄6.4.1、前置++
      • 📄6.4.1、后置++
      • 📄6.4.1、前置- -
      • 📄6.4.1、后置- -
    • 📙6.5、流插入/流提取函数重载
      • 📙6.5.1、<<运算符重载
      • 6.5.2、>>运算符重载
    • 📙6.6、解决日期类的一些bug
      • 📄6.6.1、构造函数
      • 📄6.6.2、日期-=、-、+=、+天数
  • 📔7、const成员函数
  • 📔8、取地址及const取地址运算符重载

📔前言

  • 上期讲解到了C++一个类是如何定义的即对象的实例化等。这期将讲解类的六个默认成员函数及其实现一个日期类。

📔1、类的六个默认成员函数

  • 如果一个类没有任何成员,那么这个类就称为空类。
  • 空类就真的什么都没有嘛?其实不是,任何一个什么都没有的类里,编译器其实默认生成了6个默认成员函数。
  • 默认成员函数:用户没有写,编译器会自动生成的成员函数叫作默认成员函数。
    在这里插入图片描述

📔2、构造函数

📙2.1、概念

对于Date类:

#include<iostream>
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()
{Date d1;Date d2;d1.Init(2024, 5, 2);d2.Init(2024, 5, 5);d1.Print();d2.Print();return 0;
}
  • 对于日期类来说,可以通过Init来初始化日期,但每创建一个对象都要自己调用一次,这样未免太过于麻烦了,哪有没有在创建对象时就给这个对象初始化好数据的方法呢?
  • 构造函数是一个特殊的函数,函数名与类名相同,创建对象时由编译器自动调用,以此来保证每个对象都有一个合适的初始值,并且在每个对象的生命周期里只能调用一次

📙2.2、特性

  • 构造函数之所以是一个特殊的函数那是因为,构造函数的名字叫构造但它的作用其实并不是开辟空间,而是给调用它的对象进行初始化工作
  • 构造函数的特征:
  1. 函数名与类名相同。
  2. 没有返回值(不用写返回类型)。
  3. 对象在实例化的同时,会自动调用对应的构造函数 。
  4. 构造函数可以重载。
#include<iostream>
using namespace std;class Date
{
public://无参构造函数Date() {};//带参的构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};int main()
{Date d1;//会去调用无参构造Date d2(2024, 5, 2);//会去调用有参构造return 0;
}
  • 注意: 在调用无参构造时,对象后面不能带():Date d3();(加了()这样就成了函数声明了)。
  1. 如果类内没有显示写构造函数,那么C++编译器会自动生成一个无参的构造函数,一旦用户显示写构造函数时,编译器就不会再生成了。
#include<iostream>
using namespace std;class Date
{
public:/*Date() {};Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/private:int _year;int _month;int _day;
};int main()
{//可以正常运行,因为编译器自动生成了一个无参的构造函数Date d1;return 0;
}
  1. 关于编译器默认生成的成员函数:大家会有疑惑,用户没实现函数的情况下,编译器会自动生成默认的构造函数,但在我们打印时,打印出来的都是随机值且去调试时也是随机值,就好像编译器默认生成的构造函数什么事都没做一样。
  • 解答:编译器默认生成的构造函数,并不是什么都没做,C++把类型分为了内置类型和自定义类型,编译器默认生成的构造函数对于内置类型不做处理,对自定义类型会去调用它自己的构造函数。
#include<iostream>
using namespace std;class Stack
{
public:Stack(){cout << "Stack()" << endl;}private:int* _a;int _top;int _capacity;
};//两个栈实现队列
class MyQueue
{
public:private:Stack _pushst;Stack _popst;int size;
};int main()
{MyQueue q;return 0;
}

在这里插入图片描述

  • 上述的两个栈实现队列的类就是一个很好的例子。
  • **注意:**在vs2019及之后的vs编译器里,编译器自动生成的构造函数堆内置类型都做了处理,但在vs2013里是没有处理的。这样对于学习C++的伙伴会有误导性,尽管编译器处理了我们还是当作没处理就好。
  • 在C++11中,针对内置类型成员不做初始化的缺陷,又打了补丁,即:内置类型成员变量在声明时,可以给缺省值。
#include<iostream>
using namespace std;class Stack
{
public:Stack(){cout << "Stack()" << endl;}private:int* _a;int _top;int _capacity;
};//两个栈实现队列
class MyQueue
{
public:private://自定义类型Stack _pushst;Stack _popst;//内置类型int size = 1;
};int main()
{MyQueue q;return 0;
}

在这里插入图片描述

  1. 无参构造函数和全缺省的构造函数都叫做默认构造函数,并且默认构造函数只能有一个存在(出现一个以上的默认构造函数会有二义性,编译器不知道调用哪个)。
  • **注意:**无参构造、全缺省构造函数、用户不写编译器自动生成的构造函数,都可以认为是默认构造函数。
#include<iostream>
using namespace std;class Date
{
public://无参构造函数Date() {};//全缺省构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};int main()
{//对于有一个以上默认构造函数存在的测试Date d1;//编译报错:error C2668: “Date::Date”: 对重载函数的调用不明确return 0;
}

📔3、析构函数

📙3.1、概念

  • 对于构造函数的学习,我们知道了一个对象是怎么来的,那一个对象又是怎么没的呢?
  • 析构函数:与构造函数的功能相反,析构函数并不是完全给一个对象销毁,局部对象销毁工作是由编译器自动完成的,而对象在销毁时编译器会自动调用析构函数,完成对象的资源清理工作。

📙3.2、特性

  • 析构函数跟构造函数一样也是一个特殊的函数,其特征:
  1. 析构函数名是在类名的前面加上~。
  2. 析构函数没有返回值(不用写返回类型)。
  3. 一个类里只能有一个析构函数,若为显示定义,编译器会默认生成一个析构函数。(注:析构函数不能重载)
  4. 对象的生命周期结束时,编译器会自动调用析构函数。
#include<iostream>
using namespace std;class Stack
{
public:Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}void Push(int x){if (_top == _capacity){exit(-1);}_a[_top] = x;++_top;}//析构函数~Stack(){free(_a);_a = nullptr;_top = 0;_capacity = 0;}private:int* _a;int _top;int _capacity;
};int main()
{Stack st;st.Push(1);st.Push(2);return 0;
}
  • 对象还存在时:
    在这里插入图片描述
  • 对象生命周期结束后:
    在这里插入图片描述
  1. 对于编译器自动生成的析构函数,是否完成过一些事情呢?
#include<iostream>
using namespace std;class Stack
{
public:Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}void Push(int x){if (_top == _capacity){exit(-1);}_a[_top] = x;++_top;}//析构函数~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;}private:int* _a;int _top;int _capacity;
};class MyQueue
{
public:private://自定义类型Stack _pushst;Stack _popst;//内置类型int size = 1;
};int main()
{MyQueue q;return 0;
}

在这里插入图片描述

  • 从上述可以看出,编译器生成的默认析构函数,对于自定义类型会去调用它自己的析构函数。
  1. 如果类中没有资源申请(不会在堆上开空间),我们可以不写析构函数,直接用编译器生成的默认析构函数,比如:Date类;有资源申请(会在堆上开空间)时,一定要写析构函数,否则会造成资源泄漏,比如:Stack类。

📔4、拷贝构造函数

📙4.1、概念

  • 我们在创建对象时,能否创建一个和已存在的对象一模一样的对象呢?
  • 拷贝构造函数:只有单个形参,该对象是对于本类对象的引用(一般常用const修饰),在用已存在的同类型对象去初始化另一个新对象时,由编译器自动调用。

📙4.2、特性

  • 拷贝构造函数也是特殊的成员函数,其特征为:
  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个,且必须是类类型对象的引用,使用传值方式编译器会报错,因为会引发无穷递归。
#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date d)//错误写法:编译报错,会引发无穷递归Date(const Date& d)//正确写法{cout << "Date(const Date& d)" << endl;_year = d._year;_month = d._month;_day = d._day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1(2023, 11, 11);Date d2(d1);d1.Print();d2.Print();return 0;
}

在这里插入图片描述

  • 拷贝构造函数传值传参的情况:
    在这里插入图片描述
  1. 若用户未显示写拷贝构造函数,编译器会生成一个拷贝构造函数,但编译器生成的拷贝构造函数里是按字节序完成拷贝的,这种拷贝叫作浅拷贝,或值拷贝。
//Date d1(d2);
//d1为:this;d2为d
Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
  • 浅拷贝对自定义类型带来的问题:
#include<iostream>
using namespace std;class Stack
{
public:Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;}private:int* _a;int _top;int _capacity;
};class MyQueue
{
public:private:Stack _pushst;Stack _popst;int size = 1;
};int main()
{MyQueue q1;MyQueue q2(q1);return 0;
}

在这里插入图片描述

  • 上述代码在运行时,程序崩溃了。至于为什么,接下来为大家讲解:
    在这里插入图片描述
  • 解决方法就是把拷贝构造函数写成深拷贝。
#include<iostream>
using namespace std;class Stack
{
public:Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}//深拷贝Stack(const Stack& d){_a = (int*)malloc(sizeof(int) * d._capacity);if (nullptr == _a){perror("malloc fail");exit(-1);}_capacity = d._capacity;_top = d._top;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;}private:int* _a;int _top;int _capacity;
};class MyQueue
{
public:private:Stack _pushst;Stack _popst;int size = 1;
};int main()
{MyQueue q1;MyQueue q2(q1);return 0;
}
  • 注意: 用户没写拷贝构造函数时,编译器会默认生成一个拷贝构造函数,这个拷贝构造函数内置类型成员会进行值拷贝,自定义类型成员会调用它自己的拷贝构造函数。
  • 关于深浅拷贝的问题,C++内存管理篇会详细讲解。
  1. 编译器默认生成的拷贝构造函数是值拷贝,那么我们还要显示实现吗?
  • 解答:像Date这样,类里没有申请资源的类,就没必要写了。
  • **注意:**一旦涉及申请资源的类,一定要写拷贝构造函数且实现的是深拷贝,否则就是浅拷贝。
#include<iostream>
using namespace std;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;int _month;int _day;
};int main()
{Date d1(2024, 5, 2);Date d2(d1);d1.Print();d2.Print();return 0;
}

在这里插入图片描述

  1. 拷贝构造函数的典型使用场景:
  • 使用已存在的对象去初始化一个新对象。
  • 函数参数类型为类类型对象(传值传参)
  • 函数返回值类型为类类型(传值返回)
#include<iostream>
using namespace std;class Date
{
public:Date(int year = 1, int month = 1, int day = 1){cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;}Date(const Date& d){cout << "Date(const Date& d)" << endl;}~Date(){cout << "~Date()" << endl;}private:int _year;int _month;int _day;
};Date TestDate(Date dd)
{Date tmp(dd);return tmp;
}int main()
{Date d1(2023, 12, 12);Date d2(d1);cout << endl;TestDate(d1);return 0;
}

在这里插入图片描述

  • 为了提高程序的效率,一般对象传参时,尽量使用引用类型,返回时看实际场景,能用引用就尽量用引用。

📔5、运算符重载

📙5.1、运算符重载

  • 在C++中,为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数。
  • 函数名字为:关键字operator后面紧跟需要重载的运算符符号
  • 函数原型:返回类型 operator操作符(参数列表)。
  • 注意:
  • 不能连接其他的符号来创建新的运算符重载,比如:operator@、operator#。
  • 重载操作符必须有一个类类型参数。
  • 用于内置类型的运算符其含义不能改变,比如实现+运算符重载,内部却实现的是减法。
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数是一个隐含的this指针。
  • ( .*、::、sizeof、?:、. ) 注意以上5个运算符不能重载。
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};//在类外实现==运算符重载
//在类外实现有个问题,因为类的成员变量是私有的无法访问到
//这里得让类的成员函数写成公有的,但这时候问题又有了,
//类的成员变量成为公有后,封装的意义何在
bool operator==(Date& d1, Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
  • 既然在类外无法访问到类的成员变量,那么我们就定义到类里面,让运算符重载变成类的成员函数,这样就可以访问到类的成员变量了。
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//需要注意的是:左操作数是this,右操作数是指向调用的对象//bool operator==(Date* this, Date& d)bool operator==(Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}private:int _year;int _month;int _day;
};
  • 对于运算符的重载大家些许会有疑问,既然有运算符重载那么每个类都要把能实现的运算符重载都实现吗?
  • 解答:其实并不是,对于一个类来说:实现什么的运算符重载,是看这个运算符重载对于这个类有没有实现的意义。比如:日期类里实现日期+日期有用途吗?,日期+日期没啥用嘛就不需要实现了。
  • 但如果是:日期加天数呢?唉!日期加天数不就是看多少天后的日期嘛,嗯…,这个运算符重载对日期类是有意义的,那么就实现它。
  • 思路:
    在这里插入图片描述
#include<iostream>
using namespace std;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;}//需要注意的是:左操作数是this,右操作数是指向调用的对象//bool operator==(Date* this, Date& d)bool operator==(Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}//因为12个月的天数除了二月其他都是固定的//所以写一个获取本年本月内天数的函数int GetMonthDaye(int year, int month){int Month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//判断本年的2月是否是闰年还是平年if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)){return 29;}return Month[month];}//这里传值返回会生成临时对象返回,等于会调用一次拷贝构造函数//用引用返回就不一样,因为是返回this,//this出了函数还在引用返回没有问题且引用返回不用调用拷贝构造Date& operator+(int day){_day += day;//循环结束条件:_day小于或等于当月的天数时,循环结束while (_day > GetMonthDaye(_year, _month)){_day -= GetMonthDaye(_year, _month);++_month;if (_month == 13)//判断月份是否大于12月{++_year;_month = 1;}}//this是d1对象的指针,*this就是d1return *this;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 5, 3);d1 + 50;d1.Print();return 0;
}

在这里插入图片描述

  • 上述的代码,仔细的朋友们可能已经发现问题了,这里实现的是什么,是+的运算符重载,+在内置类型里是不会改变变量本身的,而日期类实现的+运算符重载是把d1本身给修改了。显而易见这不是+运算符的重载,而是+=运算符的重载。
#include<iostream>
using namespace std;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;}bool operator==(Date& d){return _year == d._year&& _month == d._month&& _day == d._day;}int GetMonthDaye(int year, int month){int Month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)){return 29;}return Month[month];}Date& operator+=(int day){_day += day;while (_day > GetMonthDaye(_year, _month)){_day -= GetMonthDaye(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;}private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 5, 3);d1 += 50;d1.Print();return 0;
}
  • 嗯…这样就好多了。
  • 那+运算符重载要咋样实现呢?这里有的人就会说,把上面的代码复制粘贴一下,然后定义一个临时对象修改这个临时对象即可,this指向的对象就不用修改,最后返回这个临时对象。
Date operator+(int day){Date tmp(*this);tmp._day += day;while (tmp._day > GetMonthDaye(tmp._year, tmp._month)){tmp._day -= GetMonthDaye(tmp._year, tmp._month);++tmp._month;if (tmp._month == 13){++tmp._year;tmp._month = 1;}}return tmp;}

在这里插入图片描述

  • 这样确实可以,但。。。是不是过于繁琐了,我们不是已经实现了+=运算符重载了吗,直接让+复用+=就好了。
Date operator+(int day){Date tmp(*this);tmp += day;return tmp;}

📙5.2、赋值运算符重载

  • 下面的代码是拷贝构造还是赋值拷贝?
void TestDate1()
{Date d1(2024, 12, 12);Date d2 = d1;
}
  • 答案是:拷贝构造。
  • 讲解:
void TestDate1()
{Date d1(2024, 12, 12);//一个已存在的对象去拷贝初始化另一个对象,拷贝构造Date d2 = d1;//两个已存在的对象,赋值拷贝d1 = d2;
}
  1. 赋值运算符重载的格式:
  • 参数类型:const type&,传递引用提供程序的效率。
  • 返回类型:type&,因为返回的是this指针,出了函数还存在所以可以用引用返回。
  • 检测是否是自己给自己赋值。
  • 返回*this,复合连续赋值的含义。
  • 加const是避免一下的情况出现:
//*this为d2,d为d1
//原本是d2 = d1,加const是为了避免写成d1 = d2
Date& operator=(const Date& d){d._year = _year;d._month = _month;d._day = _day;}
  • 赋值运算符重载
bool operator!=(Date& d){//复用==运算符重载return !(*this == d);}Date& operator=(const Date& d){//比较地址是否不相同if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
  1. 赋值运算符只能重载成类的成员函数 ,不能重载成全局函数
//1.赋值运算符重载成全局函数,因为没有this指针需要给两个参数
//2.全局函数访问不到类内的成员变量,因为是私有的
//3.会编译报错:error C2801: “operator =”必须是非静态成员
//原因:赋值运算符重载是六个默认成员函数中的一个,用户不实现编译器会默认自动生成一个
//而编译器默认生成后的赋值运算符与我们实现的全局赋值运算符重载冲突了,
//所以赋值运算符重载只能是类的成员函数
Date& operator=(Date& d1, Date& d2)
{if (&d1 != &d2){d1._year = d2._year;d1._month = d2._month;d1._day = d2._day;}return d1;
}
  1. 用户不实现赋值运算符重载,编译器会生成一个默认的赋值运算符重载,以值的方式逐字节拷贝过去。(注:编译器生成的默认赋值运算符,内置类型会进行值拷贝,对于自定义类型会去调用对于类的赋值运算符重载)。
class Stack
{
public:Stack(int capacity = 4){_a = (int*)malloc(sizeof(int) * capacity);if (nullptr == _a){perror("malloc fail");exit(-1);}_top = 0;_capacity = capacity;}void Push(int x){if (_top == _capacity){exit(-1);}_a[_top] = x;++_top;}Stack& operator=(const Stack& st){cout << "Stack & operator=(const Stack & st)" << endl;return *this;}//析构函数~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = 0;_capacity = 0;}private:int* _a;int _top;int _capacity;
};class MyQueue
{
public:MyQueue(int size = 1){_size = size;}private://自定义类型Stack _pushst;Stack _popst;//内置类型int _size;
};int main()
{MyQueue q1(10);MyQueue q2(20);q1 = q2;return 0;
}

在这里插入图片描述

  • 注意:如果实现的类里没有申请空间的成员函数,那么就不需要去实现赋值运算符重载里,用编译器默认生成的赋值运算符重载即可。

📙5.3、前置++和后置++重载

  • ++,嗯…简单直接去复用+=即可。
//前置++Date& operator++(){*this += 1;return *this;}
  • 但主要的问题是:后置++怎么进行重载呢?
  • 有的伙伴也许会直接想,直接把++放在operator前面就行Date& ++operator(),很可惜,编译错误,没有这样的语法。那要怎样才能重载前置++呢?
  • 其实是这样的,C++为了解决后置++这个问题,弄出了占位符这个语法:
  • 在函数的形参用一个类型来进行重载,一般使用int来当占位符,但其他类型也是可以的(只是正常都用int来当) 。Date operator++(int),这个函数的参数在实参传形参时可以传也可以不传。
//后置++Date operator++(int){Date tmp = *this;*this += 1;return tmp;}
  • 注意:
  1. C++已经强制了,无参的重载为前置++,有参的重载为后置++。
  2. 内置类型的前置和后置++区别不大,但自定义类型进行++时,要考虑好再选择用前置还是后置,因为后置++的返回值必须是值返回,值返回会造成一次拷贝构造。

📔6、日期类的实现

  • 既然是实现日期这个类,那么先把日期类进行一下分文件实现。
  • Date.h(放声明)
#pragma once
#include<iostream>
using std::cout;
using std::cin;
using std::endl;class Date
{
public:Date(int year = 1, int month = 1, int day = 1);Date(const Date& d);bool operator==(Date& d);bool operator!=(Date& d);int GetMonthDaye(int year, int month);void Print();Date& operator+=(int day);Date operator+(int day);Date& operator=(const Date& d);Date& operator++();Date operator++(int);private:int _year;int _month;int _day;
};
  • Date.cpp(放函数的定义)
#include"Date.h"Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}Date::Date(const Date& d)
{cout << "Date(const Date& d)" << endl;
}void Date::Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}bool Date::operator==(Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}int Date::GetMonthDaye(int year, int month)
{int Month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)){return 29;}return Month[month];
}Date& Date::operator+=(int day)
{_day += day;while (_day > GetMonthDaye(_year, _month)){_day -= GetMonthDaye(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;
}Date Date::operator+(int day)
{Date tmp(*this);tmp += day;return tmp;
}bool Date::operator!=(Date& d)
{//复用==运算符重载return !(*this == d);
}Date& Date::operator=(const Date& d)
{//比较地址是否不相同if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}//前置++
Date& Date::operator++()
{*this += 1;return *this;
}//后置++
Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}

📙6.1、日期类比较运算符重载

  • >、<、!=、==、>=、<=,这些比较运算符对日期类都是有用的。

📄6.1.1、等于运算符重载

bool Date::operator==(Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}

📄6.1.2、不等于运算符重载

bool Date::operator!=(Date& d)
{//复用==运算符重载return !(*this == d);
}

📄6.1.3、大于运算符重载

//把正确的都判断出来,剩下的都表示d1不大于d2
bool Date::operator>(Date& d)
{//年大于就返回trueif (_year > d._year){return true;}//年相等且月大于月就返回trueelse if (_year == _year && _month > d._month){return true;}//年相等且月相等且天大于天就返回trueelse if (_year == _year && _month == d._month && _day > d._day){return true;}return false;
}

📄6.1.4、小于运算符重载

bool Date::operator<(Date& d)
{//复用==和>运算符重载return !(*this == d || *this > d);
}

📄6.1.5、大于等于运算符重载

bool Date::operator>=(Date& d)
{//复用<运算符重载return !(*this < d);
}

📄6.1.6、小于等于运算符重载

bool Date::operator<=(Date& d)
{//复用>运算符重载return !(*this > d);
}

📙6.2、日期类加天数的运算符重载

  • 一个日期+=或+天数,可以知道多少天后日期。
  • 一个日期-=或-天数,可以知道多少天前的日期。

📄6.2.1、日期+=天数

Date& Date::operator+=(int day)
{_day += day;while (_day > GetMonthDaye(_year, _month)){_day -= GetMonthDaye(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;
}

📄6.2.2、日期+天数

Date Date::operator+(int day)
{Date tmp(*this);tmp += day;return tmp;
}

📄6.2.3、日期-=天数

在这里插入图片描述

Date& Date::operator-=(int day)
{_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDaye(_year, _month);}return *this;
}

在这里插入图片描述

📄6.2.4、日期-天数

Date Date::operator-(int day)
{//和+运算符重载一样//复用-=运算符重载Date tmp(*this);tmp -= day;return tmp;
}

在这里插入图片描述

📄6.2.5、GetMonthDay函数

int Date::GetMonthDaye(int year, int month)
{int Month[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//小细节:把2月放到前面判断,可以减少其他月的判断if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)){return 29;}return Month[month];
}

📙6.3、日期-日期运算符重载

  • 这个运算符重载还是有用的,日期减日期得到相差的天数。
int Date::operator-(Date& d)
{//左大右小相差的天数是正数int flag = 1;//d1 - d2//假设左大右小Date Max = *this;Date Min = d;//假设错了,改一下if (*this < d){Max = d;Min = *this;flag = -1;//左小右大相差的天数是负数}int n = 0;//循环累加天数while (Min != Max){++Min;++n;}return n * flag;
}

📙6.4、前置和后置++/- -运算符重载

📄6.4.1、前置++

Date& Date::operator++()
{*this += 1;return *this;
}

📄6.4.1、后置++

Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}

📄6.4.1、前置- -

Date& Date::operator--()
{*this -= 1;//复用-=return *this;
}

📄6.4.1、后置- -

Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}

📙6.5、流插入/流提取函数重载

  • 在这个日期类的实现到现在,始终是在用实现的Print成员函数来输出日期,那么为什么不用cout打印呢。
  • 其实<<、>>,流插入和流提取也是可以进行运算符重载的,当我们要用流插入输出一个类时,编译器不知道你要怎么输出这个类,所以用流插入输出时需要先实现这个运算符的重载。
  • 注意:
  1. <<运算符重载的返回值是流插入,即:ostream
  2. >>运算符重载的返回值是流提取,即:istream

📙6.5.1、<<运算符重载

  • 类内实现
//引用返回,支持<<在一行内可以连续插入
ostream& Date::operator<<(ostream& out)
{out << _year << "-" << _month << "-" << _day << endl;return out;
}
  • 实现完后会发现问题:
void TestDate4()
{Date d1(2024, 12, 12);cout << d1 << endl;//编译报错:error C2679: 二元“<<”: //没有找到接受“Date”类型的右操作数的运算符(或没有可接受的转换)
}
  • 编译报错,怎么回事!其实类内实现有个特点,语法上的传参:cout << d1 << endl; -> cout << d1.operator(cout) << endl; -> cout << d1.operator(&d1,cout) << endl;
  • 类的成员函数传参时,对象一定是占用第一个参数位置的(this指针永远是第一个参数)。
  • 类内实现的输入输出运算符重载,得这样调用d1<<cout<<endl,可这样就不易读懂,所以输出输入运算符重载,只能在类外实现,让插入或提取流做第一个参数。
  • 类外实现
ostream& operator<<(ostream& out, Date& d)
{out << d._year << "-" << d._month << "-" << d._day << endl;return out;
}
  • 这样又出现问题了,类的成员变量是私有的无法访问到,写成公有的就不符合C++封装的概念了。
  • 解决方法:
  1. 写提供成员变量值的函数出来,例如:GetYear、GetMonst、GetDay。
  2. 友元(关键字:friend),举例:我的是我的,你的也是我的,但我的不是你的(这里只是让大家了解一下友元,具体内容会到类和对象下讲解)。
  • 这里用友元来解决这里的问题。
    在这里插入图片描述

6.5.2、>>运算符重载

  • 提取流运算符重载也跟插入流运算符一样,必须在类外实现成全局函数,通过友元方式访问类的成员变量。
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}

📙6.6、解决日期类的一些bug

📄6.6.1、构造函数

Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
  • 这里实现的构造函数有一个问题,年月日都没有小于等于0和月大于12,天大于当月的情况,所以构造函数这里要检查一下初始化的日期是否正确。
  • 不合法的日期我们可以采集一下任意一个措施:
  1. assert(false); 直接强制报错
  2. exit(-1); 直接终止程序
  3. 先打印一下日期,然后打印非法日期。
  • 这里我采用第三中措施。
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;if (_year <= 0 || _month <= 0 || _month > 12 || _day <= 0 || _day > GetMonthDaye(year, month)){Print();cout << "非法日期" << endl;}}

📄6.6.2、日期-=、-、+=、+天数

  • 一个日期 + 天数可以得到这个天数后的日期,如果加的是负数,那就成这个天数前的日期了,提前处理一下比较好。
  • -=运算符重载处理
Date& Date::operator-=(int day)
{if (day < 0){return *this += (-day);}_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDaye(_year, _month);}return *this;
}
  • -运算符重载处理
Date Date::operator-(int day)
{Date tmp(*this);if (day < 0){return tmp += (-day);}tmp -= day;return tmp;
}
  • +=运算符重载处理
Date& Date::operator+=(int day)
{if (day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDaye(_year, _month)){_day -= GetMonthDaye(_year, _month);++_month;if (_month == 13){++_year;_month = 1;}}return *this;
}
  • +运算符重载处理
Date Date::operator+(int day)
{Date tmp(*this);if (day < 0){return tmp -= (-day);}tmp += day;return tmp;
}

📔7、const成员函数

  • const修饰的“成员函数”叫作const成员函数,const修饰的成员函数实际上修饰的是成员函数隐含的this指针,表示在该成员函数内不能修改任何成员变量。
  • 注意:
  1. 非const对象也可以调用const成员函数。
  2. const成员函数是只读状态,建议大家实现成员函数时,如果成员变量不做修改那么就加上const,增加程序的安全性。
  3. 像流插入、流提取这样在类外重载的函数,就不用加const了,因为类外实现的函数没有隐含的this指针。
  • 在函数声明后面加const成为const成员函数。例如:日期类的比较运算符重载,不修改成员变量那么是可以加const的。
class Date
{friend ostream& operator<<(ostream& out, Date& d);friend istream& operator>>(istream& in, Date& d);public:Date(int year = 1, int month = 1, int day = 1);bool operator==(Date& d) const;bool operator!=(Date& d) const;bool operator>(Date& d) const;bool operator<(Date& d) const;bool operator>=(Date& d) const;bool operator<=(Date& d) const;int GetMonthDaye(int year, int month) const;void Print() const;Date& operator+=(int day);Date operator+(int day) const;Date& operator-=(int day);Date operator-(int day) const;int operator-(Date& d);Date& operator=(const Date& d);Date& operator++();Date operator++(int);Date& operator--();Date operator--(int);private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out, Date& d);
istream& operator>>(istream& in, Date& d);
  • 在类里加const时需要注意以下几个问题:
  1. const对象可以调用非const成员函数吗? – 不可以(权限的放大)
  2. 非const对象可以调用const成员函数吗?-- 可以(权限的缩小)
  3. const成员函数内可以调用其它的非const成员函数吗? – 不可以(权限的放大)
  4. 非const成员函数内可以调用其它的const成员函数吗? – 可以(权限的缩小)

📔8、取地址及const取地址运算符重载

	Date* operator&();const Date* operator&() const;
  • 这两个默认成员函数,一般不用实现,因为编译器会默认生成。
Date* Date::operator&()
{return this;
}const Date* Date::operator&() const
{return this;
}
  • 这两个函数不需要重载,使用编译器生成的默认取地址重载即可,除非是特殊情况,才需要重载,比如:想让别人获取特定的内容!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/8769.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

视频汇聚边缘网关EasyCVR硬件设备无法访问域名,解析失败该如何处理?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。视频汇聚融合管理平台EasyCVR既具备传统安防视…

小麦穗检测数据集VOC+YOLO格式6508张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;6508 标注数量(xml文件个数)&#xff1a;6508 标注数量(txt文件个数)&#xff1a;6508 标注…

实践指南:如何将SpringBoot项目无缝部署到Tomcat服务器

序言 SpringBoot 是一个用来简化 Spring 应用初始搭建以及开发过程的框架&#xff0c;我们可以通过内置的 Tomcat 容器来轻松地运行我们的应用。但在生产环境中&#xff0c;我们可能需要将应用部署到独立的 Tomcat 服务器上。本文给大家介绍 SpringBoot 项目部署到独立 Tomcat…

JS-拖拽元素放大缩小

效果左右布局&#xff0c;拖拽后&#xff0c;宽度放大缩小 其实自己写也可以&#xff0c;不过还是发现了两个好用的js库&#xff0c;既然不需要自己写&#xff0c;当然是能偷懒就偷懒 1、resizerjs 官网地址&#xff1a;https://github.com/eknowles/resizerjs <!doctype …

ssrf初步

一&#xff0c;简介 全称&#xff1a;Server-Side Request Forgery&#xff08;中文&#xff1a;服务器端请求伪造&#xff09; 攻击者从服务端发起请求&#xff0c;让服务器连接任意外部系统&#xff0c;从而泄露敏感数据。主要利用各种协议的请求伪造&#xff0c;例如php协…

IDEA使用Maven生成普通项目没有生成iml文件解决方法

右击主目录选择&#xff1a; Open in Terminal 在生成的控制台输入&#xff1a; mvn idea:module 回车便自动生成iml文件啦&#xff01; 双击下主目录就可以看见啦

数据分析概念定义和发展前景

数据分析概念定义和发展前景 前言一、数据分析概念二、数据的定义数据的定义数据的分类定性数据定量数据 三、数据的价值数据为什么具有价值 四、数据分析的目的对于企业来说总结 五、数据分析类型的划分描述性统计分析探索性数据分析传统的统计分析方法验证性数据分析 六、 数…

【亲测可用】linux centos7.9 快速安装python3环境 手把手实操教程

安装好linux centos7.9 默认只有python2的环境如下&#xff1a; python2.7.5这个很老旧的版本了&#xff0c;有很多新库不支持&#xff0c;性能可能也不行。 接下来快速安装python3环境&#xff0c;并设置源做到快速安装包&#xff0c;设置虚拟环境&#xff0c;打造强大的pyth…

MacOS搭建docker本地私有镜像库

相关环境 macOS: bigsur 11.7.8 docker desktop: 4.22.0 docker engine: 24.0.5 准备工作 本机已经安装好docker desktop&#xff0c;未安装的自行参考其他教程。如果不能翻墙&#xff0c;可以修改本地的镜像地址&#xff0c;可在docker desktop 设置中的docker engine中修…

Redis-新数据类型-Bitmaps

新数据类型-Bitmaps 简介 在计算机中&#xff0c;用二进制&#xff08;位&#xff09;作为存储信息的基本单位&#xff0c;1个字节等于8位。 例如 “abc” 字符串是由 3 个字节组成&#xff0c;计算机存储时使用其二进制表示&#xff0c;"abc"分别对应的ASCII码是 …

数据结构与算法 知识点整理

线性表 线性表的基本概念 线性表的定义&#xff1a;线性表是一个具有相同特性的数据元素的有限序列。 相同特性&#xff1a;所有元素属于同一数据类型 有限&#xff1a;数据元素个数是有限的 序列&#xff1a;数据元素由逻辑序号&#xff08;逻辑位序&#xff09;唯一确定…

Oracle SQL Developer导出数据库表结构,表数据,索引以及序列号等对象

目录 一、业务需求 三、环境说明 三、数据导出 四、数据导入 一、业务需求 通过Oracle SQL Developer软件将指定oracle数据库中的表结构&#xff0c;表数据&#xff0c;索引以及序列号等对象导出成SQL文件。 三、环境说明 数据库版本&#xff1a;Oracle Database 11g Expres…

【管理篇】如何向上沟通?

目录标题 向上沟通中下列问题最普遍和上级能不聊就不聊拿捏不好该不该和上级聊的分寸和尺度很难领会到上级的意图如何影响上级的一些观点和决策? 如何应对上述问题呢&#xff1f;&#x1f60e;如何管理上级&#xff1f;&#x1f44c; 向上沟通中下列问题最普遍 和上级能不聊就…

sql函数--10---In 多个字段

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.普通 in语句查询sqlMyBatis错误写法正确写法ListString[] 2.In多个字段sql案例脚本实例&#xff1a;错误写法&#xff1a;正确写法&#xff1a; MyBatis XML 写法…

Spring 事务及事务传播机制(1)

目录 事务 回顾: 什么是事务 为什么需要事务 事务的操作 Spring事务的实现 Spring编程式事务(简单了解即可, 问就是基本不用) 观察事务提交 观察事务回滚 Spring声明式事务 Transactional Transactional作用 事务 回顾: 什么是事务 定义: 事务是指逻辑上的一组操作, 构…

语义分割——前列腺分割数据集

引言 亲爱的读者们&#xff0c;您是否在寻找某个特定的数据集&#xff0c;用于研究或项目实践&#xff1f;欢迎您在评论区留言&#xff0c;或者通过公众号私信告诉我&#xff0c;您想要的数据集的类型主题。小编会竭尽全力为您寻找&#xff0c;并在找到后第一时间与您分享。 …

JVM认识之垃圾收集算法

一、标记-清除算法 1、定义 标记-清除算法是最基础的垃圾收集算法。它分为标记和清除两个阶段。先标记出所有需要回收的对象&#xff08;即垃圾&#xff09;&#xff0c;在标记完成后再统一回收所有垃圾对象。 2、优点和缺点 优点&#xff1a;实现简单缺点&#xff1a; 可能…

初阶数据结构之单链表详解

目录 一&#xff1a;单链表概念 二&#xff1a;单链表的基本操作 1.定义结点 2.创建链表&#xff08;初始化链表&#xff09; 3:新增结点 4.单链表尾插 5.单链表头插 6.单链表尾删 7&#xff1a;单链表头删 8.打印单链表 9.查找单链表结点 10.单链表删除指定结点 1…

Centos7 安装 MySQL5.7 使用 RPM 方式

1 访问网站 https://downloads.mysql.com/archives/community/ 选择合适的版本&#xff0c;点击 Download。 2 上传下载好的 mysql-5.7.44-1.el7.x86_64.rpm-bundle.tar 文件到 Centos7 机器&#xff0c;这里放到了 下载 目录。 3 解压 mysql-5.7.44-1.el7.x86_64.rpm-bundle.…

JS笔试手撕题

数据劫持 Vue2的Object.defineProperty() Vue2的响应式是通过Object.defineProperty()拦截数据&#xff0c;将数据转换成getter/setter的形式&#xff0c;在访问数据的时候调用getter函数&#xff0c;在修改数据的时候调用setter函数。然后利用发布-订阅模式&#xff0c;在数…