C++ 中的友元是一种机制,可以授予其它类或函数来访问你的类的非公有成员。
被授权的类或函数被称为友元类或友元函数。
在 C++ 中,可以通过在类中声明友元函数或友元类,或者在类的外部使用 `friend` 关键字来创建友元关系。
友元的作用是提供对类或函数私有成员的访问权,以解决某些数据封装问题。
如果某些数据成员或成员函数被声明为私有的,那么在普通情况下,只有该类的成员函数可以访问它们。
但有时,比如在代码重构或者优化性能时,我们需要在外部访问私有成员,这时候就可以使用友元函数或友元类了。
类的特性之一是封装,友元是打破封装的手段,友元不要过度使用
C++ 中的友元是一种特殊的特性,其特点可以概括如下:
1. 友元关系是单向的。
即如果类 B 是类 A 的友元,但是对于类 B 来说,类 A 并不是其友元。
2. 友元关系不能被继承。
即如果类 B 是类 A 的友元类,因此它可以直接访问 A 类的私有成员 a。但是对于类 C 来说,即使它是类 B 的子类,也无法访问 A 类的私有成员 a。
3. 友元关系不具有传递性。
即如果类 B 是类 A 的友元,类 C 是类 B 的友元,那么并不意味着类 C 也是类 A 的友元。
4. 友元关系的作用域局限于声明的类内部。
即如果类 B 是类 A 的友元,但在其它类中声明的类无法访问类 A 的私有成员。
5. 友元关系不影响类的访问控制属性。
友元只是一种机制,它不会改变类成员的访问控制属性,
类成员的访问控制属性还是 public、protected 和 private 三种,
友元只是允许某些外部实体特权地访问该类中的成员,但不会影响这些成员的访问控制属性本身。
6. 友元关系破坏了类的封装性。
即将类的私有成员开放给外部,这样可能会降低可维护性,增加程序复杂性。
总的来说,友元作为一种特殊机制,解决了类的访问控制问题,
但同时也带来了限制和风险,使用时应慎重考虑,避免滥用。
// 使用 C++ 友元函数和友元类时需要注意以下事项:
// 1. 友元函数和友元类破坏了类的封装性。通常情况下,不应该过多地使用友元。尽量让类的成员变量和成员函数私有化,只开放必要的接口。
// 2. 友元函数和友元类可以访问类的私有成员变量和成员函数,这会导致代码的可读性和可维护性下降。
// 3. 友元函数和友元类增加了代码的耦合性。如果要修改类的实现,可能会影响到友元函数或友元类的实现。
// 使用 C++ 友元函数和友元类时需要注意以下事项,并避免以下问题:
// 1. 友元函数和友元类应该仅在必要时使用,以保持类的封装性。尽量将类的成员变量和成员函数设置为私有的,只开放必要的接口。
// 2. 当使用友元函数和友元类时,需要仔细考虑访问权限和维护性问题。注意不要暴露不必要的信息,同时确保代码易于理解和维护。
// 3. 使用友元函数和友元类,需要注意内存泄漏问题,保证代码的稳定性和安全性。
// 4. 避免过多的使用友元函数和友元类,以减少代码的耦合性。尽量避免让友元函数和友元类依赖于类的实现细节,从而提高代码的复用性和可维护性。
// 5. 注意友元函数和友元类的声明顺序。如果先声明了类,再声明友元函数或友元类,可能会导致编译器无法正确识别友元函数或友元类。
// 总之,使用友元函数和友元类时需要注意设计的合理性、代码的可读性、可维护性和安全性,以保证代码的质量。同时,也需要避免过度使用友元函数和友元类,以减少代码的耦合性。
// 友元函数
// 场景一:重载操作符
// 在某些情况下,我们需要对自定义类的对象进行运算,比如加法、减法、乘法等。
// 这时可以通过重载对应的操作符来实现这些运算。
// 通常情况下,操作符重载函数应该是该类的成员函数,这样就可直接访问类的所有成员。
// 但如果需要访问类的私有成员或保护成员,那么就需要将操作符重载函数声明为友元函数。
// 下面以重载“+”操作符为例:
#include <iostream>
using namespace std;
class Complex {
public:
Complex(double r, double i): real(r), imag(i) {}
public:
Complex operator+(const Complex& c);
friend ostream& operator<<(ostream& out, const Complex& c);
private:
double real;
double imag;
};
Complex Complex::operator+(const Complex& c) {
double r = real + c.real;
double i = imag + c.imag;
return Complex(r, i);
}
ostream& operator<<(ostream& out, const Complex& c) {
out << c.real << "+" << c.imag << "i";
return out;
}
int main() {
Complex c1(1.0, 2.0), c2(3.0, 4.0);
// 类 `Complex` 重载了“+”操作符,并将之声明为友元函数。这样,在进行加法运算时,就可以直接访问类的私有成员 real 和 imag。
Complex c3 = c1 + c2;
cout << c3 << endl; // 输出 4+6i
return 0;
}
// 友元函数
// 场景二:类成员函数调用
// 有时候,我们需要在一个类的成员函数中调用另一个类的成员函数或私有成员。
// 如果这些成员都是私有的,那么这种访问通常是不允许的。这时就需要使用友元函数来实现这种访问。
#include <iostream>
using namespace std;
class B {
public:
friend class A;
private:
void private_func() {
cout << "B::private_func" << endl;
}
};
class A {
public:
void call_private_func(B& b) {
b.private_func();
}
};
int main() {
B b;
A a;
// 类 B 声明了类 A 为友元类。类 A 中的成员函数 `call_private_func()` 调用了类 B 中的私有成员函数 `private_func()`。
// 由于类 B 声明了类 A 为友元类,因此在类 A 中就可以直接调用类 B 的私有成员函数了。
a.call_private_func(b); // 输出 B::private_func
return 0;
}
// 友元函数
// 场景三:单元测试
// 在软件开发过程中,单元测试是一个重要的环节,它通常会测试一个类的各个方法是否正常工作。
// 但如果测试代码与被测试的代码都在同一个命名空间中,就会造成代码污染,影响代码的可靠性。
// 因此,我们可以使用友元函数来实现单元测试功能。
#include <iostream>
#include <cassert>
using namespace std;
class MyClass {
public:
int func1(int a) {
return a + 1;
}
int func2(int a) {
return a * 2;
}
};
class MyTest {
public:
static void test_case_1() {
MyClass obj;
assert(obj.func1(1) == 2);
assert(obj.func2(2) == 4);
}
};
int main() {
// 类 `MyTest` 定义了一个静态成员函数 `test_case_1()`,用来测试类 `MyClass` 中的成员函数 `func1()` 和 `func2
MyTest::test_case_1();
return 0;
}
// 友元函数可以是普通函数或类成员函数,这句话的意思是在声明友元函数时可以不仅限于类中的成员函数。
// 场景一:普通函数作为友元函数
// 在某些情况下,如果需要访问类的私有成员或保护成员,但我们不希望将这些成员暴露给其他成员函数,则可以将这些普通函数声明为友元函数。
// 这样,这些函数就可以访问类的私有成员或保护成员了。
// 下面以一个实现比较的函数为例:
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int a, int b): m_a(a), m_b(b) {}
public:
friend bool compare(const MyClass& a, const MyClass& b);
private:
int m_a;
int m_b;
};
bool compare(const MyClass& a, const MyClass& b) {
return a.m_a == b.m_a && a.m_b == b.m_b;
}
int main() {
// 类 `MyClass` 定义了两个私有成员 m_a 和 m_b,这两个成员不能直接被外部函数访问。
// 为了实现比较两个 `MyClass` 实例是否相等的功能,我们在类中声明了一个友元函数 `compare()`。
// 在实际使用时,我们只需要调用 `compare()` 函数,而不需要知道 `MyClass` 类的具体实现细节。
MyClass obj1(1, 2), obj2(1, 2), obj3(2, 3);
cout << "obj1 == obj2: " << compare(obj1, obj2) << endl; // 输出1
cout << "obj1 == obj3: " << compare(obj1, obj3) << endl; // 输出0
return 0;
}
// 场景二:类成员函数作为友元函数
// 类成员函数同样可以被声明为友元函数,这在一些情况下可能会很有用。
// 例如,当多个类需要相互协作时,可以在不暴露类的内部实现细节的情况下,让它们互相调用私有成员函数。
#include <iostream>
using namespace std;
// 定义三个类:`Car` 类、`Engine` 类和 `Mechanic` 类。
// 在 `Car` 类中,我们将 `Engine` 实例作为其私有成员,并将 `Mechanic` 类声明为 `Car` 类的友元类。
// 在 `Mechanic` 类中,我们可以直接访问 `Car` 类的私有成员 `Engine`,并执行 `start()` 函数。
// 这样的话,就实现了对汽车引擎的修理。
class Engine {
public:
Engine(int size): m_size(size) {}
public:
void start() {
cout << "Engine(" << m_size << ") has been started." << endl;
}
int get_size() const {
return m_size;
}
private:
int m_size;
};
class Car {
public:
Car(int size): m_engine(size) {}
public:
friend class Mechanic;
private:
Engine m_engine;
};
class Mechanic {
public:
void repair_engine(Car& car) {
cout << "Repairing a " << car.m_engine.get_size() << "L engine..." << endl;
car.m_engine.start();
}
};
int main() {
Car car(3);
Mechanic mechanic;
mechanic.repair_engine(car);
return 0;
}
// 场景三:继承中的友元函数
// 在 C++ 中,子类可以继承父类的友元函数。
// 如果在父类中声明的友元函数需要在其子类中使用,那么可以将这些函数声明为父类中的友元函数,并在子类中进行使用。
#include <iostream>
using namespace std;
// 定义了两个类 `Base` 和 `Derived`。在 `Base` 类中,我们声明了一个友元函数 `compare()`,用于比较两个 `Base` 实例的私有成员 `m_a` 是否相等。
// 在 `Derived` 类中,我们向其构造函数中添加一个额外的参数 `b`,并在类中声明了一个新的私有成员变量 `m_b`。
// 然后在 `main()` 函数中,我们分别创建了多个 `Base` 和 `Derived` 对象,并比较了它们的私有成员 `m_a` 是否相等。
class Base {
public:
Base(int a): m_a(a) {}
public:
friend bool compare(const Base& a, const Base& b);
int getA() {
return m_a;
}
private:
int m_a;
};
bool compare(const Base& a, const Base& b) {
return a.m_a == b.m_a;
}
class Derived : public Base {
public:
Derived(int a, int b): Base(a), m_b(b) {}
public:
void print() {
cout << "m_a = " << getA() << ", m_b = " << m_b << endl;
}
private:
int m_b;
};
int main() {
// 在继承中使用友元函数的时候,需要注意以下两点:
// - 子类可以使用父类中的友元函数,但是父类不能使用子类中的成员函数和变量。
// - 友元函数不能被继承,所以父类中声明的友元函数在子类中使用时需要重新声明。
// 综上所述,友元函数可以是普通函数或类成员函数,可以在类的声明中定义,可以访问类中的私有和保护成员,也可以在继承中使用。使用友元函数可以增强类的封装性,使类的成员和函数之间的访问更加自由。
Base obj1(1), obj2(2);
cout << "obj1 == obj2: " << compare(obj1, obj2) << endl; // 输出0
Derived obj3(1, 2), obj4(2, 2);
cout << "obj3 == obj4: " << compare(obj3, obj4) << endl; // 输出0
return 0;
}
// C++ 中的友元类是指在一个类中将另一个类声明为友元,这样就能使得被声明为友元的类可以访问当前类的私有成员。友元类有以下几个特点:
// 1. 在当前类的声明中使用 `friend class` 声明一个友元类。
// 2. 友元类可以访问当前类中的所有成员,包括私有成员。
// 3. 友元关系不能被继承。
// 4. 友元类之间不能互相访问私有成员,只能访问被声明为友元的那个类的私有成员。
// 1. 操作符重载
// 通过操作符重载,我们可以定义一些自定义的操作符,如加号、减号等,方便对自定义的类进行一系列运算。
// 在这个过程中,因为有些运算需要涉及到不同类型的类,而使用友元类可以让我们访问到其它类中的私有成员。
// 以下是一个使用友元类进行加号操作符重载的例子:
// 定义一个 `Point` 类和一个 `Rectangle` 类,并重载了 `Point` 类的加号操作符,用于实现点的加法。
// 然后在 `Rectangle` 类中,我们定义了一个 `calculate_area()` 函数,用于计算一个矩形的面积。
// 通过将 `Point` 类声明为 `Rectangle` 类的友元类,在 `calculate_area()` 函数中,
// 我们可以直接访问 `Point` 类的私有成员变量,实现对矩形面积的计算。
#include <iostream>
#include <cstdlib>
using namespace std;
class Point {
public:
int x;
int y;
Point(int x, int y): x(x), y(y) {};
Point operator+(const Point& rhs);
friend class Rectangle;
};
Point Point::operator+(const Point& rhs) {
return Point(x + rhs.x, y + rhs.y);
}
class Rectangle {
public:
int calculate_area(Point& p1, Point& p2, Point& p3, Point& p4) {
int width = abs(p1.x - p2.x);
int height = abs(p2.y - p3.y);
return width * height;
}
};
int main()
{
// 在测试代码中,构造了 4 个点 p1, p2, p3 和 p4,代表矩形的左下角、左上角、右上角和右下角。
// 然后构造了一个 `Rectangle` 类实例,并调用 `calculate_area` 方法计算矩形面积,最后输出结果。
// 由于这个矩形的宽为 6,高为 4,因此可以得出正确的面积 24。
Point p1(0, 0), p2(0, 4), p3(6, 4), p4(6, 0);
Rectangle rect;
int area = rect.calculate_area(p1, p2, p3, p4);
// 输出为:Area of rectangle: 24
cout << "Area of rectangle: " << area << endl;
return 0;
}
// 2. 工厂模式
// 工厂模式是一种创建对象的设计模式,其通过工厂类来创建指定类型的对象。
// 在工厂类中,需要创建指定类型的对象,就需要访问到指定类型的私有成员。
// 因此,我们可以将指定类型的类声明为工厂类的友元类,这样就可以在工厂类中直接访问到指定类型的私有成员。
// 以下是一个使用友元类实现工厂模式的例子:
#include <iostream>
#include <string>
using namespace std;
// 定义一个 `Person` 类和一个 `PersonFactory` 类。
// 通过在 `Person` 类的声明中将 `PersonFactory` 类声明为友元类,
// 在 `PersonFactory` 类中可以通过访问 `Person` 类的私有成员 `m_name` 和 `m_age`,创建一个指定类型的 `Person` 对象。
class Person {
private:
string m_name;
int m_age;
public:
Person(string name, int age): m_name(name), m_age(age) {};
friend class PersonFactory;
string get_name() { return m_name; }
int get_age() { return m_age; }
};
class PersonFactory {
public:
static Person create_person(string name, int age) {
return Person(name, age);
}
};
int main()
{
// 输出为:Name: John Age: 30
Person person = PersonFactory::create_person("John", 30);
cout << "Name: " << person.get_name() << endl;
cout << "Age: " << person.get_age() << endl;
return 0;
}
// 友元类是一种可以访问另一个类的私有成员的特殊类,通常情况下不建议过多地使用友元类,
// 因为它破坏了类的封装性,可能导致代码的不稳定性和不安全性。
// 在使用友元类时,需要注意友元类的声明和访问规则,尽量减少对其它类的侵入性。
// 3. 单元测试
// 在单元测试中,我们需要对某个类的私有成员进行测试。
// 因为不能直接访问私有成员,我们可以通过将单元测试类声明为被测试类的友元类,来访问测试类中的私有成员。
// 以下是一个使用友元类进行单元测试的例子:
// 定义了一个 `Calculator` 类和一个 `CalculatorTest` 类。通过在 `Calculator` 类的声明中将 `CalculatorTest` 类声明为友元类,
// 在 `CalculatorTest` 类中直接访问了 `Calculator` 类的私有成员,从而实现了对 `Calculator` 类中私有成员的单元测试。
#include <iostream>
#include <cassert>
using namespace std;
// 被测试的类
class Calculator {
private:
int m_num1;
int m_num2;
public:
Calculator(int num1, int num2): m_num1(num1), m_num2(num2) {};
int add() {
return m_num1 + m_num2;
}
int subtract() {
return m_num1 - m_num2;
}
friend class CalculatorTest;
};
// 测试类
class CalculatorTest {
private:
Calculator m_calculator;
public:
CalculatorTest(int num1, int num2): m_calculator(num1, num2) {};
void run_tests() {
test_add();
test_subtract();
}
void test_add() {
assert(m_calculator.add() == 7);
}
void test_subtract() {
assert(m_calculator.subtract() == 1);
}
};
int main() {
CalculatorTest test(3, 4);
test.run_tests();
return 0;
}