代码改变世界

白话C++系列(13)-- 对象指针、对象成员指针

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

对象指针

所谓对象指针,顾名思义就是有一个指针,其指向一个对象,下面通过一个例子来说明这样一个问题。

在这个例子中,我们定义了一个坐标的类(Coordinate),其有两个数据成员(一个表示横坐标,一个表示纵坐标)。当我们定义了这个类之后,我们就可以去实例化它了。如果我们想在堆中去实例化这个对象呢,就要如下所示:

通过new运算符实例化一个对象后(这个对象就会执行它的构造函数),而对象指针p就会指向这个对象。我们的重点是要说明p与这个对象在内存中的相关位置以及它们之间的对应关系。

当我们通过这样的方式实例化一个对象后,它的本质就是在内存中分配出一块空间,在这块空间中存储了横坐标(m_iX)和纵坐标(m_iY),此时m_iX的地址与p所保存的地址应该是一致的,也就是说p所指向的就是这个对象的第一个元素(m_iX)。如果想用p去访问这个元素,很简单,就可以这样来访问(p -> m_iX或者p -> m_iY),也可以在p前加上*,使这个指针变成一个对象,然后通过点号(.)来访问相关的数据成员(如(*p).m_iY)。接下来看一下如下的具体范例。

注意:这里的new运算符可以自动调用对象的构造函数,而C语言中的malloc则只是单纯的分配内存而不会自动调用构造函数。

对象指针代码实践

题目描述:

/* 示例要求

定义Coordinate类

    数据成员:m_iX和m_iY

    声明对象指针,并通过指针操控对象

 

    计算两个点,横、纵坐标的和

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

头文件(Coordinate.h)

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
public:
    int m_iX;
    int m_iY;
};

源程序(Coordinate.cpp

#include"Coordinate.h"
#include<iostream>

using namespace std;

Coordinate::Coordinate()
{
    cout <<"Coordinate()"<< endl;
}
Coordinate::~Coordinate()
{
    cout <<"~Coordinate()"<< endl;
}

主调程序(demo.cpp

#include"Coordinate.h"
#include<iostream>

#include<stdlib.h>

using namespace std;

int main()
{
    /* 使用两种方法定义对象指针 */
    Coordinate *p1 = NULL;//定义一个对象指针
    p1 = new Coordinate; //让p1指向一段内存,这里也可以写成p1 = new Coordinate(),因为其默认构造函数没有参数
    Coordinate *p2 = new Coordinate();
    
    /* 使用两种方法让对象指针访问数据成员 */
    p1->m_iX = 10;
    p1->m_iY = 20;
    (*p2).m_iX = 30;
    (*p2).m_iY = 40;
    cout << p1->m_iX +(*p2).m_iX << endl;
    cout << p1->m_iY +(*p2).m_iY << endl;
    delete p1;
    p1 = NULL;
    delete p2;
    p2 = NULL;
    system("pause");
    return 0;
}

运行结果:

此外,作为对象指针来说,还可以指向栈中的一块地址,怎么来做呢?我们来修改一下主调程序如下:

#include"Coordinate.h"
#include<iostream>
#include<stdlib.h>

using namespace std;

int main()
{
    ///* 使用两种方法定义对象指针 */
    //Coordinate *p1 = NULL;//定义一个对象指针
    //p1 = new Coordinate; //让p1指向一段内存,这里也可以写成p1 = new Coordinate(),因为其默认构造函数没有参数
    //Coordinate *p2 = new Coordinate();
    //
    ///* 使用两种方法让对象指针访问数据成员 */
    //p1->m_iX = 10;
    //p1->m_iY = 20;
    //(*p2).m_iX = 30;
    //(*p2).m_iY = 40;
    //cout << p1->m_iX +(*p2).m_iX << endl;
    //cout << p1->m_iY +(*p2).m_iY << endl;
    //delete p1;
    //p1 = NULL;
    //delete p2;
    //p2 = NULL;

    Coordinate p1; //从栈中实例化一个对象p1
    Coordinate *p2 = &p1; //让对象指针p2指向p1
    p2->m_iX = 10;
    p2->m_iY = 20;

    //这里我们来打印p1的横坐标和纵坐标,来说明是对象指针p2操纵了对象p1
    cout <<"对象p1这个点的坐标是:("<< p1.m_iX <<","<< p1.m_iY <<")"<< endl; 

    system("pause");
    return 0;
}

