白话C++系列(13)-- 对象指针、对象成员指针
对象指针
所谓对象指针,顾名思义就是有一个指针,其指向一个对象,下面通过一个例子来说明这样一个问题。
在这个例子中,我们定义了一个坐标的类(Coordinate),其有两个数据成员(一个表示横坐标,一个表示纵坐标)。当我们定义了这个类之后,我们就可以去实例化它了。如果我们想在堆中去实例化这个对象呢,就要如下所示:
通过new运算符实例化一个对象后(这个对象就会执行它的构造函数),而对象指针p就会指向这个对象。我们的重点是要说明p与这个对象在内存中的相关位置以及它们之间的对应关系。
当我们通过这样的方式实例化一个对象后,它的本质就是在内存中分配出一块空间,在这块空间中存储了横坐标(miX)和纵坐标(miY),此时miX的地址与p所保存的地址应该是一致的,也就是说p所指向的就是这个对象的第一个元素(miX)。如果想用p去访问这个元素,很简单,就可以这样来访问(p -> miX或者p -> miY),也可以在p前加上,使这个指针变成一个对象,然后通过点号(.)来访问相关的数据成员(如(p).m_iY)。接下来看一下如下的具体范例。
注意:这里的new运算符可以自动调用对象的构造函数,而C语言中的malloc则只是单纯的分配内存而不会自动调用构造函数。
对象指针代码实践
头文件(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来表示,我们当时用的是坐标类的对象,分别是mcoorA和mcoorB。现在呢,我们要把它们变成指针,如下:
初始化的时候呢,与对象成员初始化的方法可以是一样的,使用初始化列表来初始化,只不过现在是指针了,所以我们赋初值NULL。
除了可以使用初始化列表进行初始化以外,还可以使用普通的初始化,比如说,在构造函数中,写成如下方式:
当然,更多的是下面的情况,因为我们这是两个指针,一定要指向某一个对象,才能够进行操作,才会有意义。而它指向的就应该是两个点的坐标对象:
在这里面,指针mpCoorA指向了一个坐标对象(1,3),mpCoorB指向了另外一个坐标对象(5,6)。那么,这就相当于在构造函数当中,我们从堆中分配了内存。既然在构造函数当中从堆中分配了内存,那么我们就需要在析构函数中去把这个内存释放掉,这样才能够保证内存不被泄漏。
此外呢,作为对象成员和对象成员指针还有另外一个很大的不同。作为对象成员来说,如果我们使用sizeof这个对象的话,它就应该是里面所有对象的体积的总和(如下图所示)
而对象成员指针则不同,我们来看一看刚刚对象成员指针我们定义的时候是如何定义的。我们可以看到,我们定义的时候呢,是写了两个指针作为它的对象成员。而我们知道,一个指针在32位的编译器下面,它只占4个基本内存单元,那么两个指针呢,则占8个基本内存单元,而我们前面所讲到的Coordinate类呢,它有两个数据成员,这两个数据成员都是int型的,所以呢,每一个数据成员都应该占4个基本的内存单元。那么这样算下来呢,我们来想一想,如果我们使用sizeof来判断一个line这样的对象,到底有多大呢?如果在line这个对象中定义的是对象成员(即两个Coordinate),那么这两个Coordinate每一个就应该都占8个基本内存单元,那么两个呢,就应该占16个基本内存单元,打印出来就应该是16,但是现在呢,line对象中是两个对象成员指针,那么每一个对象成员指针应该只占4个基本内存单元,所以sizeof(line)计算出来就应该是8,加起来是这两个指针的大小的总和。
内存中的对象成员指针
当实例化line这个对象的时候,那么两个指针(mpCoorA和mpCoorB)也会被定义出来,由于两个指针都是指针类型,那么都会占4个基本内存单元。如果我们在构造函数当中,通过new这样的运算符从堆中来申请内存,实例化两个Coordinate这样的对象的话呢,这两个Coordinate对象都是在堆中的,而不在line这个对象当中,所以刚才我们使用sizeof的时候呢,也只能得到8,这是因为mpCoorA占4个基本内存单元,mpCoorB占4个基本内存单元,而右边的两个Coordinate对象并不在line这个对象的内存当中。当我们销毁line对象的时候呢,我们也应该先释放掉堆中的内存,然后再释放掉line这个对象。
对象成员指针代码实践
头文件(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仅仅包含mpCoorA和mpCoorB这两个对象成员指针)。
文档信息
版权声明:可自由转载(请注明转载出处)-非商用-非衍生
发表时间:2024年10月18日 10:05