类和对象(2)(6个默认成员函数)

1.类的6个默认成员函数(友情提示:这部分相当恶心)

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

2.构造函数

C语言语法存在很多问题,比如忘记初始化(很普遍),在一些情况下会造成程序的崩溃。c++是针对C语言的改造升级,所以c++有构造函数来解决忘记初始化的问题。也就是说,之前日期类中Init函数其实没必要存在。

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次

先来看看构造函数的特性:

2.2 特性

构造函数是特殊的成员函数(不能用一般函数的规则),需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。构造函数没有开辟空间,构造函数所使用的空间是使用构造函数的对象所在函数的函数栈帧。

其特征如下:

    1. 函数名与类名相同。

    2. 无返回值。

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

    4. 构造函数可以重载。

    5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成

    6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(不用传参就可以调用的构造函数)

class Date

{

public:

    Date()//无返回值,函数名和类名相同

    {

        _year = 0;

        _mouth = 0;

        _day = 0;

    }

    Date(int year, int month, int day)//函数重载

    {

        _year = year;

        _mouth = month;

        _day = day;

    }

    void Print()

    {

        cout <<_year<< "-" <<_month << "-"<< _day <<endl;

    }

private:

    int _year;

    int _month;

    int _day;

};

int main()

{

    Date d1;//调用没有参数的构造函数的方式,没有参数的时候,不可以这样写Date d1(); 否则就成了函数声明

    d1.Print();

    Date d2(2022, 5, 28);//调用有参数构造函数的方式,构造函数参数在创建的对象后面

    d2.Print();

    return 0;

}

函数重载还可以通过缺省参数更进一步

Date(int year = 1, int month = 1, int day = 1)

{

    _year = year;

    _mouth = month;

    _day = day;

}

那么问题来了,在已经有Date()作为构造函数的情况下,Date(int year = 1, int month = 1, int day = 1)可以存在吗?

可以,因为函数重载,但也只是编译通过,一旦同时存在会出现歧义。Date d1;是调用Date(),还是调用Date(int year = 1, int month = 1, int day = 1)来初始化呢?在使用时会报错。所以通常会选择只创建一个全缺省构造函数,就具备了两者的特性。

class Date

{

public:

    Date(int year = 1, int month = 1, int day = 1)

    {

        _year = year;

        _mouth = month;

        _day = day;

    }

    void Print()

    {

        cout <<_year<< "-" <<_month << "-"<< _day <<endl;

    }

private:

    int _year;

    int _month;

    int _day;

};

int main()

{

    Date d1;

    d1.Print();

    Date d2(2022, 5, 28);

    d2.Print();

    Date d3(2022);

    d3.Print();

    return 0;

}

如果没有显性定义构造函数,编译器会自动定义一个构造函数。但是定义的构造函数初始化后是随机值,和没有构造函数也没什么区别。那定义构造函数干什么?

这就涉及到c++比较恶心的一点了,c++把变量分为两种,基本类型/内置类型,包括int/char/double/指针……以及自定义类型,即用class,struct类型。而默认构造函数对内置类型的成员变量不做处理,对自定义类型的成员变量才做处理。

d1调用它的构造函数对自定义类A(A中的成员仅有一个int类型的a,且在A中已经将_aa初始化)创建的_aa进行初始化。

也就是说,当类的成员都是自定义类型的时候,就不需要写构造函数来初始化了。比如之前的用两个栈来实现队列,在myQueue的类中,成员是两个栈,这时就不需要构造函数进行初始化了,默认构造函数会把栈初始化为0(前提是栈里面有自定义的默认构造函数,如果没有,还是随机值,虽然myQueue不需要自定义构造函数,但是栈初始化还是通过栈的默认构造函数来初始化,也就是说myQueue的默认构造函数会去调用栈的默认构造函数;

或许会想在myQueue类中对栈初始化,但事实上myQueue没有调用栈中成员的权利(栈中成员变量为私有))。

如果栈里面的构造函数只有一个带参的构造函数,编译会报错,提示没有合适的默认构造函数。因为已经存在构造函数,编译器不会自动添加默认构造函数,在通过默认构造函数进行初始化的时候,找不到默认的构造函数,也就无法初始化。

