11.友元函数

11.面向对象-封装-友元函数


11.1 知识点

什么是友元

  • 关键字定义friend 是C++中用于打破类封装性的关键字。

  • 核心作用:允许外部函数或类访问当前类的 私有(private)保护(protected) 成员。

  • 封装性影响
    从面向对象封装原则来看,私有/保护成员本应禁止外部访问。友元机制打破了封装性,可能导致数据安全性降低,因此使用时需谨慎评估场景。

友元函数的概念

友元函数是一个普通函数,虽然需要在类中声明,但是它不属于类的成员
通过友元函数可以访问目标类的私有(private)和保护(protected)成员

友元函数的使用

  • 规则:
    1. 友元函数必须声明在目标类内部
    2. 需要在函数前面加上 friend 关键字
    3. 友元函数一般会有一个参数,参数类型为目标类对象
      (因为友元函数并不是类的成员函数,无法通过 this 指针访问数据)
#pragma once

class Lesson11
{
public:
    int k = 10;

private:
    int i = 0;
    void test();

protected:
    int j = 10;
    void test2();

    // 友元函数声明 需要在目标类中声明 并且在前面加上friend关键字
    // 但是这个函数并不是该类的成员
    friend void ChangePrivate(const Lesson11 l);
};
#include "Lesson11.h"
#include <iostream>
using namespace std;

void Lesson11::test()
{

}

void Lesson11::test2()
{

}

// 友元函数 是不属于我们类的 所以没有指定 Lesson11::
// 它是一个全局函数 因为我们是定义在 cpp 中 在任何地方都可以使用它
void ChangePrivate(Lesson11 l)
{
    // 公共私有保护的都能访问和修改
    l.i = 10;
    l.j = 20;
    l.k = 30;
    cout << l.i << endl;
    cout << l.j << endl;
    cout << l.k << endl;
    l.test();
    l.test2();
}
Lesson11 l;
ChangePrivate(l);

友元函数传参的几种方式

  1. 类型& 参数名
    这种方式可读可改成员
  2. const 类型& 参数名
    这种方式比较适用于只读不改,通过引用传递还可以避免不必要的拷贝,并且可以传常量引用和普通引用
  3. 类型 参数名
    不推荐,按值传递,会有拷贝
  • 注意:允许传多个参数,根据需求来定,但是至少得有目标对象参数才有意义
#pragma once

class Lesson11
{
public:
    int k = 10;

    // 属于类的
    void ChangePrivateI(int i)
    {
        this->i = i;
    }

private:
    int i = 0;
    void test();

protected:
    int j = 10;
    void test2();

    // 友元函数声明 需要在目标类中声明 并且在前面加上 friend 关键字
    // 但是这个函数并不是该类的成员
    friend void ChangePrivate(const Lesson11 l);
    friend void ChangePrivate2(const Lesson11& l);

    // 属于全局的
    friend void ChangePrivateI(Lesson11& l, int i)
    {
        l.i = i;
    }
};
#include "Lesson11.h"
#include <iostream>
using namespace std;

void Lesson11::test()
{

}

void Lesson11::test2()
{

}

// 友元函数 是不属于我们类的 所以没有指定 Lesson11::
// 它是一个全局函数 因为我们是定义在 cpp 中 在任何地方都可以使用它
void ChangePrivate(Lesson11 l)
{
    // 公共私有保护的都能访问和修改
    l.i = 10;
    l.j = 20;
    l.k = 30;
    cout << l.i << endl;
    cout << l.j << endl;
    cout << l.k << endl;
    l.test();
    l.test2();
}

void ChangePrivate2(const Lesson11& l)
{
    // 公共私有保护的都能访问和修改 

    // 加了const后只能访问和调用const修饰的成员和方法了
    /*l.i = 10;
    l.j = 20;
    l.k = 30;*/
    cout << l.i << endl;
    cout << l.j << endl;
    cout << l.k << endl;
    /*l.test();
    l.test2();*/
}

总结

