zoukankan      html  css  js  c++  java
  • 类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

  • 相关阅读:
    二维数组排序
    正则验证
    yii2视频教材
    yii2数据库简单操作
    MySQL(zip版)安装教程
    OpenCV插件
    机器视觉项目总结——光源
    VS2017运行YOLOv4
    VS打开cmd(直接在项目路径)
    【转载】Win10安装Ubuntu子系统
  • 原文地址:https://www.cnblogs.com/geloutingyu/p/8087634.html
Copyright © 2011-2022 走看看