第七章 Part5 多态

多态

多态基本概念

  • 多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
  • 多态性(polymorphism)提供接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。
  • c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
  • 静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能编译不能在编译期间确定,而需要在运行时才能决定,这这就属于晚绑定(动态多态,运行时多态)。
点击查看代码
//计算器
class Caculator{
public:
	void setA(int a){
		this->mA = a;
	}
	void setB(int b){
		this->mB = b;
	}
	void setOperator(string oper){
		this->mOperator = oper;
	}
	int getResult(){
		
		if (this->mOperator == "+"){
			return mA + mB;
		}
		else if (this->mOperator == "-"){
			return mA - mB;
		}
		else if (this->mOperator == "*"){
			return mA * mB;
		}
		else if (this->mOperator == "/"){
			return mA / mB;
		}
	}
private:
	int mA;
	int mB;
	string mOperator;
};

//这种程序不利于扩展,维护困难,如果修改功能或者扩展功能需要在源代码基础上修改
//面向对象程序设计一个基本原则:开闭原则(对修改关闭,对扩展开放)

//抽象基类
class AbstractCaculator{
public:
	void setA(int a){
		this->mA = a;
	}
	virtual void setB(int b){
		this->mB = b;
	}
	virtual int getResult() = 0;
protected:
	int mA;
	int mB;
};

//加法计算器
class PlusCaculator : public AbstractCaculator{
public:
	virtual int getResult(){
		return mA + mB;
	}
};

//减法计算器
class MinusCaculator : public AbstractCaculator{
public:
	virtual int getResult(){
		return mA - mB;
	}
};

//乘法计算器
class MultipliesCaculator : public AbstractCaculator{
public:
	virtual int getResult(){
		return mA * mB;
	}
};

void DoBussiness(AbstractCaculator* caculator){
	int a = 10;
	int b = 20;
	caculator->setA(a);
	caculator->setB(b);
	cout << "计算结果:" << caculator->getResult() << endl;
	delete caculator;
}

向上类型转换及问题

问题抛出

  • 对象可以作为自己的类或者作为它的基类的对象来使用。还能通过基类的地址来操作它。取一个对象的地址(指针或引用),并将其作为基类的地址来处理,这种称为向上类型转换。
  • 也就是说:父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。
class Animal{
public:
	void speak(){
		cout << "动物在唱歌..." << endl;
	}
};

class Dog : public Animal{
public:
	void speak(){
		cout << "小狗在唱歌..." << endl;
	}
};

void DoBussiness(Animal& animal){
	animal.speak();
}

void test(){
	Dog dog;
	DoBussiness(dog);
}

运行结果:动物在唱歌
问题抛出: 我们给DoBussiness传入的对象是dog,而不是animal对象,输出的结果应该是Dog::speak。

问题解决思路

解决这个问题,我们需要了解下绑定(捆绑,binding)概念。
把函数体与函数调用相联系称为绑定(捆绑,binding)

  • 当绑定在程序运行之前(由编译器和连接器)完成时,称为早绑定(early binding).C语言中只有一种函数调用方式,就是早绑定。

  • 上面的问题就是由于早绑定引起的,因为编译器在只有Animal地址时并不知道要调用的正确函数。

  • 编译是根据指向对象的指针或引用的类型来选择函数调用。这个时候由于DoBussiness的参数类型是Animal&,

  • 编译器确定了应该调用的speak是Animal::speak的,而不是真正传入的对象Dog::speak。

  • 解决方法就是迟绑定(迟捆绑,动态绑定,运行时绑定,late binding),意味着绑定要根据对象的实际类型,发生在运行。

  • C++语言要实现这种动态绑定,必须有某种机制来确定运行时对象的类型并调用合适的成员函数。

问题解决方案(虚函数,vitual function)

C++动态多态性是通过虚函数来实现的,虚函数允许子类(派生类)重新定义父类(基类)成员函数,而子类(派生类)重新定义父类(基类)虚函数的做法称为覆盖(override),或者称为重写。
对于特定的函数进行动态绑定,c++要求在基类中声明这个函数的时候使用virtual关键字,动态绑定也就对virtual函数起作用.

  • 为创建一个需要动态绑定的虚成员函数,可以简单在这个函数声明前面加上virtual关键字,定义时候不需要.
  • 如果一个函数在基类中被声明为virtual,那么在所有派生类中它都是virtual的.
  • 在派生类中virtual函数的重定义称为重写(override).
  • Virtual关键字只能修饰成员函数.
  • 构造函数不能为虚函数

