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直接打开源代码。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。