Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1786077
  • 博文数量: 413
  • 博客积分: 8399
  • 博客等级: 中将
  • 技术积分: 4325
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-09 10:44
文章分类

全部博文(413)

文章存档

2015年(1)

2014年(18)

2013年(39)

2012年(163)

2011年(192)

分类: C/C++

2011-08-27 14:22:05

《C++沉思录》第八章《一个面向对象程序范例》讲的是用C++来实现一个算术表达式树,来演示C++
面向对象编程的数据抽象、继承、动态绑定等特性。当然这个例子也不是十分完美,通过我们的修改同时
可以学习“引用类型参数的使用”方面的知识。也就是什么时候该使用引用类型的参数。
书中的演示的代码如下(进行了很小的改动):
  1. #include <iostream>
  2. using std::cout;
  3. using std::string;
  4. using std::ostream;
  5. using std::cout;
  6. using std::endl;

  7. //所以节点的基类
  8. class Expr_node
  9. {
  10. private:
  11.     //友元函数的说明可以写在类体的任何部分。在使用友元函数时,需要在参数表中显示地指明它要访问的对象。
  12.     friend ostream& operator<<(ostream&, const Expr_node&);
  13.     friend class Expr;    //友元类Expr来操作use
  14.     int    use;    //引用计数

  15. protected:    //派生类可以调用保护函数
  16.     virtual ~Expr_node() { /*cout << "~Expr_node" << endl; */ }
  17.     Expr_node(): use(1) { cout << "Expr_node()" << endl;} //允许派生类调用构造函数,但是不允许
  18.                                                           //直接构造类Expr_node的对象.
  19. public:
  20.     virtual void print(ostream&) const = 0;  //只要派生类实现了print函数,就可以用友元函数<<输出
  21. };
  22. ostream& operator<<(ostream& o, const Expr_node& e)
  23. {
  24.     e.print(o);    //调用print实现友元函数
  25.     return o;
  26. }

  27. //句柄类,实现表达式类
  28. class Expr
  29. {
  30. private:
  31.     friend ostream& operator<<(ostream&, const Expr&);
  32.     Expr_node*    p;    //指向各种node节点.

  33. public:
  34.     Expr(int);
  35.     Expr(const string&, Expr);
  36.     Expr(const string&, Expr, Expr);
  37.     Expr(const Expr&);
  38.     Expr& operator=(const Expr&);
  39.     ~Expr() {
  40.         if(--p->use == 0){    //友元类Expr可以直接访问Expr_node中的成员数据
  41.             //调用ostream& operator<<(ostream& o, const Expr_node& e)
  42.             cout << "delete " << *p << endl;
  43.             delete p;
  44.         }
  45.         cout << "~Expr" << endl;
  46.     }
  47. };

  48. //整数节点
  49. class Int_node: public Expr_node
  50. {
  51. private:
  52.     friend class Expr;
  53.     int    n;

  54.     Int_node(int k): n(k) {cout << "Int_node()" << n << endl;}
  55.     void print(ostream& o) const { o << n; }
  56.     int getN(){ return n;}
  57. };

  58. //一元节点
  59. class Unary_node: public Expr_node
  60. {
  61. private:
  62.     friend class Expr;
  63.     string    op;
  64.     Expr    opnd;

  65.     Unary_node(const string& a, Expr b): op(a), opnd(b) {}
  66.     void print(ostream& o) const { o << "(" << op << opnd << ")"; }
  67. };

  68. //二元节点
  69. class Binary_node: public Expr_node
  70. {
  71. private:
  72.     friend class Expr;
  73.     string    op;
  74.     Expr    left;
  75.     Expr    right;

  76.     Binary_node(const string& a, Expr b, Expr c)
  77.         : op(a), left(b), right(c) { cout << "Binary_node()" << a << b << c << endl;}
  78.     void print(ostream& o) const{
  79.         o << "(" << left << op << right << ")";
  80.     }
  81. };

  82. //句柄类的实现
  83. Expr::Expr(int n){
  84.     p = new Int_node(n);
  85. }
  86. Expr::Expr(const string& op, Expr t){
  87.     p = new Unary_node(op, t);
  88. }
  89. Expr::Expr(const string& op, Expr left, Expr right){
  90.     p = new Binary_node(op, left, right);
  91. }
  92. Expr::Expr(const Expr& t){
  93.     cout << "Expr::Expr(const Expr& t)" << endl;
  94.     p = t.p;
  95.     ++p->use;
  96. }
  97. Expr& Expr::operator=(const Expr& rhs){
  98.     rhs.p->use++;
  99.     if(--p->use == 0)
  100.             delete p;
  101.     p = rhs.p;
  102.     return *this;
  103. }
  104. ostream& operator<<(ostream& o, const Expr& t){
  105.     t.p->print(o);
  106.     return o;
  107. }

  108. int main()
  109. {

  110.     Expr a(3);
  111.     cout << "a : " << a << endl << endl;

  112.     Expr b(4);
  113.     cout << "b : " << b << endl << endl;

  114.     Expr c("+", a, b);
  115.     cout << "c : " << c << endl << endl;

  116.     return 0;
  117. }
