友元函数和友元类
- 友元函数和友元类
 
采用类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有的,依此提供类与外界间的通信接口。但是,有时需要定义 一些函数,这些函数不是类的一部分,但又需要频繁地访问类的数据成员,这时可以将这些函数定义为该函数的友元函数。除了友元函数外,还有友元类,两者统称 为友元。友元的作用是提高了程序的运行效率(即减少了类型检查和安全性检查等都需要时间开销),但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类 的私有成员。
友元函数
友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字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">123456</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">123456</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">12345678</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">12345678910111213</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">123456789</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">1234567891011</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">123456</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> | 
                    
                
                
            
        
浙公网安备 33010602011771号