【C++】面向?qū)ο笾惡蛯?duì)象(上篇)-003

第四章 類和對(duì)象


4.1 類和對(duì)象的基本概念


4.1.1 C和C++中struct區(qū)別

  • c語(yǔ)言struct只有變量
  • c++語(yǔ)言struct 既有變量,也有函數(shù)

4.1.2 類的封裝

我們編寫程序的目的是為了解決現(xiàn)實(shí)中的問(wèn)題,而這些問(wèn)題的構(gòu)成都是由各種事物組成,我們?cè)谟?jì)算機(jī)中要解決這種問(wèn)題,首先要做就是要將這個(gè)問(wèn)題的參與者:事和物抽象到計(jì)算機(jī)程序中,也就是用程序語(yǔ)言表示現(xiàn)實(shí)的事物。

那么現(xiàn)在問(wèn)題是如何用程序語(yǔ)言來(lái)表示現(xiàn)實(shí)事物?現(xiàn)實(shí)世界的事物所具有的共性就是每個(gè)事物都具有自身的屬性,一些自身具有的行為,所以如果我們能把事物的屬性和行為表示出來(lái),那么就可以抽象出來(lái)這個(gè)事物。

比如我們要表示人這個(gè)對(duì)象,在c語(yǔ)言中,我們可以這么表示:

typedef struct _Person{
    char name[64];
    int age;
}Person;
typedef struct _Aninal{
    char name[64];
    int age;
    int type; //動(dòng)物種類
}Ainmal;

void PersonEat(Person* person){
    printf("%s在吃人吃的飯!\n",person->name);
}
void AnimalEat(Ainmal* animal){
    printf("%s在吃動(dòng)物吃的飯!\n", animal->name);
}

int main(){

    Person person;
    strcpy(person.name, "小明");
    person.age = 30;
    AnimalEat(&person);

    return EXIT_SUCCESS;
}

定義一個(gè)結(jié)構(gòu)體用來(lái)表示一個(gè)對(duì)象所包含的屬性,函數(shù)用來(lái)表示一個(gè)對(duì)象所具有的行為,這樣我們就表示出來(lái)一個(gè)事物,在c語(yǔ)言中,行為和屬性是分開(kāi)的,也就是說(shuō)吃飯這個(gè)屬性不屬于某類對(duì)象,而屬于所有的共同的數(shù)據(jù),所以不單單是PeopleEat可以調(diào)用Person數(shù)據(jù),AnimalEat也可以調(diào)用Person數(shù)據(jù),那么萬(wàn)一調(diào)用錯(cuò)誤,將會(huì)導(dǎo)致問(wèn)題發(fā)生。

從這個(gè)案例我們應(yīng)該可以體會(huì)到,屬性和行為應(yīng)該放在一起,一起表示一個(gè)具有屬性和行為的對(duì)象。

假如某對(duì)象的某項(xiàng)屬性不想被外界獲知,比如說(shuō)漂亮女孩的年齡不想被其他人知道,那么年齡這條屬性應(yīng)該作為女孩自己知道的屬性;或者女孩的某些行為不想讓外界知道,只需要自己知道就可以。那么這種情況下,封裝應(yīng)該再提供一種機(jī)制能夠給屬性和行為的訪問(wèn)權(quán)限控制住。

所以說(shuō)封裝特性包含兩個(gè)方面,一個(gè)是屬性和變量合成一個(gè)整體,一個(gè)是給屬性和函數(shù)增加訪問(wèn)權(quán)限。

  • 封裝
  1. 把變量(屬性)和函數(shù)(操作)合成一個(gè)整體,封裝在一個(gè)類中
  2. 對(duì)變量和函數(shù)進(jìn)行訪問(wèn)控制
  • 訪問(wèn)權(quán)限
  1. 在類的內(nèi)部(作用域范圍內(nèi)),沒(méi)有訪問(wèn)權(quán)限之分,所有成員可以相互訪問(wèn)
  2. 在類的外部(作用域范圍外),訪問(wèn)權(quán)限才有意義:publicprivate,protected
  3. 在類的外部,只有public修飾的成員才能被訪問(wèn),在沒(méi)有涉及繼承與派生時(shí),privateprotected是同等級(jí)的,外部不允許訪問(wèn)
訪問(wèn)屬性 屬性 對(duì)象內(nèi)部 對(duì)象外部
public 公有 可訪問(wèn) 可訪問(wèn)
private 私有 可訪問(wèn) 不可訪問(wèn)
protected 保護(hù) 可訪問(wèn) 不可訪問(wèn)
//封裝兩層含義
//1. 屬性和行為合成一個(gè)整體
//2. 訪問(wèn)控制,現(xiàn)實(shí)事物本身有些屬性和行為是不對(duì)外開(kāi)放
class Person{
//人具有的行為(函數(shù))
public:
    void Dese(){ cout << "我有錢,年輕,個(gè)子又高,就愛(ài)嘚瑟!" << endl;}
//人的屬性(變量)
public:
    int mTall; //多高,可以讓外人知道
protected:
    int mMoney; // 有多少錢,只能兒子孫子知道
private:
    int mAge; //年齡,不想讓外人知道
};

int main(){

    Person p;
    p.mTall = 220;
    //p.mMoney 保護(hù)成員外部無(wú)法訪問(wèn)
    //p.mAge 私有成員外部無(wú)法訪問(wèn)
    p.Dese();

    return EXIT_SUCCESS;
}

[struct和class的區(qū)別?]
.
class默認(rèn)訪問(wèn)權(quán)限為private,struct默認(rèn)訪問(wèn)權(quán)限為public.

class A{
    int mAge;
};
struct B{
    int mAge;
};

void test(){
    A a;
    B b;
    //a.mAge; //無(wú)法訪問(wèn)私有成員
    b.mAge; //可正常外部訪問(wèn)
}

4.1.3 將成員變量設(shè)置為private

1. 可賦予客戶端訪問(wèn)數(shù)據(jù)的一致性。

如果成員變量不是public,客戶端唯一能夠訪問(wèn)對(duì)象的方法就是通過(guò)成員函數(shù)。如果類中所有public權(quán)限的成員都是函數(shù),客戶在訪問(wèn)類成員時(shí)只會(huì)默認(rèn)訪問(wèn)函數(shù),不需要考慮訪問(wèn)的成員需不需要添加(),這就省下了許多搔首弄耳的時(shí)間。

2. 可細(xì)微劃分訪問(wèn)控制。

使用成員函數(shù)可使得我們對(duì)變量的控制處理更加精細(xì)。如果我們讓所有的成員變量為public,每個(gè)人都可以讀寫它。如果我們?cè)O(shè)置為private,我們可以實(shí)現(xiàn)“不準(zhǔn)訪問(wèn)”、“只讀訪問(wèn)”、“讀寫訪問(wèn)”,甚至你可以寫出“只寫訪問(wèn)”。

class AccessLevels{
public:
    //對(duì)只讀屬性進(jìn)行只讀訪問(wèn)
    int getReadOnly(){ return readOnly; }
    //對(duì)讀寫屬性進(jìn)行讀寫訪問(wèn)
    void setReadWrite(int val){ readWrite = val; }
    int getReadWrite(){ return readWrite; }
    //對(duì)只寫屬性進(jìn)行只寫訪問(wèn)
    void setWriteOnly(int val){ writeOnly = val; }
private:
    int readOnly; //對(duì)外只讀訪問(wèn)
    int noAccess; //外部不可訪問(wèn)
    int readWrite; //讀寫訪問(wèn)
    int writeOnly; //只寫訪問(wèn)
};

4.1.3課堂練習(xí)

請(qǐng)?jiān)O(shè)計(jì)一個(gè)Person類,Person類具有nameage屬性,提供初始化函數(shù)(Init),并提供對(duì)nameage的讀寫函數(shù)(set,get),但必須確保age的賦值在有效范圍內(nèi)(0-100),超出有效范圍,則拒絕賦值,并提供方法輸出姓名和年齡.(10分鐘)

4.2 面向?qū)ο蟪绦蛟O(shè)計(jì)案例


4.2.1 設(shè)計(jì)立方體類

設(shè)計(jì)立方體類(Cube),求出立方體的面積( 2*a*b + 2*a*c + 2*b*c )和體積( a * b * c),分別用全局函數(shù)和成員函數(shù)判斷兩個(gè)立方體是否相等。

在這里插入圖片描述

//立方體類
class Cub{
public:
    void setL(int l){ mL = l; }
    void setW(int w){ mW = w; }
    void setH(int h){ mH = h; }
    int getL(){ return mL; }
    int getW(){ return mW; }
    int getH(){ return mH; }
    //立方體面積
    int caculateS(){ return (mL*mW + mL*mH + mW*mH) * 2; }
    //立方體體積
    int caculateV(){ return mL * mW * mH; }
    //成員方法
    bool CubCompare(Cub& c){
        if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH()){
            return true;
        }
        return false;
    }
