C++复习笔记13

news2024/10/6 20:41:08

多态:具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。(同一个接口根据调用对象不同产生不同的行为)。两个条件:

1. 必须通过基类的指针或者引用调用虚函数。
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

#include<iostream>
using namespace std;

class A
{
public:
	virtual void Eat()//不加虚调用父类的加了虚再调动子类的
	{
		cout <<"A::Eat()" << endl;
	}

    virtual void Foot()
	{
		cout << "A::Foot()" << endl;
	}

	virtual void Sleep()
	{
		cout << "A::Sleep()" << endl;
	}
};

class Person :public A
{
public:

	void Eat()
	{
		cout << "Person::Eat()" << endl;
	}

	void Foot()
	{
		cout << "Person::Foot()" << endl;
	}

	void Sleep()
	{
		cout << "Person::Sleep()" << endl;
	}
};

class Bird :public A
{
public:

	void Eat()
	{
		cout << "Bird::Eat()" << endl;
	}

	void Foot()
	{
		cout << "Bird::Foot()" << endl;
	}

	void Sleep()
	{
		cout << "Bird::Sleep()" << endl;
	}
};

class Dog :public A
{
public:

	void Eat()
	{
		cout << "Dog::Eat()" << endl;
	}

	void Foot()
	{
		cout << "Dog::Foot()" << endl;
	}

	void Sleep()
	{
		cout << "Dog::Sleep()" << endl;
	}
};

//class Car
//{
//private:
//	Wheel two[2];
//	Door four[4];
//};

void Active(A* pa)
{
	pa->Eat();
	pa->Foot();
	pa->Sleep();
}

void test01()
{
	Person p;
	p.Eat();
	Bird b;
	b.Eat();
	Dog d;
	d.Eat();
}

void test02()
{
	Person p;
	Bird b;
	Dog d;
	Active(&p);
}

void main()
{
	test02();
	system("pause");
}

切片原则:父类指针或引用指向子类对象,假如没有构成重写,只要没构成重写就调用父类。因为父类指针或者引用只能调用对象在中的父类部分,调不动子类部分。拿子类对象赋值给父类对象也适用切片原则,只能调动父类的成员。

#include<iostream>
using namespace std;

class Base
{
public:
	void fun()
	{
		cout << "Base::fun()" << endl;
	}

private:

	int m_a;
	int m_b;
};

class D :public Base
{
public:
	//既然改写了就说明父类的属性不合适子类,所以隐藏了
	void fun()
	{
		cout << "D::fun()" << endl;
	}
	void show()
	{
		cout << "D::show()" << endl;
	}

	void fun(int a)
	{
		cout << "D::fun(int)" << endl;
	}
private:
	int m_c;
};

//这里就是按切片原理进行理解
void test01()
{
	D d;
	Base* pb = &d;
	pb->fun();//父子类都有,调父类
	//pb->show();//调不动子类特有的
	//pb->fun(1);//调不动子类特有的
}

void main()
{
	test01();
	system("pause");
}

虚函数重写要求:
1.父子类的对应成员函数都是虚函数,子类可以不写virtual。

2.三同:返回值、函数名和参数列表必须相同。

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void fun()
	{
		cout << " this is Base::fun()" << endl;
	}
};

class D :public Base
{
public:
	//重写 返回值 函数名 参数相同 三同
	void fun(int a)
	{
		cout << "this is D::fun()" << endl;
	}
};

void test01()
{
	//父类指针或者引用指向子类对象
	D d;
	Base* pb = new D;
	Base& refb = d;

	pb->fun();//非虚调父类,虚调子类
	refb.fun();//非虚调父类,虚调子类
}

void test02()
{
	Base b;
	D d;
	b = d;
	b.fun();//父类对象指向子类对象调父类方法,与虚不虚无关
}

void main()
{
	test01();
	system("pause");
}

       通过计算带虚函数的类的大小,可以证明虚表指针的存在。32位系统下指针大小为4字节,64为系统下指针大小为8字节。

#include<iostream>
using namespace std;
class Base
{
public:
	virtual void fun()
	{
		cout << " this is Base::fun()" << endl;
	}

	 virtual void fun1()
	{
		cout << "this is Base::fun1()" << endl;
	}