这里c++的机制进行了区分化的处理。默认构造函数可能没有用。所以如果一个类中全是自定义类型,就可以使用默认的构造函数;当类中存在内置类型或者需要显式传参,就需要自己构建构造函数。所以%99的情况下都需要自己写构造函数。

这是c++的一个坑,至于为什么不填上这个坑,因为c++问世以来,已经有很多基于这个规则写出的代码,坑填上就会出现原来的代码有bug的情况。历史上只有phython进行了填坑,phython3是不兼容phython2的,这个操作被喷了好几年。但是可以打补丁。比如myQueue中的成员多了一个int类型的,又不想写构造函数怎么办?c++11打了一个补丁,可以在int类型上赋值。

即:

private:

    int _size = 0;//这里给的0不叫初始化,叫缺省值,是默认构造函数用的

    Stack _st1;

    Stack _st2;

3.析构函数

3.1 概念

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

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

3.2 特性

析构函数是特殊的成员函数。

其特征如下:

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值(不能构成函数重载)。

3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

class Date

{

public:

    Date(int year = 1, int month = 1, int day = 1)

    {

        _year = year;

        _mouth = month;

        _day = day;

    }

    void Print()

    {

        cout <<_year<< "-" <<_month << "-"<< _day <<endl;

    }

    ~Date()

    {

        cout << "Date:" << endl;//因为析构函数的特性,这里啥也写不了,只能说看看有没有调用

    }

private:

    int _year;

    int _month;

    int _day;

};

int main()

{

    Date d1;//创建d1,d1会调用它的构造函数,在出d1作用域会调用它的析构函数,在调试状态下,return 0;位置处,f11,会看到进入析构函数了。析构函数不是销毁对象,对象不需要销毁。

    int i = 0;//就像这里的i,不需要析构函数进行销毁,d1和i都在main函数的函数栈帧中,函数栈帧销毁后,d1和i就跟着销毁了

    return 0;

}

正如之前析构函数的概念中说的,析构函数的作用是负责资源的清理。真正需要资源清理的是malloc等函数开辟的空间。

class Stack

{

public:

    Stack(int capacity = 10)

    {

        int* _a = (int*)malloc(sizeof(int) * capacity);

        assert(_a);

        int _top = 0;

        int _capacity = capacity;

    }

    ~Stack()

    {

        free(_a);

        _a = nullptr;

        _top = _capacity = 0;

    }

private:

    int* _a;

    int _top;

    int _capacity;

}

int main()

{

    Stack d1;

    if( 10 )

    {

        Stack d2;//生命周期在if的括号内

    }

    return 0;

}

所以有些类不需要写析构函数,比如日期类,因为没有资源需要清理。析构函数会在对象生命周期到了以后,自动调用。对象的生命周期和之前的变量一样。

新问题:下面两个栈谁先构造,谁先析构?

int main()

{

    Stack d1(1);

    Stack d2(2);//传不同的值,然后在调试过程中通过this指针观察是哪一个栈

    return 0;

}

构造,按正常思维,先创建的先构造;析构,函数栈帧遵循后进先出原则,所以是后创建的对象,先析构。

在没有定义析构函数的情况下,默认的析构函数会不会释放malloc开辟的空间?

不会,对指针类型,编译器没有那么聪明,不能确定是定义出来的,还是malloc开辟出来的,还是fopen。如果都free掉,会报错。所以析构函数也是对内置类型和自定义类型分开处理的。对内置类型,不做处理。对自定义类型,会去调用它的析构函数。

所以栈中析构函数必须要自己写,日期类中,默认析构函数也没什么用。默认析构函数有用的地方和之前构造函数一样。在myQueue中的两个栈,在myQueue类创建的对象中,对象生命周期结束时,调用myQueue默认的析构函数,默认的析构函数再去调用栈的析构函数,完成对malloc开辟空间的释放。

析构函数和构造函数都有this指针。

4.拷贝构造函数

4.1概念

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用

还是拿日期类来举例

class Date

