当前位置: 首页 > news >正文

C++入门自学Day4-- c++类与对象(友元)

c++类与对象往期回顾:

        c++类与对象(类的初始化和静态成员)

        c++类与对象(赋值运算符与拷贝构造)

        c++类与对象(拷贝构造)

        c++类与对象(构造和析构函数)

        c++类与对象(初识2)

        c++类与对象(初识)

  

友元

   一、友元的定义

        友元是通过在类中使用关键字 friend 声明的函数、另一个类或类的成员函数,它可以访问该类的私有和保护成员,即使它不属于该类。

 二、为什么需要友元?

        原因总结

  1. 1、跨类访问私有成员:在两个类需要深度交互但又不希望暴露所有成员变量时,可以使用友元访问特定内容。

  2. 2、重载运算符(如 I/O)时需要访问私有成员:例如重载 <<、>>。

  3. 3、提高效率:比使用公共接口函数访问更直接,开销小。

  4. 4、简化设计:避免为单个函数暴露大量 getter/setter。

三、友元的三种形式

        1、友元函数(最常见)

        一个独立函数被声明为某个类的友元,可以访问该类的私有成员。

友元函数的简单实现
//类的定义
class Date{friend void Print(const Date& d);//友元函数的声明public:Date(int year = 2025,int month = 8,int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};
//友元函数的定义
void Print(const Date& d){cout<<d._year<<"-"<<d._month<<"-"<<d._day<<endl;// 访问私有成员
}int main(){Date d;Print(d);
}

类的运算符"<<"的友元实现

        cout的类型是系统的ostream类,我们之前是如何使用我们的“<<”的呢?

 cout<<d._year<<"-"<<d._month<<"-"<<d._day<<endl;

那么如何实现该类的运算符重载呢?

       "<<"运算符的两个参数:1、Date日期类,2、ostream类。其中ostream类是第一个操作数-->第一个参数,Date日期类是第二个操作数-->第二个参数。

如果把其放在类中实现:

class Date{friend void Print(const Date& d);public:Date(int year = 2025,int month = 8,int day = 1){_year = year;_month = month;_day = day;}void operator<<(ostream& out){out<<_year<<"-"<<_month<<"-"<<_day<<endl;}private:int _year;int _month;int _day;
};
void Print(const Date& d){cout<<d._year<<"-"<<d._month<<"-"<<d._day<<endl;
}

主函数调用:

int main(){Date d;d<<cout;
}

这里我们在使用该运算符“<<”,需要把d作为左操作数,cout作为右操作数。则与我们默认的<<用法“cout<<d._year<<"-"<<d._month<<"-"<<d._day<<endl;”相反,那么这里我们就需要通过友元来实现:


友元实现: “记得传入变量的引用”

class Date{friend ostream& operator<<(ostream& out,Date& d);public:Date(int year = 2025,int month = 8,int day = 1){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};ostream& operator<<(ostream& out,Date& d){out<<d._year<<"-"<<d._month<<"-"<<d._day<<endl;return out;
}

主函数调用:

int main(){Date d;cout<<d;
}

输出描述:

2025-8-1

利用友元实现就可以将 ostream类作为左操作数。


2、 友元类

        一个类被另一个类声明为友元类,可以访问后者的私有成员。

代码实现:

        这里我们定义了Date是Time的好朋友,所以Date可以访问Time的私有变量。注意顺序不要搞混了。

class Time{friend class Date;public:Time(int hour = 16,int minute = 11,int second = 59):_hour(hour),_minute(minute),_second(second){}private:int _hour;int _minute;int _second;
};class Date{public:Date(int year = 2025,int month = 8,int day = 1):_year(year),_month(month),_day(day){_t._hour = 12;}void Print(){cout<<_year<<"-"<<_month<<"-"<<_day<<"-"<<_t._hour<<"-"<<_t._minute<<"-"<<_t._second<<endl;}private:int _year;int _month;int _day;Time _t;
};

主函数调用:


int main(){Date d1;Time t;d1.Print();
}

输出描述:

2025-8-1-12-11-59


3、 友元成员函数

