C++遠征之封裝篇(下)-學習筆記

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

c++封裝概述

下半篇依然圍繞類 & 對象進行展開ios

將本來學過的簡單元素融合成複雜的新知識點。c++

  • 對象 + 數據成員 = 對象成員(對象做爲數據成員)
  • 對象 + 數組 = 對象數組(一個數組中的每一個元素都是對象)
  • 深拷貝 & 淺拷貝 (對象之間彼此賦值,彼此拷貝)
  • 對象指針(操做對象) & 對象指針成員
  • this指針
  • const + 對象 -> 常對象
  • const + 函數 -> 常成員函數
  • const + 對象成員 -> 常對象成員

設計了一個精巧的案例,走迷宮。算法

c++ 對象數組

如何實例化一個對象?實例化對象對於程序是很重要的,只有實例化了對象才能訪問成員函數和數據成員。數組

某些狀況下咱們每每須要實例化一組對象。好比:咱們想實例化出一個50人的班的學生。架構

mark

一個座標只能表明一個點,若是咱們要想定義一個矩形。定義四個點,四個點的連線造成矩形。函數

這四個點能夠定義爲一個數組,每一個點就是一個對象。動畫

對象數組: 座標類this

class Coordinate
{
public:
    int m_iX; // x座標
    int m_iY; // y座標
}

int main()
{
    Coordinate coord[3];// 棧上實例化對象數組
    coord[1].m_iX = 10;

    Coordinate *p = new Coordinate[3];// 堆上實例化對象數組,調用三次構造函數
    p[0].m_iY =20; // p -> m_iY =20;

    delete []p; // 銷燬堆中對象數組,調用三次析構函數
    p =NULL;

    return 0;
}

堆棧

棧區實例化對象數組,會分配相應的內存,系統會自動管理。每個內存中保存着x,y
堆區分配相應內存,p 與 p[0] 是等價的。編碼

C++對象數組實踐代碼

要求

2-2-InstanceArrayspa

Coordinate.h

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
public:
    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;
}

main.cpp

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

int main(void)
{
    Coordinate coor[3];
    coor[0].m_iX = 3;
    coor[0].m_iY = 5;

    Coordinate *p = new Coordinate[3];
    p->m_iX = 7;
    p[0].m_iY = 9;

    p++;
    p->m_iX = 11;
    p[0].m_iY = 13;//此時由於上面p++。p已經指向第二個地址了

    p[1].m_iX = 15;//此時p指向第三個元素

    p++;
    p->m_iY = 17;//此時p指向第三個元素

    for (int i = 0; i < 3; i++)
    {
        cout << coor[i].m_iX << " coor x&y " << coor[i].m_iY << endl;

    }
    for (int j = 0; j < 3; j++)
    {
        //cout << "p_x" << p[j].m_iX << endl;
        //cout << "p_y" << p[j].m_iY << endl;
        cout << "p_x" << p->m_iX << endl;
        cout << "p_y" << p->m_iY << endl;
        p--;
    }

    p++;//由於上面p=-1時才退出了循環。所以釋放時已經不是原來那段內存了。

    delete []p;
    p = NULL;
    system("pause");
    return 0;
}

mark

遍歷就是打印出數組中每一個元素的信息。

delete 銷燬堆中對象數組,顯示了三次的析構函數調用。 系統自動管理的棧中銷燬,在咱們敲回車時能夠看到,也一樣調用了三次。

注意讓p歸位本來位置以後,再執行刪除。

c++數組對象實踐(二)

以前咱們講使用了new,就要配套的使用delete。

若是new的是一個數組,那麼delete時就要加中括號。

但是,爲何數組對應的delete就要加中括號呢?

  • 當實例化一個數組時其實數組中的每個對象都執行了它的構造函數。
  • 在銷燬的時候,咱們也但願他們(每一個對象)都執行本身的析構函數
  • 若是不加中括號,則只銷毀第一個元素。
delete p;//將中括號去掉以後,則只銷毀當前指針指向的內存,只執行一次析構函數

mark

能夠看到只執行了一次析構函數。

對象成員

前面講的都是比較簡單的對象,數據成員都是基本的數據類型。

  • 對象中包含其餘對象(對象成員)

  • 笛卡爾座標系

mark

以座標系中的一個線段爲例,起點A(2,1),終點B(6,4).

定義一個線段類,每一個線段都有一個起點,一個終點。

點座標的定義:

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

線段的定義:

class Line
{
public:
    Line();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
}

實例化描述線段

int main(void)
{
    Line *p = new Line();

    delete p;
    p = NULL;
    return 0;
}

結論:

當咱們實例化一個line對象時,會先實例化a座標點,再實例化b座標點,當這兩個對象實例化完成以後再實例化line對象

當銷燬時正好與建立相反,先銷燬line再銷燬b,最後銷燬a

好比造汽車->零件->圖紙; 拆汽車->圖紙->零件

以上狀況構造函數都沒有參數,座標類的構造函數實際上是須要有參數的。

class Coordinate
{
public:
    Coordinate(int x, int y);
private:
    int m_iX;
    int m_iY;
}

