======================================================
最 近实在是太忙了,无工夫写呀。只能慢慢来了。呵呵,今天Aear讲的是class.ctor
class
class
private:
public:
};
也许这是很多初学者经常写出来的代码,Aear以前也写过。让我们来看看这段代码有什么问题。
首 先需要说明的是,在一个class实例化之前,所有的member都会被初始化,如果member是个class,那么那个class的 constructor就会被调用。也就是说,在运行AnotherClass的constructor之前,SomeClass的 constructor就已经运行了。接下来的代码里,SomeClassInstance又被重新执行了次
如果有多个类成员,可以用","来分割,如:
值 得注意的是,
同理,在声明类变量被附初值的时候,使用拷贝构造函数,效率更高:
=====错误=====
class
x1
=====正确=====
class
===================分割线===================
从 上面的例子可以看到,几乎所有的class,都需要提供拷贝构造函数,也就是
class1
public:
private:
}
如 果class1不提供operator
void
func(x1);
这 时候,c++会调用class1的拷贝构造函数,来把参数从x1里拷贝到Para,如果class1没有提供copy
这样大家就知道为什么要同时提供copy
class1
...
private:
}
这样别人在执行
还有,在声明构造函数的时候,单参数的构造函数,最好都用explicit来声明,例如:
class1
public:
};
其中class1(int
class1
的 时候,因为2不是class1,所以c++会用隐性的类型转换,也就是把2转换成class1,因此会调用class1(2),然后用operator
explicit
这样做再执行
class1
===================分割线===================
在运行constructor的时候,值得注意的一点就是,如果在constructor里,要初始化会throw
class1
}
大家看的明白了吧,如果不catch
===================分割线===================
最后关于destructor,需要注意的是,如果是被继承的base
BaseClass
{
public:
}
DerivedClass
{
public:
}
BaseClass
delete
如果BaseClass的destructor是virtual,那么正确的ctor
BaseClass();
DerivedClass();
~DerivedClass();
~BaseClass();
如果不是Virtual,调用顺序是:
BaseClass();
DerivedClass();
~BaseClass();
也 就是说,DerivedClass的派生类不能被正确调用,这主要是因为在delete的时候c++并不知道你delete的是
===================分割线===================
从上面的例子大家也看出来了,如果是派生类,那么就要调用基类的constructor,在多层次的派生类创建过程中,所以基类的constructor都要被调用。
也 许有人会说,所有的constructor和destructor都被compiler
======================================================
======================================================
继续上一章的内容,下面是经过调整后的Test Class代码:
class
private:
public:
};
Test
{
}
Test
{
}
下面我们假设要给这个Test Class添加一种新的功能,让Test Class 和 Integer之间能够进行加法操作。 也就是说可以执行下列代码:
Test x1(10);
x1 = x1 + 5;
x1 += 5;
实际上,我们不需要进行任何修改,上面的代码就能够正确执行。因为我们提供的构造函数Test(int
x1 = x1.operator + (Test(5));
x1 = x1.operator += (Test(5));
Implicit Type Conversion 实际上会带来很多的麻烦,要想避免潜在的危险,最好在Test(int
x1 = x1 + static_cast<Test>(5);
或
x1 = x1 + Test(5);
还有一点需要注意的是,如果不用explicit type conversion,可以运行:
x1 = x1 + 5;
但是在编译:
x1 = 5 + x1
的时候就会报错了,除非使用一个Temporary Object ,如:
x1 = Test(5) + x1;
要想使Test Class 支持 x1 = 5 + x1,最好的方法就是用helper function. 下面我们来看看Operator的另外一中定义方式。
==================分割线==================
我们可以使用friend function 来定义Test Class 的加法运算,代码如下:
class Test {
};
Test operator + ( const Test & Para1, const Test & Para2)
{
}
首先我们需要注意的是,Test(int data = 0)没有用explicit,也就是说可以进行隐性的类型转换,因此无论是运行:
还是:
都能够编译通过。
解决了基本的功能问题,让我们继续考虑一下效率。无论是在x1 = x1 + 5,还是在x1 = 5 + x1,都至少会掉用额外的constructor和destructor把5转换成Test Object,这种浪费是很没有必要的。其次允许compiler进行implicit type conversion并不是一个良好的习惯。解决这些问题的方法,就是提供专用的 operator + 来进行integer和Test object之间的加法操作,具体代码如下:
========== 支持 x1 + 5 ==========
Test operator + ( const Test & Para1, int Para2)
{
}
========== 支持 5 + x1 ==========
Test operator + ( int Para1, const Test & Para2 )
{
}
同时还要在class Test中加如下面2行(对于此例子并不需要,不过正常情况是需要的):
friend Test operator + ( int Para1, const Test & Para1 );
friend Test operator + ( const Test & Para1, int Para2 );
并且在constructor前面加上 explicit。当然,你也可以用Template进行定义,如下:
========== 支持 x1 + 5 ==========
template <class T>
T operator + ( const T & Para1, int Para2)
{
}
实际上对于 template的定义,我个人并不推荐. 首先是因为namespace的问题,到底是global namespace呢?还是一个local namespace?如果是global namespace,那不一定所有的global class 都需要 operator +,这样就提供了多余的class操作。local namespace倒是可以用,前提是所有的class都定义了 +=. 也许对于大多数class来讲,并不需要operator + 的操作。所以我觉得对于 operator 的定义,尽量少用 template (个人观点).
==================分割线==================
下面说说关于类型转换的operator. 对于一个Abstract Data Type来说,类型转换是经常用到的,比如我们前面提到的从 integer转换成 Test, 可以使用implicit type conversion 和 explicit type conversion. 但是如果我们想从Test 转换成 integer,compiler无法支持自动的类型转换,因此需要我们提供相应的operator:
class Test {
}
那么我们就可以执行:
实际上,operator int()又是一种implicit type conversion,这并是收程序员的控制。良好的程序设计,是programmer能够精确的控制每一个细微的操作。因此并不推荐使用 operator int(),好的方法是按照 < effective c++ > 中给出的那样,提供一个asInt() method,来做explicti type conversion:
============ explicti ============
class Test {
}
================== Test++ & ++Test ==================
相信大家都知道 Prefix ++ 和 Postfix ++的区别是什么,下面是代码:
// Prefix
Test& operator++()
{
}
// Postfix
Test operator++(int)
{
}
我们只是简单的看下效率问题,在 Prefix中也就是 ++ 返回的是reference,没有temporary object,在 Postfix中返回的是个object,使用了Temporary。相信大家都知道了,能不使用 Test++的地方就不要使用,尽量使用 ++Test。
比如:
for( iterator i = XXX; XXX; ++i) // 不要使用 i++
对于postfix, compiler并不能保证肯定会优化成 prefix,所以写代码的时候尽量注意。
================== 其他关于Operator ==================
有些operator,并不推荐进行overload,因为会出现无法预料的情况。这些operator 包括:
&&
, ||
, & , | , == , != , ","
举个简单的例子,如果你overload了",",那么有一个for循环如下:
for( Test x1 = x2,i = 0; ; ) {....}
到底是x1 = x2 和 i = 0呢?还是 x1 = x2.operator , (i) = 0 呢?如果overload了 & ,对于逻辑判断,x1 && x2,到底是
其次,很多operator overload需要很小心的对待,这些operator 如下:
new new[] delete delete[] -> [] ()
请仔细阅读 C++ 标准,了解详细内容后,再对这些operator进行overload,不然很容易造成程序的不稳定。
好了,关于operator 就说这么多了,欢迎大家有空去我的Blog坐坐http://blog.sina.com.cn/u/1261532101下次见。
======================================================
======================================================
今天讲的是 public inheritance, protected inheritance & private inheritance,内容不多,但是非常重要。基本的类的继承,也就是inheritance的概念大家都清楚,明确的定义不再详细说明了。先面举个例子来说明:
class People {
};
class Student : public People{
};
注意这行:
中的public,表明是public inheritance,如果换成protected,就是protected inheritance, private就是private inhertance. 首先需要说明的是3种inheritance在语法上相似,但是在语意上完全不同。我们先从public inheritance说起。
=====================public inheritance=====================
public inheritance最基本的概念就是"isa" ( is a )。 简单的说,继承类也就是Derived Class "is a" Base Class. 用上面的例子来说,People是base class, Student是 derived class,所以能够推导出: “student is a people” 这句话。如果你无法推导出 "isa"的关系,那么就不应该使用public inheritance.
其次,即使是能推导出 "isa" 的关系,也必须满足2个条件,才能使用 public inheritance. 这2个条件是:
在上面的例子中,student也是个people,所以能够Walk() 和 Eat(),因此public inheritance 是合理的。
如果满足 "isa" 但是不满足上述条件,建议使用 Delegation/Composition,具体关于Delegation和Composition,在"C++基本功和 Design Pattern系列(1)" 中有说明。让我们看下在《Effective C++》中的一个例子来说明这种情况:
class Rectangle {
};
class Square : public Rectangle {
};
我们大家都知道,一个正方形Square,一定是一个长方形Rectangle,所以满足"isa"的条件。我们给Rectangle提供了SetWidth()和SetHeight()的方法。如果不考虑上面2条,只考虑 "isa",那么这个 public inheritance是合理的,但是让我们看看会出现什么问题。
在Square中我们要求长和宽必须相等,因此我们提供了SetLength(),来同时设置正方形的长和宽。但是有一位Bill小朋友无法分辨长方形和正方形,因此写出了如下代码:
那么问题出现了,MySquare并不是一个Square。相信大家都明白了吧。语言的不精确性导致在设计过程中出现的错误是屡见不鲜的。因此,在public inheritance的时候要特别注意。也许有人会说,我们把SetHeight 和 SetWidth设置成Virtual然后在Square Class中重载不就可以了吗? 如果Rectangle和Square 2个class都是你来写,那么也许不会出现问题。但是如果一个非常复杂的class,包含几十个方法和几十个属性,并且由别人来写,那么你会不会仔细的阅读代码并且overlord每一个需要的方法呢?即使你这样做了,也许会带来更多的麻烦。因为有可能破坏内部数据的一致性。
让我们来看看interface inheritance的例子:
首先,鸟能飞,这个没有问题,火鸡是一种鸟,这也没有问题,但是: 火鸡不能飞。问题出现了,client能够调用Turkey的Fly()方法,但是得到的确是一个 RunTime Error! 这里必须强调下:"RUNTIME ERROR!",对于游戏程序来说,一个"RUNTIME ERROR"基本上就等于程序崩溃。和out of memory同等性质。如果你玩WOW做7个小时中间不能间断的任务,然后出现一只火鸡给个RUNTIME ERROR....我想是人都会崩溃吧。
所以对于这种错误,我们要在编译的时候尽量查出来,也就是 Prefer Compile Error over Runtime Error. 通过更改类的设计,我们可以避免类似的runtime error:
所以,要想使用public inheritance,必须满足:
=====================private inheritance=====================
private inheritance和public inheritance最大的区别就在于,private inheritance不满足"isa"的关系。举个例子:
class People {
};
class ET: private People{
};
外星人ET是一种类似人的生物,能做一些类似人的动作,但是并不是人。从C++的语法上面来讲,下面的代码是错误的:
使用private inheritance的目的只是简单的为了代码重用。因此如果不满足public inheritance的条件,可以使用 Delegation/composition 和 Private Inheritance。 那么在什么情况下使用 private inheritance,什么情况下使用Delegation/Composition 呢?
有2种情况是推荐使用 private inheritance的,其他的情况下,推荐使用Delegation/Composition.
情况1: 需要对Base Class中的 private/protect virtual 进行重载。比如类似Draw() 等等。
情况2: 不希望一个Base class被 client使用。
关于情况2,举个简单的例子:
如果我们不希望Base Class被别人直接使用,有2种方法,第一是:把它设置成为abstract class, 也就是包含pure virtual function. 第2种方法是把constructor 和 descturctor设置成 protected.代码如下:
class Base {
protected:
};
class Derived : private Base {
};
Base n; // Error, Base() cannot be called
Derived m; // ok, Derived can call Base()
这样我们又可以保证n的代码可以被m使用,又可以防止 client直接调用 Base进行我们不希望的操作。
=====================protected inheritance=====================
protected inheritance和 private inheritance没有本质的区别,但是如果我们希望的 Derived Class 能够作为其他 class的基类,那么就应该使用 protected inheritance.
今天就说这么多,有空来我的Blog做客: http://blog.sina.com.cn/u/1261532101 ,下次见!