白話C++系列(11)-- 對象數組、對象成員


C++遠征之封裝篇(下)

對象數組

  前面課程我們已經學會了如何實例化一個對象,只有實例化對象后,才能通過這個對象去訪問對象的數據成員和成員函數。但是在很多場合下,一個對象是遠遠不夠用的,往往需要一組對象。比如,我們想表示一個班級的學生,並且假設這個班級有50個學生。果我們還是像以前一樣,簡單的使用對象的實例化的話,就需要定義50個變量來表示這50個學生,顯然這樣做是很麻煩很愚蠢的。這時,我們就需要通過一個數組來表達這一個班的學生。還有,如果我們去定義一個坐標,那么這一個坐標只能代表一個點,但是,如果我們想去定義一個矩形的話,就需要定義4個點,然后這4個點的連線形成一個矩形,那么這4個點也可以定義成一個數組。說到這里,想必大家應該知道,今天的重點就是對象數組。

  接下來我們看下面這個例子。

      

  在這里我們定義了一個坐標類(Coordinate),並且定義了其兩個數據成員(一個表示橫坐標,一個表示縱坐標)。我們在使用的過程中,首先是在棧中實例化了一個對象數組,每個數組元素就是一個坐標的對象,並且均可以訪問對象的數據成員(如上,我們給對象數組的第2個元素的橫坐標賦值為10);其次我們又在堆上實例化了一個對象數組,同樣,每個數組元素均可以訪問對象的數據成員(如上,我們給對象數組的第1個元素的縱坐標賦值為20)。記住,在堆上實例化對象數組后,使用完畢,需要將申請的內存釋放掉(用delete []p),最后還要賦值為空(NULL)。

  接下來看看在內存中是如何存儲的(如下)。

對象數組代碼實踐

題目描述:

定義一個坐標(Coordinate)類,其數據成員包含橫坐標和縱坐標,分別從棧和堆中實例化長度為3的對象數組,給數組中的元素分別賦值,最后遍歷兩個數組。

程序框架如下:

頭文件(Coordinate.h)

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

源程序:

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

#include"Coordinate.h"

using namespace std;

/* 對象數組
/* 要求
      1. 定義Coordiante類
      2. 數據成員:m_iX、m_iY
      3. 分別從棧和堆中實例化長度為3的對象數組
      4. 給數組中的元素分別賦值
      5. 遍歷兩個數組
/* *****************************************/


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

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


int main()
{
    Coordinate coor[3]; //從棧上實例化對象數組
    coor[0].m_iX =3;
    coor[0].m_iY =5;


    Coordinate *p =new Coordinate[3];
    p->m_iX = 7; //直接寫p的話,就說明是第一個元素
    p[0].m_iY =9; //等價於 p->m_iY = 9

    p++; //將指針后移一個位置,指向第2個元素
    p->m_iX = 11;
    p[0].m_iY = 13; //這里p指向的是第二個元素,p[0]就是當前元素,等價於p->m_iY = 13

    p[1].m_iX = 15;//第3個元素的橫坐標
    p++; ////將指針后移一個位置,指向第3個元素
    p[0].m_iY = 17;//這里p指向的是第三個元素,p[0]就是當前元素,等價於p->m_iY = 17


    for(int i = 0; i < 3; i++)
    {
        cout <<"coor_X: "<< coor[i].m_iX <<endl;
        cout <<"coor_Y: "<< coor[i].m_iY <<endl;
    }
    for(int j = 0; j < 3; j++)
    {
        //如果上面p沒有經過++操作,就可以按下面來輪詢
        //cout <<"p_X: " << p[i].m_iX <<endl;
        //cout <<"p_Y: " << p[i].m_iY <<endl;
        //但是,上面我們對p做了兩次++操作,實際p已經指向了第3個元素,應如下操作
        cout <<"p_X: "<< p->m_iX <<endl;
        cout <<"p_Y: "<< p->m_iY <<endl;
        p--;
    }

    //經過了三次循環后,p指向了一個非法內存,不能直接就delete,而應該讓p再指向我們申請的一個元素的,如下
    p++; //這樣p就指向了我們申請的內存
    delete []p;
    p = NULL;


    system("pause");
    return 0;
}

