0
点赞
收藏
分享

微信扫一扫

c语言学习,free()函数分析

拾杨梅记 2024-08-17 阅读 60

1. 多态的概念

举个栗子:比如买票这个行为,当普通人买票时,是全价买票;学生买票时,是半价买票;军人

买票时是优先买票

2. 多态的定义及实现

2.1多态的构成条件

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了 Person。Person对象买票全价,Student对象买票半价。

那么在继承中要构成多态还有两个条件:

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

class Student : public Person
{
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};


void Func(Person& p)
{
	//不同的人做一件事结果不一样
	p.BuyTicket();
}

int main()
{
	Person p;
	Student s;

	Func(p);
	Func(s);
	return 0;
}

2.2 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。

class Person {
public:
 virtual void BuyTicket() { cout << "买票-全价" << endl;}
};

2.3虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型函数名字参数列表完全相同),称子类的虚函数重写了基类的虚函数。指向谁调用谁。

指针版本

void Func(Person* p)
{
	//不同的人做一件事结果不一样
	p->BuyTicket();
}

引用版本

void Func(Person& p)
{
	//不同的人做一件事结果不一样
	p.BuyTicket();
}

当然这样是不行的,必须是指针和引用

void Func(Person p)
{
	//不同的人做一件事结果不一样
	p.BuyTicket();
}

虚函数重写的两个例外:

1. 协变(基类与派生类虚函数返回值类型不同)

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

如果把继承去掉,那么便构不成斜变,返回值不同要求父子类的指针或引用

2. 析构函数的重写(基类与派生类析构函数的名字不同)

普通函数下,先子后父

class Person {
public:
	~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	~Student() { cout << "~Student()" << endl; }
};

int main()
{
	Person(p);
    Studend(s);
	return 0;
}

如果使用指向谁,就无法调用子类的了

那我们这样岂不是会内存泄漏

这样也没有内存泄漏了

class Person {
public:
	virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
	virtual ~Student()
	{
		cout << "delete[]" << _ptr << endl;
		delete[] _ptr;

		cout << "~Student()" << endl;
	}
private:
	int* _ptr = new int[10];
};

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;
	delete p2;
	return 0;
}

来一个小题练练手吧:

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;
}

再来一个吧

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;
}

我们只需要修改一个public base1 ,public base2就可以分辨出来了

2.4 C++11 override 和 final

1. final:修饰虚函数,表示该虚函数不能再被重写

class Car
{
public:
 virtual void Drive() final {}
};
class Benz :public Car
{
public:
 virtual void Drive() {cout << "Benz-舒适" << endl;}
};
class Car final
{
public:

private:
	//C++98的方法:父类的构造函数私有
	//子类的构造无法生成和实现,,导致子类对象无法实例化
	Car() {}
};
class Benz :public Car
{
public:

};

int main()
{
	Benz b;
	return 0;
}

final 修饰的类叫做最终类不能被继承

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Car {
public:
	virtual void Drive() {}
};
class Benz :public Car {
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

2.5 重载、覆盖(重写)、隐藏(重定义)的对比

3. 抽象类

3.1 概念

虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生 类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
	virtual void Drive() = 0;
};
class Benz :public Car
{
public:
	virtual void Drive()
	{
		cout << "Benz-舒适" << endl;
	}
};
class BMW :public Car
{
public:
	virtual void Drive()
	{
		cout << "BMW-操控" << endl;
	}
};
void Test()
{
	/*Benz b1;
	b1.Drive();
	BMW b2;
	b2.Drive();*/


	Car* pBenz = new Benz;
	pBenz->Drive();
	Car* pBMW = new BMW;
	pBMW->Drive();
}

3.2 接口继承和实现继承

4.多态的原理

4.1虚函数表

我们先看一道面试题:

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

int main()
{
	cout << sizeof(Base) << endl;
	return 0;
}

通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表 中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数 的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函 数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  5. 总结一下派生类的虚表生成:
  • a.先将基类中的虚表内容拷贝一份到派生类虚表中
  • b.如果派生 类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
  • c.派生类自己 新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

4.2多态的原理

上面分析了这个半天了那么多态的原理到底是什么?还记得这里Func函数传Person调用的 Person::BuyTicket,传Student调用的是Student::BuyTicket

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)
{
	p.BuyTicket();
}
int main()
{
	Person Mike;
	Func(Mike);
	Student Johnson;
	Func(Johnson);
	return 0;
}

打印虚函数表
typedef void(*VFPTR)();

void PrintVFT(VFPTR* vft)
{
	for (size_t i = 0; i < 4; i++)
	{
		printf("%p\n", vft[i]);

		VFPTR pf = vft[i];
		(*pf)();
	}
}
class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
class DeriveDerive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; } //func1重写了
	virtual void func3() { cout << "Derive::func3" << endl; } //没有重写
	virtual void func4() { cout << "Derive::func4" << endl; } //没有重写
private:
	int b;
};
int main()
{
	Base b;
	DeriveDerive dd;

	VFPTR* ptr = (VFPTR*)(*((int*)&dd));
	PrintVFT(ptr);
	return 0;
}

举报

相关推荐

0 条评论