白话C++系列(13)-- 对象指针、对象成员指针
2016-04-28 21:23 Keiven_LY 阅读(4749) 评论(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这两个对象成员指针)。