类中成员的分布:成员变量指针和成员函数指针
成员变量指针
① 成员变量指针是什么?
成员变量指针顾名思义就是“指向类类型中公有成员变量的指针”;
② 成员变量指针的注意事项:
⑴ 成员变量指针不可以使用cout进行输出,只能使用printf这个万能打印函数来进行输出,主要原因是cout这个输出流对象的重载版本不支持输出“绑定了类类型的成员变量指针”;
代码示例:
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
};
typedef int A::*vptr;
int main()
{
vptr vptr1 = &A::a, vptr2 = &A::b;
printf("class A中成员变量a的地址偏移量为%X\n", vptr1);
printf("class A中成员变量b的地址偏移量为%X\n", vptr2);
}
运行结果:
注意:若使用cout输出也可以打印出来结果只不过结果是bool型变量:
上述使用cout输出流对象输出的结果显然不令人满意。
⑵ 不要将相同数据类型的类成员变量的指针赋值给拥有相同数据类型的普通指针
int A::*类型的指针相当于“绑定了class A这个类类型之后的普通指针”,这个指针发挥作用的机理与普通指针不同:
成员函数/变量指针发挥作用必须通过“类的实例化对象+成员指针提供的偏移地址”,这样我们才可以调用成员函数/成员变量。
③ 成员变量指针的属性:
⑴ 这个指针与普通指针有些不同,成员变量指针不是一个真正的指针,成员变量是一个“绑定了类类型的升级版普通指针”,简单的说成员变量描述了一个成员变量相较于类对象的地址偏移量;
代码示例:
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
};
typedef int A::*vptr;
int main()
{
vptr vptr1 = &A::a, vptr2 = &A::b;
printf("class A中成员变量a的地址偏移量为%X\n", vptr1);
printf("class A中成员变量b的地址偏移量为%X\n", vptr2);
}
运行结果:
⑵ 我们必须通过一个实例化对象才可以调用显式类的成员变量,因为调用成员变量的两个必要条件是“类对象的首地址+类成员变量的偏移量”
代码示例:
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
};
template <typename T, typename T1>
void ShowInf(T obj, T1 T::*ptr)
{
cout << obj.*ptr << endl;
}
typedef int A::*vptr;
int main()
{
vptr vptr1 = &A::a, vptr2 = &A::b;
printf("class A中成员变量a的地址偏移量为%X\n", vptr1);
printf("class A中成员变量b的地址偏移量为%X\n", vptr2);
A obj;
obj.a = 10;
obj.b = 11;
ShowInf(obj, &A::a); //输出obj这个class A对象中的成员函数a的值
}
运行结果:
⑶ 只有公有成员变量才可以拥有成员变量指针
上述显示可以说明:当a是private属性时,&A::a不存在!
成员函数指针
① 成员函数指针的定义:
成员函数的指针顾名思义就是“指向类类型的公有成员函数的指针”;
② 成员函数的注意事项:
⑴ 成员函数地址不可以赋值给“有相同函数形式的普通函数指针”,因为成员函数指针不像普通指针那样直接可以解引用,而是必须“类类型对象+成员函数地址偏移量”才可以实现公有成员函数的访问;
上述结果表明,我们不可以使用类的成员函数指针去赋值/初始化普通函数指针,即使函数的形式都相同。强制类型转换也不会成功的!
⑵ 对于const属性的成员函数,我们需要在对应的函数指针声明中添加这一const常成员函数属性
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
public:
void Test01() const // const属性的成员函数
{
cout << "调用成员函数" << endl;
}
};
typedef void(A::*fptr)(void) const; // 在成员指针声明时添加const属性
int main()
{
fptr fptr1 = &A::Test01;
}
⑶ 对于constexpr属性的常量表达式,我们不用在成员函数指针声明时添加constexpr属性
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
public:
constexpr int RetA() // constexpr属性的成员函数
{
return this->a;
}
void Test01() const
{
cout << "调用成员函数" << endl;
}
};
typedef int (A::*cfptr)(void);
typedef void(A::*fptr)(void) const;
int main()
{
fptr fptr1 = &A::Test01;
cfptr fptr2 = &A::RetA; // 获得constexpr属性的常量表达式成员函数指针
}
为什么constexpr属性不同于const属性呢?
constexpr属性用于代码运行效率的优化,但是const属于限定了成员函数的内部实现方式,即不可以在成员函数内部修改任何成员变量。优化这个东西可有可无,但是const这个限定属性不可以随意丢弃!
C++中constexpr详解:面向对象/面向过程中constexpr的使用
⑷ 成员函数指针的使用必须搭配确定的类对象
代码示例:
#include <iostream>
using namespace std;
class A
{
public:
void Test01() const
{
cout << "调用成员函数" << endl;
}
};
typedef void(A::*fptr)(void) const;
void ShowInf(A obj, fptr func)
{
(obj.*func)();
}
int main()
{
A obj;
fptr fptr1 = &A::Test01;
ShowInf(obj, fptr1);
}
运行结果:
一定要注意:成员函数的访问必须结合“类对象的首地址和成员函数的所在地址”。
⑸ 使用成员函数/成员变量指针必须初始化类对象的成员函数,否则会出现如下错误提示:
⑹ 成员函数指针不是代表着成员函数地址相较于类对象地址的偏移量
我们要知道“成员函数”是为类类型所有,也就是说成员函数先于类对象而存在,在这里类对象相当于工厂的工人,而类的成员函数则是这个工厂生产所用的设备,这些设备是这些工人共同使用为这个工厂所有而非针对于特定的一个工人。
虽然类成员函数有地址,但是必须通过类对象调用才可以,因为类的成员函数是用于处理成员变量的,而成员变量因类对象而异,因此必须使用特定的类对象调用才可以。
代码示例:
#include <iostream>
using namespace std;
class A
{
public:
void Test01() const
{
cout << "调用成员函数" << endl;
}
};
typedef void(A::*fptr)(void) const;
void ShowInf(A obj, fptr func)
{
(obj.*func)();
}
int main()
{
A obj;
fptr fptr1 = &A::Test01;
printf("class A的成员函数地址为%X\n", fptr1);
ShowInf(obj, fptr1);
}
运行结果:
成员指针的应用
① 成员指针产生原因:
类的成员函数就像一把把工具可以完成对数据的处理,但是我们想要做一件事情需要按照一定的流程去处理这些数据不可以随意拿起来一个工具就使用,成员函数指针使得“按照特定顺序去调用成员函数去一步一步的顺序处理数据”成为可能:
⑴ 首先,我们必须先找一个顺序容器便于我们按照流程一步一步的顺序调用成员函数,其中数组就是一个好的选择,因为数组在内存中每个元素顺序排列而且可以连续访问;
⑵ 我们可以把类类型当作一个“数据处理的工厂”,这个工厂中成员变量相当于生产原料,成员函数相当于生产设备,我们如果想要加工得到一个零部件,首先要梳理思绪,想想要以何种顺序使用这些机器才可以得到我们想要得到的产品;
⑶ 按照数据处理顺序排列得到“一个调用成员函数的顺序”,我们只需将成员函数的地址取出,再将这些地址按照先后顺序依次放入顺序数组中即可得到一个完美无缺的数据处理流程;
⑷ 我们使用enum枚举变量给每个步骤取一个好听的清晰的名字,enum本质上是顺序数字,enum可以给这些可读性清晰度不高的普通数字取一个别名使得这些数字的含义更加清晰明了。
② 代码结构
⑴ 使用enum枚举变量来列举出来事件的执行流程:
enum OperationProcess
{
DataProcess = 0, Implement
};
⑵ 使用类类型来封装数据和相应操作:
class Operation
{
public:
void Pre_process()
{
cout << "数据预处理" << endl;
}
void Done()
{
cout << "执行对数据的最终操作" << endl;
}
};
⑶ 将成员函数指针提取出来按照预先规定的调用顺序放入数组中:
fptr FuncArray[] = { &Operation::Pre_process,&Operation::Done };
⑷ 对数据处理的流程进行函数封装
template <typename T, typename T1>
void DataProcessFunc(T obj, T1 ArrayPtr)
{
(obj.*ArrayPtr[DataProcess])(); // 数据处理第一步
(obj.*ArrayPtr[Implement])(); // 数据处理第二步
}
注意:
⒈ 函数的参数一定要有类对象,因为只有类对象才可以调用类的成员函数;
2. 我这里为了简单将类的成员函数的形式全部写成了void(*)(void)的格式,真实情况下,你的类成员函数的形式可能不同因此你要使用typedef/using将这些成员函数指针一一列出,举例说明:我的类成员函数中总体来讲有两种形式,那么我用typedef将他们全部定义出来:
typedef void(Operation::*fptr)(void);
typedef int(Operation::*fptr1)(int obj);
⑸ 在函数中调用我们的数据处理封装函数来返回我们想要的最终数据:
DataProcessFunc<Operation, fptr*>(obj, FuncArray); // 调用我们封装好的函数
⑹ 代码整体结构如下:
#include <iostream>
using namespace std;
enum OperationProcess
{
DataProcess = 0, Implement
};
class Operation
{
public:
void Pre_process()
{
cout << "数据预处理" << endl;
}
void Done()
{
cout << "执行对数据的最终操作" << endl;
}
};
typedef void(Operation::*fptr)(void);
template <typename T, typename T1>
void DataProcessFunc(T obj, T1 ArrayPtr)
{
(obj.*ArrayPtr[DataProcess])(); // 数据处理第一步
(obj.*ArrayPtr[Implement])(); // 数据处理第二步
}
int main()
{
Operation obj;
fptr FuncArray[] = { &Operation::Pre_process,&Operation::Done };
DataProcessFunc<Operation, fptr*>(obj, FuncArray);
}
运行结果:
静态成员函数/成员变量指针
类的静态成员由于不属于类对象属于整个类类型所有不与类对象绑定,因此静态成员函数指针相当于普通函数指针,静态成员变量指针相当于普通变量指针。
#include <iostream>
using namespace std;
class A
{
public:
static int a;
public:
static void test()
{
cout << "调用静态成员函数" << endl;
}
};
int A::a = 0; // 如果含有静态成员变量一定要对其初始化
typedef void(*fptr)(void);
int main()
{
int* vptr = &A::a; // 静态成员变量指针等同于普通指针
cout << "静态成员变量地址:" << vptr << endl;
cout << "静态成员变量值:" << *vptr << endl;
fptr sfunc = &A::test;
(*sfunc)(); // 直接调用即可不用再通过类对象了
cout << "静态成员函数地址:" << sfunc << endl;
}
运行结果:
面向对象过程中的static: 面向对象/面向过程中的static