private:
    int mL; //長(zhǎng)
    int mW; //寬
    int mH; //高
};

//比較兩個(gè)立方體是否相等
bool CubCompare(Cub& c1, Cub& c2){
    if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()){
        return true;
    }
    return false;
}

void test(){
    Cub c1, c2;
    c1.setL(10);
    c1.setW(20);
    c1.setH(30);

    c2.setL(20);
    c2.setW(20);
    c2.setH(30);

    cout << "c1面積:" << c1.caculateS() << " 體積:" << c1.caculateV() << endl;
    cout << "c2面積:" << c2.caculateS() << " 體積:" << c2.caculateV() << endl;

    //比較兩個(gè)立方體是否相等
    if (CubCompare(c1, c2)){
        cout << "c1和c2相等!" << endl;
    }
    else{
        cout << "c1和c2不相等!" << endl;
    }

    if (c1.CubCompare(c2)){
        cout << "c1和c2相等!" << endl;
    }
    else{
        cout << "c1和c2不相等!" << endl;
    }
}

4.2.2 點(diǎn)和圓的關(guān)系

設(shè)計(jì)一個(gè)圓形類(AdvCircle),和一個(gè)點(diǎn)類(Point),計(jì)算點(diǎn)和圓的關(guān)系。
假如圓心坐標(biāo)為x0, y0, 半徑為r,點(diǎn)的坐標(biāo)為x1, y1

1)點(diǎn)在圓上:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) == r*r
2)點(diǎn)在圓內(nèi):(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) < r*r
3)點(diǎn)在圓外:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) > r*r

//點(diǎn)類
class Point{
public:
    void setX(int x){ mX = x; }
    void setY(int y){ mY = y; }
    int getX(){ return mX; }
    int getY(){ return mY; }
private:
    int mX;
    int mY;
};

//圓類
class Circle{
public:
    void setP(int x,int y){
        mP.setX(x);
        mP.setY(y);
    }
    void setR(int r){ mR = r; }
    Point& getP(){ return mP; }
    int getR(){ return mR; }
    //判斷點(diǎn)和圓的關(guān)系
    void IsPointInCircle(Point& point){
        int distance = (point.getX() - mP.getX()) * (point.getX() - mP.getX()) + (point.getY() - mP.getY()) * (point.getY() - mP.getY());
        int radius = mR * mR;
        if (distance < radius){
            cout << "Point(" << point.getX() << "," << point.getY() << ")在圓內(nèi)!" << endl;
        }
        else if (distance > radius){
            cout << "Point(" << point.getX() << "," << point.getY() << ")在圓外!" << endl;
        }
        else{
            cout << "Point(" << point.getX() << "," << point.getY() << ")在圓上!" << endl;
        }
    }
private:
    Point mP; //圓心
    int mR; //半徑
};

void test(){
    //實(shí)例化圓對(duì)象
    Circle circle;
    circle.setP(20, 20);
    circle.setR(5);
    //實(shí)例化點(diǎn)對(duì)象
    Point point;
    point.setX(25);
    point.setY(20);

    circle.IsPointInCircle(point);
}

4.3 對(duì)象的構(gòu)造和析構(gòu)


4.3.1 初始化和清理

我們大家在購(gòu)買一臺(tái)電腦或者手機(jī),或者其他的產(chǎn)品,這些產(chǎn)品都有一個(gè)初始設(shè)置,也就是這些產(chǎn)品對(duì)被創(chuàng)建的時(shí)候會(huì)有一個(gè)基礎(chǔ)屬性值。那么隨著我們使用手機(jī)和電腦的時(shí)間越來(lái)越久,那么電腦和手機(jī)會(huì)慢慢被我們手動(dòng)創(chuàng)建很多文件數(shù)據(jù),某一天我們不用手機(jī)或電腦了,那么我們應(yīng)該將電腦或手機(jī)中我們?cè)黾拥臄?shù)據(jù)刪除掉,保護(hù)自己的信息數(shù)據(jù)。

從這樣的過(guò)程中,我們體會(huì)一下,所有的事物在起初的時(shí)候都應(yīng)該有個(gè)初始狀態(tài),當(dāng)這個(gè)事物完成其使命時(shí),應(yīng)該及時(shí)清除外界作用于上面的一些信息數(shù)據(jù)。

那么我們c++中OO思想也是來(lái)源于現(xiàn)實(shí),是對(duì)現(xiàn)實(shí)事物的抽象模擬,具體來(lái)說(shuō),當(dāng)我們創(chuàng)建對(duì)象的時(shí)候,這個(gè)對(duì)象應(yīng)該有一個(gè)初始狀態(tài),當(dāng)對(duì)象銷毀之前應(yīng)該銷毀自己創(chuàng)建的一些數(shù)據(jù)。

對(duì)象的初始化和清理也是兩個(gè)非常重要的安全問(wèn)題,一個(gè)對(duì)象或者變量沒(méi)有初始時(shí),對(duì)其使用后果是未知,同樣的使用完一個(gè)變量,沒(méi)有及時(shí)清理,也會(huì)造成一定的安全問(wèn)題。c++為了給我們提供這種問(wèn)題的解決方案,構(gòu)造函數(shù)析構(gòu)函數(shù),這兩個(gè)函數(shù)將會(huì)被編譯器自動(dòng)調(diào)用,完成對(duì)象初始化和對(duì)象清理工作。

無(wú)論你是否喜歡,對(duì)象的初始化和清理工作是編譯器強(qiáng)制我們要做的事情,即使你不提供初始化操作和清理操作,編譯器也會(huì)給你增加默認(rèn)的操作,只是這個(gè)默認(rèn)初始化操作不會(huì)做任何事,所以編寫類就應(yīng)該順便提供初始化函數(shù)。

為什么初始化操作是自動(dòng)調(diào)用而不是手動(dòng)調(diào)用?既然是必須操作,那么自動(dòng)調(diào)用會(huì)更好,如果靠程序員自覺(jué),那么就會(huì)存在遺漏初始化的情況出現(xiàn)。

4.3.1 構(gòu)造函數(shù)和析構(gòu)函數(shù)

構(gòu)造函數(shù)主要作用在于創(chuàng)建對(duì)象時(shí)為對(duì)象的成員屬性賦值,構(gòu)造函數(shù)由編譯器自動(dòng)調(diào)用,無(wú)須手動(dòng)調(diào)用。

析構(gòu)函數(shù)主要用于對(duì)象銷毀前系統(tǒng)自動(dòng)調(diào)用,執(zhí)行一些清理工作。

構(gòu)造函數(shù)語(yǔ)法:

  • 構(gòu)造函數(shù)函數(shù)名和類名相同,沒(méi)有返回值,不能有void,但可以有參數(shù)。
  • ClassName(){}

析構(gòu)函數(shù)語(yǔ)法:

  • 析構(gòu)函數(shù)函數(shù)名是在類名前面加”~”組成,沒(méi)有返回值,不能有void,不能有參數(shù),不能重載。
  • ~ClassName(){}
class Person{
public:
    Person(){
        cout << "構(gòu)造函數(shù)調(diào)用!" << endl;
        pName = (char*)malloc(sizeof("John"));
        strcpy(pName, "John");
        mTall = 150;
        mMoney = 100;
    }
    ~Person(){
        cout << "析構(gòu)函數(shù)調(diào)用!" << endl;
        if (pName != NULL){
            free(pName);
            pName = NULL;
        }
    }
public:
    char* pName;
    int mTall;
    int mMoney;
};

void test(){
    Person person;
    cout << person.pName << person.mTall << person.mMoney << endl;
}

4.3.1 構(gòu)造函數(shù)的分類及調(diào)用

  • 按參數(shù)類型:分為無(wú)參構(gòu)造函數(shù)和有參構(gòu)造函數(shù)
  • 按類型分類:普通構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)(復(fù)制構(gòu)造函數(shù))
