C++第十五章_友元类_友元成员函数_异常机制_将对象作为异常类型_栈解退_C风格字符串和C++字符串输入问题

目录

1、友元类

2、友元成员函数

3、异常机制

4、将对象作为异常类型

5、栈解退

6、C风格字符串和C++字符串输入问题

1、友元类

01)什么时候使用友元类?
     假如要编写一个电视机Tv类和一个遥控器Remote类,很显然,Tv类不是Remote类,Remote类也不是Tv类,即不存在is-a关系;
     遥控器Remote类也不是电视Tv类的一部分,反之亦然,因此包含、私有继承、保护继承的has-a关系也不存在;
     事实上,遥控器可以改变电视机的状态,这表明Remote类作为Tv类的一个友元。
02)下面的语句使Remote成为友元类:

friend class Remote;

     友元声明可以位于Tv类的公有、私有或保护部分,其位置无关紧要;由于Remote类中使用了Tv类的一些方法,所以要先声明
     Tv类,然后声明Remote类;也可以使用前向声明,以后将介绍;
03)友元类的所有方法都可以访问原始类的私有成员和保护成员,例如:

 1 class A
 2 {    
 3      friend class B;
 4 
 5      private:    int aa;
 6 };
 7 
 8 class B
 9 {
10 public:    
11 void output()    
12 {        
13     cout << a.aa << endl;  //直接访问A类中的私有数据aa
14 }
15 private:    A a;  //创建A类对象
16 
17 }
18 
19  
20  ———————————————— 
21 版权声明:本文为CSDN博主「风雪残存」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
22 原文链接:https://blog.csdn.net/u012230798/article/details/86576169
友元类访问原始类私有数据示例