编译:g++ -Wall Expr_node.cpp  -o expr
运行:./expr
结果:
Expr_node()
Int_node()3
a : 3

Expr_node()
Int_node()4
b : 4

Expr::Expr(const Expr& t)
Expr::Expr(const Expr& t)
Expr::Expr(const Expr& t)
Expr::Expr(const Expr& t)

Expr_node()
Expr::Expr(const Expr& t)
Expr::Expr(const Expr& t)

Binary_node()+34
~Expr
~Expr
~Expr
~Expr
c : (3+4)

delete (3+4)
~Expr
~Expr
~Expr
delete 4
~Expr
delete 3
~Expr
从运行结果我们可以看到Expr::Expr(const Expr& t)复制构造函数被调用了6次之多!我们来分析一下
者6次调用分别发生在什么地方:
我们知道:函数的参数调用实际是对传入的实参生成一个副本相当于复制了实参,这样在函数中的对
                 该参数的修改只是作用在这个副本上。
在main函数中:代码 Expr c("+", a, b); 导致了对a, b分别调用了一次Expr::Expr(const Expr& t)
而代码 Expr c("+", a, b);会导致调用构造函数: Expr::Expr(const string& op, Expr left, Expr right)
这里又对a, b分别调用了一次Expr::Expr(const Expr& t)来生产a, b的副本left, right.
  1. Expr::Expr(const string& op, Expr left, Expr right){
  2.     p = new Binary_node(op, left, right);
  3. }
接着又调用了构造函数:Binary_node(const string& a, Expr b, Expr c),所以又要产生left, right的
副本b, c。所以分别对left, right调用了一次Expr::Expr(const Expr& t)
总共调用了6次复制构造函数:Expr::Expr(const Expr& t)
虽然复制构造函数很简单:
  1. Expr::Expr(const Expr& t){
  2.     cout << "Expr::Expr(const Expr& t)" << endl;
  3.     p = t.p;
  4.     ++p->use;
  5. }
只是指针的复制和加法的操作,调用次数多了,还是会影响性能的。那么有没有什么办法来介绍复制构造
函数的调用呢?这里造成多次生成副本的原因是因为参数类型的。在C中,可以使用指针来避免在函数调用
是参数的复制,而在C++中除了指针,我们还可以使用“引用参数类型”来更简洁的达到C中指针参数的效果。
修改后的代码如下(粗体为修改的地方):
  1. #include <iostream>
  2. using std::cout;
  3. using std::string;
  4. using std::ostream;
  5. using std::cout;
  6. using std::endl;

  7. //所以节点的基类
  8. class Expr_node
  9. {
  10. private:
  11.     //友元函数的说明可以写在类体的任何部分。在使用友元函数时,需要在参数表中显示地指明它要访问的对象。
  12.     friend ostream& operator<<(ostream&, const Expr_node&);
  13.     friend class Expr;    //友元类Expr来操作use
  14.     int    use;    //引用计数

  15. protected:    //派生类可以调用保护函数
  16.     virtual ~Expr_node() {/*cout << "~Expr_node" << endl; */ }
  17.     Expr_node(): use(1) { cout << "Expr_node()" << endl;}    //允许派生类调用构造函数,但是不
  18.                                                              //允许直接构造类Expr_node的对象.
  19. public:
  20.     virtual void print(ostream&) const = 0;    //只要派生类实现了print函数,就可以用友元函数输出.
  21. };

  22. ostream& operator<<(ostream& o, const Expr_node& e)
  23. {
  24.     e.print(o);    //调用print实现友元函数
  25.     return o;
  26. }

  27. //句柄类,实现表达式类
  28. class Expr
  29. {
  30. private:
  31.     friend ostream& operator<<(ostream&, const Expr&);
  32.     Expr_node*    p;    //指向各种node节点.

  33. public:
  34.     Expr(int);
  35.     Expr(const string&, const Expr&);
  36.     Expr(const string&, const Expr&, const Expr&);
  37.     Expr(const Expr&);
  38.     Expr& operator=(const Expr&);
  39.     ~Expr() {
  40.         if(--p->use == 0){    //友元类Expr可以直接访问Expr_node中的成员数据
  41.             //调用ostream& operator<<(ostream& o, const Expr_node& e)
  42.             cout << "delete " << *p << endl;
  43.             delete p;
  44.         }
  45.         cout << "~Expr" << endl;
  46.     }
  47. };

  48. //整数节点
  49. class Int_node: public Expr_node
  50. {
  51. private:
  52.     friend class Expr;
  53.     int    n;

  54.     Int_node(int k): n(k) {cout << "Int_node()" << n << endl;}
  55.     void print(ostream& o) const { o << n; }
  56.     int getN(){ return n;}
  57. };

  58. //一元节点
  59. class Unary_node: public Expr_node
  60. {
  61. private:
  62.     friend class Expr;
  63.     string    op;
  64.     Expr    opnd;

  65.     Unary_node(const string& a, const Expr& b): op(a), opnd(b) {}
  66.     void print(ostream& o) const { o << "(" << op << opnd << ")"; }
  67. };

  68. //二元节点
  69. class Binary_node: public Expr_node
  70. {
  71. private:
  72.     friend class Expr;
  73.     string    op;
  74.     Expr    left;
  75.     Expr    right;

  76.     Binary_node(const string& a, const Expr& b, const Expr& c)
  77.         : op(a), left(b), right(c) { cout << "Binary_node()" << a << b << c << endl;}
  78.     void print(ostream& o) const{
  79.         o << "(" << left << op << right << ")";
  80.     }
  81. };

  82. //句柄类的实现
  83. Expr::Expr(int n){
  84.     p = new Int_node(n);
  85. }
  86. Expr::Expr(const string& op, const Expr& t){
  87.     p = new Unary_node(op, t);
  88. }
  89. Expr::Expr(const string& op, const Expr& left, const Expr& right){
  90.     p = new Binary_node(op, left, right);
  91. }
  92. Expr::Expr(const Expr& t){
  93.     cout << "Expr::Expr(const Expr& t)" << endl;
  94.     p = t.p;
  95.     ++p->use;
  96. }
  97. Expr& Expr::operator=(const Expr& rhs){
  98.     rhs.p->use++;
  99.     if(--p->use == 0)
  100.             delete p;
  101.     p = rhs.p;
  102.     return *this;
  103. }
  104. ostream& operator<<(ostream& o, const Expr& t){
  105.     t.p->print(o);
  106.     return o;
  107. }

  108. int main()
  109. {
  110.     Expr a(3);
  111.     cout << "a : " << a << endl << endl;

  112.     Expr b(4);
  113.     cout << "b : " << b << endl << endl;

  114.     Expr c("+", a, b);
  115.     cout << "c : " << c << endl << endl;

  116.     return 0;
  117. }