	 virtual void fun2()
	{
		cout << "this is Base::fun2()" << endl;
	}
private:
	//_vfptr//虚表指针是每个对象的第一个成员,系统为对象添加的隐藏成员
	int m_a;
};

void test01()
{
	Base b;
	cout << sizeof(Base) << endl;
}

void main()
{
	test01();
	system("pause");
}

       子类的虚函数可以不写virtual,但是这样不规范,父类的虚函数在整个继承体系中,只要重写了它一直是虚函数。

#include<iostream>
using namespace std;
class Base
{
public:               //子类的虚函数可以不写virtual,但是这样不规范
	virtual void fun()//父类的虚函数在整个继承体系中,只要重写了它一直是虚函数
	{
		cout << " this is Base::fun()" << endl;
	}

	 virtual void fun1()
	{
		cout << "this is Base::fun1()" << endl;
	}

private:
	//_vfptr//虚表指针是每个对象的第一个成员,系统为对象添加的隐藏成员
	int m_a;
};

class D :public Base
{
	virtual void fun()
	{
		cout << " this is D::fun()" << endl;
	}
};

class D1 :public Base
{
	virtual void fun()
	{
		cout << " this is D1::fun()" << endl;
	}
};

void test01()
{
	Base* pb = new D;
	pb->fun();

	Base* pb1 = new D1;//不同子类的重写
	pb1->fun();
	
	pb->fun1();//没重写,调父类
}

void main()
{
	test01();
	system("pause");
}
#include<iostream>
using namespace std;

class Base
{
public:               //子类的虚函数可以不写virtual,但是这样不规范
	virtual void fun()//父类的虚函数在整个继承体系中,只要子类重写了它一直是虚函数
	{
		cout << "this is Base::fun()" << endl;
	}

	virtual void fun1()
	{
		cout << "this is Base::fun1()" << endl;
	}

private:
	//_vfptr//虚表指针是每个对象的第一个成员,系统为对象添加的隐藏成员
	int m_a;
};

class D :public Base
{
public:
    void fun()
	{
		cout << "this is D::fun()" << endl;
	}

	void fun(int a)
	{
		cout << "D::fun(int)" << endl;
	}
};

class C :public D
{
public:
    void fun()
	{
		cout << "this is C::fun()" << endl;
	}

	void fun(int a)
	{
		cout << "C::fun(int)" << endl;
	}
};

void test01()
{
	D* pd = new C;//说明虽然没有写出virtual 但是D类中的fun显然是虚函数
	pd->fun();
	pd->fun(1);//没有构成虚函数重写,所以调用的是父类的
}

void main()
{
	test01();
	system("pause");
}

多态的两个特例:

1.协变:父类和子类的对应成员函数以其类型的引用或指针作为返回值,也可以构成虚函数重写从而实现多态。

2.虚析构函数的重写,析构函数设置为虚函数是为了防止子类对象在堆区开辟空间时,父类指针指向new出来的子类对象,delete父类指针时只能调用父类的析构函数而无法调用子类的析构函数,从而导致子类开辟在堆区的空间无法释放。

父类的构造函数没有写成虚函数,调用父类的析构函数而没有调动子类的析构函数。只对对象中的父类成员进行了释放和销毁,而没有对子类部分的数据进行释放和销毁。所以要求父类的析构函数为虚函数。

3.编译后析构函数的名称统一处理成destructor,所以才能构成重写。

#include<iostream>
using namespace std;

class Base
{
public:
	Base()//父类的构造函数不能是虚函数
	{
		cout << "Base::Base()" << endl;
	}

	virtual ~Base()//编译后析构函数的名称统一处理成destructor
	{
		cout << "Base::~Base()" << endl;
	}

public:               //子类的虚函数可以不写virtual,但是这样不规范
	virtual void fun()//父类的虚函数在整个继承体系中,只要子类重写了它一直是虚函数
	{
		cout << "this is Base::fun()" << endl;
	}

private:
	//_vfptr//虚表指针是每个对象的第一个成员,系统为对象添加的隐藏成员
	int m_a;
};

class D :public Base
{
public:

	D()
	{
		cout << "D::D()" << endl;
	}

    void fun()
	{
		cout << "this is D::fun()" << endl;
	}