{

public:

    Date(int year = 1, int month = 1, int day = 1)

    {

        _year = year;

        _mouth = month;

        _day = day;

    }

    Date(Date& d)//拷贝构造函数,如果不涉及d的改变,最好加上const,防止出现赋值写反了的情况

    {

        _year = d._year;

        _mouth = d._mouth;

        _day = d._day;

    }

    void Print()

    {

        cout <<_year<< "-" <<_month << "-"<< _day <<endl;

    }

    ~Date()

    {

        cout << "Date:" << endl;

    }

private:

    int _year;

    int _month;

    int _day;

};

int main()

{

    Date d1(2022, 5, 31);//当我们需要创建一个和d1一模一样的函数时,就需要拷贝构造函数

    Date d2(d1);

    return 0;

}

至于为什么拷贝构造函数的参数使用传值的方式会导致无限递归调用,是因为调用拷贝构造要先传参,传值传参是创建一个新的对象,新的对象要创建就必须调用拷贝构造函数,调用拷贝构造就要传参。这样就导致了死循环。用引用就可以解决这个问题。

以函数举例,把实参传给形参是将实参拷贝给实参。但是对自定义类型来说,不是把实参拷贝给形参,而是调用拷贝构造函数。

这是语法规定。至于为什么这样规定,涉及深浅拷贝问题。

示例:

……

void func(Date d)

{

}

int main()

{

    Date d1(2022, 5, 31);

    func(d1);//在此处按f11,会先进入Date类中的拷贝构造函数,完成d1的拷贝后,才进入func函数

    return 0;

}

在这里避免拷贝构造函数的方式只有引用。

如果用指针也能达到相似的效果,但是使用指针的构造函数就是普通的构造函数,不是拷贝构造函数。

对栈的拷贝构造就不能这样写了。

class Stack

{

public:

    Stack(int capacity = 10)

    {

        int* _a = (int*)malloc(sizeof(int) * capacity);

        assert(_a);

        int _top = 0;

        int _capacity = capacity;

    }

    Stack(const Stack& d)//这样写能解决无限构造的问题,但是还是有问题的,程序会崩溃

    {

        _a = d._a;//因为两个对象指向同一块空间,所以在两个对象生命周期结束时,会对一块相同的空间调用析构函数两次

                       //结果就是free两次相同的空间,程序崩溃

        _top = d._top;

        _capacity = d._capacity;

    }

    ~Stack()

    {

        free(_a);

        _a = nullptr;

        _top = _capacity = 0;

    }

private:

    int* _a;

    int _top;

    int _capacity;

}

int main()

{

    Stack d1(10);

    Stack d2(d1);

    return 0;

}

所以栈的拷贝构造不能这样写,这样的拷贝方式叫浅拷贝;栈的拷贝方式叫做深拷贝。栈的拷贝只能通过深拷贝实现。

这就是c++为什么要有构造函数的原因,而不是直接传值,因为传值会出现两次free的现象,而栈使用的拷贝构造函数,实现的是深拷贝。深拷贝会在之后详细说明。

现在再来看类的6个默认成员函数,默认的意思就是自己不写,编译器会自己生成。

下面来介绍一下具体的步骤:

class Date

{

public:

    Date(int year = 1, int month = 1, int day = 1)

    {

        year = year;//如果这样写,结果将是形参赋给形参,赋值遵循就近原则,先在构建函数的作用域中找有没有year,有就使用

        _mouth = month;//_mouth访问的也不是private中的_month,private中的只是声明。

        _day = day;//编译器如果发现赋值的对象是类中的成员,会在前面自动加上this->

    }

    void Print()

    {

        cout <<_year<< "-" <<_month << "-"<< _day <<endl;

    }

    ~Date()

    {

        cout << "Date:" << endl;

    }

private:

    int year;//这里的year没有被赋值成2022

    int _month;

    int _day;

};

int main()

{

    Date d1(2022, 5, 31);

    return 0;

}

关于构造函数,一个新的现象。

class A

{

public:

    A(int a = 0)

    {

        _a = a;

    }

private:

    int _a;

};