注意: 仅需要在基类中声明一个函数为virtual.调用所有匹配基类声明行为的派生类函数都将使用虚机制。

点击查看代码
class Animal{
public:
	virtual void speak(){
		cout << "动物在唱歌..." << endl;
	}
};
class Dog : public Animal{
public:
	virtual void speak(){
		cout << "小狗在唱歌..." << endl;
	}
};
void DoBussiness(Animal& animal){
	animal.speak();
}
void test(){
	Dog dog;
	DoBussiness(dog);
}

C++如何实现动态绑定

动态绑定什么时候发生?所有的工作都是由编译器在幕后完成。当我们告诉通过创建一个virtual函数来告诉编译器要进行动态绑定,那么编译器就会根据动态绑定机制来实现我们的要求, 不会再执行早绑定。

问题:C++的动态捆绑机制是怎么样的?
首先,我们看看编译器如何处理虚函数。当编译器发现我们的类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中秘密增加一个指针,这个指针就是vpointer(缩写vptr),这个指针是指向对象的虚函数表。在多态调用的时候,根据vptr指针,找到虚函数表来实现动态绑定。

验证对象中的虚指针:

class A{
public:
	virtual void func1(){}
	virtual void func2(){}
};

//B类为空,那么大小应该是1字节,实际情况是这样吗?
class B : public A{};

void test(){
	cout << "A size:" << sizeof(A) << endl;
	cout << "B size:" << sizeof(B) << endl;
}

在编译阶段,编译器秘密增加了一个vptr指针,但是此时vptr指针并没有初始化指向虚函数表(vtable),什么时候vptr才会指向虚函数表?在对象构建的时候,也就是在对象初始化调用构造函数的时候。编译器首先默认会在我们所编写的每一个构造函数中,增加一些vptr指针初始化的代码。如果没有提供构造函数,编译器会提供默认的构造函数,那么就会在默认构造函数里做此项工作,初始化vptr指针,使之指向本对象的虚函数表。
起初,子类继承基类,子类继承了基类的vptr指针,这个vptr指针是指向基类虚函数表,当子类调用构造函数,使得子类的vptr指针指向了子类的虚函数表。

  • 当子类无重写基类虚函数时:

image
image

过程分析:
Animal* animal = new Dog;
animal->fun1();
当程序执行到这里,会去animal指向的空间中寻找vptr指针,通过vptr指针找到func1函数,此时由于子类并没有重写也就是覆盖基类的func1函数,所以调用func1时,仍然调用的是基类的func1.
**代码验证: **示例代码\71 验证子类无重写基类函数
**执行结果: **我是基类的func1
**测试结论: **无重写基类的虚函数,无意义

  • 当子类重写基类虚函数时:
    image

image

过程分析:
Animal* animal = new Dog;
animal->fun1();
当程序执行到这里,会去animal指向的空间中寻找vptr指针,通过vptr指针找到func1函数,由于子类重写基类的func1函数,所以调用func1时,调用的是子类的func1.
代码验证: 示例代码\72 验证子类重写基类函数
执行结果: 我是子类的func1
测试结论: 无重写基类的虚函数,无意义

多态的成立条件:

  • 有继承
  • 子类重写父类虚函数函数
    1. 返回值,函数名字,函数参数,必须和父类完全一致(析构函数除外)
    2. 子类中virtual关键字可写可不写,建议写
  • 类型兼容,父类指针,父类引用 指向 子类对象

静态编译和动态编译

1.1多态分类
1. 静态多态 函数重载
2. 动态多态 虚函数 继承关系

1.2静态联编
1.地址早绑定 在编译阶段就绑定好了的

1.3动态联编
地址晚绑定,运行时绑定好地址
1.4多态
1.父类的引用或指针,指向子类的对象

多态基本概念

  1. 多态是面向对象程序设计语言中数据抽象和继承之外的第三个基本特征。
  2. 多态性(polymorphism)提供接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。
  3. c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
  4. 静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能编译不能在编译期间确定,而需要在运行时才能决定,这这就属于晚绑定(动态多态,运行时多态)。