	~D()
	{
		cout << "D::~D()" << endl;
	}
};

void test01()
{
	//D d;//正常构造析构
	D* pd = new D;//正常调用父子类的构造
	delete pd;//正常调动父子类析构
}

void test02()
{
	Base* pb = new D;
	delete pb;//父类的构造函数没有写成虚函数,调用父类的析构函数而没有调动子类的析构函数
	//只对对象中的父类成员进行了释放和销毁,而没有对子类部分的数据进行释放和销毁
	//所以要求父类的析构函数为虚函数
}

void main()
{
	test02();
	system("pause");
}

        多态机制在父类的构造函数中不起作用 :即在父类的构造函数用this指针调重写的父类接口,执行的还是父类的虚函数。

#include<iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		cout << "Base::Base()" << endl;
		this->fun();//多态机制在父类构造函数中不起作用,这里调父类的fun()
		//这里对象都没有构造完成,不可能达到多态(不可能调动子类方法)
	}

	void list()
	{
		this->fun();//调动子类的fun()
	}

	virtual ~Base()
	{
		cout << "Base::~Base()" << endl;
	}

public:            
	virtual void fun()
	{
		cout << "this is Base::fun()" << endl;
	}

private:
	int m_a;
};

class D :public Base
{
public:

	D()
	{
		cout << "D::D()" << endl;
	}

	void fun()
	{
		cout << "this is D::fun()" << endl;
	}

	~D()
	{
		cout << "D::~D()" << endl;
	}
};

void test01()
{
	D d;
}

void test02()
{
	D d;
	d.list();
}

void main()
{
	test02();
	system("pause");
}

final关键字:写在需要构成重写的父类虚函数的后面,表示这个虚函数不能构成重写,假如这个虚函数在子类中构成重写,则会报错。

#include<iostream>
using namespace std;
class Car
{
public:
	virtual void Drive() final {}//final 修饰虚函数终结了这个虚函数的后续继承 防止重写
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};

void test01()
{
	Car* pc = new Benz;
	pc->Drive();
}

void main()
{
	test01();
	system("pause");
}

override关键字:写在子类虚函数的后面,表示这个虚函数必须构成重写。假如这个虚函数没有构成重写则会报错。

#include<iostream>
using namespace std;

class Car {
public:
	virtual void Drive() {}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }//与父类对比看子类没有重写这个虚函数就会报错
	virtual void Drive(int a) override { cout << "Benz-舒适" << endl; }//子类没有重写这个虚函数就会报错
	virtual void Drive(int a, int b) { cout << "Benz-舒适" << endl; }
};

void test01()
{
	Car* pc = new Benz;
	pc->Drive();
}

void main()
{
	test01();
	system("pause");
}

 观察底层的虚表可以发现子类的虚函数地址覆盖了父类的虚函数地址。

#include<iostream>
using namespace std;

class Base
{ 
public:

	Base()
	{
		cout << "Base::Base()" << endl;
	}

	virtual void fun()
	{
		cout << "Base::fun()" << endl;
	}

	void fun(int a) {
		cout << "Base::fun(int)" << endl;
	}//重载

	virtual void list()
	{
		cout << "Base::list()" << endl;
	}

    void print()
	{
		cout << "Base::print()" <<endl;
	}
private:
	int m_base = 1;
};

class D :public Base
{
public:

	D()
	{
		cout << "D::D()" << endl;
	}

	void fun(int a){}//同名函数隐藏

	virtual void fun()//重写(覆盖)
	{
		cout << "D::fun()" << endl;
	}
};

void test01()
{
	D d;
	Base* pb = &d;
	pb->fun(1);

}

void main()
{
	test01();
	system("pause");
}

父类必须写成析构的例子,假如不构成重写,会造成内存泄漏。

#include<iostream>
#include<vld.h>
using namespace std;

//父类不使用虚析构时不是说delete只释放了子类中的父类空间(没有释放子类对象空间)而是子类成员在堆上申请的空间
//有可能没有机会释放

class Base
{
public:

	Base()
	{
		cout << "Base::Base()" << endl;
	}
	virtual void fun(int a) final//这个函数不重写不行 不然编译报错
	{
		cout << "Base::fun(int)" << endl;
	}

