类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