自学内容网 自学内容网

【c++篇】:解析c++类--优化编程的关键所在(二)

在这里插入图片描述

一.默认成员函数

如果一个类什么也没有就是空类。但是空类中就什么也没有吗?事实上并非如此。

在c++中,当一个类被定义时,编译器会自动为这个类生成一些特殊的成员函数,这些函数被称为默认成员函数。虽然这些函数在类中可能没有被显示定义,但在默写情况编译器会隐式的为他们生成实现。类的六个默认成员函数如图所示:

在这里插入图片描述
在上面的六个默认成员函数中,构造,析构,拷贝赋值,赋值运算符重载这四个函数最为重要,也是本篇文章将要重点讲解的。

二.构造函数

2.1.构造函数的概念

首先我们来看一下关于日期类Data:

class Data {
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(){
    Data d1;
    d1.Init(2024, 10, 20);
    d1.Print();
    return 0;
}

在上面这段代码中,通过成员函数Init给创建的对象初始化,但如果每次创建对象都是调用该函数,就会有点麻烦。

编译器无法预期一个程序在执行过程中会在何时创建一些什么对象,而只能根据当时的上下文要求来创建。对象的初始化最好能够通过运行时执行一个函数来完成,而且是在对象创建的同时完成初始化,这个函数就是构造函数

2.2构造函数的特性

构造函数是特殊的成员函数,构造函数虽然名字叫做构造,但是它的主要任务是完成对对象的初始化,并不是开空间创建对象。

  • 构造函数函数名和类名相同。

  • 构造函数无返回值。

  • 对象实例化时,编译器会自动调用对应的构造函数。

  • 构造函数可以重载。比如:

    class Data {
    public:
        //无参构造函数
    Data() {
    }
        //带参构造函数
    Data(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;
    };
    void test1() {
        //调用带参构造函数创建对象
    Data d1(2024, 10, 20);
        //调用无参构造函数创建对象
    Data d2;
        //无参调用时对象后面不能带括号,否则就像函数声明一样
    Data d3();
    }
    
  • 如果类中没有显示定义构造函数时,c++编译器会自动生成一个无参的默认构造函数,而当用户显示定义时,编译器就不再生成。还是这个日期类Data