class Date

{

public:

    Date(int year = 2022, int mouth = 6, int day = 1)

    {

        _year = year;

        _mouth = mouth;

        _day = day;

    }

private:

    int _year;

    int _mouth;

    int _day;

    A _aa;

}

int main()

{

    Date d1;

    return 0;

}

在进入调试中后发现,除了被构造函数初始化的d1._year,d1._mouth,d1._day之外,_aa也被初始化成了0。说明编译器也对_aa进行了处理,处理_aa的地方叫初始化列表,初始化列表比较复杂,之后再进行讲解。

现在,我需要指定_aa初始化的值,要怎么做?

没有好的办法,当前能达成目的的方式有一种。

public:

    Date(int year = 2022, int mouth = 6, int day = 1)

    {

        _year = year;

        _mouth = mouth;

        _day = day;

        A aa(1);//这涉及了下面要讲的赋值

        _aa = a;

    }

通过赋值来实现对_aa的初始化存在问题。比如在没有写Date构造函数,A没有默认构造函数的情况下,

class A

{

public:

    A(int a)

    {

        _a = a;

    }

private:

    int _a;

};

class Date

{

private:

    int _year;

    int _mouth;

    int _day;

    A _aa;

}

int main()

{

    Date d1;

    return 0;

}

会报错。Date没有合适的默认构造函数。过程是这样的:d1创建,生成默认构造函数,对内置类型不做处理,对_aa调用他的默认构造函数。_aa存在构造函数,所以没有默认构造函数。调用默认构造函数失败,报错。所以在这样的情况下,就必须显式写默认构造函数了,而且

public:

    Date(int year = 2022, int mouth = 6, int day = 1)

    {

        _year = year;

        _mouth = mouth;

        _day = day;

        A aa(1);

        _aa = a;

    }

这样写还不行,因为要先走初始化列表,初始化列表之后再讲。目前只需要知道,如果A不提供默认构造函数,那当前的知识是不够解决这样的问题的。

结论:一般情况下,一个c++类都要自己写构造函数。只有少数情况下,可以让编译器默认生成。

1.类里面都是自定义类型,并且这些成员都提供了默认构造函数。

2.成员中的内置类型都给了缺省值

析构函数的细节方面没什么讲的,之前已经说的差不多了。

拷贝构造函数

拷贝构造函数也是6个默认成员函数之一,所以如果自己不写,也会生成一个。但是默认的拷贝构造函数和构造函数不同。

class Date

{

public:

    Date(int year = 2022, int mouth = 6, int day = 2)

    {

        _year = year;

        _mouth = mouth;

        _day = day;

    }

    //此时没有写拷贝构造函数

    void print()

    {

        cout << _year << "-" << _mouth << "-" << _day << endl;

    }

private:

    int _year;

    int _mouth;

    int _day;

}

void func(Date d)

{

    d.print();

}

int main()

{

    Date d1(10);

    Date d2(d1);

    func(d1);

    d2.print();

    return 0;

}

运行会打印两行2022-6-2,说明d2的值完成拷贝了。

默认拷贝构造函数会对内置类型成员的浅拷贝,即按字节拷贝,就像memcpy一样。但是不代表拷贝构造函数不需要自己写了,已经说了,默认拷贝构造函数会对内置类型成员完成浅拷贝,也就是说,对栈(成员为:int* ,int ,int)进行的是浅拷贝。就存在重复free的问题了。指向同样的空间还有一个问题就是修改数据会互相影响。所以要自己来写拷贝构造函数,通过深拷贝来解决。默认拷贝构造函数对自定义类型会去调用它的拷贝构造函数。

总结:对一般的类,编译器生成拷贝构造函数就足够了,只有在类中存在直接管理资源的情况才需要手写拷贝构造函数。

下面来说说运算符重载:

在需要比较两个相同类的大小时会用到

内置类型可以直接使用各种运算符的,而自定义类型不支持运算符。因为内置类型是系统定义的,系统有内置类型的相关规则,而自定义类型系统没有相关规则,自定义对象+1是加到_year,_mouth,还是_day,系统不知道

