C++进阶学习---多肽_编程开发

来源:微信公众号「编程学习基地」 @[toc](目录) #### 虚函数

普通成员函数前加关键字virtual,称为虚函数

覆盖
  • 子类成员函数和基类的虚函数具有相同函数原型,该成员函数也就是虚函数,无论其是否带有virtual关键字,都对基类虚函数构成覆盖
覆盖条件
  • 函数为成员函数(非静态)
  • 基类使用virtual
  • 原型严格相同

虚函数程序示例:

#include<iostream>
#include<string>
using namespace std;
//基类 Animal
class Animal
{
public:
	Animal(string name) :m_name(name) {  }
	void say()	
	{
		cout << m_name << ":$%^&@" << endl;
	}
protected:
	string m_name;
};

//派生类 Dog
class Dog :public Animal
{
public:
	Dog(string name) :Animal(name) {}
	void say()
	{
		cout << m_name << ":汪汪汪" << endl;
	}
};

int main()
{
	Animal *a = new Animal("动物");
	a->say();				//Animal类说 $%^&@
	delete a;

	a = new Dog("旺财");		//隐式转换成基类对象
	a->say();
    
	delete a;
	return 0;
}
打印结果:
动物:$%^&@
旺财:$%^&@

加关键字virtual:

virtual void say()	//函数前加关键字virtual,形成虚函数
{
    cout << m_name << ":$%^&@" << endl;
}
int main()
{
	Animal *a = new Animal("动物");
	a->say();
	delete a;

	a = new Dog("旺财");  //ISA   隐式转换成基类对象
	//a调用的是虚函数,并且形成覆盖,a实际指向Dog类对象 调用Dog::say()
    /*
	 * 基类成员函数前加virtual 该成员函数成为虚函数
 	 * 没有virtual 对象调用函数看声明类型  有虚函数并且形成覆盖,对象调用函数看实际内存中类型
	 */
	a->say();
	delete a;
	return 0;
}
打印结果
动物:$%^&@
旺财:汪汪汪

虚函数表

编译器通过指针或引用调用虚函数,不会立即生成函数调用指令,而是用一段代码代替确定真实类型,找到虚函数表从而找到入口地址,根据入口地址调用函数。

C++进阶学习---多肽_编程开发_02

#include<iostream>
using namespace std;
class N {
public:
	void foo() {
		cout << "N::foo" << endl;
	}
	void bar() {
		cout << "N::bar" << endl;
	}
	int m_a;
	int m_b;
};

class A {
public:
	virtual void foo() {
		cout << "A::foo" << endl;
	}
	virtual void bar() {
		cout << "A::bar" << endl;
	}
	int m_a;
	int m_b;
};

class B :public A {
public:
	void foo() {
		cout << "B::foo" << endl;
	}
	void bar() {
		cout << "B::bar" << endl;
	}
};

int main() {
	/*
	*	实例化三个对象,在监视里面查看内存
	*/
	N n;
	A a;
	B b;
	//offsetof(s,m) 计算m相对s的偏移地址
	cout << "sizeof(N):" << sizeof(N) << ",m_a:" << offsetof(N, m_a) << ",m_b:" << offsetof(N, m_b) << endl;
	cout << "sizeof(A):" << sizeof(A) << ",m_a:" << offsetof(A, m_a) << ",m_b:" << offsetof(A, m_b) << endl;
	cout << "sizeof(B):" << sizeof(B) << ",m_a:" << offsetof(B, m_a) << ",m_b:" << offsetof(B, m_b) << endl;
	/*
	*	从上面可以得知有虚函数的类,比没有虚函数的类多了一个四字节的东东,那个东东就是一个指针,指向虚函数表
	*	不管有多少个虚函数,一个对象只有一个指向虚函数表的指针
	*/
	
	//自己定义一个虚函数表
	typedef void(*VFUN) (void *);  // 将void* 重定义为函数指针
	typedef VFUN* VPTR;            // 重定义指向虚函数表指针的类型

	VPTR vf_ptr = *(VPTR*)&a;		//拿到对象a的虚函数表指针
	
	cout << vf_ptr << "[" << vf_ptr[0] << "," << vf_ptr[1] << "]" << endl;
	//通过虚函数表调用虚函数
	vf_ptr[0](&a);	//&a相当于创一个this指针过去
	vf_ptr[1](&a);	//&a相当于创一个this指针过去

	cin.get();
	return 0;
}

