C++:this指针 and 类的六个默认成员函数

首先来看一个例子:

class Date
{public:
    void Show()
    {
        cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

我的问题是:Show函数没有传参,为什么能够访问得到成员变量呢?
原因就在于this指针。

一个对象的this指针并不是对象本身的一部分,this作用域是在类内部,只能在成员函数中使用。当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

下图说明:
这里写图片描述

可以看到,this指针是被隐含传递了,并且默认为成员函数的第一个参数。

那么我们可以自己使用this指针传递吗?
不可以。隐含传递this指针是编译器的任务,我们不能越俎代庖。但是我们可以使用该隐含的this指针。
比如下面的代码为例:

这里写图片描述

还有几个容易被忽略的问题:
this指针存放在何处?其实很容易想到,this指针t在成员函数的开始执行前构造,在成员的执行结束后清除。那么也就是说,在函数调用期间,this指针才会存在,所以this指针一定存在于栈上。

this指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全局变量。在汇编级别里面,一个值只会以3种形式出现:立即数、
寄存器值和内存变量值。不是存放在寄存器就是存放在内存中,它们并不是和高级语言变量对应的。

默认成员函数

构造函数

成员变量是私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该有且仅在对应对象时自动执行一次,这时调用的函数称为构造函数。

class Date
{
public:
    //无参的构造函数
    Date()
    {}
    //带参的构造函数
    Date(int year,int month,int day)
    {
        _year=year;
        _month=month;
        _day=day;
    }
private:
    int _year;
    int _month;
    int _day;
};
class Date
{
public:
    //缺省参数的构造函数
    Date(int year=1900,int month=1,int day=1)
    {
        _year=year;
        _month=month;
        _day=day;
    }
    //半缺省参数的构造函数
    Date(int year,int month=1)
    {
        _year=year;
        _month=month;
        _day=1;
    }
private:
    int _year;
    int _month;
    int _day;

};

特点:

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

析构函数

当一个对象的生命周期结束时,c++编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数。

特点:

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

  2. 析构函数无参数无返回值。

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

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

  5. 析构函数并不是删除对象,而是做一些清理工作。清理工作,并不是删除,删除是由操作系统完成的。

    当需要free,close的可以交给析构函数来管理。

class Array
{
public:
    Array(int size)
    {
        _ptr=(int*)malloc(size*sizeof(int));
    }
    //析构函数
    ~Array()
    {
        free(_ptr);
        _ptr=NULL;
    }
private:
    int* _ptr;
};

拷贝构造

创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造函数,

特征:

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

为什么会引发无穷递归调用?原因是:
如果使用值传递时,会调用自己的拷贝构造进行初始化赋值时,又会调用自己的拷贝构造,仍然是值传递,那么就会一直调用自己的拷贝构造。
见下例:

这里写图片描述

可参考:关于拷贝(复制)构造函数为什么不能用值传递

当我们不写拷贝构造函数时,系统会自动默认生成一个拷贝构造函数。那么不写可不可以,会不会出现什么问题。在下面这种场景下,如果不写拷贝构造就会出现问题。
这里写图片描述

class Date
{
public:
    //缺省参数的构造函数
    Date(int year=1900,int month=1,int day=1)
    {
        _year=year;
        _month=month;
        _day=day;
    }
    //析构函数
    ~Date()
    {}
     //拷贝构造函数
    Date(const Date& d)
    {
        _year=d._year;
        _month=d._month;
        _day=d._day;
    }
    void Show()
    {
        cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(1988,8,2);
    d1.Show();
    //调用拷贝构造函数,两种方式等价
    Date d2(d1);
    d2.Show();
    Date d3=d1;
    d3.Show();
    return 0;
}

运算符重载

运算符重载的意义:为了增强的代码的可读性。

运算符重载的特征:

  1. operator加合法的运算符 构成函数名
  2. 重载运算符之后,不能改变运算符的优先级/结合性/操作数个数

5个不能重载的运算符:

  1. ‘.’ 成员访问
  2. .* 成员指针访问
  3. :: 域运算符
  4. sizeof 长度运算符
  5. ?: 条件运算符

赋值运算符重载

当我们使用单纯的运算符的功能不能够帮助我们实现一些功能时,c++的运算符重载就能够帮助我们解决这个问题。

//赋值运算符的重载
    Date& operator= (const Date& d)//为什么返回的是引用
    {
        if(this!=&d)//该条件判断了?
        {
            this->_year=d._year;
            this->_month=d._month;
            this->_day=d._day;
        }
        return *this;
    }

在这段代码中我做了两个标注的问题:
1.为什么返回的是引用类型:考虑的是以下场景,在使用赋值运算符时,a=b=c;可以理解为先将c赋给b,b通过隐含的this指针传递给成员函数,再进行成员变量的一一赋值,然后将该结果作为引用返回,那么就可以将刚才的结果作为下一个赋值的一个操作数,继而进行赋值。

2.if判断语句判断了什么条件?由于左操作数是通过this指针隐含传递的,所以需要判断一次是否是将自己赋给自己。

3.返回*this:在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;

成员变量的初始化

成员变量的两种初始化方式:

  1. 初始化列表
  2. 构造函数内进行赋值

初始化列表:以一个冒号开始,接着一个逗号分隔数据列表,每个数据成员都放在一个括号中进行初始化,尽量使用初始化列表进行初始化,因为比较高效。

为什么高效?
如果这个类的大小非常大,当使用构造函数初始化时,需要先调用一次默认构造函数,在进行成员变量的赋值,在这中间的开销是非常大的。当使用初始化列表时,只需要调用一次拷贝构造函数即可。效率的差别不言而喻。

那些成员变量必须放在初始化列表中?

  1. 常量成员变量。
  2. 引用类型成员变量。
  3. 没有缺省构造函数的类成员变量。

成员变量按声明顺序执行,而不按照出现顺序执行。声明的顺序与定义的顺序保持一致。

猜你喜欢

转载自blog.csdn.net/mxrrr_sunshine/article/details/80857304