《C++沉思录》第八章《一个面向对象程序范例》讲的是用C++来实现一个算术表达式树,来演示C++
面向对象编程的数据抽象、继承、动态绑定等特性。当然这个例子也不是十分完美,通过我们的修改同时
可以学习“引用类型参数的使用”方面的知识。也就是什么时候该使用引用类型的参数。
书中的演示的代码如下(进行了很小的改动):
- #include <iostream>
-
using std::cout;
-
using std::string;
-
using std::ostream;
-
using std::cout;
-
using std::endl;
-
-
//所以节点的基类
-
class Expr_node
-
{
-
private:
-
//友元函数的说明可以写在类体的任何部分。在使用友元函数时,需要在参数表中显示地指明它要访问的对象。
-
friend ostream& operator<<(ostream&, const Expr_node&);
-
friend class Expr; //友元类Expr来操作use
-
int use; //引用计数
-
-
protected: //派生类可以调用保护函数
- virtual ~Expr_node() { /*cout << "~Expr_node" << endl; */ }
-
Expr_node(): use(1) { cout << "Expr_node()" << endl;} //允许派生类调用构造函数,但是不允许
- //直接构造类Expr_node的对象.
-
public:
-
virtual void print(ostream&) const = 0; //只要派生类实现了print函数,就可以用友元函数<<输出
-
};
-
ostream& operator<<(ostream& o, const Expr_node& e)
-
{
-
e.print(o); //调用print实现友元函数
-
return o;
-
}
-
-
//句柄类,实现表达式类
-
class Expr
-
{
-
private:
-
friend ostream& operator<<(ostream&, const Expr&);
-
Expr_node* p; //指向各种node节点.
-
-
public:
-
Expr(int);
-
Expr(const string&, Expr);
-
Expr(const string&, Expr, Expr);
-
Expr(const Expr&);
-
Expr& operator=(const Expr&);
-
~Expr() {
-
if(--p->use == 0){ //友元类Expr可以直接访问Expr_node中的成员数据
- //调用ostream& operator<<(ostream& o, const Expr_node& e)
-
cout << "delete " << *p << endl;
-
delete p;
-
}
-
cout << "~Expr" << endl;
-
}
-
};
-
-
//整数节点
-
class Int_node: public Expr_node
-
{
-
private:
-
friend class Expr;
-
int n;
-
-
Int_node(int k): n(k) {cout << "Int_node()" << n << endl;}
-
void print(ostream& o) const { o << n; }
-
int getN(){ return n;}
-
};
-
-
//一元节点
-
class Unary_node: public Expr_node
-
{
-
private:
-
friend class Expr;
-
string op;
-
Expr opnd;
-
-
Unary_node(const string& a, Expr b): op(a), opnd(b) {}
-
void print(ostream& o) const { o << "(" << op << opnd << ")"; }
-
};
-
-
//二元节点
-
class Binary_node: public Expr_node
-
{
-
private:
-
friend class Expr;
-
string op;
-
Expr left;
-
Expr right;
-
-
Binary_node(const string& a, Expr b, Expr c)
-
: op(a), left(b), right(c) { cout << "Binary_node()" << a << b << c << endl;}
-
void print(ostream& o) const{
-
o << "(" << left << op << right << ")";
-
}
-
};
-
-
//句柄类的实现
-
Expr::Expr(int n){
-
p = new Int_node(n);
-
}
-
Expr::Expr(const string& op, Expr t){
-
p = new Unary_node(op, t);
-
}
-
Expr::Expr(const string& op, Expr left, Expr right){
-
p = new Binary_node(op, left, right);
-
}
-
Expr::Expr(const Expr& t){
-
cout << "Expr::Expr(const Expr& t)" << endl;
-
p = t.p;
-
++p->use;
-
}
-
Expr& Expr::operator=(const Expr& rhs){
-
rhs.p->use++;
-
if(--p->use == 0)
-
delete p;
-
p = rhs.p;
-
return *this;
-
}
-
ostream& operator<<(ostream& o, const Expr& t){
-
t.p->print(o);
-
return o;
-
}
-
-
int main()
-
{
-
-
Expr a(3);
-
cout << "a : " << a << endl << endl;
-
-
Expr b(4);
-
cout << "b : " << b << endl << endl;
-
-
Expr c("+", a, b);
-
cout << "c : " << c << endl << endl;
-
-
return 0;
-
}
编译: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.
- Expr::Expr(const string& op, Expr left, Expr right){
-
p = new Binary_node(op, left, right);
-
}
接着又调用了构造函数: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)虽然复制构造函数很简单:
- Expr::Expr(const Expr& t){
-
cout << "Expr::Expr(const Expr& t)" << endl;
-
p = t.p;
-
++p->use;
-
}
只是指针的复制和加法的操作,调用次数多了,还是会影响性能的。那么有没有什么办法来介绍复制构造
函数的调用呢?这里造成多次生成副本的原因是因为参数类型的。在C中,可以使用指针来避免在函数调用
是参数的复制,而在C++中除了指针,我们还可以使用“引用参数类型”来更简洁的达到C中指针参数的效果。
修改后的代码如下(粗体为修改的地方):
- #include <iostream>
-
using std::cout;
-
using std::string;
-
using std::ostream;
-
using std::cout;
-
using std::endl;
-
-
//所以节点的基类
-
class Expr_node
-
{
-
private:
-
//友元函数的说明可以写在类体的任何部分。在使用友元函数时,需要在参数表中显示地指明它要访问的对象。
-
friend ostream& operator<<(ostream&, const Expr_node&);
-
friend class Expr; //友元类Expr来操作use
-
int use; //引用计数
-
-
protected: //派生类可以调用保护函数
- virtual ~Expr_node() {/*cout << "~Expr_node" << endl; */ }
-
Expr_node(): use(1) { cout << "Expr_node()" << endl;} //允许派生类调用构造函数,但是不
- //允许直接构造类Expr_node的对象.
-
public:
-
virtual void print(ostream&) const = 0; //只要派生类实现了print函数,就可以用友元函数输出.
-
};
-
-
ostream& operator<<(ostream& o, const Expr_node& e)
-
{
-
e.print(o); //调用print实现友元函数
-
return o;
-
}
-
-
//句柄类,实现表达式类
-
class Expr
-
{
-
private:
-
friend ostream& operator<<(ostream&, const Expr&);
-
Expr_node* p; //指向各种node节点.
-
-
public:
-
Expr(int);
-
Expr(const string&, const Expr&);
-
Expr(const string&, const Expr&, const Expr&);
-
Expr(const Expr&);
-
Expr& operator=(const Expr&);
-
~Expr() {
-
if(--p->use == 0){ //友元类Expr可以直接访问Expr_node中的成员数据
- //调用ostream& operator<<(ostream& o, const Expr_node& e)
-
cout << "delete " << *p << endl;
-
delete p;
-
}
-
cout << "~Expr" << endl;
-
}
-
};
-
-
//整数节点
-
class Int_node: public Expr_node
-
{
-
private:
-
friend class Expr;
-
int n;
-
-
Int_node(int k): n(k) {cout << "Int_node()" << n << endl;}
-
void print(ostream& o) const { o << n; }
-
int getN(){ return n;}
-
};
-
-
//一元节点
-
class Unary_node: public Expr_node
-
{
-
private:
-
friend class Expr;
-
string op;
-
Expr opnd;
-
-
Unary_node(const string& a, const Expr& b): op(a), opnd(b) {}
-
void print(ostream& o) const { o << "(" << op << opnd << ")"; }
-
};
-
-
//二元节点
-
class Binary_node: public Expr_node
-
{
-
private:
-
friend class Expr;
-
string op;
-
Expr left;
-
Expr right;
-
-
Binary_node(const string& a, const Expr& b, const Expr& c)
-
: op(a), left(b), right(c) { cout << "Binary_node()" << a << b << c << endl;}
-
void print(ostream& o) const{
-
o << "(" << left << op << right << ")";
-
}
-
};
-
-
//句柄类的实现
-
Expr::Expr(int n){
-
p = new Int_node(n);
-
}
-
Expr::Expr(const string& op, const Expr& t){
-
p = new Unary_node(op, t);
-
}
-
Expr::Expr(const string& op, const Expr& left, const Expr& right){
-
p = new Binary_node(op, left, right);
-
}
-
Expr::Expr(const Expr& t){
-
cout << "Expr::Expr(const Expr& t)" << endl;
-
p = t.p;
-
++p->use;
-
}
-
Expr& Expr::operator=(const Expr& rhs){
-
rhs.p->use++;
-
if(--p->use == 0)
-
delete p;
-
p = rhs.p;
-
return *this;
-
}
-
ostream& operator<<(ostream& o, const Expr& t){
-
t.p->print(o);
-
return o;
-
}
-
-
int main()
-
{
-
Expr a(3);
-
cout << "a : " << a << endl << endl;
-
-
Expr b(4);
-
cout << "b : " << b << endl << endl;
-
-
Expr c("+", a, b);
-
cout << "c : " << c << endl << endl;
-
-
return 0;
-
}
编译: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等的私有构造函数:
- Expr::Expr(int n){
-
p = new Int_node(n);
-
}
-
Expr::Expr(const string& op, Expr t){
-
p = new Unary_node(op, t);
-
}
-
Expr::Expr(const string& op, const Expr& left, const Expr& right){
-
p = new Binary_node(op, left, right);
-
}
因为类Expr是类Int_node等的友元类!将Expr_node的派生类的所有函数定义成private,可以达到:
“只能通过它的友元类,也即使它的代理类来访问它”的效果。
总结:
函数的参数调用实际是对传入的实参生成一个副本,相当于复制了实参,这样在函数中的对该参数的修改
只是作用在这个副本上,并没有对实参进行修改,只是对这个副本进行了修改,如果你想修改实参的值,
就需要用引用或指针,还有另一种情况,如果你的实参类型比较复杂,比如类类型,那么在函数调用中他
将复制整个类类型,消耗资源,
使用引用就可以直接对这个值进行操作,避免了生成副本消耗的资源。
当我们不希望函数修改引用参数时,应该使用const引用参数。
阅读(2174) | 评论(0) | 转发(0) |