class Line{
public:
    Line(int x1,int y1, int x2,int y2);
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
}

若是直接寫成以下會出問題

int main(void)
{
    Line *p = new Line(2,1,6,4);

    delete p;
    p = NULL;
    return 0;
}

由於傳進去,並無賦值給裏面的對象成員。
要對代碼進一步改造:爲line的構造函數配備初始化列表

Line(int x1,int y1, int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout << "Line" << endl;
}

c++對象成員實踐(一)

要求

2-6-ObjectMember

Coordinate.h :

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
public:
    int getX();
    void setX(int x);

    int getY();
    void setY(int y);
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;
}

int Coordinate::getX() {
    return m_iX;
}
void Coordinate::setX(int x) {
    m_iX = x;
}
int Coordinate::getY(){
    return m_iY;
}
void Coordinate::setY(int y) {
    m_iY = y;
}

Line.h :

#include "Coordinate.h"

class Line {
public:
    Line();
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(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() {
    cout << "Line()" << endl;
}
Line::~Line() {
    cout << "~Line()" << endl;
}
void Line::setCoorA(int x, int y) {
    m_coorA.setX(x);
    m_coorA.setX(y);
}
void Line::setCoorB(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;
}

main.cpp

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

using namespace std;

int main(void)
{
    Line *p = new Line();

    delete p;
    p = NULL;
    system("pause");
    return 0;
}

mark

上圖中咱們能夠看到類在建立和銷燬時對於對象成員的前後處理順序。

C++對象成員實踐(二)[類往對象成員傳參]:

做爲線段這個類,咱們但願在他建立的時候將裏面的兩個點肯定下來。線段類構造函數帶參數,而且傳給點。

Coordinate(int x, int y);

Line::Line(int x1,int y1,int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout << "Line()" << endl;
}

重點在於:

  • Coordinate有兩個參數
  • Line的構造函數使用初始化列表

2-6-2-ObjectMemberParameter

Coordinate.h:

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
public:
    int getX();
    void setX(int x);
    int getY();
    void setY(int y);
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;
}

int Coordinate::getX() {
    return m_iX;
}
void Coordinate::setX(int x) {
    m_iX = x;
}
int Coordinate::getY(){
    return m_iY;
}
void Coordinate::setY(int y) {
    m_iY = y;
}

Line.h:

#include "Coordinate.h"

class Line {
public:
    Line(int x1,int y1,int x2,int y2);
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(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::setCoorA(int x, int y) {
    m_coorA.setX(x);
    m_coorA.setX(y);
}
void Line::setCoorB(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;
}

main.cpp:

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

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

mark

注意:

若是對象A中有對象成員B,對象B沒有默認構造函數,那麼對象A必須在初始化列表中初始化對象B。

若是對象成員B有默認的構造函數,那麼就能夠不用在對象A中使用初始化列表初始化B。

單元鞏固

定義具備2個對象的Coordinate數組,遍歷對象數組,打印對象信息

#include <iostream>
using namespace std;
class Coordinate
{
    
public:
    Coordinate()
    {
    }
    // 打印座標的函數
    void printInfo()  
    {
        cout << "("<<m_iX<<","<<m_iY<<")"<<endl;
    }
public:
    int m_iX;
    int m_iY;
};
int main(void)
{
    //定義對象數組
    Coordinate coorArr[2];
    coorArr[0].m_iX = 1;
    coorArr[0].m_iY = 2;
    coorArr[1].m_iX = 3; 
    coorArr[1].m_iY = 4;

    //遍歷數組,打印對象信息
    for(int i = 0; i < 2; i++)
    {
        coorArr[i].printInfo();
    }   
    return 0;
}

mark

深拷貝和淺拷貝

講解拷貝構造函數的時候只講解了拷貝構造函數的聲明方法以及什麼時候會被自動調用。
但沒有講解如何來實現拷貝構造函數,由於對象間的拷貝不簡單。

有兩種: 深拷貝和淺拷貝

class Array
{
public:
    Array() { m_iCount = 5;}
    Array(const Array& arr)
    { m_iCount = arr.m_iCount;}
private:
    int m_iCount;
};

int main(void)
{
    Array arr1;
    Array arr2 = arr1;
    return 0;
}

實例化arr1時就會調用構造函數Array(), 而後m_iCount = 5。arr2時會調用拷貝構造函數,
Array(const Array& arr) 將arr1做爲參數經過arr傳入。

加強版

class Array
{
public:
    Array(){
        m_iCount = 5;
        m_pArr = new int[m_iCount];
    }
    Array(const Array& arr){
        m_iCount = arr.m_iCount;
        m_pArr = arr.m_pArr;
    }
private:
    int m_iCount;
    int *m_pArr;
}

int main(void)
{
    Array arr1;
    Array arr2 = arr1;
    return 0;
}
  • 共同的特色,普通版和加強版都只是將數據成員的值進行了簡單的拷貝。咱們也把這種拷貝模式稱之爲淺拷貝。對於第一個例子來講使用淺拷貝是沒有問題的。

mark

將arr1中的指針拷貝到arr2中,兩個指針此時就會指向同一個內存地址。

  • 可是加強版會出現問題就是隻簡單地把指針拷貝過去的話(對象arr1和arr2的指針會指向同一個地址),一旦修改拷貝對象的數據,被拷貝對象裏面的數據也會隨之發生改變。(銷燬了arr1,指針不存在了, 銷燬arr2時會出現空指針回收)

所以,咱們想要的是指向兩塊不一樣的內存,而後內存中的值(元素)對應相等

mark

class Array
{
public:
    Array(){
        m_iCount = 5;
        m_pArr = new int[m_iCount];
    }
    Array(const Array& arr){
        m_iCount = arr.m_iCount;

        m_pArr = new int[m_iCount]; // 重點實現: 不是直接賦值,而是開闢了本身的內存地址
        for(int i=0;i<m_iCount;i++){
            // 依次對應賦值,保證值相等。
            m_pArr[i] = arr.m_pArr[i];
        }
    }
private:
    int m_iCount;
    int *m_pArr;
}

int main(void)
{
    Array arr1;
    Array arr2 = arr1;
    return 0;
}

深拷貝:新建一個堆,而後就經過循環的方法把堆中的一個一個的數據拷過去。這樣就能夠避免在修改拷貝對象的數據時,同時改變了被拷貝對象的數據.

淺拷貝代碼實踐

要求:

要求

說明一下某些場合深拷貝的實用性。

3-2-ShallowCopy

淺拷貝代碼:

Array.h

class  Array
{
public:
     Array();
     Array(const Array& arr);
    ~ Array();

    int getCount();
    void setCount(int val);
private:
    int m_iCount;
};

Array.cpp:

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

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

Array::Array(const Array& arr) {
    m_iCount = arr.m_iCount;
    cout << "Array(&)" << endl;
}

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

void Array::setCount(int c) {
    m_iCount = c;
}
int Array::getCount() {
    return m_iCount;
}

main.cpp:

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

int main(void)
{
    Array arr1;
    arr1.setCount(5);

    Array arr2 = arr1;

    cout << "arr2,count:" <<arr2.getCount() << endl;
    system("pause");
    return 0;
}

mark

深拷貝代碼實踐

淺拷貝代碼:

Array::Array(const Array& arr) {
    m_pArr = arr.m_pArr; //使用淺拷貝
    m_iCount = arr.m_iCount;
    cout << "Array(&)" << endl;
}

注意由於這樣使得兩個arr指向同一塊內存。在銷燬時同一塊內存被銷燬兩次,會產生錯誤停止。

3-3-DeepCopy

Array.h:

class  Array
{
public:
     Array(int count);
     Array(const Array& arr);
    ~ Array();

    int getCount();
    void setCount(int val);
    void printAddr();
private:
    int m_iCount;
    int *m_pArr;
};

Array.cpp:

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

Array::Array(int count )
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    cout << "Array()" << endl;
}

Array::Array(const Array& arr) {
    m_pArr = arr.m_pArr;//使用淺拷貝
    m_iCount = arr.m_iCount;
    cout << "Array(&)" << endl;
}

Array::~Array() {
    delete[]m_pArr;
    m_pArr = NULL;
    cout << "~Array()" << endl;
}

void Array::setCount(int c) {
    m_iCount = c;
}
int Array::getCount() {
    return m_iCount;
}
void Array::printAddr() {
    cout << "m_pArr:" << m_pArr << endl;
};

main.cpp

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

int main(void)
{
    Array arr1(5);

    Array arr2(arr1);

    arr1.printAddr();
    arr2.printAddr();
    system("pause");
    return 0;
}

mark

能夠看到淺拷貝中,兩個指向同一塊內存。釋放內存時會出現問題。

mark

改造爲深拷貝

修改Array.cpp的拷貝函數

Array::Array(const Array &arr) {
    m_iCount = arr.m_iCount;
    m_pArr = new int[m_iCount]; //使用深拷貝
    for (int i =0;i<m_iCount;i++)
    {
        m_pArr[i] = arr.m_pArr[i];
    }
    cout << "Array(&)" << endl;
}

深拷貝:1.申請一段內存。再將源對象的內存中數值拷貝到對應位置一份。

爲了看起來更加清晰,在構造函數爲arr1的每個元素賦值

Array::Array(int count)
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    for (int i=0;i<m_iCount;i++)
    {
        m_pArr[i] = i;
    }
    cout << "Array()" << endl;
}

此時再次運行。不會報錯並且兩個已經指向不一樣內存地址。

mark

Array.h中添加void printArr();

在Array.cpp增長打印數組函數:

void Array::printArr() {
    for (int i=0;i<m_iCount;i++)
    {
        cout << m_pArr[i] << endl;
    }
}

此時main.cpp添加:

arr1.printArr();
arr2.printArr();

完整深拷貝代碼:

3-3-DeepCopy

Array.h:

class  Array
{
public:
    Array(int count);
    Array(const Array& arr);
    ~Array();