为了自定义类型可以使用各种运算符,制定了运算符重载的规则。

运算符重载涉及一个新的关键字:operator,operator后面加运算符就可以重载这个运算符,本质上是通过函数来实现对自定义类型的比较。运算符重载函数的参数个数取决于运算符的操作数。

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

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

函数原型:返回值类型 operator操作符(参数列表)

注意:

1.不能通过连接其他符号来创建新的操作符:比如operator@

2.重载操作符必须有一个类类型或者枚举类型的操作数

3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义

4.作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的

5.操作符有一个默认的形参this,限定为第一个形参

6.sizeof 、:: 、.*、?: 、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。(不是*是.*,两者不一样)

class Date

{

public:

    Date(int year = 0, int mouth = 0, int day = 0)

    {

        _year = year;

        _mouth = mouth;

        _day = day;

    }

    void print()

    {

        cout << _year << "-" << _mouth << "-" << _day << endl;

    }

private:

    int _year;

    int _mouth;

    int _day;

};

int main()

{

    Date d1(2022, 6, 2);

    Date d2(2022, 6, 3);

    return 0;

}

当需要比较d1,d2是否相等时,运算符重载函数如下:

bool operator==(Date d1, Date d2)

{

    return d1._year == d2._year && 

       d1._mouth == d2._mouth && 

       d1._day == d2._day;

}

调用方式:

if(operator == (d1, d2))

{

    cout << " == " << endl;

}

这样写是编译不通过的,原因很简单,d1,d2的成员变量是私有的,不能直接调用。

解决方案有三种:

1.讲成员变量设置成公有——直接ps

2.通过在类中写获得成员变量的函数来获得成员变量

class Date

{

public:

    ……

    int GetYear()

    {

        return _year;

    }

}

比较的地方修改成d1.GetYear() == d2.GetYear(),GetMouth和GetDay函数也要写,这里省略。

随之而来的还有一个新的问题,使用运算符的方式变成了:operator==(实参1, 实参2),感觉很别扭,没有使用运算符的感觉,还不如写成operatorEqual(形参1,形参2)的形式。

所以实际中使用就是按运算符来使用的。

if(d1 == d2)

{

    cout << "==" << endl;

}

编译器在发现d1是自定义类型后,会去看==有没有函数重载,如果有,if(d1 == d2)会被编译器处理成对应的重载运算符函数if(operator == (d1, d2))

3.将运算符重载函数直接放到Date类中去——最终归宿

bool operator==(Date d1, Date d2)

{

    return d1._year == d2._year && 

       d1._mouth == d2._mouth && 

       d1._day == d2._day;

}直接放入会报错:函数参数太多

因为函数参数中有一个隐藏的this指针,所以实际应该这么写:

bool operator==(Date d)

{

    return _year == d._year && 

       _mouth == d._mouth && 

       _day == d._day;

}

调用的地方变成了:d1.operator==(d2)

当然也可以写成d1 == d2,编译器会在全局域和类域中找(优先在类域中找),如果存在运算符重载函数,编译器会替换运算符重载函数,即此时d1==d2会被处理成d1.operator==(d2),实际会处理的更深一点:if(d1.operator==(&d1, d2))

之前的方式都要调用拷贝构造函数,可以改用引用,如果不需要修改引用对象的话,最好加上const

class Date

{

public:

    Date(int year = 0, int mouth = 0, int day = 0)

    {

        _year = year;

        _mouth = mouth;

        _day = day;

    }

    void operator=(const Date& d)

    {

        _year = d._year;

        _mouth = d._mouth;

        _day = d._day;

    }

    void print()

    {

        cout << _year << "-" << _mouth << "-" << _day << endl;

    }

private:

    int _year;

    int _mouth;

    int _day;

};

int main()

{

    Date d1(2022, 6, 2);

    Date d2(2022, 6, 3);

    Date d3(d1);//拷贝构造函数——一个已经存在的对象初始化一个要创建的对象

    d2 = d1;//赋值运算符重载——两个已经存在的对象之间的赋值

    return 0;

}