打印结果

sizeof(N)8,m_a:0,m_b:4
sizeof(A)12,m_a:4,m_b:8
sizeof(B)12,m_a:4,m_b:8
00CB8B34[00CB141A,00CB142E]
A::foo
A::bar

监视里面也可以很好的看到A,B两个类的对象有一个__vfptr的指针,指向两个虚函数,

C++进阶学习---多肽_C  _03

多态

单台和多态
#include<iostream>
using namespace std;
//单肽,一个函数一个功能
int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

//利用函数指针实现多态
int calc(int x, int y, int(*pfun)(int, int))
{
	return pfun(x, y);
}

int main()
{
	cout << calc(2, 3, add) << endl;
	cout << calc(2, 3, sub) << endl;
	cin.get();
	return 0;
}
多态理解
  • 子类提供了对基类虚函数的有效覆盖,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际上调用的将是子类中的覆盖版本,而非基类中的原始版本
  • 一般函数调用是通过调用对象类型决定,而多态这是通过调用者指针或引用的实际目标对象的类型决定
  • 多态只能通过引用或指针表现,且指针更灵活
  • 除构造和析构函数,通过基类中this指针可以满足多态

其实在虚函数里面就已经讲解到多肽了

#include<iostream>
#include<string>
using namespace std;
//基类 Animal
class Animal
{
public:
	Animal(string name) :m_name(name) {  }
	virtual void say()
	{
		cout << m_name << ":$%^&@" << endl;
	}
protected:
	string m_name;
};

//派生类 Dog
class Dog :public Animal
{
public:
	Dog(string name) :Animal(name) {}
	void say()
	{
		cout << m_name << ":汪汪汪" << endl;
	}
};

int main()
{
	Animal *a = new Animal("动物");
	a->say();				//Animal 说 $%^&@
	delete a;

	a = new Dog("旺财");		//隐式转换成基类对象
	a->say();				//旺财 说 汪汪汪
	delete a;
	/*
	*	指针a通过指针指向的改变实现多肽
	*	a指向Animal时说	$%^&@
	*	a指向Dog时说	汪汪汪
	*/
	cin.get();
	return 0;
}
多肽的好处
  • 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性
  • 派生类的功能可以被基类的指针或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。

虚析构

只有虚析构,没有虚构造

class A
{
public:
	A()
	{
		cout << "A构造" << endl;
	}
	~A()
	{
		cout << "A析构" << endl;
	}
};

class B :public A
{
public:
	B()
	{
		p = new int(10);
		cout << "B构造" << endl;
	}
	~B()
	{
		cout << "B析构" << endl;
		delete p;
	}

private:
	int *p;
};

int main()
{
	A* a = new B; //隐式转换为基类对象
	delete a;
	cin.get();
	return 0;
}

不加virtual 修饰~A(),打印结果

A构造
B构造
A析构

内存泄漏了,B类里面的*p指向的堆内存没有释放

解决办法:

class A
{
public:
	A()
	{
		cout << "A构造" << endl;
	}
	virtual ~A()	//虚析构
	{
		cout << "A析构" << endl;
	}
};

析构前面加上关键字virtual构成虚析构

A构造
B构造
B析构
A析构

注意:

  • 如果父类的析构函数不是虚析构,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数
  • 如果父类的析构函数是虚析构,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数

纯虚函数

形如virtual 返回值 函数名(形参表)=0;的虚函数,称为纯虚函数或抽象方法

class Base
{
	virtual void fun(int) = 0; //纯虚函数
};