    class Data {
    public:
    //将构造函数屏蔽时,代码可以通过编译,因为编译器自动生成一个无参的默认构造函数
    /*Data(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() {
    Data d1;
    
    return 0;
    }
    

    我们再来看一下如果当前对象d1调用成员函数Print会有什么结果:

    在这里插入图片描述

    在这里插入图片描述

    这时我们可能会有疑问,不是编译器会自动生成默认构造函数完成初始化吗?为什么d1对象依旧是随机值?这时候我们就需要了解到什么是内置类型,什么是自定义类型。

    c++把类型分成内置类型自定义类型

    内置类型:就是语言提供的数据类型,比如常用的int,char,double以及指针类型等等。

    自定义类型:就是我们使用的class/struct/union等自己定义的类型。

    当我们没有写构造函数时,编译器会自动生成一个无参的默认构造函数,但是:内置类型不做处理,自定义类型会去调用它的默认构造。比如下面这段代码:

    //一个成绩类,包含语数英三个成员变量
    class Scores {
    public:
    Scores() {
    _chian = 0;
    _math = 0;
    _english = 0;
    }
    int _chian;
    int _math;
    int _english;
    };
    class Data {
    public:
    void Print() {
    cout << _year << " " << _month << " " << _day << endl;
    cout << _s._chian << " " << _s._math << " " << _s._english << endl;
    }
    private:
    int _year;
    int _month;
    int _day;
        //假设日期类成员变量包含一个成绩类对象
    Scores _s;
    };
    int main() {
    Data d1;
    d1.Print();
    return 0;
    }
    

    在这里插入图片描述

    这时候我们就可以看到,我们在Data类中定义了一个成员变量Scores类对象,当我们创建Data类对象d1时,打印结果就会如上图所示,我们并没有写构造函数,而是使用编译器自动生成的默认构造函数,内置类型year,month,day没有处理,而自定义类型s则是调用它对应类的默认构造函数完成初始化。

    注意:在c++11中针对内置类型不初始化的情况,又增加了新的解决方法:内置类型成员变量在类中声明时可以给默认值。比如下面这段代码:

    class Scores {
    public:
    Scores() {
    _chian = 0;
    _math = 0;
    _english = 0;
    }
    int _chian;
    int _math;
    int _english;
    };
    class Data {
    public:
    void Print() {
    cout << _year << " " << _month << " " << _day << endl;
    cout << _s._chian << " " << _s._math << " " << _s._english << endl;
    }
    private:
        //内置类型
        //给成员变量设置默认值
    int _year=1;
    int _month=1;
    int _day=1;
        //自定义类型
    Scores _s;
    };
    int main() {
    Data d1;
    d1.Print();
    return 0;
    }
    

    在这里插入图片描述

  • 无参的构造函数和全缺省的构造函数都称为默认构造函数,但是只能有一个。比如下面这段代码,就会出现错误

    class Data {
    public:
    Data() {
    _year = 1;
    _month = 1;
    _day = 1;
    }
    Data(int year=0, int month=0, int day=0) {
    _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() {
    Data d1;
    d1.Print();
    return 0;
    }
    

    在这里插入图片描述

三.析构函数

3.1析构函数的概念

通过前面的构造函数我们知道一个对象是怎么来的,那一个对象又是怎样没得呢?

析构函数和构造函数相反,析构函数不是完成对对象本身的销毁,局部销毁工作是由编译器完成,而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

把对象的初始化工作放到构造函数中,把清理工作放到析构函数中,当对象创建时,自动调用构造函数,当对象消亡时,自动调用析构函数,这样就不用担心对象的初始化和清理了,即使忘记,也能自动调用。

3.2析构函数的特性

  • 析构函数名是在类名前加上字符~

  • 无参数无返回类型。

  • 一个类只能有一个析构函数,所以析构函数不能重载。如果没有写时,编译器会自动生成默认析构函数。

  • 当对象生命周期结束时,编译器会自动调用析构函数。以下面这个栈为例:

    class Stack {
    public:
        //构造函数
    Stack(int capacity=4) {
    cout << "Stack" << endl;
    _a = (int*)malloc(sizeof(int) * capacity);
    if (_a == nullptr) {
    perror("malloc fail");
    return;
    }
    _top = 0;
    _capacity = capacity;
    }
    void PushStack(int x) {
    //checkcapacity();
    _a[_top++] = x;
    }
    //......
        //析构函数
    ~Stack() {
    cout << "~Stack" << endl;
    free(_a);
    _a = nullptr;
    _top = 0;
    _capacity = 0;
    }
    private:
    int* _a;
    int _top;
    int _capacity;
    };
    
    int main() {
    Stack st1;
    st1.PushStack(1);
    st1.PushStack(2);
    return 0;
    }
    

    在这里插入图片描述

    从上面的结果中我们可以看到,我们只是创建了一个对象st1,但并没有显示调用它的构造和析构,而是编译器自动调用完成初始化和清理。

  • 对于系统自动生成的默认析构函数,内置类型不做处理,自定义类型会调用它的默认析构函数。以两个栈实现一个队列为例:

    class Stack {
    public:
        //构造函数
    Stack(int capacity=4) {
    cout << "Stack" << endl;
    _a = (int*)malloc(sizeof(int) * capacity);
    if (_a == nullptr) {
    perror("malloc fail");
    return;
    }
    _top = 0;
    _capacity = capacity;
    }
    void PushStack(int x) {
    //checkcapacity();
    _a[_top++] = x;
    }
    //......
        //析构函数
    ~Stack() {
    cout << "~Stack" << endl;
    free(_a);
    _a = nullptr;
    _top = 0;
    _capacity = 0;
    }
    private:
    int* _a;
    int _top;
    int _capacity;
    };
    
    class Queue {
    public:
        //自定义类型Stack 
        //两个对象pushst,popst
    Stack pushst;
    Stack popst;
    };
    int main() {
        //创建一个类Queue对象q
    Queue q;
    return 0;
    }
    

    在这里插入图片描述

    从上面的结果中我们可以看到,类Queue中只有两个自定义类型成员变量pushst,popst,当我们创建一个类Queue对象q时,自定义类型成员变量pushst,popst会自动调用他们对应类的默认构造和析构函数完成初始化和清理工作。

  • 一般情况下,如果类中有动态申请资源时,需要显示写析构函数来释放资源;如果没有动态申请的资源或者全是自定义类型时,不需要写析构函数。

四.拷贝构造函数

4.1拷贝构造函数的概念

在现实生活中我们一定经常遇到拷贝的情况,比如将一个文件新复制一份用来备用,或者说一对双胞胎,这些都是常见的拷贝。

而在前面了解到对象的初始化和清理后,我们再来试想一下能不能将一个已经创建的对象拷贝给一个新的对象?

这时候就要引出我们要学的拷贝构造函数

拷贝构造函数,又称复制构造函数,是c++中的一种特殊的构造函数,用于创建对象的副本。他通过使用已有对象的属性值来初始化一个新的对象,实现对象的复制操作。

4.2拷贝构造函数的特性

  • 拷贝构造函数是构造函数的一个重载形式。(也就是说函数名和类名相同)。

  • 拷贝构造函数的参数只能有一个且必须是类类型对象的引用,如果是传值方式编译器会报错,因为会引发无限递归调用拷贝构造函数。

    class Date {
    public:
    Date(int year, int month, int day) {
    _year = year;
    _month = month;
    _day = day;
    }
        //拷贝构造函数
    Date(const Date& d) {
    _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 d1(2024, 10, 21);
    Date d2 = (d1);
    d1.Print();
    d2.Print();
    
    return 0;
    }
    

    在这里插入图片描述

    上面所示样例是使用引用方式,结果是正确的。

    如果拷贝构造函数是传值方式时,就会出现以下错误:

    Date(const Date d) {
    _year = d._year;
    _month = d._month;
    _day = d._day;
    }
    

    在这里插入图片描述

  • 如果没有显示定义拷贝构造函数时,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对对象按内存存储按字节序完成拷贝,这种拷贝方式为值拷贝或者浅拷贝。

    在前面的构造函数和析构函数我们知道,这两个对于内置类型不做处理,自定义类型会调用它的默认构造或析构。而拷贝构造函数则是有所不同,对于内置类型会完成值拷贝,而自定义类型会去调用它的拷贝构造。以下面这个为例:

    class Time {
    public:
    Time(int hour=1, int minute=1, int second=1) {
    _hour = hour;
    _minute = minute;
    _second = second;
    }
    Time(const Time& t) {
    _hour = t._hour;
    _minute =t._minute;
    _second =t._second;
    cout << "const Time& t" << endl;
    }
    
    
    //private:
    int _hour;
    int _minute;
    int _second;
    };
    
    class Date {
    public:
    void Print() {
    cout << _year << "-" << _month << "-" << _day;
    cout << "-" << t._hour <<"-" << t._minute <<"-"<< t._second << endl;
    }
    
    private:
        //内置类型
    int _year=0;
    int _month=0;
    int _day=0;
        //自定义类型
    Time t;
    };
    
    int main() {
    Date d1;
        //用已经存在的对象d1拷贝构造对象d2
    Date d2(d1);
    d1.Print();
    d2.Print();
    return 0;
    }
    

    在这里插入图片描述

    在上面的这一段代码中,Data类中有内置类型和自定义类型Time对象t,用已经存在的对象d1拷贝构造对象d2,在类Data中,我们并没有写拷贝构造函数,而是编译器默认生成并调用,内置类型成员变量year,month,day完成值拷贝,自定义类型t调用了他自己的拷贝构造函数完成拷贝。

  • 编译器生成的默认拷贝构造函数只能完成字节序的值拷贝,对于使用动态内存开辟的对象需要使用深拷贝,这时候就需要自己显示实现拷贝构造函数。还是以栈为例:

    如果按照下面的代码运行,程序会发生崩溃而中断。这是因为用对象st1拷贝构造一个新的对象st2,st1使用了动态内存开辟空间,如果直接使用编译器生成的默认拷贝构造,只能完成值拷贝,并不能实现深拷贝,所以就会出现如下错误:

    class Stack {
    public:
    Stack(int capacity=4) {
    cout << "Stack" << endl;
    _a = (int*)malloc(sizeof(int) * capacity);
    if (_a == nullptr) {
    perror("malloc fail");
    return;
    }
    _top = 0;
    _capacity = capacity;
    }
    void PushStack(int x) {
    //checkcapacity();
    _a[_top++] = x;
    }
    //......
    ~Stack() {
    cout << "~Stack" << endl;
    free(_a);
    _a = nullptr;
    _top = 0;
    _capacity = 0;
    }
    private:
    int* _a;
    int _top;
    int _capacity;
    };
    int main() {
    Stack s1;
    s1.PushStack(1);
    s1.PushStack(2);
        s1.PushStack(3);
    s1.PushStack(4);
    Stack s2(s1);
    return 0;
    }
    

    在这里插入图片描述

    在这里插入图片描述

    注意:如果类中没有涉及到动态资源申请时,拷贝构造函数可以写也可以不写,而一旦涉及到有动态资源申请时,就一定要写拷贝构造函数,否则就是浅拷贝。

五. 赋值运算符重载

5.1运算符重载

对于运算符我们一定不会陌生,先以常见的为例,比较运算符<,>我们一定经常用吧,如果现在有一个日期类的对象d1和d2:

class Date {
public:
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
int _year = 0;
int _month = 0;
int _day = 0;
Time t;
};
int main() {
Date d1(2024, 10, 21);
Date d2(2023, 10, 21);
bool ret = d1._year < d2._year;
cout << ret << endl;
return 0;
}

我们想要比较这两个对象的成员变量年份时,我们可以这样直接比较:

bool ret = d1._year < d2._year;

在这里插入图片描述

但是如果我们想要直接比较对象d1和d2,还可以直接使用比较运算符吗?结果就会如下所示:

bool ret = d1 < d2;

在这里插入图片描述

前面这两个不同点在于,年份是内置类型,可以直接使用运算符比较,而两个对象则是自定义类型,不能直接使用。

c++为了方便这一点引入了运算符重载。

运算符重载是对已有运算符的重新定义,使其能够处理自定义类型的数据。这实际上是通过定义一个特殊的函数来实现的。该函数也是具有其返回值类型,函数名字以及参数列表。

函数名字为:关键字operator后面接需要重载的运算符符号。

注意:

  • 运算符重载并不是创建新的运算符,比如:operator@
  • 重载操作符必须有一个类类型参数
  • 不能改变内置类型的运算符含义
  • 作为类成员函数重载时,形参比操作数目少一,这是因为,成员函数的第一个参数为隐藏的this指针
  • .*,::,sizeof,?:,.这五个运算符不能重载

明白了这个概念后,我们再来看刚才对于两个对象的比较,用运算符重载来实现:

全局函数operator<

bool operator<(const Date& d1, const Date& d2) {
if (d1._year < d2._year) {
return true;
}
if (d1._year == d2._year && d1._month < d2._month) {
return true;
}
if (d1._year == d2._year && d1._day == d2._month && d1._day < d2._day) {
return true;
}
return false;
}
int main() {
Date d1(2022, 10, 21);
Date d2(2023, 10, 21);
cout << (d1<d2) << endl;
return 0;
}

在这里插入图片描述
作为类成员函数operator<,结果依然和上图一样

class Date {
public:
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
    //作为类成员函数
    //bool operator<(Date*this,const Date& d)
bool operator<(const Date& d) {
if (_year < d._year) {
return true;
}
if (_year == d._year && _month < d._month) {
return true;
}
if (_year == d._year && _day == d._month && _day < d._day) {
return true;
}
return false;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
Time t;
};
int main() {
Date d1(2022, 10, 21);
Date d2(2023, 10, 21);
cout << (d1<d2) << endl;
return 0;
}

5.2赋值运算符重载

注意:赋值运算符重载是将一个自定义类型赋值给另一个,和拷贝构造函数的区别就是:拷贝构造是用一个已经存在的对象初始化另一个对象;而赋值运算符重载是已经存在的两个对象之间赋值拷贝。

  • 赋值运算符重载格式
    • 参数类型:const T&,传递引用可以提高效率
    • 返回值:类型是T&,返回引用可以提高效率,返回的是*this,因为要符合连续赋值的含义
    • 检查是否自己给自己赋值
class Date {
public:
    //构造函数
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}
    //遍历打印函数
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
    //=重载函数
Date& operator=(const Date& d) {
        //先判断一下是否自己给自己赋值
if (this !=& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};

int main() {
Date d1(2022, 10, 21);
Date d2(2023, 10, 21);
    //将对象d1赋值给d2
d2 = d1;
d1.Print();
d2.Print();
return 0;
}

上面代码结果:

在这里插入图片描述

  • 如果用户没有显示时,编译器会自动生成一个默认赋值运算符重载函数,这里就和拷贝构造函数一样,实现的是值拷贝,如果是内置类型成员变量可以直接复制,自定义类型成员变量就去调用对应类的的赋值运算符重载完成赋值。

    还是以Date类和Time类为例:

    class Time {
    public:
    Time(int hour=1, int minute=1, int second=1) {
    _hour = hour;
    _minute = minute;
    _second = second;
    }
        //Time类的赋值运算符重载
    Time& operator=(const Time& t) {
    if (this != &t) {
    _hour = t._hour;
    _minute =t._minute;
    _second =t._second;
    }
    return *this;
    }
    int _hour;
    int _minute;
    int _second;
    };
    class Date {
    public:
    Date(int year, int month, int day) {
    _year = year;
    _month = month;
    _day = day;
    }
    void Print() {
    cout << "内置类型成员变量:";
    cout << _year << "-" << _month << "-" << _day << endl;
    cout << "自定义类型成员变量:";
    cout << t._hour << "-" << t._minute << "-" << t._second << endl;
    }
    
    private:
        //内置类型
        //Date类中没有赋值运算符重载,默认生成并调用
    int _year=0;
    int _month=0;
    int _day=0;
        //自定义类型
        //调用对应类的赋值运算符重载
    Time t;
    };
    
    int main() {
    Date d1(2024, 10, 21);
    Date d2(2023, 10, 21);
    d2 = d1;
    d1.Print();
    d2.Print();
    return 0;
    }
    

    在这里插入图片描述

  • 和拷贝构造函数一样,如果类中没有申请动态资源的,可以不写赋值运算符重载,编译器默认生成的就可以完成赋值,而如果类中含有申请动态资源的,就需要用户自己显示实现。

    还是以栈为例,以下代码运行会报错,和拷贝构造函数错误原因一样。

    class Stack {
    public:
    Stack(int capacity=4) {
    _a = (int*)malloc(sizeof(int) * capacity);
    if (_a == nullptr) {
    perror("malloc fail");
    return;
    }
    _top = 0;
    _capacity = capacity;
    }
    void PushStack(int x) {
    //checkcapacity();
    _a[_top++] = x;
    }
    ......
    ~Stack() {
    free(_a);
    _a = nullptr;
    _top = 0;
    _capacity = 0;
    }
    private:
    int* _a;
    int _top;
    int _capacity;
    };
    int main() {
    Stack s1;
    s1.PushStack(1);
    s1.PushStack(2);
    Stack s2;
    s2 = s1;
    return 0;
    }
    

    在这里插入图片描述

  • 赋值运算符重载只能重载成类的成员函数不能重载成全局函数。因为如果类中没有赋值运算符重载时,编译器会默认生成,如果在全局中重载,就会和默认生成的产生冲突。

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

  • 前置++:

    • 前置++返回加一后的结果
    • 因为this指针指向的对象在函数调用结束时不会销毁,所以用引用做返回值提高效率
  • 后置++:

    • 为了让前置++和后置++构成重载,c++规定,后置++重载时多加上一个int类型的参数,但调用时不用传参,只是为了区分
    • 后置++返回加一之前的值,所以要先将this指针指向的对象保存一份,然后this+1
    • 因为用来保存的为临时变量,所以只能用传值方式返回,不能用引用

完整代码如下:

class Date {
public:
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
//前置++
Date& operator++() {
_day ++;
return *this;
}
    //后置++
Date operator++(int) {
Date tmp(*this);
_day++;
return tmp;
}

private:
int _year = 0;
int _month = 0;
int _day = 0;
};

int main() {
Date d1(2022, 10, 21);
Date d2(2023, 10, 21);
d1++;
d2++;
d1.Print();
d2.Print();
return 0;
}

结果如下:

在这里插入图片描述

六.const成员

我们知道被const修饰的将具有常性,不能再进行修改,如果以下面这种情况为例,用const修饰对象d1,然后调用Print成员函数就会报错:

class Date {
public:
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}
    //void Print(Date* this)
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main(){
    //d1.Print(&d1)
    const Date d1(2024, 10, 21);
    d1.Print();
    return 0;
}

在这里插入图片描述

这里报错的原因是因为,被const修饰的对象d1调用成员函数Print时,实际上是这样d1.Print(&d1),而成员函数则是void Print(Date* this),从const Date*Date*权限放大,所以会出现错误,为了解决此问题,就要在成员函数后面加const修饰:

//void Print(const Date* this)
void Print() const {
cout << _year << "-" << _month << "-" << _day << endl;
}

加上const修饰后,this指针就会变成const Date*,从const Date*const Date*权限平移,可以运行。

如果创建一个对象d2Date类型,从Date*const Date*权限缩小,依然可以运行。所以在成员函数后加上const修饰后,对于非const修饰的对象和const修饰的对象都可以调用。

class Date {
public:
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}
    //void Print(const Date* this)
void Print() const {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main(){
    //d2.Print(&d2)
    Date d2(2024, 10, 20);
    d2.Print();
    return 0;
}

注意:

  • 允许权限平移和权限缩小,但是不能权限放大。
  • 只要成员函数内部不修改成员变量,都应该加const
  • 要修改成员变量的成员函数不能加const

七.取地址和const取地址操作符重载

  • 普通对象取地址操作符重载:

    Date* operator&(){
        return this;
    }
    
  • const对象取地址操作符重载:

    const Date* operator&() const {
        return this;
    }
    

    这两默认成员函数一般不用重新定义,编译器会默认生成。使用默认生成的就可以,一般特殊情况会需要重载,比如需要获取到指定的内容。

    class Date {
    public:
    Date(int year,int month,int day) {
    _year = year;
    _month = month;
    _day = day;
    }
        //普通对象取地址操作符重载
    Date* operator&() {
    cout << "Date* operator&()" << endl;
    return this;
    }
        //const对象取地址操作符重载
    const Date* operator&()const {
    cout << "const Date* operator&()" << endl;
    return this;
    }
    
    private:
    int _year = 0;
    int _month = 0;
    int _day = 0;
    };
    int main(){
        Date d1(2024, 10, 22);
        const Date d2(2024, 10, 21);
        cout << &d1 << endl;
        cout << &d2 << endl;
        return 0;
    }
    

    自己显示的取地址操作符重载函数结果:

    在这里插入图片描述

编译器默认生成的取地址操作符重载结果:

class Date {
public:
Date(int year,int month,int day) {
_year = year;
_month = month;
_day = day;
}

private:
int _year = 0;
int _month = 0;
int _day = 0;
};
int main(){
    Date d1(2024, 10, 22);
    const Date d2(2024, 10, 21);
    cout << &d1 << endl;
    cout << &d2 << endl;
    return 0;
}

在这里插入图片描述

八.日期类Date完整实现

这里主要提供头文件和函数定义文件,测试文件就不再展示,可以自己尝试创建几个对象进行检验。

头文件Date.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
using namespace std;

class Date {
public:
//获取当月天数
int GetDay(int year, int month);
//构造函数
Date(int year,int month,int day);
//拷贝构造函数
Date(const Date& d);
//赋值运算符重载
Date& operator=(const Date& d);

//日期+=天数
Date& operator+=(int day);
//日期+天数
Date operator+(int day);
//日期-=天数
Date& operator-=(int day);
//日期-天数
Date operator-(int day);

//前置++
Date& operator++();
//后置++
Date operator++(int);
//前置--
Date& operator--();
//后置--
Date operator--(int);

//比较运算符重载
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);
//打印
void Print();

private:
int _year;
int _month;
int _day;
};

函数定义文件Date.cpp

#include"Date.h"
int Date::GetDay(int year, int month) {
int Day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
int day = Day[month];
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) ||( year % 400 == 0))) {
day++;
}
return day;
}

Date::Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}

void Date::Print() 
{
cout << _year << "-" << _month << "-" << _day << endl;
}

Date::Date(const Date& d) 
{
_year = d._year;
_month = d._month;
_day = d._day;
}

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

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

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

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

Date& Date::operator++() {
if (_month == 12 && _day == GetDay(_year, _month)) {
_year++;
_month = 1;
_day = 1;
}
else if (_day == GetDay(_year, _month)) {
_month++;
_day = 1;
}
else {
_day++;
}
return *this;
}

Date Date::operator++(int) {
Date tmp(*this);
if (_month == 12 && _day == GetDay(_year, _month)) {
_year++;
_month = 1;
_day = 1;
}
else if (_day == GetDay(_year, _month)) {
_month++;
_day = 1;
}
else {
_day++;
}
return tmp;
}

Date& Date::operator--() {
if (_month == 1 && _day ==1) {
_year--;
_month = 12;
_day = GetDay(_year,_month);
}
else if (_day == 1) {
_month--;
_day = GetDay(_year, _month);
}
else {
_day--;
}
return *this;
}

Date Date::operator--(int) {
Date tmp(*this);
if (_month == 1 && _day == 1) {
_year--;
_month = 12;
_day = GetDay(_year, _month);
}
else if (_day == 1) {
_month--;
_day = GetDay(_year, _month);
}
else {
_day--;
}
return tmp;
}

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

bool Date::operator<(const Date& d) {
if (_year <d._year) {
return true;
}
if (_year == d._year && _month < d._month) {
return true;
}
if (_year == d._year && _month == d._month && _day < d._day) {
return true;
}
return false;
}

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

bool Date::operator>(const Date& d) {
return !(*this < d) && !(*this == d);
}

bool Date::operator<=(const Date& d) {
return (*this < d) || (*this == d);
}

bool Date::operator>=(const Date& d) {
return (*this > d) || (*this == d);
}

bool Date::operator!=(const Date& d) {
return !(*this == d);
}

以上就是关于c++类六个默认成员函数的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述


原文地址:https://blog.csdn.net/2301_82347435/article/details/143171052

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!