编译:g++ -Wall Expr_node.cpp  -o expr
运行:./expr
结果:
Expr_node()
Int_node()3
a : 3

Expr_node()
Int_node()4
b : 4

Expr_node()
Expr::Expr(const Expr& t)
Expr::Expr(const Expr& t)
Binary_node()+34
c : (3+4)

delete (3+4)
~Expr
~Expr
~Expr
delete 4
~Expr
delete 3
~Expr
很显然,只是调用了两次Expr的复制构造函数。并且这两次调用发生在:
Binary_node(const string& a, const Expr& b, const Expr& c)构造函数中。

比较修改之前和修改之后的代码,可知没有使用引用参数时,每调用一次函数,就要生成一次实际参数
的副本
,而才有引用参数可以避免,因为它实际上相当与一个指针。

要理解该程序要搞清楚下面几个问题:
1.为什么要将Expr_node::Expr_node(): use(1) { cout << "Expr_node()" << endl;}定义成
   protected?
因为类
Expr_node是一个虚拟基类,所以不能允许直接调用它的构造函数来获得它的对象,但同时
Expr_node的派生类有需用调用基类的构造函数来初始化use. 所以定义成protected.
2.为什么类Expr中可以调用类Int_node等的私有构造函数:
  1. Expr::Expr(int n){
  2.     p = new Int_node(n);
  3. }
  4. Expr::Expr(const string& op, Expr t){
  5.     p = new Unary_node(op, t);
  6. }
  7. Expr::Expr(const string& op, const Expr& left, const Expr& right){
  8.     p = new Binary_node(op, left, right);
  9. }
因为类Expr是类Int_node等的友元类!将Expr_node的派生类的所有函数定义成private,可以达到:
“只能通过它的友元类,也即使它的代理类来访问它”的效果。

总结:
函数的参数调用实际是对传入的实参生成一个副本,相当于复制了实参,这样在函数中的对该参数的修改
只是作用在这个副本上,并没有对实参进行修改,只是对这个副本进行了修改,如果你想修改实参的值,
就需要用引用或指针,还有另一种情况,如果你的实参类型比较复杂,比如类类型,那么在函数调用中他
将复制整个类类型,消耗资源,使用引用就可以直接对这个值进行操作,避免了生成副本消耗的资源
当我们不希望函数修改引用参数时,应该使用const引用参数。
阅读(2180) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~