運行結果:

  從運行結果來看,首先看到的是打印出六行“Coordinatre()”,這是因為分別從棧實例化了長度為3的對象數組和從堆實例化了長度為3的對象數組,每實例化一個對象就要調用一次默認構造函數。

     最后只打印出三行“~Coordinate()”,那是不是只是從堆上實例化的對象銷毀時調用了析構函數,而從棧實例化的對象銷毀時,沒有調用析構函數呢?

     非也,從棧實例化的對象在銷毀時,系統自動回收內存,即自動調用析構函數,只是當我們按照提示“請按任意鍵結束”,按下任何鍵后,屏幕會閃一下,就在這閃的過程中,會出現三行“~Coordinate()”的字樣,只是我們不容易看到而已。

對象成員

  前面我們講到的類都是比較簡單的,它們共同的特點是,其數據成員都是基本的數據類型,但是在現實的生活中,問題要遠比這個復雜的多。比如,之前我們編寫過汽車的類,但是當時我們只申明了汽車輪子的個數,如果要解決實際的問題,顯然這是不夠的。起碼輪子本身就是一個對象,汽車上還有沙發座椅,還有發動機等等。再比如,我們如果要定義一個房子的類,房子對象當中,應該有各種各樣的家俱,還有漂亮的燈飾等等,而這些家俱和燈飾其實也是一個個對象。可見,在對象當中包含着其他對象是一種非常常見的現象,接下來我們就學習一下對象成員。

  為了說明對象成員,我們以坐標系中的一段線段為例,以此來說明對象成員的定義和使用方法。

  上面是一個直角坐標系,在這個坐標系中,我們定義了一條線段AB,起點A的坐標為(2, 1),終點B的坐標為(6, 4)。如果我們要定義像這樣的一個線段的類,那么每條線段都有兩個點連接而成,這意味着我們需要定義一個表示點的類,這個類包含橫坐標和縱坐標,而且一個線段中應該包含兩個坐標的對象。可見,要描述這個問題,我們至少要定義兩個類,一個來定義坐標的點,一個來定義坐標系中的線段。

  先來定義坐標點的類,如下:

  在這個類中有兩個數據成員,分別表示點的橫坐標和縱坐標,另外還包含一個它的構造函數。

  接着看一下線段類的定義,如下:

 

在這個線段的類中,有兩個數據成員,這兩個數據成員都是點(一個是起點,一個是終點),而且這兩個點必須是坐標類型的,另外,我們也定義了它的構造函數。

定義完點的類和線段的類后,我們就可以通過實例化來描述一條線段了,如下:

這里大家可能會有這樣一個疑問:在這種對象作為數據成員的情況下,當實例化線段(Line)時,到底是先實例化線段還是先實例化作為對象成員的坐標點的對象呢?而當我們去delete p的時候,也就是說,當線段被銷毀時,是先銷毀點對象還是先銷毀線段對象呢?

結論:

  當我們實例化Line對象時,先實例化點A對象,再實例化點B對象,最后實例化Line這個對象。而銷毀時,則與創建時相反,先銷毀Line這個對象,然后銷毀點B這個對象,最后銷毀點A這個對象。

上面我們講的對象作為數據成員時,構造函數都是沒有參數的。然而作為一條線段,它的兩個點在實例化時,其實是應該可以由調用者來確定的,也就是說,這兩個坐標點在Line這個對象實例化的時候,是能夠通過給它的構造函數傳遞參數,從而可以使這兩點生成在確定的位置上。也就是說,坐標類的構造函數應該有參數,即如下所示:

從而,這就需要線段(Line)的類,它的構造函數也需要有參數,而這些參數未來可以傳值給它的數據成員,即如下所示:

如果我們在實例化線段時,僅僅如下所示肯定會是出錯的。

因此,我們需要將代碼做進一步改進,即配備初始化列表。在初始化列表中,我們要實例化m_coorA和m_coorB,並且將Line所傳入的這四個參數分配到這兩個對象成員中去。

 當做完這些工作之后,我們就可以在主調函數中像之前那樣去實例化新的對象了,並且2和1必然會傳值給第一個坐標點對象,6和4必然會傳值給第二個坐標點對象。

對象成員代碼實踐

題目描述:

