對象指針
所謂對象指針,顧名思義就是有一個指針,其指向一個對象,下面通過一個例子來說明這樣一個問題。
在這個例子中,我們定義了一個坐標的類(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這兩個對象成員指針)。