点击查看代码
#include<iostream>
using namespace std;

class Animal
{
public:

	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat : public Animal
{
public:
	virtual void speak()
	{
		cout << "小猫在说话" << endl;
	}
};


//调doSpeak时,会提前在编译阶段找形参类型,speak函数的地址早就绑定好了,早绑定--静态联编,编译阶段就确定好了地址
//如果想要调用猫的speak,就不能提前绑定好函数的地址,所以需要在运行时候再去确定函数的地址
//动态联编:
//写法:将speak()改为虚函数;
//在父类上声明虚函数,发生了多态,已经变成了晚绑定地址了,现在只在运行阶段才能确定到底调用的是谁
//什么叫多态:父类的引用或者指针,指向子类对象
void doSpeak(Animal & animal)
{
	animal.speak();
}

//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
	Cat cat;
	doSpeak(cat);//动物在说话
}

int main()
{
	test01();

	system("pause");
	return 0;
}

多态原理解析

多态原理解析:

  1. 当父类中有了一个虚函数后,内部结构就发生了改变,内部多了一个 vfptr(virtual function pointer)虚函数表指针指向vftable 虚函数表
  2. 子类中 ,进行集成 ,会继承 vfptr 和vftable,vfptr也会指向父类中的 vftable,创建子类对象时,在构造函数中,就会把虚函数表的指针指向自己的虚函数表如果发生了重写,会替换掉虚函数表中原有的的speak(),改为&Cat::speak
  3. 深入剖析:如下图:
    image

Animal的虚函数表
image

Cat重写了speak()之后,Cat的虚函数表
image

Cat重写了speak()之后,Cat的虚函数表
image

点击查看代码
#include<iostream>
using namespace std;

class Animal
{
public:

	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat : public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};


//调doSpeak时,会提前在编译阶段找形参类型,speak函数的地址早就绑定好了,早绑定--静态联编,编译阶段就确定好了地址
//如果想要调用猫的speak,就不能提前绑定好函数的地址,所以需要在运行时候再去确定函数的地址
//动态联编:
//写法:将speak()改为虚函数,在父类上声明虚函数,发生了多态,已经变成了晚绑定地址了,现在只在运行阶段才能确定到底调用的是谁
//什么叫多态:父类的引用或者指针,指向子类对象
void doSpeak(Animal & animal)
{
	animal.speak();
}

//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
	Cat cat;
	doSpeak(cat);//当基类中的函数不是虚函数的时候,就会发生静态绑定,调用的还是父类的speak方法【动物在说话】,而添加了虚函数之后【小猫在说话】
}

void test02()
{
	//cout << sizeof(Animal) << endl;//1 函数不算对象身上
	//cout << sizeof(Animal) << endl;//4 写上virtual关键字之后
	Animal * animal = new Cat;//发生多态,父类的指针指向子类的对象

	animal->speak();//猫在说话
	//过程:(不需要记住,知道重写的原理就行了)
	//*(int *)animal,先强转成int类型的指针,再取 *,用来寻找虚函数表,
	//之后就找到了自己的虚函数表
	//虚函数表内部的结构也是一个数组,数组也是int类型的,也要进行强转,转成对象:
	//(int *)*(int *)animal
	//强转完之后,要再取到它:
	//*(int *)*(int *)animal : 这是函数的地址,函数指针指向函数地址,函数指针:
	//((void(*)())
	//指向 *(int *)*(int *)animal :
	//(((void(*)()) (*(int *)*(int *)animal))
	//(((void(*)()) (*(int *)*(int *)animal))();//调用函数
}

int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

多态深入解析

猫吃鱼的函数调用:
编译器的调用方式:
((void()())(((int)(int *)animal + 1)))();

点击查看代码
#include<iostream>
using namespace std;

class Animal
{
public:

	virtual void speak()
	{
		cout << "动物在说话" << endl;
	}
	virtual void eat()
	{
		cout << "动物在吃饭" << endl;
	}
};

class Cat : public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}

	virtual void eat()
	{
		cout << "小猫在吃鱼" << endl;
	}
};


