C++中的多態用法總結

1,父類中的方法加virtual與不加virtual的區別

先定義兩個beanBaseBeanUserBeanUserBean繼承於BaseBeanBaseBean中含有兩個屬性id(int類型)create_timestd::string類型)。UserBean中含有屬性namestd::string類型)。定義兩個類BaseModelUserModelUserModel繼承於BaseModelBaseModel中定義了一個方法virtual int addRecord(BaseBean *);其定義如下:

int BaseModel::addRecord(BaseBean *data)

{

        std::cout << "base" << std::endl;

        return 0;

}

UserModel中重新聲明int addRecord(BaseBean *);並定義這個方法:

int UserModel::addRecord(BaseBean *data)

{

    std::cout << "user" << std::endl;

    return 0;

}

main函數中測試這兩個類的addRecord方法:

    BaseModel base;

    UserModel user;

    BaseModel *basePtrOfBase = new BaseModel;

    BaseModel *basePtrOfUser = new UserModel;

    BaseModel &baseRefOfBase = base;

    BaseModel &baseRefOfUser = user;

    BaseModel baseOfUser = user;

    UserBean userBean;

    userBean.setId(1);

    userBean.setName("first user");

    std::cout << "base:";

    base.addRecord(&userBean);

    std::cout << "user:";

    user.addRecord(&userBean);

    std::cout << "basePtrOfBase:";

    basePtrOfBase->addRecord(&userBean);

    std::cout << "basePtrOfUser:";

    basePtrOfUser->addRecord(&userBean);

    std::cout << "baseRefOfBase:";

    baseRefOfBase.addRecord(&userBean);

    std::cout << "baseRefOfUser:";

    baseRefOfUser.addRecord(&userBean);

    std::cout << "baseOfUser:";

baseOfUser.addRecord(&userBean);

