C++:默认成员函数和this指针

类的六个默认成员函数
这里写图片描述
在学习这些默认的成员函数之前,务必先来了解一下this指针,因为除了构造函数外其他的默认成员函数都有一个隐式的形参–this指针。这对于我们之后在默认成员函数的学习和使用方面有着很大的影响。
this指针

1、每个成员函数都有一个指针形参,他的名字是固定的,称为this指针,this指针是隐式的构造函数没有this指针)。
2、编译器会对成员函数进行处理,在对象调用成员函数时,对象地址做实参传递给成员函数的第一个形参this指针。
3、this指针是成员函数的隐含的指针形参,是编译器自己处理的,我们不能再成员函数的形参中添加this指针的参数定义,也不能在调用时显示传递对象地址给this指针。
如图:编译器处理成员函数隐含的this指针:
这里写图片描述
需要注意一个问题,有时候也会被面试问到。
this指针是隐式的,不需要我们定义也不需要显式的传参,但他依旧是一个形参,因此this指针存在于栈上。

接下来我们主要学习以下4个默认成员函数。
1、构造函数
首先我们就有一个问题,在定义一个类时,我们通常会将成员变量定义为私有的,那么我们如何对这些私有的成员变量进行初始化呢???

//比如我们定义一个日期类
class Date
{
public:
    void Display()
    {
        cout<<year<<"-"<<month<<"-"<<day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
}
//我们看到这里的成员变量是私有的,如何初始化?
//这里我们就有了构造函数这一说

注意一点:构造函数是公有的,并且只在定义对象时自动执行一次
什么样的函数才叫构造函数呢??接下来看一下构造函数所具有的几大特征

1、函数名与类名相同
2、没有返回值
3、对象构造时系统自动调用对应的构造函数
4、构造函数可以重载
5、可以在类里面定义,也可以在类外面定义
6、若类中没有给出构造函数,则C++编译器会自动产生一个缺省的构造函数,但一旦我们定义了,系统就不会自动生成了。
7、无参的构造函数和全缺省的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个

代码展示:

class Date
{
public:
    //1.1:无参构造函数
    Date() //函数名与类名相同,没有返回值
    {
        _year = 1900;
        _month = 1;
        _day = 1;
    }
    //无参构造函数与全缺省的构造函数都是缺省构造函数,且只能存在一个。
    //1.2:全缺省的构造函数
    //Date(int year=1900,int month=1,int day=1)
    //{
    //    _year = year;
    //    _month = month;
    //    _day = day;
    //}

    //2:带参构造函数
    Date(int year,int month,int day)  //可构成函数重载
    {
        _year = year;
        _month = month;
        _day = day;
    }
    //1和2两个构造函数构成函数重载
    //函数重载:函数名相同,参数列表不同(参数个数,参数类型)
    //返回值可同可不同。
    void show()
    {
        cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
void Test()
{
    Date d1;//会调用无参构造函数
    Date d2(2018,7,31);//会调用有参构造函数
}

2、拷贝构造函数
我们有时候会遇到一种情况就是当我们实例化出一个新的对象时,想要用一个同类的对象对其进行初始化,这时我们就需要用到拷贝构造函数。

拷贝构造函数的特征:

1、拷贝构造函数其实就是构造函数的一个重载。
2、拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。
3、若为显式的定义,系统会默认缺省的拷贝构造函数,缺省的拷贝构造函数会依次拷贝类成员进行初始化。

代码展示:

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;
    }
private:
    int _year;
    int _month;
    int _day;
};
void Test()
{
    Date d1(2018,1,1);
    //以下两种方法等价
    Date d2(d1);
    Date d3=d1;
}

这里写图片描述
为什么拷贝构造函数必须使用引用传参?因为使用传值方式会引发无穷递归问题。
这里的无穷递归问题到底又是怎么一回事?
这里写图片描述
3、析构函数
我们知道构造函数是对象实例化时自动调用对应的构造函数,而当一个对象的生命周期结束时也会自动调用一个函数—析构函数。(其目的是为了做一些清理工作)
析构函数的几大特征:

1、函数名是在类名前加上~
2、没有参数没有返回值(包括void类型)
3、一个类有且只有一个析构函数(不能重载)
4、对象生命周期结束时,C++编译系统会自动调用析构函数
4、若类中没有给出析构函数,则C++编译器会自动产生一个缺省的析构函数(即使我们定义了,系统还是会自动生成一个,在调用时先调用用户自定义的在调用系统自动生成的)。

注意:因为我们说是在一个对象的生命周期结束的时候调用析构函数,就会想到说那么析构函数的作用就是把这个对象删掉。其实不是,析构函数并不会把一个生命周期已经结束的对象删除,只是做一些清理工作。
问题又来了,清理工作到底是什么???
代码展示:

class Array
{
public:
    //有参构造函数
    Array(int n)
    {
        _ptr = (int*)malloc(n*sizeof(int));
    }
    //析构函数
    ~Array()//没有参数和返回值
    {
        if(_ptr)
        {
            free(_ptr);//清理工作,也就是释放动态开辟的空间
            _ptr = NULL;
        }
    }
private:
    int *_ptr;
};

4、赋值运算符重载
为了增强程序的可读性,C++支持运算符的重载。
运算符重载特征:

1、operator+合法的运算符构成函数名(如:重载>运算符的函数名为 operator>)。
2、重载运算符后,不能改变运算符的优先级/结合性/操作数个数
3、.(成员访问运算符).*(成员指针访问运算符)::(域运算符)?:(条件运算符)sizeof(长度运算符)。这五个运算符是不能被重载的。

赋值运算符的重载是对一个已经存在的对象进行拷贝赋值。
代码展示:

class Date
{
public:
    //构造函数
    Date()
    {
        _year = 1900;
        _month = 1;
        _day = 1;
    }
    //拷贝构造函数
    Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    //赋值运算符重载函数
    Date& operator= (const Date& d)//&是引用传参
    {
        if(this != &d)//&是取地址
        //这里的判断是为了防止自己给自己赋值的情况
        {
            this->_year = d._year;
            this->_month = d._month;
            this->_day = d._day;
        }
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};

那么想一想,赋值运算符重载函数为什么返回值是 Date&呢???
看一下:

Date d1;
Date d3;
d3 = d1;//调用赋值运算符的重载
//走到这我们的d1已经赋给了d3
//试想。如果赋值运算符的重载返回的不是Date&,假设是void
//那么如果我们还想要将d3赋值给另外一个对象,一定是不行的
//因为在类型上都不匹配了,新实例化的对象是Date,而你在之前
//的赋值完成返回以后就把d3的类型改了,不再是Date类的对象了

所以说,之所以要返回Date&,是为了之后可以继续使用进行赋值。
另外需要注意的一点就是,拷贝构造的调用和赋值运算符重载的调用的区别。

Date d1;
//调用拷贝构造
Date d2 = d1;//此时d2还并不存在

//调用赋值运算符的重载
Date d3;//d3已经被实例化出来
d3 = d1;

//拷贝构造,顾名思义就是以拷贝的方式去构造出来,
//所以操作的对象是一个不存在的对象和一个已经存在的对象之间
//赋值是两个都已经存在的对象之间进行

猜你喜欢

转载自blog.csdn.net/qq_40927789/article/details/81294336