参考博客:https://blog.csdn.net/u012230798/article/details/86576169

 1 #ifndef TV_H_
 2 #define TV_H_
 3 
 4 class Tv
 5 {
 6 private:
 7     int state;  //on or off
 8     int volume;  //音量
 9     int maxchannel;  //频道的最大数
10     int channel;  //选择的频道
11     int mode;  //Antenna or able
12     int input;  //TV or DVD
13 public:
14     friend class Remote;  //声明Remote为Tv的一个友元类
15     enum {Off, On};  //电视机的开关
16     enum {MinVal,MaxVal};  //声音的最小和最大值
17     enum {Antenna, Cable};  //天线or电缆
18     enum {TV,DVD};  //TV模式orDVD模式
19     
20     Tv(int s=Off,int mc=125) : state(s),volume(5),maxchannel(mc),channel(2),mode(Cable),input(TV) {};
21     
22     void onoff() {state = (state == On) ? Off:On;}  //若state == On成立,则state=Off,否则state=On
23     bool ison() const {return state == On;}
24     bool volup();  //增大音量
25     bool voldown();  //减小音量
26     void chanup();  //频道数增大
27     void chandown();  //频道数减小
28     void set_mode() {mode = (mode == Antenna) ? Cable : Antenna;}
29     void set_input() {input = (input == DVD) ? TV:DVD;}
30     void settings() const;  //diaplay all the settings
31 };
32 
33 /*Remote中的方法除了构造函数外,都调用了原始类Tv中的方法*/
34 class Remote
35 {
36 private:
37     int mode;
38 public:
39     Remote(int m = Tv::TV) : mode(m) {}
40     bool volup(Tv & t) {return t.volup();}  //直接调用Tv中的volup()
41     bool voldown(Tv & t) {return t.voldown();}
42     void onoff(Tv & t) {t.onoff();}
43     void chanup(Tv & t) {t.chanup();}
44     void chandown(Tv & t) {t.chandown();}
45     void set_chan(Tv & t,int c) {t.channel = c;}  //友元类的所有方法(Tv & t也是)都可以访问原始类的私有成员和保护成员
46     void set_mode(Tv & t) {t.set_mode();}
47     void set_input(Tv & t) {t.set_input();}
48 };
49 
50 #endif
tv.h
 1 //tv.cpp
 2 #include <iostream>
 3 #include "tv.h"
 4 
 5 /*Tv类中增大音量函数*/
 6 bool Tv::volup()
 7 {
 8     if(volum < MaxVal)
 9     {
10         volume++;
11         return true;
12     }
13     else
14         return false;
15 }
16 
17 /*Tv类中减小音量函数*/
18 bool Tv::voldown()
19 {
20     if(volume > MinVal)
21     {
22         volume--;
23         return true;
24     }
25     else
26         return false;
27 }
28 
29 /*Tv类中增大频道数函数*/
30 void Tv::chanup()
31 {
32     if(channel < maxchannel)
33         channel++;
34     else
35         channel = 1;  //增大到maxchannel后回到频道1
36 }
37 
38 /*Tv类中减小频道数函数*/
39 void Tv::chandown()
40 {
41     if(channel > 1)
42         channel--;
43     else
44         channel = maxchannel;  //减小到频道1后换到频道最大值
45 }
46 
47 /*diaplay all the settings*/
48 void Tv::settings() const
49 {
50     using std::cout;
51     using std::endl;
52     
53     cout << "Tv is " << (state = Off ? "Off" : "On") << endl;  //此处则可以省略下面if的else语句
54     if(state == On) //On是在Tv中定义的一个枚举量
55     {
56         cout << "Volume setting = " << volume << endl;
57         cout << "Channel setting = " << channel <<endl;
58         cout << "Mode = " << (mode == Antenna ? "antenna":"cable") << endl; //在Tv类和Remote类中都定义了mode
59         cout << "Input = " << (input == DVD ? "DVD":"TV") << endl;
60     }
61 }
tv.cpp
 1 //use_tv.cpp
 2 #include <iostream>
 3 #include "tv.h"
 4 
 5 int main()
 6 {
 7     using std::cout;
 8     Tv s42;  //此处将调用Tv类中的构造函数,由于Tv类中的构造函数都有默认参数,所以此处相当于省略了参数而使用默认参数,来初始化s42
 9     cout<<"Tv对象初始化设置:"<<endl;
10     s42.settings();  //显示设置
11     
12     s42.onoff();  //切换开关状态
13     s42.chanup();  //频道加
14     cout<<"使用电视机本身设置后的参数:"<<endl;
15     s42.sttings();
16     
17     Remote grey;  //使用Remote类构造函数中的默认参数初始化并创建Remote对象
18     grey.volup(s42);  //增大音量
19     grey.volup(s42);  //再次增大音量
20     grey.chanup(s42);  //频道加
21     grey.set_chan(s42.5);  //设置频道5;
22     cout<<"使用遥控器设置后的参数:"<<endl;
23     s42.settings();
24     
25     Tv s58(Tv::On);  //使用参数On创建Tv对象s58
26     s58.set_mode();
27     s58.settings()
28     
29 }
use_tv.cpp

 2、友元成员函数

01)从上一个例子中我们可以看出,在Remote类中只有Remote::set_chan()方法直接影响了Tv类中的私有数据(channel);
     而友元函数的特点之一就是可以访问类的私有数据(P391),所以我们可以只让Remote::set_chan()方法成为Tv类的友元,
     而不是让Remote类中的所有方法都成为Tv类中的友元函数。
02)让Remote::set_chan()成为Tv类的友元方法是在Tv类中做如下声明:

1 class Tv
2 {
3    friend void Remote::set_chan(Tv & t, int c);
4    ...
5 };

这里存在矛盾的地方:
一、因为在Tv类中使用了Remote类,所以要让编译器能够处理这条语句,它必须知道Remote类的存在,否则编译器无法知道
      Remote类的存在,也就无法知道Remote类中的set_chan()方法,解决方法是让Remote定义在Tv定义之前;
二、由friend void Remote::set_chan(Tv & t, int c)可知,在set_chan()方法中使用了Tv类对象,这意味着Tv定义应该放在Rmeote
  定义之前。
解决以上矛盾的方法是使用前向声明:class Tv;
这样声明的排序次序应如下:

1 class Tv;  //前向声明
2 class Remote {...};
3 class Tv {...};