運行結果如下:

}MDJ0Z}BY8~_F1N7[Q{~FQ5.png

現在,將BaseModel中的addRecord方法聲明語句中的virtual去掉,再次運行結果如下:

G]K0P4Z%EW%D`TML(22S])3.png

通過將兩次運行結果進行對比,可以看出差異主要在BaseModel *basePtrOfUser = new UserModel;BaseModel &baseRefOfUser = user;定義的實例調用中。所以對於父類中的方法加virtual與不加virtual主要影響的就是使用父類聲明的指針或引用對象中。如果使用父類聲明的指針或引用對象是使用子類來實例化的。則其調用父類中含有virtual方法時,會調用實例化的子類中的該方法(如果子類中有重寫了這個方法的話)

 

2,多態的使用

在使用父類類型作爲函數的形參類型時,要注意傳遞的普通對象和指針及引用的區別。還是以上面的BaseModelUserModel爲例。先定義在BaseModel中聲明以下幾個函數:

    int addRecord(BaseBean );

    virtual int addRecordPrc(BaseBean ) = 0;

virtual int getList(std::list<BaseBean> *rtnList) = 0;

addRecord的定義如下:

int BaseModel::addRecord(BaseBean data)

{

    data.setCreateTime("2019-02-16");

    return this->addRecordPrc(data);

}

UserModel中對addRecordPrcgetList的定義如下:

int UserModel::addRecordPrc(BaseBean data)

{

    UserBean *user = static_cast<UserBean *>(&data);

       std::cout << user->getId() << std::endl;

std::cout << user->getCreateTime().c_str() << std::endl;

return 0;

}

 

int UserModel::getList(std::list<BaseBean> *rtnList)

{

    UserBean user1;

    user1.setId(1);

    user1.setName("user1");

    user1.setCreateTime("2019-02-15");

    rtnList->push_back(user1);

    UserBean user2;

    user2.setId(2);

    user2.setName("user2");

    user2.setCreateTime("2019-02-16");

    rtnList->push_back(user2);

return 0;

}

現在調用UserModel中的addRecord方法。

UserModel userModel;

    UserBean userBean;

    userBean.setId(1);

    userBean.setName("first user");

userModel.addRecord(userBean);

運行結果:

57WSJ{MUI%FXZE5D{%_9Q[3.png

現在,在addRecordPrc中加上std::cout << user->getName().c_str() << std::endl;再次運行程序,運行結果如下:

(1$2~{(62R2CS9Y{10~`~8S.png

可以看到,程序在運行到輸出username時崩潰了。這是爲什麼呢?

現在我們把addRecord的形參類型改爲BaseBean *,addRecordPrc的形參不變,調整代碼再次運行,發現結果與之前一樣。將addRecord的形參類型改爲BaseBean,而addRecordPrc的形參類型改爲BaseBean *,調整代碼再次運行,結果還是與之前一樣。將addRecordaddRecordPrc的形參類型都改爲BaseBean *,調整代碼再次運行程序。程序終於可以正常運行了。運行結果如下:

_SJ)YCFMF3V0UL15(3@{3@9.png

那這到底是爲什麼呢?爲什麼使用BaseBean指針就可以,而直接使用BaseBean對象就會導致程序崩潰呢?

addRecord的參數類型是BaseBean時,addRecord的參數變量data只能獲取到了設定的idcreate_time兩個屬性的值,即只是將調用時傳遞的參數userBean中的屬性的值拷貝給了data,且只拷貝了idcreate_time屬性,因爲data變量沒有name屬性,所以data變量不能獲取到設定的name屬性的值。這時,即使它被指針的方式傳遞給addRecordPrcaddRecordPrc的參數變量獲取到的name也是null。而當addRecord的參數類型是BaseBean *時,addRecord的參數變量data獲取到的是一個指向包含id,namecreate_time三個屬性值的內存的地址,所以,data能夠到獲取到這三個屬性的值。此時如果在addRecord中輸出name,是可以成功的。向addRecordPrc的參數傳遞與此相同。

接下來測試下getList方法:

    std::list<BaseBean> userList;

    userModel.getList(&userList);

    for(std::list<BaseBean>::iterator it = userList.begin(); it != userList.end();it++)

    {

        UserBean *u = static_cast<UserBean *>(&(*it));

        cout << u->getId() << endl;

        cout << u->getCreateTime().c_str() << endl;

        cout << u->getName().c_str() << endl;

}

運行結果:

LM6~UNP@8FBCI9UY%TIJDP9.png

程序崩潰的原因和調用addRecord時相同。調用getList方法時傳遞的userList中的元素只能獲取到從getList方法體中設定的元素的拷貝值。而userList的元素是BaseBean類型,所以只能獲取到idcreate_time屬性的值。將userList中的元素改爲BaseBean *,即將getList的參數類型改爲std::list<BaseBean *>*,調整程序,再次運行程序,運行結果爲:

~MRVBU]J[SWAB9TR~8G2ZCQ.png

程序正常。這裏要注意,改爲std::list<BaseBean *>*後,不能簡單的將原來的rtnList->push_back(user1);改爲rtnList->push_back(&user1);。這是因爲user1getList函數中的局部變量。當getList函數執行完畢後,user1的生命週期就結束了,它的內存空間將會被釋放。這時如果再執行上述調用,將會直接導致程序崩潰,而不是像上面那樣執行到getName才崩潰。正確的做法是使用new。即:UserBean *user1 = new UserBean;。使用new時,千萬不要忘記在調用完後調用delete釋放內存空間。如下所示:

    for(std::list<BaseBean *>::iterator it = userList.begin(); it != userList.end();it++)

    {

        UserBean *u = static_cast<UserBean *>((*it));

        cout << u->getId() << endl;

        cout << u->getCreateTime().c_str() << endl;

        cout << u->getName().c_str() << endl;

        delete u;

    }


源代碼路徑:https://pan.baidu.com/s/1-z5fCUkLTGvaGPXafC__Gg  提取碼:b7os

這份源代碼是基於Qt Creator寫的。如果需要直接編譯運行,可以通過Qt Creator直接打開源代碼。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章