代码改变世界

白话C++系列(14)-- this指针

2016-04-28 21:37  Keiven_LY  阅读(1028)  评论(0编辑  收藏  举报

this指针

我们先来看一个下面的例子

在这个例子中,我们定义了一个Array数组的类,并且只定义了一个数据成员len,同时定义了三个成员函数:一个是Array类的有參的构造函数,将传入的_len赋值给其数据成员len,还有两个数据成员len的封装函数(getLen和setLen)。通过观察,大家是不是发现参数与数据成员均不重名(比如我们这里的数据成员是len,但是所有的传入参数都是_len)。大家回想一下,在此前的代码中,是不是也都是这种情况?是不是也都是数据成员与它的参数在表达同一个意思的时候用的是不同的名字,这也是当时我们故意这样做的。为什么这样做呢,这是为了顺利完成前面知识的讲解。但是,大家当时是不是都注意到这个问题呢?是否考虑过,如果传入的参数与数据成员重名会怎样呢?下面我们就来一探究竟。还是看一个例子,如下:

我们看到在这个例子当中,传入的参数与其数据成员重名了。那么,重名之后,我们发现有两个问题:一个是其构造函数中,一个是setLen()封装函数中,无论是我们还是计算机无法判断究竟是将传入的参数赋值给其数据成员了,还是将其数据成员赋值给传入的参数了。既然计算机无法判断,就会把这样的一种赋值认为是错误的。可见在这个例子中,我们遇到的主要问题呢,就是编译器无法分辨哪个是作为参数的len,哪个又是作为数据成员的len。这就是说,我们迫切需要一种技术,这种技术要么可以标记出参数,要么可以标记出数据成员,那么这种技术就是我们这里所要讲到的this指针。

this指针是什么呢?

this指针就是指向其自身数据的指针。

我们一起来看一下,在刚才的例子中,如果我们实例化一个arr1对象,那么this指针就相当于给arr1取地址,也就是说this就是arr1的地址;如果我们继续实例化一个对象arr2,那么this指针此时就是arr2的地址。我们画一个示意图如下所示:

可见,通过this指针就可以访问到它表达的对象的自身的任何数据。比如说,当this是arr1的地址的时候,就可以访问到arr1的数据成员len及其他数据成员;如果this表达的是arr2的地址的时候,也就可以访问到arr2的数据成员len及其他数据成员。这从另一个角度来说,就可以标记处它自身的数据成员,应用到代码当中呢,我们可以写成这样:

我们可以看到,如果我们用与数据成员重名的参数,那么我们就可以在数据成员的前面用this加指针符号来表达数据成员的len,然后将参数的len赋值给数据成员的len。这样计算机就不会疑惑究竟是传入的参数赋值给其数据成员了,还是将其数据成员赋值给传入的参数了,从而就可以正常的编译了,进而我们也可以使用与数据成员重名的参数来进行表达了。下面我们回到之前我们没有使用this指针的例子来继续观察。

通过观察,我们还发现了什么呢?难道大家没有对成员函数中直接去访问数据成员这种做法产生过怀疑吗?好吧,我们还是先来回顾一下,此前所学的一些知识吧。

  这是一个汽车的类,在这个汽车的类中,我们有一个数据成员,这个数据成员就是指这个汽车的轮子的个数,当然,还定义了一个成员函数。当时我们给大家讲的时候,是讲的这些数据成员以及它的成员函数究竟是放在内存中的什么位置的。回顾一下,如上面有图所示,如果我们实例化了一个car1对象,那么car1就拥有了自己的数据成员(轮子的个数 wheelCount),同理我们又实例化了一个car2和car3,那么car2和car3也就拥有了自己的数据成员(轮子的个数wheelCount),但是呢,它的成员函数却只有一份,这份成员函数是写在代码区的,如果car1这个对象,car2这个对象,car3这个对象分别去调用成员函数的时候,那么car1,car2和car3都可以去访问代码区中的这个成员函数,而访问的时候也不会出任何的问题,在成员函数被调用当中呢,也各自调用了各自的数据成员,并且也没有出现混乱。

  那么讲到这里,大家是不是发现了什么呢?既然函数的逻辑代码都是以二进制的方式存储在代码区中,参数中也没有数据成员,那么在调用数据成员的时候怎么可能成功呢?更重要的是,当存在多个对象时,函数又如何确定该调用哪个对象的数据成员呢?要解决这个问题,也归功于this指针,我们继续来看下面这个例子。

这个例子非常奇怪,我们仔细的看一下。对比之前的例子,是不是每一个成员函数的参数列表中都多出了一个this指针,那么有了这样一个this指针,刚刚前面所提到的一系列问题也就迎刃而解了。

我们可以设想一下,当我们在实例化对象,并使用这些成员函数时,this指针就代表着这个对象本身的地址,也就是说,当我们去实例化arr1的时候,此时在它的构造函数传入参数this,那么当它执行给len赋值10的时候,就相当于是在给this的len赋值10的操作。因为this就指的arr1,所以我们用this去指向len的时候,其实指向的就是arr1这个对象的len,也就不会给其他的对象赋值了。同理如果用arr1去调用另外一个成员函数getLen()的时候呢,我们在这也同时传入了一个this,所以我们在调用return len语句的时候,就相当于调用return this->len,也就是arr1的len,也就不会调用错这个数据成员了。同理当我们去实例化一个arr2对象的时候,这个时候的this就是arr2的地址了,那么调用arr2的成员函数时就跟调用arr1的成员函数一样的意思。从而使arr1和arr2在同时调用成员函数的时候呢,不会产生对象错乱的情况,因为每次调用成员函数呢,都需要this指针。所以C++编译器就干脆把这些事就替我们干了,于是摆在我们面前的成员函数就成了下面的样子(不加this),其实在编译的时候,编译器会自动的为每一个成员函数或者参数列表都加上this指针,因为编译器已经为我们干了这些事情,所以我们在自定义的时候就不需要加this指针这个参数,使用的时候也完全可以当作没有这回事。最后,还有一个问题,那就是系统为每一个成员函数都加了一个this指针,那么这个this指针究竟加在这个参数列表的什么位置呢?是第一个位置,还是最后一个位置呢?为什么要这样设计呢?请看下回分解。

this指针代码实践

题目描述:

/* 示例要求

定义一个Array类

数据成员:m_iLen 表示数组长度

成员函数:

构造函数

    析构函数

       m_iLen的封装函数

    信息打印函数printInfo

/*  **********************/

程序结构:

头文件(Array.h

class Array
{
public:
    Array(int len);
    ~Array();
    void setLen(int len);
    int getLen();
    Array printInfo();
private:
    int len;
};

源程序(Array.cpp

#include"Array.h"
#include<iostream>
using namespace std;

Array::Array(int len)
{
    this->len = len;
}
Array::~Array()
{
    cout <<"~Array()"<< endl;
}
void Array::setLen(int len)
{
    this->len = len;
}
int Array::getLen()
{
    return len;
}
Array Array::printInfo()
{
    cout <<"len = "<< len << endl;
    return *this;   //this本身是一个指针,加上*后就是一个对象
}

主调程序(demo.cpp

#include<iostream>
#include<stdlib.h>
#include"Array.h"
using namespace std;
int main()
{
    Array arr1(10);
    arr1.printInfo().setLen(5);  
    cout <<"len = "<< arr1.getLen() << endl;
    system("pause");
    return 0;
}

这里我们先实例化了一个对象arr1,并且对数据成员赋了初值10(实例化的过程就应该调用了构造函数);第二行我们先是调用了printInfo()函数,打印出arr1的长度,并且调用的结果就是返回一个Array对象,然后我们接着又让这个返回的Array对象调用了setLen()函数,并且对这个对象的数据成员赋值5(目的是想看通过这样的操作后,arr1的长度是不是由10变成了5),我们运行一下程序,结果如下:

从结果看,出来两遍arr1的长度都是10,可见在这里我们调用setLen()并没有改变arr1的长度,这是为什么呢?这是因为我们调用完printInfo()函数后,返回出去的*this变成的是另外一个对象,它并不是arr1。那么如果想要让它是arr1我们该怎么办呢?我们前面已经学过,如果采用引用的话,就可以实现这一目的了。所以我们在Array.h和Array.cpp中将printInfo()函数修改如下:

Array.h中作如下声明:Array& printInfo();

Array.cpp中作如下实现:

Array &Array::printInfo()
{
    cout <<"len = "<< len << endl;
    return *this;   
}

主调函数不变,我们再来运行程序,结果如下:

从运行结果看,arr1的长度已经由10变成了5。更离奇的是,我们使用了连续的点号(arr1.printInfo().setLen(5);),这样就使得多个方法能串起来使用。这一点就能够发挥出this指针的作用。想一下,如果我们这个时候将setLen()也改造一下,是不是它的后面也可以加点号呢?我们来尝试一下,如下:

Array.h中作如下声明:Array& setLen(int len);

Array.cpp中作如下实现:

Array &Array::setLen(intlen)
{
    this->len = len;
    return *this;
}

demo.cpp修改如下:

int main()
{
    Array arr1(10);
    arr1.printInfo().setLen(5).printInfo();  

    system("pause");
    return 0;
}

运行结果如下:

从运行结果来看,使得连续的操作都是针对的是arr1的操作。

最后,我们再来通过代码来说明一下this指针的本质。之前我们已经说过,this指针的本质就相当于它所在对象的地址,那么我们来验证一下(通过打印出this指针的值)

我们来修改一下printInfo()函数如下:

Array.h中作如下声明:Array* printInfo(); //让它指直接返回指针

Array.cpp中作如下实现:

Array* Array::printInfo()
{
    cout <<"this指针的地址是: "<<this<< len << endl;
    returnthis;   
}

demo.cpp修改如下:

int main()
{
    Array arr1(10);
    arr1.printInfo();
    cout <<"arr1的地址是: "<<&arr1 << endl;

    system("pause");
    return 0;
}

运行结果如下:

 从运行结果来看,this指针的值与arr1这个对象的地址是一样的,也就验证了this指针的本质就是其所在对象的地址。