C++類別別模板(模板類)與友元詳解

2020-07-16 10:04:24
下面我們分四種情況分別討論。

1. 函數、類、類的成員函數作為類別範本的友元

函數、類、類的成員函數都可以作為類別範本的友元。程式範例如下:
void Func1() {  }
class A {  };
class B
{
public:
    void Func() { }
};
template <class T>
class Tmpl
{
    friend void Func1();
    friend class A;
    friend void B::Func();
};
int main()
{
    Tmpl<int> i;
    Tmpl<double> f;
    return 0;
}
類別範本範例化時,除了型別引數被替換外,其他所有內容都原樣保留,因此任何從 Tmp1 範例化得到的類都包含上面三條友元宣告,因而也都會把 Func1、類 A 和 B::Func 當作友元。

2. 函數模板作為類別範本的友元

例題:在《C++類別模板(模板類)》一節中我們定義了 Pair 模板,將<<運算子過載為一個函數模板,並將該函數模板作為 Pair 模板的友元,這樣,任何從 Pair 模板範例化得到的物件都能用<<運算子通過 cout 輸出。

程式程式碼如下(函數模板作為類別範本的友元):
#include <iostream>
#include <string>
using namespace std;
template <class T1, class T2>
class Pair
{
private:
    T1 key;  //關鍵字
    T2 value;  //值
public:
    Pair(T1 k, T2 v) : key(k), value(v) { };
    bool operator < (const Pair<T1, T2> & p) const;
    template <class T3, class T4>
    friend ostream & operator << (ostream & o, const Pair<T3, T4> & p);
};
template <class T1, class T2>
bool Pair <T1, T2>::operator< (const Pair<T1, T2> & p) const
{  //“小”的意思就是關鍵字小
    return key < p.key;
}
template <class Tl, class T2>
ostream & operator << (ostream & o, const Pair<T1, T2> & p)
{
    o << "(" << p.key << "," << p.value << ")";
    return o;
}
int main()
{
    Pair<string, int> student("Tom", 29);
    Pair<int, double> obj(12, 3.14);
    cout << student << " " << obj;
    return 0;
}
程式的輸出結果是:
(Torn, 29) (12, 3.14)

第 13、14 行將函數模板 operator<< 宣告為類別範本 Pair 的友元。在 Visual Studio 中,這兩行也可以用下面的寫法替代:

friend ostream & operator<< <T1, T2>(ostream & o, const Pair<T1, T2> & p);

但在 Dev C ++ 中,替代後編譯就無法通過了。

編譯本程式時,編譯器自動生成了兩個 operator << 函數,它們的原型分別是:

ostream & operator << (ostream & o, const Pair<string, int> & p);
ostream & operator << (ostream & o, const Pair<int, double> & p);

前者是 Pair <string, int> 類的友元,但不是 Pair<int, double> 類的友元;後者是 Pair<int, double> 類的友元,但不是 Pair<string, int> 類的友元。

3. 函數模板作為類的友元

實際上,類也可以將函數模板宣告為友元。程式範例如下:
#include <iostream>
using namespace std;
class A
{
    int v;
public:
    A(int n) :v(n) { }
    template <class T>
    friend void Print(const T & p);
};
template <class T>
void Print(const T & p)
{
    cout << p.v;
}
int main()
{
    A a(4);
    Print(a);
    return 0;
}
程式的輸出結果是:
4

編譯器編譯到第 19 行Print(a);時,就從 Print 模板範例化出一個 Print 函數,原型如下:

void Print(const A & p);

這個函數本來不能存取 p 的私有成員。但是編譯器發現,如果將類 A 的友元宣告中的 T 換成 A,就能起到將該 Print 函數宣告為友元的作用,因此編譯器就認為該 Print 函數是類 A 的友元。

思考題:類還可以將類別範本或類別範本的成員函數宣告為友元。自行研究這兩種情況該怎麼寫。

4. 類別範本作為類別範本的友元

一個類別範本還可以將另一個類別範本宣告為友元。程式範例如下:
#include <iostream>
using namespace std;
template<class T>
class A
{
public:
    void Func(const T & p)
    {
        cout << p.v;
    }
};
template <class T>
class B
{
private:
    T v;
public:
    B(T n) : v(n) { }
    template <class T2>
    friend class A;  //把類別範本A宣告為友元
};
int main()
{
    B<int> b(5);
    A< B<int> > a;  //用B<int>替換A模板中的 T
    a.Func(b);
    return 0;
}
程式的輸出結果是:
5

在本程式中,A< B<int> > 類成為 B<int> 類的友元。