【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)限才有意義:public梗肝,privateprotected
  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)系作者
  • 序言:七十年代末祥国,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌舌稀,老刑警劉巖啊犬,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異壁查,居然都是意外死亡觉至,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門睡腿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)语御,“玉大人,你說(shuō)我怎么就攤上這事席怪∮Υ常” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵挂捻,是天一觀的道長(zhǎng)碉纺。 經(jīng)常有香客問(wèn)我,道長(zhǎng)刻撒,這世上最難降的妖魔是什么骨田? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮疫赎,結(jié)果婚禮上盛撑,老公的妹妹穿的比我還像新娘。我一直安慰自己捧搞,他們只是感情好抵卫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著胎撇,像睡著了一般介粘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晚树,一...
    開(kāi)封第一講書(shū)人閱讀 51,688評(píng)論 1 305
  • 那天姻采,我揣著相機(jī)與錄音,去河邊找鬼爵憎。 笑死慨亲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宝鼓。 我是一名探鬼主播刑棵,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼愚铡!你這毒婦竟也來(lái)了蛉签?” 一聲冷哼從身側(cè)響起胡陪,我...
    開(kāi)封第一講書(shū)人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碍舍,沒(méi)想到半個(gè)月后柠座,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡片橡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年妈经,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捧书。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡狂塘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鳄厌,到底是詐尸還是另有隱情,我是刑警寧澤妈踊,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布了嚎,位于F島的核電站,受9級(jí)特大地震影響廊营,放射性物質(zhì)發(fā)生泄漏歪泳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一露筒、第九天 我趴在偏房一處隱蔽的房頂上張望呐伞。 院中可真熱鬧,春花似錦慎式、人聲如沸伶氢。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)癣防。三九已至,卻和暖如春掌眠,著一層夾襖步出監(jiān)牢的瞬間蕾盯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工蓝丙, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留级遭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓渺尘,卻偏偏與公主長(zhǎng)得像挫鸽,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沧烈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容