	virtual void fun()
	{
		cout << "Base::fun()" << endl;
	}

	virtual ~Base()
	{
		cout << "Base::~Base()" << endl;
	}
};

class D :public Base
{
public:

	D()
	{
		m_data = new char[10];
		cout << "D::D()" << endl;
	}

	void fun() override//这个函数不构成重写不行,不然编译报错
	{
		cout << "D::fun()" << endl;
	}

	~D()
	{
		delete[] m_data;
		cout << "D::D()" << endl;
	}
private:
	char* m_data;
};

void test01()
{
	Base* pb = new D;
	delete pb;
}

void main()
{
	test01();
	system("pause");
}

虚指针例题:

#include<iostream>
using namespace std;

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Func2()" << endl;
	}
private:
	int _b = 1;
};

void main()
{
	cout << sizeof(Base) << endl;
	Base b;
	system("pause");
}

一个类对应一张虚函数表,同一个类下的不同对象共用一张虚函数表。

 

#include<iostream>
using namespace std;
class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}

		virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()
	{
		cout << "Base::Func3()" << endl;
	}
private:
	int _b = 1;
};
class Derive : public Base
{
public:
	virtual void Func1()
	{
		cout << "Derive::Func1()" << endl;
	}
    
	virtual void func3()
	{
		cout << "Derive::Func3()" << endl;
	}

private:
	int _d = 2;
};
int main()
{
	Base b1;
	Base b2;
	Derive d1;
	Derive d2;
	d1.Func1();
	return 0;
}

纯虚函数和抽象类:

1.含有纯虚函数的类叫做抽象类。

2.抽象类不能实例化对象。

3.子类继纯虚函数但不重写(即使只有一个没有重写)就还是抽象类 不能实例化对象。

编译时多态:函数地址早绑定,在编译时就能确定函数的调用关系,编译时底层改写了函数名。

运行时多态:函数地址晚绑定,运行时才能确定函数的调用关系,编译时只能确定是父类的指针在调用这个接口。

#include<iostream>
using namespace std;

class A
{
public:
	virtual void Eat()
	{
		cout << "A::Eat()" << endl;
	}

	virtual void Sleep()
	{
		cout << "A::Sleep()" << endl;
	}

	virtual void Foot()
	{
		cout << "A::Foot()" << endl;
	}
};

class Bird:public A
{
public:
	virtual void Eat()
	{
		cout << "Bird::Eat()" << endl;
	}

	virtual void Sleep()
	{
		cout << "Bird::Sleep()" << endl;
	}

	virtual void Foot()
	{
		cout << "Bird::Foot()" << endl;
	}
};

class Dog:public A
{
public:
	virtual void Eat()
	{
		cout << "Dog::Eat()" << endl;
	}

	virtual void Sleep()
	{
		cout << "Dog::Sleep()" << endl;
	}

	virtual void Foot()
	{
		cout << "Dog::Foot()" << endl;
	}
};


//运行时多态
void Active(A* pa)//传谁就调谁,在实现这里时有可能还不知道具体要调谁即main函数啥样
{
	pa->Eat();
	pa->Foot();
	pa->Sleep();
}

void main()
{
	Dog dog;
	Bird b;
	Active(&b);
}

重载、覆盖和隐藏的对比:重载应该是函数名相同参数列表不同

多态底层原理:

 通过玩指针来得到虚函数表的各个函数指针:

先取b的地址,强转成一个int*的指针。再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针(虚表地址)。

虚函数表是一个函数指针数组,所以虚表地址可以看做首元素(第一个函数指针)的地址。

*(int*)(*(int*)&b)强转为int*再解引用得到前四个字节的值,再强转成pFun(函数指针)类型就得到了第一个虚函数指针。函数名和函数指针作用相同。

按照int*大小进行偏移再解引用就可以得到数组中其他元素的值,在进行强转得到其他虚函数的指针,按照函数名调用函数。

#include<iostream>
using namespace std;

typedef void(*pFun)();

class Base
{
public:
	virtual void f()
	{
		cout << "Base::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base::h()" << endl;
	}
private:
	int m_a=0;
	int m_b=1;
};