class Person{
public:
    Person(){
        cout << "no param constructor!" << endl;
        mAge = 0;
    }
    //有參構(gòu)造函數(shù)
    Person(int age){
        cout << "1 param constructor!" << endl;
        mAge = age;
    }
    //拷貝構(gòu)造函數(shù)(復(fù)制構(gòu)造函數(shù)) 使用另一個(gè)對(duì)象初始化本對(duì)象
    Person(const Person& person){
        cout << "copy constructor!" << endl;
        mAge = person.mAge;
    }
    //打印年齡
    void PrintPerson(){
        cout << "Age:" << mAge << endl;
    }
private:
    int mAge;
};
//1. 無(wú)參構(gòu)造調(diào)用方式
void test01(){
    
    //調(diào)用無(wú)參構(gòu)造函數(shù)
    Person person1; 
    person1.PrintPerson();

    //無(wú)參構(gòu)造函數(shù)錯(cuò)誤調(diào)用方式
    //Person person2();
    //person2.PrintPerson();
}
//2. 調(diào)用有參構(gòu)造函數(shù)
void test02(){
    
    //第一種 括號(hào)法,最常用
    Person person01(100);
    person01.PrintPerson();

    //調(diào)用拷貝構(gòu)造函數(shù)
    Person person02(person01);
    person02.PrintPerson();

    //第二種 匿名對(duì)象(顯示調(diào)用構(gòu)造函數(shù))
    Person(200); //匿名對(duì)象,沒(méi)有名字的對(duì)象

    Person person03 = Person(300);
    person03.PrintPerson();

    //注意: 使用匿名對(duì)象初始化判斷調(diào)用哪一個(gè)構(gòu)造函數(shù),要看匿名對(duì)象的參數(shù)類型
    Person person06(Person(400)); //等價(jià)于 Person person06 = Person(400);
    person06.PrintPerson();

    //第三種 =號(hào)法 隱式轉(zhuǎn)換
    Person person04 = 100; //Person person04 =  Person(100)
    person04.PrintPerson();

    //調(diào)用拷貝構(gòu)造
    Person person05 = person04; //Person person05 =  Person(person04)
    person05.PrintPerson();
}

b為A的實(shí)例化對(duì)象,A a = A(b) 和 A(b)的區(qū)別?
.
當(dāng)A(b) 有變量來(lái)接的時(shí)候,那么編譯器認(rèn)為他是一個(gè)匿名對(duì)象,當(dāng)沒(méi)有變量來(lái)接的時(shí)候,編譯器認(rèn)為A(b) 等價(jià)于 A b.

注意:不能調(diào)用拷貝構(gòu)造函數(shù)去初始化匿名對(duì)象,也就是說(shuō)以下代碼不正確:

class Teacher{
public:
    Teacher(){
        cout << "默認(rèn)構(gòu)造函數(shù)!" << endl;
    }
    Teacher(const Teacher& teacher){
        cout << "拷貝構(gòu)造函數(shù)!" << endl;
    }
public:
    int mAge;
};
void test(){
    
    Teacher t1;
    //error C2086:“Teacher t1”: 重定義
    Teacher(t1);  //此時(shí)等價(jià)于 Teacher t1;
}

4.3.2 拷貝構(gòu)造函數(shù)的調(diào)用時(shí)機(jī)

  • 對(duì)象以值傳遞的方式傳給函數(shù)參數(shù)
  • 函數(shù)局部對(duì)象以值傳遞的方式從函數(shù)返回(vs debug模式下調(diào)用一次拷貝構(gòu)造,qt不調(diào)用任何構(gòu)造)
  • 用一個(gè)對(duì)象初始化另一個(gè)對(duì)象
class Person{
public:
    Person(){
        cout << "no param contructor!" << endl;
        mAge = 10;
    }
    Person(int age){
        cout << "param constructor!" << endl;
        mAge = age;
    }
    Person(const Person& person){
        cout << "copy constructor!" << endl;
        mAge = person.mAge;
    }
    ~Person(){
        cout << "destructor!" << endl;
    }
public:
    int mAge;
};
//1. 舊對(duì)象初始化新對(duì)象
void test01(){

    Person p(10);
    Person p1(p);
    Person p2 = Person(p);
    Person p3 = p; // 相當(dāng)于Person p2 = Person(p);
}

//2. 傳遞的參數(shù)是普通對(duì)象,函數(shù)參數(shù)也是普通對(duì)象,傳遞將會(huì)調(diào)用拷貝構(gòu)造
void doBussiness(Person p){}

void test02(){
    Person p(10);
    doBussiness(p);
}

//3. 函數(shù)返回局部對(duì)象
Person MyBusiness(){
    Person p(10);
    cout << "局部p:" << (int*)&p << endl;
    return p;
}
void test03(){
    //vs release、qt下沒(méi)有調(diào)用拷貝構(gòu)造函數(shù)
    //vs debug下調(diào)用一次拷貝構(gòu)造函數(shù)
    Person p = MyBusiness();
    cout << "局部p:" << (int*)&p << endl;
}

Test03結(jié)果說(shuō)明:

編譯器存在一種對(duì)返回值的優(yōu)化技術(shù),RVO(Return Value Optimization).在vs debug模式下并沒(méi)有進(jìn)行這種優(yōu)化,所以函數(shù)MyBusiness中創(chuàng)建p對(duì)象,調(diào)用了一次構(gòu)造函數(shù),當(dāng)編譯器發(fā)現(xiàn)你要返回這個(gè)局部的對(duì)象時(shí),編譯器通過(guò)調(diào)用拷貝構(gòu)造生成一個(gè)臨時(shí)Person對(duì)象返回,然后調(diào)用p的析構(gòu)函數(shù)。

我們從常理來(lái)分析的話,這個(gè)匿名對(duì)象和這個(gè)局部的p對(duì)象是相同的兩個(gè)對(duì)象,那么如果能直接返回p對(duì)象,就會(huì)省去一個(gè)拷貝構(gòu)造和一個(gè)析構(gòu)函數(shù)的開(kāi)銷,在程序中一個(gè)對(duì)象的拷貝也是非常耗時(shí)的,如果減少這種拷貝和析構(gòu)的次數(shù),那么從另一個(gè)角度來(lái)說(shuō),也是編譯器對(duì)程序執(zhí)行效率上進(jìn)行了優(yōu)化。

所以在這里,編譯器偷偷幫我們做了一層優(yōu)化:
當(dāng)我們這樣去調(diào)用: Person p = MyBusiness();
編譯器偷偷將我們的代碼更改為:

 void MyBussiness(Person& _result){
       _result.X:X(); //調(diào)用Person默認(rèn)拷貝構(gòu)造函數(shù)
       //.....對(duì)_result進(jìn)行處理
       return;
   }
int main(){
   Person p; //這里只分配空間,不初始化
   MyBussiness(p);
}

4.3.3 構(gòu)造函數(shù)調(diào)用規(guī)則

  • 默認(rèn)情況下,c++編譯器至少為我們寫的類增加3個(gè)函數(shù)
    1.默認(rèn)構(gòu)造函數(shù)(無(wú)參,函數(shù)體為空)
    2.默認(rèn)析構(gòu)函數(shù)(無(wú)參,函數(shù)體為空)
    3.默認(rèn)拷貝構(gòu)造函數(shù),對(duì)類中非靜態(tài)成員屬性簡(jiǎn)單值拷貝

  • 如果用戶定義拷貝構(gòu)造函數(shù),c++不會(huì)再提供任何默認(rèn)構(gòu)造函數(shù)

  • 如果用戶定義了普通構(gòu)造(非拷貝),c++不在提供默認(rèn)無(wú)參構(gòu)造,但是會(huì)提供默認(rèn)拷貝構(gòu)造

4.3.4 深拷貝和淺拷貝

4.3.4.1 淺拷貝

同一類型的對(duì)象之間可以賦值,使得兩個(gè)對(duì)象的成員變量的值相同,兩個(gè)對(duì)象仍然是獨(dú)立的兩個(gè)對(duì)象,這種情況被稱為淺拷貝.

一般情況下,淺拷貝沒(méi)有任何副作用,但是當(dāng)類中有指針,并且指針指向動(dòng)態(tài)分配的內(nèi)存空間,析構(gòu)函數(shù)做了動(dòng)態(tài)內(nèi)存釋放的處理,會(huì)導(dǎo)致內(nèi)存問(wèn)題。


在這里插入圖片描述

4.3.4.2 深拷貝

當(dāng)類中有指針,并且此指針有動(dòng)態(tài)分配空間,析構(gòu)函數(shù)做了釋放處理,往往需要自定義拷貝構(gòu)造函數(shù),自行給指針動(dòng)態(tài)分配空間,深拷貝。


在這里插入圖片描述
class Person{
public:
    Person(char* name,int age){
        pName = (char*)malloc(strlen(name) + 1);
        strcpy(pName,name);
        mAge = age;
    }
    //增加拷貝構(gòu)造函數(shù)
    Person(const Person& person){
        pName = (char*)malloc(strlen(person.pName) + 1);
        strcpy(pName, person.pName);
        mAge = person.mAge;
    }
    ~Person(){
        if (pName != NULL){
            free(pName);
        }
    }
private:
    char* pName;
    int mAge;
};

void test(){
    Person p1("Edward",30);
    //用對(duì)象p1初始化對(duì)象p2,調(diào)用c++提供的默認(rèn)拷貝構(gòu)造函數(shù)
    Person p2 = p1;
}

4.3.4 多個(gè)對(duì)象構(gòu)造和析構(gòu)

4.3.4.1 初始化列表

構(gòu)造函數(shù)和其他函數(shù)不同,除了有名字,參數(shù)列表,函數(shù)體之外還有初始化列表。
初始化列表簡(jiǎn)單使用:

class Person{
public:
#if 0
    //傳統(tǒng)方式初始化
    Person(int a,int b,int c){
        mA = a;
        mB = b;
        mC = c;
    }
#endif
    //初始化列表方式初始化
    Person(int a, int b, int c):mA(a),mB(b),mC(c){}
    void PrintPerson(){
        cout << "mA:" << mA << endl;
        cout << "mB:" << mB << endl;
        cout << "mC:" << mC << endl;
    }
private:
    int mA;
    int mB;
    int mC;
};

注意:初始化成員列表(參數(shù)列表)只能在構(gòu)造函數(shù)使用。

4.3.4.2 類對(duì)象作為成員

在類中定義的數(shù)據(jù)成員一般都是基本的數(shù)據(jù)類型。但是類中的成員也可以是對(duì)象,叫做對(duì)象成員。

C++中對(duì)對(duì)象的初始化是非常重要的操作,當(dāng)創(chuàng)建一個(gè)對(duì)象的時(shí)候,c++編譯器必須確保調(diào)用了所有子對(duì)象的構(gòu)造函數(shù)。如果所有的子對(duì)象有默認(rèn)構(gòu)造函數(shù),編譯器可以自動(dòng)調(diào)用他們。但是如果子對(duì)象沒(méi)有默認(rèn)的構(gòu)造函數(shù),或者想指定調(diào)用某個(gè)構(gòu)造函數(shù)怎么辦?

那么是否可以在類的構(gòu)造函數(shù)直接調(diào)用子類的屬性完成初始化呢?但是如果子類的成員屬性是私有的,我們是沒(méi)有辦法訪問(wèn)并完成初始化的。

解決辦法非常簡(jiǎn)單:對(duì)于子類調(diào)用構(gòu)造函數(shù),c++為此提供了專門的語(yǔ)法,即構(gòu)造函數(shù)初始化列表。

當(dāng)調(diào)用構(gòu)造函數(shù)時(shí),首先按各對(duì)象成員在類定義中的順序(和參數(shù)列表的順序無(wú)關(guān)) 依次調(diào)用它們的構(gòu)造函數(shù),對(duì)這些對(duì)象初始化,最后再調(diào)用本身的函數(shù)體。也就是說(shuō),先調(diào)用對(duì)象成員的構(gòu)造函數(shù),再調(diào)用本身的構(gòu)造函數(shù)。

析構(gòu)函數(shù)和構(gòu)造函數(shù)調(diào)用順序相反,先構(gòu)造,后析構(gòu)。

//汽車類
class Car{
public:
    Car(){
        cout << "Car 默認(rèn)構(gòu)造函數(shù)!" << endl;
        mName = "大眾汽車";
    }
    Car(string name){
        cout << "Car 帶參數(shù)構(gòu)造函數(shù)!" << endl;
        mName = name;
    }
    ~Car(){
        cout << "Car 析構(gòu)函數(shù)!" << endl;
    }
public:
    string mName;
};

//拖拉機(jī)
class Tractor{
public:
    Tractor(){
        cout << "Tractor 默認(rèn)構(gòu)造函數(shù)!" << endl;
        mName = "爬土坡專用拖拉機(jī)";
    }
    Tractor(string name){
        cout << "Tractor 帶參數(shù)構(gòu)造函數(shù)!" << endl;
        mName = name;
    }
    ~Tractor(){
        cout << "Tractor 析構(gòu)函數(shù)!" << endl;
    }
public:
    string mName;
};

//人類
class Person{
public:
#if 1
    //類mCar不存在合適的構(gòu)造函數(shù)
    Person(string name){
        mName = name;
    }
#else
    //初始化列表可以指定調(diào)用構(gòu)造函數(shù)
    Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){
        cout << "Person 構(gòu)造函數(shù)!" << endl;
    }
#endif
    
    void GoWorkByCar(){
        cout << mName << "開(kāi)著" << mCar.mName << "去上班!" << endl;
    }
    void GoWorkByTractor(){
        cout << mName << "開(kāi)著" << mTractor.mName << "去上班!" << endl;
    }
    ~Person(){
        cout << "Person 析構(gòu)函數(shù)!" << endl;
    }
private:
    string mName;
    Car mCar;
    Tractor mTractor;
};

void test(){
    //Person person("寶馬", "東風(fēng)拖拉機(jī)", "趙四");
    Person person("劉能");
    person.GoWorkByCar();
    person.GoWorkByTractor();
}

4.3.5 explicit關(guān)鍵字

c++提供了關(guān)鍵字explicit,禁止通過(guò)構(gòu)造函數(shù)進(jìn)行的隱式轉(zhuǎn)換。聲明為explicit的構(gòu)造函數(shù)不能在隱式轉(zhuǎn)換中使用。

[explicit注意]

  • explicit用于修飾構(gòu)造函數(shù),防止隱式轉(zhuǎn)化。
  • 是針對(duì)單參數(shù)的構(gòu)造函數(shù)(或者除了第一個(gè)參數(shù)外其余參數(shù)都有默認(rèn)值的多參構(gòu)造)而言。
class MyString{
public:
    explicit MyString(int n){
        cout << "MyString(int n)!" << endl;
    }
    MyString(const char* str){
        cout << "MyString(const char* str)" << endl;
    }
};

int main(){

    //給字符串賦值?還是初始化?
    //MyString str1 = 1; 
    MyString str2(10);

    //寓意非常明確,給字符串賦值
    MyString str3 = "abcd";
    MyString str4("abcd");

    return EXIT_SUCCESS;
}

4.3.6 動(dòng)態(tài)對(duì)象創(chuàng)建

當(dāng)我們創(chuàng)建數(shù)組的時(shí)候,總是需要提前預(yù)定數(shù)組的長(zhǎng)度,然后編譯器分配預(yù)定長(zhǎng)度的數(shù)組空間,在使用數(shù)組的時(shí),會(huì)有這樣的問(wèn)題,數(shù)組也許空間太大了,浪費(fèi)空間,也許空間不足,所以對(duì)于數(shù)組來(lái)講,如果能根據(jù)需要來(lái)分配空間大小再好不過(guò)。

所以動(dòng)態(tài)的意思意味著不確定性。

為了解決這個(gè)普遍的編程問(wèn)題,在運(yùn)行中可以創(chuàng)建和銷毀對(duì)象是最基本的要求。當(dāng)然c早就提供了動(dòng)態(tài)內(nèi)存分配(dynamic memory allocation),函數(shù)mallocfree可以在運(yùn)行時(shí)從堆中分配存儲(chǔ)單元。

然而這些函數(shù)在c++中不能很好的運(yùn)行,因?yàn)樗荒軒臀覀兺瓿蓪?duì)象的初始化工作。

4.3.6.1 對(duì)象創(chuàng)建

當(dāng)創(chuàng)建一個(gè)c++對(duì)象時(shí)會(huì)發(fā)生兩件事:

  1. 為對(duì)象分配內(nèi)存
  2. 調(diào)用構(gòu)造函數(shù)來(lái)初始化那塊內(nèi)存

第一步我們能保證實(shí)現(xiàn),需要我們確保第二步一定能發(fā)生。c++強(qiáng)迫我們這么做是因?yàn)槭褂梦闯跏蓟膶?duì)象是程序出錯(cuò)的一個(gè)重要原因。

4.3.6.2 C動(dòng)態(tài)分配內(nèi)存方法

