类3(友元)

一、友元介绍

我们知道,类的成员函数可以访问同类的其他成员函数,包括公有、私有和保护成员。而类的外部函数只能访问类的公有成员。

友元是一种允许非类成员函数访问类的非公有成员的一种机制。
可以把一个函数指定为类的友元,也可以把整个类指定为另一个类的友元。

友元函数
友元类

二、友元函数
友元函数在类作用域外定义,但它需要在类体中进行说明
为了与该类的成员函数加以区别,定义的方式是在类中用关键字friend说明该函数,格式如下:

friend  类型 友元函数名(参数表);
友元的作用在于提高程序的运行效率

友元函数注意事项:
1、
友元函数不是类的成员函数,在函数体中访问对象的成员,必须用对象名加运算符“.”加对象成员名。但友元函数可以访问类中的所有成员(公有的、私有的、保护的),一般函数只能访问类中的公有成员。

2、友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分,但结果一样。

3、某类的友元函数的作用域并非该类作用域。如果该友元函数是另一类的成员函数,则其作用域为另一类的作用域,否则与一般函数相同。

4、友元函数破坏了面向对象程序设计类的封装性,所以友元函数如不是必须使用,则尽可能少用。或者用其他手段保证封装性。

就如我们在上一篇博客中的 Sales_data 类,如果将其中的数据成员修改成 private,则还需要将 add,read,print 这三个函数声明成 Sales_data 类的友元函数,因为我们在这三个函数中直接访问了 Sales_data 类的私有数据成员:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Sales_data{
 5 
 6 //友元声明
 7 friend Sales_data add(const Sales_data&, const Sales_data&);
 8 friend std::istream &read(std::istream&, Sales_data&);
 9 friend std::ostream &print(std::ostream&, const Sales_data&);
10     
11     //数据成员
12 private:
13     std::string book_no;
14     unsigned units_sold = 1;
15     double revenue = 1.0;
16 
17 public:
18     Sales_data() = default;//不接受任何实参,默认构造函数
19     Sales_data(const std::string &s): book_no(s){}//除了book_no外其他成员将被编译器赋予默认值
20     Sales_data(const std::string &s, unsigned n, double p): book_no(s), units_sold(n), revenue(p * n){}
21     Sales_data(std::istream&);
22 
23     std::string isbn() const {
24         return book_no;
25         // return this->book_no;//等价语句
26     }
27     Sales_data& combine(const Sales_data&);
28     double avg_price() const;
29 };
30 
31 // Sales_data的非成员函数声明
32 Sales_data add(const Sales_data&, const Sales_data&);
33 std::ostream &print(std::ostream&, const Sales_data&);
34 std::istream &read(std::istream&, Sales_data&);
35 
36 Sales_data::Sales_data(std::istream &is){
37     read(is, *this);//read 函数的作用是从 is 中读取一条信息然后存入 this 中
38 }
39 
40 double Sales_data::avg_price() const{
41     if(units_sold) return revenue / units_sold;
42     return 0;
43 }
44 
45 Sales_data& Sales_data::combine(const Sales_data &rhs){
46     units_sold += rhs.units_sold;
47     revenue += rhs.revenue;
48     return *this;
49 }
50 
51 istream &read(istream &is, Sales_data &item){//从给定流中将数据读到给定的对象里
52     double price = 0;
53     is >> item.book_no >> item.units_sold >> price;
54     item.revenue = price * item.units_sold;
55     return is;
56 }
57 
58 ostream &print(ostream &os, const Sales_data &item){//将给定对象的内容打印到给定的流中
59     os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
60     //print 函数不负责换行。一般来说执行输出的函数应该尽量减少对格式的控制,这样可以确保由用户代码来决定是否换行
61     return os;
62 }
63 
64 Sales_data add(const Sales_data &lhs, const Sales_data &rhs){
65     Sales_data sum = lhs;//默认情况下拷贝的是数据成员
66     sum.combine(rhs);//把 rhs 的数据成员加到 sum 中
67     return sum;//返回 sum 的副本
68 }
69 
70 int main(void){
71     Sales_data total(cin), x;
72     // print(cout, x);
73     // read(cin, total);
74     while(read(cin, x)){
75         total = add(total, x);
76         print(cout, total);
77         cout << endl;
78     }
79     return 0;
80 }

还需要注意的一点是:友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class gel{
 5     // friend void f(gel&){}//友元函数可以定义在内的内部,然而好像这并没啥用,要是成员函数还用啥友元声明啊
 6     friend void f(gel&);//友元函数声明只是声明了访问权限,并不能当成一般的函数声明来用
 7     
 8 public:
 9     // gel(){//错误,f函数还没声明
10     //     f(*this);
11     // }
12     void lou();
13     void ting();
14 
15 private:
16     int x;
17 };
18 
19 // void gel::lou(){
20 //     f(*this);//错误,f函数还没声明
21 // }
22 
23 void f(gel &it);//声明f函数
24 
25 void gel::ting(){//正确
26     f(*this);
27 }
28 
29 void f(gel &it){
30     it.x += 1;
31 }
32 
33 int main(void){
34 
35 }

 