    int getCount();
    void setCount(int val);
    void printAddr();
    void printArr();
private:
    int m_iCount;
    int *m_pArr;
};

Array.cpp:

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

Array::Array(int count)
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    for (int i = 0; i < m_iCount; i++)
    {
        m_pArr[i] = i;
    }
    cout << "Array()" << endl;
}

Array::Array(const Array& arr) {
    m_iCount = arr.m_iCount;
    m_pArr = new int[m_iCount]; //使用深拷貝
    for (int i = 0; i < m_iCount; i++)
    {
        m_pArr[i] = arr.m_pArr[i];
    }

    cout << "Array(&)" << endl;
}

Array::~Array() {
    delete[]m_pArr;
    m_pArr = NULL;
    cout << "~Array()" << endl;
}

void Array::setCount(int c) {
    m_iCount = c;
}
int Array::getCount() {
    return m_iCount;
}
void Array::printAddr() {
    cout << "m_pArr:" << m_pArr << endl;
};
void Array::printArr() {
    for (int i = 0; i < m_iCount; i++)
    {
        cout << m_pArr[i] << endl;
    }
}

main.cpp:

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

int main(void)
{
    Array arr1(5);

    Array arr2(arr1);

    arr1.printAddr();
    arr2.printAddr();
    arr1.printArr();
    arr2.printArr();
    system("pause");
    return 0;
}