/*  對象成員

/*  具體要求:

定義兩個類:

        坐標類:Coordinate

        數據成員:橫坐標m_iX,縱坐標m_iY

        成員函數:構造函數、析構函數,數據成員的封裝函數

        線段類:Line

        數據成員:點A m_coorA,點B  m_coorB

        成員函數:構造函數,析構函數,數據成員的封裝函數,信息打印函數

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

程序框架如下:

頭文件(Coordinate.h)

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
    void setX(int x);
    int getX();
    void setY(int y);
    int getY();
private:
    int m_iX;
    int m_iY;
};

源程序(Coordinate.cpp)

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

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

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

頭文件(Line.h)

#include "Coordinate.h"

class Line
{
public:
    Line();
    ~Line();
    void setA(int x, int y);
    void setB(int x, int y);
    void printInfo();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
};

源程序(Line.cpp)

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

using namespace std;

Line::Line()
{
    cout <<"Line()"<< endl;
}
Line::~Line()
{
    cout <<"~Line()"<< endl;
}
void Line::setA(int x, int y)
{
    m_coorA.setX(x);
    m_coorA.setY(y);    

}
void Line::setB(int x, int y)
{
    m_coorB.setX(x);
    m_coorB.setY(y);

}
void Line::printInfo()
{
    cout << "(" << m_coorA.getX() <<","<< m_coorA.getY()<< ")" <<endl;
    cout << "(" << m_coorB.getX() <<","<< m_coorB.getY()<< ")" <<endl;
}

主調程序(demo.cpp)

//我們首先來實例化一個線段類的對象,如下
#include <iostream>
#include Line.h"

using namespace std;

int main()
{
    Line *p = new Line();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

運行結果如下:

   從運行結果來看,先連續調用了兩次坐標類的構造函數,再調用了一次線段類的構造函數,這就意味着先創建了兩個坐標類的對象,這兩個坐標類的對象就是A點和B點,然后才調用線段這個對象,線段這個對象是在A點和B點初始化完成之后才被創建。而在銷毀時,先調用的是線段類的西溝函數,然后連續調用兩次坐標類的析構函數。可見,對象成員的創建與銷毀的過程正好相反,也驗證了我們之前給出的結論。

       作為一條線段來說,我們非常希望的是,在這條線段創建的時候就已經將線段的起點和終點確定下來。為了達到這個目的,我們往往希望線段這個類的構造函數是帶有參數的,並且這個參數將來能夠傳給這兩個點,所以接下來我們將進一步完善這個程序。

完善頭文件(Coordinate.h)

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

完善源程序(Coordinate.cpp)

#include<iostream>
#include "Coordinate.h"
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;
}
void Coordinate::setX(int x)
{
    m_iX = x;
}
int Coordinate::getX()
{
    return m_iX;
}
void Coordinate::setY(int y)
{
    m_iY = y;
}
int Coordinate::getY()
{
    return m_iY;
}

完善頭文件(Line.h)

#include"Coordinate.h"
class Line
{
public:
    Line(int x1, int y1, int x2, int y2);
    ~Line();
    void setA(int x, int y);
    void setB(int x, int y);
    void printInfo();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
};

完善源程序(Line.cpp)

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

Line::Line(int x1, int y1, int x2, int y2):m_coorA(x1, y1), m_coorB(x2, y2)
{
    cout <<"Line()"<< endl;
}
Line::~Line()
{
    cout <<"~Line()"<< endl;
}
void Line::setA(int x, int y)
{
    m_coorA.setX(x);
    m_coorA.setY(y);    

}
void Line::setB(int x, int y)
{
    m_coorB.setX(x);
    m_coorB.setY(y);

}
void Line::printInfo()
{
    cout <<"("<<m_coorA.getX() <<","<< m_coorA.getY()<<")"<<endl;
    cout <<"("<<m_coorB.getX() <<","<< m_coorB.getY()<<")"<<endl;
}

完善主調程序(demo.cpp)

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

using namespace std;

int main()
{
    Line *p = new Line(1,2,3,4);
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

運行結果:

從這個結果來看,我們更能清晰的看到,在實例化對象時,先實例化點A,再實例化點B,最后實例化線段;在銷毀對象時,先銷毀線段,再銷毀點B,最后銷毀點A。

最后,我們來看一看,通過這樣的值傳遞,能否正確的打印出來,在主調函數中增加一行代碼來調用線段的信息打印函數,如下紅色標記:

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

using namespace std;

int main()
{
    Line *p = new Line(1,2,3,4);
    p->printInfo();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

運行結果:

在此結果中,我們看到了A點坐標和B點坐標,即符合信息打印。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM