友元函数和友元类

友元函数和友元类

采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需要定义 一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该函数的友元函数。除了友元函数外,还有友元类,两者统称 为友元。友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类 的私有成员。

友元函数
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
friend 类型 函数名(形式参数);

友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。
一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
友元函数的调用与一般函数的调用方式和原理一致。
友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
friend class 类名;
其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。

例如,以下语句说明类B是类A的友元类:
class A
{

public:
friend class B;

};
经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。

使用友元类时注意:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。

(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

友元函数

特点

友元函数是能够访问类中的私有成员的成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样。 友元关系不具对称性。即 A 是 B 的友元,但 B 不一定是 A 的友元。 友元关系不具传递性。即 B 是 A 的友元,C 是 B 的友元,但是 C 不一定是 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
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
#include<iostream>
#include<cmath>
using namespace std;
class Point
{
public:
    Point(double xx, double yy)
    {
        x=xx;
        y=yy;
    };
    void Getxy();
    friend double Distance(Point &a, Point &b);
private:
    double x, y;
};
void Point::Getxy()
{
    cout<<"("<<x<<","<<y<<")"<<endl; }="" double="" distance(point="" &a,="" point="" &b)="" {="" dx="a.x" -="" b.x;="" dy="a.y" b.y;="" return="" sqrt(dx*dx+dy*dy);="" int="" main(void)="" p1(3.0,="" 4.0),="" p2(6.0,="" 8.0);="" p1.getxy();="" p2.getxy();="" d="Distance(p1," p2);="" cout="" <<="" "distance="" is"="" endl;="" 0;="" }<="" pre=""><br>
说 明:在该程序中的Point类中说明了一个友元函数Distance(),它在说明时前边加friend关键字,标识它不是成员函数,而是友元函数。它的 定义方法与普通函数定义一样,而不同于成员函数的定义,因为它不需要指出所属的类。但是,它可以引用类中的私有成员,函数体中 a.x,b.x,a.y,b.y都是类的私有成员,它们是通过对象引用的。在调用友元函数时,也是同普通函数的调用一样,不要像成员函数那样调用。本例 中,p1.Getxy()和p2.Getxy()这是成员函数的调用,要用对象来表示。而Distance(p1,
 p2)是友元函数的调用,它直接调用,不需要对象表示,它的参数是对象。(该程序的功能是已知两点坐标,求出两点的距离。)<br>
<p></p>
<p></p>
<h2 class="headline-1">
3友元类</h2>
<p></p>
<p></p>
 
友元除了前面讲过的函数以外,友元还可以是类,即一个类可以作另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。
 
让我们回顾一下重载的“等于操作符的”定义,它是为名字空间域中定义的String 类而提供的,针对两个String 对象的“等于操作符”如下:
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
</td>
<td class="code">
 
 
<code class="cpp color1 bold">bool</code><code class="cpp plain">operator==(
</code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &str1, </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &str2 )</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">if</code><code class="cpp plain">(
 str1.size() != str2.size() )</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">return</code><code class="cpp keyword bold">false</code><code class="cpp plain">;</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">return</code><code class="cpp functions bold">strcmp</code><code class="cpp plain">(
 str1.c_str(), str2.c_str() ) ? </code><code class="cpp keyword bold">false</code><code class="cpp plain">:
</code><code class="cpp keyword bold">true</code><code class="cpp plain">;</code>
 
<code class="cpp plain">}</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
把这个定义与被定义为成员函数的操作符定义相比较:
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
</td>
<td class="code">
 
 
<code class="cpp color1 bold">bool</code><code class="cpp plain">String::operator==(
</code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &rhs ) </code><code class="cpp keyword bold">const</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">if</code><code class="cpp plain">(
 _size != rhs._size )</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">return</code><code class="cpp keyword bold">false</code><code class="cpp plain">;</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">return</code><code class="cpp functions bold">strcmp</code><code class="cpp plain">(
 _string, rhs._string ) ? </code><code class="cpp keyword bold">false</code><code class="cpp plain">:
</code><code class="cpp keyword bold">true</code><code class="cpp plain">;</code>
 