mark

能夠看到深拷貝時指向兩個不一樣的地址,其中內容保持一致。

C++對象指針

有一個指針用來指向一個對象。

demo:

class Coordinate{
public:
    int m_iX;
    int m_iY;
}

將咱們的座標類,在堆中實例化:

Coordinate *p = new Coordinate; //執行構造函數

堆中

p指向m_iX,訪問方式爲p->m_iX;
*p變成一個對象,採用.訪問其中元素。

具體示例代碼:

int main(void)
{
    Coordinate *p = new Coordinate;
    p -> m_iX = 10;  //(*p).m_iX =10;
    p -> m_iY = 20;  //(*p).m_iY =20;
    delete p;
    p = NULL;
    return 0;
}
  • new 會自動調用對象的構造函數;
  • C語言中的 malloc 則不會調用相關對象的構造函數,只是分配內存。

C++對象指針實踐:

要求

4-2-ObjectPointer

Coordinate.h

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
    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;
}

main.cpp:

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

using namespace std;

int main()
{
    // 使用對象指針指向內存,兩種方法

    // 堆中實例化
    Coordinate *p1 = NULL;
    p1 = new Coordinate; //由於有默認構造函數,括號可寫可不寫
    Coordinate *p2 = new Coordinate(); //方法2

    p1->m_iX = 10;
    p1->m_iY = 20; // 指針方式
    (*p2).m_iX = 30; //*p2使p2變成了一個對象
    (*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;
}

mark

VS中 先ctrl+kctrl+c能夠註釋一段代碼

//棧中實例化
    Coordinate p1;
    Coordinate *p2 = &p1;//讓p2指向p1的地址,p2可操做p1

    p2->m_iX = 10; // (*p2).m_ix =10;
    p2->m_iY = 20;

    cout << p1.m_iX << "," << (*p2).m_iY << endl;

mark

能夠看到p2指向p1的地址,修改p2,p1也會修改。

編碼練習

定義一個座標類,在堆上實例化座標對象,並給出座標(3,5),而後打印座標信息,銷燬座標對象。

#include <iostream>
using namespace std;
class Coordinate
{
    
public:
    Coordinate(int x, int y)
    {
        // 設置X,Y的座標
        m_iX = x;
        m_iY = y;
    }
public:
    int m_iX;
    int m_iY;
};

int main(void)
{
    // 在堆上建立對象指針
    Coordinate *p = new Coordinate(3,5);
    // 打印座標
    cout <<"("<<(*p).m_iX<<","<<(*p).m_iY<<")"<< endl;
    // 銷燬對象指針
    delete p;
    p = NULL;
    return 0;
}

mark

C++對象成員指針

  • 對象成員: 一個對象成爲另一個類的數據成員
  • 對象成員指針: 對象的指針成爲另一個類的數據成員

座標類:

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

線段類:

class Line {
public:
    Line();
    ~Line();
private:
    Coordinate m_coorA; //起點
    Coordinate m_coorB; //終點
};

將線段類代碼中的對象成員變成對象成員指針:

class Line {
public:
    Line();
    ~Line();
private:
    Coordinate *m_pCoorA;
    Coordinate *m_pCoorB;
};

初始化時仍可使用初始化列表進行初始化:

Line::line():m_pCoorA(NULL),m_pCoorB(NULL){
}

也能夠在構造函數中使用普通初始化:

Line::line()
{
    m_pCoorB = NULL;
    m_pCoorA = NULL;
}

通常的普通初始化狀況:

Line::line()
{
    m_pCoorB = new Coordinate(1,3);
    m_pCoorA = new Coordinate(5,6);
}
Line::~line()
{
    delete m_pCoorA;
    delete m_pCoorB;
}
int main(void)
{
    Line line();
    cout << sizeof(line) <<endl; //8
    return 0;
}

對象成員與對象成員指針的不一樣:

對於對象成員來講,sizeof是裏面全部對象的總和。

指針在32位編譯器下佔4個基本內存單元,兩個指針佔8個內存單元。

  • 對象成員指針的定義: 類名 *指針名;
  • 若存在對象成員指針1,2。sizeof(對象),只計算各指針所佔內存的總和。

對象成員指針sizeof

建立時line對象中只有兩個佔4字節的指針。
而實例化出的兩個對象在堆中;銷燬時,先銷燬堆中的,再釋放line對象。

對象成員指針實踐

要求

4-5-ObjectMemberPointer

Coordinate.h

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
public:
    int getX();
    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;
}