三、友元类
如果某类B的成员函数会频繁的存取另一个类A的数据成员, 而A的数据成员的Private/Protectd限制造成B存取的麻烦, B只能通过A的Public的成员函数进行间接存取
把B做成A类的友元类,即A类向B类开放其Private/Protectd内容, 让B直接存取

友元类:一个类可以作另一个类的友元
友元类的所有成员函数都是另一个类的友元函数

友元类的声明:
friend class 类名;

友元类注意事项:
1、友元关系是单向的
2、友元关系不能被传递
3、友元关系不能被继承

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4 
 5 class Screen{
 6     
 7 friend class window_mgr;//将window_mgr类声明成Screen的友元类
 8 
 9 public:
10     typedef std::string::size_type pos;
11     Screen() = default;
12     //cursor为默认值0
13     Screen(pos ht, pos wd, char c): height(ht), width(wd), contents(ht * wd, c){};
14     char get() const{
15         return contents[cursor];
16     }
17 
18 private:
19     pos cursor = 0;
20     pos height = 0, width = 0;
21     std::string contents;
22 };
23 
24 
25 class window_mgr{
26     
27 public:
28     // window_mgr();
29     // ~window_mgr();
30     using screen_index = std::vector<Screen>::size_type;
31     void clear(screen_index);
32 
33 private:
34     std::vector<Screen> screen{Screen(24, 80, ' ')};
35     
36 };
37 
38 void window_mgr::clear(screen_index i){
39     Screen &s = screen[i];
40     s.contents = std::string(s.height * s.width, ' ');
41     //因为window_mgr是Screen类的友元类,所以可以直接访问Screen类中的private对象contents,height,width
42 }
43 
44 int main(void){
45 
46 }

上面的代码中,我们在 Screen 类中定义了 window_mgr 类为其友元类,所以 window_mgr 类的成员函数可以直接访问 Screen 类中的所有数据成员,包括 private 类型的成员。

除了令整个 window_mgr 类作为 Screen 类的友元之外,还可以只声明 window_mgr 类中的某个函数为 Screen 类的友元函数。当把一个成员函数声明成友元时,必须明确指出该成员函数属于哪个类:

 1 #include <iostream>
 2 #include <vector>
 3 using namespace std;
 4 
 5 class Screen;//下面的window_mgr需要用Screen作形参,所以我们需要先声明一下Screen类
 6 
 7 class window_mgr{
 8     
 9 public:
10     // window_mgr();
11     // ~window_mgr();
12     void clear(int i);
13 
14 private:
15     std::vector<Screen> screen;
16 
17 };
18 
19 class Screen{
20 
21 // friend class window_mgr;//将window_mgr类声明成Screen的友元类
22 friend void window_mgr::clear(int);//将window::clear定义成Screen的友元函数
23 // friend void clear(int);//没有指明clear所属的类,只样声明的clear友元函数和window_mgr类里的clear成员函数半毛钱关系都没有
24 
25 
26 public:
27     typedef std::string::size_type pos;
28     Screen() = default;
29     //cursor为默认值0
30     Screen(pos ht, pos wd, char c): height(ht), width(wd), contents(ht * wd, c){};
31     char get() const{
32         return contents[cursor];
33     }
34 
35 private:
36     pos cursor = 0;
37     pos height = 0, width = 0;
38     std::string contents;
39 };
40 
41 
42 void window_mgr::clear(int i){
43     Screen &s = screen[i];
44     s.contents = std::string(s.height * s.width, ' ');
45 }
46 
47 int main(void){
48 
49 }

然而我们发现想要令某个成员函数作为友元,我们必须自信组织程序的结构以满足声明和定义的彼此依赖关系。这相当麻烦,在整个例子中:

1.首先定义 window_mgr 类,其中声明 clear 函数,但是不能定义它。在使用 Screen 的成员之前还必须声明 Screen 类

2.接下来定义 Screen,包括 clear 的友元声明

3.定义 clear,此时它才可以使用 Screen 的成员

4.尽管重载函数的名字相同,但它们仍然是不同的函数。因此一个类想把一组重载函数声明成它的友元,它需要对这组函数中的每个函数都单独声明成友元函数

以上内容大部分摘自:http://www.jb51.net/article/41328.htm

posted @ 2017-12-25 22:29  geloutingyu  阅读(406)  评论(0编辑  收藏  举报