1,父類中的方法加virtual與不加virtual的區別
先定義兩個bean。BaseBean和UserBean。UserBean繼承於BaseBean。BaseBean中含有兩個屬性id(int類型)和create_time(std::string類型)。UserBean中含有屬性name(std::string類型)。定義兩個類BaseModel和UserModel。UserModel繼承於BaseModel。BaseModel中定義了一個方法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);
運行結果如下:
現在,將BaseModel中的addRecord方法聲明語句中的virtual去掉,再次運行結果如下:
通過將兩次運行結果進行對比,可以看出差異主要在BaseModel *basePtrOfUser = new UserModel;和BaseModel &baseRefOfUser = user;定義的實例調用中。所以對於父類中的方法加virtual與不加virtual主要影響的就是使用父類聲明的指針或引用對象中。如果使用父類聲明的指針或引用對象是使用子類來實例化的。則其調用父類中含有virtual方法時,會調用實例化的子類中的該方法(如果子類中有重寫了這個方法的話)。
2,多態的使用
在使用父類類型作爲函數的形參類型時,要注意傳遞的普通對象和指針及引用的區別。還是以上面的BaseModel和UserModel爲例。先定義在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中對addRecordPrc和getList的定義如下:
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);
運行結果:
現在,在addRecordPrc中加上std::cout << user->getName().c_str() << std::endl;再次運行程序,運行結果如下:
可以看到,程序在運行到輸出user的name時崩潰了。這是爲什麼呢?
現在我們把addRecord的形參類型改爲BaseBean *,addRecordPrc的形參不變,調整代碼再次運行,發現結果與之前一樣。將addRecord的形參類型改爲BaseBean,而addRecordPrc的形參類型改爲BaseBean *,調整代碼再次運行,結果還是與之前一樣。將addRecord和addRecordPrc的形參類型都改爲BaseBean *,調整代碼再次運行程序。程序終於可以正常運行了。運行結果如下:
那這到底是爲什麼呢?爲什麼使用BaseBean指針就可以,而直接使用BaseBean對象就會導致程序崩潰呢?
當addRecord的參數類型是BaseBean時,addRecord的參數變量data只能獲取到了設定的id和create_time兩個屬性的值,即只是將調用時傳遞的參數userBean中的屬性的值拷貝給了data,且只拷貝了id和create_time屬性,因爲data變量沒有name屬性,所以data變量不能獲取到設定的name屬性的值。這時,即使它被指針的方式傳遞給addRecordPrc,addRecordPrc的參數變量獲取到的name也是null。而當addRecord的參數類型是BaseBean *時,addRecord的參數變量data獲取到的是一個指向包含id,name和create_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;
}
運行結果:
程序崩潰的原因和調用addRecord時相同。調用getList方法時傳遞的userList中的元素只能獲取到從getList方法體中設定的元素的拷貝值。而userList的元素是BaseBean類型,所以只能獲取到id和create_time屬性的值。將userList中的元素改爲BaseBean *,即將getList的參數類型改爲std::list<BaseBean *>*,調整程序,再次運行程序,運行結果爲:
程序正常。這裏要注意,改爲std::list<BaseBean *>*後,不能簡單的將原來的rtnList->push_back(user1);改爲rtnList->push_back(&user1);。這是因爲user1是getList函數中的局部變量。當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直接打開源代碼。