int Coordinate::getX() {
    return m_iX;
}
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 setCoorA(int x, int y);
    void setCoorB(int x, int y);
    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 = 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;
}
void Line::printInfo() {
    cout << "("<<(*m_pCoorA).getX()<<","<< (*m_pCoorA).getY()<< ")" << endl;
    cout << "(" << m_pCoorB->getX() << "," << m_pCoorB->getY() << ")" << endl;
}

main.cpp:

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

int main(void)
{
    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;
}

運行結果:

mark

  • 能夠看出在類包含指針對象時,先實例化指針對象a,b。再去調用父對象的構造函數。
  • 在銷燬時也是先按照順序銷燬指針對象a,b;最後銷燬父對象。
  • 指針p佔用四個內存空間,對象Line中包含兩個對象成員指針,也就是包含兩個指針,大小爲8.

這裏咱們使用的是32位編譯,狀況是如上圖所示。

mark

能夠看到,在64位編譯狀況下,一個指針佔8個字節。

這種狀況下對象的銷燬與建立順序一致。

這裏咱們須要注意對象成員與對象成員指針在建立與銷燬時的不一樣

this指針

  • 對象指針
  • 對象成員指針
  • this指針

例子:

class Array
{
    public:
        Array(int _len){len = _len;}
        int getLen(){return len;}
        void setLen(int _len){len = _len;}
    private:
        int len;
}

參數與數據成員,並不一樣名。
數據成員與參數在表達同一個意思的時候取類似的名字。

問題: 當參數和數據成員同名會怎麼樣呢?

class Array
{
    public:
        Array(int len){len = len;} //錯
        int getLen(){return len;}
        void setLen(int len){len = len;}//錯
    private:
        int len;
}

計算機和人類都沒法判斷是把參數賦值給成員了,仍是成員賦值給參數。沒法分辨兩個len了。

this指針: 指向對象自身數據的指針

Array arr1;  //this <-> &arr1
Array arr2;  //this <-> &arr2

this

this表達什麼地址,取決於當前所在做用域。這樣就能夠標記出自身的成員,與參數能夠分清哪一個是哪一個了。

class Array
{
    public:
        Array(int len){this->len = len;}//對
        int getLen(){return len;}
        void setLen(int len){this->len = len;}//對
    private:
        int len;
}

對象結構:

對象結構

  • 存在多個對象,成員函數只有代碼區內的一份。

又沒有傳遞參數,成員函數如何肯定該調用哪一個對象的數據成員?

  • 成員函數如何訪問到對應對象的數據成員?
class Array
{
    public:
        Array(T *this,int len){this->len = len;}//對
        int getLen(T *this){return this->len;}
        void setLen(T *this,int len){this->len = len;}//對
    private:
        int len;
}

this指針

  • 經過this來實現分辨不一樣的arr。
  • 編譯器自動的爲每個成員函數的參數列表都自動加上了this指針。

this指針在參數列表中的位置(代碼實踐)

要求:

this代碼實踐

正常版本1:

4-7-ThisPointerPosition

Array.h

class  Array
{
public:
    Array(int len);
    ~ Array();

    void printAddr();
    void printArr();
    int getLen();
    void setLen(int val);
    void printInfo();
private:
    int m_iLen;
};

Array.cpp:

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