<code class="cpp plain">}</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
你看到区别了吗?我们注意到必须要修改函数定义内部对于String 类私有数据成员的引用方式。因为新的等于操作符是全局函数,不是类成员函数,它不能直接引用String
 的私有数据成员,它使用访问成员函数size()和c_str()来获得String
 对象的大小,以及底层的C 风格字符串。
 
另外一种可能的实现是把全局“等于操作符”声明为String 类的友元friend。通过把函数或操作符声明为友元,一个类可以授予这个函数或操作符访问其非公有成员的权利。
 
友元声明以关键字friend 开始,它只能出现在类定义中。因为友元不是授权类的成员,所以它不受其所在类的声明区域public private protected 的影响。这里我们选择把所有友元声明组织在一起并放在类头之后:
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
</td>
<td class="code">
 
 
<code class="cpp keyword bold">class</code><code class="cpp plain">String
</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp color1 bold">bool</code>
<code class="cpp plain">
operator==( </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &, </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 & );</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp color1 bold">bool</code>
<code class="cpp plain">
operator==( </code><code class="cpp keyword bold">const</code><code class="cpp color1 bold">char</code>
<code class="cpp plain">
*, </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 & );</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp color1 bold">bool</code>
<code class="cpp plain">
operator==( </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &, </code><code class="cpp keyword bold">const</code><code class="cpp color1 bold">char</code>
<code class="cpp plain">
* );</code>
 
<code class="cpp keyword bold">public</code><code class="cpp plain">:</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ... String 类中的其他部分</code>
 
<code class="cpp plain">};</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
String 类中的三个友元声明把全局域中声明的三个重载的“比较操作符”(在上节介绍)声明为String 类的友
 
元既然这些等于操作符已经被声明为友元那么它们的定义就可以直接引用String 的私有成员了。
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
10
 
11
 
12
 
13
</td>
<td class="code">
 
 
<code class="cpp comments">//
 friend 操作符直接引用 String 的私有成员</code>
 
<code class="cpp comments">//
 friend operators: refer to String private members directly</code>
 
<code class="cpp color1 bold">bool</code><code class="cpp plain">operator==(
</code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &str1, </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &str2 )</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">if</code><code class="cpp plain">(
 str1._size != str2._size )</code>
 
<code class="cpp spaces">        </code><code class="cpp keyword bold">return</code><code class="cpp keyword bold">false</code><code class="cpp plain">;</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">return</code><code class="cpp functions bold">strcmp</code><code class="cpp plain">(
 str1._string, str2._string ) ? </code><code class="cpp keyword bold">false</code><code class="cpp plain">:
</code><code class="cpp keyword bold">true</code><code class="cpp plain">;</code>
 
<code class="cpp plain">}</code>
 
<code class="cpp keyword bold">inline</code><code class="cpp color1 bold">bool</code>
<code class="cpp plain">
operator==( </code><code class="cpp keyword bold">const</code><code class="cpp plain">String
 &str, </code><code class="cpp keyword bold">const</code><code class="cpp color1 bold">char</code>
<code class="cpp plain">
*s )</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">return</code><code class="cpp functions bold">strcmp</code><code class="cpp plain">(
 str._string, s ) ? </code><code class="cpp keyword bold">false</code><code class="cpp plain">:
</code><code class="cpp keyword bold">true</code><code class="cpp plain">;</code>
 
<code class="cpp plain">}</code>
 
<code class="cpp comments">//
 以下略</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
<h3 class="headline-2">
如何判断是类的友元</h3>
 
有 人可能会说在这种情况下由于c_str()和size()是内联的它们提供了等价的效率,并且保留了成员封装所以没必要直接访问_size 和_string ,这是对的。使用成员访问函数而不是直接访问成员,并不总是意味着它的效率较低。由于存在这些访问函数,所以没有必要把等于操作符声明为String
 类的友元。
那 么我们怎样判断一个非类成员的操作符应该是类的友元还是应该使用成员访问函数呢?一般来说,类的实现者应该尽量使得名字空间函数和访问类内部表示的操作符 的数目最小化。如果已经提供了访问成员函数并且它们具有等同的效率那么最好是使用这些成员函数,并且把名字空间操作符与类表示中的变化隔离开。但是如果类 的实现者决定不为该类的某些私有成员提供访问成员函数,而且名字空间操作符需要引用这些私有成员才能完成它们的操作,那么就必须使用友元机制。
友元声明的最常见用法是允许非成员的重载操作符访问一个视其为朋友的类的私有成员。原因是除了提供左和右操作数的对称性外,可使非成员的重载操作符就像成员函数一样能够完全访问一个类的私有成员。
虽然友元声明的主要用处是在重载操作符上,但是在某些情况下一个名字空间函数,另一个在此之前被定义的类的成员函数或者一个完整的类必须声明为友元。
在使一个类成为另一个类的友元时,友元类的成员函数,被赋予访问授权类的非公有成员的权利。
下面我们将更详细地了解函数而不是操作符的友元声明。
一个类必须把它希望与之建立友元关系的重载函数集中的每个函数都声明为友元。
例如
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
</td>
<td class="code">
 
 
<code class="cpp keyword bold">extern</code><code class="cpp plain">ostream&
 storeOn( ostream &, Screen & );</code>
 
<code class="cpp keyword bold">extern</code><code class="cpp plain">BitMap&
 storeOn( BitMap &, Screen & );</code>
 
<code class="cpp comments">//
 ...</code>
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Screen</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp plain">ostream&
 storeOn( ostream &, Screen & );</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp plain">BitMap&
 storeOn( BitMap &, Screen & );</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ...</code>
 
<code class="cpp plain">};</code>
 
</td>
</tr>
</tbody>
</table>
 
 
如果一个函数操纵两个不同类类型的对象而且该函数需要访问这两个类的非公有成员,则这个函数可以被声明为这两个类的友元,或者作为一个类的成员函数并声明为另一个类的友元让我们来看一看怎样做:
 
 
<h3 class="headline-2">
被声明两个类的友元声明</h3>
如果我们决定一个函数必须被声明为两个类的友元则友元声明如下
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
 
7
 
8
 
9
 
10
 
11
</td>
<td class="code">
 
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Window;
</code><code class="cpp comments">//
 只声明</code>
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Screen</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp color1 bold">bool</code>
<code class="cpp plain">
is_equal( Screen &, Window & );</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ...</code>
 
<code class="cpp plain">};</code>
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Window
</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp color1 bold">bool</code>
<code class="cpp plain">
is_equal( Screen &, Window & );</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ...</code>
 
<code class="cpp plain">};</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
<h3 class="headline-2">
作为一个类的函数又是另一个类的友元</h3>
 
如果我们决定该函数必须作为一个类的成员函数并又是另一个类的友元,则成员函数声明和友元声明如下:
 
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="code">
 
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Window;</code>
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Screen</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp keyword bold">public</code><code class="cpp plain">:</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 copy 是类 Screen 的成员</code>
 
<code class="cpp spaces">    </code><code class="cpp plain">Screen&
 copy( Window & );</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ...</code>
 
<code class="cpp plain">};</code>
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Window</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 copy 是类 Window 的一个友元</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp plain">Screen&
 Screen::copy( Window & );</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ...</code>
 
<code class="cpp plain">};</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
只有当一个类的定义已经被看到时它的成员函数才能被声明为另一个类的友元。这并不总是能够做到的。
 
例如如果Screen 类必须把Window 类的成员函数声明为友元,而Window类必须把Screen 类的成员函数声明为友元。该怎么办呢?在这种情况下可以把整个Window类声明为Screen 类的友元。
 
例如:
 
 
<table border="0" cellpadding="0" cellspacing="0">
<tbody>
<tr>
<td class="gutter">
 
1
 
2
 
3
 
4
 
5
 
6
</td>
<td class="code">
 
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Window;</code>
 
<code class="cpp keyword bold">class</code><code class="cpp plain">Screen</code>
 
<code class="cpp plain">{</code>
 
<code class="cpp spaces">    </code><code class="cpp keyword bold">friend</code><code class="cpp keyword bold">class</code>
<code class="cpp plain">
Window;</code>
 
<code class="cpp spaces">    </code><code class="cpp comments">//
 ...</code>
 
<code class="cpp plain">};</code>
 
</td>
</tr>
</tbody>
</table>
 
 
 
Screen 类的非公有成员现在可以被Window 的每个成员函数访问。
<br>
<p></p>
<p class="tit">
</p>                        </x<<","<<y<<")"<<endl;></cmath></iostream>
原文地址:https://www.cnblogs.com/shsgl/p/5147155.html