NO.12|C++面向对象|虚方法|拷贝构造函数|权限|抽象类|多态|虚析构函数|虚基类|拷贝赋值|移动赋值|类模板|模板类|虚函数表(C++)

![[Pasted image 20250323103235.png]]

请问构造函数中的能不能调用虚方法

参考回答
不要在构造函数中调用虚方法,从语法上讲,调用完全没有问题,但是从效果上看,往往不能达到需要的目的。
派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。
同样,进入基类析构函数时,对象也是基类类型。
所以,虚函数始终仅仅调用基类的虚函数(如果是基类调用虚函数),不能达到多态的效果,所以放在构造函数中是没有意义的,而且往往不能达到本来想要的效果

请问拷贝构造函数的参数是什么传递方式,为什么

参考回答

  1. 拷贝构造函数的参数必须使用引用传递
  2. 如果拷贝构造函数中的参数不是一个引用,即形如CClass(const CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数。因此拷贝构造函数的参数必须是一个引用。
    需要澄清的是,传指针其实也是传值,如果上面的拷贝构造函数写成CClass(const CClass* c_class),也是不行的。事实上,只有传引用不是传值外,其他所有的传递方式都是传值

说说类方法和数据的权限有哪几种

参考回答

  1. C++通过 public、protected、private 三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符
关键字权限
public可以被任意实体访问
protected只允许子类及本类的成员函数访问
private只允许本类的成员函数访问
  1. 下面介绍一个例子。
    父类:
class Person  
{ 
public:  
	Person(const string& name, int age) : m_name(name), m_age(age)  
	{ 
	} 
	void ShowInfo()  
	{  
		cout << "姓名:" << m_name << endl;
		cout << "年龄:" << m_age << endl;  
	}  
protected:  
	string m_name; //姓名  
private:  
	int m_age; //年龄  
};

子类:

class Teacher : public Person  
{ 
public:  
	Teacher(const string& name, int age, const string& title)  
		: Person(name, age), m_title(title)  
	{ 
	} 
	void ShowTeacherInfo()  
	{  
		ShowInfo(); //正确,public属性子类可见  
		cout << "姓名:" << m_name << endl; //正确,protected属性子类可见  
		cout << "年龄:" << m_age << endl; //错误,private属性子类不可见  
		cout << "职称:" << m_title << endl; //正确,本类中可见自己的所有成员  
	}  
private:  
	string m_title; //职称  
};

调用方:

void test()  
{  
	Person person("张三", 22);  
	person.ShowInfo(); //public属性,对外部可见  
	cout << person.m_name << endl; //protected属性,对外部不可见  
	cout << person.m_age << endl; //private属性,对外部不可见  
}

如何理解抽象类

参考回答

  1. 抽象类的定义如下:
    纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”,有虚函数的类就叫做抽象类
  2. 抽象类有如下几个特点:
    1)抽象类只能用作其他类的基类,不能建立抽象类对象。
    2)抽象类不能用作参数类型、函数返回类型或显式转换的类型。
    3)可以定义指向抽象类的指针和引用,此指针可以指向它的派生类,进而实现多态性

什么是多态?除了虚函数,还有什么方式能实现多态

参考回答

  1. 多态是面向对象的重要特性之一,它是一种行为的封装,就是不同对象对同一行为会有不同的状态。(举例 : 学生和成人都去买票时,学生会打折,成人不会)
  2. 多态是以封装和继承为基础的。在C++中多态分为静态多态(早绑定)和动态多态(晚绑定)两种,其中动态多态是通过虚函数实现,静态多态通过函数重载实现,代码如下
class A  
{ 
public:  
	void do(int a);  
	void do(int a, int b);  
};

简述一下虚析构函数,什么作用

参考回答
  1. 虚析构函数,是将基类的析构函数声明为virtual,举例如下
class TimeKeeper  
{ 
public:  
	TimeKeeper() {}  
	virtual ~TimeKeeper() {}  
};
  1. 虚析构函数的主要作用是防止内存泄露。
    定义一个基类的指针p,在delete p时,如果基类的析构函数是虚函数,这时只会看p所赋值的对象,如果p赋值的对象是派生类的对象,就会调用派生类的析构函数(毫无疑问,在这之前也会先调用基类的构造函数,在调用派生类的构造函数,然后调用派生类的析构函数,基类的析构函数,所谓先构造的后释放);如果p赋值的对象是基类的对象,就会调用基类的析构函数,这样就不会造成内存泄露。
    如果基类的析构函数不是虚函数,在delete p时,调用析构函数时,只会看指针的数据类型,而不会去看赋值的对象,这样就会造成内存泄露
答案解析

我们创建一个TimeKeeper基类和一些及其它的派生类作为不同的计时方法

class TimeKeeper  
{ 
public:  
	TimeKeeper() {}  
	~TimeKeeper() {} //非virtual的  
};  
//都继承与TimeKeeper  
class AtomicClock :public TimeKeeper{};  
class WaterClock :public TimeKeeper {};  
class WristWatch :public TimeKeeper {};

如果客户想要在程序中使用时间,不想操作时间如何计算等细节,这时候我们可以设计factory(工厂)函数,让函数返回指针指向一个计时对象。该函数返回一个基类指针,这个基类指针是指向于派生类对象的

TimeKeeper* getTimeKeeper()  
{  
	//返回一个指针,指向一个TimeKeeper派生类的动态分配对象  
}

因为函数返回的对象存在于堆中,因此为了在不使用时我们需要使用释放该对象(delete)

TimeKeeper* ptk = getTimeKeeper();  
delete ptk;