為了在運(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存,c在他的標(biāo)準(zhǔn)庫(kù)中提供了一些函數(shù),malloc以及它的變種callocrealloc,釋放內(nèi)存的free,這些函數(shù)是有效的、但是原始的,需要程序員理解和小心使用。為了使用c的動(dòng)態(tài)內(nèi)存分配函數(shù)在堆上創(chuàng)建一個(gè)類的實(shí)例,我們必須這樣做:

class Person{
public:
    Person(){
        mAge = 20;
        pName = (char*)malloc(strlen("john")+1);
        strcpy(pName, "john");
    }
    void Init(){
        mAge = 20;
        pName = (char*)malloc(strlen("john")+1);
        strcpy(pName, "john");
    }
    void Clean(){
        if (pName != NULL){
            free(pName);
        }
    }
public:
    int mAge;
    char* pName;
};
int main(){

    //分配內(nèi)存
    Person* person = (Person*)malloc(sizeof(Person));
    if(person == NULL){
        return 0;
    }
    //調(diào)用初始化函數(shù)
    person->Init();
    //清理對(duì)象
    person->Clean();
    //釋放person對(duì)象
    free(person);

    return EXIT_SUCCESS;
}

問(wèn)題:

  1. 程序員必須確定對(duì)象的長(zhǎng)度。
  2. malloc返回一個(gè)void指針,c++不允許將void賦值給其他任何指針,必須強(qiáng)轉(zhuǎn)。
  3. malloc可能申請(qǐng)內(nèi)存失敗,所以必須判斷返回值來(lái)確保內(nèi)存分配成功。
  4. 用戶在使用對(duì)象之前必須記住對(duì)他初始化,構(gòu)造函數(shù)不能顯示調(diào)用初始化(構(gòu)造函數(shù)是由編譯器調(diào)用),用戶有可能忘記調(diào)用初始化函數(shù)。

c的動(dòng)態(tài)內(nèi)存分配函數(shù)太復(fù)雜,容易令人混淆,是不可接受的,c++中我們推薦使用運(yùn)算符newdelete.

4.3.6.3 new operator

C++中解決動(dòng)態(tài)內(nèi)存分配的方案是把創(chuàng)建一個(gè)對(duì)象所需要的操作都結(jié)合在一個(gè)稱為new的運(yùn)算符里。當(dāng)用new創(chuàng)建一個(gè)對(duì)象時(shí),它就在堆里為對(duì)象分配內(nèi)存并調(diào)用構(gòu)造函數(shù)完成初始化。

Person* person = new Person;
相當(dāng)于:
Person* person = (Person*)malloc(sizeof(Person));
    if(person == NULL){
        return 0;
    }
person->Init();

New操作符能確定在調(diào)用構(gòu)造函數(shù)初始化之前內(nèi)存分配是成功的,所有不用顯式確定調(diào)用是否成功。

現(xiàn)在我們發(fā)現(xiàn)在堆里創(chuàng)建對(duì)象的過(guò)程變得簡(jiǎn)單了,只需要一個(gè)簡(jiǎn)單的表達(dá)式,它帶有內(nèi)置的長(zhǎng)度計(jì)算、類型轉(zhuǎn)換和安全檢查。這樣在堆創(chuàng)建一個(gè)對(duì)象和在棧里創(chuàng)建對(duì)象一樣簡(jiǎn)單。

4.3.6.4 delete operator

new表達(dá)式的反面是delete表達(dá)式。delete表達(dá)式先調(diào)用析構(gòu)函數(shù),然后釋放內(nèi)存。正如new表達(dá)式返回一個(gè)指向?qū)ο蟮闹羔樢粯樱?code>delete需要一個(gè)對(duì)象的地址。

delete只適用于由new創(chuàng)建的對(duì)象。

如果使用一個(gè)由malloc或者calloc或者realloc創(chuàng)建的對(duì)象使用delete,這個(gè)行為是未定義的。因?yàn)榇蠖鄶?shù)newdelete的實(shí)現(xiàn)機(jī)制都使用了mallocfree,所以很可能沒(méi)有調(diào)用析構(gòu)函數(shù)就釋放了內(nèi)存。

如果正在刪除的對(duì)象的指針是NULL,將不發(fā)生任何事,因此建議在刪除指針后,立即把指針賦值為NULL,以免對(duì)它刪除兩次,對(duì)一些對(duì)象刪除兩次可能會(huì)產(chǎn)生某些問(wèn)題。

class Person{
public:
    Person(){
        cout << "無(wú)參構(gòu)造函數(shù)!" << endl;
        pName = (char*)malloc(strlen("undefined") + 1);
        strcpy(pName, "undefined");
        mAge = 0;
    }
    Person(char* name, int age){
        cout << "有參構(gòu)造函數(shù)!" << endl;
        pName = (char*)malloc(strlen(name) + 1);
        strcpy(pName, name);
        mAge = age;
    }
    void ShowPerson(){
        cout << "Name:" << pName << " Age:" << mAge << endl;
    }
    ~Person(){
        cout << "析構(gòu)函數(shù)!" << endl;
        if (pName != NULL){
            delete pName;
            pName = NULL;
        }
    }
public:
    char* pName;
    int mAge;
};

void test(){
    Person* person1 = new Person;
    Person* person2 = new Person("John",33);

    person1->ShowPerson();
    person2->ShowPerson();

    delete person1;
    delete person2;
}

4.3.6.5 用于數(shù)組的new和delete

使用newdelete在堆上創(chuàng)建數(shù)組非常容易。

//創(chuàng)建字符數(shù)組
char* pStr = new char[100];
//創(chuàng)建整型數(shù)組
int* pArr1 = new int[100]; 
//創(chuàng)建整型數(shù)組并初始化
int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//釋放數(shù)組內(nèi)存
delete[] pStr;
delete[] pArr1;
delete[] pArr2;

當(dāng)創(chuàng)建一個(gè)對(duì)象數(shù)組的時(shí)候,必須對(duì)數(shù)組中的每一個(gè)對(duì)象調(diào)用構(gòu)造函數(shù),除了在棧上可以聚合初始化,必須提供一個(gè)默認(rèn)的構(gòu)造函數(shù)。

class Person{
public:
    Person(){
        pName = (char*)malloc(strlen("undefined") + 1);
        strcpy(pName, "undefined");
        mAge = 0;
    }
    Person(char* name, int age){
        pName = (char*)malloc(sizeof(name));
        strcpy(pName, name);
        mAge = age;
    }
    ~Person(){
        if (pName != NULL){
            delete pName;
        }
    }
public:
    char* pName;
    int mAge;
};

void test(){
    //棧聚合初始化
    Person person[] = { Person("john", 20), Person("Smith", 22) };
    cout << person[1].pName << endl;
    //創(chuàng)建堆上對(duì)象數(shù)組必須提供構(gòu)造函數(shù)
    Person* workers = new Person[20];
}

4.3.6.6 delete void*可能會(huì)出錯(cuò)

如果對(duì)一個(gè)void*指針執(zhí)行delete操作,這將可能成為一個(gè)程序錯(cuò)誤,除非指針指向的內(nèi)容是非常簡(jiǎn)單的,因?yàn)樗鼘⒉粓?zhí)行析構(gòu)函數(shù).以下代碼未調(diào)用析構(gòu)函數(shù),導(dǎo)致可用內(nèi)存減少。

class Person{
public:
    Person(char* name, int age){
        pName = (char*)malloc(sizeof(name));
        strcpy(pName,name);
        mAge = age;
    }
    ~Person(){
        if (pName != NULL){
            delete pName;
        }
    }
public:
    char* pName;
    int mAge;
};

void test(){
    void* person = new Person("john",20);
    delete person;
}

問(wèn)題:
malloc、freenew、delete可以混搭使用嗎?也就是說(shuō)malloc分配的內(nèi)存,可以調(diào)用delete嗎?通過(guò)new創(chuàng)建的對(duì)象,可以調(diào)用free來(lái)釋放嗎?

4.3.6.7 使用new和delete采用相同形式

Person* person = new Person[10];
delete person;

以上代碼有什么問(wèn)題嗎?(vs下直接中斷、qt下析構(gòu)函數(shù)調(diào)用一次)

使用了new也搭配使用了delete,問(wèn)題在于Person有10個(gè)對(duì)象,那么其他9個(gè)對(duì)象可能沒(méi)有調(diào)用析構(gòu)函數(shù),也就是說(shuō)其他9個(gè)對(duì)象可能刪除不完全,因?yàn)樗鼈兊奈鰳?gòu)函數(shù)沒(méi)有被調(diào)用。

我們現(xiàn)在清楚使用new的時(shí)候發(fā)生了兩件事: 一、分配內(nèi)存;二、調(diào)用構(gòu)造函數(shù),那么調(diào)用delete的時(shí)候也有兩件事:一、析構(gòu)函數(shù);二、釋放內(nèi)存。

那么剛才我們那段代碼最大的問(wèn)題在于:person指針指向的內(nèi)存中到底有多少個(gè)對(duì)象,因?yàn)檫@個(gè)決定應(yīng)該有多少個(gè)析構(gòu)函數(shù)應(yīng)該被調(diào)用。換句話說(shuō),person指針指向的是一個(gè)單一的對(duì)象還是一個(gè)數(shù)組對(duì)象,由于單一對(duì)象和數(shù)組對(duì)象的內(nèi)存布局是不同的。更明確的說(shuō),數(shù)組所用的內(nèi)存通常還包括“數(shù)組大小記錄”,使得delete的時(shí)候知道應(yīng)該調(diào)用幾次析構(gòu)函數(shù)。單一對(duì)象的話就沒(méi)有這個(gè)記錄。單一對(duì)象和數(shù)組對(duì)象的內(nèi)存布局可理解為下圖:

在這里插入圖片描述

本圖只是為了說(shuō)明,編譯器不一定如此實(shí)現(xiàn),但是很多編譯器是這樣做的。

當(dāng)我們使用一個(gè)delete的時(shí)候,我們必須讓delete知道指針指向的內(nèi)存空間中是否存在一個(gè)“數(shù)組大小記錄”的辦法就是我們告訴它。當(dāng)我們使用delete[],那么delete就知道是一個(gè)對(duì)象數(shù)組,從而清楚應(yīng)該調(diào)用幾次析構(gòu)函數(shù)。

結(jié)論:
如果在new表達(dá)式中使用[],必須在相應(yīng)的delete表達(dá)式中也使用[].如果在new表達(dá)式中不使用[], 一定不要在相應(yīng)的delete表達(dá)式中使用[].

