1.概要
- 友元函数就是我给某个函数的特权,可以访问我的私有成员;
- 这个权限在给他权限的类中分配,有多少类给了这个函数特权,这个函数就有多少特权;
- 友元函数是一个独立的函数,不属于任何类;
2.内容
友元函数(friend function)是C++中的一种特殊函数,它不属于任何类,但有权访问一个或多个类的私有(private)或受保护(protected)成员。这种访问权限是通过在类的定义中使用friend
关键字来授予的。
友元函数的特性
-
访问权限:友元函数可以访问其友元类的所有成员,包括私有和受保护的成员。
-
不是成员函数:尽管友元函数可以访问类的私有成员,但它并不是该类的成员函数。它不会出现在类的成员函数列表中,也不会影响类的对象大小或布局。
-
定义位置:友元函数可以在类的定义内部声明,但通常在其外部定义(即实现)。如果友元函数在类的定义内部定义,则它被视为内联函数。
-
参数:友元函数可以通过参数接收其友元类的对象或引用,从而访问该对象的私有成员。
-
不具有传递性:如果一个类是另一个类的友元类,那么它的成员函数可以访问那个类的私有成员。但是,这种友元关系并不传递给其他类,除非特别声明。
-
破坏封装性:使用友元函数会破坏类的封装性,因为外部函数可以访问类的内部实现细节。因此,应谨慎使用友元函数,只在确实需要时才使用。
友元函数的声明和定义
以下是一个简单的例子,展示了如何声明和定义一个友元函数:
#include <iostream>
class Box {
private:
double width;
public:
// 构造函数
Box(double w) : width(w) {}
// 声明友元函数
friend void printWidth(Box box);
};
// 定义友元函数
void printWidth(Box box) {
// 友元函数可以访问Box类的私有成员
std::cout << "Width of box: " << box.width << std::endl;
}
int main() {
Box box(10.0);
// 调用友元函数
printWidth(box);
return 0;
}
在这个例子中,printWidth
函数被声明为Box
类的友元函数。因此,它可以在不违反封装性的情况下访问Box
类的私有成员width
。在main
函数中,我们创建了一个Box
对象,并通过友元函数printWidth
打印了它的宽度。
注意事项
- 友元函数应该只用于确实需要访问类的私有成员的情况。
- 过度使用友元函数会降低代码的可维护性和可读性。
- 友元函数不会改变类的成员函数访问权限,它只提供了一种额外的访问方式。
3.关联知识
1.可以访问多个类的友元函数
在C++中,一个友元函数确实可以被多个类声明为友元,从而允许它访问这些类的私有或受保护成员。这种设计在某些情况下是有用的,特别是当多个类之间需要紧密协作,并且某个函数需要同时访问这些类的内部数据时。
以下是一个简单的例子,展示了如何声明一个友元函数,使其能够访问两个不同类的私有成员:
#include <iostream>
class ClassA; // 前向声明
class ClassB; // 前向声明
// 友元函数声明,它可以访问ClassA和ClassB的私有成员
void accessBothClasses(ClassA& a, ClassB& b);
class ClassA {
private:
int valueA;
public:
ClassA(int val) : valueA(val) {}
// 声明友元函数
friend void accessBothClasses(ClassA& a, ClassB& b);
};
class ClassB {
private:
int valueB;
public:
ClassB(int val) : valueB(val) {}
// 声明友元函数
friend void accessBothClasses(ClassA& a, ClassB& b);
};
// 友元函数定义
void accessBothClasses(ClassA& a, ClassB& b) {
std::cout << "ClassA's valueA: " << a.valueA << std::endl;
std::cout << "ClassB's valueB: " << b.valueB << std::endl;
}
int main() {
ClassA a(10);
ClassB b(20);
// 调用友元函数
accessBothClasses(a, b);
return 0;
}
在这个例子中,accessBothClasses
函数被声明为ClassA
和ClassB
的友元函数。因此,它可以在main
函数中同时访问ClassA
对象的valueA
成员和ClassB
对象的valueB
成员,而不会违反封装性原则。
需要注意的是,虽然友元函数提供了访问类私有成员的便利,但过度使用可能会破坏类的封装性,降低代码的可维护性和可读性。因此,在使用友元函数时应该谨慎,确保只有在确实需要时才使用,并且尽量保持其访问范围的最小化。
2.友元函数可以在内的外部声明吗
友元函数确实可以在类的外部进行声明,但通常这种声明是为了在类的定义之外提供函数的实现或者是在其他文件中进行声明以便跨文件访问。然而,友元函数的“友元”身份,即它访问类私有成员的权限,必须在类的定义内部通过friend
关键字明确授予。
这里有一个关于友元函数在类外部声明的例子:
// 假设我们有两个类ClassA和ClassB的定义,以及一个友元函数friendFunction
class ClassA; // 前向声明ClassA
class ClassB; // 前向声明ClassB
// 在这里声明友元函数,注意此时它还不是任何类的友元
void friendFunction(ClassA& a, ClassB& b);
class ClassA {
private:
int valueA;
public:
ClassA(int val) : valueA(val) {}
// 在ClassA内部声明friendFunction为友元
friend void friendFunction(ClassA& a, ClassB& b);
};
class ClassB {
private:
int valueB;
public:
ClassB(int val) : valueB(val) {}
// 在ClassB内部也声明friendFunction为友元
friend void friendFunction(ClassA& a, ClassB& b);
};
// 友元函数的实现,在类的外部
void friendFunction(ClassA& a, ClassB& b) {
// 可以访问ClassA和ClassB的私有成员
std::cout << "ClassA's valueA: " << a.valueA << std::endl;
std::cout << "ClassB's valueB: " << b.valueB << std::endl;
}
int main() {
ClassA a(10);
ClassB b(20);
// 调用友元函数
friendFunction(a, b);
return 0;
}
在这个例子中,friendFunction
首先在类的外部被声明为一个普通的函数。然后,在ClassA
和ClassB
的定义内部,通过friend
关键字将它声明为这两个类的友元。这样,friendFunction
就能够访问ClassA
和ClassB
的私有成员了。
总结来说,友元函数的“友元”身份必须在类的内部声明,但它的实现(即函数体)可以在类的外部。同时,友元函数的声明(不带friend
关键字的普通函数声明)也可以在类的外部进行,以便在其他地方引用或实现。
3.友元函数声明在类内,可以通过类的对象访问友元函数吗
友元函数虽然是在类内声明的,但它并不属于类的成员函数,因此不能通过类的对象来直接访问友元函数。友元函数是一个独立的函数,它之所以能在类内被声明为友元,是为了赋予它访问该类私有成员的权限。
友元函数的调用方式与普通的非成员函数相同,即直接通过函数名来调用,并传递相应的参数。它不需要通过类的对象来调用,因为它不是类的成员函数。
下面是一个例子来说明这一点:
#include <iostream>
class Box {
private:
double width;
public:
Box(double w) : width(w) {}
// 声明友元函数
friend void printWidth(Box box);
};
// 定义友元函数
void printWidth(Box box) {
std::cout << "Width of box: " << box.width << std::endl;
}
int main() {
Box box(10.0);
// 直接调用友元函数,而不是通过box对象
printWidth(box);
// 下面的调用方式是错误的,因为printWidth不是Box类的成员函数
// box.printWidth(); // 错误!
return 0;
}
在这个例子中,printWidth
函数被声明为Box
类的友元函数,因此它可以访问Box
类的私有成员width
。但是,在main
函数中,我们是直接通过函数名printWidth
来调用这个友元函数的,并传递了一个Box
对象作为参数。我们没有通过box
对象来调用printWidth
函数,因为printWidth
不是Box
类的成员函数。
所以,总结一下:友元函数不能通过类的对象来访问,而是像普通的非成员函数一样直接调用。
4.友元函数的参数,可以是普通变量吗
友元函数的参数可以是普通变量,而不仅仅是类的对象或类的引用。友元函数作为特殊的函数,其特殊性在于它能够访问授予它友元身份的类的私有或受保护成员,而不在于其参数类型。
友元函数的参数类型取决于函数需要执行的操作以及它要访问的数据。如果友元函数需要处理或访问类的实例,那么它可能会接受一个类的对象或引用作为参数。但是,如果友元函数执行的操作与类的实例无关,或者它只需要访问类的静态成员或执行一些与类相关的全局操作,那么它的参数就可以是普通变量或其他类型的数据。
下面是一个例子,展示了友元函数接受普通变量作为参数的情况:
#include <iostream>
class Box {
private:
static int totalBoxes; // 静态成员变量
public:
Box() {
totalBoxes++;
}
// 声明友元函数,它接受一个普通变量作为参数
friend void printTotalBoxes(int multiplier);
};
// 定义静态成员变量
int Box::totalBoxes = 0;
// 定义友元函数
void printTotalBoxes(int multiplier) {
// 友元函数可以访问Box类的静态成员
std::cout << "Total boxes (multiplied by " << multiplier << "): "
<< Box::totalBoxes * multiplier << std::endl;
}
int main() {
Box box1;
Box box2;
Box box3;
// 调用友元函数,并传递一个普通变量作为参数
printTotalBoxes(2);
return 0;
}
在这个例子中,printTotalBoxes
函数被声明为Box
类的友元函数,并且它接受一个普通变量multiplier
作为参数。这个函数的作用是打印出Box
类静态成员变量totalBoxes
的值,并将其乘以multiplier
参数。在main
函数中,我们创建了三个Box
对象,然后调用printTotalBoxes
函数,并传递一个整数作为参数。
因此,友元函数的参数可以是普通变量,这取决于函数的设计和需求。
5.友元函数可以访问的类的私有成员,这些类,必须是友元函数的参数中的类吗
友元函数可以访问的类的私有成员,并不局限于友元函数参数中传递的类实例。友元函数能够访问所有将其声明为友元的类的私有成员,无论这些类的实例是否作为参数传递给友元函数。
友元函数的访问权限是基于类级别的,而不是基于对象级别的。一旦一个函数被某个类声明为友元,这个函数就可以访问该类的所有私有和保护成员,无论它是否接收了该类的对象或引用作为参数。
下面是一个例子,说明友元函数可以访问没有作为参数传递的类的私有成员:
#include <iostream>
class ClassA {
private:
int valueA;
public:
ClassA(int val) : valueA(val) {}
// 声明友元函数
friend void accessClasses();
};
class ClassB {
private:
int valueB;
public:
ClassB(int val) : valueB(val) {}
// 也声明同一个友元函数
friend void accessClasses();
};
// 友元函数定义,它没有接收任何参数
void accessClasses() {
// 创建ClassA和ClassB的对象
ClassA a(10);
ClassB b(20);
// 友元函数可以访问ClassA和ClassB的私有成员
std::cout << "ClassA's valueA: " << a.valueA << std::endl;
std::cout << "ClassB's valueB: " << b.valueB << std::endl;
}
int main() {
// 调用友元函数,它没有接收任何参数
accessClasses();
return 0;
}
在这个例子中,accessClasses
函数被ClassA
和ClassB
都声明为友元函数。即使accessClasses
函数没有接收ClassA
或ClassB
的对象作为参数,它仍然可以在函数体内创建这些类的对象,并访问它们的私有成员。
因此,友元函数可以访问的类的私有成员并不局限于友元函数参数中的类实例。只要一个类将某个函数声明为友元,该函数就可以访问该类的私有成员,无论上下文如何。