void main()
{
	Base b;
	//cout << "对象的地址:" << &b << endl;
	//cout << "虚表的地址:" << *(int*)&b << endl;//(int*)&b截取前四个字节是所保存的虚指针的地址,解引用获得指向 这里强转再解引用是整形
	printf("虚表的地址:0x%p\n", *(int*)&b);
	printf("对象的地址:0x%p\n", &b);

	/*pFun pfun;
	pfun =(void(*)()) (*(int*)&b);
	pfun();*/
	pFun pfun;
	pfun = (pFun)(*((int*)(*(int*)&b)+0));
	pfun();

	pfun = (pFun)(*((int*)(*(int*)&b)+1));
	pfun();

	pfun = (pFun)(*((int*)(*(int*)&b) +2));
	pfun();

动态绑定和静态绑定:

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。

单继承和多继承时不同情况下的虚表结构:

单继承无虚函数覆盖:子类拷贝一份父类虚表,子类独有的虚函数指针按声明顺序追加到拷贝的表后面。

#include<iostream>
using namespace std;

class Base
{
	public:
		virtual void f()
		{
			cout << "Base::f()" << endl;
		}
	
		virtual void g()
		{
			cout << "Base::g()" << endl;
		}
	
		virtual void h()
		{
			cout << "Base::h()" << endl;
		}
};

class D:public Base
{
public:
	virtual void f1()
	{
		cout << "D::f1()" << endl;
	}

	virtual void g1()
	{
		cout << "D::g1()" << endl;
	}

	virtual void h1()
	{
		cout << "D::h1()" << endl;
	}
};


void main()
{
	D d;
}

单继承有虚函数覆盖:子类拷贝一份父类续表,子类重写父类的虚函数的指针会在对应位置进行覆盖,子类特有的虚函数按照声明顺序追加在这张虚函数表后面。

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void f()
	{
		cout << "Base::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base::h()" << endl;
	}
};

class D :public Base
{
public:
	virtual void f()
	{
		cout << "D::f()" << endl;
	}

	virtual void g1()
	{
		cout << "D::g1()" << endl;
	}

	virtual void h1()
	{
		cout << "D::h1()" << endl;
	}
};

void main()
{
	D d;
}

多继承无虚函数覆盖:子类从每一个父类继承一张虚函数表,子类特有的虚函数的指针按声明顺序只追加在第一张虚表后面。

#include<iostream>
using namespace std;

class Base1
{
public:
	virtual void f()
	{
		cout << "Base1::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base1::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base1::h()" << endl;
	}
};

class Base2
{
public:
	virtual void f()
	{
		cout << "Base2::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base2::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base2::h()" << endl;
	}
};

class Base3
{
public:
	virtual void f()
	{
		cout << "Base3::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base3::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base3::h()" << endl;
	}
};

class D :public Base1,public Base2, public Base3
{
public:
	virtual void f1()
	{
		cout << "D::f1()" << endl;
	}

	virtual void g1()
	{
		cout << "D::g1()" << endl;
	}

	virtual void h1()
	{
		cout << "D::h1()" << endl;
	}
};

void main()
{
	D d;
}

 多继承有虚函数覆盖覆盖:子类从每一个父类继承一张虚函数表,子类重写的父类虚函数的函数指针会在对应父类的虚函数表中的对应位置进行覆盖,子类特有的虚函数的指针按声明顺序只追加在第一张虚表后面。

#include<iostream>
using namespace std;

class Base1
{
public:
	virtual void f()
	{
		cout << "Base1::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base1::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base1::h()" << endl;
	}
};

class Base2
{
public:
	virtual void f()
	{
		cout << "Base2::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base2::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base2::h()" << endl;
	}
};

class Base3
{
public:
	virtual void f()
	{
		cout << "Base3::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base3::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base3::h()" << endl;
	}
};

class D :public Base1,public Base2, public Base3
{
public:
	virtual void f()
	{
		cout << "D::f1()" << endl;
	}

	virtual void g1()
	{
		cout << "D::g1()" << endl;
	}

	virtual void h1()
	{
		cout << "D::h1()" << endl;
	}
};

void main()
{
	D d;
}

另一种虚函数指针调用方式:

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void f()
	{
		cout << "Base::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base::h()" << endl;
	}
private:
	int m_a=0;
	int m_b=1;
};

typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
	cout << " 虚表地址>" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		VFPTR f = vTable[i];
		f();
	}
	cout << endl;
}
int main()
{
	Base b;
	// 思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质是一个存虚函数指针的指针数组,
	//这个数组最后面放了一个nullptr
	// 1.先取b的地址,强转成一个int*的指针
	// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针
	// 3.再强转成VFPTR*,因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。
	// 4.虚表指针传递给PrintVTable进行打印虚表
	// 5.需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处理不干净,虚表最后面
		
	VFPTR * vTableb = (VFPTR*)(*(int*)&b);
	PrintVTable(vTableb);
	//VFPTR* vTabled = (VFPTR*)(*(int*)&d);
	//PrintVTable(vTabled);
	return 0;
}

有问题的初始化方式,会导致虚表指针为空:

#include<iostream>
using namespace std;

class Base
{
public:
    void f()
	{
		cout << "Base1::f()" << endl;
	}

	virtual void g()
	{
		cout << "Base1::g()" << endl;
	}

	virtual void h()
	{
		cout << "Base1::h()" << endl;
	}
private:
	int m_a;
	int m_b;
};

class D:public Base
{
    virtual  void f()
	{
		cout << "D::f()" << endl;
	}

	virtual void g()
	{
		cout << "D::g()" << endl;
	}

	virtual void h()
	{
		cout << "D::h()" << endl;
	}
};

void test01()
{
	D d;
	memset(&d, 0, sizeof(d));//不能这样初始化,虚表指针干空了
	Base b;
	Base* pb = &d;
	//pb->f();
	pb->g();
}

void main()
{
	test01();
	system("pause");
}

构造顺序例题:顺序为A,B,C,D,两个类同时虚拟继承了一个类,这个类只被构造一次 

#include<iostream>
//两个类同时虚拟继承了一个类,这个类只被构造一次
using namespace std;
class A {
public:
	A(const char* s) { cout << s << endl; }
	~A() {}
};
class B :virtual public A
{
public:
	B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:
	C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D :public B, public C
{
public:
	D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2), C(s1, s3), A(s1)
	{
		cout << s4 << endl;
	}
};
int main() {
	D* p = new D("class A", "class B", "class C", "class D");
	delete p;
	return 0;
}

构造顺序:ABACD

#include<iostream>
using namespace std;
class A {
public:
	A() { cout << "1111111111" << endl; }
	~A() {}
};
class B : public A
{
public:
	B() { cout << "2222222222" << endl; }
};
class C : public A
{
public:
	C() { cout << "3333333333" << endl; }
};
class D :public B, public C
{
public:
	D()
	{
		cout << "4444444444" << endl;
	}
};
int main() {
	D* p = new D;
	delete p;
	return 0;
}

构造顺序:BACACD

#include<iostream>
using namespace std;
class A {
public:
	A() { cout << "1111111111" << endl; }
	~A() {}
};
class B 
{
public:
	B() { cout << "2222222222" << endl; }
};
class C 
{
public:
	C() { cout << "3333333333" << endl; }
};
class D :public A, virtual public B, public C
{
public:
	D()
	{
		cout << "4444444444" << endl;
	}

private:
	A a;
	B b;
	C c;
};
int main() {
	D* p = new D;
	delete p;
	return 0;
}

例题:下面代码段输出是什么?

答案是输出B->1,这里p->test()会调用父类的test(),父类的中的this->func()满足多态原理,this指针指向B类对象,调动B类的func(),而子类重写虚函数时会继承接口而重写实现,val采用父类func的缺省参数1。

#include<iostream>
using namespace std;
class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};
class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}

 多继承中指针偏移问题?下面说法正确的是( )

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

答案:C 有关于指针偏移的问题 指针偏向于先继承的那个父类。

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}

1. 什么是多态?答:参考本博客。
2. 什么是重载、重写(覆盖)、重定义(隐藏)?答:参考本博客。
3. 多态的实现原理?答:参考本博客。
4. inline函数可以是虚函数吗?答:不能,因为inline函数没有地址,无法把地址放到虚函数表中。
5. 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式。无法访问虚函数表,所以静态成员函数无法放进虚函数表。
6. 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。
7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以,并且最好把基类的析构函数定义成虚函数。参考本博客。
8. 对象访问普通函数快还是虚函数更快?答:首先如果是普通对象,是一样快的。如果是指针对象或者是引用对象,则调用的普通函数快,因为构成多态,运行时调用虚函数需要到虚函数表中去查找。
9. 虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。
10. C++菱形继承的问题?虚继承的原理?答:参考上一篇博客。注意这里不要把虚函数表和虚基表搞混了。
11. 什么是抽象类?抽象类的作用?答:参考本博客。抽象类强制重写了虚函数,另外抽象类体现出了接口继承关系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/363087.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32 Arduino FAT文件系统详细使用教程

ESP32 Arduino FAT文件系统详细使用教程&#x1f4cc;参考1&#xff08;在 Linux环境下创建二进制文件&#xff09;&#xff1a;https://github.com/marcmerlin/esp32_fatfsimage&#x1f4cc;参考2 http://marc.merlins.org/perso/arduino/post_2019-03-30_Using-FatFS-FFat-o…

Scala函数式编程(第五章:函数基础、函数高级详解)

文章目录第 5 章 函数式编程5.1 函数基础5.1.1 函数基本语法5.1.2 函数和方法的区别5.1.3 函数定义5.1.4 函数参数5.1.5 函数至简原则&#xff08;重点&#xff09;5.2 函数高级5.2.1 高阶函数5.2.2 匿名函数5.2.3 高阶函数案例5.2.4 函数柯里化&闭包5.2.5 递归5.2.6 控制抽…

GooglePlay SSL Error Handler

应用上架GooglePlay 收到邮件提示 出现这个原因是因为我在app中使用webview加载Https的H5界面&#xff0c;在onReceivedSslError()中处理SslErrorHandler时&#xff0c;出现白屏现象&#xff0c;原因是webview默认在加载有证书验证的url时&#xff0c;会默认使用handler.cancel…

SPDK vhost target

SPDK vhost target主流的I/O设备虚拟化的方案1.virtio2.vhost加速1&#xff09;QEMU virtio-scsiQemu 架构2&#xff09;Kernel vhost-scsi3&#xff09;SPDK vhost-user-scsi3.SPDK vhost-scsi加速4.SPDK vhost-NVMe加速主流的I/O设备虚拟化的方案 纯软件模拟&#xff1a;完全…

vTESTstudio - VT System CAPL Functions - VT7001(续1)

vtsSetInterconnectionMode - 设置VT7001的电源输出模式功能&#xff1a;设置电源模块VT7001的三个可能电源和两个电源输出的互连模式。注意&#xff1a;此函数不能在任何CAPL处理程序例程或ECU节点中调用。它只能在测试模块的MainTest方法上下文中调用&#xff1b;使用在测量开…

企业为什么需要数据可视化报表

数据可视化报表是在商业环境、市场环境已经改变之后&#xff0c;发展出来为当前企业提供替代解决办法的重要方案。而且信息化、数字化时代&#xff0c;很多企业已经进行了初步的信息化建设&#xff0c;沉淀了大量业务数据&#xff0c;这些数据作为企业的资产&#xff0c;是需要…

Logstash:在 Logstash 管道中的定制的 Elasticsearch update by query

我们知道 Elasticsearch output plugin 为我们在 Logstash 的 pipeline 中向 Elasticsearch 的写入提供了可能。我们可以使用如下的格式向 Elasticsearch 写入数据&#xff1a; elasticsearch {hosts > ["https://localhost:9200"]index > "data-%{YYYY.M…

ROS2手写自定义点云(PointCloud2)数据并发布

目录前言实现前言 继续学习ROS2&#xff0c;最近把navigation2的路径规划部分学习了一遍&#xff0c;但是还没有进行测试&#xff0c;于是先把这个部分先空出来后面再总结。先写一个与避障有关系的如何自己发点云数据。 在nav2里面有一个非常重要的部分就是costmap部分&#…

Python是未来的编程语言?学Python前景如何?薪资高吗?

Python是一种强大的语言&#xff0c;为世界各地的开发人员提供了多种用途。根据TIOBE指数&#xff0c;Python的排名还在继续攀升。开发人员和技术专业人员也不断发现Python的新用途&#xff0c;包括数据分析和机器学习等。 Python现在有着庞大的用户基础&#xff0c;并且它深深…

