[杂记]关于C++中友元的一些理解

友元旨在让函数或类访问另一个类中的成员, 下面根据友元的类型简单做一下整理.

1. 普通函数作友元

如果一个函数想要访问一个类中的(私有)成员, 则必须将它声明为该类的友元函数. 这种是最简单的情形, 例如下面的代码:

class BaseClass{
    
    
private:
    int value; 
public: 
    BaseClass(int v) : value(v) {
    
    }
    int countOnes() const ;
    friend std::ostream& operator<<(std::ostream& os, BaseClass& bc);
};

int BaseClass::countOnes() const {
    
    
    int v = this->value;
    int num = 0;
    while (v){
    
    
        ++num; 
        v &= (v - 1);
    }
    return num;
   
}

std::ostream& operator<<(std::ostream& os, BaseClass& bc){
    
    
    os << bc.value << "   " << bc.countOnes() << "\n";
    return os;
}


int main(){
    
    
    BaseClass bc (5);
    std::cout << bc;
    system("pause");
    return 0;
}

输出:

5   2

以上代码中重载了<<运算符, 并输出成员value的值和二进制的value中包含多少个1. 这样就可以在类外调cout << .

2. 一个类作为另一个类的友元

如果两个类之间不是继承关系, 也不是一个包含另一个的关系, 而是只是共享某些特征, 可以考虑将一个类作为另一个类的友元. 例如, 我们希望B类能访问A类的成员, 那么B把A当朋友. 当然这种不是双向的(比如大多数感情一样), A并不能访问B的成员. 这种情形, 要在A类的声明中加入friend class B(声明的位置无关紧要), 说明B是A的友元.

例如下面的代码:

class FriendA {
    
    
private:
    int money;
    friend class FriendB;
public:
    FriendA(int m) : money(m) {
    
    }

};

class FriendB{
    
    
private:
    int money;
public:
    FriendB(int m) : money(m) {
    
    }
    bool AmIRich(FriendA& fa) {
    
    
        return this->money > fa.money;
    }
};

int main(){
    
    
    
    FriendA a (100);
    FriendB b (30);

    std::cout << b.AmIRich(a);

    system("pause");
    return 0;
}

输出:

0

这种情况下, FriendB能访问FriendA的所有成员. 但有时候我们不希望这样, 而是规定B的某些函数才能访问A的成员, 这样就有了成员函数作友元.

3. 成员函数作友元

这种情况略微复杂一些. 复杂在我们必须要弄明白类声明的顺序. 例如, 如果FriendB类的一个函数想要获取到FriendA类的一个成员, 如果这么写:

class FriendA {
    
    
private:
    int money;
    std::string address;
    friend std::string FriendB::arriveA(FriendA&);
public:
    FriendA(int m, std::string add) : money(m), address(add) {
    
    }

};

class FriendB{
    
    
private:
    int money;
public:
    FriendB(int m) : money(m) {
    
    }
    std::string arriveA(FriendA& fa) {
    
    
        return fa.address;
    }
};

就会报错:
在这里插入图片描述
为什么呢? 因为编译器看到FriendB::arriveA()函数的时候, 它不认识, 它不知道FriendB类里面有没有这个函数. 那我们如果把FriendB类放到FriendA类前面呢? 也不行, 因为FriendB类里函数定义用到了FriendA. 打破这种僵局的方法是,

  1. B的函数定义有A的参数, 则A的声明必须在B前面, 也即在B之前加上class A; 声明
  2. A中声明B的函数为A的友元, 则B的完整定义必须在A之前
  3. 在A的友元函数的定义中, 编译器必须知道该函数已经是A的友元, 因此要在类定义之后定义. 如果放在B中, 这时候编译器还不知道它是A的友元函数.

总之, 声明定义的原则是: 编译器到这里的时候知道不知道.

综上, 代码修改如下:

class FriendA;  // 首先声明 B中的函数知道有A这么个东西

class FriendB{
    
    
private:
    int money;
public:
    FriendB(int m) : money(m) {
    
    }
    std::string arriveA(FriendA& fa);  // 不能在此定义函数, 因为编译器不知道fa是可访问的
};

class FriendA {
    
    
private:
    int money;
    std::string address;
    friend std::string FriendB::arriveA(FriendA&);  // 声明友元关系, 这时候编译器知道arriveA是B的一个成员了
public:
    FriendA(int m, std::string add) : money(m), address(add) {
    
    }

};

std::string FriendB::arriveA(FriendA& fa){
    
      // 在类外定义函数才行
    return fa.address;
}

int main(){
    
    
    std::string str = "Sdasdjw";
    FriendA fa (2, str);
    FriendB fb (2);
    std::cout << fb.arriveA(fa);

    system("pause");
    return 0;
}

输出:

Sdasdjw

4. 两个类互为友元类或者共有友元函数

感情当然可以是双向的. 如果A是B的友元类, B也是A的友元类, 这种情况直接声明即可.但需要注意的是, 若先声明A后声明B, 则需要将A的涉及到B的函数放在B后面类外定义. 这样编译器才能知道B中的信息. 例如:

class B;

class A{
    
    
private:
    int num;
    friend class B;
public: 
    A(int n) : num(n) {
    
    }
    void showBName(B& b);
};

class B{
    
    
private:
    char* name;
    friend class A;
public: 
    B(char* n) {
    
    
        this->name = new char[strlen(n)];
        strcpy(this->name, n);

    }
    virtual ~B() {
    
     delete[] this->name; }
    void showANum(A& a){
    
    
        std::cout << a.num;
    }
    
};

void A::showBName(B& b){
    
    
    std::cout << b.name;
}

int main(){
    
    
    A a (233);
    
    B b ("sdajw");

    a.showBName(b);
    b.showANum(a);

    system("pause");
    return 0;
}

共有友元函数情形相同, 最好也是在两个类后再定义.

5. 模板类的友元函数

模板类的友元函数大概分三类, 第一类是非模板友元函数. 也即当作最普通的函数看待, 我们需要对每个可能的模板类型都重载一个友元函数.

第二类是约束模板友元函数, 也就是模板类的类型要与类的类型保持一致.

第三类是非约束模板友元函数, 也即友元函数比较独立, 友元函数的类型与类的类型是独立(不同)的.

5.1 非约束模板友元函数

这样的友元在声明时, 需要额外再写一个template, 因为和类的template是不同的.

template<typename T>
class TestClass{
    
    
private:
    T name;
public:
    TestClass(T t) {
    
    this->name = t;}
    template<typename T1> friend void showName(T1&);
};
template<typename T1>
void showName(T1& t){
    
    
    std::cout << t.name << "\n";
}   

 
int main(){
    
    
    TestClass<std::string> tc ("asdwqdw");
    showName(tc);
    system("pause");
    return 0;
}

输出:

asdwqdw

5.2 约束模板友元函数

约束, 即友元函数的类型与模板的类型相同, 这时要在类中将模板具体化, 而且要提前声明, 如下:

template<typename T> void showName(T&);  // 提前声明

template<typename T>
class TestClass{
    
    
private:
    T name;
public:
    TestClass(T t) {
    
    this->name = t;}
    friend void showName<>(TestClass<T>&); // <>是模板具体化, 将函数参数类型与模板绑定
};

template<typename T>
void showName(T& cls){
    
      // 正常声明即可 不要声明成TestClass<T>&
    std::cout << cls.name << "\n";
}

 
int main(){
    
    
    TestClass<std::string> tc ("asdwqdw");
    showName(tc);
    system("pause");
    return 0;
}

输出:

asdwqdw

猜你喜欢

转载自blog.csdn.net/wjpwjpwjp0831/article/details/126884737