这里的赋值重载函数是有问题的,因为赋值表达式有返回值的,要支持连续赋值。

实例:

int i = 0, j , k;

k = j = i;//表达式j = i要有返回值才能赋值给k

所以应该写成:

Date operator=(const Date& d)

{

    _year = d._year;

    _mouth = d._mouth;

    _day = d._day;

    return *this;//this是指向d2的指针,解引用指针就可以得到d2

}

当然还是存在问题,比如传值返回,要有中间变量,还要调用拷贝构造函数,可以改进为引用返回(仅仅在出作用域后变量没有销毁的情况下用):Date& operator=(const Date& d)

如果写成const Date& operator=(const Date& d),(d3 = d2) = d1;就不合乎语法了,因为返回值为const Date&,所以d3被强转为const Date类型,因为优先级的问题,先执行d3 = d2,再执行d3 = d1。d3 = d2是一个运算符重载函数,函数结束后,d3才变成const Date类型。此时d3 = d1就会出现问题。d3变成const Date类型是暂时性的,只限于本行代码。原生=运算符就是不加const的,所以写函数重载的时候也不要加。

还有一种可能d1 = d1,自己给自己赋值,语法上没有问题,但是降低效率所以运算符重载函数可以再修改一下:

Date& operator=(const Date& d)

{

    if(this != &d)//如果使用*this != d,则需要考虑有没有重载 !=,而且一个是指针比较,一个是函数调用,指针比较更优

    {

        _year = d._year;

        _mouth = d._mouth;

        _day = d._day;

    }

    return *this;

}

赋值运算符重载函数也是一个默认函数,规则参考拷贝构造函数。不写,编译器会自动生成一个赋值运算符重载函数,进行对值的浅拷贝。注意:只有赋值运算符重载函数是默认函数,其他运算符要自己写重载。

void func(const Date& d)

{

    d.print();

    //这里会报错,还有一些传值(值为类)返回的函数,不使用对象接受返回值,而是直接使用返回值调用print函数也会报错,原因和这种情况一样

}

int main()

{

    Date d1(2022, 6, 13);

    d1.print();

    func(d1);

}

已知编译器会将类中print函数处理成这样:

void print(Date* const this)

{

    cout << _year << "-" << _mouth << "-" << _day << endl;

}

也就是说d1.print(&d1)能通过d.print(&d)不能通过,原因在于:this指针是Date* const类型,而d是const Date类型,取地址后就是const Date*形式。const Date*类型赋给Date* const类型,典型的权限放大。this指针是隐性的,不能显性的作形参,没有办法在参数前加const,作为弥补方案,可以在函数后面加const表示被const修饰:

void print() const //相当于void print(const Date* const this)

{

    cout << _year << "-" << _mouth << "-" << _day << endl;

}

在任何有this指针函数后面加上const,都表示this指针被修饰成const Date* const this形式,this指向空间中的数据不可修改。如果声明和定义分开,那么声明和定义都要加上const

建议不需要修改成员变量的成员函数都加上const,让普通对象和const对象都可以调用

思考:

1.const成员函数能不能调用非const的成员函数?

2.非const成员函数能不能调用const的成员函数?

成员函数调用成员函数就是指在成员函数中调用另一个成员函数

Date operator=(const Date& d)

{

    print();//编译器会处理成this->print();

    _year = d._year;

    _mouth = d._mouth;

    _day = d._day;

    return *this;

}

1不可以,2可以,调用成员函数的本质就是将this指针传递过去。

1不可以的原因就是const Date* const this传递给了 Date* const this造成了权限放大。

最后两个默认成员函数:取地址操作符函数重载以及const取地址操作符函数重载

Date* operator&()

{

    return this;

}

const Date* operator&() const

{

    return this;

}

默认生成的函数就够用了,除非你不想别人拿到你的真实地址。

一个完整的日期类需要写很多运算符重载函数,>,>=,==,!=,<,<=,但不是所有运算符都需要重载,比如+,日期加日期,没有意义,根据具体情况,需要重载的是那些有意义的运算符。不过日期加天数就有意义了。还有前置++和后置++的区别,在函数中也要体现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值