能否像下面这样排序呢?

1 class Remote;
2 class Tv {...};
3 class Remote {...};

答案是不能。因为Remote类中的set_chan()方法在Tv中用到,所以必须先声明Remote类,再声明Tv类,即要将Tv类做前向声明。

03)还有一个问题,例如在Remote类中一个函数的定义如下:

void onoff(Tv & t) {t.onoff();} 

在Remote类中将调用Tv类中的方法,然而Tv类证明在Remote类声明之后,解决方法是在Remote类中只包含类的声明,不包含
类方法的定义即可.

注:tvfm.h包含了函数声明和方法定义:

 1 #ifndef TVFM_H_
 2 #define TVFM_H_
 3 
 4 class Tv;  //前向声明
 5 
 6 class Remote
 7 {
 8 public:
 9     enum State{Off, On};  //电视机的开关
10     enum {MinVal,MaxVal};  //声音的最小和最大值
11     enum {Antenna, Cable};  //天线or电缆
12     enum {TV,DVD};  //TV模式orDVD模式
13 private:
14     int mode;
15 public:
16     Remote(int m = Tv::TV) : mode(m) {}
17     bool volup(Tv & t);  //除了构造函数外,均只声明方法,因为Tv定义在Rmeote声明在Tv声明之后
18     bool voldown(Tv & t);
19     void onoff(Tv & t);
20     void chanup(Tv & t);
21     void chandown(Tv & t);
22     void set_chan(Tv & t,int c); 
23     void set_mode(Tv & t);
24     void set_input(Tv & t);
25 }
26 
27 class Tv
28 {
29 private:
30     int state;  //on or off
31     int volume;  //音量
32     int maxchannel;  //频道的最大数
33     int channel;  //选择的频道
34     int mode;  //Antenna or able
35     int input;  //TV or DVD
36 public:
37     friend void Remote::set_chan(Tv & t, int c);  //声明Remote中的set_chan()方法为Tv的一个友元函数
38     
39     enum {Off, On};  //电视机的开关
40     enum {MinVal,MaxVal};  //声音的最小和最大值
41     enum {Antenna, Cable};  //天线or电缆
42     enum {TV,DVD};  //TV模式orDVD模式
43     
44     Tv(int s=Off,int mc=125) : state(s),volume(5),maxchannel(mc),channel(2),mode(Cable),input(TV) {};
45     
46     void onoff() {state = (state == On) ? Off:On;}  //若state == On成立,则state=Off,否则state=On
47     bool ison() const {return state == On;}
48     bool volup();  //增大音量
49     bool voldown();  //减小音量
50     void chanup();  //频道数增大
51     void chandown();  //频道数减小
52     void set_mode() {mode = (mode == Antenna) ? Cable : Antenna;}
53     void set_input() {input = (input == DVD) ? TV:DVD;}
54     void settings() const;  //diaplay all the settings
55 };
56 
57 /*
58 01)Remote类中的方法定义,加上关键字inline使所有方法均成为内联函数
59 02)必须将定义放在后面,因为在Remote中的方法中要使用Tv对象
60 */
61 inline bool Remote::volup(Tv & t) {return t.volup();}  //直接调用Tv中的volup()
62 inline bool Remote::voldown(Tv & t) {return t.voldown();}
63 inline void Remote::onoff(Tv & t) {t.onoff();}
64 inline void Remote::chanup(Tv & t) {t.chanup();}
65 inline void Remote::chandown(Tv & t) {t.chandown();}
66 inline void Remote::set_chan(Tv & t,int c) {t.channel = c;}  //友元函数的所有方法(Tv & t也是)都可以访问原始类的私有成员和保护成员
67 inline void Remote::set_mode(Tv & t) {t.set_mode();}
68 inline void Remote::set_input(Tv & t) {t.set_input();}
69 #endif
tvfm.h

 Remote类成为Tv类的友元类:Remote中的所有方法均可以影响Tv类中的私有成员;如下图所示:

