1、为什么需要最好将析构函数设置为虚函数
构造函数的调用顺序: 先构造基类 再构造子类 析构函数的调用顺序: 析构的时候先析构子类在析构基类
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
};
class Child :public Base
{
public:
Child()
{
cout << "Child()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
};
int main(void)
{
Base *pb = new Child;
delete pb;
return 0;
}
输出结果为:
Base()
Child()
~Base()
现在将析构函数改为虚函数
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
virtual ~Base()
{
cout << "~Base()" << endl;
}
};
输出结果为:
Base()
Child()
~Child()
~Base()
分析:上面未加virtual的析构函数,在对象析构 delete pb的时候 少调用了一次~Child(),这个原因是c++的静态机制导致的,在未加virtual的情况 执行delete b的时候根据Base *b = new Child 左边的类型决定当前对象的类型,从而决定调用子类还是基类的析构函数,所以这里先执行基类的析构函数,导致子类的析构函数没有执行。反之在前方加上了virtual关键字后,执行delete b的时候根据Base *b = new Base右边的类型决定当前对象的类型,从而决定调用子类还是基类的析构函数,所以在执行delete b的时候先执行子类的析构函数,基类的析构函数自动执行。(只对加上了virtual关键字的函数有用,未加上virtual关键字的函数,相当于没有加入虚函数表,不存在这种动态机制,仍以左边的类型来决定调用子类还是基类的函数,如果当前子类的新增的函数,该指针无法调用子类的方法)
在有Base *b = new Child基类指针指向子类对象的情况,如何判断当前调用的是子类的方法还是基类的方法
首先看基类的该方法是否有virtal关键字,有则根据Base *b = new Child等式右边,调用的是Child类中的方法,否则则根据右边,调用的是Base 里面的方法。
2、为什么有时候要将虚函数设置为private权限
class Base
{
public:
Base();
private:
~Base();
};
int main(void) {
Base *b = new Base;
delete b; //报错 不可访问
return 0;
}
为了解决上面这个问题,同时不改变析构函数的访问权限,我们需要自定定义一个对象清理函数。正好利用报错这个性质,可以让提醒使用该类的用户,调用你提供的清理函数进行清理。
class Base
{
public:
Base();
void Clear(); //自己提供的类清理函数
private:
~Base();
};
int main(void) {
Base *b = new Base;
delete b; //报错 不可访问
return 0;
}
3、模板类和类模板的区别
类模板是一个模板,模板类是一个类。(模板类是类模板实例化后的一个产物,类模板比作是一个作饺子的模子,而模板类就是用这个模子做出来的饺子,至于饺子什么馅儿的就需要你自己去实例化自己的内容。)
4.、为什么有时候要禁止拷贝构造 赋值函数
关于拷贝构造函数的禁用原因,主要是两个原因。第一是浅拷贝问题(也就是说的双杀问题),第二 个则是基类拷贝问题。
先看看第一个浅拷贝的原因(双杀)
class Base
{
public:
Base()
{
cout << "Base()" << endl;
_buf = new char[20];
}
~Base()
{
cout << "~Base()" << endl;
delete[] _buf;
}
private:
char * _buf;
};
int main(void)
{
//
//Base b; //调用一次构造函数
//Base b2(b); //默认拷贝构造函数函数
//return 0; //return 0 执行结束后报错
//
Base*b = new Base;
Base*b2 = new Base(*b);
delete b;
delete b2; //执行到这步出错
return 0;
}
分析:导致上面程序执行出错的原因:在执行Base b2(b)或 Base*b2 = new Base(*b),程序会默认执行Base的拷贝构造函数
实际上默认的拷贝构造函数 会直接复制buf_这个指针给要拷贝的对象
导致两个Base
对象的_buf指向同一个内存区,这会导致析构的时候两次删除同一片区域的问题(这个问题也就是双杀
问题)。
要解决这个问题我们有两个办法
1、禁止拷贝构造函数和赋值函数 当然这样的类不允许进行拷贝 赋值操作
class Base
{
public:
Base()
{
cout << "Base()" << endl;
_buf = new char[20];
}
Base(const Base &)=delete; //禁用
Base& operator=(cosnt Base &) = delete;//禁用
~Base()
{
cout << "~Base()" << endl;
delete[] _buf;
}
private:
//或设置权限为private
Base(const Base &); //禁用
Base& operator=(cosnt Base &) ;//禁用
char * _buf;
};
第二种办法 就需要我们自己定义拷贝构造函数和赋值函数
在自己实现的拷贝构造函数和赋值函数中对涉及到内存分配分指针进行内存分配后将要拷贝对象中指针指向的内存的那内容拷贝到新对象中;
Base::Base(const Base& base)
{
int length = strlen(base._buf);
_buf= new char[length+1];
strcpy_s(m_pData, length + 1, base._buf);
}
5、为什么有时候基类类型的指针指向子类对象 但无法掉用子的类的新增方法
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
void kill()
{
}
virtual ~Base()
{
cout << "~Base()" << endl;
}
};
class Child :public Base
{
public:
Child()
{
cout << "Child()" << endl;
}
virtual void set() //新增方法
{
cout << " Child Set()" << endl;
}
~Child()
{
cout << "~Child()" << endl;
}
};
int main(void)
{
Base *pb = new Child;
pb->set();//这里报错 "Base"没有成员 set();
delete pb;
return 0;
}
分析 :C++在对没有virtual关键字申明的成员函数进行编译时,默认处理方式为静态机制, Base *pb = new Child; 这里对set()函数而言,当前对象的类型实际为Base 所以会报错 。实际的类型Base *pb = new Child 默认都是左边 只用在调用的函数为虚函数时候,才看Base *pb = new Child 右边对象类型,这里注意的是对象实际类型跟基类的函数是否加入动态机制有关,否则都是以等号左边的为主;
解决办法:
1、在基类定义该方法 2、在基类定义该方法生命为纯虚函数 3、在基类定义该方法申明为虚函数