//调doSpeak时,会提前在编译阶段找形参类型,speak函数的地址早就绑定好了,早绑定--静态联编,编译阶段就确定好了地址
//如果想要调用猫的speak,就不能提前绑定好函数的地址,所以需要在运行时候再去确定函数的地址
//动态联编:
//写法:将speak()改为虚函数,在父类上声明虚函数,发生了多态,已经变成了晚绑定地址了,现在只在运行阶段才能确定到底调用的是谁
//什么叫多态:父类的引用或者指针,指向子类对象
void doSpeak(Animal & animal)
{
	animal.speak();
}

//如果发生了继承的关系,编译器允许进行类型转换
void test01()
{
	Cat cat;
	doSpeak(cat);//动物在说话
}

void test02()
{
	//cout << sizeof(Animal) << endl;//1 函数不算对象身上
	//cout << sizeof(Animal) << endl;//4 写上virtual关键字之后
	Animal * animal = new Cat;//发生多态,父类的指针指向子类的对象

	animal->speak();//猫在说话
	//过程:(不需要记住,知道重写的原理就行了)
	//*(int *)animal,先强转,再取 *
	//之后就找到了自己的虚函数表
	//虚函数表内部的结构也是一个数组,数组也是int类型的,也要进行强转:
	//(int *)*(int *)animal
	//强转完之后,要再取到它:
	//*(int *)*(int *)animal : 这是函数的地址,函数指针指向函数地址,函数指针:
	//((void(*)())
	//指向 *(int *)*(int *)animal :
	//(((void(*)()) (*(int *)*(int *)animal))
	//(((void(*)()) (*(int *)*(int *)animal))();//调用函数
	
	//*((int*)*(int *)animal+1) 猫吃鱼的的地址
	((void(*)())(*((int*)*(int *)animal + 1)))();
}

int main()
{
	//test01();
	test02();
	system("pause");
	return 0;
}

image

多态案例

多态案例:
早期方法,不利于扩展

  • 真正的开发中,有个开发原则--开闭原则
  • 对扩展开放,对修改关闭
  • 利用多态实现,非常利于后期扩展,结构性非常好,可读性高,但是发生多态以后,内部变得复杂了,效率稍微降低,
  • C++比C语言效率稍微低。就低在了多态。
  • 多态的内部结构多了一个指针,空类里什么都没有,发生多态以后,内部多了一个vfptr指针,所以内部变得复杂了,效率稍微降低,
多态案例代码
#include<iostream>
#include<string>
using namespace std;

//class Calculate
//{
//public:
//	void setvl(int v)
//	{
//		this->val1 = v;
//	}
//
//	void setv2(int v)
//	{
//		this->val2 = v;
//	}
//
//	int getResult(string oper)
//	{
//		if (oper == "+")
//		{
//			return val1 + val2;
//		}
//		else if (oper == "-")
//		{
//			return val1 - val2;
//		}
//	}
//private:
//	int val1;
//	int val2;
//
//};


//真正的开发中,有个开发原则--开闭原则
//对扩展开放,对修改关闭


//利用多态实现计算器
class abstractCalculator
{
public:
	virtual int getResult(){ return 0; };

	void setvl(int v)
	{
		this->val1 = v;
	}

	void setv2(int v)
	{
		this->val2 = v;
	}

	int val1;
	int val2;
};

//加法计算器
class PlusCalculator : public abstractCalculator
{
public:
	virtual int getResult()
	{ 
		return val1 + val2; 
	};
};


//减法法计算器
class SubCalculator : public abstractCalculator
{
public:
	virtual int getResult()
	{
		return val1 - val2;
	};
};
//乘法法计算器
class MulCalculator : public abstractCalculator
{
public:
	virtual int getResult()
	{
		return val1 * val2;
	};
};



void test01()
{
	abstractCalculator * abc = new PlusCalculator;
	//加法计算器
	abc = new PlusCalculator;
	abc->setvl(10);
	abc->setv2(20);

	cout << abc->getResult() << endl;
	delete abc;//没有赋为空

	//减法计算器
	abc = new SubCalculator;
	abc->setvl(10);
	abc->setv2(20);

	cout << abc->getResult() << endl;
	delete abc;//没有赋为空

	//乘法计算器
	abc = new MulCalculator;
	abc->setvl(10);
	abc->setv2(20);

	cout << abc->getResult() << endl;
	delete abc;//没有赋为空

}