只让Remote中的set_chan()方法成为Tv类的友元函数,则只有Remote::set_chan()可以影响Tv类中的私有变量;如下图所示:

 3、异常机制

01)调用abort()函数,需包含头文件#include <cstdlib>;在程序中调用abort()会打印一个随系统而异的文字
02)异常机制:使用try、throw、catch关键字

 1 #include <iostream>
 2 
 3 double hmean(double a, double b);  //声明一个函数
 4 
 5 int main()
 6 {
 7     double x,y,z;
 8     
 9     std::cout<<"Enter two numbers: ";
10     while(cin>>x>>y)
11     {
12         try
13         {
14             z = hmean(x,y);
15         }
16         catch(const char *s)  //catch是关键字,并不是函数; throw后的string会赋给s;如果没有执行throw(即没有异常)会忽略catch中的语句
17         {
18             std::cout << s << std::endl;
19             std::cout << "Enter a new pair of numbes: ";
20             continue;  //返回到while继续执行
21         }
22         std::cout<<"Harmonic mean of " << x << " and " << y << " is " << z <<std::endl;
23         std::cout << "Enter next set of numbers <q to qiut>" << std::endl;  //继续执行while循环
24     }
25     
26     std::cout << "Bye!" << std::endl;
27     return 0;
28 }
29 
30 double hmean(double a, double b)
31 {
32     if(a == -b)
33         throw "Bad hmean() arguments: a=-b not allowed";  //由于throw后的字符串和main()中catch中的char *s匹配,所以执行throw后会执行catch
34     return 2.0*a*b/(a+b);
35 }
36 /*
37 01)若输入的两个数字为6,10,则直接执行try中的语句,执行完毕后,跳过catch中的语句;
38 02)若输入的两个数字为6,-6,则执行try中的hmean(),后执行throw,然后跳转到main()中的catch,执行catch中的语句
39 03)程序执行到throw后,程序会沿函数调用的顺序后退,直到遇到try块的函数
40 */
error3.cpp

对于以上程序,注:

01)若输入的两个数字为6,10,则直接执行try中的语句,执行完毕后,跳过catch中的语句;
02)若输入的两个数字为6,-6,则执行try中的hmean(),后执行throw,然后跳转到main()中的catch,执行catch中的语句
03)程序执行到throw后,程序会沿函数调用的顺序后退,直到遇到try块的函数