此处基类的析构函数是非virtual的,因此通过一个基类指针删除派生类对象是错误的
解决办法: 将基类的析构函数改为virtual就正确了

class TimeKeeper  
{ 
public:  
	TimeKeeper() {}  
	virtual ~TimeKeeper() {}  
};

声明为virtual之后,通过基类指针删除派生类对象就会释放整个对象(基类+派生类)

说说什么是虚基类,可否被实例化

参考回答

  1. 在被继承的类前面加上virtual关键字,这时被继承的类称为虚基类,代码如下:
class A  
class B1:public virtual A;  
class B2:public virtual A;  
class D:public B1,public B2;
  1. 虚继承的类可以被实例化,举例如下
class Animal {/* ... */ };  
class Tiger : virtual public Animal { /* ... */ };  
class Lion : virtual public Animal { /* ... */ }
int main( )  
{  
	Liger lg;  
	/*既然我们已经在Tiger和Lion类的定义中声明了"virtual"关键字,于是下面的代码编译OK  
	*/  
	int weight = lg.getWeight();  
}

简述一下拷贝赋值和移动赋值

参考回答

  1. 拷贝赋值是通过拷贝构造函数来赋值,在创建对象时,使用同一类中之前创建的对象来初始化新创建的对象。
  2. 移动赋值是通过移动构造函数来赋值,二者的主要区别在于
    1)拷贝构造函数的形参是一个左值引用,而移动构造函数的形参是一个右值引用;
    2)拷贝构造函数完成的是整个对象或变量的拷贝,而移动构造函数是生成一个指针指向源对象或变量的地址,接管源对象的内存,相对于大量数据的拷贝节省时间和内存空间

仿函数了解吗?有什么作用

参考回答

  1. 仿函数(functor)又称为函数对象(function object)是一个能行使函数功能的类。仿函数的语法几乎和我们普通的函数调用一样,不过作为仿函数的类,都必须重载operator()运算符,举个例子:
class Func{  
public:  
	void operator() (const string& str) const {  
		cout<<str<<endl;  
	}  
};  

Func myFunc;  
myFunc("helloworld!");  

>>>helloworld!
  1. 仿函数既能想普通函数一样传入给定数量的参数,还能存储或者处理更多我们需要的有用信息。我们可以举个例子:
    假设有一个 vector<string> ,你的任务是统计长度小于5的string的个数,如果使用 count_if函数的话,你的代码可能长成这样:
bool LengthIsLessThanFive(const string& str) {  
	return str.length()<5;  
} 
int res=count_if(vec.begin(), vec.end(), LengthIsLessThanFive);

其中 count_if 函数的第三个参数是一个函数指针,返回一个bool类型的值。一般的,如果需要将特定的阈值长度也传入的话,我们可能将函数写成这样:

bool LenthIsLessThan(const string& str, int len) {  
	return str.length()<len;  
}

这个函数看起来比前面一个版本更具有一般性,但是他不能满足 count_if 函数的参数要求:
count_if 要求的是unary function(仅带有一个参数)作为它的最后一个参数。如果我们使用仿函数,是不是就豁然开朗了呢

class ShorterThan {  
public:  
	explicit ShorterThan(int maxLength) : length(maxLength) {}  
	bool operator() (const string& str) const {  
		return str.length() < length;  
	}  
private:  
	const int length;  
};

C++ 中哪些函数不能被声明为虚函数

常见的不不能声明为虚函数的有:普通函数(非成员函数),静态成员函数,内联成员函数,构造函数,友元函数。

  1. 为什么C++不支持普通函数为虚函数?
    普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时绑定函数。
  2. 为什么C++不支持构造函数为虚函数?
    这个原因很简单,主要是从语义上考虑,所以不支持。因为构造函数本来就是为了明确初始化对象成员才产生的,然而virtual function主要是为了再不完全了解细节的情况下也能正确处理对象。
    另外,virtual函数是在不同类型的对象产生不同的动作,现在对象还没有产生,如何使用virtual函数来完成你想完成的动作。(这不就是典型的悖论)
    构造函数用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成,所以不能将构造函数定义为虚函数
  3. 为什么C++不支持内联成员函数为虚函数?
    其实很简单,那内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的绑定函数)
    内联函数是在编译时期展开,而虚函数的特性是运行时才动态联编,所以两者矛盾,不能定义内联函数为虚函数
  4. 为什么C++不支持静态成员函数为虚函数?
    这也很简单,静态成员函数对于每个类来说只有一份代码,所有的对象都共享这一份代码,他也没有要动态绑定的必要性。
    静态成员函数属于一个类而非某一对象,没有this指针,它无法进行对象的判别
  5. 为什么C++不支持友元函数为虚函数?
    因为C++不支持友元函数的继承,对于没有继承特性的函数没有虚函数的说法

解释下 C++ 中类模板和模板类的区别

参考回答
  1. 类模板是模板的定义,不是一个实实在在的类,定义中用到通用类型参数
  2. 模板类是实实在在的类定义,是类模板的实例化。类定义中参数被实际类型所代替。
答案解析
  1. 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如
    template <class T1,class T2>class someclass{…};在定义对象时分别代入实际的类型名,如 someclass<int,double> obj;
  2. 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。
  3. 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

虚函数表里存放的内容是什么时候写进去的

参考回答
  1. 虚函数表是一个存储虚函数地址的数组,以NULL结尾。虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的 vfptr,然后调用构造函数。即:虚表在构造函数之前写入
  2. 除了在构造函数之前写入之外,我们还需要考虑到虚表的二次写入机制,通过此机制让每个对象的虚表指针都能准确的指向到自己类的虚表,为实现动多态提供支持
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值