int main()
{
	test01();
	
	system("pause");
	return 0;
}

抽象类和纯虚函数

抽象基类和纯虚函数
在设计时,常常希望基类仅仅作为其派生类的一个接口。这就是说,仅想对基类进行向上类型转换,使用它的接口,而不希望用户实际的创建一个基类的对象。
同时创建一个纯虚函数允许接口中放置成员原函数,而不一定要提供一段可能对这个函数毫无意义的代码。

做到这点,可以在基类中加入至少一个纯虚函数(pure virtual function),使得基类称为抽象类(abstract class).

  • 纯虚函数使用关键字virtual,并在其后面加上=0。如果试图去实例化一个抽象类,编译器则会阻止这种操作。

  • 当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类。

  • Virtual void fun() = 0;告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。

    建立公共接口目的是为了将子类公共的操作抽象出来,可以通过一个公共接口来操纵一组类,且这个公共接口不需要事先(或者不需要完全实现)。可以创建一个公共类.

案例: 模板方法模式
image

点击查看代码
#include<iostream>
#include<string>
using namespace std;

//抽象制作饮品
class AbstractDrinking{
public:
	//烧水
	virtual void Boil() = 0;
	//冲泡
	virtual void Brew() = 0;
	//倒入杯中
	virtual void PourInCup() = 0;
	//加入辅料
	virtual void PutSomething() = 0;
	//规定流程
	void MakeDrink(){
		Boil();
		Brew();
		PourInCup();
		PutSomething();
	}
};

//制作咖啡
class Coffee : public AbstractDrinking{
public:
	//烧水
	virtual void Boil(){
		cout << "煮农夫山泉!" << endl;
	}
	//冲泡
	virtual void Brew(){
		cout << "冲泡咖啡!" << endl;
	}
	//倒入杯中
	virtual void PourInCup(){
		cout << "将咖啡倒入杯中!" << endl;
	}
	//加入辅料
	virtual void PutSomething(){
		cout << "加入牛奶!" << endl;
	}
};

//制作茶水
class Tea : public AbstractDrinking{
public:
	//烧水
	virtual void Boil(){
		cout << "煮自来水!" << endl;
	}
	//冲泡
	virtual void Brew(){
		cout << "冲泡茶叶!" << endl;
	}
	//倒入杯中
	virtual void PourInCup(){
		cout << "将茶水倒入杯中!" << endl;
	}
	//加入辅料
	virtual void PutSomething(){
		cout << "加入食盐!" << endl;
	}
};

//业务函数
void DoBussiness(AbstractDrinking* drink){
	drink->MakeDrink();
	delete drink;
}

void test(){
	DoBussiness(new Coffee);
	cout << "--------------" << endl;
	DoBussiness(new Tea);
}

int main()
{
	test01();
	
	system("pause");
	return 0;
}

image

纯虚函数和多继承

多继承带来了一些争议,但是接口继承可以说一种毫无争议的运用了。
绝大数面向对象语言都不支持多继承,但是绝大数面向对象对象语言都支持接口的概念,c++中没有接口的概念,但是可以通过纯虚函数实现接口。

接口类中只有函数原型定义,没有任何数据定义。

多重继承接口不会带来二义性和复杂性问题。接口类只是一个功能声明,并不是功能实现,子类需要根据功能说明定义功能实现。
注意:除了析构函数外,其他声明都是纯虚函数。

虚析构和纯虚析构

虚析构函数作用

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

点击查看代码
class People{
public:
	People(){
		cout << "构造函数 People!" << endl;
	}
	virtual void showName() = 0;
	virtual ~People(){
		cout << "析构函数 People!" << endl;
	}
};

class Worker : public People{
public:
	Worker(){
		cout << "构造函数 Worker!" << endl;
		pName = new char[10];
	}
	virtual void showName(){
		cout << "打印子类的名字!" << endl;
	}
	~Worker(){
		cout << "析构函数 Worker!" << endl;
		if (pName != NULL){
			delete pName;
		}
	}
private:
	char* pName;
};

void test(){

	People* people = new Worker;
	people->~People();
}

//结果:
image

纯虚析构函数

