友元函数

  • 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到一个类的里面。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,他不是类的成员函数。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • 一个函数可以是多个类的友元函数。
  • 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
  • 友元类的关系是单向的,不具有交换性,比如 A 类是 B 类的友元,但是 B 类不是 A 类的友元。
  • 友元类关系不能传递,如果 A 是 B 的友元, B 是 C 的友元,但是 A 不是 C 的友元。
  • 有时提供了便利。但是友元会增加耦合度(目前可理解为代码模块的关联度),破坏了封装,所以友元不宜多用。

还是按照老方法,我们通过代码来理解这一点:

#include<iostream>
using namespace std;
class A
{
	friend void func();
private:
	int val = 10;
};
void func()
{

	A aa;
	cout << aa.val << endl;
}
int main()
{
	func();
	return 0;
}

    func是全局函数,理论上我们不可以在类外使用类中的私有成员变量,
    但是我们将func声明成了类A的友元函数就可以在func函数中使用私有成员变量

#include<iostream>
using namespace std;
class A
{
	friend void func(const A& aa, const B& bb);
private:
	int val1=1;
};
class B
{
	friend void func(const A& aa, const B& bb);
private :
	int val2 = 2;
};
void func(const A& aa, const B& bb)
{
	cout << aa.val1 << endl;
	cout << bb.val2 << endl;
}
int main()
{
    A aa;
    B bb;
	func();
	return 0;
}

一个全局函数可以声明为多个类的友元函数,就像上面的代码,但是,上面的代码是有一些小问题的:

我们可以看见出现了一堆报错。这里小编就直接给出报错的原因:由于友元函数func在A类中的声明中包含类B,那么编译器在进行编译时就会向上搜索B类的声明或定义,但是B的定义在A的后面,所以搜索不到就会报错。

解决这个报错的方法就是使用前置声明:在A类中友元函数的声明前对B类进行前置声明:

#include<iostream>
using namespace std;

//前置声明:
class B;

class A
{
	friend void func(const A& aa, const B& bb);
private:
	int val1=1;
};

class B
{
	friend void func(const A& aa, const B& bb);
private :
	int val2 = 2;
};

void func(const A& aa, const B& bb)
{
	cout << aa.val1 << endl;
	cout << bb.val2 << endl;
}

int main()
{
	A aa;
	B bb;
	func(aa,bb);
	return 0;
}
#include<iostream>
using namespace std;

class A
{
	friend class B;
private:
	int val1=1;
	int val2=2;
};

class B
{
public:
	void func1(const A& aa)
	{
		cout << aa.val1 << endl;
	}

	void func2(const A& aa)
	{
		cout << aa.val2 << endl;
	}
private:
	int val3=3;
};

如以上代码,如果B类中需要大量使用A的类中的私有成员变量,我们就可以将B类声明为A类的友元类,这样以后,B类就可以随意使用A类中的私有成员变量。

当然,两个类也可以互为友元类:

class A
{
	friend class B;
public:
	void func(const B& bb)
	{
		cout << bb.val3<<endl;

	}
private:
	int val1 = 1;
	int val2 = 2;
};

class B
{
	friend class A;
public:
	void func1(const A& aa)
	{
		cout << aa.val1 << endl;
	}

	void func2(const A& aa)
	{
		cout << aa.val2 << endl;
	}
private:
	int val3 = 3;
};

但是如果我们这样去写代码,编译时又会发生报错:

这是因为我们在类A中使用了类B,而B的定义在A的定义之后,有的朋友可能会说,我们想前面一样加上前置声明class B不就好了吗?

但是,加上前置声明只是告诉编译器有这样一个类,但编译器并不知道这个类中具体的成员,而我们在A中的成员函数中使用了B类的具体细节(成员变量),所以就会报错。如果我们不使用B类中的具体细节,像下面这样,就不会报错:

#include<iostream>
using namespace std;

class B;

class A
{
	friend class B;
public:
	void func(const B& bb)
	{
		//cout << bb.val3<<endl;

	}
private:
	int val1 = 1;
	int val2 = 2;
};

class B
{
	friend class A;
public:
	void func1(const A& aa)
	{
		cout << aa.val1 << endl;
	}

	void func2(const A& aa)
	{
		cout << aa.val2 << endl;
	}
private:
	int val3 = 3;
};

而且我们在写友元类的声明的时候,就已经在A类中对B类进行声明了,是没有必要再去加上前置声明的。