对象成员指针

对象成员指针是什么呢?那么我们来想一想,之前我们学习过对象成员。对象成员,就是作为一个对象来说,它成为了另外一个类的数据成员。而对象成员指针呢,则是对象的指针成为了另外一个类的数据成员了。

我们先来回顾一个熟悉的例子,如下:

左边呢,我们定义了一个点的坐标类,它的数据成员有点的横坐标和纵坐标;右边呢,我们定义了一个线段类,在这个线段类中,需要有两个点(一个起点和一个终点),我们用点A和点B来表示,我们当时用的是坐标类的对象,分别是m_coorA和m_coorB。现在呢,我们要把它们变成指针,如下:

初始化的时候呢,与对象成员初始化的方法可以是一样的,使用初始化列表来初始化,只不过现在是指针了,所以我们赋初值NULL。

除了可以使用初始化列表进行初始化以外,还可以使用普通的初始化,比如说,在构造函数中,写成如下方式:

当然,更多的是下面的情况,因为我们这是两个指针,一定要指向某一个对象,才能够进行操作,才会有意义。而它指向的就应该是两个点的坐标对象:

在这里面,指针m_pCoorA指向了一个坐标对象(1,3),m_pCoorB指向了另外一个坐标对象(5,6)。那么,这就相当于在构造函数当中,我们从堆中分配了内存。既然在构造函数当中从堆中分配了内存,那么我们就需要在析构函数中去把这个内存释放掉,这样才能够保证内存不被泄漏。

此外呢,作为对象成员和对象成员指针还有另外一个很大的不同。作为对象成员来说,如果我们使用sizeof这个对象的话,它就应该是里面所有对象的体积的总和(如下图所示)

  而对象成员指针则不同,我们来看一看刚刚对象成员指针我们定义的时候是如何定义的。我们可以看到,我们定义的时候呢,是写了两个指针作为它的对象成员。而我们知道,一个指针在32位的编译器下面,它只占4个基本内存单元,那么两个指针呢,则占8个基本内存单元,而我们前面所讲到的Coordinate类呢,它有两个数据成员,这两个数据成员都是int型的,所以呢,每一个数据成员都应该占4个基本的内存单元。那么这样算下来呢,我们来想一想,如果我们使用sizeof来判断一个line这样的对象,到底有多大呢?如果在line这个对象中定义的是对象成员(即两个Coordinate),那么这两个Coordinate每一个就应该都占8个基本内存单元,那么两个呢,就应该占16个基本内存单元,打印出来就应该是16,但是现在呢,line对象中是两个对象成员指针,那么每一个对象成员指针应该只占4个基本内存单元,所以sizeof(line)计算出来就应该是8,加起来是这两个指针的大小的总和。

内存中的对象成员指针

当实例化line这个对象的时候,那么两个指针(m_pCoorA和m_pCoorB)也会被定义出来,由于两个指针都是指针类型,那么都会占4个基本内存单元。如果我们在构造函数当中,通过new这样的运算符从堆中来申请内存,实例化两个Coordinate这样的对象的话呢,这两个Coordinate对象都是在堆中的,而不在line这个对象当中,所以刚才我们使用sizeof的时候呢,也只能得到8,这是因为m_pCoorA占4个基本内存单元,m_pCoorB占4个基本内存单元,而右边的两个Coordinate对象并不在line这个对象的内存当中。当我们销毁line对象的时候呢,我们也应该先释放掉堆中的内存,然后再释放掉line这个对象。

