• C++9 --- 友元


    一、友元

    1、为什么需要友元这个概念

    想要从外部,这里是通过Add函数访问私有数据成员,又不想通过接口(共有函数)去访问
    可能有人说友元破坏了类的封装性,但还是看设计时的需求和要求。

    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	friend void Add(A& a,A& b);//将Add函数作为类A的友元,不能再类A中去定义
    protected:
    	//friend void Add(A& a,A& b);//不管放在哪,都可实现友元
    	int m_j;//也可访问
    private:
    	//friend void Add(A& a,A& b);
    	int m_i;
    };
    void Add(A& a,A& b)
    {
    	cout << a.m_i << endl;
    	cout << b.m_i << endl;
    
    	cout << a.m_i + b.m_i << endl;
    	//cout <
    }
    void main()
    {
    	A a(5);
    	A b(8);
    	Add(a,b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    运行结果:
    在这里插入图片描述

    2、什么是友元

    友元 – 让函数或者类作为另一个类的朋友,则可以访问当前类的private或者protected成员
    是单向的

    友元 friend 机制允许一个类授权其他的函数访问它的非公有成员。

    友元声明以关键字 friend 开头,它只能出现在类的声明中,它们不受其在类体中的public private 和 protected 区的影响.

    友元分为外部函数友元,成员函数友元,类友元。

    友元的特点:
    1)不具有对称性:A是B的友元.并不意味着B是A的友元。
    2)不具有传递性:A是B的友元,B是C的友元,但A不是C的友元。
    3)不具有继承性::Base类型继承Object类型,如果Object类型是A的友元,但Base类型不是A友元。

    3、友元的三种情况:

    1)一个普通函数作为类的友元,那么在当前函数中就可以通过对象访问类的private或者protected

    注意:这个函数只能在外部定义,在当前类中引用即可
    
    • 1

    代码示例:

    class B;//需要事先进行 - 前项引用声明,实际上没有定义
    class C;
    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	friend int Sum(A& a,B& b,C& c);//会报错,因为在定义类A的时候,没有定义B,需要使用前项引用声明
    private:
    	int m_i;
    };
    class B
    {
    public:
    	B(int j = 0):m_j(j){}
    	friend  int Sum(A& a,B& b,C& c);
    private:
    	int m_j;
    };
    class C
    {
    public:
    	C(int k = 0):m_k(k){}
    	friend int Sum(A& a,B& b,C& c);
    private:
    	int m_k;
    };
    int Sum(A& a,B& b,C& c)
    {
    	return a.m_i + b.m_j + c.m_k;
    }
    void main()
    {
    	A a(10);
    	B b(20);
    	C c(30);
    	cout << Sum(a,b,c) << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    运行结果:

    在这里插入图片描述

    2)一个类的成员函数最为另一个类的友元

    注意:成员函数建议放在类外定义,

    class A;
    class C;
    class B
    {
    public:
    	B(int j = 0):m_j(j){}
    	 void Sub(A& a,C &c);//这里需要在类外定义,只能进行声明,因为要对Sub进行定义,那其中的B和A类都需要定义,实际上没有定义,所以不能实现,需要在类外定义
    private:
    	int m_j;
    };
    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	//friend void Sub(A& a);//error,这句话的意思的将一个普通函数作为类的友元
    	friend void B::Sub(A& a,C &c);
    private:
    	int m_i;
    };
    class C
    {
    public:
    	C(int k = 0):m_k(k){}
    	friend void B::Sub(A& a,C &c);
    private:
    	int m_k;
    };
    void B::Sub(A& a,C &c)
    {
    	cout << a.m_i - c.m_k - m_j << endl;
    }
    void main()
    {
    	A a(40);
    	B b(20);
    	C c(10);
    	b.Sub(a,c);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    运行结果:

    在这里插入图片描述

    3)一个类作为类外一个类的友元类

    一个类A作为类外一个类B的友元类,则A的所有成员函数就可以访问B的私有数据成员
    使用情况示例:当前类内函数需要大量使用另一个类的私有数据成员

    //B和A互为朋友
    class B;
    class C;
    class A
    {
    public:
    	A(int a = 0) :m_a(a) {}
    	void print(B& b);
    	void test(B& b);
    	void show(B& b);
    	void print(C& c);
    private:
    	int m_a;
    };
    class B
    {
    public:
    	B(int b = 0) :m_b(b) {}
    	friend class A;
    private:
    	int m_b;
    };
    class C
    {
    public:
    	C(int c = 0) :m_c(c) {}
    	friend class A;
    private:
    	int m_c;
    };
    void A::print(B& b)
    {
    	cout << "A::print b "<<b.m_b << endl;
    }
    void A::test(B& b)
    {
    	cout << "A::test "<<b.m_b << endl;
    }
    void A::show(B& b)
    {
    	cout << "A::show " << b.m_b << endl;
    }
    void A::print(C& c)
    {
    	cout << "A::print c "<<c.m_c << endl;
    }
    void main()
    {
    	B b(30);
    	A a(40);
    	C c(10);
    
    	a.print(b);
    	a.show(b);
    	a.test(b);
    	a.print(c);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    运行结果:

    在这里插入图片描述
    B和A互为朋友:

    class B;
    class C;
    class A
    {
    public:
    	A(int a = 0) :m_a(a) {}
    	void print(B& b);
    	void test(B& b);
    	void show(B& b);
    	void print(C& c);
    	friend class B;
    private:
    	int m_a;
    };
    class B
    {
    public:
    	B(int b = 0) :m_b(b) {}
    	friend class A;
    	void Print(A& a);
    private:
    	int m_b;
    };
    
    void B::Print(A& a)
    {
    	cout << "B::print a "<< a.m_a << endl;
    }
    class C
    {
    public:
    	C(int c = 0) :m_c(c) {}
    	friend class A;
    private:
    	int m_c;
    };
    void A::print(B& b)
    {
    	cout << "A::print b "<<b.m_b << endl;
    }
    void A::test(B& b)
    {
    	cout << "A::test "<<b.m_b << endl;
    }
    void A::show(B& b)
    {
    	cout << "A::show " << b.m_b << endl;
    }
    void A::print(C& c)
    {
    	cout << "A::print c "<<c.m_c << endl;
    }
    void main()
    {
    	B b(30);
    	A a(40);
    	C c(10);
    
    	a.print(b);
    	a.show(b);
    	a.test(b);
    	a.print(c);
    
    	b.Print(a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    运行结果:

    在这里插入图片描述

    4、友元注意点:

    1)友元是单向的

    2)友元是不能传递的

    3)友元是不能继承的

    对于以下代码:
    BB是AA的朋友,CC是BB的朋友,如果CC没有作为AA的友元类,则CC和AA没有关系 — 即友元不能传递
    CC能不能访问AA的私有数据成员? 不能

    class BB;
    class CC;
    class AA
    {
    public:
    	friend class BB;
    private:
    	int m_a;
    };
    class BB
    {
    public:
    	friend class CC;
    	void Show(AA& a);
    private:
    	int m_b;
    };
    class CC
    {
    public:
    	void print(BB& b);
    	void test(AA& a);
    };
    //
    void CC::test(AA& a)
    {
    	//cout << a.m_a << endl;  //error  CC不是AA的朋友
    }
    void BB::Show(AA& a)
    {
    	cout << a.m_a << endl;
    }
    void CC::print(BB& b)
    {
    	cout << b.m_b << endl;
    }
    void main()
    {
    	//AA a;
    	BB b;
    	CC c;
    	c.print(b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    运行结果:

    在这里插入图片描述

    运行 cout << a.m_a << endl; 时会报错:

    在这里插入图片描述

    5、相关应用 – 实现 cout << a + b << endl; 的重载实现(a,b为类)

    1)欲实现代码(实现两个不同类的相加):

    //error
    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    private:
    	int m_i;
    };
    class B
    {
    public:
    	B(int j = 0):m_j(j){}
    private:
    	int m_j;
    };
    void main()
    {
    	A a(10);
    	B b(20);
    	cout << a + b << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    当前实现不了
    在这里插入图片描述

    其中cout << a + b << end;可解读为//a.+(b) 或 +(a,b)

    2)两个不是同一类型的对象要进行 + 的前提:

    只有两个类其中的数据成员的个量、数据成员的类型一致的情况下,才能去尝试去进行相加。

    3)代码实现示例:

    class B;
    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	int operator+(B& b);
    private:
    	int m_i;
    };
    class B
    {
    public:
    	B(int j = 0):m_j(j){}
    	friend int A::operator+(B& b);
    private:
    	int m_j;
    };
    int A::operator+(B& b)
    {
    	return m_i + b.m_j;
    }
    
    void main()
    {
    	A a(10);
    	B b(20);
    	cout << a + b << endl; // a.+(b) 或  +(a,b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    运行结果:

    在这里插入图片描述

    6、运算符重载重载成友元形式

    一般情况下运算符重载重载成友元形式是以下这种情况

    对于以下基本的+运算符重载示例

    注意:
    重载成成员函数可以将第一个操作数省略,
    重载成友元函数不能省略第一个操作数,是几个操作数就写几个操作数

    重载成成员函数形式 – a.+(b)
    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	void print()
    	{
    		cout << m_i << endl;
    	}
    	A operator+(A& b)
    	{
    		return m_i + b.m_i;
    	}
    private:
    	int m_i;
    };
    void main()
    {
    	A a(5);
    	A b(10);
    	(a + b).print();//可以解析成 a.+(b) 或 +(a,b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行结果:
    在这里插入图片描述

    重载成 普通函数 – +(a,b) ,这里重载成友元函数

    友元必须将两个参数都有

    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	void print()
    	{
    		cout << m_i << endl;
    	}
    	A operator+(A& b)
    	{
    		return m_i + b.m_i;
    	}
    	friend A operator-(A& a,A& b);//重载成友元函数
    private:
    	int m_i;
    };
    A operator-(A& a,A& b)//两个参数都需要
    {
    	return a.m_i - b.m_i;
    }
    void main()
    {
    	A a(5);
    	A b(10);
    	(a + b).print();//a.+(b) +(a,b)
    	(a - b).print();//-(a,b)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    运行结果:

    在这里插入图片描述

    前置++和后置++的不同重载
    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	void print()
    	{
    		cout << m_i << endl;
    	}
    	A operator+(A& b)
    	{
    		return m_i + b.m_i;
    	}
    	friend A operator-(A& a,A& b);
    	A operator++(int) //重载后a++
    	{
    		
    		int t = m_i;
    		m_i = m_i + 1;
    		return t;
    	}
    	friend A& operator++(A& a);
    private:
    	int m_i;
    };
    A& operator++(A& a)
    {
    	++a.m_i;
    	return a;
    }
    A operator-(A& a,A& b)
    {
    	return a.m_i - b.m_i;
    }
    void main()
    {
    	A a(5);
    	A b(10);
    	//(a + b).print();//a.+(b) +(a,b)
    	//(a - b).print();//-(a,b)
    	(a++).print();//a.++()
    	(++b).print();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行结果:

    在这里插入图片描述

    7、对输出运算符cout的重载实现

    欲实现 A a(5); cout<< a <

    1)思考:

    怎么去重载输出运算符?类比于(a + b).print();//a.+(b) +(a,b)
    重载输出时返回类型是什么类型的?
    输出能不能作为左边,是值返回还是引用返回?

    对于代码:`cout<< a <

    可以解析为:cout.<<(a) 或者 <<(cout,a)

    2)相关知识点

    cout -是- ostream类的对象,输出流类
    cin -是- istream类的对象,输入流类
    在这里插入图片描述
    在这里插入图片描述

    对于 cout << a;

    一般情况下运算符可以解析成下面两种形式

    (1)cout.<<(a) ostream类中重载了<<,程序员不能修改
    (2)<<(cout,a) 可以在程序员自己定义的类中将<<重载成友元

    返回值为引用

    对于 cout< cout< 即(cout< 所以返回值为引用

    返回值为ostream类型

    (cout<

    3)使用模板

    friend ostream& operator<<(ostream &out,A &a)//除了第二个参数,其他都是定死的
    
    • 1

    4)使用示例

    class A
    {
    public:
    	A(int i = 0):m_i(i){}
    	void print()
    	{
    		cout << m_i << endl;
    	}
    	A operator+(A& b)  //this
    	{
    		return m_i + b.m_i;
    	}
    	/*
    	//较高的编译器,可能会对上一个重载进行报错,可用如下重载
    	//也可将声明函数的A& a的&去掉,但不推荐
    	A& operator+(A& b)  //this
    	{
    		m_i = m_i + b.m_i;
    		return m_i + b.m_i;
    	}
    	*/
    	friend ostream& operator<<(ostream& out, A&a);
    private:
    	int m_i;
    };
    //cout<
    ostream& operator << (ostream & out, A&a)
    {
    	out << a.m_i;
    	return out;
    }
    void main()
    {
    	A a(5);
    	A b(10);
    	A c(20);
    	cout << a << endl;  //5
    
    	c = a + b; //15
    	cout << c << endl;//15
    
    	cout << (a + b) << endl;//15
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    运行结果:

    在这里插入图片描述

    5)重载运算符的相关使用规则

    从语法上讲,运算符既可以定义为全局函数,也可以定义为成员函数。文献[Murray ,p44-p47]对此问题作了较多的阐述,并总结了表8-4-1的规则。
    在这里插入图片描述
    由于C++语言支持函数重载,才能将运算符当成函数来用,C语言就不行。我们要以平常心来对待运算符重载。
    (1)不要过分担心自己不会用,它的本质仍然是程序员们熟悉的函数。
    (2〉不要过分热心地使用,如果它不能使代码变得更加易读易写,那就别用,否则会自找麻烦。

    不能被重载的运算符

    在C++运算符集合中,有一些运算符是不允许被重载的。这种限制是出于安全
    方面的考虑,可防止错误和混乱。
    (1)不能改变C++内部数据类型(如int, fl oat 等)的运算符。
    (2)不能重载‘ . ’,因为 ‘.’ 在类中对任何成员都有意义,已经成为标准用法
    (3)不能重载目前C++运算符集合中没有的符号,如#,@,$等。原因有两点,一是难以理解,二是难以确定优先级。
    (4)对已经存在的运算符进行重载时,不能改变优先级规则,否则将引起混乱。

    相关习题:
    编写类 String的构造函数、析构函数和赋值函数
    已知类String的原型为:

    class String
    {
    public:
    	String(const char *str = NULL);            //普通构造函数
    	String(const String &other);               //拷贝构造函数
    	~String(void);                             //析构函数
    	String & operator =(const String &other);  //赋值函数
    private:
    char *m_data;    //用于保存字符串
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    请编写String的上述4个函数

    答案:

    //String的析构函数
    String::~String(void)
    {
    	delete[]m_data;
    	//由于m_data 是内部数据类型,也可以写成 delete m_data;
    }
    //String 的普通构造函数
    String::String(const char *str)
    {
    	if(str == NULL)
    	{
    		m_data = new char[1]; //若能加 NULL 判断则更好
    		*m_data = '\0';
    	}
    	else
    	{
    		int length = strlrn(str);
    		m_data = new char[length + 1]; //若能加 NULL 判断则更好
    		strcpy(m_data,str);
    	}
    }
    //拷贝构造函数
    String & String::operate =(const String &other)
    {
    	//(1)检查自赋值
    	if(this == &other)
    		return *this;
    		
    	//(2)释放原有的内存资源
    	delete[]m_data;
    	
    	//(3)分配新的内存资源,并赋值内容
    	int length = strlen(other.m_data);
    	m_data = new char[length + 1];   //若能加 NULL 判断则更好
    	strcpy(m_data,other.m_data);
    	
    	//(4)返回本对象的引用
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    2.5 晶体管单管放大电路的三种基本接法
    http协议与tomcat
    Java中getBytes()方法
    (附源码)python方块新闻网站 毕业设计 091600
    浅谈前端微服务背景及Micro-app的使用
    SpringMVC入门
    蓝桥杯动态规划每日一题
    ERROR Failed to compile with 16 errors
    Ubuntu Zookeeper开机自启动服务
    【6】Spring Boot 3 集成组件:knift4j+springdoc+swagger3
  • 原文地址:https://blog.csdn.net/kyrie_sakura/article/details/127587182