4.3.7 靜態(tài)成員

在類定義中,它的成員(包括成員變量和成員函數(shù)),這些成員可以用關(guān)鍵字static聲明為靜態(tài)的,稱為靜態(tài)成員。

不管這個(gè)類創(chuàng)建了多少個(gè)對(duì)象,靜態(tài)成員只有一個(gè)拷貝,這個(gè)拷貝被所有屬于這個(gè)類的對(duì)象共享。

4.3.7.1 靜態(tài)成員變量

在一個(gè)類中,若將一個(gè)成員變量聲明為static,這種成員稱為靜態(tài)成員變量。與一般的數(shù)據(jù)成員不同,無(wú)論建立了多少個(gè)對(duì)象,都只有一個(gè)靜態(tài)數(shù)據(jù)的拷貝。靜態(tài)成員變量,屬于某個(gè)類,所有對(duì)象共享。

靜態(tài)變量,是在編譯階段就分配空間,對(duì)象還沒(méi)有創(chuàng)建時(shí),就已經(jīng)分配空間。

  • 靜態(tài)成員變量必須在類中聲明,在類外定義。
  • 靜態(tài)數(shù)據(jù)成員不屬于某個(gè)對(duì)象,在為對(duì)象分配空間中不包括靜態(tài)成員所占空間。
  • 靜態(tài)數(shù)據(jù)成員可以通過(guò)類名或者對(duì)象名來(lái)引用。
class Person{
public:
    //類的靜態(tài)成員屬性
    static int sNum;
private:
    static int sOther;
};

//類外初始化,初始化時(shí)不加static
int Person::sNum = 0;
int Person::sOther = 0;
int main(){


    //1. 通過(guò)類名直接訪問(wèn)
    Person::sNum = 100;
    cout << "Person::sNum:" << Person::sNum << endl;

    //2. 通過(guò)對(duì)象訪問(wèn)
    Person p1, p2;
    p1.sNum = 200;

    cout << "p1.sNum:" << p1.sNum << endl;
    cout << "p2.sNum:" << p2.sNum << endl;

    //3. 靜態(tài)成員也有訪問(wèn)權(quán)限,類外不能訪問(wèn)私有成員
    //cout << "Person::sOther:" << Person::sOther << endl;
    Person p3;
    //cout << "p3.sOther:" << p3.sOther << endl;

    system("pause");
    return EXIT_SUCCESS;
}

4.3.7.2 靜態(tài)成員函數(shù)

在類定義中,前面有static說(shuō)明的成員函數(shù)稱為靜態(tài)成員函數(shù)。靜態(tài)成員函數(shù)使用方式和靜態(tài)變量一樣,同樣在對(duì)象沒(méi)有創(chuàng)建前,即可通過(guò)類名調(diào)用。靜態(tài)成員函數(shù)主要為了訪問(wèn)靜態(tài)變量,但是,不能訪問(wèn)普通成員變量。

靜態(tài)成員函數(shù)的意義,不在于信息共享,數(shù)據(jù)溝通,而在于管理靜態(tài)數(shù)據(jù)成員,完成對(duì)靜態(tài)數(shù)據(jù)成員的封裝。

  • 靜態(tài)成員函數(shù)只能訪問(wèn)靜態(tài)變量,不能訪問(wèn)普通成員變量
  • 靜態(tài)成員函數(shù)的使用和靜態(tài)成員變量一樣
  • 靜態(tài)成員函數(shù)也有訪問(wèn)權(quán)限
  • 普通成員函數(shù)可訪問(wèn)靜態(tài)成員變量、也可以訪問(wèn)非經(jīng)常成員變量
class Person{
public:
    //普通成員函數(shù)可以訪問(wèn)static和non-static成員屬性
    void changeParam1(int param){
        mParam = param;
        sNum = param;
    }
    //靜態(tài)成員函數(shù)只能訪問(wèn)static成員屬性
    static void changeParam2(int param){
        //mParam = param; //無(wú)法訪問(wèn)
        sNum = param;
    }
private:
    static void changeParam3(int param){
        //mParam = param; //無(wú)法訪問(wèn)
        sNum = param;
    }
public:
    int mParam;
    static int sNum;
};

//靜態(tài)成員屬性類外初始化
int Person::sNum = 0;

int main(){

    //1. 類名直接調(diào)用
    Person::changeParam2(100);

    //2. 通過(guò)對(duì)象調(diào)用
    Person p;
    p.changeParam2(200);

    //3. 靜態(tài)成員函數(shù)也有訪問(wèn)權(quán)限
    //Person::changeParam3(100); //類外無(wú)法訪問(wèn)私有靜態(tài)成員函數(shù)
    //Person p1;
    //p1.changeParam3(200);
    return EXIT_SUCCESS;
}

4.3.7.3 const靜態(tài)成員屬性

如果一個(gè)類的成員,既要實(shí)現(xiàn)共享,又要實(shí)現(xiàn)不可改變,那就用 static const 修飾。

定義靜態(tài)const數(shù)據(jù)成員時(shí),最好在類內(nèi)部初始化。

class Person{
public:
    //static const int mShare = 10;
    const static int mShare = 10; //只讀區(qū),不可修改
};
int main(){

    cout << Person::mShare << endl;
    //Person::mShare = 20;

    return EXIT_SUCCESS;
}

4.3.7.4 靜態(tài)成員實(shí)現(xiàn)單例模式

單例模式是一種常用的軟件設(shè)計(jì)模式。在它的核心結(jié)構(gòu)中只包含一個(gè)被稱為單例的特殊類。通過(guò)單例模式可以保證系統(tǒng)中一個(gè)類只有一個(gè)實(shí)例而且該實(shí)例易于外界訪問(wèn),從而方便對(duì)實(shí)例個(gè)數(shù)的控制并節(jié)約系統(tǒng)資源。如果希望在系統(tǒng)中某個(gè)類的對(duì)象只能存在一個(gè),單例模式是最好的解決方案。

在這里插入圖片描述

Singleton(單例):在單例類的內(nèi)部實(shí)現(xiàn)只生成一個(gè)實(shí)例,同時(shí)它提供一個(gè)靜態(tài)的getInstance()工廠方法,讓客戶可以訪問(wèn)它的唯一實(shí)例;為了防止在外部對(duì)其實(shí)例化,將其默認(rèn)構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)設(shè)計(jì)為私有;在單例類內(nèi)部定義了一個(gè)Singleton類型的靜態(tài)對(duì)象,作為外部共享的唯一實(shí)例。

用單例模式,模擬公司員工使用打印機(jī)場(chǎng)景,打印機(jī)可以打印員工要輸出的內(nèi)容,并且可以累積打印機(jī)使用次數(shù)。

class Printer{
public:
    static Printer* getInstance(){ return pPrinter;}
    void PrintText(string text){
        cout << "打印內(nèi)容:" << text << endl;
        cout << "已打印次數(shù):" << mTimes << endl;
        cout << "--------------" << endl;
        mTimes++;
    }
private:
    Printer(){ mTimes = 0; }
    Printer(const Printer&){}
private:
    static Printer* pPrinter;
    int mTimes;
};

Printer* Printer::pPrinter = new Printer;

void test(){
    Printer* printer = Printer::getInstance();
    printer->PrintText("離職報(bào)告!");
    printer->PrintText("入職合同!");
    printer->PrintText("提交代碼!");
}

4.4 C++面向?qū)ο竽P统跆?/h2>


4.4.1 成員變量和函數(shù)的存儲(chǔ)

在c語(yǔ)言中,“分開(kāi)來(lái)聲明的,也就是說(shuō),語(yǔ)言本身并沒(méi)有支持“數(shù)據(jù)”和“函數(shù)”之間的關(guān)聯(lián)性我們把這種程序方法稱為“程序性的”,由一組“分布在各個(gè)以功能為導(dǎo)航的函數(shù)中”的算法驅(qū)動(dòng),它們處理的是共同的外部數(shù)據(jù)。

c++實(shí)現(xiàn)了“封裝”,那么數(shù)據(jù)(成員屬性)和操作(成員函數(shù))是什么樣的呢?

“數(shù)據(jù)”和“處理數(shù)據(jù)的操作(函數(shù))”是分開(kāi)存儲(chǔ)的。

  • c++中的非靜態(tài)數(shù)據(jù)成員直接內(nèi)含在類對(duì)象中,就像c struct一樣。
  • 成員函數(shù)(member function)雖然內(nèi)含在class聲明之內(nèi),卻不出現(xiàn)在對(duì)象中。
  • 每一個(gè)非內(nèi)聯(lián)成員函數(shù)(non-inline member function)只會(huì)誕生一份函數(shù)實(shí)例.
class MyClass01{
public:
    int mA;
};

class MyClass02{
public:
    int mA;
    static int sB;
};

class MyClass03{
public:
    void printMyClass(){
        cout << "hello world!" << endl;
    }
public:
    int mA;
    static int sB;
};