4、将对象作为异常类型

 1 /*将对象作为异常类型*/
 2 #include <iostream>
 3 
 4 class bad_hmean
 5 {
 6 private:
 7     double v1;
 8     double v2;
 9 public:
10     bad_hmean(double a=0, double b=0) : v1(a),v2(b) {}  //使用默认值初始化私有变量,创建对象时,可以使用新值覆盖默认值
11     void mesg();  //用于报告错误信息
12 };
13 inline void bad_hmean::mesg()
14 {
15     std::cout << "hmean(" << v1 << ", " << v2 << "):" << "invalid arguments: a=-b not allowed!" << std::endl;
16 }
17 
18 class bad_gmean
19 {
20 private:
21     double v1;
22     double v2;
23 public:
24     bad_gmean(double a=0, double b=0) : v1(a),v2(b) {}  //使用默认值初始化私有变量,创建对象时,可以使用新值覆盖默认值
25     const char* mesg();  //用于报告错误信息
26 };
27 inline const char* bad_gmean::mesg()
28 {
29     return "gmean() arguments should be >=0" << std::endl;
exc_mean.h
 1 /*error4.cpp for exc_mean.h*/
 2 #inlude <iostream>
 3 #include "exc_mean.h"
 4 
 5 using std::cin;
 6 using std::cout:
 7 using std::endl;
 8 
 9 double hmean(double a, double b);
10 double gmean(double a, double b);
11 
12 int main()
13 {
14     double x,y,z;
15     
16     while(cin>>x>>y)
17     {
18         try
19         {
20             z = hmean(x,y);
21             z = gmean(x,y);
22         }
23         catch(bad_hmean & b)  //与hmean()中的throw匹配
24         {
25             b.mesg();
26             cout << "继续输入数字或者是按下任意字母退出程序!"<< endl;
27             continue;  //会到while循环
28         }
29         catch(bad_gmean & h)  //与gmean()中的throw匹配
30         {
31             h.mesg();
32             cout << "Try again!" << endl;
33             cout << "继续输入数字或者是按下任意字母退出程序!"<< endl;//如果输入的是字母,则while中的条件会判断不成立从而退出循环
34             continue;  //会到while循环
35         }
36     }
37     cout << "Bye!\n";
38     return 0;
39 }
40 
41 double hmean(double a, double b)
42 {
43     bad_hmean bh(a,b);  //使用a、b创建并初始化类对象bh
44     if(a == -b)
45         throw bh;  //如果执行该语句,则返回到main()中的catch(bad_hmean & b)处继续执行
46     return 2.0*a*b/(a+b);
47 }
48 double gmean(double a, double b)
49 {
50     bad_gmean bg(a,b);  //使用a、b创建并初始化类对象bg
51     if(a<0 || b<0)
52         throw bg;  //如果执行该语句,则返回到main()中的catch(bad_gmean & h)处继续执行
53     return std::sqrt(a*b);
54 }
error4.cpp

5、栈解退

 栈解退定义:

 1 /*将对象作为异常类型*/
 2 #include <iostream>
 3 
 4 class bad_hmean
 5 {
 6 private:
 7     double v1;
 8     double v2;
 9 public:
10     bad_hmean(double a=0, double b=0) : v1(a),v2(b) {}  //使用默认值初始化私有变量,创建对象时,可以使用新值覆盖默认值
11     void mesg();  //用于报告错误信息
12 };
13 inline void bad_hmean::mesg()
14 {
15     std::cout << "hmean(" << v1 << ", " << v2 << "):" << "invalid arguments: a=-b not allowed!" << std::endl;
16 }
17 
18 class bad_gmean
19 {
20 private:
21     double v1;
22     double v2;
23 public:
24     bad_gmean(double a=0, double b=0) : v1(a),v2(b) {}  //使用默认值初始化私有变量,创建对象时,可以使用新值覆盖默认值
25     const char* mesg();  //用于报告错误信息
26 };
27 inline const char* bad_gmean::mesg()
28 {
29     return "gmean() arguments should be >=0" << std::endl;
exc_mean.h
  1 /*栈解退*/
  2 #include <iostream>
  3 #include <cmath>  //for sqrt()
  4 #include <string>
  5 #include "exc_mean.h"
  6 
  7 /*定义一个类*/
  8 class demo
  9 {
 10 private:
 11     std::string word;
 12 public:
 13     demo(const std::string & str)  //构造函数的定义
 14     {
 15         word = str;
 16         std::cout << "demo " << word << " created\n";
 17     }
 18     ~demo()
 19     {
 20         std::cout << "demo " << word << " destroyed\n";
 21     }
 22     void show() const
 23     {
 24         std::cout << "demo " << word << " lives\n";
 25     }
 26 };
 27 
 28 /*声明函数*/
 29 double hmean(double a, double b);  
 30 double gmean(double a, double b);  
 31 double means(double a, double b);  
 32 
 33 int main()
 34 {
 35     using std::cout;
 36     using std::cin;
 37     using std::endl;
 38     
 39     double x,y,z;  //使用一个函数块
 40     {
 41         demo d1("found in block in main()");  //创建demo对象
 42         cout << "Enter two numbers: ";
 43         while(cin>>x>>y)
 44         {
 45             try  //正常执行完try中的语句后会自动回到while处继续执行
 46             {
 47                 z = means(x,y);
 48                 cout << "The mean mean of " << x << " and " <<y << " is " << z << endl;
 49                 cout << "继续输入数字或者是按下任意字母退出程序!"<< endl;//如果输入的是字母,则while中的条件会判断不成立从而退出循环
 50             }
 51             catch(bad_hmean & b)  //与hmean()中的throw匹配
 52             {
 53                 b.mesg();
 54                 cout << "继续输入数字或者是按下任意字母退出程序!"<< endl;
 55                 continue;  //会到while循环
 56             }
 57             catch(bad_gmean & h)  //与gmean()中的throw匹配
 58             {
 59                 h.mesg();
 60                 cout << "Try again!" << endl;
 61                 cout << "继续输入数字或者是按下任意字母退出程序!"<< endl;//如果输入的是字母,则while中的条件会判断不成立从而退出循环
 62                 continue;  //会到while循环
 63             }
 64         }
 65     }
 66     cout << "Bye!\n";
 67     cin.get();  //使dos界面停止
 68     return 0;
 69 }
 70 
 71 /*函数定义*/
 72 double hmean(double a, double b)
 73 {
 74     bad_hmean bh(a,b);  //使用a、b创建并初始化类对象bh
 75     if(a == -b)
 76         throw bh;  //如果执行该语句,则返回到means()中的catch(bad_hmean & b)处继续执行
 77                    //因为程序是沿着函数调用的反方向去寻找try块,找到了try块之后,程序会到try块的最后处继续执行(即执行try块后面的语句)
 78     return 2.0*a*b/(a+b);
 79 } 
 80 double gmean(double a, double b)
 81 {
 82     bad_gmean bg(a,b);  //使用a、b创建并初始化类对象bg
 83     if(a<0 || b<0)
 84         throw bg;  //如果执行该语句,则去means()中找有没有catch(bad_gmean & h)处继续执行,若没有则到main()中去找与bg匹配的catch
 85                    //因为程序是沿着函数调用的反方向去寻找try块,找到了try块之后,程序会到try块的最后处继续执行(即执行try块后面的语句)
 86     return std::sqrt(a*b);
 87 } 
 88 double means(double a, double b)
 89 {
 90     double am,bm,gm;
 91     demo d2("found in means()");  //创建另外一个demo对象
 92     am = (a+b)/2.0;
 93     try
 94     {
 95         hm = hmean(a,b);
 96         gm = gmean(a,b);
 97     }
 98     catch(bad_hmean & b)
 99     {
100         b.mesg();  //调用bad_hmean类中的mesg()
101         std::cout << "Caught in means()\n";
102         throw;  //该throw会导致means()终止执行,并将标识为bad_hmean & b(bad_hmean类对象)传递给main()中的catch(bad_hmean & b)
103     }
104     d2.show();  //假如上面的throw 被执行,则不会执行该句
105     return (am+bm+gm)/3.0;
106 }
error5.cpp

程序执行过程如下:

情况1:

情况2:

 6、C风格字符串和C++字符串输入问题

01)对于C风格字符串

1 char info[100];
2 cin >> info;  //读入一个单词
3 cin.getline(info,100);  //读入一行,并直接从输入流中删除掉了键盘输入的换行符(可以接收空格).读入100个字符,并将info[100]设置为\0
4 cin.getline(info,100,':');  //读入一行,直到遇到:,并且丢弃:
5 cin.get(info,100);  //读入一行,把键盘输入的换行符留着了输入缓冲区中(可以接收空格).读入100个字符,并将info[100]设置为\0

02)对于string对象(有两种方式输入)

1 string stuff;
2 cin >> stuff;  //读入一个单词
3 getline(cin,stuff);  //读入一行,并从输入流中删除掉了键盘输入的换行符(可以接收空格)
4 getline(stuff,':');  //读入一行,直到遇到:,并且丢弃:

string版本的getline()将自动调整string对象的大小,使之刚好能够存储输入的字符,且不需要指定读取多少个字符的数值参数

03)string结束读入的条件:
    (1)到达文件尾。此时输入流的eofbit将被设置,意味着方法fial()和eof()都将返回true;
    (2)遇到分界字符(默认为\n),这时将把分界字符从输入流中删除它,但不存储它;
    (3)读取的字符数达到最大允许值,这时将设置输入流的fialbit,这意味着方法fail()将返回true。

 

posted @ 2019-08-21 22:44  兵临城下的匹夫  阅读(321)  评论(0编辑  收藏  举报
TOP