抽象类

  • 至少拥有一个纯虚函数的类成为抽象类
  • 抽象类不能实例化为对象
  • 抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
class Base
{
	virtual void foo(int) = 0; //纯虚函数
	virtual void fun(int) = 0;
};//抽象类

//抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
class B:public Base
{
	void foo(int) {}
};

  • 不能实例化抽象类

纯抽象类

全部由纯虚函数构成的抽象类成为纯抽象类或接口

继承多肽示例程序

某小型公司,主要有四类员工( Employee ):经理( Manager )、技术人员( Technician )、销售经理( SalesManager )和推销员( SalesMan )。
现在 , 需要存储这些人员的姓名( name )、编号( id )、当月薪水( salary )。计算月薪总额并显示全部信息。人员编号基数为 1000 ,每输入一个人员工信息编号顺序加 1 。
月薪计算办法是:

经理拿固定月薪 8000 元;
技术人员按每小时 100 元领取月薪;

推销员的月薪按该推销员当月销售额的 4% 提成;

销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5% 。

继承关系图:

C++进阶学习---多肽_C  _04

#include<string>
#include<iostream>
using namespace std;
class Employee
{
protected:
	string m_name;
	int m_id;
	float m_salary;
	static int Id;
public:
	Employee() :m_name(""), m_salary(0.0f), m_id(++Id){}
	virtual void getSalary() = 0;//纯虚函数
	void show() 
	{
		cout << "姓名:" << m_name << endl;
		cout << "员工ID:" << m_id << endl;
		cout << "工资:" << m_salary << endl << endl;
	}
	virtual ~Employee() {}
};	//员工类

int Employee::Id = 1000;

class Manager : virtual public Employee
{
protected:
	float m_baseSalary;	//基本工资
public:
	Manager(string name)
	{
		m_name = name;
		m_baseSalary = 8000.0f;	//经理拿固定月薪 8000 元
	}
	void getSalary()
	{
		m_salary = m_baseSalary;
	}
	~Manager() {}
};	//经理类

class Technician : public Employee
{
private:
	int m_hour;
public:
	Technician(string name, int hour) 
	{
		m_name = name;
		m_hour = hour;
	}
	void getSalary() 
	{
		m_salary = m_hour * 70;	//技术人员按每小时 100 元领取月薪
	}
	~Technician() {}
};	//技术人员

class SalesMan : virtual public Employee
{
private:
	float m_Count;  //销售额
public:
	SalesMan(string name, float Count) 
	{
		m_name = name;
		m_Count = Count;
		m_partCount += m_Count;
	}
	void getSalary() 
	{
		m_salary = m_Count * 0.04;	//推销员的月薪按该推销员当月销售额的 4% 提成
	}
	~SalesMan() {}
protected:
	static float m_partCount;	//总销售 					
};	//推销员
float SalesMan::m_partCount = 0.0f;

class SalesManager : public Manager, public SalesMan
{
public:
	SalesManager(string name) :Manager(name), SalesMan(name, 0)
	{
		m_name = name;
		m_baseSalary = 5000;
	}
	void getSalary()
	{
		//销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5% 。
		m_salary = m_baseSalary + m_partCount * 0.05;
	}
	~SalesManager() {}
};	//销售经理

int main()
{
	Employee* emp[5] = { 0, };
	emp[0] = new Manager("经理");
	emp[0]->getSalary();
	emp[0]->show();

	emp[1] = new Technician("技术人员", 99);
	emp[1]->getSalary();
	emp[1]->show();

	emp[2] = new SalesMan("销售人员", 60000);
	emp[2]->getSalary();
	emp[2]->show();

	emp[3] = new SalesMan("销售人员", 900000);
	emp[3]->getSalary();
	emp[3]->show();

	emp[4] = new SalesManager("销售经理");
	emp[4]->getSalary();
	emp[4]->show();

	for (int i = 0; i < 5; ++i)
	{
		delete emp[i];
	}
	getchar();
	return 0;
}