Array::Array(int len)
{
    m_iLen = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

void Array::setLen(int len) {
    m_iLen = len;
}
int Array::getLen() {
    return m_iLen;
}

void Array::printInfo() {

}

main.cpp:

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

int main(void)
{
    Array arr1(10);

    system("pause");
    return 0;
}

mark

改動: 將全部m_iLen都改成與參數名相同的len,此時已經人眼分辨不出哪一個是數據成員,哪一個是參數

爲了程序更好看(能夠分辨出哪一個是哪一個),咱們引入this。

改動Array.h中數據成員:

private:
    int len;

改動過的Array.cpp:

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

Array::Array(int len)
{
    this->len = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

void Array::setLen(int len) {
    this->len = len;
}
int Array::getLen() {
    return len;
}

void Array::printInfo() {

}

main.cpp添加調用arr1的打印函數。

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

int main(void)
{
    Array arr1(10);
    cout << arr1.getLen() << endl;
    system("pause");
    return 0;
}

mark

this指針實踐(二)

4-8-ThisPointerPositionLinkCall

將Array.h中的printInfo()改成:

Array printInfo();

Array.cpp中的printInfo()改成:

Array Array::printInfo() {
    cout << "len:" << len << endl;
    return *this; //this自己是一個指針,而加上*之後變成一個對象。
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    arr1.printInfo();
    system("pause");
    return 0;
}

mark

這裏的printInfo被正常的調用了,可是沒有體現出返回this指針的價值。

由於printInfo()是有返回值this指針的,能夠進行鏈式的調用。

arr1.printInfo().setLen(5);
cout << "len_after_set:" << arr1.getLen() << endl;

mark

打印出來的結果能夠看出,咱們並無成功的改變掉arr1的值。

由於咱們返回的 *this 出來以後又變成了另一個臨時的對象。這是一個臨時的對象,並非arr1。

若是想讓它就是arr1,那麼引用就能夠實現。

Array.h

Array& printInfo();
Array& Array::printInfo() {
    cout << "len:" << len << endl;
    return *this; //this自己是一個指針,而加上*之後變成一個對象。
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    arr1.printInfo().setLen(5);
    cout << "len_after_set:" << arr1.getLen() << endl;
    system("pause");
    return 0;
}

mark

能夠看到由於是引用,arr1都沒有被銷燬,而是被賦上新值。

咱們若是想讓鏈式調用變得更長,只須要給setLen方法,讓其也返回this指針的引用。

4-8-ThisPointerPositionLinkCallTwo

Array.h

class  Array
{
public:
     Array(int len);
    ~ Array();

    void printAddr();
    void printArr();
    int getLen();
    Array& setLen(int val);
    Array& printInfo();
private:
    int len;
};

Array.cpp:

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

Array::Array(int len )
{
    this->len = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

Array& Array::setLen(int len) {
    this->len = len;
    return *this;
}
int Array::getLen() {
    return len;
}

//添加引用以後纔是arr1
Array& Array::printInfo() {
    cout << "len:" << len << endl;
    return *this;//this自己是一個指針,而加上*之後變成一個對象。
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    //由於此時this返回了當前對象。因此可使用"."
    arr1.printInfo().setLen(5).printInfo();
    system("pause");
    return 0;
}

運行結果:

mark

指針實現版本:

若是咱們不是返回一個引用,而是返回一個指針,那又是什麼狀況?

  • 將返回類型Array &改成 Array *
  • return *this改成return this
  • 將對象的使用符.改成指針操做符->

完整代碼以下:
4-8-ThisPointerPositionLinkCallTwoByPointer

Array.h:

class  Array
{
public:
     Array(int len);
    ~ Array();
    void printAddr();
    void printArr();
    int getLen();
    Array* setLen(int val);
    Array* printInfo();
private:
    int len;
};

Array.cpp:

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

Array::Array(int len )
{
    this->len = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

Array* Array::setLen(int len) {
    this->len = len;
    return this;
}
int Array::getLen() {
    return len;
}

Array* Array::printInfo() {
    cout << "len:" << len << endl;
    return this;//this自己是一個指針.
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    //由於此時this返回了arr1的指針,因此要使用"->"
    arr1.printInfo()->setLen(5)->printInfo();
    system("pause");
    return 0;
}

mark

最終結果與剛纔一致。說明不論是引用仍是指針均可以實現改變實際的值。

this指針的本質: 至關於所在對象的地址

在printInfo中打印出this指針的值

Array.cpp:

Array* Array::printInfo() {
    cout << this << endl;
    return this;//this自己是一個指針,而加上*之後變成一個對象。
}

main.cpp:

int main(void)
{
    Array arr1(10);
    
    arr1.printInfo();
    cout << &arr1 << endl;
    system("pause");
    return 0;
}

運行結果:

mark

this指針無需用戶定義,是編譯器自動產生的。

常對象成員和常成員函數

const重出江湖

例子:

class Coordinate
{
public:
    Coordinate(int x,int y);
private:
    const int m_iX;
    const int m_iY;
}

//錯誤作法: 
Coordinate::Coordinate(int x,int y)
{
    m_iY =y;
    m_iX =x;
}

//正確作法:使用初始化列表

Coordinate::Coordinate(int x,int y):m_iX(x),m_iY(y)
{
}

做爲一個類的數據成員是能夠用const來修飾的,只不過之前講的都是一些基本數據類型的數據成員。

常對象成員: 對象成員經過const修飾

線段: 一旦起點和終點被肯定就不容許修改了。

class Line
{
public:
    Line(int x1,int y1,int x2 ,int y2)
private:
    const Coordinate m_coorA;
    const Coordinate m_coorB;
}

//初始化兩個對象使用初始化列表

Line::Line(int x1,int y1,int x2 ,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout<< "Line" << endl;
}

//調用:

int main(void)
{
    Line *p = new Line(2,1,6,4);

    delete p;
    p = NULL;
    return 0;
}

const修飾成員函數(常成員函數)

例子:

class Coordinate
{
public:
    Coordinate(int x,int y);
    void changeX() const; // 常成員函數
    void changeX();
private:
    int m_iX;
    int m_iY;
}

//定義常成員函數
void Coordinate::changeX() const
{
    m_iX = 10;//錯誤
};

//普通函數
void Coordinate::changeX() 
{
    m_iX = 20;
};

思考: 爲何常成員函數中不能改變數據成員的值?

隱藏的參數this指針

實際成員函數有一個隱藏的參數,this指針。

const修飾的指針變成常指針

this指針變成了一個常指針,經過常指針改變數據,顯然是不被容許的。

互爲重載:

void changeX() const;
    void changeX(); //互爲重載

Q:當直接調用:coordinate.changeX()到底調用的是哪個呢?

A:調用的是普通的不帶const的。

Q: 那麼想調用那個帶const的如何寫?

A:代碼以下

int main(void)
{
    // 實例化對象時用const修飾這個對象
    const Coordinate coordinate(3,5);//常對象
    coordinate.changeX(); // 調用的是常成員函數
    return 0;
}

常對象調用的是常成員函數。
普通對象調用的是普通成員函數。

常對象成員與常成員函數代碼實踐

要求

5-2-ConstantMemberFunction

Coordinate.h :

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
public:
    int getX() const;//此處聲明該成員函數爲常成員函數
    void setX(int x); // 至關於setX(Coordinate *this, int x)
    int getY() const;//同上
    void setY(int y);
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;
}

int Coordinate::getX() const{
    return m_iX;
}
void Coordinate::setX(int x) { 
    m_iX = x;
}
int Coordinate::getY() const{
    return m_iY;
}
void Coordinate::setY(int y) {
    m_iY = y;
}

Line.h:

#include "Coordinate.h"

class Line {
public:
    Line(int x1, int y1, int x2, int y2);
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(int x, int y);
    void printInfo();
    void printInfo() const;//互爲重載

private:
    const Coordinate m_coorA; // Coordinate const m_coorA;
    Coordinate m_coorB;
};

Line.cpp:

#include "Line.h"
#include <iostream>
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::setCoorA(int x, int y) {
    //m_coorA.setX(x); // 出現問題: 此時至關於在setX中傳遞了一個this指針
    //m_coorA.setX(y);
}
void Line::setCoorB(int x, int y) {
    m_coorB.setX(x);
    m_coorB.setY(y);
}
void Line::printInfo() {
    cout << "printInfo()" << endl;
    cout << "(" << m_coorA.getX() << "," << m_coorA.getY() << ")" << endl;
    cout << "(" << m_coorB.getX() << "," << m_coorB.getY() << ")" << endl;

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

main.cpp

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

int main(void)
{
    Line line(1, 2, 3, 4);
    line.printInfo();//調用的是普通的

    const Line line2(1, 2, 3, 4);
    line2.printInfo();//調用的是常成員函數
    
    system("pause");
    return 0;
}

mark

void setX(int x); // 至關於setX(Coordinate *this, int x)

咱們在定義的時候,Coordinate *this要求的是一個既有寫權限又有讀權限的指針。

而咱們在調用的時候,咱們傳入的m_coorA是一個只有讀權限的對象。

若是咱們想讓函數能用,加const就能夠了。setX是不能加的,否則就不能修改x,y的值了
可是getX 和 getY是能夠加的。

int getX() const;//此處聲明該成員函數爲常成員函數
    int getY() const;// const應寫在函數聲明的後面

而後將const也要同步到定義上去。此處略去自行完成。將setX和setY註釋掉。

int getX(const Coordinate *this) const;

這就要求咱們傳入的是常對象。

  • 常對象只能調用常成員函數。
  • 普通對象能夠調用所有成員函數(很是成員函數)。

思考:爲何須要const成員函數?

咱們定義的類的成員函數中,經常有一些成員函數不改變類的數據成員,也就是說,這些函數是"只讀"函數,而有一些函數要修改類數據成員的值。若是把不改變數據成員的函數都加上const關鍵字進行標識,顯然,可提升程序的可讀性。其實,它還能提升程序的可靠性,已定義成const的成員函數,一旦企圖修改數據成員的值,則編譯器按錯誤處理。

常指針與常引用

對象指針與對象引用

class Coordinate
{
public:
    Coordinate(int x, int y);
public:
    int getX();
    int getY();
    void printInfo() const; // 常成員函數
private:
    int m_iX;
    int m_iY;
};

定義:

int Coordinate::getX(){
    return m_iX;
}

int Coordinate::getY() const{
    return m_iY;
}

void Coordinate::printInfo() const
{
    cout << "(" <<m_iX <<","<<m_iY <<")"<<endl;
}

對象的引用 & 對象的指針

int main(void)
{
    Coordinate coor1(3,5);
    Coordinate &coor2 = coor1; //對象的引用,起別名
    Coordinate *pCoor = &coor1;//對象的指針,注意取地址符。
    coor1.printInfo();
    coor2.printInfo(); //也將打印出coor1的座標(3,5)
    pCoor -> printInfo();
    return 0;
}

對象的常指針與對象的常引用

int main(void)
{
    Coordinate coor1(3,5);
    const Coordinate &coor2 = coor1; //常:對象的引用
    const Coordinate *pCoor = &coor1; //常:對象的指針,注意取地址符。
    coor1.printInfo(); //普通對象正常使用

    coor2.getX(); //錯誤,常引用(只具備讀權限的this指針)
    // 定義getX的時候沒有加const,所以它內部隱藏傳入的this是要求一個讀寫權限都有的。
    
    // 只能調用常成員函數
    coor2.printInfo();

    pCoor -> getY(); //錯誤。常指針(只有只讀權限)。
    //getY的隱藏參數要求傳入的是讀寫權限的this
    
    // 只能調用常成員函數
    pCoor -> printInfo();
    
    return 0;
}

更復雜的例子:

int main()
{
    Coordinate coor1(3,5);
    Coordinate coor2(7,9);

    //定義一個對象指針
    Coordinate *const pCoor = &coor1; 
    // 注意,const在*的後面。
    // pCoor一旦指向一個對象就不能指向其餘的對象

    //指針不能夠指向其餘對象,可是指針自己指向的對象內容可變。
    //這是一個具備讀寫權限的指針。只限於當前指向的對象。
    pCoor ->getY(); //正確

    //pCoor已經被const修飾了,不容許修改
    pCoor = &coor2;

    //printInfo是一個常成員函數(要求讀權限的指針),而pCoor具備讀寫權限,大權限能夠調用小權限的。
    pCoor -> printInfo();

    return 0;
}

常指針容易混淆的:

  • const *p -> *p不能夠再賦值
  • *const p -> p不能夠再賦值
  • const * const p -> *pp都不能夠再賦值

不能把小權限的指向大權限的,可是能夠把大權限的指向小權限的。

  • 常對象只能調用常成員函數,不能調用普通成員函數
  • 普通對象可以調用常成員函數,也可以調用普通成員函數(權限大調用權限小)
  • 常指針和常引用都只能調用對象的常成員函數。(小權限調小權限)
  • 一個對象能夠有多個對象常引用 (小名自身不能夠改,可是能夠有多個別名)

單元鞏固

定義一個座標類,在棧上實例化座標類常對象,並給出座標(3,5),而後定義常引用、常指針,最後使用對象、引用、指針分別經過調用信息打印函數打印座標信息。

#include <iostream>
using namespace std;
class Coordinate
{
    
public:
    Coordinate(int x, int y)
    {
        // 設置X,Y的座標
        m_iX = x;
        m_iY = y;
    }
    // 實現常成員函數
    void printInfo() const
    {
        cout << "("<<m_iX<<","<<m_iY<<")"<<endl;
    }
public:
    int m_iX;
    int m_iY;
};


int main(void)
{
    const Coordinate coor(3, 5);

    // 建立常指針p
    const Coordinate *p = &coor;
    // 建立常引用c
    const Coordinate &c = coor;
    
    coor.printInfo();
    p->printInfo();
    c.printInfo();  
    
    return 0;
}

mark

迷宮程序(走出迷宮)

走出規則(算法):

  • 左手規則 & 右手規則
  • 原則:保證手始終觸牆(黑暗中在家扶牆走)
  • 結果:走出迷宮

狀況1(建議設計成這樣):有入有出。

mark

狀況2:出入口是一個)

出入爲一個

架構描述

涉及兩個類: 迷宮類(MazeMap)& 走迷宮的人(Person)

二維數組:

1表明牆,0表明路,本身決定。

1牆0路

迷宮類(MazeMap)

數據成員:

- 牆壁字符
- 通路字符
- 迷宮數組

成員函數:

- 構造函數
- 數據封裝函數
- 迷宮回執函數
- 迷宮邊界檢查函數

人類(MazePerson)

數據成員:

- 人的字符
- 人的朝向
- 人當前位置(設置在入口)
- 人前一個位置 (人走動,前位置抹掉,後一個位置繪製)
- 人的速度

成員函數:

- 構造函數
- 數據封裝函數
- 向不一樣方向前進的函數(上下左右)
- 轉彎函數
- 開始函數

控制檯動畫控制:

/*
 * 函數名稱:gotoxy
 * 函數功能:肯定控制檯中字符的輸出位置
 * 函數列表:
 *      x:橫座標
 *      y:縱座標
 */

void MazePerson::gotoxy(int x, int y)   
{   
    COORD cd;    
    cd.X   =   x; 
    cd.Y   =   y;
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);  
    SetConsoleCursorPosition(handle,cd); // 把光標定位到相應位置上    
};

首先須要定義一個迷宮,迷宮是一個二維數組,WaLL是牆,Road是路。

繪製迷宮cout就能夠了。有了迷宮的二維數組,先實例化一個迷宮對象,經過setMazeMap函數將
二維數組設置進去,SetMazeWall告訴計算機牆用什麼表示。設置好以後繪製迷宮。

走迷宮的人,設置人的位置位於入口。設置人的速度,設置人的字符形狀。

人開始運動。

注意事項:

  • 枚舉類型:方向(上下左右)
  • 常量定義:宏定義 & const

成就感源於克服困難

迷宮代碼實現

未完待續, 以後補充。

相關文章
相關標籤/搜索