類中成員的分布:成員變量指針和成員函數指針
成員變量指針
① 成員變量指針是什麼?
成員變量指針顧名思義就是“指向類類型中公有成員變量的指針”;
② 成員變量指針的注意事項:
⑴ 成員變量指針不可以使用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