友元函数本质上是一个特殊的全局函数,该全局函数需要在想要做朋友的类中进行友元声明(前面加上 friend。该全局函数就可以在内部访问到对应类对象的私有或保护的成员。但是一定记住需要将类对象以参数的形式传递进去 因为在内部无法通过 this 去访问成员。


11.2 知识点代码

Lesson11.h

#pragma once
class Lesson11
{
public:
    int k = 10;
    //属于类的
    void ChangePrivateI(int i)
    {
        this->i = i;
    }
private:
    int i = 0;
    void test();
protected:
    int j = 10;
    void test2();

    //友元函数声明 需要在目标类中声明 并且在前面加上friend关键字
    //但是这个函数并不是该类的成员
    friend void ChangePrivate(const Lesson11 l);

    friend void ChangePrivate2(const Lesson11& l);

    //属于全局的
    friend void ChangePrivateI(Lesson11& l, int i)
    {
        l.i = i;
    }
};

Lesson11.cpp

#include "Lesson11.h"
#include <iostream>
using namespace std;
void Lesson11::test()
{

}

void Lesson11::test2()
{

}

//友元函数 是不属于我们类的 所以没有指定Lesson11::
//它是一个全局函数 因为我们是定义在cpp中 在任何地方都可以使用它
void ChangePrivate(Lesson11 l)
{
    // 公共私有保护的都能访问和修改
    l.i = 10;
    l.j = 20;
    l.k = 30;
    cout << l.i << endl;
    cout << l.j << endl;
    cout << l.k << endl;
    l.test();
    l.test2();

}


void ChangePrivate2(const Lesson11& l)
{
    // 公共私有保护的都能访问和修改 

    // 加了const后只能访问和调用const修饰的成员和方法了
    /*l.i = 10;
    l.j = 20;
    l.k = 30;*/
    cout << l.i << endl;
    cout << l.j << endl;
    cout << l.k << endl;
    /*l.test();
    l.test2();*/

}

Lesson11_面向对象_封装_友元函数.cpp

#include <iostream>
#include "Lesson11.h"
int main()
{
    #pragma region 知识点一 什么是友元
    //友元是C++中的一个关键字:friend(朋友)
    //使用友元机制允许 
    //我们在外部访问某个类的
    //私有(private)或保护(protected)的成员
    // 
    //从封装性的角度来看,私有和保护的成员不应该被外部访问到
    //因此友元它一定程度上破坏了封装性,需要谨慎使用
    #pragma endregion

    #pragma region 知识点二 友元函数的概念
    //友元函数是一个普通函数,虽然需要在类中声明,但是它不属于类的成员
    //通过友元函数可以访问目标类的私有(private)和保护(protected)成员
    #pragma endregion

    #pragma region 知识点三 友元函数的使用

    //规则:
    //1.友元函数必须声明在目标类内部
    //2.需要在函数前面加上friend关键字
    //3.友元函数一般会有一个参数,参数类型为目标类对象
    // (因为友元函数并不是类的成员函数,无法通过this指针访问数据)
    Lesson11 l;
    ChangePrivate(l);

    #pragma endregion

    #pragma region 知识点四 友元函数传参的几种方式
    //1.类型& 参数名
    //  这种方式可读可改成员
    //2.const 类型& 参数名
    //	这种方式比较适用于只读不改,通过引用传递还可以避免不必要的拷贝,并且可以传常量引用和普通引用
    //3.类型 参数名
    //	不推荐,按值传递,会有拷贝

    //注意:允许传多个参数,根据需求来定,但是至少得有目标对象参数才有意义
    #pragma endregion

    //总结
    //友元函数
    //本质上是一个特殊的全局函数,该全局函数需要在想要做朋友的类中进行友元声明(前面加上 friend)
    //该全局函数就可以在内部访问到对应类对象的私有或保护的成员
    //但是一定记住需要将类对象以参数的形式传递进去 因为在内部无法通过this去访问成员
}

11.3 练习题

简要说明友元函数有什么作用

友元函数的主要作用就是允许非成员函数访问类的私有的(private)和保护的(protected)成员
在今后的开发中,比较常见的友元函数应用场景是:

  1. 日志系统,我们可以利用友元函数获取到私有保护的信息,将它们记录到日志系统的文档中,方便我们之后排查问题
  2. 调试工具,我们获取到私有保护成员,便于定位问题
  3. 根据自己的逻辑需求决定是否使用友元函数

实现 friend bool isEqual(const MyClass& obj1, const MyClass& obj2); 友元函数,使其能够比较两个 MyClass 对象的数据是否相等

#pragma once
#include <iostream>

using namespace std;
class A
{
private:
    int i = 10;
    string str = "韬老师";
public:
    float f = 4.0f;
protected:
    bool b = false;

public:
    A(int i, string str, float f, bool b);
    friend bool isEqual(const A& obj1, const A& obj2);
};
#include "A.h"

A::A(int i, string str, float f, bool b)
{
    this->i = i;
    this->str = str;
    this->f = f;
    this->b = b;
}

bool isEqual(const A& obj1, const A& obj2)
{
    // 判断两个对象中的所有成员变量里面的值是不是都相同
    // 如果有指针,应该判断指针中存储的值是否一致
    // 如果有数组,应该先判断长度是否一致,再判断里面的元素是否一致
    if (obj1.i == obj2.i &&
        obj1.str == obj2.str &&
        obj1.f == obj2.f &&
        obj1.b == obj2.b)
    {
        return true;
    }

    return false;
}
A a1 = A(11, "韬老狮", 2.4f, false);
A a2 = A(12, "韬老狮", 2.4f, false);
if (isEqual(a1, a2))
    cout << "两个对象的值相等" << endl;
else
    cout << "两个对象的值不相等" << endl; // 两个对象的值不相等

尝试将 B 类中的成员函数作为 A 类的友元函数

#pragma once
class A;

class B
{
public:
    void GetAInfo(const A& a);
};
#pragma once
#include <iostream>
#include "B.h"
using namespace std;
class A
{
private:
    int i = 10;
    string str = "韬老师";
public:
    float f = 4.0f;
protected:
    bool b = false;

public:
    A(int i, string str, float f, bool b);
    friend bool isEqual(const A& obj1, const A& obj2);
    friend void B::GetAInfo(const A& a);
};
#include "B.h"
#include "A.h"
#include <iostream>
using namespace std;
void B::GetAInfo(const A& a)
{
    cout << a.i << endl;
    cout << a.f << endl;
    cout << a.str << endl;
    cout << a.b << endl;
}
A a3 = A(666, "韬老狮666", 1.6f, false);
B b;
b.GetAInfo(a3);
// 666
// 1.6
// 韬老狮666
// 0

11.4 练习题代码

A.h

#pragma once
#include <iostream>
#include "B.h"
using namespace std;
class A
{
private:
    int i = 10;
    string str = "韬老师";
public:
    float f = 4.0f;
protected:
    bool b = false;

public:
    A(int i, string str, float f, bool b);
    friend bool isEqual(const A& obj1, const A& obj2);
    friend void B::GetAInfo(const A& a);
};

A.cpp

#include "A.h"

A::A(int i, string str, float f, bool b)
{
    this->i = i;
    this->str = str;
    this->f = f;
    this->b = b;
}

bool isEqual(const A& obj1, const A& obj2)
{
    //判断两个对象中的所有成员变量里面的值 是不是都相同
    //如果有指针,应该判断指针中存储的值是否一致
    //如果有数组,应该先判断长度是否一致,再判断里面的元素是否一致
    if (obj1.i == obj2.i &&
        obj1.str == obj2.str &&
        obj1.f == obj2.f &&
        obj1.b == obj2.b)
    {
        return true;
    }

    return false;
}

B.h

#pragma once
class A;

class B
{
public:
    void GetAInfo(const A& a);
};

B.cpp

#include "B.h"
#include "A.h"
#include <iostream>
using namespace std;
void B::GetAInfo(const A& a)
{
    cout << a.i << endl;
    cout << a.f << endl;
    cout << a.str << endl;
    cout << a.b << endl;
}

Lesson11_练习题.cpp

#include <iostream>
#include "A.h"
#include "B.h"
int main()
{
    #pragma region 练习题一
    //请简要说明友元函数有什么作用
    // 
    //友元函数的主要作用就是允许非成员函数访问类的私有的(private)和保护的(protected)成员
    //在今后的开发中,比较常见的友元函数应用场景是:
    //1.日志系统,我们可以利用友元函数获取到私有保护的信息,将他们记录到日志系统的文档中,方便我们之后排查问题
    //2.调试工具,我们获取到私有保护成员,我定位问题
    //3.根据自己的逻辑需求决定是否使用友元函数
    #pragma endregion

    #pragma region 练习题二
    //请实现 friend bool isEqual(const MyClass & obj1, const MyClass & obj2); 友元函数
    //使其能够比较两个 MyClass 对象的数据是否相等:
    A a1 = A(11, "韬老狮", 2.4f, false);   
    A a2 = A(12, "韬老狮", 2.4f, false);
    if (isEqual(a1, a2))
        cout << "两个对象的值相等" << endl;
    else
        cout << "两个对象的值不相等" << endl;//两个对象的值不相等
    #pragma endregion

    #pragma region 练习题三

    //尝试将B类中的成员函数作为A类的友元函数

    A a3 = A(666, "韬老狮666", 1.6f, false);
    B b;
    b.GetAInfo(a3);
    //666
    //1.6
    //韬老狮666
    //0

    #pragma endregion
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