对象成员指针代码实践

/* 对象成员指针

要求:

定义两个类:

    坐标类:Coordinate

    数据成员:m_iX和m_iY

    成员函数:构造函数、西沟函数、数据成员封装函数

    线段类:Line

    数据成员:点A指针 m_pCoorA,点B指针m_pCoorB

    成员函数:构造函数、析构函数、信息打印函数

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

头文件(Coordinate.h

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
    int getX();
    int getY();
public:
    int m_iX;
    int m_iY;
};

源程序(Coordinate.cpp)

#include"Coordinate.h"
#include<iostream>

using namespace std;

Coordinate::Coordinate(int x, int y)
{
    m_iX = x;
    m_iY = y;
    cout <<"Coordinate()  "<< m_iX <<","<< m_iY << endl;
} 
Coordinate::~Coordinate()
{
    cout <<"~Coordinate()  "<< m_iX <<","<< m_iY << endl;
}
int Coordinate::getX()
{
    return m_iX;;
}
int Coordinate::getY()
{
    return m_iY;;
}

头文件(Line.h

#include"Coordinate.h"

classLine
{
public:
    Line(int x1, int y1, int x2, int y2);
    ~Line();
    void printInfo();
private:
    Coordinate *m_pCoorA;
    Coordinate *m_pCoorB;
};

源程序(Line.cpp

#include"Line.h"
#include<iostream>

using namespace std;

Line::Line(int x1, int y1, int x2, int y2)
{
    //从堆中实例化两个坐标对象,并使指针m_pCoorA和m_pCoorB分别指向这两个对象
    m_pCoorA = new Coordinate(x1, y1);
    m_pCoorB = new Coordinate(x2, y2);
    cout <<"Line()"<< endl;
}
Line::~Line()
{
    delete m_pCoorA;
    m_pCoorA = NULL;
    delete m_pCoorB;
    m_pCoorB = NULL;
    cout <<"~Line()"<< endl;
}
voidLine::printInfo()
{
    cout <<"printInfo()"<< endl;
    cout <<"("<< m_pCoorA->getX() <<","<< m_pCoorA->getY() <<")"<< endl;
    cout <<"("<< m_pCoorB->getX() <<","<< m_pCoorB->getY() <<")"<< endl;
}

主调函数(demo.cpp

首先我们只实例化一个线段对象(同时传入四个参数),然后就销毁这个对象,不做其他操作,如下:

#include"Line.h"
#include<iostream>
#include<stdlib.h>

using namespace std;

int main()
{
    //从堆中实例化一个线段对象,并传入四个参数
    Line *p = new Line(1,2, 3, 4);
    delete p;
    p = NULL;


    system("pause");
    return 0;
}

我们来看一下运行结果:

从这个运行结果来看,首先实例化了一个点坐标对象A,然后又实例化了一个点坐标对象B,接着才实例化了一个线段的对象;由于后面调用了delete,A和B就会触发这两个Coordinate对象的析构函数,最后调用Line本身的析构函数。

此外,我们现在在main函数中打印一下信息,通过p来调用printInfo()函数,同时通过sizeof来计算一下其大小,如下代码:

int main()
{
    //从堆中实例化一个线段对象,并传入所个参数
    Line *p = new Line(1,2, 3, 4);
    p->printInfo();

    delete p;
    p = NULL;

    cout <<sizeof(p) << endl;
    cout <<sizeof(Line) << endl;

    system("pause");
    return 0;
}

再来看一下运行结果:

从运行结果看,通过p是可以正常调用信息打印printInfo()函数的(屏幕中间已经打印出信息打印函数名,并且也打印出了A点坐标和B点坐标)。最后,打印出4和8,告诉我们,指针p本身大小为4,而Line对象大小为8(说明Line仅仅包含m_pCoorA和m_pCoorB这两个对象成员指针)。