class MyClass04{
public:
    void printMyClass(){
        cout << "hello world!" << endl;
    }
    static void ShowMyClass(){
        cout << "hello world!" << endl;
    }
public:
    int mA;
    static int sB;
};

int main(){

    MyClass01 mclass01;
    MyClass02 mclass02;
    MyClass03 mclass03;
    MyClass04 mclass04;

    cout << "MyClass01:" << sizeof(mclass01) << endl; //4
    //靜態(tài)數(shù)據(jù)成員并不保存在類對(duì)象中
    cout << "MyClass02:" << sizeof(mclass02) << endl; //4
    //非靜態(tài)成員函數(shù)不保存在類對(duì)象中
    cout << "MyClass03:" << sizeof(mclass03) << endl; //4
    //靜態(tài)成員函數(shù)也不保存在類對(duì)象中
    cout << "MyClass04:" << sizeof(mclass04) << endl; //4

    return EXIT_SUCCESS;
}

通過(guò)上面的案例,我們可以的得出:C++類對(duì)象中的變量和函數(shù)是分開(kāi)存儲(chǔ)。

4.4.2 this指針

4.4.2.1 this指針工作原理

通過(guò)上例我們知道,c++的數(shù)據(jù)和操作也是分開(kāi)存儲(chǔ),并且每一個(gè)非內(nèi)聯(lián)成員函數(shù)(non-inline member function)只會(huì)誕生一份函數(shù)實(shí)例,也就是說(shuō)多個(gè)同類型的對(duì)象會(huì)共用一塊代碼

那么問(wèn)題是:這一塊代碼是如何區(qū)分那個(gè)對(duì)象調(diào)用自己的呢?

在這里插入圖片描述

c++通過(guò)提供特殊的對(duì)象指針,this指針,解決上述問(wèn)題。This指針指向被調(diào)用的成員函數(shù)所屬的對(duì)象。

c++規(guī)定,this指針是隱含在對(duì)象成員函數(shù)內(nèi)的一種指針。當(dāng)一個(gè)對(duì)象被創(chuàng)建后,它的每一個(gè)成員函數(shù)都含有一個(gè)系統(tǒng)自動(dòng)生成的隱含指針this,用以保存這個(gè)對(duì)象的地址,也就是說(shuō)雖然我們沒(méi)有寫上this指針,編譯器在編譯的時(shí)候也是會(huì)加上的。因此this也稱為“指向本對(duì)象的指針”,this指針并不是對(duì)象的一部分,不會(huì)影響sizeof(對(duì)象)的結(jié)果。

this指針是C++實(shí)現(xiàn)封裝的一種機(jī)制,它將對(duì)象和該對(duì)象調(diào)用的成員函數(shù)連接在一起,在外部看來(lái),每一個(gè)對(duì)象都擁有自己的函數(shù)成員。一般情況下,并不寫this,而是讓系統(tǒng)進(jìn)行默認(rèn)設(shè)置。

this指針永遠(yuǎn)指向當(dāng)前對(duì)象。

成員函數(shù)通過(guò)this指針即可知道操作的是那個(gè)對(duì)象的數(shù)據(jù)。This指針是一種隱含指針,它隱含于每個(gè)類的非靜態(tài)成員函數(shù)中。This指針無(wú)需定義,直接使用即可。

注意:靜態(tài)成員函數(shù)內(nèi)部沒(méi)有this指針,靜態(tài)成員函數(shù)不能操作非靜態(tài)成員變量。

c++編譯器對(duì)普通成員函數(shù)的內(nèi)部處理

在這里插入圖片描述

4.4.2.2 this指針的使用

  • 當(dāng)形參和成員變量同名時(shí),可用this指針來(lái)區(qū)分
  • 在類的非靜態(tài)成員函數(shù)中返回對(duì)象本身,可使用return *this.
class Person{
public:
    //1. 當(dāng)形參名和成員變量名一樣時(shí),this指針可用來(lái)區(qū)分
    Person(string name,int age){
        //name = name;
        //age = age; //輸出錯(cuò)誤
        this->name = name;
        this->age = age;
    }
    //2. 返回對(duì)象本身的引用
    //重載賦值操作符
    //其實(shí)也是兩個(gè)參數(shù),其中隱藏了一個(gè)this指針
    Person PersonPlusPerson(Person& person){
        string newname = this->name + person.name;
        int newage = this->age + person.age;
        Person newperson(newname, newage);
        return newperson;
    }
    void ShowPerson(){
        cout << "Name:" << name << " Age:" << age << endl;
    }
public:
    string name;
    int age;
};

//3. 成員函數(shù)和全局函數(shù)(Perosn對(duì)象相加)
Person PersonPlusPerson(Person& p1,Person& p2){
    string newname = p1.name + p2.name;
    int newage = p1.age + p2.age;
    Person newperson(newname,newage);
    return newperson;
}

int main(){

    Person person("John",100);
    person.ShowPerson();

    cout << "---------" << endl;
    Person person1("John",20);
    Person person2("001", 10);
    //1.全局函數(shù)實(shí)現(xiàn)兩個(gè)對(duì)象相加
    Person person3 = PersonPlusPerson(person1, person2);
    person1.ShowPerson();
    person2.ShowPerson();
    person3.ShowPerson();
    //2. 成員函數(shù)實(shí)現(xiàn)兩個(gè)對(duì)象相加
    Person person4 = person1.PersonPlusPerson(person2);
    person4.ShowPerson();

    system("pause");
    return EXIT_SUCCESS;
}

4.4.2.3 const修飾成員函數(shù)

  • 用const修飾的成員函數(shù)時(shí),const修飾this指針指向的內(nèi)存區(qū)域,成員函數(shù)體內(nèi)不可以修改本類中的任何普通成員變量,
  • 當(dāng)成員變量類型符前用mutable修飾時(shí)例外。
//const修飾成員函數(shù)
class Person{
public:
    Person(){
        this->mAge = 0;
        this->mID = 0;
    }
    //在函數(shù)括號(hào)后面加上const,修飾成員變量不可修改,除了mutable變量
    void sonmeOperate() const{
        //this->mAge = 200; //mAge不可修改
        this->mID = 10;
    }
    void ShowPerson(){
        cout << "ID:" << mID << " mAge:" << mAge << endl;
    }
private:
    int mAge;
    mutable int mID;
};

int main(){

    Person person;
    person.sonmeOperate();
    person.ShowPerson();

    system("pause");
    return EXIT_SUCCESS;
}

4.4.2.4 const修飾對(duì)象(常對(duì)象)

  • 常對(duì)象只能調(diào)用const的成員函數(shù)
  • 常對(duì)象可訪問(wèn) const 或非 const 數(shù)據(jù)成員,不能修改,除非成員用mutable修飾
class Person{
public:
    Person(){
        this->mAge = 0;
        this->mID = 0;
    }
    void ChangePerson() const{
        mAge = 100;
        mID = 100;
    }
    void ShowPerson(){
        this->mAge = 1000;
        cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
    }

public:
    int mAge;
    mutable int mID;
};

void test(){    
    const Person person;
    //1. 可訪問(wèn)數(shù)據(jù)成員
    cout << "Age:" << person.mAge << endl;
    //person.mAge = 300; //不可修改
    person.mID = 1001; //但是可以修改mutable修飾的成員變量
    //2. 只能訪問(wèn)const修飾的函數(shù)
    //person.ShowPerson();
    person.ChangePerson();
}

4.5 友元

類的主要特點(diǎn)之一是數(shù)據(jù)隱藏,即類的私有成員無(wú)法在類的外部(作用域之外)訪問(wèn)。但是,有時(shí)候需要在類的外部訪問(wèn)類的私有成員,怎么辦?

解決方法是使用友元函數(shù),友元函數(shù)是一種特權(quán)函數(shù),c++允許這個(gè)特權(quán)函數(shù)訪問(wèn)私有成員。這一點(diǎn)從現(xiàn)實(shí)生活中也可以很好的理解:

比如你的家,有客廳,有你的臥室,那么你的客廳是Public的,所有來(lái)的客人都可以進(jìn)去,但是你的臥室是私有的,也就是說(shuō)只有你能進(jìn)去,但是呢,你也可以允許你的閨蜜好基友進(jìn)去。

程序員可以把一個(gè)全局函數(shù)、某個(gè)類中的成員函數(shù)、甚至整個(gè)類聲明為友元。

4.5.1 友元語(yǔ)法

  • friend關(guān)鍵字只出現(xiàn)在聲明處
  • 其他類、類成員函數(shù)、全局函數(shù)都可聲明為友元
  • 友元函數(shù)不是類的成員,不帶this指針
  • 友元函數(shù)可訪問(wèn)對(duì)象任意成員屬性,包括私有屬性