        某个类的某个成员函数是另一个类的友元。

代码实现:

class A;class B {
public:void printA(const A& a);  // 只声明
};class A {
private:int value = 123;friend void B::printA(const A&);  // 指定 B 的某个成员函数为友元
};void B::printA(const A& a) {std::cout << a.value << std::endl;
}

四、友元的特性

特性

说明

单向性

A 是 B 的友元,不代表 B 是 A 的友元

非继承性

子类不会继承父类的友元关系

破坏封装性

友元机制打破了类的私有保护机制,所以应慎用

不能被对象调用

友元函数不是类成员,不能通过对象来调用它(除非类中有转发函数)


五、内部类(天生的友元类)

        1、内部类的定义:

        内部类是定义在另一个类内部的类,属于外部类的成员类型之一。

代码实现:

class Outer {
private:int secret = 42;public:class Inner {  // 内部类public:void show() {std::cout << "I'm Inner class!" << std::endl;}};
};

那么如何来定义一个内部类对象呢?

Outer::Inner obj;
obj.show();

        2、内部类的注意事项:

        ❌ 不能直接访问外部类!内部类不是外部类的成员函数,它只是作用域在外部类中,所以默认无法访问外部类的私有成员,除非你显式地授予权限。      

方法:friend class Inner;  // 👈 把 Inner 声明为友元  把内部类声明为友元类即可


六、使用友元的注意事项

  • ✔️ 控制最小访问范围:只给确实需要访问的函数/类加 friend。

  • ❌ 不要滥用友元:频繁使用友元会破坏类的封装性,使得代码耦合度高,难以维护。

  • 📦 与封装结合使用:友元应作为封装的补充机制,而非替代品。


七、总结一句话

        友元机制是 C++ 提供的一种“例外访问权”,用于在特定情况下允许外部函数或类访问某个类的私有或保护成员,以提高效率、简化操作、增强协作,但使用时应谨慎控制访问范围,避免滥用破坏封装性。

http://www.dtcms.com/a/310487.html

相关文章:

  • JavaScript语法树简介:AST/CST/词法/语法分析/ESTree/生成工具
  • 水果忍者经典版:离线版,永久无限制!!
  • IPD数字化的困难与解法
  • 如何在 VMware Workstation 虚拟机中利用 Nvidia 显卡的硬件加速功能
  • 利用 AI 在 iPhone 上实现 App 文本情绪价值评估(下)
  • 浅谈低代码平台涉及的一些技术选型
  • 【BUUCTF系列】[ACTF2020 新生赛]Exec 1
  • 用 Ubuntu 22.04 (Jammy) 的 MongoDB 源
  • Skia-如何渲染文本(上)
  • Android中页面生命周期变化
  • 多人命题系统
  • Qt 开发自动化测试框架搭建
  • 【Open3D】基础操作之三维变换
  • Nginx跨域问题与 MIME 类型错误深度排错指南:解决 MIME type of “application/octet-stream“ 报错
  • 【LeetCode刷题指南】--单值二叉树,相同的树
  • 《人形机器人的觉醒:技术革命与碳基未来》——类人关节设计:柔性驱动革命之液压人工肌肉
  • python中appium
  • 在PyCharm中将现有Gitee项目重新上传为全新项目
  • WordPress 前端显示英文,后台显示中文的设置
  • CH7216A USB Type C上的 DisplayPort 转 HDMI 2.0 转换器【CH7216A-BF】
  • JSON 对象在浏览器中顺序与后端接口返回不一致的问题
  • 基于cygwin或msmy的windows环境下的jupyterlab的C内核搭建
  • Lipschitz连续函数
  • Flutter 替换镜像源
  • 牛客——接头密匙
  • .net依赖注入框架 Autofac和MEF的对比
  • 如何在企业微信中打开外部网页或者自己开发的本地网页
  • vue+ts 基础面试题 (四)
  • 『React』 组件通信全攻略
  • 工业环境中无人叉车安全标准深度解析