C++入门自学Day4-- c++类与对象(友元)
c++类与对象往期回顾:
c++类与对象(类的初始化和静态成员)
c++类与对象(赋值运算符与拷贝构造)
c++类与对象(拷贝构造)
c++类与对象(构造和析构函数)
c++类与对象(初识2)
c++类与对象(初识)
友元
一、友元的定义
友元是通过在类中使用关键字 friend 声明的函数、另一个类或类的成员函数,它可以访问该类的私有和保护成员,即使它不属于该类。
二、为什么需要友元?
原因总结
-
1、跨类访问私有成员:在两个类需要深度交互但又不希望暴露所有成员变量时,可以使用友元访问特定内容。
-
2、重载运算符(如 I/O)时需要访问私有成员:例如重载 <<、>>。
-
3、提高效率:比使用公共接口函数访问更直接,开销小。
-
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++ 提供的一种“例外访问权”,用于在特定情况下允许外部函数或类访问某个类的私有或保护成员,以提高效率、简化操作、增强协作,但使用时应谨慎控制访问范围,避免滥用破坏封装性。