class Building;
//友元類
class MyFriend{
public:
    //友元成員函數(shù)
    void LookAtBedRoom(Building& building);
    void PlayInBedRoom(Building& building);
};
class Building{
    //全局函數(shù)做友元函數(shù)
    friend void CleanBedRoom(Building& building);
#if 0
    //成員函數(shù)做友元函數(shù)
    friend void MyFriend::LookAtBedRoom(Building& building);
    friend void MyFriend::PlayInBedRoom(Building& building);
#else   
    //友元類
    friend class MyFriend;
#endif
public:
    Building();
public:
    string mSittingRoom;
private:
    string mBedroom;
};

void MyFriend::LookAtBedRoom(Building& building){
    cout << "我的朋友參觀" << building.mBedroom << endl;
}
void MyFriend::PlayInBedRoom(Building& building){
    cout << "我的朋友玩耍在" << building.mBedroom << endl;
}

//友元全局函數(shù)
void CleanBedRoom(Building& building){
    cout << "友元全局函數(shù)訪問(wèn)" << building.mBedroom << endl;
}

Building::Building(){
    this->mSittingRoom = "客廳";
    this->mBedroom = "臥室";
}

int main(){

    Building building;
    MyFriend myfriend;

    CleanBedRoom(building);
    myfriend.LookAtBedRoom(building);
    myfriend.PlayInBedRoom(building);

    system("pause");
    return EXIT_SUCCESS;
}

友元類注意
1.友元關(guān)系不能被繼承。
2.友元關(guān)系是單向的,類A是類B的朋友,但類B不一定是類A的朋友。
3.友元關(guān)系不具有傳遞性。類B是類A的朋友,類C是類B的朋友,但類C不一定是類A的朋友。

思考: c++是純面向?qū)ο蟮膯幔?/strong>

如果一個(gè)類被聲明為friend,意味著它不是這個(gè)類的成員函數(shù),卻可以修改這個(gè)類的私有成員,而且必須列在類的定義中,因此他是一個(gè)特權(quán)函數(shù)。c++不是完全的面向?qū)ο笳Z(yǔ)言,而只是一個(gè)混合產(chǎn)品。增加friend關(guān)鍵字只是用來(lái)解決一些實(shí)際問(wèn)題,這也說(shuō)明這種語(yǔ)言是不純的。畢竟c++設(shè)計(jì)的目的是為了實(shí)用性,而不是追求理想的抽象。 --- Thinking in C++

4.5.2 課堂練習(xí)

請(qǐng)編寫電視機(jī)類,電視機(jī)有開(kāi)機(jī)和關(guān)機(jī)狀態(tài),有音量,有頻道,提供音量操作的方法,頻道操作的方法。由于電視機(jī)只能逐一調(diào)整頻道,不能指定頻道,增加遙控類,遙控類除了擁有電視機(jī)已有的功能,再增加根據(jù)輸入調(diào)臺(tái)功能。

提示:遙控器可作為電視機(jī)類的友元類。

class Remote;

class Television{
    friend class Remote;
public:
    enum{ On,Off }; //電視狀態(tài)
    enum{ minVol,maxVol = 100 }; //音量從0到100
    enum{ minChannel = 1,maxChannel = 255 }; //頻道從1到255
    Television(){
        mState = Off;
        mVolume = minVol;
        mChannel = minChannel;
    }

    //打開(kāi)電視機(jī)
    void OnOrOff(){
        this->mState = (this->mState == On ? Off : On);
    }
    //調(diào)高音量
    void VolumeUp(){
        if (this->mVolume >= maxVol){
            return;
        }
        this->mVolume++;
    }
    //調(diào)低音量
    void VolumeDown(){
        if (this->mVolume <= minVol){
            return;
        }
        this->mVolume--;
    }
    //更換電視頻道
    void ChannelUp(){
        if (this->mChannel >= maxChannel){
            return;
        }
        this->mChannel++;
    }
    void ChannelDown(){
        if (this->mChannel <= minChannel){
            return;
        }
        this->mChannel--;
    }
    //展示當(dāng)前電視狀態(tài)信息
    void ShowTeleState(){
        cout << "開(kāi)機(jī)狀態(tài):" << (mState == On ? "已開(kāi)機(jī)" : "已關(guān)機(jī)") << endl;
        if (mState == On){
            cout << "當(dāng)前音量:" << mVolume << endl;
            cout << "當(dāng)前頻道:" << mChannel << endl;
        }
        cout << "-------------" << endl;
    }
private:
    int mState; //電視狀態(tài),開(kāi)機(jī),還是關(guān)機(jī)
    int mVolume; //電視機(jī)音量
    int mChannel; //電視頻道
};

//電視機(jī)調(diào)臺(tái)只能一個(gè)一個(gè)的調(diào),遙控可以指定頻道
//電視遙控器
class Remote{
public:
    Remote(Television* television){
        pTelevision = television;
    }
public:
    void OnOrOff(){
        pTelevision->OnOrOff();
    }
    //調(diào)高音量
    void VolumeUp(){
        pTelevision->VolumeUp();
    }
    //調(diào)低音量
    void VolumeDown(){
        pTelevision->VolumeDown();
    }
    //更換電視頻道
    void ChannelUp(){
        pTelevision->ChannelUp();
    }
    void ChannelDown(){
        pTelevision->ChannelDown();
    }
    //設(shè)置頻道 遙控新增功能
    void SetChannel(int channel){
        if (channel < Television::minChannel || channel > Television::maxChannel){
            return;
        }
        pTelevision->mChannel = channel;
    }

    //顯示電視當(dāng)前信息
    void ShowTeleState(){
        pTelevision->ShowTeleState();
    }
private:
    Television* pTelevision;
};


//直接操作電視
void test01(){

    Television television;
    television.ShowTeleState();
    television.OnOrOff(); //開(kāi)機(jī)
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.ChannelUp(); //頻道+1
    television.ChannelUp(); //頻道+1
    television.ShowTeleState();
}

//通過(guò)遙控操作電視
void test02(){
    //創(chuàng)建電視
    Television television;
    //創(chuàng)建遙控
    Remote remote(&television);
    remote.OnOrOff();
    remote.ChannelUp();//頻道+1
    remote.ChannelUp();//頻道+1
    remote.ChannelUp();//頻道+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.ShowTeleState();
}

4.5 強(qiáng)化訓(xùn)練(數(shù)組類封裝)


MyArray.h

#ifndef MYARRAY_H
#define MYARRAY_H

class MyArray{
public:
    //無(wú)參構(gòu)造函數(shù),用戶沒(méi)有指定容量,則初始化為100
    MyArray();
    //有參構(gòu)造函數(shù),用戶指定容量初始化
    explicit MyArray(int capacity);
    //用戶操作接口
    //根據(jù)位置添加元素
    void SetData(int pos, int val);
    //獲得指定位置數(shù)據(jù)
    int GetData(int pos);
    //尾插法
    void PushBack(int val);
    //獲得長(zhǎng)度
    int GetLength();
    //析構(gòu)函數(shù),釋放數(shù)組空間
    ~MyArray();
private:
    int mCapacity; //數(shù)組一共可容納多少個(gè)元素
    int mSize; //當(dāng)前有多少個(gè)元素
    int* pAdress; //指向存儲(chǔ)數(shù)據(jù)的空間
};

#endif

MyArray.cpp

#include"MyArray.h"

MyArray::MyArray(){
    this->mCapacity = 100;
    this->mSize = 0;
    //在堆開(kāi)辟空間
    this->pAdress = new int[this->mCapacity];
}
//有參構(gòu)造函數(shù),用戶指定容量初始化
MyArray::MyArray(int capacity){
    this->mCapacity = capacity;
    this->mSize = 0;
    //在堆開(kāi)辟空間
    this->pAdress = new int[capacity];
}
//根據(jù)位置添加元素
void MyArray::SetData(int pos, int val){
    if (pos < 0 || pos > mCapacity - 1){
        return;
    }
    pAdress[pos] = val;
}
//獲得指定位置數(shù)據(jù)
int MyArray::GetData(int pos){
    return pAdress[pos];
}
//尾插法
void MyArray::PushBack(int val){
    if (mSize >= mCapacity){
        return;
    }
    this->pAdress[mSize] = val;
    this->mSize++;
}
//獲得長(zhǎng)度
int MyArray::GetLength(){
    return this->mSize;
}
//析構(gòu)函數(shù),釋放數(shù)組空間
MyArray::~MyArray(){
    if (this->pAdress != nullptr){
        delete[] this->pAdress;
    }
}

TestMyArray.cpp

#include"MyArray.h"

void test(){
    //創(chuàng)建數(shù)組
    MyArray myarray(50);
    //數(shù)組中插入元素
    for (int i = 0; i < 50; i++){
        //尾插法
        myarray.PushBack(i);
        //myarray.SetData(i, i);
    }
    //打印數(shù)組中元素
    for (int i = 0; i < myarray.GetLength(); i++){
        cout << myarray.GetData(i) << " ";
    }
    cout << endl;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容