这种情况下,我们只能对类中的成员函数进行声明和定义的分离:

#include<iostream>
using namespace std;

class B;

class A
{
	friend class B;
public:
	void func(const B& bb);
private:
	int val1 = 1;
	int val2 = 2;
};

class B
{
	friend class A;
public:
	void func1(const A& aa);

	void func2(const A& aa);
private:
	int val3 = 3;
};

void A::func(const B& bb)
{
	cout << bb.val3 << endl;
}

void B::func1(const A& aa)
{
	cout << aa.val1 << endl;
}

void B::func2(const A& aa)
{
	cout << aa.val2 << endl;
}

这样一来代码就可以正常编译啦

内部类

  • 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,他只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  • 内部类默认是外部类的友元类。
  • 内部类本质也是一种封装,当 A 类跟 B 类紧密关联,A 类实现出来主要就是给 B 类使用,那么可以考虑把 A 类设计为 B 的内部类,如果放到private/protected位置,那么 A 类就是 B 类的专属内部类,其他地方都用不了。

我们来写代码来感受一下内部类的特点:

//内部类
#include<iostream>
using namespace std;

class A
{
private:
	static int _a1;
	int val = 10;
public:
	//内部类
	class B
	{
	private:
		int val1 = 90;
		static int i;
	};

};

//内部类受到访问限定符和类域的限制,如果我们要使用内部类,就需要指定类域并且突破访问限定符
//对B类中的静态成员变量成员变量进行定义
int A::B::i = 10;

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

如以上代码,B类就是A类的内部类,需要注意的是,B类并不是A的成员,以上代码的运行结果是4就证明了这一点。

内部类相对于定义在全局域的类,他受外部类类域限制和访问限定符限制,所以使用时需要突破访问限定符并且指定类域

不知道大家是否还记得这一题:https://www.nowcoder.com/share/jump/801179881758114193406

我们在上一节中利用了静态成员变量和构造函数的特点对这道题进行了解答,现在,我们可以使用内部类的相关性质来解决这一道题:

#include <regex>
class Solution {


    private:
    static int i;
    static int ret;
//内部类是外部类的友元类
    class Sum
    {
        public:
        Sum()
        {
            ret+=i;
            i++;
        }
    };
    public:
    int Sum_Solution(int n) 
    {
        Sum arr[n];
        return ret;
    }
};
int Solution::i=1;
int Solution::ret=0;

在这一道题中,我们将Sum类设置成内部类,并在Solution类中进行了两个静态变量的声明,我们在Sum_Solution这个函数中创建了一个Sum类型的数组,那么就会调用n次sum的构造函数,基于这个我们就可以对ret进行累加,最后得到结果,我们之所以能在Sum类中使用Solution类中中的私有成员变量,是因为内部类默认为外部类的友元类。

匿名对象

  • 用 类型 (实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名 (实参) 定义出来的叫有名对象。
  • 匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。

我们继续来写代码:

//匿名对象
#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year (year)
		,_month(month)
		,_day(day)

	{

	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//有名对象
	Date d1(2025, 9, 17);
	//匿名对象:可以传参
	Date(2025, 3, 3);
	//匿名对象:也可以不传参
	Date(2025.3, 4);
	int i = 10;
	return 0;
}

在上述代码中我们创建了两个匿名对象,注意匿名对象的生命周期只在当前这一行,我们可以在调试中感受这一点:

当前代码已经创建了第一个匿名对象,此时我们继续进行调试:

可以看到代码运行到析构函数这里,而析构的对象就是第一个匿名对象。

我们学匿名对象有啥用?

如果我们有一个函数的形参是类的对象的引用,那么如果我们呢想要给定缺省值就可以给定匿名对象,需要注意的是匿名对象是具有常性的,当被引用对象是匿名对象的时候,匿名对象的生命周期就不再是他被定义的那一行了,而是引用结束。

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year (year)
		,_month(month)
		,_day(day)

	{

	}
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

void func(const Date& d = Date(1, 2, 3))
{
	cout << "void func(const Date& d = Date(1, 2, 3))" << endl;
}
int main()
{
	//ref作为匿名对象的引用,匿名对象的生命周期延长,与ref的生命周期相等,即从运行到这一行代码直到main函数栈桢销毁
	const Date& ref = Date(3,4,5);

	return 0;
}

 

 

更多推荐