经纬度坐标点和距离之间的转换

1.纬度相同&#xff0c;经度不同 在纬度相同的情况下&#xff1a; 经度每隔0.00001度&#xff0c;距离相差约1米&#xff1b; 每隔0.0001度&#xff0c;距离相差约10米&#xff1b; 每隔0.001度&#xff0c;距离相差约100米&#xff1b; 每隔0.01度&#xff0c;距离相差约1000米…

Linux 远程登录

Linux 一般作为服务器使用&#xff0c;而服务器一般放在机房&#xff0c;你不可能在机房操作你的 Linux 服务器。 这时我们就需要远程登录到Linux服务器来管理维护系统。 Linux 系统中是通过 ssh 服务实现的远程登录功能&#xff0c;默认 ssh 服务端口号为 22。 Window 系统…

SpringCloud+Dubbo3 = 王炸 !

前言 全链路异步化的大趋势来了 随着业务的发展&#xff0c;微服务应用的流量越来越大&#xff0c;使用到的资源也越来越多。 在微服务架构下&#xff0c;大量的应用都是 SpringCloud 分布式架构&#xff0c;这种架构总体上是全链路同步模式。 全链路同步模式不仅造成了资源…

第二章 runtime-core初始化核心流程和runtime-core更新核心流程

runtime-core初始化核心流程 1 创建app 2 进行初始化 2.1 基于组件生成虚拟节点 2.2 进行render 调用patch 根据不同的vnode类型进行不同类型的组件处理 组件 2.2.1 创建component instance对象 2.2.2 setup component 初始化props slots 各种 2.2.3 setupRenderEffect…

通过Docker部署rancher

先创建k8s集群 https://blog.csdn.net/weixin_44371237/article/details/123974335 环境准备 一台linux主机&#xff0c;4G内存 通过Docker部署rancher 启动rancher docker run --privileged -d --restartunless-stopped -p 80:80 -p 443:443 rancher/rancher查看本地镜像…

python基础:简单实现从网页中获取小说名单列表并存入文件中

python基础&#xff1a;简单实现从网页中获取小说名单列表并存入文件中1.技术储备 requests&#xff1a;requests是使用Apache2 licensed 许可证的HTTP库&#xff0c;可以用于网页数据请求 requests.get():发起网络请求的一种方式&#xff0c;类似的还有post、 put、 delete、…

[MySQL]基本数据类型及表的基本操作

哈喽&#xff0c;大家好&#xff01;我是保护小周ღ&#xff0c;本期为大家带来的是 MySQL 数据库常用的数据类型&#xff0c;数据表的基本操作&#xff1a;创建、删除、修改表&#xff0c;针对修改表的结构进行了讲解&#xff0c;随后是如何向数据表中添加数据&#xff0c;浅浅…

Vulnhub_Venom

目录 一 测试 &#xff08;一&#xff09;信息收集 1 端口服务探测 2 目录扫描 3 前端源码信息收集 &#xff08;二&#xff09;漏洞发现 1 前端注释敏感信息泄露 2 CVE-2018-19422-Subrion CMS v 4.2.1任意文件上传 &#xff08;三&#xff09;提权 1 sudo…

4万字c++讲解+区分c和c++,不来可惜了(含代码+解析)

目录 1 C简介 1.1 起源 1.2 应用范围 1.3 C和C 2开发工具 3 基本语法 3.1 注释 3.2关键字 3.3标识符 4 数据类型 4.1基本数据类型 4.2 数据类型在不同系统中所占空间大小 4.3 typedef声明 4.4 枚举类型 5 变量 5.1 变量的声明和定义 5.2 变量的作用域 6 运算符…

面试之设计模式(简单工厂模式)

案例 在面试时&#xff0c;面试官让你通过面对对象语言&#xff0c;用Java实现计算器控制台程序&#xff0c;要求输入两个数和运算符号&#xff0c;得出结果。大家可能想到是如下&#xff1a; public static void main(String[] args) {Scanner scanner new Scanner(System.…

一文让你了解SpringCloud五大核心组件

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;SpringCloud五大核心组件 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林…