【C++】friend 友元和内部类

[本节内容]

友元函数中的友元类和内部类

注意:友元函数本质上是提供了一种突破封装的方式,有时会为我们带来更多的便利。但是友元函数的使用会增加耦合度,破坏类的封装性,所以友元不宜多用。

1.友元函数的背景

在学习友元函数之前,如果我们想要输入一个完整的对象,即使这个对象成员比较简单(例如只有一个成员a),或者对象成员本身构成一个整体(例如日期),目前我们只能通过 类.成员访问再用 cout<< 输出对象的全部成员,从而达到预期。但如果我们想直接用cout<<输出一个对象呢?
我们能不能完成呢?直接看下面这段代码:

class A
{
public:
	A(int a = 10) //对象A的构造函数
	{
		_a = a;
	}
private:
	int _a;
};
int main()
{
	A  a;
	cout << a ;      
	return 0;
}

在这里插入图片描述

大家运行后会发现编译不通过,因为 cout << a; 这里的a是一个对象,无法用<<输出。这时我们会想到尝试重载operator<<去输出这个对象,于是可以对代码进行修改:

class A
{
public:
	
	A(int a = 10)
	{
		_a = a;
	}
void operator<<(ostream &out)   //重载<<
	{
		out << _a;    //输出a的值
	}
private:
	int _a;
};

int main()
{
	A a;
	cout << a ;      
	return 0;
}

在这里插入图片描述

cout << a ;运行后大家会发现,还是编译错误!!因为在使用重载的<<运算符时,我们将输出流对象 cout作为了运算符的第一个参数,但实际上成员函数规定函数中隐含的this指针(即左操作数)才应该是这个重载的运算符的第一个参数。此时两个参数同时在抢占第一个参数的位置,所以导致程序编译不通过。
这时,如果我们改为用a.operator<<(cout) ;或者 a << cout;完成输出这个对象内容的效果是没问题的(如上图),但是这样使用<<与我们习惯中的用法相反。所以我们将operator<<重载为成员函数不是最好的办法。

那么如果你为了让this指针消失,想要把operator<< 重载成全局函数,那不用实践都知道是错误的,因为全局函数在类外无法访问类内的成员。那么此时还有一个解决办法,就是把这个类外函数operator<< 写成类的友元函数~~
具体实现的做法看下面的代码:

#include<iostream>
using namespace std;

class A
{
public:
	friend ostream & operator<<(ostream& _cout, const A& a); //类内声明这是自己的友元函数
	A(int a = 10)
	{
		_a = a;
	}
private:
	int _a;
};
ostream & operator<<(ostream& _cout, const A& a)  //类外重载<<,目的是可自己定义参数顺序
{
	_cout << a._a;   // 规定<<的使用规则为:cout是第一个参数,a成员是第二个参数
	return _cout;
}
int main()
{
	A a;
	cout<<a;  //此时可正确输出a对象中成员_a的值
	return 0;
}

在这里插入图片描述

由此可以看出,友元函数是一个能直接访问类的私有成员的非成员函数,简单说它是一个普通函数,定义在类外部所以也不属于任何类。使用时只需要在类的内部加上friend关键字进行声明,即可访问此类的所有成员。

格式:      friend 类型 函数名 (形式参数); 

  注:友元函数中没有this指针,它的参数有以下情况:
    a.要访问非静态成员时,需要对象做参数
    b.要访问静态成员或全局变量时,直接成员做参数  

下面我将演示如何使用cin>>和cout<<输出一个日期类。
实现输出Date类,我们只需要在Date类中,声明operator>>和operator<<的重载函数为友元函数即可。
使用友元函数相关的操作如下,模拟完整的Date类代码单独在一篇博客中,有需要的自取 Date类的完整代码

class Date
{
public:
	friend ostream &operator <<(ostream& _cout, const Date& d);     //输出,所有加const表示只读数据
	friend istream &operator >>(istream& _cin,  Date& d);          //输入,允许改写数据
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
ostream &operator<<(ostream &_cout, const Date& d)
{
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}
istream &operator>>(istream &_cin, Date& d)
{
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d(2018,1,1);
	cin >> d;
	cout << d << endl;
	system("pause");
	return 0;
}

在这里插入图片描述

注意:

1)友元函数不受类的访问限定符限制,所以友元的声明无论放在哪里都可以,它的调用也与一般函数相同。
2)一个函数可以是多个类的友元函数,只要在每个类内分别声明即可。
3)友元函数不是类的成员函数!!!
4)友元函数不能用const修饰。
5) 友元函数无this指针,必须通过对象名访问成员即 对象.成员方式

2.友元类

友元类与友元函数不同。友元类中所有的成员函数都是另一个类的友元函数,都可以访问到另一个类的非公有成员/函数。

格式:   friend class 类名;(类名是程序中已定义过的类)
注意:
 1.友元关系是单向的,不具有交换性
 例如:在B类中声明A类为其友元类,那么在A类中可以直接访问B的私有成员,但B无法访问A的成员。
 
 2.友元关系不能传递
  例如: B是A的友元类,C是B的友元类,无法说明C是A的友元类。

下面代码中Date和Time就是友元类:

class Date;           //类的前置声明
class Time
{
	friend class Date;     //声明为Date的友元类,即Date类可直接访问Time类的私有成员
public:
	Time(int hour=11,int minute=59,int second=59)
		:_hour(hour)
		, _minute(minute)
		, _second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	void setTimeOfDate(int hour, int minute, int second)
	{  //可以直接访问时间类的私有成员
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
	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 d(2019,3,4);
	d.setTimeOfDate(16, 17, 18);
	d.Print();
	return 0;
}

在这里插入图片描述
在面试时,我们会遇到很多与友元函数相关的问题,下面有一些我自己的额总结,有需要的童鞋可以看下:使用友元函数时常见的问题

3.内部类
3.1 概念

如果一个类定义在另一个类的内部,这个内部的类就叫内部类。
内部类也是一个独立的类,它并不属于外部类,所以无法通过外部类的对象去访问内部类成员,外部类对内部类没有任何优先的访问权限。
注意: 这里的内部类是外部类的友元类,即内部类可以通过外部类的对象参数访问外部类中的成员。但是外部类不是内部类的友元类。

3.2 特性

1.内部类声明在外部类的public,protected,private都一样。
2.内部类可以直接访问外部类的static成员和枚举成员,不需要外部类的对象/类名。
3.sizeof(外部类)=外部类,与内部类无关。

class A
{
private:
	static int _k;              //静态成员
	int _h;                     //私有成员
public:
	A()
	{
		_h = 10;
	}
	class B          //B是A的内部类,B可直接访问A的成员变量
	{
	public:
		void fun(const A&a)
	{
		cout << _k <<"  "<< a._h << endl;//_k是静态成员可直接访问,_h是私有成员要通过对象的调用
	  }
	};
};
int A::_k = 10;               //静态成员必须在类外初始化
int main()
{
	A::B b;                                     // :: 访问限定符,表明B在A内
	b.fun(A());                           //未定义A类的对象,所以函数参数用A类构造一个对象
	cout << sizeof(A) << endl;  //4  static存在静态区,B是内部类不占空间,故sizeof(int _h)=4
	cout << sizeof(A::B) << endl;       //1   B是空类,故为1
	system("pause");
	return 0;
}

在这里插入图片描述

以上就是我关于友元类和内部类学习的一些内容啦,后续会继续补充。喜欢小编的写作风格可以关注小编~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值