纯虚析构函数在c++中是合法的,但是在使用的时候有一个额外的限制:必须为纯虚析构函数提供一个函数体。
那么问题是:如果给虚析构函数提供函数体了,那怎么还能称作纯虚析构函数呢?
纯虚析构函数和非纯析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。

点击查看代码
//非纯虚析构函数
class A{
public:
	virtual ~A();
};

A::~A(){}

//纯析构函数
class B{
public:
	virtual ~B() = 0;
};

B::~B(){}

void test(){
	A a; //A类不是抽象类,可以实例化对象
	B b; //B类是抽象类,不可以实例化对象
}

如果类的目的不是为了实现多态,作为基类来使用,就不要声明虚析构函数,反之,则应该为类声明虚析构函数。

重写 重载 重定义

  • 重载,同一作用域的同名函数
    1. 同一个作用域
    2. 参数个数,参数顺序,参数类型不同
    3. 和函数返回值,没有关系
    4. const也可以作为重载条件 //do(const Teacher& t){} do(Teacher& t)
  • 重定义(隐藏)
    1. 有继承
    2. 子类(派生类)重新定义父类(基类)的同名成员(非virtual函数)
  • 重写(覆盖)
    1. 有继承
    2. 子类(派生类)重写父类(基类)的virtual函数
    3. 函数返回值,函数名字,函数参数,必须和基类中的虚函数一致
点击查看代码
class A{
public:
	//同一作用域下,func1函数重载
	void func1(){}
	void func1(int a){}
	void func1(int a,int b){}
	void func2(){}
	virtual void func3(){}
};

class B : public A{
public:
	//重定义基类的func2,隐藏了基类的func2方法
	void func2(){}
	//重写基类的func3函数,也可以覆盖基类func3
	virtual void func3(){}
};

指向成员的指针

指向成员变量的指针

  • 定义格式
   <数据类型> <类名>::*<指针名>
   例如: int A::*pPram;
  • 赋值/初始化
   <数据类型> <类名>::*<指针名> = &<类名>::<非静态数据成员>
   例如: int A::*pParam = &A::param;
  • 解引用
   <类对象名>.*<非静态数据成员指针>
   <类对象指针>->*<非静态数据成员指针>
   例如: A a; 
        a.*pParam; 
        a->*pParam;
class A{
public:
	A(int param){
		mParam = param;
	}
public:
	int mParam;
};

void test(){
	A a1(100);
	A* a2 = new A(200);
	int* p1 = &a1.mParam;
	int A::*p2 = &A::mParam;

	cout << "*p1:" << *p1 << endl;
	cout << "a1.*p2:" << a1.*p2 << endl;
	cout << "a2->*p2:" << a2->*p2 << endl;
}

指向成员函数的指针

定义格式
   <返回类型> (<类名>::*<指针名>)(<参数列表>)
   例如: void (A::*pFunc)(int,int);

赋值/初始化
   <返回类型>(<类名>::*<指针名>)(<参数列表>)  = &<类名>::<非静态数据函数>
   例如: void (A::pFunc)(int,int) = &A::func;

解引用
   (<类对象名>.*<非静态成员函数>)(<参数列表>)
   (<类对象指针>->*<非静态成员函数>)(<参数列表>)
   例如: A a; 
        (a.*pFunc)(10,20); 
        (a->*pFunc)(10,20);

class A{
public:
	int func(int a,int b){
		return a + b;
	}
};

void test(){
	A a1;
	A* a2 = new A;

	//初始化成员函数指针
	int(A::*pFunc)(int, int) = &A::func;
	//指针解引用
	cout << "(a1.*pFunc)(10,20):" << (a1.*pFunc)(10, 20) << endl;
	cout << "(a2->*pFunc)(10,20):" << (a2->*pFunc)(10, 20) << endl;
}

指向静态成员的指针

  • 指向类静态数据成员的指针
    指向静态数据成员的指针的定义和使用与普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联。
  • 指向类静态成员函数的指针
    指向静态成员函数的指针和普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联·
class A{
public:
	static void dis(){
		cout << data << endl;
	}
	static int data;
};

int A::data = 100;

void test(){
	int *p = &A::data;
	cout << *p << endl;
	void(*pfunc)() = &A::dis;
	pfunc();
}
posted @ 2023-02-17 00:03  nullptrException  阅读(45)  评论(0)    收藏  举报