关于作者

用户名:cyberfan
笔名:cyberfan
地区:
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



LOMO是一种生活态度!

访问统计:
文章个数:1067
评论个数:168
留言条数:0




Powered by BlogDriver 2.1

cyberfan's blog

 

性格决定命运 思路决定出路 建站目的:网络资源丰富,为了大家互相交流,资源共享。声明:本站部分资料来自网络和论坛,不做商业用途,仅与网友分享!!如有侵权请告之 cyberfan@163.com,本站将立即删除!特此声明!!

文章

深度探索C++对象模型(二)  (作者置顶)

深度探索C++对象模型(6)

这是这个系列笔记的第7篇了,我们还在和构造函数打交道,以前写程序时怎么根本没有考虑过构造函数的事情呢?原来编译器为我们做了这么多的事情,我们都不知道.,要想完全搞明白,看来还需要一段时间.我们继续向下走,进入一个新的章节.在第三章一开始,我就吃了一惊..书上给出了一个例子:
class X{};
class Y:public virtual class X{};
class Z:public virtual class X{};
class A:public Y,public Z{};

下面的结果会因为机器,以及编译有关,不同的情况会产生不同的结果.(怎么会是这样?)

sizeof X; //结果为1
sizeof Y; //结果为8
sizeof Z; //结果为8
sizeof A; //结果为12

一个没有任何成员的类,大小居然不是0.

为什么?

首先一个没有明显的含有成员的类,它的大小不是0,因为实际上它不是空的,它被编译器安插了一个char,为的是使这个类的两个对象能够在内存中被分配独一无二的地址.至于两个派生的类Y和Z,因为语言本身造成的负担,还有编译器对于特殊情况进行的优化处理,再有Alignment的限制,因此结果变成了8.这个8是怎么组成的?

4个bytes用来存放指针,什么指针?指向virtual base class subobject的指针呀.

一个同class X一样的char.它占了1 个bytes.然后受到Alignment的限制,所以填补了3个bytes.

4+1+3=8

不过需要注意的是不同的编译器Y和Z大小的结果也会不同.因为新的编译器会将一个空的virtual base class看做是派生类对象的开头部分,因此派生类有了member,因此也就不必分配char的那一个bytes.也就用不到填补的3个bytes,因此有可能在某些编译器中,class Y和class Z的大小为4.

最后看看A.根据我们对class Y的分析可以得出以下算式:

4+4+1+3=12;

不是我们想象的16,而是12.如果换成我们上面说的新的编译器来编译,结果很有可能是8.

我们来看Data Member 的Binding,现在我们对数据成员的绑定只需要记住一个防御性风格:始终把嵌套类型的声明放在class的开始部分,这样做可以确保非直觉绑定的正确性。看下面的一个例子:

typedef int length; //zai
class point3d
{
public:
//length被决议成global typedef 也就是int
//_val被决议成Point3d::_val
void mumble(length val){_val=val;}
length mumble(){return _val;}
//……
private:
//length必须在这个class对它的第一个参考操作之前被看见
//这样声明将使先前的参考操作不合法
typedef float length;
length _val;
//……
};

怎么成了抄书了,雷神也不知不觉,可能是在这章的理解上比较容易些吧,不用去想个看的见摸的着的东西比划。好象小朋友学算术,一位数的计算不用掰手指头,可是两位数或者三位数的计算,手指头加上脚指头还是不够。学习就是这么回事。理解力和抽象能力很重要。回来继续学习。

通过这一章我还知道了。数据成员的布局。数据成员的存取。并且对Static data members有了进一步的了解,在class的生命周期中,静态成员被看作是全局变量,每一个member的存取不会导致任何空间或效率上的额外负担。不论是从一个复杂的继承关系中继承还是直接声明的,Static data member都只会有一个实体。并且有着非常直接的存取路径。另外如果两个类都声明了一个相同名字的静态成员变量,那么编译器会通过一种算法,为我们解决名字冲突的问题。而非静态的成员变量的存去实际上是通过implicit class object(this指针)来完成的。例如
Point3d

Point3d::translate(const Point3d &pt)
{
x+=pt.x;
y+=pt.y;
z+=pt.z;
}

被编译器经过内部转换成为了下面这个样子:

Point3d
Point3d::translate(Point3d *const this,const Point3d &pt)
{
this->x+=pt.x;
this->y+=pt.y;
this->z+=pt.z;
}

如果要对一个非静态的成员变量进行存取,编译器会把类对象的起始地址加上数据成员的偏移量。例如:

Point3d origin;
origin._y=0.0;
//地址&origin._y将等于
&origin+(&Point3d::_y-1);
目的是使编译系统能够区分出以下两种情况:
一个指向数据成员的指针,用来指出类的第一个成员。
一个指向数据成员的指针,没有指出任何成员。
这是什么意思?什么是指向数据成员的指针。书上的例子:
class Point3d
{
public:
virtual ~Point3d();
//……
protected:
static Point3d origin;//静态的数据成员,位置在class object之外
float x,y,z;//每个float是4bytes
}
&Point3d::z; //这个值是什么?

我们在这篇文章开始的时候已经知道了还有一个vptr,不过vptr的位置也许在对象的开始,也许在对象的结尾部。所以上面的操作的值应该是8或者12(如果vptr在前面的话)。但实际上取会的值被加上了1。原因是必须要区别一个不指向任何成员的指针,和一个指向第一个成员的指针。又有点不好理解了,举个例子:

想象你和你的另外两个朋友合住一个三室一厅的房子,你住在第一间。如果你给一个你们三个人共同的朋友的地址你可以给房号就行了。不用给出你们的任意一个人的那间房子号(不指向任何成员)。但如果你给你的一个私人朋友地址,你会给出房间号和你的那个房间号。为了使这个地址有区别,你必须有一个厅来作为偏移量(offset)。不知道大家明白这个例子吗,也许这个例子会影响你的正确思维。那就太糟糕了。不过我还是喜欢这样想问题,也许不太准确,但可以帮助我,因为想象一个内存空间比想象一个三居室要难好几点儿。

深度探索C++对象模型(7)

在单一继承的体系中,虚函数机制是一种很有效率的机制。我们判断一个类是否支持多态,只需要看它有没有虚函数便可以了。 第四章,函数的语意学。先做个复习C++支持三种成员函数:静态、虚、和非静态。每一种函数的调用方式都不同,当然他们的作用也会有区别,一般来说我们只要掌握根据我们的需要正确的使用这三种类型的成员函数便可以了,至于内部是如何运做的我们可以不知。

我们的在设计和使用类时最常用的便是非静态成员函数,使用成员函数是为了封装和隐藏我们的数据,我想这是成员函数和外部函数的最明显的区别。但是他们的效率是否有不同呢?我们不会想为了保护我们的数据而使用成员函数,最后确导致效率降低的结。让我们看看非静态成员函数在实际的执行时被编译器搞成了什么样子。

float magnitude3d(const Point3d *_this){…}
//这是一个外部函数,它有参数。表示它间接的取得坐标(Point3d)成员。
float Point3d::mangnitude3d() const {…}
//这是一个成员函数,它直接取得坐标(Point3d)的成员。

表面上看,似乎成员函数的效率高很多,但实际上他们的效率真的想我们想象的那样吗?非也。实际上一个成员函数被内部转化成了外部函数。
1、 一个this指针被加入到成员函数的参数中,为的是能够使类的对象调用这个函数。
2、 将对所有非静态数据成员的存取操作改为由this来存取。
3、 对函数的名称进行重新的处理,使它成为程序中独一无二的。

这时后,经过以上的转换,成员函数已经成为了非成员函数。
float Point3d::mangnitude3d() const {…}//成员函数将被变成下面的样子
//伪码
mangnitude3d__7Point3dFv(register Point3d * const this)
{
return sqrt(this->_x * this->x+
this->_y * this->y+
this->_z * this->z);
}

调用此函数的操作也被转换
obj. mangnitude3d()
被转换成:
mangnitude3d__7Point3dFv(*obj);
怎么样看出来了吧,和我们开始声明的非成员函数没有区别了。因此得出结论:两个铁球同时落地。

一般来说,一个成员的名称前面会被加上类的名称,形成唯一的命名。实际上在对成员名称做处理时,除了加上了类名,还会将参数的链表一并加上,这样才能保证结果是独一无二的。

我们在来看看静态成员函数。我们有这样的概念,成员函数的调用必须是用类的对象,象这样obj.fun();或者这样ptr->fun().但实际上,只有一个或多个静态数据成员被成员函数存取时才需要类的对象。类的对象提供一个指针this,用来将用到的非静态数据成员绑定到类对象对应的成员上。如果没有用到任何一个成员数据,就不需要用到this指针,也就没有必要通过类的对象来调用一个成员函数。而且我们还知道静态数据成员是在类之外的,可以被视做全局变量的,只不过它只在一个类的生命范围内可见。(参考前面的笔记)。而且一般来说我们会将静态的数据成员声明为一个非Public。这样我们便必须提供一个或多个成员函数用来存取这个成员。虽然我们可以不依靠类的对象存取静态数据成员,但是这个可以用来存取静态成员的函数确实必须绑定在类的对象上的。为了更加好的解决这个问题,cfront2.0引入了静态成员函数的概念。

静态成员函数是没有this指针的。因为它不需要通过类的对象来调用。而且它不能直接存取类中的非静态成员。并且不能够被声明为virtual,const,volatile.如果取得一个静态成员函数的地址,那么我们获得的是这个函数在内存中的位置。(非静态成员函数的地址我们获得的是一个指向这个类成员函数的指针,函数指针)。可以看到由于静态成员函数没有this指针,和非成员函数非常的相似。

有了前面几章的基础,好象这些描述理解起来也不很费劲,而且我们的思路可以跟着书上所说的一路倾泻下来,这便是读书的乐趣所在了,如果一本书读起来都想读第一章时那样费劲,我想我读不下去的可能性会很高。

继续我们的学习,下面书上开始将虚函数了。我们知道虚函数是C++的一个很重要的特性,面向对象的多态便是由虚函数实现的。多态的概念是一个用一个public base class的指针(或者引用),寻址出一个派生类对象。虚函数实现的模型是这样。每一个类都有一个虚函数表,它包含类中有作用的虚函数的地址,当类产生对象时会有一个指针,指向虚函数表。为了支持虚函数的机制,便有了“执行期多态”的形式。

下面这样。
我们可以定义一个基类的指针。
Point *ptr;
然后在执行期使他寻址出我们需要的对象。可以是
ptr =new Point2d;
还可以是
ptr=new Pont3d;
ptr这个指针负责使程序在任何地方都可以采用一组由基类派生的类型。这种多态形式是消极的,因为它必须在编译时期完成。与之对应的是一种多态的积极形式,即在执行期完成用指针或引用查找我们的一个派生类的对象。
象下面这样:
ptr->z();
要想达到我们目的,这个函数z()应该是虚函数,并且还应该知道ptr所指的对象的真实类型,以便我们选择z()的实体。以及z()实体的位置,以便我们能够调用它。这些工作编译器都会为我们做好,编译器是如何做的呢?
我们已知每一个类会有一个虚函数表,这个表中含有对应类的对象的所有虚函数实体的地址,并且可能会改写一个基类的虚函数实体。如果没有改写基类存在的虚函数实体,则会继承基类的函数实体,这还没完,还会有一个pure_virtual_called()的函数实体。每一个虚函数不论是继承的还是改写的,都会被指派一个固定的索引值,这个索引在整个继承体系中保持与特定的虚函数关联。
说明:当没有改写基类的虚函数时,该函数的实体地址是被拷贝到派生类的虚函数表中的。

这样我们便实现了执行期的积极多态。这种形式的特点是,我们从头到尾都不知道ptr指针指向了那一个对象类型,基类?派生类1?派生类2?我们不知道,也不需要知道。我们只需要知道ptr指向的虚函数表。而且我们也不知道z()函数的实体会被调用,我们只知道z()函数的函数地址被放在虚函数表中的位置。

总结:在单一继承的体系中,虚函数机制是一种很有效率的机制。我们判断一个类是否支持多态,只需要看它有没有虚函数便可以了。

但是构造函数和析构函数和new和delete不同,他们并非必须成对的出现。决定是否为一个类写构造函数或者析构函数,是取决于这个类对象的生命在哪里结束(或开始)。需要什么操作才能保证对象的完整。

深度探索C++对象模型(8)

书的第四章后半部分详细的讲解内联函数,由于比较容易理解,我做一个简单总结便过去吧。

内联函数和其他的函数相比是一种效率很高的函数,未优化的情况下效率可以提高25%,优化以后简直是数量级的变化,书上的给出的数据是0.08比4.43。简直没法比了。内联函数对于封装提供了一种必要的支持,可以有效的存去类中的非共有数据成员,同时可以替代#define(前置处理宏)。但是它也有缺点,程序会随着调用内联函数次数的增多,而产生大量的扩展码。

在内联函数的扩展时每一个形式参数被对应的实参取代,因此会有副作用。通常需要引入临时对象解决多次对实际参数求值的操作产生的副作用。

第五章的开始给出了一个不恰当的抽象类的声明:
class Abstract_base
{
public:
virtual ~Abstract_base()=0;//纯虚析构函数
virtual void interface() const=0; //纯虚函数
virtual const char* mumble() const{return _mumble;}
protected:
char *_mumble;
};

这是一个不能产生实体的抽象类,因为它有纯虚函数。为什么说它存在不合适的地方呢?以下逐一进行说明。

1、 它没有一个明确的构造函数,因为没有构造函数来初始化数据成员则它的派生类无法决定数据成员的初值。类的成员数据应该在构造函数或成员函数中被指定初值,否则将破坏封装性质。

2、 每一个派生类的析构函数会被编译器进行扩展以静态调用方式调用其上层基类的析构,哪怕是纯虚函数。但是编译器并不能在链接时找到纯虚的析构函数,然后合成一个必要的函数实体,因此最好不要把虚的析构函数声明成纯虚的。

3、 除非必要,不要把所有的成员函数都声明为虚函数。这不是一个好设计观念。

4、 除非必要,不要使用const声明函数,因为很多派生的实体需要修改数据成员。

有了以上的观点上面的抽象类应该改为下面这种样子:
class Abstract_base
{
public:
virtual ~Absteact_base(); //不在是纯虚
virtual void interface()=0; //不在是const
const char * mumble() const{return _mumble;} //不在是虚函数
protected:
Abstract_base(char *pc=0); //增加了唯一参数的构造
Char *_mumble;
};

下一个问题,对象的构造。构造一个对象出来很简单,这是我们在编程时经常要做的事情。我理解书上的意思是为我们分析了各种不同的类,例如一个没有Copy constructor,Copy operator的类,或者有私有变量但是没有定义虚函数的类等等,当他们构造对象时也有多种情况,global,local,还有在new时,编译器都做了什么,内存的分配情况如何。搞清楚它们也很有意思。另外这好象是前面几章学到的东西的一个进一步的研究。我们找出最复杂的虚拟继承来进行一下研究。当一个类对象被构造时,实际上这个类的构造函数被调用,不论是我们自己写的,还是由编译器为我们合成的。并且编译器会背着我们做很多的扩充工作,将记录在成员初始化列表中的数据成员的初始化工作放进构造函数,如果一个数据成员没有在成员初始化列表中出现,则会调用默认的构造函数,这个类的所有基类的构造都会被调用,以基类的声明顺序。所有的虚拟基类的构造也会被调用。还要为virtual table pointers设定初始值,指向适当的virtual tables。好家伙,编译器还真累。好象说的不是很清楚,抄一段书上的代码。

已知一个类的层次结构和派生关系如下图:

见书上P211。
这是程序员给出的PVertex的构造函数:
PVertex::PVertex(float x,float y,float z):_next(0),Vertex3d(x,y,z),Point(x,y)
{
if(spyOn)
cerr<<”within PVertex::PVertex()”<<”size:”<<size()<<endl;
}

它可能被扩展成为:
//C++伪码
// PVertex构造函数的扩展结果
PVertex *
PVertex::PVertex(PVertex * this,bool most_derived,float x,float y,float z)
{
//条件式的调用虚基类的构造函数
if(_most_derived!=false)
this->Point::Point(x,y);
//无条件的调用上层基类的构造函数
this->Vertex3d::Vertex3d(x,y,z);

//将相关的vptr初始化
this->_vptr_PVerex=_vtbl_PVertex;
this->_vptr_Point_PVertex=_vtbl_Point_PVertex;

//原来构造函数中的代码
if(spyOn)
cerr<<”within PVertex::PVertex()”<<”size:”
//经虚拟机制调用
<<(*this->_vptr_PVertex[3].faddr )(this)<<endl;
//返回被构造的对象
return this;
}

通过上面的代码我们可以比较清晰的了解在有多重继承+虚拟继承的时候构造一个对象时,编译会将构造函数扩充成一个什么样子。以及扩充的顺序。知道了这个相对于无继承,或者不是虚拟继承时对象的构造应该也可以理解了。与构造对象相对应的是析构。但是构造函数和析构函数和new和delete不同,他们并非必须成对的出现。决定是否为一个类写构造函数或者析构函数,是取决于这个类对象的生命在哪里结束(或开始)。需要什么操作才能保证对象的完整。象构造函数一样析构函数的最佳实现策略是维护两份destructor实体。一个complete object实体,总是设定好vptrs,并调用虚拟基类的析构函数。一个base class subobject实体。除非在析构函数中调用一个虚函数,否则绝不会调用虚拟基类的析构函数,并设定vptrs。

一个对象生命结束于析构函数开始执行的时候。它的扩展形式和构造函数的扩展顺序相反。

当编译一个C++程序时,计算机的内存被分成了4个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(heap),我们称堆是自由的内存区域,我们可以通过new和delete把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦new了它,必须delete它,否则程序将崩溃,这便是内存泄漏。(C#已经通过内存托管解决了这一令人头疼的问题)。C++通过new来分配内存,new的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于new的知识,下面看看通过这本书,使我们能够更进一步的了解到些什么。

深度探索C++对象模型(9)

这一章主要是说Runtime Semantics执行期语义学。

这是我们平时写的程序片段:
Matrix identity; //一个全局对象
Main()
{
Matrix m1=identity;
……
return 0;
}

很常见的一个代码片段,雷神从来没有考虑过identity如何被构造,或者如何被销毁。因为它肯定在Matrix m1=identity之前就被构造出来了,并且在main函数结束前被销毁了。我们不用考虑这些问题,好象C++就应该这样。但这本书是研究C++底层机制的。既然我们在看这本书,说明我们希望了解C++的编译器又做了那些大量的工作,使得我们可以这样使用对象。

在C++程序中所有的全局对象都被放在data segment中,如果明确赋值,则对象以该值为初值,否则所配置到内存内容为0。也就是说,如果我们有以下定义
Int v1=1024;
Int v2;
则v1和v2都被配置于data segment,v1值为1024,v2值为0。(雷神在VC6环境用MFC编程时中发现如果int v2;v2的值不为0,而是-8,不知为什么?编译器造成的?)。

如果有一个全局对象,并且这个对象有构造函数和析构函数的话,它需要静态的初始化操作和内存释放工作,C++是一种跨平台的编程语言,因此它的编译器需要一种可以移植的静态初始化和内存释放的方法。下面便是它的策略。

1、 为每一个需要静态初始化的档案产生一个_sit()函数,内带构造函数或内联的扩展。

2、 为每一个需要静态的内存释放操作的文件中,产生一个_std()函数,内带析构函数或内联的扩展。

3、 提供一个_main()函数,用来调用所有的_sti()函数,还有一个exit()函数调用所有的_std()函数。

侯先生说:

Sit可以理解成static initialization的缩写。

Std可以理解成static deallocation的缩写。

那么main函数会被编译器变成这样:

Matrix identity; //一个全局对象
Main()
{
_main();//对所有的全局对象做static initialization动作。
Matrix m1=identity;
……
exit();//对所有的全局对象做static deallocation动作。
}
其中_main()会有一个对identity对象的静态初始化的_sti函数,象下面伪码这样:
// matrix_c是文件名编码_identity表示静态对象,这样能够保证向执行文件提供唯一的识别符号
_sti__matrix_c_identity()
{
identity.Matrix:: Matrix(); //这就是静态初始化
}

相应的在exit()函数也会有一个_std_matrix_c_identity(),来进行static deallocation动作。

但是被静态初始化的对象有一些缺点,在使用异常时,对象不能被放置在try区段内。还有对象的相依顺序引出的复杂度,因此不建议使用需要静态初始化的全局对象。

局部静态对象在C++底层机制是如何构造和在内存中销毁的呢?

1、 导入一个临时对象用来保护局部静态对象的初始化操作。

2、 第一次处理时,临时对象为false,于是构造函数被调用,然后临时对象被改为true.

3、 临时对象的true或者false便成为了判断对象是否被构造的标准。

4、 根据判断的结果决定对象的析构函数是否执行。

如果一个类定义了构造函数或者析构函数,则当你定义了一个对象数组时,编译器会通过运行库将你的定义进行加工,例如:
point knots[10]; //我们的定义
vec_new(&knots,sizeof(point),10,&point::point,0); //编译器调用vec_new()操作。

下面给出vec_new()原型,不同的编译器会有差别。

void * vec_new(
void *array, //数组的起始地址
size_t elem_size, //每个对象的大小
int elem_count, //数组元素个数
void(*constructor)(void*),
void(*destructor)(void* ,char)
)
对于明显获得初值的元素,vec_new()不再有必要,例如:
point knots[10]={
Point(), //knots[0]
Point(1.0,1.0,0.5), //knots[1]
-1.0 //knots[2]
};
会被编译器转换成:
//C++伪码
Point::Point(&knots[0]);
Point::Point(&knots[1],1.0,1.0,0.5);
Point::Point(&knots[2],-1.0,0.0,0.0);
vec_new(&knots,sizeof(point),10,&point::point,0); //剩下的元素,编译器调用vec_new()操作。
怎么样,很神奇吧。

当编译一个C++程序时,计算机的内存被分成了4个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(heap),我们称堆是自由的内存区域,我们可以通过new和delete把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦new了它,必须delete它,否则程序将崩溃,这便是内存泄漏。(C#已经通过内存托管解决了这一令人头疼的问题)。C++通过new来分配内存,new的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于new的知识,下面看看通过这本书,使我们能够更进一步的了解到些什么。
Point3d *origin=new Point3d; //我们new 了一个Point3d对象
编译器开始工作,上面的一行代码被转换成为下面的伪码:
Point3d * origin;
If(origin=_new(sizeof(Point3d)))
{
try{
origin=Point3d::Point3d(origin);
}
catch(…){
_delete(origin);
throw;
}
}
而delete origin;
会被转换成(我将书上的代码改为exception handling情况):
if(origin!=0){
try{
Point3d::~Point3d(origin);
_delete(origin);
catch(…){
_delete(origin); //不知对否?
throw;
}
}

一般来说对于new的操作都直截了当,但语言要求每一次对new的调用都必须传回一个唯一的指针,解决这个问题的办法是,传回一个指针指向一个默认为size=1的内存区块,实际上是以标准的C的malloc()来完成。同样delete也是由标准C的free()来完成。原来如此。

最后这篇笔记再说说临时对象的问题。
T operator+(const T&,const T&); //如果我们有一个函数
T a,b,c; //以及三个对象:
c=a+b;
//可能会导致临时对象产生。用来放置a+b的返回值。然后再由 T的copy constructor把临时对象当作c的初值。也有可能直接由拷贝构造将a+b的值放到c中,这时便不需要临时对象。另外还有一种可能通过操作符的重载定义,经named return value优化也可以获得c对象。这三种方法结果一样,区别在于初始化的成本。对临时对象书上有很好的总结:
在某些环境下,有processor产生的临时对象是有必要的,也是比较方便的,这样的临时对象由编译器决定。
临时对象的销毁应该是对完整表达式求值过程的最后一个步骤。
因为临时对象是根据执行期语义有条件的产生,因此它的生命规则就显得很复杂。C++标准要求凡含有表达式执行结果的临时对象,应该保留到对象的初始化操作完成为止。当然这样也会有例外,当一个临时对象被一个引用绑定时,对象将残留,直到被初始化的引用的生命结束,或者超出临时对象的作用域。

- 作者: cyberfan 2006年03月7日, 星期二 20:33  回复(1) |  引用(0) 加入博采

深度探索C++对象模型(一)  (作者置顶)

《深入C++对象模型》一书没有什么可以被成为重点的东西,感觉每一个字都不应该放过,全是重点。

深度探索C++对象模型(1)

第一章:关于对象(Object Lessons)读完这一章使我想到了一个很久以前看到的一个笑话,编写一个HELLO WORLD的程序,随着水平和职务的不一样,程序代码也随着变化。当初看时完全当作笑话来看,现在看来写此笑话的人水平不一般。如果要使你的代码能够最大限度的适应不同的运行环境,和最大限度的复用,则在设计和编写的过程中需要考虑的问题很多,因此代码已变的不在具有C语言的简洁,高效。而牺牲了这些优势换来的是更好的封装。当然如果你只是要打印Hello World则不必这样做了。

以C++的思维方式解决问题,对于对C语言已经很熟悉的人来说会很不能适应。需要一段时间来适应,不然会将代码写的似是而非。而且不能邯郸学步,必须从思想上彻底的C++(OO),如果只是依葫芦画瓢,那结果很可能是用C++的语法编写C式的程序。本人曾经犯的典型的低级的错误之一,就是无意识的一个类无限制的扩充,完全没有考虑到类的多层结构(基类-派生类),需要属性或方法便在类中增加,虽然也用到了多态、重载等一些OO的设计方式,但最后这个类庞大无比,除了在当前系统中任劳任怨的工作外,一点复用的可能都没有,如果另一个系统还需要一个类似的东西,那只能重新设计实现一个新的类。并且最致命的是在维护更新时带来得麻烦,需要不断全部编译不说,而且代码在用了大量注释后,在过一段时间读起来也是一件重脑力劳动。及失去了C的简洁清晰和高效,也不完全具备C++的面向对象的特性。这根本不能叫C++程序。(我想有时间重写一下以前代码也会有很多收获,温故而知新吗)C和C++在编程思想上是相互矛盾的。这也就是说如果你想学C++,完全可以不学C,只需要一本好书和一个不太笨的大脑再加上努力就可以了,如果你已有C的经验在一定的情况下反而会捣乱。

本章是对对象模型的一个大略浏览。既然我们选择了C++而不是C作为开发工具,那我们的编程思想也应该转为C++的,而不能再延续C的Procedural方式。我们必须学会C++的思考方式。采用抽象数据类型或用一个多层的class体系对数据以及数据处理函数进行封装,只有摆脱C程序的使用全局数据的惯性,才能充分发挥出C++对象模型的强大威力。

在C++中有两种数据成员static和nonstatic,以及三种成员函数static、nonstatic和virtual。C++对象模型对内存空间和存取时间做了优化,nonstatic的数据成员被置于类对象之内,而static数据成员被置于类对象之外。static和nonstatic成员函数被放在类对象之外。而virtual函数是由类对象的一个指向vtbl(虚函数表)的指针vptr来进行支持。而vptr的设定和重置由类的构造函数、析构函数以及copy assignment运算符自动完成。

我们设计的每一个类几乎都要有一个或多个构造函数、析构函数和一个Assignment运算符。他们的作用是构造函数产生一个新的对象并确定它被初始化。析构函数销毁一个对象并确定它已经被适当的清理(避免出现内存泄露的问题),Assignment运算符给对象一个新值。

这是第一章的第一部分,由于雷神最近几天在做模式小组的主页,时间周转不开了。本想写完整个一章再发,考虑一下还是先发一部分吧。原因有2。1、第一章的后半部可能又要拖上10天半个月的。2、笔记实在难写,我不愿意将笔记做成将书上的重点再抄一边,而是喜欢尽量将自己的理解描述出来,谁知第一章便如此的难以消化,已经反复读了3遍,还是有些夹生。所以本着对大家和自己负责的态度,雷神准备再看它3遍在说。突然发现自己的C++还差的很远,好可怕呀。

深度探索C++对象模型(2)

笔记贴出后,有朋友便给我提出了一个很好的建议,原文如下:
史列因:我刚看了你写的“深度探索C++对象模型(1)”,感觉很不错。不过我有一个建议:你说“谁知第一章便如此的难以消化,已经反复读了3遍,还是有些夹生”是很自然的。第一章是一个总览,如果你能全看懂,后面的就没什么看的必要了。第一章的内容后面都有详细介绍,开始只要有个大概印象就可以了。这本书中很多内容都是前后重复的。我建议你先不管看懂看不懂,只管向后看,之后再从头看几遍,那样效果好得多。
我想史列因说的应该是一种非常好的阅读方式,类似《深度探索C++对象模型》这样的技术书籍,需要的是理解,和学习英文不同,不能靠死记硬背,如果出现理解不了的情况,那你不妨将书放下,打一盘红警(俺骄傲的说,我是高手)。或者跳过去也是一个不错的方法。好了,我们还是继续研究C++的对象模型吧。

简单的对象模型

看书上的例子(注释是表示solt的索引)
Class Point
{
public:
Point(float xval); //1
virtual ~Point(); //2

float x() const; //3
static int PointCount(); //4
protected:
virtual ostream& print(ostream &os) const; //5
float _x; //6
static int _point_count; //7
}
每一个Object是一系列的Slots,每一个Slots指向一个members。

表格驱动对象模型

当构造对象时便会有一个类似指针数组的东西存放着类数据成员在内存中位置的指针,还有指向成员函数的指针。为了对一个类产生的所有对象实体有一个标准的表达,所以对象模型采用了表格,把所有的数据成员放在数据成员表中,把所有的成员函数的地址放在了成员函数表中,而类对象本身有指向这两个表的指针。

为了便于理解,雷神来举个不恰当的例子说明一下,注意是不很恰当的例子

我们把写字楼看成一个类,写字楼中的人看成是类的数据成员,而每一个租用写字楼的公司看成类的成员函数。我们来看一个实体,我们叫它雷神大厦。雷神大厦的物业管理部门需要登记每个出入写字楼的人,以便发通行证,并且需要登记每个公司的房间号,并制作了一个牌子在大厅的墙上。实际上这便是类的对象构造过程。你可以通过大厅墙上的公司列表找到任何一家在雷神大厦租房的公司,也可以通过物业提供的花名册找到任何一个出入雷神大厦的人。

C++的对象模型

C++对象模型是从简单对象模型派生得来,并对内存空间和存取时间做了优化。它引入了虚函数表(virtual table)的方案。每个类产生一堆指向虚函数的指针,放在表格中。每个类的对象被添加了一个指针(vptr),指向相关的虚函数表(virtual table)。而这个指针是由每一个类的constructor、destructor和copy assignment运算符自动完成。

我们还用上面的雷神大厦举例,物业管理为了提高效率,对长期稳定的公司和人员不再登记,指对不稳定或不能确定的公司进行登记,以便于管理。

得出结论,C++对象模型和双表格对象模型相比,提高了空间和存储时间的效率,却失去了弹性。

试想一下,没有整个雷神大厦人员和公司的名录,如果他们发生变化,则需要物业管理部门做很多工作。重新确定长期稳定的公司和人员是那些。对应应用程序则需要重新编译。(这次更离谱,但为了保持连贯,大家请进行理解性的思考,不要局限字面的意思)

这篇笔记是分成多次一点点写的,甚至每天抽出一个小时都不能保证(没办法最近实在忙),因此可能会有不连贯,如果你读起来很不爽认为雷神的思维短路了,那属于正常。不过雷神还是再上传之前努力的将思路进行了一下整理。希望能把这些支言片语串起来。

深度探索C++对象模型(3)

多态是一种威力强大的设计机制,允许你继承一个抽象的public接口之后,封装相关的类型,需要付出的代价就是额外的间接性--不论是在内存的获得,或是在类的决断上,C++通过class的pointer和references来支持多态,这种程序风格就称为"面向对象". 这篇笔记主要解决了几个常常被人问到的问题。
1、C++支持多重继承吗?
2、结构和类的区别是什么?
3、如何设计一个面向对象的模型?

C++支持多重继承(JAVA和C#不支持多重继承),虽然我想我可能一辈子用不到它这一特性(C++是雷神的业余爱好),但至少我要知道它可以。典型的多重继承是下面这个:
//iostream 从istream 和 ostream 两个类继承。
class iostream:public istream,public ostream
{......};

结构struct和类class到底有没有区别?VCHELP上前几天还看到一个帖子在讨论这个问题。其实结构和类真的没什么区别,不过我们需要掌握的是什么时候用结构好,什么时候用类好,当然这没有严格的规定。通常我们混合使用它们,从书上的例子,我们可以看出为什么还需要保留结构,并且书上给出了一个方法:
struct C_point{.......}; //这是一个结构
class Point
{
public:
operator C_point(){return _c_point;}
//....
private:
C_point _c_point;
//....
}
这种方法被成为组合(composition).它将一个对象模型的全部或部分用结构封装起来,这样做的好处是你既可以在C++中应用这个对象模型,也可以在C中应用它。因为struct封装了lass的数据,使C++和C都能有合适的空间布局。

面向对象模型是有一些彼此相关的类型,通过一个抽象的base class(用来提供接口),被封装起来。真正的子类都是通过它派生的。当然一个设计优秀的对象模型还必须考虑很多的细节问题,雷神根据自己的理解写出一个面向对象模型的代码,大家可以看看,高手请给指出有没有问题。

思路:我想要实现一个人员管理管理的对象模型,雷神一直在思考一个人员管理的组件(当然最终它会用C#实现的一个业务逻辑对象,并通过数据库控制对象和数据库进行交互,通过WEB FORM来显示界面)。这里借用一下自己的已经有的的想法,用C++先进行一下实验,由于只是为了体会面向对象的概念,我们采用面向对象的方法实现一个链表程序,而且没有收集信息的接口。信息从mina()函数显式给出。
这个对象模型应该可以实现对人员的一般性管理,要求具备以下功能:
创建一个人员信息链表
添加、删除人员信息
显示人员信息

//*************************************************
//PersonnelManage.cpp
//创建人:雷神
//日期:
//版本:
//描述:
//*************************************************

#include <iostream.h>
#include <string.h>
//基类,是此对象模型的最上层父类
class Personnel
{
friend class point_list; //用来实现输出链表,以及插入或删除人员的功能.
protected:
char serial_number[15];//编号
char name[10];//名称
char password[15]//口令
Personnel *pointer;
Personnel *next_link;
public:
Personnel(char *sn,char *nm,char *pwd)
{
strcpy(serial_number,sn);
strcpy(name,sm);
strcpy(password,pwd);
next_link=0;
}
Personnel()
{
serial_number[0]=NULL;
name[0]=NULL;
password[0]=NULL;
next_link=0;
}
void fill_serial_number(char *p_n)
{
strcpy(serial_number,p_n);
}
void fill_name(char *p_nm)
{
strcpy(name,p_nm);
}
void fill_password(char *p_pwd)
{
strcpy(password,p_pwd);
}

virtual void addnew(){}
virtual void display()
{
cout<<"\n编号:"<<serial_number<<"\n";
cout<<"名字:"<<name<<"\n";
cout<<"口令:"<<password<<"\n"
}
};
//下面是派生的子类,为了简单些我在把子类进行了成员简化。
//思路:由父类派生出成员子类,正式成员要求更详细的个人资料,这里省略了大部份.
//并且正式成员可以有一些系统的操作权限,这里省略了大部份。
//正式成员子类
class Member:public Personnel
{
friend class point_list;
private:
char member_email[50];
char member_gender[10];
double member_age;
public:
Member(char *sn,char *nm,char *pwd,char *em,char *gd,double ag):Personnel(sn,nm,pwd)
{
strcpy(member_email,em);
strcpy(member_gender,gd);
member_age=age;
}
Member():Personnel()
{
member_email[0]=NULL;
member_gender=NULL;
member_age=0.0;
}
void fill_email(char *p_em)
{
strcpy(member_email,p_em);
}
void fill_gender(char *p_gd)
{
strcpy(member_gender,p_gd);
}
void fill_age(double ages)
{
member_age=ages;
}

void addnew()
{
pointer=this;
}
void display()
{
Personnel::display()
cout<<"电子邮件:"<<member_email<<"\n";
cout<<"性别:"<<member_gender<<"\n";
cout<<"年龄"<<member_age<<"\n";
}
};

//好了,我们还需要实现一个超级成员子类和一个项目经理的子类.
//这是超级成员类
class Supermember:public Member
{
friend class point_list;
private:
int sm_documentcount;//提交的文档数
int sm_codecount;//提交的代码段数
public:
Supermember(char *sn,char *nm,char *pwd,char *em,char *gd,double ag,int dc,int cc):Member(sn,nm,pwd,gd,ag)
{
sm_documnetcount=0;
sm_codecount=0;
}
Spupermember():Member()
{
sm_documentcount=0;
sm_codecount=0;
}
void fill_documentcount(int smdc)
{
sm_documentcount=smdc;
}
void fill_codecount(int smcc)
{
sm_codecount=smcc;
}

void addnew()
{
pointer=this;
}
void display()
{
Member::display()
cout<<"提交文章数:"<<sm_documentcount<<"\n";
cout<<"提交代码段数"<<sm_codecount<<"\n";
}
};

//实现友元类
class point_list
{
private:
Personnel *location;
public:
point_list()
{
location=0;
}
void print();
void insert(Personnel *node);
void delete(char *serial_number);
}
//显示链表
void point_list::print()
{
Personnel *ps=location;
while(ps!=0)
{
ps->display();
ps=ps->next_link;
}
}
//插入链表
void point_list::insert(Personnel *node)
{
Personnel *current_node=location;
Personnel *previous_node=0;
while(current_node!=0 && (strcmp(current_node->name,node->name<0)
{
previous_node=current_node;
current_node=current_node->next_link;
}
node->addnew()
node->pointer->next_link=current_node;
if(previous_node==0)
location=node->pointer;
else
previous_node->next_link=node->pointer;
}

//从链表中删除
void point_list::delete(char *serial_number)
{
Personnel *current_node=location;
Personnel *previous_node=0;
while(current_node!=0 && strcmp(current_node->serial_number,serial_number)!=0)
{
previous_node=current_node;
current_node=current_node->next_link;
}
if(current_node !=0 && previous_node==0)
{
location=current_node->next_link;
}
else if(current_node !=0 && previous_node!=0)
{
previous_node->next_link=current_node->next_link;
}
}

//这是主函数,我们显式的增加3个Supermember信息,然后在通过编号删除一个
//我们没有从成员再派生出管理成员,所以没有办法演示它,但我们可以看出要实现它并不难
//注意:此程序没有经过验证,也许会有BUG.
main()
{
point_list pl;
Supermember sm1("000000000000001","雷神","123456","lsmodel@ai361.com","男",29.9,10,10);
Supermember sm1("000000000000002","木一","234567","MY@ai361.com","男",26.5,20,5);
Supermember sm1("000000000000003","落叶夏日","345678","LYXR@ai361.com","男",24.8,5,15);
//如果我们还派生了管理人员,可能的方式如下:
//Managemember mm1("000000000000004","ADMIN","888888","webmaster@ai361.com","男",30,5,15,......);

//下面是将上面的3个人员信息加到链表中
pl.insert(&sm1);
pl.insert(&sm2);
pl.insert(&sm3);
//对应管理人员的 pl.insert(&mm1);

//下面是显示他们
//下面是显示人员列表
pl.print();

//下面是删除一个人员信息
pl.delete("000000000000001");
//我们再显示一次看看.
cout<<"\n删除后的列表:\n";
pl.print();
}

程序没有上机验证,在我的脑子里运行了一下,我想输出结果应该是这样的:

编号:000000000001
名称:雷神
口令:123456
电子邮件:lsmodel@ai361.com
性别:男
年龄:29.9
提交文章数:10
提交代码数:10

编号:000000000002
名称:木一
口令:234567
电子邮件:MY@21CN.com
性别:男
年龄:26.5
提交文章数:20
提交代码数:5

编号:000000000003
名称:落叶夏日
口令:345678
电子邮件:LYXR@163.com
性别:男
年龄:24.8
提交文章数:5
提交代码数:15

删除后的列表:

编号:000000000002
名称:木一
口令:234567
电子邮件:MY@21CN.com
性别:男
年龄:26.5
提交文章数:20
提交代码数:5

编号:000000000003
名称:落叶夏日
口令:345678
电子邮件:LYXR@163.com
性别:男
年龄:24.8
提交文章数:5
提交代码数:15

****************************************************************************************

通过上面的例子,我想我们能够理解对象模型的给我们带来的好处,我们用了大量的指针和引用,来完成多态的特性.和书上的资料库的例子不同,我们多了一层,那是因为我考虑人员可能是匿名,也可能是注册的,所以为了区别他们,用了两层来完成接口,然后所有注册的正式成员才都由Member类派生出不同的权限的人员,例如超级成员和管理人员.

最后用书上的一段话总结一下吧.P34
总而言之,多态是一种威力强大的设计机制,允许你继承一个抽象的public接口之后,封装相关的类型,需要付出的代价就是额外的间接性--不论是在内存的获得,或是在类的决断上,C++通过class的pointer和references来支持多态,这种程序风格就称为"面向对象".

深度探索C++对象模型(4)

读完了《深度探索C++对象模型》的第一章,虽然还是有些疑惑,但是已经感到收获很大。按照朋友的说法,第一章是一个概括的介绍,具体的细节会在以后的章节阐述,如果没有通读本书,第一章还是比较不容易理解的。

第二章主要讲的的构造函数语意(Semantics),这是一个什么意思?我的英文和中文学的都不好,但我想是书上弄错了(也许只是一个笔误),也许应该翻译成语义比较恰当。The study or science of meaning in anguage forms. 语义学以语言形式表示意思的研究或科学。我们要研究构造函数的,并且以语言的形式将它描述清楚。

看完题目我的第一个感觉,构造函数我知道。构造函数是一个类的成员函数,构造函数和析构函数是进行对象数据的创建,初始化,清除工作的成员函数,可以重载构造函数,使一个类不止具备一个构造函数,因有时需要以这些方法中的某一种分别创建不同的对象。不能重载析构函数。构造函数作为成员函数和类有相同的名字。例:一个类名为:aClass,构造函数就是aClass()。构造函数没有返回值,而且不能定义其返回类型,void也不行。析构函数同样使用这一点。当编写重载函数时,只有参数表不同,通过比较其参数个数或参数类型可以区分两个重载函数。但是我读完第一小段后就知道这一章要告诉我们什么了。

这一章并不是要告诉我们什么是构造函数,它的作用是什么。而是要告诉我们的是构造函数是如何工作的。我的。在得知这点后我很兴奋,因为我确实不知道构造函数是如何构造一个类的对象的,并且一直想知道。我一直对面向对象神奇的功能很感兴趣。为什么一个类在被实例化时,可以自动的完成很多工作,使我们的主函数清晰,简单,稳健,高效。以前只看到了表面,没有深入,这会我们有机会去皮剔肉深入骨髓了。

书上主要讨论了几种情况:

带有缺省构造函数的成员对象。如果一个类没有任何的构造函数,但他有一个成员对象,这个对象的类有一个缺省的构造函数,那么编译器会在需要的时候为这个类合成一个构造函数。
举个例子:
我们有以下几个类。它们都有一个构造函数。
猫{public:猫(),......};
狗{public:狗(),......};
鸟{public:鸟(),......};
鱼{public:鱼(),......};
我们又有一个类。宠物,我们将猫作为它的成员之一。并且没有给它声明构造函数。
宠物{
public:
猫 一只猫;
狗 一只狗;
鸟 一只鸟;
鱼 一只鱼;
private:
int ival;
......
}
则当需要的时候编译器会为它合成一个构造函数,并且采用内联方式。大概象下面的样子。
inline
宠物::宠物()
{
猫.猫::猫();
狗.狗::狗();
鸟.鸟::鸟();
鱼.鱼::鱼();
ival=0;
}
为什么会这样,我们来看看编译器的行动。编译器开始执行用户的代码,准备生成宠物对象之前,会首先调用必要的构造函数,来初始化类的成员,以便为对象分配合适的内存空间。结果编译器会合成上面的构造函数,如果程序员为宠物类写了一个构造函数。 宠物::宠物(){ival=0;}那编译器也会将这个构造函数扩张成上面的那样。编译器是怎样实现的呢?原来当一个类没有任何用户定义的构造函数,而是由编译器自动生成的话,则这个被暗中生成的构造函数将会是一个没有什么用处的构造函数。但是通过编译器的工作能够为我们合成一个nontrivial default constructor.

好象香港电影中演的,如果你惹上官司(你要设计一个类),你又没有钱去请高级的律师(没有给出构造函数),那会给你分配一个律师(缺省的构造函数),当然这个律师的能力也许和那些大律师比起来有差距(trivial)。不过我们要知道他们也不是一点用都没有。但是由于有律师行的督导,可以使这些律师能够努力做到最好(nontrivial)。

同样的道理,我们可以理解另外的几种nontrivial default constructor的情况。

如果你的类没有任何的构造函数,并且它派生于一个有着缺省构造函数的基类,那这个派生类的缺省构造函数会被视为nontrivial,因此需要被合成出来,他的合成步骤是调用上一层基类的缺省构造函数,并根据它们的声明次序为派生类合成一个构造函数。

如果类声明或继承了一个虚函数,或者类派生于一个继承串链,其中有一个或更多的虚拟基类。由于缺少使用者声明的构造函数,则编译器会合成一个缺省的构造函数,以便正确的初始化每一个类对象的vptr。

最后说一点,在合成的缺省构造函数中,只有基类的子对象和类的成员对象会被初始化,所有其他的非静态数据成员都不会被初始化,因为这些操作是需要程序员来做的。编译器没有必要连这些工作都做了。

好了,这篇就写到这里吧。这本书真的是雷神所看过的书中,看的最慢的一本了。但这些深层的知识有必要了解的很清楚吗,我们不知道编译器如何合成缺省的构造函数不也能写程序吗?雷神用侯大师的话来回答这个问题:练从难处练,用从易处用。知其然而不知其所以然,不是一个严谨的学习态度。

深度探索C++对象模型(5)

上一篇我们对合成确省的构造函数做了一个了解,这一篇我们继续看看构造函数这个有趣的东西.Copy Constructor是什么?我们经常看到代码中有一些这样的函数调用方式X(X&) (“X of X ref”). 这个函数用用户自定义类型作为参数,那它的参数的构造便是由Copy Constructor负责的. 可见这个玩意非常重要,实际上Copy Constructor是由编译器自动合成的,不需要你去作任何事情,但编译器都做了些什么呢?我们的问题出来了.

我们有三种情况需要用一个对象的内容作为另一个类对象的初值.也就是需要编译器来为我们自动合成Copy Constructor.一种是我们在编程中肯定回用到的由类生成对象例如以下形式:
class ClassA{......}
ClassA a;
ClassA b=a; //一个Class对象以另一个对象做初值
另外的一种情况是以对象为参数在函数中传递看下面的伪码:
//例如我们有一个CUser类
CUser{
CUser();
......
};
//我们还有一个CDatabase类,它有一个AddNew的方法
CDatabase{
......
public:
AddNew(CUser userone);
......}
//我们用CUser类产生了一个对象实例.userone,并将他作为AddNew函数的参数,以便
//AddNew函数能够完成在数据库中增加一条记录,用来记录一个用户的信息
CDatabase db=new CDatabase();
db.AddNew(CUser userone) //在这里,你不用将你的用户类的成员全部展开.
还有一种当然是用做函数的return,例如你可以在CDatabase类中添加一个函数用来读取一个用户的信息例如这样CUser GetUserOne(int userID),通过一个用户的唯一的编号可以获得一个用户的信息,并返回一个CUser类的对象.

我们来看看Copy Constructor是如何工作的.首先Copy Constructor和Default Constructor一样都是在需要的时候由编译器产生出来,一个类如果没有声明一个Copy Constructor就会存在一个隐含的声明(或定义).它也被分为trivial和nontrivial两种.

我们来看书上的例子:
Class Word
{
public:
Word(const char*);
~Word(){delete [] str;}
private:
int cnt;
Char *str;
}
这个类的声明不需要合成出Default Copy Constructor.但当进行如下应用时:
#include "Word.h"
Word noun("lsmodel");
void foo()
{
Word verb=noun;
}
结果将会出现灾难性的后果.为什么?因为我们的逻辑对象verb和全局对象noun都指向了相同的字符串,在退出函数foo()之前verb会执行析构,则字符串被删除,从此全局对象nonu指向了一堆无意义的东西.你可以声明一个explicit copy constructor来解决这个问题,当然还可以让编译器来自动的给你合成一个Copy construct.
我们将上面的Word类改写成下面的样子:
Class Word
{
public:
Word(const String&);//注意这里和我们开始的X(X&)形式一样
~Word();
//......
private:
int cnt;
String str; // 这个成员是String类的对象,String是我们自定义的类型
};
Class String
{
public:
String(const char*);
String(const String&);//这里声明了一个Copy constructir
~String();
//......
}
这时在执行我们的代码
#include "Word.h"
Word noun("lsmodel");
void foo()
{
Word verb=noun;
}
编译器会为我们的Word类合成一个Copy Constructor,用来调用它的str(member class String object)的Copy Constructor.象下面伪码表示的这样:
inline Word::Word(const Word &wd)
{
str.String::String(wd.str);
cnt=wd.cnt;
}
当这个类中有一个或多个虚函数时,或者这个类是派生于一个继承串链,并且这个串中有一个或多个虚拟的基类时.这个类在进行拷贝时便不会展现逐次拷贝(bitwise copy).并且会通过合成的Copy Constructor来重新明确的设定vptr来指向虚函数表,而不是将右边对象的vprt直接拷贝过来.书上的ZooAnimal例子的可以很清晰的描述出这点.

如果一个对象以另一个对象做初值,而后者有一个Virtual Base Class Subobject,那会怎样呢?任何一个编译器都会做到在派生类对象中的virtual base class Subobject的位置在执行期就准备妥当,但bitwise copy可能会破坏这一位置,因此也需要由编译器合成出一个copy constructor,来安插一些代码来设定virtual base class pointer/offset,对每一个成员执行必要的memberwise初始化操作,以及执行内存相关的工作.

我们这篇学习的内容是:当一个对象以另一个对象作为初始值时,会发生什么事情.分成了两种情况,一种是我们声明了explicit copy constructor,这个不是这篇文章需要搞明白的(我想大家也都很明白了).我们想知道的是我们没有为class声明explicit copy constructor函数时编译器都干了些什么.编译器会为我们合成一个copy constructor.以便适应任何时候的对象被正确的初始化.并且我们了解了有以下四种情况class不在按位逐一进行拷贝.
1.当你设计的类声明了一个explicit copy constructor函数时.
2.当你设计的类是由一个具有explicit copy constructor的基类派生的时.
3.当你设计的类声明了一个或多个虚函数时.
4.当你设计的类派生自一个继承串链,这个继承串链中有一个或多个virtual base classes时.

- 作者: cyberfan 2006年03月7日, 星期二 20:31  回复(0) |  引用(0) 加入博采

C++风格与技巧  (作者置顶)

目录:

我如何写这个非常简单的程序?

为什么编译要花这么长的时间?

为什么一个空类的大小不为0?

我必须在类声明处赋予数据吗?

为什么成员函数默认不是virtual的?

为什么析构函数默认不是virtual的?

为什么不能有虚拟构造函数?

为什么重载在继承类中不工作?

我能够在构造函数中调用一个虚拟函数吗?

有没有“指定位置删除”(placement delete)?

我能防止别人继承我自己的类吗?

为什么不能为模板参数定义约束(constraints)?

既然已经有了优秀的qsort()函数,为什么还需要一个sort()?

什么是函数对象(function object)?

我应该如何对付内存泄漏?

我为什么在捕获一个异常之后就不能继续?

为什么C++中没有相当于realloc()的函数?

如何使用异常?

怎样从输入中读取一个字符串?

为什么C++不提供“finally”的构造?

什么是自动指针(auto_ptr),为什么没有自动数组(auto_array)?

可以混合使用C风格与C++风格的内存分派与重新分配吗?

我为什么必须使用一个造型来转换*void?

我如何定义一个类内部(in-class)的常量?

为什么delete不会将操作数置0?

我能够写“void main()”吗?

为什么我不能重载点符号,::,sizeof,等等?

怎样将一个整型值转换为一个字符串?

“int* p”正确还是“int *p”正确?

对于我的代码,哪一种布局风格(layout style)是最好的?

我应该将“const”放在类型之前还是之后?

使用宏有什么问题?

我如何写这个非常简单的程序?

特别是在一个学期的开始,我常常收到许多关于编写一个非常简单的程序的询问。这个问题有一个很具代表性的解决方法,那就是(在你的程序中)读入几个数字,对它们做一些处理,再把结果输出。下面是一个这样做的例子:

#include<iostream>

#include<vector>

#include<algorithm>

using namespace std;

int main()

{

vector<double> v;

double d;

while(cin>>d) v.push_back(d); // 读入元素

if (!cin.eof()) { // 检查输入是否出错

cerr << "format error\n";

return 1; // 返回一个错误

}

cout << "read " << v.size() << " elements\n";

reverse(v.begin(),v.end());

cout << "elements in reverse order:\n";

for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

return 0; // 成功返回

}

对这段程序的观察:

这是一段标准的ISO C++程序,使用了标准库(standard library)。标准库工具在命名空间std中声明,封装在没有.h后缀的头文件中。

如果你要在Windows下编译它,你需要将它编译成一个“控制台程序”(console application)。记得将源文件加上.cpp后缀,否则编译器可能会以为它是一段C代码而不是C++。

是的,main()函数返回一个int值。

读到一个标准的向量(vector)中,可以避免在随意确定大小的缓冲中溢出的错误。读到一个数组(array)中,而不产生“简单错误”(silly error),这已经超出了一个新手的能力——如果你做到了,那你已经不是一个新手了。如果你对此表示怀疑,我建议你阅读我的文章“将标准C++作为一种新的语言来学习”("Learning Standard C++ as a New Language"),你可以在本人著作列表(my publications list)中下载到它。

!cin.eof()是对流的格式的检查。事实上,它检查循环是否终结于发现一个end-of-file(如果不是这样,那么意味着输入没有按照给定的格式)。更多的说明,请参见你的C++教科书中的“流状态”(stream state)部分。

vector知道它自己的大小,因此我不需要计算元素的数量。

这段程序没有包含显式的内存管理。Vector维护一个内存中的栈,以存放它的元素。当一个vector需要更多的内存时,它会分配一些;当它不再生存时,它会释放内存。于是,使用者不需要再关心vector中元素的内存分配和释放问题。

程序在遇到输入一个“end-of-file”时结束。如果你在UNIX平台下运行它,“end-of-file”等于键盘上的Ctrl+D。如果你在Windows平台下,那么由于一个BUG它无法辨别“end-of-file”字符,你可能倾向于使用下面这个稍稍复杂些的版本,它使用一个词“end”来表示输入已经结束。

#include<iostream>

#include<vector>

#include<algorithm>

#include<string>

using namespace std;

int main()

{

vector<double> v;

double d;

while(cin>>d) v.push_back(d); // 读入一个元素

if (!cin.eof()) { // 检查输入是否失败

cin.clear(); // 清除错误状态

string s;

cin >> s; // 查找结束字符

if (s != "end") {

cerr << "format error\n";

return 1; // 返回错误

}

}

cout << "read " << v.size() << " elements\n";

reverse(v.begin(),v.end());

cout << "elements in reverse order:\n";

for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

return 0; // 成功返回

}

更多的关于使用标准库将事情简化的例子,请参见《C++程序设计语言》中的“漫游标准库”("Tour of the Standard Library")一章。

为什么编译要花这么长的时间?

你的编译器可能有问题。也许它太老了,也许你安装它的时候出了错,也许你用的计算机已经是个古董。在诸如此类的问题上,我无法帮助你。

但是,这也是很可能的:你要编译的程序设计得非常糟糕,以至于编译器不得不检查数以百计的头文件和数万行代码。理论上来说,这是可以避免的。如果这是你购买的库的设计问题,你对它无计可施(除了换一个更好的库),但你可以将你自己的代码组织得更好一些,以求得将修改代码后的重新编译工作降到最少。这样的设计会更好,更有可维护性,因为它们展示了更好的概念上的分离。

看看这个典型的面向对象的程序例子:

class Shape {

public: // 使用Shapes的用户的接口

virtual void draw() const;

virtual void rotate(int degrees);

// ...

protected: // common data (for implementers of Shapes)

Point center;

Color col;

// ...

};

clas Circle : public Shape {

public:

void draw() const;

void rotate(int) { }

// ...

protected:

int radius;

// ...

};

class Triangle : public Shape {

public:

void draw() const;

void rotate(int);

// ...

protected:

Point a, b, c;

// ...

};

设计思想是,用户通过Shape的public接口来操纵它们,而派生类(例如Circle和Triangle)的实现部分则共享由protected成员表现的那部分实现(implementation)。

这不是一件容易的事情:确定哪些实现部分是对所有的派生类都有用的,并将之共享出来。因此,与public接口相比,protected成员往往要做多得多的改动。举例来说,虽然理论上“中心”(center)对所有的图形都是一个有效的概念,但当你要维护一个三角形的“中心”的时候,是一件非常麻烦的事情——对于三角形,当且仅当它确实被需要的时候,计算这个中心才是有意义的。

protected成员很可能要依赖于实现部分的细节,而Shape的用户(译注:user此处译为用户,指使用Shape类的代码,下同)却不见得必须依赖它们。举例来说,很多(大多数?)使用Shape的代码在逻辑上是与“颜色”无关的,但是由于Shape中“颜色”这个定义的存在,却可能需要一堆复杂的头文件,来结合操作系统的颜色概念。

当protected部分发生了改变时,使用Shape的代码必须重新编译——即使只有派生类的实现部分才能够访问protected成员。

于是,基类中的“实现相关的信息”(information helpful to implementers)对用户来说变成了象接口一样敏感的东西,它的存在导致了实现部分的不稳定,用户代码的无谓的重编译(当实现部分发生改变时),以及将头文件无节制地包含进用户代码中(因为“实现相关的信息”需要它们)。有时这被称为“脆弱的基类问题”(brittle base class problem)。

一个很明显的解决方案就是,忽略基类中那些象接口一样被使用的“实现相关的信息”。换句话说,使用接口,纯粹的接口。也就是说,用抽象基类的方式来表示接口:

class Shape {

public: //使用Shapes的用户的接口

virtual void draw() const = 0;

virtual void rotate(int degrees) = 0;

virtual Point center() const = 0;

// ...

// 没有数据

};

class Circle : public Shape {

public:

void draw() const;

void rotate(int) { }

Point center() const { return center; }

// ...

protected:

Point cent;

Color col;

int radius;

// ...

};

class Triangle : public Shape {

public:

void draw() const;

void rotate(int);

Point center() const;

// ...

protected:

Color col;

Point a, b, c;

// ...

};

现在,用户代码与派生类的实现部分的变化之间的关系被隔离了。我曾经见过这种技术使得编译的时间减少了几个数量级。

但是,如果确实存在着对所有派生类(或仅仅对某些派生类)都有用的公共信息时怎么办呢?可以简单把这些信息封装成类,然后从它派生出实现部分的类:

class Shape {

public: //使用Shapes的用户的接口

virtual void draw() const = 0;

virtual void rotate(int degrees) = 0;

virtual Point center() const = 0;

// ...

// no data

};

struct Common {

Color col;

// ...

};

class Circle : public Shape, protected Common {

public:

void draw() const;

void rotate(int) { }

Point center() const { return center; }

// ...

protected:

Point cent;

int radius;

};

class Triangle : public Shape, protected Common {

public:

void draw() const;

void rotate(int);

Point center() const;

// ...

protected:

Point a, b, c;

};

为什么一个空类的大小不为0?

要清楚,两个不同的对象的地址也是不同的。基于同样的理由,new总是返回指向不同对象的指针。

看看:

class Empty { };

void f()

{

Empty a, b;

if (&a == &b) cout << "impossible: report error to compiler supplier";

Empty* p1 = new Empty;

Empty* p2 = new Empty;

if (p1 == p2) cout << "impossible: report error to compiler supplier";

}

有一条有趣的规则:一个空的基类并不一定有分隔字节。

struct X : Empty {

int a;

// ...

};

void f(X* p)

{

void* p1 = p;

void* p2 = &p->a;

if (p1 == p2) cout << "nice: good optimizer";

}

这种优化是允许的,可以被广泛使用。它允许程序员使用空类以表现一些简单的概念。现在有些编译器提供这种“空基类优化”(empty base class optimization)。

我必须在类声明处赋予数据吗?

不必须。如果一个接口不需要数据时,无须在作为接口定义的类中赋予数据。代之以在派生类中给出它们。参见“为什么编译要花这么长的时间?”。

有时候,你必须在一个类中赋予数据。考虑一下复数类的情况:

template<class Scalar> class complex {

public:

complex() : re(0), im(0) { }

complex(Scalar r) : re(r), im(0) { }

complex(Scalar r, Scalar i) : re(r), im(i) { }

// ...

complex& operator+=(const complex& a)

{ re+=a.re; im+=a.im; return *this; }

// ...

private:

Scalar re, im;

};

设计这种类型的目的是将它当做一个内建(built-in)类型一样被使用。在声明处赋值是必须的,以保证如下可能:建立真正的本地对象(genuinely local objects)(比如那些在栈中而不是在堆中分配的对象),或者使某些简单操作被适当地inline化。对于那些支持内建的复合类型的语言来说,要获得它们提供的效率,真正的本地对象和inline化都是必要的。

为什么成员函数默认不是virtual的?

因为很多类并不是被设计作为基类的。例如复数类。

而且,一个包含虚拟函数的类的对象,要占用更多的空间以实现虚拟函数调用机制——往往是每个对象占用一个字(word)。这个额外的字是非常可观的,而且在涉及和其它语言的数据的兼容性时,可能导致麻烦(例如C或Fortran语言)。

要了解更多的设计原理,请参见《C++语言的设计和演变》(The Design and Evolution of C++)。

为什么析构函数默认不是virtual的?

因为很多类并不是被设计作为基类的。只有类在行为上是它的派生类的接口时(这些派生类往往在堆中分配,通过指针或引用来访问),虚拟函数才有意义。

那么什么时候才应该将析构函数定义为虚拟呢?当类至少拥有一个虚拟函数时。拥有虚拟函数意味着一个类是派生类的接口,在这种情况下,一个派生类的对象可能通过一个基类指针来销毁。例如:

class Base {

// ...

virtual ~Base();

};

class Derived : public Base {

// ...

~Derived();

};

void f()

{

Base* p = new Derived;

delete p; // 虚拟析构函数保证~Derived函数被调用

}

如果基类的析构函数不是虚拟的,那么派生类的析构函数将不会被调用——这可能产生糟糕的结果,例如派生类的资源不会被释放。

为什么不能有虚拟构造函数?

虚拟调用是一种能够在给定信息不完全(given partial information)的情况下工作的机制。特别地,虚拟允许我们调用某个函数,对于这个函数,仅仅知道它的接口,而不知道具体的对象类型。但是要建立一个对象,你必须拥有完全的信息。特别地,你需要知道要建立的对象的具体类型。因此,对构造函数的调用不可能是虚拟的。

当要求建立一个对象时,一种间接的技术常常被当作“虚拟构造函数”来使用。有关例子,请参见《C++程序设计语言》第三版15.6.2.节。

下面这个例子展示一种机制:如何使用一个抽象类来建立一个适当类型的对象。

struct F { // 对象建立函数的接口

virtual A* make_an_A() const = 0;

virtual B* make_a_B() const = 0;

};

void user(const F& fac)

{

A* p = fac.make_an_A(); // 将A作为合适的类型

B* q = fac.make_a_B(); // 将B作为合适的类型

// ...

}

struct FX : F {

A* make_an_A() const { return new AX(); } // AX是A的派生

B* make_a_B() const { return new BX(); } // AX是B的派生

};

struct FY : F {

A* make_an_A() const { return new AY(); } // AY是A的派生

B* make_a_B() const { return new BY(); } // BY是B的派生

};

int main()

{

user(FX()); // 此用户建立AX与BX

user(FY()); // 此用户建立AY与BY

// ...

}

这是所谓的“工厂模式”(the factory pattern)的一个变形。关键在于,user函数与AX或AY这样的类的信息被完全分离开来了。

为什么重载在继承类中不工作?

这个问题(非常常见)往往出现于这样的例子中:

#include<iostream>

using namespace std;

class B {

public:

int f(int i) { cout << "f(int): "; return i+1; }

// ...

};

class D : public B {

public:

double f(double d) { cout << "f(double): "; return d+1.3; }

// ...

};

int main(){

D* pd = new D;

cout << pd->f(2) << '\n';

cout << pd->f(2.3) << '\n';

}

它输出的结果是:

f(double): 3.3

f(double): 3.6

而不是象有些人猜想的那样:

f(int): 3

f(double): 3.6

换句话说,在B和D之间并没有发生重载的解析。编译器在D的区域内寻找,找到了一个函数double f(double),并执行了它。它永远不会涉及(被封装的)B的区域。在C++中,没有跨越区域的重载——对于这条规则,继承类也不例外。更多的细节,参见《C++语言的设计和演变》和《C++程序设计语言》。

但是,如果我需要在基类和继承类之间建立一组重载的f()函数呢?很简单,使用using声明:

class D : public B {

public:

using B::f; // make every f from B available

double f(double d) { cout << "f(double): "; return d+1.3; }

// ...

};

进行这个修改之后,输出结果将是:

f(int): 3

f(double): 3.6

这样,在B的f()和D的f()之间,重载确实实现了,并且选择了一个最合适的f()进行调用。

我能够在构造函数中调用一个虚拟函数吗?

可以,但是要小心。它可能不象你期望的那样工作。在构造函数中,虚拟调用机制不起作用,因为继承类的重载还没有发生。对象先从基类被创建,“基类先于继承类(base before derived)”。

看看这个:

#include<string>

#include<iostream>

using namespace std;

class B {

public:

B(const string& ss) { cout << "B constructor\n"; f(ss); }

virtual void f(const string&) { cout << "B::f\n";}

};

class D : public B {

public:

D(const string & ss) :B(ss) { cout << "D constructor\n";}

void f(const string& ss) { cout << "D::f\n"; s = ss; }

private:

string s;

};

int main()

{

D d("Hello");

}

程序编译以后会输出:

B constructor

B::f

D constructor

注意不是D::f。设想一下,如果出于不同的规则,B::B()可以调用D::f()的话,会产生什么样的后果:因为构造函数D::D()还没有运行,D::f()将会试图将一个还没有初始化的字符串s赋予它的参数。结果很可能是导致立即崩溃。

析构函数在“继承类先于基类”的机制下运行,因此虚拟机制的行为和构造函数一样:只有本地定义(local definitions)被使用——不会调用虚拟函数,以免触及对象中的(现在已经被销毁的)继承类的部分。

更多的细节,参见《C++语言的设计和演变》13.2.4.2和《C++程序设计语言》15.4.3。

有人暗示,这只是一条实现时的人为制造的规则。不是这样的。事实上,要实现这种不安全的方法倒是非常容易的:在构造函数中直接调用虚拟函数,就象调用其它函数一样。但是,这样就意味着,任何虚拟函数都无法编写了,因为它们需要依靠基类的固定的创建(invariants established by base classes)。这将会导致一片混乱。

有没有“指定位置删除”(placement delete)?

没有,不过如果你需要的话,可以自己写一个。

看看这个指定位置创建(placement new),它将对象放进了一系列Arena中;

class Arena {

public:

void* allocate(size_t);

void deallocate(void*);

// ...

};

void* operator new(size_t sz, Arena& a)

{

return a.allocate(sz);

}

Arena a1(some arguments);

Arena a2(some arguments);

这样实现了之后,我们就可以这么写:

X* p1 = new(a1) X;

Y* p2 = new(a1) Y;

Z* p3 = new(a2) Z;

// ...

但是,以后怎样正确地销毁这些对象呢?没有对应于这种“placement new”的内建的“placement delete”,原因是,没有一种通用的方法可以保证它被正确地使用。在C++的类型系统中,没有什么东西可以让我们确认,p1一定指向一个由Arena类型的a1分派的对象。p1可能指向任何东西分派的任何一块地方。

然而,有时候程序员是知道的,所以这是一种方法:

template<class T> void destroy(T* p, Arena& a)

{

if (p) {

p->~T(); // explicit destructor call

a.deallocate(p);

}

}

现在我们可以这么写:

destroy(p1,a1);

destroy(p2,a2);

destroy(p3,a3);

如果Arena维护了它保存着的对象的线索,你甚至可以自己写一个析构函数,以避免它发生错误。

这也是可能的:定义一对相互匹配的操作符new()和delete(),以维护《C++程序设计语言》15.6中的类继承体系。参见《C++语言的设计和演变》10.4和《C++程序设计语言》19.4.5。

我能防止别人继承我自己的类吗?

可以,但你为什么要那么做呢?这是两个常见的回答:

效率:避免我的函数被虚拟调用

安全:保证我的类不被用作一个基类(例如,保证我能够复制对象而不用担心出事)

根据我的经验,效率原因往往是不必要的担心。在C++中,虚拟函数调用是如此之快,以致于它们在一个包含虚拟函数的类中被实际使用时,相比普通的函数调用,根本不会产生值得考虑的运行期开支。注意,仅仅通过指针或引用时,才会使用虚拟调用机制。当直接通过对象名字调用一个函数时,虚拟函数调用的开支可以被很容易地优化掉。

如果确实有真正的需要,要将一个类封闭起来以防止虚拟调用,那么可能首先应该问问为什么它们是虚拟的。我看见过一些例子,那些性能表现不佳的函数被设置为虚拟,没有其他原因,仅仅是因为“我们习惯这么干”。

这个问题的另一个部分,由于逻辑上的原因如何防止类被继承,有一个解决方案。不幸的是,这个方案并不完美。它建立在这样一个事实的基础之上,那就是:大多数的继承类必须建立一个虚拟的基类。这是一个例子:

class Usable;

class Usable_lock {

friend class Usable;

private:

Usable_lock() {}

Usable_lock(const Usable_lock&) {}

};

class Usable : public virtual Usable_lock {

// ...

public:

Usable();

Usable(char*);

// ...

};

Usable a;

class DD : public Usable { };

DD dd; // 错误: DD::DD() 不能访问

// Usable_lock::Usable_lock()是一个私有成员

(来自《C++语言的设计和演变》11.4.3)

为什么不能为模板参数定义约束(constraints)?

可以的,而且方法非常简单和通用。

看看这个:

template<class Container>

void draw_all(Container& c)

{

for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

}

如果出现类型错误,可能是发生在相当复杂的for_each()调用时。例如,如果容器的元素类型是int,我们将得到一个和for_each()相关的含义模糊的错误(因为不能够对对一个int值调用Shape::draw的方法)。

为了提前捕捉这个错误,我这样写:

template<class Container>

void draw_all(Container& c)

{

Shape* p = c.front(); // accept only containers of Shape*s

for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

}

对于现在的大多数编译器,中间变量p的初始化将会触发一个易于了解的错误。这个窍门在很多语言中都是通用的,而且在所有的标准创建中都必须这样做。在成品的代码中,我也许可以这样写:

template<class Container>

void draw_all(Container& c)

{

typedef typename Container::value_type T;

Can_copy<T,Shape*>(); // accept containers of only Shape*s

for_each(c.begin(),c.end(),mem_fun(&Shape::draw));

}

这样就很清楚了,我在建立一个断言(assertion)。Can_copy模板可以这样定义:

template<class T1, class T2> struct Can_copy {

static void constraints(T1 a, T2 b) { T2 c = a; b = a; }

Can_copy() { void(*p)(T1,T2) = constraints; }

};

Can_copy(在运行时)检查T1是否可以被赋值给T2。Can_copy<T,Shape*>检查T是否是Shape*类型,或者是一个指向由Shape类公共继承而来的类的对象的指针,或者是被用户转换到Shape*类型的某个类型。注意这个定义被精简到了最小:

一行命名要检查的约束,和要检查的类型

一行列出指定的要检查的约束(constraints()函数)

一行提供触发检查的方法(通过构造函数)

注意这个定义有相当合理的性质:

你可以表达一个约束,而不用声明或复制变量,因此约束的编写者可以用不着去设想变量如何被初始化,对象是否能够被复制,被销毁,以及诸如此类的事情。(当然,约束要检查这些属性的情况时例外。)

使用现在的编译器,不需要为约束产生代码

定义和使用约束,不需要使用宏

当约束失败时,编译器会给出可接受的错误信息,包括“constraints”这个词(给用户一个线索),约束的名字,以及导致约束失败的详细错误(例如“无法用double*初始化Shape*”)。

那么,在C++语言中,有没有类似于Can_copy——或者更好——的东西呢?在《C++语言的设计和演变》中,对于在C++中实现这种通用约束的困难进行了分析。从那以来,出现了很多方法,来让约束类变得更加容易编写,同时仍然能触发良好的错误信息。例如,我信任我在Can_copy中使用的函数指针的方式,它源自Alex Stepanov和Jeremy Siek。我并不认为Can_copy()已经可以标准化了——它需要更多的使用。同样,在C++社区中,各种不同的约束方式被使用;到底是哪种约束模板在广泛的使用中被证明是最有效的,还没有达成一致的意见。

但是,这种方式非常普遍,比语言提供的专门用于约束检查的机制更加普遍。无论如何,当我们编写一个模板时,我们拥有了C++提供的最丰富的表达力量。看看这个:

template<class T, class B> struct Derived_from {

static void constraints(T* p) { B* pb = p; }

Derived_from() { void(*p)(T*) = constraints; }

};

template<class T1, class T2> struct Can_copy {

static void constraints(T1 a, T2 b) { T2 c = a; b = a; }

Can_copy() { void(*p)(T1,T2) = constraints; }

};

template<class T1, class T2 = T1> struct Can_compare {

static void constraints(T1 a, T2 b) { a==b; a!=b; a<b; }

Can_compare() { void(*p)(T1,T2) = constraints; }

};

template<class T1, class T2, class T3 = T1> struct Can_multiply {

static void constraints(T1 a, T2 b, T3 c) { c = a*b; }

Can_multiply() { void(*p)(T1,T2,T3) = constraints; }

};

struct B { };

struct D : B { };

struct DD : D { };

struct X { };

int main()

{

Derived_from<D,B>();

Derived_from<DD,B>();

Derived_from<X,B>();

Derived_from<int,B>();

Derived_from<X,int>();

Can_compare<int,float>();

Can_compare<X,B>();

Can_multiply<int,float>();

Can_multiply<int,float,double>();

Can_multiply<B,X>();

Can_copy<D*,B*>();

Can_copy<D,B*>();

Can_copy<int,B*>();

}

// 典型的“元素必须继承自Mybase*”约束:

template<class T> class Container : Derived_from<T,Mybase> {

// ...

};

事实上,Derived_from并不检查来源(derivation),而仅仅检查转换(conversion),不过这往往是一个更好的约束。为约束想一个好名字是很难的。

既然已经有了优秀的qsort()函数,为什么还需要一个sort()?

对于初学者来说,

qsort(array,asize,sizeof(elem),elem_compare);

看上去太古怪了,而且比这个更难理解:

sort(vec.begin(),vec.end());

对于专家来说,在元素与比较方式(comparison criteria)都相同的情况下,sort()比qsort()更快,这是很重要的。而且,qsort()是通用的,所以它可以用于不同容器类型、元素类型、比较方式的任意有意义的组合。举例来说:

struct Record {

string name;

// ...

};

struct name_compare { // 使用"name"作为键比较Record

bool operator()(const Record& a, const Record& b) const

{ return a.name<b.name; }

};

void f(vector<Record>& vs)

{

sort(vs.begin(), vs.end(), name_compare());

// ...

}

而且,很多人欣赏sort()是因为它是类型安全的,使用它不需要进行造型(cast),没有人必须去为基本类型写一个compare()函数。

更多的细节,参见我的文章《将标准C++作为一种新的语言来学习》(Learning C++ as a New language),可以从我的文章列表中找到。

sort()胜过qsort()的主要原因是,比较操作在内联(inlines)上做得更好。

什么是函数对象(function object)?

顾名思义,就是在某种方式上表现得象一个函数的对象。典型地,它是指一个类的实例,这个类定义了应用操作符operator()。

函数对象是比函数更加通用的概念,因为函数对象可以定义跨越多次调用的可持久的部分(类似静态局部变量),同时又能够从对象的外面进行初始化和检查(和静态局部变量不同)。例如:

class Sum {

int val;

public:

Sum(int i) :val(i) { }

operator int() const { return val; } // 取得值

int operator()(int i) { return val+=i; } // 应用

};

void f(vector v)

{

Sum s = 0; // initial value 0

s = for_each(v.begin(), v.end(), s); // 求所有元素的和

cout << "the sum is " << s << "\n";

//或者甚至:

cout << "the sum is " << for_each(v.begin(), v.end(), Sum(0)) << "\n";

}

注意一个拥有应用操作符的函数对象可以被完美地内联化(inline),因为它没有涉及到任何指针,后者可能导致拒绝优化。与之形成对比的是,现有的优化器几乎不能(或者完全不能?)将一个通过函数指针的调用内联化。

在标准库中,函数对象被广泛地使用以获得弹性。

我应该如何对付内存泄漏?

写出那些不会导致任何内存泄漏的代码。很明显,当你的代码中到处充满了new 操作、delete操作和指针运算的话,你将会在某个地方搞晕了头,导致内存泄漏,指针引用错误,以及诸如此类的问题。这和你如何小心地对待内存分配工作其实完全没有关系:代码的复杂性最终总是会超过你能够付出的时间和努力。于是随后产生了一些成功的技巧,它们依赖于将内存分配(allocations)与重新分配(deallocation)工作隐藏在易于管理的类型之后。标准容器(standard containers)是一个优秀的例子。它们不是通过你而是自己为元素管理内存,从而避免了产生糟糕的结果。想象一下,没有string和vector的帮助,写出这个:

#include<vector>

#include<string>

#include<iostream>

#include<algorithm>

using namespace std;

int main() // small program messing around with strings

{

cout << "enter some whitespace-separated words:\n";

vector<string> v;

string s;

while (cin>>s) v.push_back(s);

sort(v.begin(),v.end());

string cat;

typedef vector<string>::const_iterator Iter;

for (Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";

cout << cat << '\n';

}

你有多少机会在第一次就得到正确的结果?你又怎么知道你没有导致内存泄漏呢?

注意,没有出现显式的内存管理,宏,造型,溢出检查,显式的长度限制,以及指针。通过使用函数对象和标准算法(standard algorithm),我可以避免使用指针——例如使用迭代子(iterator),不过对于一个这么小的程序来说有点小题大作了。

这些技巧并不完美,要系统化地使用它们也并不总是那么容易。但是,应用它们产生了惊人的差异,而且通过减少显式的内存分配与重新分配的次数,你甚至可以使余下的例子更加容易被跟踪。早在1981年,我就指出,通过将我必须显式地跟踪的对象的数量从几万个减少到几打,为了使程序正确运行而付出的努力从可怕的苦工,变成了应付一些可管理的对象,甚至更加简单了。

如果你的程序还没有包含将显式内存管理减少到最小限度的库,那么要让你程序完成和正确运行的话,最快的途径也许就是先建立一个这样的库。

模板和标准库实现了容器、资源句柄以及诸如此类的东西,更早的使用甚至在多年以前。异常的使用使之更加完善。

如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时,你可以使用资源句柄(resource handle),以将内存泄漏的可能性降至最低。这里有个例子:我需要通过一个函数,在空闲内存中建立一个对象并返回它。这时候可能忘记释放这个对象。毕竟,我们不能说,仅仅关注当这个指针要被释放的时候,谁将负责去做。使用资源句柄,这里用了标准库中的auto_ptr,使需要为之负责的地方变得明确了。

#include<memory>

#include<iostream>

using namespace std;

struct S {

S() { cout << "make an S\n"; }

~S() { cout << "destroy an S\n"; }

S(const S&) { cout << "copy initialize an S\n"; }

S& operator=(const S&) { cout << "copy assign an S\n"; }

};

S* f()

{

return new S; // 谁该负责释放这个S?

};

auto_ptr<S> g()

{

return auto_ptr<S>(new S); // 显式传递负责释放这个S

}

int main()

{

cout << "start main\n";

S* p = f();

cout << "after f() before g()\n";

// S* q = g(); // 将被编译器捕捉

auto_ptr<S> q = g();

cout << "exit main\n";

// *p产生了内存泄漏

// *q被自动释放

}

在更一般的意义上考虑资源,而不仅仅是内存。

如果在你的环境中不能系统地应用这些技巧(例如,你必须使用别的地方的代码,或者你的程序的另一部分简直是原始人类(译注:原文是Neanderthals,尼安德特人,旧石器时代广泛分布在欧洲的猿人)写的,如此等等),那么注意使用一个内存泄漏检测器作为开发过程的一部分,或者插入一个垃圾收集器(garbage collector)。

我为什么在捕获一个异常之后就不能继续?

换句话说,C++为什么不提供一种简单的方式,让程序能够回到异常抛出点之后,并继续执行?

主要的原因是,如果从异常处理之后继续,那么无法预知掷出点之后的代码如何对待异常处理,是否仅仅继续执行,就象什么也没有发生一样。异常处理者无法知道,在继续之前,有关的上下文环境(context)是否是“正确”的。要让这样的代码正确执行,抛出异常的编写者与捕获异的编写者必须对彼此的代码与上下文环境都非常熟悉才行。这样会产生非常复杂的依赖性,因此无论在什么情况下,都会导致一系列严重的维护问题。

当我设计C++的异常处理机制时,我曾经认真地考虑过允许这种继续的可能性,而且在标准化的过程中,这个问题被非常详细地讨论过。请参见《C++语言的设计和演变》中的异常处理章节。

在一次新闻组的讨论中,我曾经以一种稍微不同的方式回答过这个问题。

为什么C++中没有相当于realloc()的函数?

如果你需要,你当然可以使用realloc()。但是,realloc()仅仅保证能工作于这样的数组之上:它们被malloc()(或者类似的函数)分配,包含一些没有用户定义的复制构造函数(copy constructors)的对象。而且,要记住,与通常的期望相反,realloc()有时也必须复制它的参数数组。

在C++中,处理内存重新分配的更好的方法是,使用标准库中的容器,例如vector,并让它自我增长。

如何使用异常?

参见《C++程序设计语言》第4章,第8.3节,以及附录E。这个附录针对的是如何在要求苛刻的程序中写出异常安全的代码的技巧,而不是针对初学者的。一个关键的技术是“资源获得即初始化”(resource acquisiton is initialization),它使用一些有析构函数的类,来实现强制的资源管理。

怎样从输入中读取一个字符串?

你可以用这种方式读取一个单独的以空格结束的词:

#include<iostream>

#include<string>

using namespace std;

int main()

{

cout << "Please enter a word:\n";

string s;

cin>>s;

cout << "You entered " << s << '\n';

}

注意,这里没有显式的内存管理,也没有可能导致溢出的固定大小的缓冲区。

如果你确实想得到一行而不是一个单独的词,可以这样做:

#include<iostream>

#include<string>

using namespace std;

int main()

{

cout << "Please enter a line:\n";

string s;

getline(cin,s);

cout << "You entered " << s << '\n';

}

在《C++程序设计语言》(可在线获得)的第3章,可以找到一个对诸如字符串与流这样的标准库工具的简介。对于使用C与C++进行简单输入输出的详细比较,参见我的文章《将标准C++作为一种新的语言来学习》(Learning Standard C++ as a New Language),你可以在本人著作列表(my publications list)中下载到它。

为什么C++不提供“finally”的构造?

因为C++提供了另外一种方法,它几乎总是更好的:“资源获得即初始化”(resource acquisiton is initialization)技术。基本的思路是,通过一个局部对象来表现资源,于是局部对象的析构函数将会释放资源。这样,程序员就不会忘记释放资源了。举例来说:

class File_handle {

FILE* p;

public:

File_handle(const char* n, const char* a)

{ p = fopen(n,a); if (p==0) throw Open_error(errno); }

File_handle(FILE* pp)

{ p = pp; if (p==0) throw Open_error(errno); }

~File_handle() { fclose(p); }

operator FILE*() { return p; }

// ...

};

void f(const char* fn)

{

File_handle f(fn,"rw"); //打开fn进行读写

// 通过f使用文件

}

在一个系统中,需要为每一个资源都使用一个“资源句柄”类。无论如何,我们不需要为每一个资源获得都写出“finally”语句。在实时系统中,资源获得要远远多于资源的种类,因此和使用“finally”构造相比,“资源获得即初始化”技术会产生少得多的代码。

什么是自动指针(auto_ptr),为什么没有自动数组(auto_array)?

auto_ptr是一个非常简单的句柄类的例子,在<memory>中定义,通过“资源获得即初始化”技术支持异常安全。auto_ptr保存着一个指针,能够象指针一样被使用,并在生存期结束时释放指向的对象。举例:

#include<memory>

using namespace std;

struct X {

int m;

// ..

};

void f()

{

auto_ptr<X> p(new X);

X* q = new X;

p->m++; // 象一个指针一样使用p

q->m++;

// ...

delete q;

}

如果在...部分抛出了一个异常,p持有的对象将被auto_ptr的析构函数正确地释放,而q指向的X对象则产生了内存泄漏。更多的细节,参见《C++程序设计语言》14.4.2节。

auto_ptr是一个非常简单的类。特别地,它不是一个引用计数(reference counted)的指针。如果你将一个auto_ptr赋值给另一个,那么被赋值的auto_ptr将持有指针,而原来的auto_ptr将持有0。举例:

#include<memory>

#include<iostream>

using namespace std;

struct X {

int m;

// ..

};

int main()

{

auto_ptr<X> p(new X);

auto_ptr<X> q(p);

cout << "p " << p.get() << " q " << q.get() << "\n";

}

将会打印出一个指向0的指针和一个指向非0的指针。例如:

p 0x0 q 0x378d0

auto_ptr::get()返回那个辅助的指针。

这种“转移”语义不同于通常的“复制”语义,这是令人惊讶的。特别地,永远不要使用auto_ptr作为一个标准容器的成员。标准容器需要通常的“复制”语义。例如:

std::vector<auto_ptr<X> >v; // 错误

auto_ptr只持有指向一个单独元素的指针,而不是指向一个数组的指针:

void f(int n)

{

auto_ptr<X> p(new X[n]); //错误

// ...

}

这是错误的,因为析构函数会调用delete而不是delete[]来释放指针,这样就不会调用余下的n-1个X的析构函数。

那么我们需要一个auto_array来持有数组吗?不。没有auto_array。原因是根本没有这种需要。更好的解决方案是使用vector:

void f(int n)

{

vector<X> v(n);

// ...

}

当...部分发生异常时,v的析构函数会被正确地调用。

可以混合使用C风格与C++风格的内存分派与重新分配吗?

在这种意义上是可以的:你可以在同一个程序中使用malloc()和new。

在这种意义上是不行的:你不能使用malloc()来建立一个对象,又通过delete来释放它。你也不能用new建立一个新的对象,然后通过free()来释放它,或者通过realloc()在数组中再建立一个新的。

C++中的new和delete操作可以保证正确的构造和析构:构造函数和析构函数在需要它们的时候被调用。C风格的函数alloc(), calloc(), free(), 和realloc()却不能保证这一点。此外,用new和delete来获得和释放的原始内存,并不一定能保证与malloc()和free()兼容。如果这种混合的风格在你的系统中能够运用,只能说是你走运——暂时的。

如果你觉得需要使用realloc()——或者要做更多——考虑使用标准库中的vector。例如:

// 从输入中将词读取到一个字符串vector中

vector<string> words;

string s;

while (cin>>s && s!=".") words.push_back(s);

vector会视需要自动增长。

更多的例子与讨论,参见我的文章《将标准C++作为一种新的语言来学习》(Learning Standard C++ as a New Language),你可以在本人著作列表(my publications list)中下载到它。

我为什么必须使用一个造型来转换*void?

在C语言中,你可以隐式地将*void转换为*T。这是不安全的。考虑一下:

#include<stdio.h>

int main()

{

char i = 0;

char j = 0;

char* p = &i;

void* q = p;

int* pp = q; /* 不安全的,在C中可以,C++不行 */

printf("%d %d\n",i,j);

*pp = -1; /* 覆盖了从i开始的内存 */

printf("%d %d\n",i,j);

}

使用一个并不指向T类型的T*将是一场灾难。因此,在C++中,如果从一个void*得到一个T*,你必须进行显式转换。举例来说,要得到上列程序的这个令人别扭的效果,你可以这样写:

int* pp = (int*)q;

或者使用一个新的类型造型,以使这种没有检查的类型转换操作变得更加清晰:

int* pp = static_cast<int*>(q);

造型被最好地避免了。

在C语言中,这种不安全的转换最常见的应用之一,是将malloc()的结果赋予一个合适的指针。例如:

int* p = malloc(sizeof(int));

在C++中,使用类型安全的new操作符:

int* p = new int;

附带地,new操作符还提供了胜过malloc()的新特性:

new不会偶然分配错误的内存数量;

new会隐式地检查内存耗尽情况,而且

new提供了初始化。

举例:

typedef std::complex<double> cmplx;

/* C风格: */

cmplx* p = (cmplx*)malloc(sizeof(int)); /* 错误:类型不正确 */

/* 忘记测试p==0 */

if (*p == 7) { /* ... */ } /* 糟糕,忘记了初始化*p */

// C++风格:

cmplx* q = new cmplx(1,2); // 如果内存耗尽,将抛出一个bad_alloc异常

if (*q == 7) { /* ... */ }

我如何定义一个类内部(in-class)的常量?

如果你需要一个通过常量表达式来定义的常量,例如数组的范围,你有两种选择:

class X {

static const int c1 = 7;

enum { c2 = 19 };

char v1[c1];

char v2[c2];

// ...

};

乍看起来,c1的声明要更加清晰,但是要注意的是,使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必是static和const形式。这是很严重的限制:

class Y {

const int c3 = 7; // 错误:不是static

static int c4 = 7; // 错误:不是const

static const float c5 = 7; // 错误:不是整型

};

我倾向使用枚举的方式,因为它更加方便,而且不会诱使我去使用不规范的类内初始化语法。

那么,为什么会存在这种不方便的限制呢?一般来说,类在一个头文件中被声明,而头文件被包含到许多互相调用的单元去。但是,为了避免复杂的编译器规则,C++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话,这种规则就被破坏了。对于C++在这个设计上的权衡,请参见《C++语言的设计和演变》。

如果你不需要用常量表达式来初始化它,那么可以获得更大的弹性:

class Z {

static char* p; // 在定义中初始化

const int i; // 在构造函数中初始化

public:

Z(int ii) :i(ii) { }

};

char* Z::p = "hello, there";

你可以获取一个static成员的地址,当且仅当它有一个类外部的定义的时候:

class AE {

// ...

public:

static const int c6 = 7;

static const int c7 = 31;

};

const int AE::c7; // 定义

int f()

{

const int* p1 = &AE::c6; // 错误:c6没有左值

const int* p2 = &AE::c7; // ok

// ...

}

为什么delete不会将操作数置0?

考虑一下:

delete p;

// ...

delete p;

如果在...部分没有涉及到p的话,那么第二个“delete p;”将是一个严重的错误,因为C++的实现(译注:原文为a C++ implementation,当指VC++这样的实现了C++标准的具体工具)不能有效地防止这一点(除非通过非正式的预防手段)。既然delete 0从定义上来说是无害的,那么一个简单的解决方案就是,不管在什么地方执行了“delete p;”,随后都执行“p=0;”。但是,C++并不能保证这一点。

一个原因是,delete的操作数并不需要一个左值(lvalue)。考虑一下:

delete p+1;

delete f(x);

在这里,被执行的delete并没有拥有一个可以被赋予0的指针。这些例子可能很少见,但它们的确指出了,为什么保证“任何指向被删除对象的指针都为0”是不可能的。绕过这条“规则”的一个简单的方法是,有两个指针指向同一个对象:

T* p = new T;

T* q = p;

delete p;

delete q; // 糟糕!

C++显式地允许delete操作将操作数左值置0,而且我曾经希望C++的实现能够做到这一点,但这种思想看来并没有在C++的实现中变得流行。

如果你认为指针置0很重要,考虑使用一个销毁的函数:

template<class T> inline void destroy(T*& p) { delete p; p = 0; }

考虑一下,这也是为什么需要依靠标准库的容器、句柄等等,来将对new和delete的显式调用降到最低限度的另一个原因。

注意,通过引用来传递指针(以允许指针被置0)有一个额外的好处,能防止destroy()在右值上(rvalue)被调用:

int* f();

int* p;

// ...

destroy(f()); // 错误:应该使用一个非常量(non-const)的引用传递右值

destroy(p+1); // 错误:应该使用一个非常量(non-const)的引用传递右值

我能够写“void main()”吗?

这种定义:

void main() { /* ... */ }

在C++中从未被允许,在C语言中也是一样。参见ISO C++标准3.6.1[2]或者ISO C标准5.1.2.2.1。规范的实现接受这种方式:

int main() { /* ... */ }

int main(int argc, char* argv[]) { /* ... */ }

一个规范的实现可能提供许多版本的main(),但它们都必须返回int类型。main()返回的int值,是程序返回一个值给调用它的系统的方式。在那些不具备这种方式的系统中,返回值被忽略了,但这并不使“void main()”在C++或C中成为合法的。即使你的编译器接受了“void main()”,也要避免使用它,否则你将冒着被C和C++程序员视为无知的风险。

在C++中,main()并不需要包含显式的return语句。在这种情况下,返回值是0,表示执行成功。例如:

#include<iostream>

int main()

{

std::cout << "This program returns the integer value 0\n";

}

注意,无论是ISO C++还是C99,都不允许在声明中漏掉类型。那就是说,与C89和ARM C++形成对照,当声明中缺少类型时,并不会保证是“int”。于是:

#include<iostream>

main() { /* ... */ }

是错误的,因为缺少main()的返回类型。

为什么我不能重载点符号,::,sizeof,等等?

大多数的运算符能够被程序员重载。例外的是:

. (点符号) :: ?: sizeof

并没有什么根本的原因要禁止重载?:。仅仅是因为,我没有发现有哪种特殊的情况需要重载一个三元运算符。注意一个重载了 表达式1?表达式2:表达式3 的函数,不能够保证表达式2:表达式3中只有一个会被执行。

Sizeof不能够被重载是因为内建的操作(built-in operations),诸如对一个指向数组的指针进行增量操作,必须依靠它。考虑一下:

X a[10];

X* p = &a[3];

X* q = &a[3];

p++; // p指向a[4]

// 那么p的整型值必须比q的整型值大出一个sizeof(X)

所以,sizeof(X)不能由程序员来赋予一个不同的新意义,以免违反基本的语法。

在N::m中,无论N还是m都不是值的表达式;N和m是编译器知道的名字,::执行一个(编译期的)范围解析,而不是表达式求值。你可以想象一下,允许重载x::y的话,x可能是一个对象而不是一个名字空间(namespace)或者一个类,这样就会导致——与原来的表现相反——产生新的语法(允许 表达式1::表达式2)。很明显,这种复杂性不会带来任何好处。

理论上来说,.(点运算符)可以通过使用和->一样的技术来进行重载。但是,这样做会导致一个问题,那就是无法确定操作的是重载了.的对象呢,还是通过.引用的一个对象。例如:

class Y {

public:

void f();

// ...

};

class X { // 假设你能重载.

Y* p;

Y& operator.() { return *p; }

void f();

// ...

};

void g(X& x)

{

x.f(); // X::f还是Y::f还是错误?

}

这个问题能够用几种不同的方法解决。在标准化的时候,哪种方法最好还没有定论。更多的细节,请参见《C++语言的设计和演变》。

怎样将一个整型值转换为一个字符串?

最简单的方法是使用一个字符串流(stringstream):

#include<iostream>

#include<string>

#include<sstream>

using namespace std;

string itos(int i) // 将int转换成string

{

stringstream s;

s << i;

return s.str();

}

int main()

{

int i = 127;

string ss = itos(i);

const char* p = ss.c_str();

cout << ss << " " << p << "\n";

}

自然地,这种技术能够将任何使用<<输出的类型转换为字符串。对于字符串流的更多说明,参见《C++程序设计语言》21.5.3节。

“int* p”正确还是“int *p”正确?

二者都是正确的,因为二者在C和C++中都是有效的,而且意义完全一样。就语言的定义与相关的编译器来说,我们还可以说“int*p”或者“int * p”。

在“int* p”和“int *p”之间的选择与正确或错误无关,而只关乎风格与侧重点。C侧重表达式;对声明往往比可能带来的问题考虑得更多。另一方面,C++则非常重视类型。

一个“典型的C程序员”写成“int *p”,并且解释说“*p表示一个什么样的int”以强调语法,而且可能指出C(与C++)的语法来证明这种风格的正确性。是的,在语法上*被绑定到名字p上。

一个“典型的C++程序员”写成“int* p”,并且解释说“p是一个指向int的指针类型”以强调类型。是的,p是一个指向int的指针类型。我明确地倾向于这种侧重方向,而且认为对于学好更多的高级C++这是很重要的。

严重的混乱(仅仅)发生在当人们试图在一条声明中声明几个指针的时候:

int* p, p1; // 也许是错的:p1不是一个int*

把*放到名字这一边,看来也不能有效地减少这种错误:

int *p, p1; // 也许是错的?

为每一个名字写一条声明最大程度地解决了问题——特别是当我们初始化变量的时候。人们几乎不会这样写:

int* p = &i;

int p1 = p; // 错误:int用一个int*初始化了

如果他们真的这么干了,编译器也会指出。

每当事情可以有两种方法完成,有人就会迷惑。每当事情仅仅是一个风格的问题,争论就会没完没了。为每一个指针写一条声明,而且永远都要初始化变量,这样,混乱之源就消失了。更多的关于C的声明语法的讨论,参见《C++语言的设计和演变》。

对于我的代码,哪一种布局风格(layout style)是最好的?

这种风格问题属于个人的爱好。人们往往对布局风格的问题持有强烈的意见,不过,也许一贯性比某种特定的风格更加重要。象大多数人一样,我花了很长的时间,来为我的偏好作出一个固定的结论。

我个人使用通常称为“K&R”的风格。当使用C语言没有的构造函数时,需要增加新的习惯,这样就变成了一种有时被称为“Stroustrup”的风格。例如:

class C : public B {

public:

// ...

};

void f(int* p, int max)

{

if (p) {

// ...

}

for (int i = 0; i<max; ++i) {

// ...

}

}

比大多数布局风格更好,这种风格保留了垂直的空格,我喜欢尽可能地在合理的情况下对齐屏幕。对函数开头的大括弧的放置,有助于第一眼就分别出类的定义和函数的定义。

缩进是非常重要的。

设计问题,诸如作为主要接口的抽象基类的使用,使用模板以表现有弹性的类型安全的抽象,以及正确地使用异常以表现错误,比布局风格的选择要重要得多。

我应该将“const”放在类型之前还是之后?

我把它放在前面,但那仅仅是个人爱好问题。“const T”和“T const”总是都被允许的,而且是等效的。例如:

const int a = 1; // ok

int const b = 2; // also ok

我猜想第一种版本可能会让少数(更加固守语法规范)的程序员感到迷惑。

为什么?当我发明“const”(最初的名称叫做“readonly”,并且有一个对应的“writeonly”)的时候,我就允许它出现在类型之前或之后,因为这样做不会带来任何不明确。标准之前的C和C++规定了很少的(如果有的话)特定的顺序规范。

我不记得当时有过任何有关顺序问题的深入思考或讨论。那时,早期的一些使用者——特别是我——仅仅喜欢这种样子:

const int c = 10;

看起来比这种更好:

int const c = 10;

也许我也受了这种影响:在我最早的一些使用“readonly”的例子中

readonly int c = 10;

比这个更具有可读性:

int readonly c = 10;

我创造的那些最早的使用“const”的(C或C++)代码,看来已经在全球范围内取代了“readonly”。

我记得这个语法的选择在几个人——例如Dennis Ritchie——当中讨论过,但我不记得当时我倾向于哪种语言了。

注意在固定指针(const pointer)中,“const”永远出现在“*”之后。例如:

int *const p1 = q; // 指向int变量的固定指针

int const* p2 = q; //指向int常量的指针

const int* p3 = q; //指向int常量的指针

使用宏有什么问题?

宏不遵循C++中关于范围和类型的规则。这经常导致一些微妙的或不那么微妙的问题。因此,C++提供更适合其他的C++(译注:原文为the rest of C++,当指C++除了兼容C以外的部分)的替代品,例如内联函数、模板与名字空间。

考虑一下:

#include "someheader.h"

struct S {

int alpha;

int beta;

};

如果某人(不明智地)地写了一个叫“alpha”或“beta”的宏,那么它将不会被编译,或者被错误地编译,产生不可预知的结果。例如,“someheader.h”可能包含:

#define alpha 'a'

#define beta b[2]

将宏(而且仅仅是宏)全部大写的习惯,会有所帮助,但是对于宏并没有语言层次上的保护机制。例如,虽然成员的名字包含在结构体的内部,但这无济于事:在编译器能够正确地辨别这一点之前,宏已经将程序作为一个字符流进行了处理。顺便说一句,这是C和C++程序开发环境和工具能够被简化的一个主要原因:人与编译器看到的是不同的东西。

不幸的是,你不能假设别的程序员总是能够避免这种你认为“相当白痴”的事情。例如,最近有人报告我,他们遇到了一个包含goto的宏。我也见过这种情况,而且听到过一些——在很脆弱的时候——看起来确实有理的意见。例如:

#define prefix get_ready(); int ret__

#define Return(i) ret__=i; do_something(); goto exit

#define suffix exit: cleanup(); return ret__

void f()

{

prefix;

// ...

Return(10);

// ...

Return(x++);

//...

suffix;

}

作为一个维护的程序员,就会产生这种印象;将宏“隐藏”到一个头文件中——这并不罕见——使得这种“魔法”更难以被辨别。

一个常见的微妙问题是,一个函数风格的宏并不遵守函数参数传递的规则。例如:

#define square(x) (x*x)

void f(double d, int i)

{

square(d); // 好

square(i++); // 糟糕:这表示 (i++*i++)

square(d+1); //糟糕:这表示(d+1*d+1); 也就是 (d+d+1)

// ...

}

“d+1”的问题,可以通过在“调用”时或宏定义时添加一对圆括号来解决:

#define square(x) ((x)*(x)) /*这样更好 */

但是, i++被执行了两次(可能并不是有意要这么做)的问题仍然存在。

是的,我确实知道有些特殊的宏并不会导致C/C++预处理宏这样的问题。但是,我无心去发展C++中的宏。作为替代,我推荐使用C++语言中合适的工具,例如内联函数,模板,构造函数(用来初始化),析构函数(用来清除),异常(用来退出上下文环境),等等。

- 作者: cyberfan 2006年03月7日, 星期二 20:30  回复(0) |  引用(0) 加入博采

危险的关系:美元本位制与金融风险——从日本的经验看中国  (作者置顶)
作者
高柏

简介
  


危险的关系:美元本位制与金融风险——从日本的经验看中国
  来源:21世纪经济报道

  编者按:

  近代以来,面对工业化和全球化无法避免的趋势,中国一直试图在不断变动的国际贸易秩序中寻找属于自己的位置。近30年来,随着世界工厂的翅膀振动全球,贸易顺差以及外汇储备激增的事实让中国再一次遇到了一个全新的难题。

  在某种意义上,当代中国仍在继续着从传统农业社会向工业化文明的转型,还有从计划经济体制走向市场经济的探索。而且伴随着国门的打开,中国融入全球化进程也在不断加速。局面的复杂造成了两难甚至多重困境,但更为棘手的是,中国应该如何在全球化的进程中更多的维护自身利益,同时避免其负面效应,却依然是一个尚未得解的难题。

  日本曾是世界经济皇冠上的明珠,但十年的衰退让这颗曾经的明珠变得黯淡无光。古人云:以史为鉴。作者高柏教授多年来致力于研究日本问题,今天,就让我们暂时收起争论,从日本的经验教训中去寻找对中国有益的因子吧!

  2005年,美国的对外贸易逆差达到了创纪录的7258亿美元,据美国官方的统计,其中对华贸易逆差为2016亿美元(中国公布的数字为1142亿美元,原因在于两国的计算方法不同,美国将途经香港转运的货物计算在内,而中国没有)。同年,美国的财政赤字也高达4270亿美元,而个人储蓄率则为20世纪30年代大萧条以来的最低点———负百分之零点五。

  中国在2005年则是风景这边独好,不但GDP增至18万亿人民币,约2.2万亿美元,对外贸易顺差也达到1019亿美元,与2004年相比增长699亿美元,外汇储备更是达到创纪录的8189亿美元。然而,中国的其它一些数据则给人们敲响了警钟。按照美国官方的计算方式,2005年中国对美国的贸易顺差已经接近中国GDP的10%。换言之,中国经济现在严重地依赖美国市场。同时,在中国巨额的外汇储备中,三分之二以上为美元资产,黄金储备只有600吨左右,占全部外汇储备的比例仅为1.4%。而美国黄金储备超过8000吨,占全部外汇储备的60%以上。德国黄金储备3433吨,法国2945吨,意大利2451吨,均为上述国家全部外汇储备的50%以上。一旦发生美元急剧贬值的局面,中国的外汇储备将大幅度缩水,中国经济将遭受沉重的打击。

  正像斯蒂芬•罗奇所指出的那样,中国把自己与一个可能是发达国家中最不稳定的经济大国拴在了一起,把自己不成熟的金融体系与一种可能是世界上最不稳定的货币拴在了一起。美国对其经常账户进行大幅度的调整只是一个时间问题,因为它必须实现对外贸易的重新平衡。为了实现这个平衡,美国必须减少消费,美元要进一步贬值。同时,随着美国国会内部贸易保护主义压力的上升,特别是在今年面临国会中期选举的局面下,美国政府刚刚宣布成立对华执行办公室(China enforcement office),其主要的职责是监督中国遵守国际贸易条款的情况。刚刚发表的2006年《总统经济报告》中更是明确指出中国的汇率制度是造成中国以及全球经济不平衡的部分原因,并表明美国将继续向中国施加压力让人民币升值。

  如果真的出现美元急剧贬值,人民币急剧升值的这种局面,这对中国经济意味着什么呢?

  与许多从结构的角度做出的关于中国出口以及经济增长可能受到影响的分析不同,本文侧重于揭示国际金融秩序与国内经济制度在美元与人民币强烈互动的条件下对中国经济的影响。

  下面我首先说明以美元本位制为代表的现行国际金融秩序造成金融风险的机制。然后以本的经验为例说明,为什么一个具有重视协调轻视监控企业治理模式的生产大国可以在布雷顿森林体系下实现经济的高速增长;而在实现浮动汇率和全面开放资本账户后,作为一个拥有对美贸易巨额顺差的生产大国在一系列的国际国内政治经济互动中走向了泡沫。最后,我来分析一下中国经济在未来的十年里存在哪些可能制造经济泡沫的因素。

  本文的结论是中国在决定放弃“模拟布雷顿森林体系”时务必认清以美元本位制为基础的现行国际金融秩序具有制造金融风险的内在机制。一旦与这个秩序全面接轨,公共政策环境与企业治理两个层面的连锁反应将可能为大规模金融危机的出现提供十分有利的环境。

  美元本位制和美元危机的可能性

  自2003年夏季以来,美元危机就已经成为了一个引起国际关注的焦点。两方面的指标显示美元危机的现实可能性正在日益增加。第一,美国的贸易逆差以每小时一百万美元的速度增长。2002年以来,美国贸易逆差数字连续四年破纪录,去年7258亿的数字比前年增长了17.5%,占GDP的比例也由5.3%上升到了5.8%。第二,据理查德•邓肯研究,美国对其他国家的负债早在几年前就已达3万亿美元,相当于30%的美国国内生产总值,并仍然以每年5%的速度增长。根据经济学的原理,这种局面无法长期维持下去,必然要靠美元的大幅度贬值来恢复正常。过去数年,美元走软,仅2003年一年,美元对欧元、日元、英镑和瑞士法郎的汇率都发生了大幅度下跌。随着美联储在2005年不断提升利率,美元资产重新受到国际资本的青睐,美元汇率在2005年下半年有所改善。但是,随着格林斯潘时代的结束,以及美国贸易赤字、财政赤字和个人储蓄的全面恶化,美国政府的金融政策很有可能要朝着让美元贬值的方向发展。

  面临日益增加的美元贬值风险,各国纷纷开始采取措施应对。日本早在2003年就花掉1888亿美元,在2004年的头两个月里又花了1000多亿美元,干预外汇市场以防止日元以更快的速度升值。而俄国在2003年十月一个月就换掉了价值55亿美元的外汇,其中对欧元的需求增长了42%。2005年底中国有关部门负责人关于中国将进一步优化外汇储备的货币结构和资产结构,继续拓宽外汇储备投资领域的发言更是被国际金融市场一致解读为中国将要抛售美元资产。如果各国中央银行真的抛售美元资产的话,这不仅将大大地削弱美元作为关键货币的地位,而且将大大地削弱美国吸引外资的能力。由于美国国内储蓄严重不足,一旦无法有效吸引外资,难免出现严重的经济衰退。这将对全球经济有重大的影响,严重倚赖美国市场的中国出口将受到沉重的打击。

  为什么美国能拥有如此巨额的贸易赤字和债务?它和美元本位制有什么样的关系?为什么美元本位制包含着巨大的金融风险?

  要想回答这些问题,我们必须把现行的美元本位制与历史上的金本位制和布雷顿森林体系下的美元-黄金本位制进行比较。

  金本位制是在英国霸权主导下,支撑上一波全球化浪潮(1870-1913年)的国际金融秩序。在金本位制下,黄金的跨国流动直接与各国商品的相对价格相连。进口国等于是用黄金来支付进口。由于一国的黄金储备有自然的限制,该国只有靠出口来增加黄金储备。当黄金流失到一定程度时,该进口国的物价必然下降从而使得该国的商品在国际市场上的竞争力上升。同时,原来出口国的物价必然上升从而使得该国的商品在国际市场上的竞争力下降。因此,黄金的流动在动态过程中维持各国货币在等量黄金购买力方面的汇率,并防止各国的国际收支发生严重的不平衡。

  布雷顿森林体系是在美国霸权主导下,支持以生产和贸易扩张为标志的战后全球化第一阶段的国际金融秩序。它在20世纪70年代初的崩溃使战后的全球化过程进入了以金融和财政扩张为标志的第二阶段。布雷顿森林体系采取的是美元-黄金本位制。在这种体制下,各国的货币与美元以固定汇率挂钩,美元则与黄金以固定的价格挂钩。美元取代了黄金成为各国储备的硬通货。各国的中央银行可以随时用美元向美国兑换黄金。由于战后的西欧国家与日本均面临美元短缺的困境,无力购买美国产品。美国政府通过马歇尔计划向西欧盟国提供了大量援助。同时美国允许美元对西欧国家货币及日元的汇率长期低估。另一方面,美元的国际关键货币的地位使得美国政府可以大手大脚的花钱打越南战争和实施大规模的社会福利计划。到了70年代初,美国面临着巨额贸易逆差和财政赤字,无力再用黄金换回各国持有的大量美元。尼克松不得不于1971年宣布停止用美元兑换黄金。

  理查德•邓肯用“美元本位制”来描述取代布雷顿森林体系的现行国际金融秩序。在金本位制下,各国储备是黄金。在布雷顿森林体系下,各国储备的是美元和黄金,但美国必须储备黄金。在目前的美元本位制下,各国储备的是美元资产。而美国却用不着再储备黄金用于国际支付,尽管它仍然是世界上最大的黄金储备国。美元本位制与金本位制和布雷顿森林体系相比一个最关键的不同在于现在被各国大量储备的美元不受任何类似于黄金的实物支持。在金本位制和布雷顿森林体系中,没有任何政府或企业可以凭空造出黄金来平衡国际收支逆差。在美元本位制中,他们却可以造出各种金融工具来达到这个目的。正因为如此,这个特点就成了美元本位制孕育金融风险的出发点。

  这个美元本位制在第一次石油危机后的石油美元循环中被制度化。众所周知,石油危机始于石油输出国大幅度提高国际市场原油价格,由1970年的1.8美元一桶涨到1980年的39美元一桶。仅1978年一年,这些石油输出国就赚取了1335亿美元。这些国家无论如何努力也无法通过进口等价的商品来平衡他们的国际收支。于是他们把大量的石油美元用于购买美国政府发行的债券。其结果是,石油美元一方面导致了美国巨额的贸易逆差,另一方面也通过购买美国政府债券向美国经济提供了资金。这就是所谓的石油美元循环。

  大卫•斯派罗指出,石油美元循环的产生不是市场力量作用的结果,而是美国的霸权政治使然。当时美国面临的挑战是想尽一切办法为急剧增长的经常账户和财政赤字融资。同时,随着石油价格不断上浮,国际金融市场陷入随时崩盘的危险境地。现实的解决方案或是说服石油输出国把它们巨额的石油美元投资美国,或是让美国人大幅度增加储蓄。美国政府的解决方案是靠卖债券把石油美元引回美国来填补巨额的经常账户赤字和财政赤字。它在1974年与沙特阿拉伯签署了一份秘密协定,让沙特的中央银行通过拍卖市场以外的渠道购买美国政府债券。几年后,美国政府又同沙特签署了另一份秘密协定,使石油输出国继续用美元标价。这两个秘密协定均违反美国向其他发达国家做出的不采取单方面行动的承诺。通过石油美元的循环,美国顺利地渡过了两次石油危机的冲击。

  由于美国不用储蓄就可以借债,美国积累了大量的对外债务。由于国际石油市场用美元计价,各国必须把美元作为外汇储备的关键货币。由于美元作为各国外汇储备关键货币的地位,对那些拥有对美贸易巨额顺差的国家来说,如果它们的中央银行把这些美元换成本国货币拿回国内势必造成严重的通货膨胀。因此,它们都把这些外汇储备用来购买美国的政府债券、企业债券、股票或房地产。倚仗美元作为各国外汇储备关键货币的特殊地位,美国没有任何先储蓄后消费或先生产后消费的负担,完全靠政府和私人的举债来消费。美元本位制使美国变为世界上最大的债务国,并积累了大量的贸易逆差。

  在现行的美元本位制下,各国外汇储备的关键货币——美元的背后没有黄金的支持。美国政府债券的背后也没有美国国内储蓄的支持。这种只由纸币而不是黄金支持的信用创造经常导致全球范围内以经济过热和资产价格暴涨为特征的信用泡沫。理查德•邓肯指出,自从70年代初以来,美国的经常账户已经积累了3万亿美元的赤字。当这3万亿美元的货币进入对美贸易顺差国的银行系统时,这些国家就开始了一个制造泡沫的过程。商业银行开始疯狂地扩大信贷,经济空前地繁荣,股票市场和房地产市场价格以及企业利润急速上涨。由于每个产业都可以获得低息贷款,它们都建造了多余的生产能力。当投资和经济成长异乎寻常地加速时,资产价格的泡沫就会不可避免地出现。邓肯预言,由于大部分泡沫化的信用都要成为永远无法追回的不良资产,这个由美元纸币搭起的经济大厦早晚要坍塌。它将再一次提醒世界各国,为什么几千年来人类总爱用黄金而不是纸币来作为保值的最佳手段。到国际金融危机真的出现的时候,世人将看到目前中国的600吨黄金储备与美国超过8000吨的黄金储备将会给两国经济带来什么样不同的后果。

  2003-2004年中国经济经历的经济过热无非是美元本位制在对美贸易巨额顺差国制造信用扩张的又一个实例。这为中国关于人民币汇率政策的讨论提供了一个重要的新视角。

  我曾经指出,中国至今为止的经济增长在很大程度上借助于一个“模拟布雷顿森林体系”。中国一直没有开放资本账户,并且从1994年起人民币与美元挂钩,汇率一直比较稳定。尽管在人民币美元汇率在2005年夏季开始小幅浮动,并在2006年初出现了迄今为止浮动的高峰,但这与把货币的汇率完全交由市场决定的浮动汇率制还根本无法同日而语。

  这个“模拟布雷顿森林体系”为中国的出口提供了一个十分有利的环境。中国经济历经亚洲金融危机和美国泡沫经济的破灭而没有受到重大影响的一个主要原因是中国严格限制资本账户上的资金流动。近年来,国际上有人主张人民币升值,有人主张人民币实现浮动汇率,有人主张中国开放资本账户。这些主张的实质是要求中国取消这个“模拟布雷顿森林体系”。这个“模拟布雷顿森林体系”对中国经济到底有多重要?下面我们可以通过日本的经验来看一看像中国这样一个具有重视协调轻视监控企业治理模式,并拥有对美贸易巨额顺差的生产大国,如果全面开放资本账户并实行浮动汇率的话,会有什么样的后果。

  布雷顿森林体系与日本经济的高速增长

  在20世纪90年代经济泡沫破灭之前,日本模式一直是一个经济奇迹的代表。日本经济在1956-1973年期间保持了平均每年9.6%的增长速度。从这层意义上来说,中国经济这二几年来的高速增长与日本在1973年以前的情形十分相似。

  我曾经指出,日本经济战后高速增长的重要原因在于布雷顿森林体系下的固定汇率与限定资本自由流动,以及美国与其冷战中的盟国在关税贸易总协定的框架下的不对称合作(asymmetric cooperation)。这不仅允许日本政府运用扩张型的金融政策与紧缩型的财政政策搭配来追求以出口导向为特征的经济发展,而且为日本产品提供了一个稳定和可预期的国际市场。在这种环境中,日本以重视协调轻视监控为特征的企业治理模式推动了日本企业和银行在投资与借贷两个方面过度竞争(excessive competition)。过度竞争的结果导致日本的投资率远远高于其他国家,这种长时期的大规模投资支撑了日本经济的高速增长。

  日本经济中重视协调轻视监控的过度竞争机制是如何运作的呢?

  在布雷顿森林体系下,一国的财政金融政策不受他国财政金融政策的影响。这是当年凯恩斯代表英国政府与美国谈判建立布雷顿森林体系时一再坚持的原则。其着眼点是为英国战后建设一个福利国家留下一个政策性手段。日本政府则利用了这个政策手段来为它出口导向型的发展战略服务。战后的美元短缺以及东亚的冷战局面促使美国在与其盟国的国际贸易中实行了不对称合作,即美国向日本全面开放市场,与此同时允许日本对美国产品关闭市场。在这样一个国际环境中,摆在日本面前的任务是如何最大限度地动员国内有限的资本在高附加值的新兴产业里建立比较优势,并通过不断扩大国际市场占有率来刺激经济的发展。

  与西欧的福利国家依靠政府的财政政策刺激经济成长不同,日本政府主要依靠金融政策来刺激经济增长并平衡国际收支。战后初期日本政府仍然延续其战时的扩张型财政政策来刺激经济恢复,美国政府提供的援助填补了日本政府巨额的预算赤字。后来美国不愿继续承受这个负担,于1949年命令日本政府健全财政彻底消除预算赤字,这就是有名的“道奇路线”的重要组成部分。“道奇路线”的实施把日本经济立即带入了萧条。为了减轻“道奇路线”的打击,日本政府开始实施扩张性的金融政策。到60年代中期为止,日本政府一直采取扩张型金融政策和紧缩型财政政策的搭配(policy mix)。

  在扩张型金融政策环境中,日本银行向主要都市银行提供稳定的资本借贷信用。日本金融体制的特点是中央银行通过对都市银行的资本借贷信用来控制货币的发行量。这既有别于德国的以主要银行对借贷的控制来影响货币发行量的模式,也有别于美国的以资本市场为主的模式。日本银行支持经济成长的方式与通产省以产业政策刺激经济成长的方式不同。日本银行侧重于保证稳定的资本提供,而通产省则侧重于向战略产业分配资源。尽管这二者互相依赖,但各自按自己特有的逻辑运作。

  日本银行的扩张型金融政策直接导致了日本都市银行的信贷扩张从而助长了企业在投资方面的过度竞争。每当都市银行向企业提供的贷款超过它们拥有的存款额,它们就向日本银行申请借贷信用。由于都市银行发放信贷不受自有资金的限制,于是它们争先恐后地向大企业提供贷款。从理论上来说,日本银行应该根据私人银行的资本动员能力来提供借贷信用。但在实践上,日本银行在向都市银行提供资本借贷信用时采取一视同仁的态度。日本银行在日本经济高度增长期间扮演着两个相互矛盾的角色。一方面,它通过提供资金支持经济增长。另一方面,它又必须对私人银行的财会健全负责。为了刺激经济成长,日本银行应该在私人银行需要资本的时候提供借贷信用。但是,过于容易获得的借贷信用又削弱了日本银行规范私人银行的能力,日本银行在向都市银行发放信用时的平均主义间接地削弱了都市银行对企业金融的监督。

  大藏省在银行业实行的护送舰队式(convoy administration)的管理体制也助长了私人银行的信贷扩张。在控制银行的投资风险方面,美国政府注重事后的处理。它向每个银行账户提供十万美元的保险,但并不对私人银行的日常事务进行干涉。与此相反,日本政府注重事前的防范。大藏省对私人银行的日常运作严加干涉并严格控制新银行的产生。当私人银行面临破产的风险时,大藏省积极为其寻找兼并对象。日本政府认为,由科学技术进步推动的经济增长需要大量的投资。为保证资本的有效提供,政府必须减少银行投资的风险。如果银行经常破产,它很可能使存款人对银行失去信任,从而导致整个银行系统崩溃。护送舰队式的管理体制固然维持了存款人对银行的信心,保证了银行业的稳定,并通过提供一种稳定的商业环境支持了经济增长。但与此同时,它也鼓励了私人银行在向企业盲目扩大信贷。

  通产省在它的产业政策中培植竞争寡占(competitive oligopolies)的做法也刺激了日本企业之间的过度竞争。通产省在20世纪50年代到60年代中期严格控制外汇。它在分配有限的外汇额度时固然歧视中小企业,但对大企业却采取了平均主义的态度,经常按现有生产规模来分配外汇额度。很多西方学者在批评日本产业政策时经常把日本政府错误地想象成一个用国家权力直接将资源分配给一个主要培养对象的社会主义计划经济下的政府。而实际上,通产省十分重视战略产业中大企业的国际竞争力。它从不在一个产业只扶植一个企业,而是同时扶植几个大企业,让他们通过在国内市场上的竞争而加强他们的国际市场竞争力。这种竞争政策的后果是日本企业在投资方面的竞争十分激烈。

  在微观层面上,日本也发展出一系列与企业治理有关的制度与机制来适应布雷顿森林体系下以扩张型金融政策发展经济的宏观环境。

  到70年代末为止,间接金融一直是日本企业融资最主要的方式。间接金融指企业以银行贷款为主要资本来源。这与主要依赖发行股票与债券的直接融资形成了鲜明对照。银行贷款占1958-1974年间日本企业融资总额中的68%~83.3%。间接金融的好处在于填补了企业对资金的需求与家庭储蓄之间的鸿沟。由于日本企业在50年代与60年代一直面临着资金短缺的压力,它们不得不从商业银行贷款。为确保资本来源,日本企业与银行保持着十分密切的关系。尽管间接金融强化了日本企业与银行之间的协调,却严重地削弱了股东对企业的控制。当企业有了银行的贷款时,就没有必要向股东提供较高的分红率了。当股东的地位被削弱后,管理者在企业决策方面的独立性就必然增强。这种独立性经常促使日本企业在进行投资决策时过度竞争。

  企业融资时的主银行制度是另一导致过度竞争的机制。主银行是指向一个企业提供最大额贷款的银行。在日本,主银行通常持有该企业的股票,并为向该企业贷款的其他银行进行所谓的委托监督(delegated monitoring),即代替其他债权人对该企业的财会健全进行监督。有一种很有影响的观点认为,日本的银行系统在经济高度增长时期能够有效地监督企业。近来两位日本学者的研究却证明这种观点有很大问题。他们发现,在1955-1960年间世界银行向日本的十一个钢铁投资项目提供了40%~50%的贷款。作为贷款的条件,世界银行要求日本的钢厂保持1比1的负债资本比例。这些钢厂纷纷通过日本劝业银行申请延期实现这个比例。世界银行先后派了几个代表团去日本,提醒日本的钢厂不要建造过量的生产能力,但遇到了日本钢厂强烈的抵抗。日本方面坚持认为,在经济快速增长时厂家必须建造过量的生产能力以便占领市场。世界银行于1962年将这些日本钢厂实现健全企业财会的期限延长到1967年。日本方面于1965年再次申请延长期限,并提议将负债资本比例改为2比1。世界银行不得不接受了日本方面的建议。这表明,当时不仅日本的主银行没能监督日本的企业,就连世界银行也没能履行监督的职能。

  企业之间的相互持股是另一个削弱监控的机制。相互持股本来是防止上市公司被人在股票市场恶意收购的企业策略。50年代初,财阀解体后原属于三菱财阀的阳和房地产公司面临被一个个人投资者在股市上恶意收购的危险。原三菱旗下的企业认为这是一大威胁。它们决定共同出资将阳和房地产公司买下来。随着反垄断法在1953年放宽,其他企业纷纷效法这种做法,形成战后日本企业相互持股的第一波。

  60年代末,在外商投资自由化的浪潮中,丰田汽车公司率先将自身股票的50%卖给与其有生意的大银行,另外的10%卖给与其有生意往来的大钢铁公司以及丰田自己的子公司。丰田认为,如果美国的三大汽车公司只是来日本设厂,丰田有足够的竞争能力生存。如果它们利用其资金方面的优势通过股票市场把丰田恶意收购,丰田则根本无能为力。其它汽车公司也纷纷效法丰田的做法。这形成日本企业相互持股的第二波。

  相互持股导致了日本企业股东结构的深刻变化。1950年,日本上市公司股东的60.3%为个人,23.7%为机构。到了1973年,个人股东的比例下降到32.7%,而机构股东的比例则上升到60.4%。相互持股显然完成了防止上市公司被恶意收购的使命。但与此同时,它也制造了一种人质效应(hostage effect)。既然机构股东之间相互持股,它们的利益就紧紧地绑在一起。因此,它们没有必要去监控这些公司。你要监督别人,别人就要来监督你。人质效应大大地增加了管理者的自主权,为投资方面的过度竞争打开了方便之门。

  终生雇佣制也导致日本企业强烈的增长意识。西方关于终生雇佣制的研究一般侧重于这种实践对雇员忠诚心、干劲或工作满意度的影响。但经常忽视终生雇佣制对企业治理和企业投资行为的影响。终生雇佣制对企业治理一个重要的影响是将管理的优先目标从为股东获得更多的利润转向公司全体成员的生存。由于战后日本没有一个有效的劳动力市场,雇员们无法为了获得更多的利益而轻易地跳槽。他们只能在本公司的内部劳动力市场(internallabor market)竞争。为了减轻雇员们长期在同一个工作环境中积累的与他人之间因竞争而产生的紧张与矛盾,日本的公司必须不断成长以便为雇员提供更多的提升、加薪和加奖金的机会。在日本,一个企业在社会上的地位经常取决于其大小,而非是否拥有健全的财会。

  上述的各种在布雷顿森林体系下发展出来的公共政策与企业治理的制度和机制,有力地支持了日本各大企业集团在经济高速增长期间大而全的一体化投资战略(the one-set investmentstrategy),即在所有的新兴战略产业里以过度竞争的形式全面投资以占领未来市场竞争的制高点。由于大企业可以很容易获得银行的贷款,它们争先恐后地扩大设备投资与技术引进。50年代后期和60年代前期正是战后科技革命的高潮,新兴产业不断涌现。日本的企业集团信奉熊彼特的技术创新理论,在石油化工、汽车、家电以及合成纤维等新兴产业实行产品生产各个阶段的全面整合。它们既注意保证原材料的供给,也注意占领产品的市场。为了避免单一产品市场波动造成的损失,它们在同一企业集团内部实行多极整合,全面投资各相关产品的生产能力。

  简言之,日本经济的高速增长离不开这个在投资与借贷方面的过度竞争机制,而这个过度竞争机制本身是日本经济体制在战后初期适应布雷顿森林体系和关税贸易总协定过程中发展出来的。

  布雷顿森林体系下的固定汇率和限制资本自由流动从两个方面有力地支持了日本经济的高速增长。第一,它们允许日本政府用自己的金融手段有效地实现国内经济增长的目标。在70年代以前,由于国际收支一直是日本经济增长的瓶颈,日本政府严格地根据外汇储备来调节国际收支的平衡和经济增长之间的矛盾,并较少地受蒙代尔-弗雷明三维悖论(Trilemma)的影响。如果没有固定汇率,日元必然很早就升值从而大大地削弱日本产品在国际市场上的竞争力了。如果没有限制资本自由流动,当日本政府提高利率以便为经济降温时,国外的短期资本为了获得较高的利润必然流向日本从而增加日本的外汇储备和通货膨胀的压力。当日本政府降低利率以刺激经济增长时,大量资本为寻求更高的获利机会将流出日本从而使经济局面恶化。第二,固定汇率和限制资本自由流动将因重视协调轻视监控的企业治理模式而放大的金融风险限制在一个很小的范围。像亚洲金融危机显示的那样,在资本自由流动的条件下,短期资本的大笔流入和流出对一国经济的起伏有重大影响。换言之,大笔流入的短期资本使已经过热的经济更热,而大笔流出的短期资本可以把一个正在增长的经济体推入严重衰退的深渊。在这个过程中,重视协调轻视监控的企业治理模式会进一步加大投资的流入量。如果没有固定汇率和限制资本自由流动,日本恐怕早就遭遇大的经济泡沫了。

  美元本位制与日本经济泡沫的产生

  当布雷顿森林体系被美元本位制取代后,美元本位制形成泡沫的机制在以重视协调轻视监控的企业治理模式为基础的经济体制以及蒙代尔-弗雷明三维悖论的作用下进一步放大。

  从70年代末期开始,作为拥有对美国最大贸易顺差的国家,日本开始直接面临美元本位制下国际政治经济互动给自身经济带来的风险。第一次石油危机后,美国经济在巨额的贸易赤字和财政赤字中挣扎。为吸引外国资本来弥补本国储蓄的不足,美国采取了高利率的政策。这吸引了大量的外国资本向美国流动,由此产生的强烈的美元需求使美元变得坚挺。坚挺的美元意味着其它货币的疲软,这有助于各国向美国大举出口。其结果是美国的国际收支平衡进一步恶化,对外贸易逆差剧增。在这种背景下,一些美国的经济学家于1985年初提出,美国经济已无法继续支持坚挺的美元与严重的国际收支不平衡,美元应该尽快贬值10%~20%。否则一旦国际上出现突发事件,美元将面临重大危机。如果投资人纷纷抛售美元资产,美国经济的硬着陆将不可避免。在布雷顿森林体系下,美国国内的利益集团很少对政府的国际金融政策施加压力。这种情形在80年代迅速改变。从1979年到1985年美国国会平均每年有65个关于金融政策的议案或法规。到了1985年后期,美国国会有7个关于汇率的法案。这就是1985年美国策动的由五个发达国家的财政部长在纽约缔结广场协议(the Plaza Accord)的政治经济背景。

  读者不难看出,中国自2003年夏季以来面临的国际政治经济局面和日本在1985年时相比,十分相似的地方包括美国拥有巨大的贸易逆差和财政赤字,美元随时有急剧贬值的可能性,而中日两国拥有巨额的对美贸易顺差,两国都面临着来自美国要求本国货币升值的政治压力。尽管美国政府最近在汇率的问题上不像前两年那样急于向中国政府施加高压,但随着2006年国会中期选举的展开,可以预见美国国会在汇率问题上必然要加强对行政部门的压力。

  让我们按照时间的顺序来看看日本在回应美国的压力时犯了哪些错误。

  当时美国向日本提出的要求涉及开放农产品市场、扩大内需、加强反垄断法、金融自由化以及参加多国间政策协调。从当时日本国内的政治局面来看,其他几项要求均直接威胁构成自民党权力基础的既得利益集团的利益,只有金融自由化和参加多国间政策协调不仅面临的反对少,而且有很多的支持者。日本于是决定回应美国在这方面的要求。不幸的是日本的回应伴随着几个致命的误判。理解这些误判对今天的中国来说有着直接的现实意义。

  日本的第一个误判是认为一个生产大国可以自然成为一个金融大国。随着当时日本成为世界上最大的债权国,世界上10个最大的银行里有8个是日本的银行,日本有很多人认为日本必将取代美国成为世界上的金融霸权。基于这种认识,日本大藏省由少壮派官员组成的国际派极力主张日本的金融业与国际金融秩序全面接轨。1985年的广场协议并不是美国强加给日本的,这个协议从一开始就是由日美两国的高级官员共同秘密策划的。当时日本的政治家与主管金融政策的大藏省少壮派官员想的并不是什么金融风险,而是日本如何通过这个协议增加本国在世界上的政治影响力,并在不远的将来取美国而代之,执世界金融之牛耳。在这种心态下,日本选择参与多边国际金融政策合作想向世界表明日本愿为国际经济秩序的稳定做出贡献。

  亚洲金融危机以后,日本有人认为广场协议完全是美国要搞垮日本经济的阴谋诡计。说美国在国际上推动金融自由化和缔结广场协议是为了本国的利益固然不错,但说这完全是美国的阴谋则有欠公平。因为美国政府的政策目标从一开始就十分明确。广场协议这一多边国际金融政策协调就是为了通过这些国家中央银行对外汇市场的干预将过于坚挺的美元强行贬值以减少美国的贸易赤字。这充其量也只是一个阳谋。当时日本作为对美贸易顺差第一大国面临美国巨大的政治压力不假。但这并不等于日本必须让步。实际上,日本在八十年代对大多数来自美国的关于开放国内市场的要求都是能拖就拖或根本置之不理。关键是日本自己在众多的美国要求中出于想成为世界金融霸主的目的选择金融这一项来满足美国才吃了大亏。

  第二个误判是不了解蒙代尔-弗雷明三维悖论的效应。根据这个悖论,一国政府在稳定汇率、维持货币自由兑换和国内经济政策目标这三项政策目标中同时最多只能实现两项而不可能实现三项。日本在当时的国际政治经济条件下不可能选择停止日元的自由兑换。因此,日本政府等于是已经选择了三项政策目标中的一项。剩下的选择是要稳定的汇率还是要能调控国内经济局面的手段。日本政府在履行广场协议的过程中两次受到蒙代尔-弗雷明三维悖论的困扰。先是为了让美元贬值导致日本经济衰退,后是为了从衰退中脱身让美元升值结果导致泡沫的出现。1985年的广场协议并没有设定各国中央银行在外汇市场进行干预的任何特定的目标汇率。在广场协议前,美元与日元的汇率大致上是1比243。起初日本政府期待美元对日元比价降到1比220。当美元日元汇率变为1比190,日本大藏大臣仍然说可以接受。美元日元汇率持续升至1比180时,汇率开始影响日本的出口。经济景气开始恶化。到了1986年,日本全体产业的企业平均利润率下降3.1%。其中,制造业下降22.2%,与出口有关的产业下降42.9%。

  日本政府为了刺激经济复苏被迫开始采取扩张型金融政策去刺激经济成长。从1987年2月起,日本银行也数次降低利率。最后的利率只有2.5%,是到当时为止的史上最低。从1986年2月到1988年1月,日本银行还不断抛出日元买美元以求降低日元对美元的比价。这两者加在一起导致在1986和1987两年的货币提供超过正常水平的1.4%和1%。过量的货币提供意味着金融市场充斥着大量的资本,为泡沫的产生创造了宏观层面的条件。实际上,中国2003年以来面临的是与日本很相似的蒙代尔-弗雷明三维悖论的困扰。两者都是当政府想要维持一个特定的汇率时就失去了控制国内经济局面的手段。

  第三个误判是放弃扩张型金融政策与紧缩型财政政策的搭配而把两种政策同时变为扩张型。这进一步刺激了泡沫的形成。从1979年以来,日本政府为了减少预算赤字,一直奉行每年政府预算减少10%,政府投资减少5%的原则。1987年7月,日本首相中曾根康弘为了下届选举不得不同意增加政府开支。日本政府将上述的两个百分比都变成零。同年5月,日本政府还决定增加6万亿日元额外的政府开支,这相当于日本国民生产总值的1.8%。与此同时,东京都圈内三个大型城市发展计划直接地导致了土地价格急剧上涨,并使拥有土地的上市企业的股票价格随之上涨。

  第四个误判是不了解一个重视协调轻视监控的企业治理模式在美元本位制和蒙代尔-弗雷明三维悖论的双重夹击下,会出现企业纷纷从生产领域转向服务业以寻求新的利润机会的局面。这是泡沫经济的微观基础。在强势日元的条件下,日本产品在国际市场上的价格上涨,利润空间下降。生产作为营利的手段不像原来那样有吸引力了。各大企业集团纷纷把目光转向有较大获取利润机会的服务业。在1986年,东京股票交易所上市的1888家公司中有371家已经进入新的生意领域。每个公司平均有3.9个新的生意项目。很多建高尔夫球场的企业采取预售会员权的办法集资。更有人以买卖高尔夫球场会员权为金融投机的手段。在泡沫经济的高峰,一张最贵的高尔夫球场的会员证可达3.5亿日元。一个英国记者感叹道,在日本买一张高尔夫球场会员证的费用在英国可以盖一个高尔夫球场。在一家房地产开发商预售一个有252个单元的大型高级公寓的第一天,8500个买主蜂拥而至。由于建筑公司无法跟上这种强烈的市场需求,有的不得不靠抽奖来决定谁能买上一个高级公寓。其它的商品,像退休保险、宝石和著名画家的画纷纷变成了企业和个人金融投机的手段。

  很多在世界上有名的日本企业也纷纷加入这种金融投机。1987年,日本有的蓝筹股公司的税前利润来自于金融投机的部分与来自该公司本来业务部分的比值,在本田汽车公司为26.1%,索尼为62.8%,三洋电器为134.2%,五十铃为1962.4%。由此可以看出,当用于生产的资本大量进入服务业时,出现泡沫经济的风险随之剧增。如果2003年人民币与美元的汇率已经改为浮动制,这次美元贬值必然导致中国出口的急剧下跌,并使很多企业的投资由生产领域转向消费领域,去年的房地产业过热就会远远不止已经达到的水平。

  与开放资本账户有关的金融自由化大大地增强了日本企业在国内外金融市场上融资的能力,却严重地削弱了日本中央银行对经济宏观调节的能力,同时也大大地增强了日本企业面临的金融风险。1986年之前,日本企业靠发行股票与债券融资的总量为4万亿日元。在泡沫经济中,这个数字暴涨到1989年的26万亿日元。仅仅在1985-1987年间,日本企业就增发了多达二百亿股的股票。当大企业纷纷靠直接金融融资时,中小企业则变成了银行追逐的借贷对象。在1985-1990年间,中小企业总资本中银行贷款的比例由30.7%上升到39.8%。这为日后泡沫经济的破灭留下了一大伏笔。更重要的是,日本企业在金融自由化后大举参与金融投机。从1983年3月至1985年6月,日本企业用于金融投机的特殊金融信托资产由9000亿日元增长到40000亿日元。

  从以上分析可以看出,一国的汇率体制与美元本位制全面接轨后将产生一系列公共政策与企业治理层面上的连锁反应。同是一个重视协调轻视监控的企业治理模式,在布雷顿森林体系下可以支持经济的高速增长,在美元本位制下却制造了一个巨大的经济泡沫。

  为什么生产大国并不等于金融大国?

  许多主张人民币升值、实行浮动汇率和开放资本账户的依据是中国已经成为或正在成为世界工厂。作为一个世界上重要的生产大国,中国已经有了成为一个金融大国的资本。从日本的经验来看,为什么一个具有重视协调轻视监控企业治理模式的生产大国难以变为金融大国呢?这个问题要从企业治理中协调与监控的悖论关系上来回答。

  在现有的学术文献中,协调和监控一直属于两个不同学术领域的研究对象。交换成本经济学(transaction cost economics)专门研究协调的问题。而工业组织代理人理论(agency theory of industrial organization)专门研究监控的问题。在很长一段时间里,这两者之间似乎没有什么必然的联系。美国经济学家、哈佛大学商学院教授卡尔•凯斯特把协调与监控这两个过去分别研究的问题第一次看成是企业治理面临的一个悖论中相互矛盾的两个侧面。他指出所有的现代企业都必须解决两个问题,一个是不同企业在生产过程中为建立和保持契约交换关系而产生的协调问题,另一个是与产权和管理分离有关的监控问题。

  凯斯特的另一重大贡献是指出受到不同历史条件的制约,一国企业治理模式的发展通常是侧重解决这两大任务中的一个。其结果是一个长于协调的企业治理模式经常弱于监控。反之亦然。换言之,协调与监控是一个矛盾的两个侧面。加强协调往往导致削弱监控。因此,美国的企业治理以高交换成本低代理人成本著称,而日本的企业治理模式则以低交换成本高代理人成本著称。我曾经把凯斯特的这个理论历史化,通过引入国际金融秩序和公共政策环境这两个变量在战后的变化来解释为什么日本在布雷顿森林体系下实现了经济的高速成长而在美元本位制下走向泡沫。

  一个具有以重视协调轻视监控为特征的企业治理模式的生产大国很难成为金融大国的原因,在于一个金融大国的企业治理模式必须重视监控。金融大国的根本在于资本运作,而资本运作中最重要的一环就是防范风险。离开了一个强有力的监控体系,金融大国的银行业会很快因为不良资产发生危机。纵观历史,国际上的金融大国都是像美国和英国这样的有着长期管理国际金融秩序经验的前霸权国或现霸权国。这里边的道理是由于金本位制和布雷顿森林体系要求英镑美元与黄金挂钩,如果这些国家的银行不被严格地监控,本国的黄金储备很快就会被掏空。一旦霸权国的金融体制出现问题,整个国际金融体系可能都会因此而崩溃。日本在八十年代中期犯的错误是试图参与一个它自身除了资金以外在其它方面并没有任何比较制度优势的国际金融游戏。日本的经验对中国的启示是一个拥有大量的金融资产却不善于管理金融资本的生产大国,决不能简单地把巨额的外汇储备或贸易顺差等同于成为金融大国的唯一重要条件,而忽视一个金融大国必备的制度条件。在这种认识下轻率地参加国际金融游戏是十分危险的。

  避免中国经济泡沫的出现

  以上我们讨论了为什么现行的美元本位制是一种充满风险的国际金融秩序,并以日本的经验为例说明为什么一个重视协调轻视监控的企业治理模式在这种金融秩序下面临着巨大的风险。现在我们回到中国的议题上来。

  参照日本1980年代的经验,我们可以预见,“模拟布雷顿森林体系”的命运将是中国在国际政治经济的互动中即将面临的最大挑战。随着中国日益成为世界工厂和国际资本流动的主要目的地,中国经济与其它国家经济的利益冲突日益增加。在这个全新的国际政治经济环境里,人民币的汇率必然成为各主要贸易对象国关注的焦点。特别是随着中美之间的贸易往来进一步深化,美元本位制对中国经济的影响将会大大增加。随着中美之间经济摩擦的增加,在未来两国的互动中,经济因素会变得越来越重要。在这样的状况下,美国可能不仅会要求人民币升值,而且还会要求人民币实现浮动汇率和开放资本账户。

  作为对美拥有巨额贸易顺差的生产大国,中国不仅会在贸易顺差和外汇储备激增时丧失货币政策对经济进行宏观调控的有效性,而且会直接进入与美国的政治经济互动。当一直支持中国经济高速增长的“模拟布雷顿森林体系”不复存在时,中国政府对本国经济进行政策性调整时的自主性将严重下降。与经济学家讨论蒙代尔-弗雷明三维悖论时很少涉及国际政治的因素不同,在现实生活中拥有与美国贸易巨额顺差的国家必然像日本那样受到来自美国的政治压力,而被迫在蒙代尔-弗雷明三维悖论的三项政策目标中进行选择。

  如果我们假设“模拟布雷顿森林体系”可能被取消,与日本相比,从现在算起的未来十年里有哪些潜在因素可能使中国经济产生泡沫呢?

  第一,在财政方面,受2008年北京奥运会、2010年上海世博会、开发西部和振兴东北等大投资项目影响,中国扩张型财政政策的规模以及它们的影响可能要比日本当年要大得多。当然这里面有一个两国经济发展阶段不同的区别。但是,从1997-1998年的亚洲金融危机来看,即使是发展阶段较低的泰国、印度尼西亚和马来西亚等国,在盲目扩大投资之后仍然无法避免金融危机。

  第二,在金融方面,如果在未来的十年里,中国对美国的贸易顺差仍维持在高水平上,人民币面临的来自美国的政治压力只会进一步加强。如果人民币继续盯住美元,可以想象中国对美国的贸易顺差将近一步扩大。除非大幅度增加进口,否则人民银行将面临巨大的增发基础货币从而导致通货膨胀的压力。

  同时,与日本十分恐惧外资相反,中国大力鼓励外资的流入。各国资本向中国流动的趋势恐怕只会加强而不会减弱。如果中国在未来的十年里开放资本账户,开放的程度与速度将决定中国在多大程度上面临亚洲金融危机时各发展中国家经历的大笔短期投资撤出所造成的风险。

  第三,与日本的1986年相比,中国政府对通货紧缩的忧虑和就业的压力要远远大于当时日本在广场协议后面临的压力。这很容易使得决策部门在把握为经济降温的时机时犹豫不决,从而提高出现泡沫的几率。

  前边说过,扩张型的金融政策必须要靠紧缩型的财政政策来平衡,反之亦然。否则当金融和财政政策都变成扩张或紧缩时,就会出现通货膨胀或通货紧缩。

  第四,尽管在具体的制度和机制上与日本不同,中国的企业治理模式在重视协调轻视监控这个基本原则上却与日本一样。产权关系不清,行政干预银行的融资决定,以国内生产总值为考核官员政绩的标准,以及腐败都严重妨碍监控职能的行使。一旦有信贷扩张的机会,这种企业治理模式必然加速经济泡沫的出现。

  与80年代的日本相比,中国缺少两个有利条件。第一,尽管中国拥有巨大的贸易顺差和外汇储备,却远远没有在高附加值的产品上建立起国际竞争力。在这点上,中国可能更像亚洲金融危机前的东南亚国家而不像日本。日本与东南亚国家的区别在于同样经历了泡沫经济的破灭后,日本仍然能靠在高附加值产品上的国际竞争力保持经常账户上的巨额黑字,而东南亚国家的情况却要差得多。换言之,中国在经济的家底儿方面远不如日本,因为高附加值产品的国际竞争力直接与一国在多大程度上可以承受一个大的经济倒退相关。第二,目前中国只是刚刚开始还远远没有能够真正解决发展过程中出现的日益严重的收入不平等问题,而日本早在60年代后期就已经很好地解决了这个问题。中国在面临经济危机时政治的稳定性方面远远不如日本,因为不平等的程度直接关系到一国在经历一个由泡沫经济破灭引起的经济衰退后,在多大程度上可以避免大的政治动荡的问题。这两方面的条件使中国在处理关于未来人民币汇率走向的国际国内政治经济的互动时处于比日本更为不利的状态。

  实际上,中国经济受到国际金融秩序的严重影响从2003年以来经济过热的过程中就可见一斑。尽管受到非典的影响,中国经济2003年的增长率仍然高达9.1%。这个高速增长的主要驱动力来自超乎寻常的投资增长。2003年全社会固定资产投资高达55118亿人民币,与去年同比增长幅度高达26.7%。银行信贷是2003年投资大幅度增长的主要动力。2003年中国货币的供应量处于前所未有的宽松状态。当时国内的讨论更多地集中在中国经济是否过热,而很少人注意为什么中国的投资和银行信贷会在2003年大幅度增长,以及认清银行信贷大幅度增长的原因对我们正确对待中国经济的内外环境有什么作用。

  中国经济2003-2004年超乎异常地加速增长是一个典型的代表现行国际金融秩序的美元本位制在与美国拥有巨额贸易顺差的国家制造泡沫的实例。2003年的中国济局部过热显示当弱势美元导致中国对美贸易顺差突然剧增时,中央银行为维持稳定的汇率被动地靠大量增加货币投放来吸收外汇储备,这新增的货币投放直接刺激了银行的信贷扩张,再加上政府仍然在担心通货紧缩,未及时加大政策调整的力度,经济的几个部门开始出现过热现象。而伴随着贸易顺差的增加以及外汇储备的上升,2005年全社会固定资产投资已达88604亿元,比上年增长25.7%。过去这两三年的经验显示当中国经济与世界经济体系紧密相连时,他国的金融政策开始对中国经济产生重要影响。我们应该从现在开始全面正视国际因素给中国经济带来的风险。作为世界上的最大经济体,美国2003年的汇率政策无论是在美国国内还是国外均产生了重大影响。

  首先,弱势美元导致了美国贸易赤字的急剧增加。美国政府的大规模减税在2003年刺激了美国经济的复苏。经济复苏使美国消费者信心大增,美国人又开始大手大脚地花钱。同时,为给2004年的总统大选作准备,美国政府在2003年一直采取弱势美元的政策。这样做的目的是增强美国产品的国际竞争力,扩大出口以便在美国制造更多的工作机会,从而为布什赢得选民的支持。美元在2003年与日元和欧元的汇率分别贬值17%和14%。一方面,经济的复苏刺激了进口,另一方面,弱势美元使美国进口时的费用增加,结果是美国2003年的贸易赤字达创纪录的4894亿美元。

  其次,弱势美元使中国经济在两个方面受益。第一,由于人民币与美元挂钩,当美元贬值时,人民币与其他货币的汇率随之贬值。这大大增强了中国商品在美国以外的国际市场上的竞争力。第二,由于中国是美国的主要贸易伙伴之一,美国经济复苏导致美国购买力的增强使得美国从中国进口了更多的商品,中国对美贸易顺差达到了空前的幅度。这两个因素加在一起的结果是中国2003年的出口比前一年增长近40%。中国的外汇储备也增长了40.8%,达到了前所未有的4033亿美元,与2002年相比多出了1900多亿美元。

  2003年新增出来的1900多亿美元的外汇储备导致了中国货币发放大幅度增加、银行信贷的急剧扩张以及中国经济的局部过热。由于中国实行外汇管理,出口的企业必须把赚取的外汇在指定银行兑换成人民币。为了兑换这1900多亿美元,如果按照大致1美元兑8.2元人民币的汇率计算,中国的银行就不得不增发超过1.5万亿的人民币基础货币。这使得中国的银行系统被突然注入了大笔可供发放信贷的资金。2003年中国的银行发放的新增贷款达到3万亿人民币。比2002年多出了1.2万亿。可见,2003年新增的外汇储备额大于新增的银行贷款额。我们有理由推测,中国经济2003年非凡的增长与美元汇率有很强的相关性。这绝不是说中国经济2003年发展的推动力都来自美国,而是说美国因素至少是中国经济2003年与往年相比超速发展的原因之一,即为什么中国的银行信贷急剧扩大。

  如果上述推论可以成立,美元汇率与2003年中国经济过热的关系就引出一个重要的问题,即当中国还没有在国际压力下取消“模拟布雷顿森林体制”的时侯就已经面临此等风险,如果这个体制真的马上消失了,等待中国经济的命运会是什么样?

  2004年以来,中国政府开始进行宏观调控,并在一些方面初见成效。但是,尽管如此,中国作为一个贸易出超的大国在美元本位制主导的国际金融秩序中仍然面临着出现经济泡沫的风险,而且这种风险随着中国贸易顺差的增加只能进一步加强。我们看到,尽管在2005年宏观调控的状况下,中国人民银行广义货币供应量(M2)余额仍然高达29.88万亿人民币,同比增长17.57%,而央行年初的政策目标为增长15%。货币供应量上升的一个主要原因就是由于迅速扩大的贸易顺差导致了外汇占款的增加。而且,也是在宏观调控的条件下,2005年中国全社会固定资产投资在前两年高速增加后的较大基盘上又增长了25.7%,增幅的回落只有区区0.9%。这一切都是在美元与其他主要货币汇率在2005年下半年相对坚挺这一特定国际环境下的结果。如果2005年美元大幅度贬值的话,那无论是中国的广义货币供应量还是全社会固定资产投资总量恐怕都会远远超过现有数字,由此中国经济将会面临更大的泡沫风险。

  2003年的中国经济过热与国际上出现的要求人民币升值的压力一起向中国敲响了警钟。美元本位制给中国经济带来的风险之所以过去没有引起人们的关注是因为无论是中国对美贸易顺差,还是中国外汇储备购买的美元金融资产,都还没有达到一定的程度,而这种情形如今已经发生了本质性的变化。过去几年的经验显示,即使在中国不取消“模拟布雷顿森林体系”的条件下,中国巨额的对美贸易顺差也会导致国内基础货币的增发,从而增加引发经济泡沫的压力。中国用巨额外汇储备购买了巨额的美元资产。如果美元一旦大幅度贬值,中国拥有的美元资产就会蒙受巨大的损失,出口将受到毁灭性打击,经济成长将遭受重大挫折。根据理查德•邓肯的分析,中国在2001年对美国的贸易顺差大致等于中国国内生产总值的7%。而同年中国经济的增长率为8%。也就是说,如果没有与美国的贸易顺差,中国经济的增长率要大为下降,甚至变成零增长。前边提过,2005年中国对美国的贸易顺差(按美国官方的计算)大致接近中国GDP的10%。而同年中国经济的增长率也为10%左右。因此,中国经济面临的风险不仅仅是简单的美元贬值,而是美元大幅贬值导致的国际金融秩序混乱,以及这种混乱对中国经济的打击和由此引起的政治不稳定。

  有人可能会说到目前为止中国在美元本位制下不是一直是受益多于受害吗?不假。

  美元本位制对中国来说既是挑战也是机会。重要的是中国之所以受益多于受害,其最根本的原因是中国政府用自身政策创造出来的“模拟布雷顿森林体系”一直保护着中国经济。目前种种关于人民币汇率的讨论以及国际压力的目标恰恰是要取消这个“模拟姆雷顿森林体系”,逼着中国开放资本账户和实现人民币的浮动汇率。从日本的历史经验可以看出,如果实行了浮动汇率和全面开放资本账户,一个具有重视协调轻视监控的企业治理模式的生产大国将会在不知不觉中被推向一个现行国际金融秩序中暗藏的漩涡。它可以在几年之内先被推向顶峰,然后再被抛向谷底。

  从某种意义上来说,中国现在面临着一个两难局面:如果不想办法减少与美国的贸易顺差将间接加剧美元的危机。美元危机一旦爆发,中国经济将蒙受其害;但是,如果中国以在短期内人民币升值,浮动汇率,或开放资本账户的方式来减少这个顺差,中国经济很可能马上就会遭到打击甚至引发政治的不稳定。因此在讨论人民币汇率政策的未来走向时,不应该头疼医头脚痛医脚地讨论,而是应该具有前瞻性地放在国际金融秩序和全球化的大背景中来分析,并将关于浮动汇率和开放资本账户的立场一并考虑。应该避免的是对外部压力做出单纯的反应来变动汇率,而对全局缺少整体把握。

  换言之,为了追求长期可持续的和平发展,中国必须从根本上调整本国经济与国际经济秩序的关系。从这层意义上来看,及时地通过解决国内以各种形式存在的经济不平等,从而有效地扩大内需已经成为中国经济在近期内转型中最为紧迫的任务。这是一个与时间的赛跑:如果中国在国际经济秩序出现重大危机之前就完成这一具有深远历史意义的转变,不仅会减轻本国经济对国际市场的严重依赖,减少与此相伴的风险,并通过进一步增加进口为世界经济的增长提供动力,而且还会实现全国人民共同富裕的理想。在这个过程中,中国应该尽可能地延续“模拟布雷顿森林体系”的存在,而用其他手段来减少贸易顺差。这种延续决不是一味的拖延,而是为实现这一转变赢得宝贵的时间。

  同时,中国也必须珍惜赢得的每一分钟,真正地下大决心来促进这一转变。否则,中国也有可能像日本在1971-1989年间那样,先是本国的无作为间接地促进了国际经济秩序中危机的出现,而在危机发生后国际政治经济互动的过程中,又被拖进一个处处被动应急式的反应过程。到头来,不仅会像日本那样以泡沫的出现和破灭的形式来承受经济方面巨大的打击,更有可能面临连日本也未曾遭遇的国内政治冲突和社会矛盾急剧增加的风险。

  为此,我们必须清楚地认识到生产大国与金融大国的重要区别,万万不能被世界工厂以及二十一世纪是中国的世纪等赞誉冲昏头脑。君不见,国际上在日本泡沫经济崩盘之前关于日本即将取代美国的预测以及在亚洲金融危机之前关于二十一世纪是太平洋世纪等泡沫期的意识形态不是在短短的几年里就烟消云散,再也无人提起了吗?

- 作者: cyberfan 2006年03月7日, 星期二 20:16  回复(0) |  引用(0) 加入博采

创业者如何组装一部赚钱机器  (作者置顶)
如果说企业是一部赚钱机器,那么企业专业经理人就是一个熟练的机器操作工人,而创业者则是一个在赚钱机器生产线工作的装配工人,创业的工作是把机器设计好,把机器安装好,让它成为一部可以操作的机器,让未来的经营者能按操作守则操作机器。一个是驾驶员,一个是制造者,两种人所从事的阶段性工作不一样,因此他们的工作内容也不一样,一般说来,设计制造机器者工作难度较高,而操作者可通过专业训练便可取得相关技能而成为一个成功的操作员。

虽然说装配工人日久之后也有可能变为驾驶员或操作员,但他们本身目前的工作性质是不同的,因此所要求的培训也不同,工作的重点也不同。经营者注重的是营运管理,策略方向与把舵的能力,利用机器的操作创造出利润。而创业者注重的则是依物之本末,事之终始,以及知其先后的能力,循序渐进地创造出一部赚钱机器,创业者强调的是先后秩序,循序渐进,避免拆掉重装,走回头路。

组建一部赚钱机器与学习驾驶之道需要不一样的知识与技术,对处事之先后顺序的要求也不一样。创业者在组建赚钱机器的过程中,要认识的是机器的组建过程,其先后顺序以及组建原则而不是它的操作方法与技巧。以下我们将这一部分工作分为几点详述。

机器设计力求简单易懂

这部机器的设计应该力求简单、易懂,把机器的构想简化,只做有用的,有意义的事,舍远求近,去繁存易,不做无关,无谓的大小事,做实事,不做虚事,不求虚名,对自己诚实,专注创造价值给客户,不随便花钱,不注重排场,投资者的钱不是收入,每用一元投资者的钱每年至少必需赚回0.2元,而且一元仍存在。这一部分看似老生常谈,其实是创业者非常重要的认识。

按部就班组装一部机器


组建的过程必需有先后优先级,并依循序渐进的策略,一步一步地扎根,根往地下扎有多深多广,地上的树叶就有多大多宽的茂盛。

因此创业必需是一步一步地走,就如同组装一部机器,一定是按部就班,零件、螺丝钉都有先后之分别,倒置不得。一有错乱,机器就装不起来,必需拆掉重装,错误的组装,即使勉强装成也无法顺利运作。

激活中心点

创业者必需选中一点,真正的一点点做为事业的起点,这一点必需很精准,一点则通,以英文来描述,这一点就是Missing Link,就是Sweet Spot,万事俱备,只欠一点,当创业者把这一点做成之后,整个解决方案就完成了,由于这一点是解决方案的一环,因此完成之后,它便立刻产生经济价值,这个经济价值就可以为创业者带来收入,微软的Gates创立微软之后很快地为Apple计算机写了Basic的程序,并创造2万1千美元的收入,之后他又快速地把应用软件卖给Apple又得到7万8千美元,这就奠定了微软健康发展的基础,因此激活点的选择非常重要,必须是可以快速产生现金的工作,为整个创业过程带来动力。

产品、市场、客户三位一体

创业者的第一个产品(或者服务)应该有明确的价值,对某特定市场的客户群,它是一个优先的选择,正因为创业者所有精力与资源都集中到创造与传递这个产品上面,它对客户应有无比的吸引力,因此在创业之初,产品、市场与客户应该是一体的,而且也唯有当它们是一体的时候,这个初创事业才能创造收入。其间比喻犹如对号锁,每个号码都对了,锁才会开,同时也是,只要号码都对,锁一定开,新创业的的产品、市场、客户组合犹如对号锁必需都正确,才会创造收入。只要一个错了,它就失败,因此努力把三位弄成一体,确定完全正确就变成创业者无比重要的工作,切忌拿着答案找问题,一厢情愿的作风是失败的保证。



一般说来,产品是针对某特定客户群创造价值的,它有针对性,所以能够打动那一群特定的客户,因此能够完成首轮的商业循环。

创业之初只要快速完成商业循环,便能获利,这是为什么大部分成功企业第一年都能获利的原因。第一年就获利是创业成功的保证。
第一年就该获利

这一句话是指态度、思考逻辑,而不是指财务报表,由于对自然事物的认识沟通困难,所以用比较简单的语言来描述创业的态度及思考逻辑。创业第一年就该赚钱的意义是(1)不可期待别人投资以延续创业的生命,寻找别人的投资,只能适用于大规模的资本密集企业,对一般创业者是不适用的。(2)把现金流量看成比技术的突破还重要,创业者有个心理取向,总是认为只要技术有所突破,钱的流动不是问题,这是错误的观念,应该说,技术的改造与突破必需在产生利润的条件之下完成才有意义,不可一厢情愿,相信技术第一,前苏联之所以会垮,是垮在经济上面,它的科技正是世界一流的,但它的科技都没有能带来利润,因此对后续的科技发展就显得虚弱无力。光有科技缺乏一个经济体系将科技转化成利润,这类的科技无法长久持续发展,没法持续发展的科技,就像断线的风筝,去了不会再回来,自然也就没用了。(3)把经营范围控制在可获利的范围之内,避免好大喜功,一步登天的伟大企图心,把事与物分先后顺序,由本而末,自始至终,不可跳跃,同时要有第一年就获利的期待,第一年就要获利的要求将约束创业者,谨慎行事,避免跳跃式的风险。(4)创业者本人必需承受第一年的所有开销也就是创业者的责任要自负,由于责任要自负,将会培养创业者对钱的正确观念。这样,创业成功之火才会点燃。

因此任何创业者有责任回答“第一年如何获利”的问题,有了第一年的获利,才有基础谈论扩充,才有资格提议要求别人的投资。

人的事业不可寻求外来投资

事业创造价值是由资源产生的,资源包括硬件与软件,硬的部分是指厂房、设备、土地,而软的部分则包括人力、组织、执行力,知识经济时代,以人为本的软资源的事业越来越多,也越来越多新创事业是以人为本,利用人的智能、组织与人力,来创造利润,这一类事业所需固定硬件资本越来越少,它完全依赖人的智能、执行力以创造价值,因此为了要取得平衡,第一年的投资,创业者应自行掏腰包才能平衡。资源的有效使用以及人的效率,所以以人为本的事业不可寻求外来投资应以创业者个人资本来投入,这样才会最大程度地保障创业者与投资者双方的利益。




需要外来投资是完成第一年之后需要设备扩张的时候才可,接受外来投资必须有明确的资本回报率,如此才可以接受投资。

回报率应高于放款利息

创业者对资源成本应该有观念,切勿漠视,钱是有成本的,创业者要时时刻刻体念到“钱”的可贵,每投入一分钱,必需争取到适当的回报,以人为本的事业每年应该获利,获利能力应该是以人为本软资源投入的1.3倍才可以算是有效率,如果达不到这样的水准,即表示这样的经营模式实际上浪费了经济资源,若每一位员工的平均产出值是1.3倍时,它的结算结果应可高于贷款利率,否则就是拿钱倒贴,那就不算创业了。

定位要清晰,有舍有得

天天重复自己的定位陈述才不会迷失,每家企业都必需清楚定位,定位是帮助企业把资源投放在最有效率的地方,有定位才有方向,才有根据,如此才不会迷失。

一般创业者遇到最大的挑战是急于立功,凡是上门的顾客都要,有时碍于情面,有时受害于无知,不敢说“NO”。创业者必需清楚,客户有些是会让你赚钱的,有些是会让你亏损的,要正确区分这两类客户,只有选择好客户,婉拒坏客户,企业才会赚钱。因此定位就非常重要,定位才会让资源集中,提高效率,才会促进成长,成长之后,就必需重新定位,然后才能再成长,如此循环不己。

定位包括(1)产品(2)价位(3)地区(4)价值链(5)通路(6)累积的核心能力。




在定位时,必需明确化所要服务的客户,依特定目标客户群制定产品的定位,以及对它的价格,至于对价格或产品线以外有所需求的客户则应谢?,不可动摇企业之本,要固本,本固而利生,同时对企业本身在价值链中的定位也很重要,企业很容易受到诱惑不知不觉地增加上下游的工作,结果价值链定位不明,效率低落,自然无法赚钱。

对通路与地区采取无目标式地扩充,通常是新创事业初尝胜绩之后的狂妄行为,有些新创事业刚刚尝到一些胜利,容易冲昏了头,误以为自己英明,于是亳无章法地扩充地区与通路,结果必得其反。

而累绩核心能力应该是初创事业创造竞争力的利器,把核心能力定位之后,企业才能累积它的竞争力,才能把后来者拴在后头,并筑起进入障碍以及追赶障碍,保证企业的可持续成功。

事业是越简单越容易赚钱,事业简单易懂,不但顾客易懂,连员工也易懂,因此效率会提升,吉列刮胡刀、可口可乐就是例子,它们的技术都受到专利保护,而营利模式都是很容易了解,可口可乐产品单纯,行销则通过三个通路,零售、酒店、超级市场,三大通路,每一个通路的营利模式虽各有异,但却容易执行,所以它们都获得很大的成功。

另外新创事业不可要求外人投资,以自有资金创业成长的公司,根深蒂固,方能长久永续发展,微软、戴尔、威名百货都是属于这一类的公司,它们都是靠一点一滴累积然后茁壮成长,到今天立于不败之地,永续经营,它们也都是第一年就获利的公司。

定位要清晰,Intel定位清晰,Intel定位自己为PC芯片公司,所以芯片事业非常成功,它也曾试图制造PC板,以及系统组装,但都以失败告终,所以集中资源在定位范围之内,才能达到高效率使用资源,才能获利。

创业者组装一部“赚钱机器”,要仔细咀嚼以上八点创业成功之见,反复深思,相信本能,回归基本,不急不缓,按部就班,一点一滴累积,不断完善事业策略,自然就会成功。

- 作者: cyberfan 206年03月7日, 星期二 12:58  回复(0) |  引用(0) 加入博采

仁爱与博爱:中西文化析论三二  (作者置顶)
“博爱”的呼声遍天下,却已很少再听到“仁爱”之名!“博爱”虽然不是西方文化的专利,更不是基督教的专利,但是,当我们在使用“博爱”这个名词的时候,常常不自觉地把它与西方文化联系起来,认为它是一个从西方引进的美好词语。我们去赞美“博爱”,这没有什么问题;但是,当我们赞美“博爱”的时候忽视、轻视、贬低了“仁爱”,就不能不说是一个问题了,因为于此相伴随着的,是失去对华夏文化的自尊和自信!
华夏文化讲不讲“博爱”?《论语》中说“泛爱众”,这不是“博爱”吗?《墨子》中说“兼爱”,这不是“博爱”吗?《周易》中说“厚德载物”、“生生之谓易”,这不是“博爱”吗?《老子》中说“生之畜之,生而不有,为而不恃,长而不宰”的“玄德”,不是“博爱”吗?但是,华夏文化不单纯讲这种“博爱”,因为这只是“推爱”的必然结果,却不是途径。
大家都知道“老吾老以及人之老,幼吾幼以及人之幼”,却不可能说对自己的父母和别人的父母付出完全同样的爱心,对自己的子女和别人的子女具有完全相等的爱心。孔子说:“非其鬼而祭之,谄也。”我们要祭祀我们自己的祖先,而不是去祭祀别人的祖先。如果有人不祭祀自己的祖先,却去祭祀别人的祖先,那么,这个人不是谄媚别人是为了什么呢?同样,如果一个人不孝敬自己的父母,不爱自己的子女,却去孝敬别人的父母,爱别人的子女,那么,他的心态能算是正常吗?因此,爱是有层次的,而不可能是完全相同的。
由对自己父母的孝敬到对别人父母的孝敬,由对自己子女的爱心到对别人子女的爱心,需要一个“推及”的过程,没有这个“推及”的过程,就会有两种情况:一种是只孝敬自己的父母而不尊重别人的父母,只爱自己的子女却不重视别人的子女,这是自私的“孝”与“爱”,要知道,我的父母、子女对于别人来说,都是“别人”的,都这样的话,谁的父母、子女还能得到别人的尊重和爱护呢?另一种是对自己的父母、子女完全相同的对待,这样可能做到吗?妻子对公婆的孝敬和对父母的孝敬、丈夫对岳父母的孝敬和对父母的孝敬,两者能一样吗?孝敬父母是发自内心的真诚,孝敬公婆或岳父母则是出于理性的修养。
如何实现这个“推及”的过程?第一,“己欲立而立人,己欲达而达人”;第二,“己所不欲,勿施于人”;第三,“亲亲而仁民,仁民而爱物”。
对第一条,大家都很熟悉,但是,要看我们是否努力落实到自己的行动中去了。每个人都会有自己的理想追求,在我们要实现自己的理想追求的时候,可曾想到过不要影响别人的理想追求?可曾想到过在有条件的情况下去帮助别人实现他的理想追求?我们常常看到的是,有的人为了达到自己的目的,有意地在背后污蔑、损害别人,这是卑鄙无耻;有的人为了害怕别人超越自己而影响自己的目的,在能帮助别人的情况下也不帮助,甚至在对方遭遇不幸的时候幸灾乐祸,这是自私自利。卑鄙无耻的人,会遭到他人的唾弃;自私自利的人,不会有真朋友。
对第二条,大家更熟悉,可是,要做到这一条,比第一条难一些。遇到困难或危险的时候,自己避之惟恐不及,却把别人推到前面去;在需要担当风险的时候,自己推脱责任,却让别人去承担责任;看到别人为救天下而周游列国的时候,讥讽为“丧家之犬”,之后又对被大众奉为圣贤的人肆意侮辱。这样的人根本就没有仁爱之心,如何“推己及人”?这样的人,根本谈不到“先天下之忧而忧,后天下之乐而乐”,因为他是把自己不爱做的事专门让别人去做,甚至别人做了之后还要冷嘲热讽,或者夺人之功为己有。
第三条涉及到推爱的次序问题。《孟子·尽心上》说:“君子之于物也,爱之而弗仁;于民也,仁之而弗亲。亲亲而仁民,仁民而爱物。”也就是说,人对于动植物,只有爱惜之情,却谈不到仁心,因为仁是人与人之间的爱,所以说“仁者爱人”,对动植物的爱不能称之为仁。所以,动植物可以作为我们的食物,我们却要爱惜,不能为了奢侈享受失去节制,否则就是暴殄天物。官员对于百姓,必须有仁爱之心,却谈不到亲近,要保持适当的距离,因为一旦官员与民太亲近就难得保持公正之心,所以说“近之则不逊,远之则怨”,官员只要尊重百姓的人格和本性就可以了。子女对于父母,需要亲近,却不是说只是仁爱的问题,因为亲近之中包含仁爱,仁爱之中却不包含亲近,所以,父母与子女之间有着天然的关系,一旦这种天然的关系遭到破坏,那么,家庭也就破裂了,家庭的破裂会动摇社会稳定的基础。
有人会说:“如果只要求我去对别人仁爱,别人对我不讲仁爱怎么办?我的兄弟姐妹不孝敬自己的父母,我凭什么去孝敬?”华夏文化主张“正己”,就像孔子说的:“躬自厚而薄责于人。”要知道,我们用仁爱之心对待别人,别人才可能对我仁爱;我们对别人不仁爱,我们还有什么资格去要求别人对我们的仁爱?要知道,父母是我与兄弟姐妹一起的父母,但是,更是我的父母,当父母受苦受难的时候,是我的父母在受苦受难,如果我能孝敬父母,那么,我与兄弟姐妹的父母就都避免了受苦受难,所以,孝不孝敬完全在我,何必攀着别人呢?
《周易》的“元亨利贞”对应“仁礼义智”,这里所说的“仁”是“五常”之中的一部分,四者合起来则是“生生”的“大道”。孔安国《尚书序》中说:“三皇之书,名为三坟,言大道也;五帝之书,名为五典,言常道也。”“五常”之中的“仁”是“爱人”,属于“常道”,也就是“据于德”的“德”中的一个方面,在此基础上“推及”,便可以达到“大道”之中的“仁”的境界,这个“仁”便是“至仁”、“至善”,也就是“无为无不为”、“无私无欲”的“道”。“至仁至善”的境界,就是“博爱”、“兼爱”的境界,但是,如果有了“推及”的过程,它就是我们的理想境界;如果没有“推及”的过程,只谈“博爱”、“兼爱”,却会导致使人把自己的父母与别人的父母同样对待的“妄爱”、“滥爱”。
“仁爱”是由立足点同时又讲究推及于人的,“博爱”则没有立足点而空谈爱心的;“仁爱”是实实在在的,“博爱”则具有迷惑性。如果他们不爱自己的民族,莫非能真心去爱其他民族吗?他们对自己的父母不孝敬,莫非能真心去敬重别人的父母吗?他们对自己的子女不爱护,莫非能真心去爱护别人的子女吗?他们不关系自己的亲人,莫非能真心去关系朋友吗?他们对狗与猫特别关爱,关爱的程度超过了自己的亲人!不讲“仁爱”却讲“博爱”是我们选择文化的错误!

- 作者: cyberfan 2006年03月7日, 星期二 11:58  回复(0) |  引用(0) 加入博采

企业管理实践中的诡道  (作者置顶)
现在所有的管理学的教育与培训,大都是从人性的正面去寻求合理的解释和解决方法,给人的感觉印象是只要是好的、正确的、合理的、真的、善的、美的东西就一定会有用,其实完全不是那么回事,前面说的东西是结果,不是始更不代表过程。

  企业管理首先是要找对人、做对事,最终的结果是要把事做对、做好了,达到预期的效果。但在管理实践中管理者大都最愿意一步到位,尤其是在选人、用人上只知道要什么,不注重过程中必然的阶段、过程和方法。管理实践中的失败往往都是管理太执着于结果,而忽略了自己的和别人的起点及必然的过程,管理就象生孩子一样,必然要有十月怀胎、一朝分娩的过程。既要孩子又不愿意怀胎,岂能不荒唐。

  下面从本人多年来从江湖组织到组建非正式组织和正规团队的经验来加以阐述。

  一、拉班子、搭架子

  企业新建初期,首先是管理框架的建立,这时需要一些得力的人来帮助组建,但是这个时候企业往往都面临很少的选择,都是首先从家人、亲戚、朋友、同事、熟人等自己知道、的、了解的或信任的人群中来选择。

  创立企业是个无中生有的过程,不可能一下找到那么多需要的人才,需要有个事先积累、事后培养的过程,所以企业筹备的过程往往也是人才积累的过程,自上而下的设置好部门机构、物色合适的人选,并进行必要的分工。因为企业没有定形,团队也没有稳定,此时主要还是依靠开发者个人的影响力和号召力来凝聚一帮人,进行企业前期的组建和筹备工作。

  这一阶段,大家面对新公司、新项目,彼此间的感情、关系处于初次见面、请多关照的阶段,一般都不会有太大的问题,一切都由创始人安排、调遣和协调。

  二、招兵买马

  这个过程也是必然的过程,这个时候就要招聘中下层管理人员及员工,然后进行必要的培训、上岗,然后进行初创期的磨合。

  此时团队还是有形无核,任何部门和机构的核心力量都没有形成,但是随着时间的推移,这种核心的力量会慢慢形成,尤其团队中的非组织象旋涡一样慢慢形成,三个月内一定会形成由三、五个人为核心的非组织团队,并逐步扩大到外围。

  以十个人团队为例,很快就会有个三人小组形成,并很快带动三、五个人也许还有一到两个人会被冷落到组织以外。一旦到这个时候企业对团队的领导,已经不是原来的部门领导在起作用,实际是由非组织的领袖在起作用,部门领导其实只是个表面的发号施令者或者是指令的传达者。

  这是个非常关键的时候,如果企业决策者在这个的时候的态度和选择非常重要,如果半年以内,团队的非正式组织没有形成,以后这个团队就会先天不足,缺乏战斗力号召力,就象害了痨病一样。

  很多企业的经营者、管理者,总把非组织置于管理以外,甚至鄙视、打击、压制非组织,其实这是团队最大的损失。问题的关键是怎么把这支部队从地下转到地上,来发挥正面的作用,而不是总与正式组织相抗衡。

  三、去壳中层

  团队中的非组织经过一半年到一年的磨合,就会趋于稳定,非组织一旦成熟后,也就是正式团的管理者要下马的时候,否则这个团队要么处于涣散状态,要么就激烈的较量和内耗中。正式组织的领导者的能力与号召力,往往都不及非组织的领导者。

  企业在前期的拉的班子、搭的架子就是为了哺育这个新领导者的壳,就象鸡蛋的壳和哺乳动物的胎衣一样,一旦新生命的诞生就意味着它们的历史使命已经完成。

  企业初创期的团队或部门的负责人,在一年左右的时间必须全部换掉,改由非组织的领袖担任部门的负责人,最初的部门负责人仅仅是个灯泡,真正的目的是要孵化团队的新领导,取代他的领导,这很残酷,但没有经历这道工序产生的领导人,是没有凝聚力和号召力的而且越强势、越有水平、越有能力反对者的力量也越强大。

  所以企业在去壳的过程中一定要处理好,新与旧的关系,企业一定要重用的人,千万不要第一次搭建班子时就委以重任,应该在去壳前刻意将其培养成非组织中的领导者或核心成员,在去壳前委以重任,过早地委以重任,很快就会成为众矢之敌,会过早地毁灭其在企业的“政治”前程。

  原因很简单,企业早最初的部门负责人,会被所有的人误以是老板的人,本能地都会把他当作“敌人”,如果长期处于这种敌对状态,团队肯定会一团糟。

  这与管理者的个人品性能力没有太大的关系,仅仅是一个心理上的必然过程。很多企业的经营者管理者,太执着于自己看中的人,结果导致纷争不断,矛盾四起,搬着石头唱戏,吃力不讨好。非组织的领导人只要不被重用,一定是麻烦最大的制造者。江湖无所不在,企业也不例外,这没有什么道理好讲的。

  总之没有江湖经验的管理者绝对不是好的管理者,没有江湖经验的管理者,就象没有消防栓的大楼,管理能力危机管理能力是很脆弱和苍白的,这样的人其实就是个“管理太监”。

  四、请退高管

  企业在完成一定的积累进入稳定发展期后,如果要想继续发展就必须请退原始的企业高级管理人员,因为面临新的发展,原有的创业者就会成为企业新生力量的障碍,旧的不去新的就不会来。旧的管理体制或机制不除,新的管理体制和机制就不会形成,这对企业的决策者也是一种挑战,需要决心和勇气,往往也需要过渡或变通的方法来解决。

  维持现状不变,进行新的投资或者干脆异地投资是个斧底抽薪的办法,给予期权或股份成立分公司、派往外地等也是个变通的方法,总之公司的经营权、管理权必然全面移交到新的领导核心手上。

  同一代领导人之间仅仅是勾心斗角的竞争,尚能做到有理有节,不同代之间领导人间则往往是你死我活的决斗,隔代的领导人之间则往往充满着阴谋诡计。如果有三代以上领导人同在一个屋檐下,那这个企业一定会乱得一锅粥,决策者根本不知道下面发生了什么.如此企业不死才怪。所以公司的高级管理层的领导,必须一代一代地清理,以保持队伍的纯洁,留下的一定甘当绿叶的。如果一定要立地成王,只能把它赶出狼群,另谋高就。

  五、优胜劣汰

  员工合理流动的目的是为了保持队伍的凝聚力、竞争力和战斗力,优胜劣汰是放之四海而皆准的自然法则,团队必须毫不留情地定期清退最差的员工,团队的整体素质才能得到不断提升。团队也才可能不断获取向上的动力。

  在对待人的问题上既要严格更要人道,要做到统一标准,亲疏勿论;坚持原则,合理补偿。千万不要因一人、一事破坏规距。

  作为企业的领导者要善于拿自己人开刀,或者也做些类似于曹操割发代罚的把戏。

  六、约法三章

  企业最终能做大、做多强,不在于别人做的如何,根本的原因在于公司的决策者对自我的管理和约束,包括自己身边的人、家人,企业家在自我的成长和发展过程中,必须要找自己一定不能做的事,才能做好自己一定要做好的事。

  要信任职业的管理者和专业的管理人员,必然赋予关键部门的人以否决权,比如财务部门、人力资源部门、技术部门等等。财力不够的事不能做,技术力量不够的事不能做,人力资源不够的事不能做。

  人力资源的磨合期不能底于一年,临时招聘到一起的人员不能胜任大任,否则一不小心团队就会成为乌合之众。新的投资或项目必须以成熟的团队去经营,才会有好的结果。

  七、百年大业、树人为上

  优秀的企业家总是在创造财富与创造人才,企业的长远发展是生命力的传递,必须通过德才兼备的人来接力,所以企业家要善于当导演,而非总是抢话筒。在接班人的培养上,要重影响力的传承,而非位置的移交。培养和造就接班人,是企业家终身的使命,而非一时心血来潮。原则是:全面撒网、重点培养,先交学费,后予重任。

  没有经历过失败和挫折磨练的人坚决不能重用,人没有生而知之的,重用这样的人,意味着企业必须为他自负交纳昂贵的学费,甚至面临破产。所以要先交学费,后予重任。当然没有竞争也就没有动力,接班人应全面撒网、重点培养。

  如果一定要培养自己的子女为接班人,千万不要相信“龙生龙、凤生凤”的鬼话,管理必须从实战中来。培养自己的孩子就意味着要牺牲别人家优秀的孩子,要牺牲一个又一个的陪练,直到他的能力和水平真的超过别人家的孩子为止。

  八、白天不行善,晚上鬼敲门

  千万不要忘记每一个富人后面都有成千上百的穷人在盯着你,他们不用做什么,仅用冷漠的眼光就能把你送进心灵的地狱。

  在这个世界上不管你财富来得多么的合情、合理、合法,只要你拥有了比别人更多的财富,就意味着你在制造穷人,这是大自然不变的法则。

  财富本无姓,只是人在争,人人都是人,为何就你富?市场法则是丛林法则的继续,是兽性指挥人性的结果;在破坏地球、造福自己的壮举中,人类至今还没有找到自己的位置。人类不能等待上帝来分配财富,又不能放任自己永远处于你死我活的决斗中,于是就采用了市场经济的方法来挖掘和分配财富,市场经济仅仅是不得已的选择,市场经济中所谓的公平仅仅是人类自欺欺人把戏。当一个人拥有一辈子或几辈子都花不完的钱的时候其实已经是上当了,巨额财富对你来说也许是宝藏,但对你的子孙后代或在别人眼里可能就是个潘多拉盒。

  所以企业家要多积善行德,以修造功德。否则白天不行善,晚上鬼敲门。世界上只有穷人喜欢的富人才最安全,帮助穷人就是成全自己,起码不至于天天活在胆战心惊之中。

- 作者: cyberfan 2006年03月6日, 星期一 21:50  回复(0) |  引用(0) 加入博采

从“毛泽东热”看“中国式管理”  (作者置顶)
有时候,我们不得不说人类确实是很可悲,明明自己是在贩卖新酒,但是,还是要用旧瓶子把它包装起来。君不见,欧洲文艺复兴的思想家和艺术大师们,明明就是在倡导一种全新的文化,但是还要用古希腊来包装。到现在,中国管理学界明明是不满于现状,要构造全新的中国管理学,却要大张旗鼓地鼓吹什么“中国式管理”——用根本就不存在管理学的古老思想,经过一番炒作,名之曰“中国式管理”,好像这样就可以与西方管理分庭抗礼一样。真是可悲!

  时下流行的“东西南北中,处处宣讲毛泽东”,其实就是“中国式管理”的一种形式。感叹之余,我在思考这样一个问题:怎样才能本着“建设性”的原则为中国管理学的成熟做些有益的工作。既然大家都要谈“毛泽东思想”的管理学问题,我就从这个问题开始吧。首先,我必须声明,本人也是一个“崇毛者”之一,这里说讲的话也是怀着无比崇敬的心情讲的。但是,我还是要明确指出,“毛泽东思想”可以在管理的艺术层面给我们一些有价值东西,但其本身与管理学有天壤之别,不能简单套用。当前,中国迫切需要本着管理学的研究规范,在宣讲管理艺术的同时,把管理艺术有效地转化为管理技术。只有这样,才有可能真正对中国管理学的建设,推进中国管理学走向成熟,做些有益工作。

  (一)中国管理学走向成熟:时不我待

  总体上看,宣讲“毛泽东思想”在企业管理中的运用,把毛泽东在推动中国民族独立过程中曾经成功运用过的创造性思维、智慧和方法等等,在企业管理领域中宣讲、普及。对于企业界,尤其是企业的高层人士,有效思考企业理论、谋划企业发展战略、驾驭越来越不确定的市场竞争,在激烈的竞争中求发展,这些都是非常积极和有效的。但是,这种现象也反应了中国管理学一个令人悲叹的现实:管理学供给不足!

  也许马上就有朋友会反驳了:现在的书店占据书架最多的恐怕就是财经类、管理类的图书,怎能说是供给不足呢?看仔细些吧,这些图书中除了翻译、编译、编著以及一些“写作高手”妙手加工的图书之外,有几本是认真研究中国管理问题的;再看看吧,打开中国管理学最高的专业学术期刊《管理世界》,有几篇是真正意义上的管理学文章?再看看,财经类、商学院的期刊,除了研究生赶着满足答辩要求、教师和科研人员为了评职称所写的“学术论文”之外,没有几篇称得上是系统研究管理学的学术研究成果;学院讲坛上的老师最多也就是贩卖一些现成的知识加上一些案例;企业管理的咨询人士、管理实务者,倒是细心研究了不少的管理问题,在研究过程中也有一些创新。但是,他们哪有那么多时间和精力进一步升华、系统化和理论化呢?这样一来,中国管理学能不供给不足吗?当然,这种状况其实也是中国管理学走向成熟所必经的一个阶段。尽管这样,我们还是要心中有数:这是一个不正常的现象,它需要我们不断改进和发展。相反,不是简单地向老祖宗求救了事。

  其实,在中国经济和社会高速发展的今天,各种商业模式创新层出无穷,各种有效的管理模式也在积极尝试。实践证明,其中的一些尝试,效果还是很好的。为什么我们的学者就不能及时对他们进行跟踪研究呢?为什么就不可以把我们研究成果放在更长远的历史中考虑呢?作为一名管理学研究者,我感到这种现状确实可悲。回到企业管理的毛泽东热问题,研究者们为什么就不能系统研究毛泽东的思想能否在企业层面上运用呢?如何才能在企业管理层面上的运用呢?在企业管理的哪个层面可以运用而在哪个层面是不能运用的呢?为什么就不能坐下来认真研究,怎样才能把毛泽东的思想概念转化为可以操作的管理技术呢?需要一些什么样的相关程序、操作方法呢?进一步研究毛泽东思想的管理方法应该如何测量呢?所有这些都是规范管理学中必不可少的内容。如果按照规范的管理学研究方法考问,毛泽东的思想概念在企业管理层面运用还有不少问题需要进一步研究的。

  (二)“毛泽东思想”不是管理学

  记得,毛泽东在第一次接见尼克松时候,尼迫不及待要与毛探讨台湾问题,毛很风趣地说:这些具体问题让下边人谈去,我们不谈,我们谈哲学、谈思想、谈文化。这说明,毛泽东本人在管理和运作一个大国时候,他是非常清楚自己的思想,清楚自己思想作用的边界和条件,清楚由思想到行动所需要的各种相关技术,而且,这些这些技术有些是自己没有具备的,所以需要相关的人去谈——这是我们学习“毛泽东思想”时候不可以忽视的!

  不少人脑海里对建国后的旧电影印象很深。特别是一些抗战题材的电影,如《地道战》。电影里头的主人公在困难的时候,手捧着《论持久战》,电影的画面不断地闪烁着“战略上藐视敌人,战术上重视敌人”、“敌疲我打、敌退我追”等等。一些咨询培训师在讲述竞争战略的时候,开口闭口就是“四渡赤水、巧渡金沙、夜袭贵阳”等等,生动无比。其实,所有这些故事就像孙子兵法或者三国故事一样,充其量只是说是军事谋略而已。从中,我们可以得到一些有用的智慧启迪,但是,这些都不是管理学的主要课题。作为一个“崇毛”者,我以为,毛的智慧和谋略对我们企业管理最大的意义不在这里。相反,从撤出井冈山开始,能够审时度势地打出“抗日”这个牌子(用现代商业术语其实可以说是造概念),这个概念对于吸纳和整合国内外资源,从而为日后取得胜利——这是值得我们在战略管理中思考的一个重要问题。用德鲁克的话讲,就是基于事业的企业理论。相反,蒋某人在驾驭战略的高度、远见和胸怀方面,尤其是“概念”的号召力方面,就显得逊色多了。这也是他日后由盛而衰的关键一点。

  说到这里,我们可以这样客观地看待毛泽东的思想:如果说毛泽东的思想可以成为中国抗日致胜、反蒋获胜的利器,追根溯源的一点并不是说这种思想本身有多少值得管理者参悟的东西,如同对《老子》或者《庄子》参禅悟道一样。相反,毛的思想、文章和著作都是很平民化的,很容易阅读和理解,根本不需要这样到处宣讲。如果我们真要把毛泽东的思想运用到企业进行经营和管理,除了要深入理解毛泽东的思想概念之外,更为重要的是思考一下:毛是如何站在历史的最高点上提出自己的政治纲领?这对我们在企业战略管理中的使命定位、社会角色、价值创造定位等等,都是极有帮助作用的。我们不少的企业家觉得这些问题太大、太空了,对企业赚钱意义没有直接作用。其实,只要路子走对,利润只是自然的结果。正是这些“概念”决定了企业路子有没有走对?是你在引导市场还是市场在引导你?你所能够得到的资源是比竞争对手多还是少?说白些,你最后是成为毛泽东还是蒋介石,分水岭在哪里?就在于你的政治纲领是顺势还是逆势。顺之者昌,逆之者亡。

  (三)管理学不能停留在思想层面

  当然,我丝毫不认为说管理学就只能讲技术,不能讲艺术。相反,管理学(其实任何的经验科学其实都一样)最忌讳的是艺术和技术的分离。但是,在中国目前的形势看,我们尤其需要关注应该如何把管理艺术转化成为管理技术,包括,前面提到的传统思想文化精华,什么道家权变了、周易八卦、孙子兵法了、什么管子政治思想了、什么胡雪岩了等等等等,毛泽东的军事和政治谋略也不例外。当然,要把这些思想和方法转化成为管理程序和工具,谈何容易!

  其实,没有必要太过于悲观。事实上,我们许多企业的成功实践表明,他们已经在运用而且一直在运用中国文化,只不过是他们没有及时对之进行归纳和理论升华而已。我就不信,现在驰骋商海、位居企业重要位置的“老三届”们,他们在思考企业发展问题上:不懂得在确立自己企业的发展方向时候,如何使之更能够顺势、更能够吸纳资源和整合资源。这说明什么呢?说明他们不但身体力行了毛的思想精髓,而且有效吸收了古代的智慧(如大道无门,有容乃大)。然而,他们还不会就此停止的,他们深深明白一条:企业的思想、理念要实施,要转化为价值创造,要比竞争对手更能创造价值,他们还需要许许多多的人去执行。为此,他们需要把这些思想和理念转化为可以传播和考核的程序、工具。这就是管理技术范畴的问题了。

  那么,什么是管理程序和工具呢?回答这个问题不是三言两语,但是我们可以这样理解。管理就是能够让许多人按照同一目标做事,这就涉及到如何把这个目标有效传达给那么多的人,而不是象我们日常生活中一样去告诉他并监督他做好。这就要求一系列的管理分工,包括要有人专门研究这些目标如何确定、用什么样的方式传达目标、怎样测评工作有没有做好等等,这一系列的过程都需要人、组织、信息来完成。还有一个重要的一点,那就是怎样为之完成,怎样为之没有完成,即测评的标准。我们把上述这些过程简单称为管理活动,所谓管理程序和工具,简单而言就是确保管理活动顺利完成所需要的一些规则、规定和表格等等。有些人对这些琐碎东西不以为然,那就看你的组织要多大了。

- 作者: cyberfan 2006年03月6日, 星期一 21:50  回复(0) |  引用(0) 加入博采

由西门庆勾引潘金莲看营销谈判的过程技巧  (作者置顶)
谈判是一切为达成双边或多边一致的过程,谈判的行为包括其间的语言表达或其他——往往是最容易被忽略,而又往往是非常重要的——行为活动。达成一致的过程事实上就是谈判的双方或多方心理状态趋同的过程。
  谈判包涵的范围很广,除了一般所说的商业谈判、政治谈判,小孩向家长要求买玩具是谈判,教育学生改正缺点是谈判,甚至追求女孩子也是谈判。谈判具有强烈的目的性——达成目标,即达成双边或多边需求的一致。

  过程技巧是谈判的一个重要技巧。把握谈判的过程技巧,就是把握“双方或多方思想趋同”——这一过程中双方/多方的心理变化过程的技巧。

  谈判过程技巧的要决就是:对谈判的局势暨双方(或多方)心理状态的判断、把握和引导。  

  西门庆勾引潘金莲的连环计  

  读过《水浒》的人对西门庆与潘金莲这两个人物应该不会陌生,书中通过西门庆勾引潘金莲的情节,把谈判过程技巧之精髓演绎得淋漓尽致。从这个意义上分析,施耐庵可谓深谙谈判之道的行家。

  西门庆勾引潘金莲的计策是王婆定下的。这一计策,层层深入,滴水不漏,进可攻,退可守,可谓精妙绝伦!可以为我们学习谈判提供良好的范例(聪明的女人很可怕!)。

  王婆设计的十个步骤一个不能少,而且要绝对遵循其先后顺序(过程性)。十个步骤如同计算程序的流程,少了一个就得不出正确的结果(勾引成功)。王婆设计这一过程正好引导了潘金莲的心理逐步接受、合拍(计算出正确的结果——勾引成功)。不可操之过急,“心急吃不得热豆腐”;不可拖拉迟缓,否则错失良机。这十个步骤环环相扣,层层铺垫,步步为营,层层推进,真可谓经典!就算潘金莲无意,也不会落下轻薄的口实(“买卖不成仁义在”)。

  以下是王婆向西门庆口述的十步连环计:

  步骤一是潘金莲答应给王婆做寿衣。

  步骤二是答应在王婆家做。

  步骤三是第二天继续在王婆家做。

  ——先看这前三个步骤。这些步骤是通过由浅到深的过程来判断握局势,前后衔接,层层铺垫,同时这三个步骤又是为西门庆和潘金莲见面做铺垫,这是引导过程。

  步骤四西门庆和潘金莲见面了。

  步骤五步西门庆和潘金莲达上话。便是短兵相接,慢慢开始了,这是最重要(不相见/接触,如何勾引),也是最平常的步骤,精彩在后头。

  步骤六潘金莲默认答应一起吃饭。请注意:开始布局了。

  步骤七是王婆出门买东西,潘金莲和西门庆单独在王婆家。注意!这是一个非常重要的局势判断,也是一个重要的伏笔,同时也是很惊险的一步(若是潘金莲起身回家呢?当然,通过对局势的判断,王婆吃定了潘金莲)。

  步骤八是同桌吃饭。这一步骤相当关键,万不可当成是王婆和西门庆在闲聊,他们是在用闲聊的方式,完成了对潘金莲极其高明的叫卖。这一步骤的精妙在于运用“随风潜入夜”的手法达到了向潘金莲推销的效果,从而影响潘金莲的心理判断(相当于一个销售员对客户说:“我的公司很有信誉,我的产品一级棒。”)。

  步骤九是吃得正香没酒了,王婆去买酒,顺便把门也关了。这一步设计的很绝!相当于推销中的促成成交。这一步骤的精妙在于火候把握精准和简单有效的要求成交。

  步骤十是西门庆捡筷子时顺便捏了潘金莲的脚(请注意,这绝对不是顺便,而是精心设计,但戏一定要演得真,一定得装成是顺便,否则就没趣了)。潘金莲这时发话了,就象一些客户所说的那样——“你的东西我买了!”大功告成!!!

  所有这十个步骤全为这一句话!此时还不抓紧签单,又能做什么呢? 看看西门庆的反应——直奔目标。  


  现代追女孩的四部曲  

  西门庆与潘金莲是几百年前的人物,下面再以现实生活中追女孩为例说明谈判的过程技巧:

  小伙在街上碰到一位美女,一见钟情,想追其为妻,生子(这是谈判需达成的“一致”,即目的)。此时,小伙开动脑筋,拟订了追求的步骤(计划好谈判的过程)。

  第一步、结识

  “小青,你好” (其实,鬼才知道她叫什么名字)。当对方表示你认错人时,要有一点惊愕,尴尬,而且一定要有一点歉意和腼腆。“我们认识一下,好吗?”一副很绅士的样子,把电话号码骗到就算阶段成功。

  切记:此时,万万不可开门见山“小姐,我们结婚生小孩吧”(尽管这是你的出发点和最终目的)。若是这样,你能得到的只有一句“流氓”,外加一记响亮的耳光。

  第二步、交朋友

  找到一切可以找到的借口,进行接触,建立友情(并非爱情,切记!)并加温,将其煮熟,变成半成品——过渡到拍拖。

  要点:要细火慢熬。今天借书,明天归还,后天讨论书中的情节,等等。总之,醉翁之意不在酒,一切在于两人相处的积累和延续,加深了解,加以必要的自我包装,以达成阶段目的。

  切记:不可操之过急,“心急吃不得热豆腐”,弄巧成拙反而烫坏了嘴。若还想一步到位,生米做成熟饭,那将得到比当初若说“小姐,我们结婚生小孩吧。”更激烈的回报(千万不要逾越过程)。当然也要趁热打铁,继续加温(引导局势和双方的心理状态向目标迈进)。

  第三步、拍拖

  继续加温,此时是给爱情加温了。

  1、 牵手。先到公园,两人同坐在一张长椅上,一人一端。聊天的过程中,不自觉的靠近,然后,无意中碰到女孩的手,(请注意对方的反应,若积极,则继续前进),然后轻轻的抓住一个手指,两个,然后是整只手。在上述过程中,请注意选择合适的环境,要在公园,而且是有很多对情侣在卿卿我我的环境中,还要注意营造良好的气氛,手机最好不要成为干扰,聊聊风花雪月(这里遵循的是谈判的另一个需要注重的重要因素——营造良好气氛)。

  2、 Kiss。这一阶段应该是手拉手进电影院了,循序渐进,干了该干的事,就算成功。

  以上两步完成后就把拍拖引向结束——如果你想沉醉于此,多多体验的话,也不着急。慢慢煲吧。当然,这里还有许多实际的、具体的问题需要一一达成一致(就是谈合同,继续引导局势和双方的心理状态向目标迈进)。

  第四步、恭喜,大功告成。

  可以结婚生子了。顺便提醒一下,怀小孩需要经过10个月,否则很可能是次品(又是一种新的过程,不可逾越)。

- 作者: cyberfan 2006年03月6日, 星期一 21:08  回复(0) |  引用(0) 加入博采

华夏道,天下为公  (作者置顶)
华夏大道,天下为公(全文)
——春秋战国时代的人文关怀与复古之风


文\南方在野(2005-2-15)


(1)

老子《道德经》共八十一章,申言“大道”,质疑人性的弱点,开复古之风。

老子认为,“道”在这个社会上已经被消逝了,已经被背离很远了,是必定要返回的,“大曰逝,逝曰远,远曰反。”(《道德经》二十五章)。他直言,种种社会弊病的根本原因是“大道废”,而所谓圣人的仁义、智慧都是不能解决根本问题的。所以他主张“绝圣弃智”“绝仁弃义”,以回归“大道”。(十八章,十九章)。老子说“圣人无常心,以百姓之心为心。”(四十九章)。他赞扬“小国寡民”的自然状态(八十章),在老子看来“道法自然”(二十五章),人之本性尚自然,但统治者强行的作为违背了人之本性。——通过对统治者所谓圣智仁义的批评,老子揭露了人性的弱点;通过“小国寡民”自然状态的复古式赞扬,老子宣扬了人性的解放。

如《道德经》开篇所言“道,可道,非常道”,老子并没有给道下一个明确的定义。但老子所言的“大道”,在复古的外壳下充盈着对人性的关怀。


(2)

为什么要披上复古的外衣?或者说没有任何一个民族象中华民族一样,保存着对自己幼年的回忆。或者说中华民族的确是拿着石器告别野蛮的另类。传说中炎帝、黄帝时期的发明创造,尧、舜、禹时期的治水与“禅让”,物质、精神、政治劳动的社会大分工与空前的创造力,留给后人无限的暇想。中华儿女总是魂归上古,带着对人性的追问,探索社会发展的“大道”。

老子之后再次高举复古主义大旗的,是孔子。他“信而好古”(《述而》),称赞尧舜时代为“大同社会”,在《礼记-礼运篇》中讲,“大道之行也,天下为公。选贤举能,讲信修睦。”充满了无限向往之情。这里孔子所言的“大道”,与老子所言基本相同。然而,作为力求实践的政治活动家,孔子并没有发展天下为公的思想,更没有践行天下为公的华夏大道。“郁郁乎文哉,吾从周!”他屈服于强权横行的现实,退而求其次,主张回到周公所缔造的礼乐文明的西周,宣扬为“天下为家”服务的仁、义、礼、信、让。

但,孔子所言的仁义智慧,在春秋末期有现实的意义。征对当时统治者的昏暗、人之主体性价值与尊严惨遭践踏的社会状况,孔子希望高扬人的尊严与价值。他提出仁者“爱人”的人道主义原则,直言批评统治子者“未能事人,焉能事鬼?”(《先进》)。仁者何在呢?他相信人“性相近也,习相远也。”(《论语·阳货》)。主张通过有教无类的办法将人的社会存在导向社会理性,培养仁智统一的理想人格,以实现“成人之道”(《宪问》),以挽救人的价值与尊严。

孔子虽言“克己复礼”(《颜渊》),称赞西周的礼乐文明,但这就象李敖在北大演讲赞扬北洋军阀,其原意是对春秋时政与当局的批评。孔子甚至明白地指出“大道之行也,天下为公。”从骨子里充满着“大道”不得的郁闷。——孔子的政治学说,在在复古的外壳下充盈着人道主义和理性主义的光辉。

老子没有说错,背离了“大道”的仁义是无能为力的。孔子的仁义智慧,后来被统治者及御用文人长期加工、歪曲,背离“大道”越来越远,发展到最后,如老子所言,堕落成了一种“大伪”。


(3)

对孔子这种退而求其次的软弱,后来墨子奋起而批评。

墨子提倡更为广泛的人道原则,以此解放平民。他批评孔子的仁义存在亲疏厚薄之别,从而提出“兼相爱,交相利”(《兼爱中》),充满社会平等思想与对社会等级的批判思想。他肯定人的尊严和人的价值,认为人不是手段而是目的,说“仁,爱己者,非为用己也,非若爱马。”(《经说上》)。认为真正的仁人,“必兴天下之利,除天下之害,以此为事也。”(《兼爱中》)。他直言指责执政者与知识分子“此何难之有?特上弗以为政,士不以为行政也。”(《兼爱中》)。

墨子以更为深刻的普世价值“背周道而用夏政”。他清醒地看到“民有三患:饥者不得食,寒者不得衣,劳者不得息。”(〈非乐上〉)。所以反对等级制度礼乐文明,主张“节用”、“节葬”、“非乐”。他指出,儒家所言的“天命”是“天下之大害也”(〈非命上〉)。“命者,暴王所作,穷人所术(述),非仁者所之言也。”(〈非儒下〉)。

墨子这种强烈的平民革命思想,源自他对华夏“大道”“天下为公”坚强的信念,源自他独树一帜的复古思想。与孔子不同,墨子说“吾以为古之善者则述之,今之善者则作之”(〈墨子—耕柱〉)。在国家的起源上,他石破天惊地提出社会契约论,“选天下之贤可者,立为天子。”(〈尚同上〉)。“君,臣,萌(民),通约也。”所以他主张:“官无常贵,而民无终贱,有能则举之,无能则下之。”(〈尚贤上〉)。

墨子把这种天下为公社会契约之理奉为“天志”,说“我有天志,譬若轮人之有规,匠之有矩。”(《天志上》)。


如何实现天下为公社会契约之道?首先,墨子严格区分了战争的性质:他把大国攻小国、强国攻弱国的战争,叫做“攻”,强烈反对;而把讨伐暴虐害民之君的战争,称之为“诛”,大加赞赏。其次,墨子主张平民的结社自由,《淮南子—泰族训》记载:“墨子服役者百八十人,皆可使赴火蹈刀,死不旋踵。”这个团体的首领称之为“巨子”。墨子死后,这个团体仍然存在了一段很长的时间。





   (4)


老孔墨三位中华思想之开山祖,在人性的探讨这一基本问题上有着惊人的相似。老子没有明言但已非常清楚地提出了人性“自然”,孔子的“性相近也,习相远也。”无疑是对老子人性观的传承。墨子一书开篇为《所染》,以染丝为喻,“非独染丝然也,国也有染。”“非独国有染也,士也有染。”一语道破人性的自然状态,并指出改造环境的重要性。三位先哲清楚地看到当时不正常社会体制对于人性的异化,致力于将人的尊严从异化之中解救出来,于是不同程度的宣扬复古之风,究其实质,并不是什么“开历史的倒风车”,反之是一次前所未有的思想解放运动,是中国最早的原生态的文艺复兴与启蒙运动,其思想的内核,就是人文精神。

由于共同的人性追求,中华三圣老孔墨的"大道"观,可谓一脉相承。老子提出"大道"一词,虽然没有对其下一个明确的定义,但指出了中华大道的基本原则"师法自然",从而把握了人性解放的根本;孔子在政治实践上退而求其次,但具体阐述了中华大道的明确内涵是"天下为公",从而指出了一个普世的价值。墨子则提倡更为广泛的人道原则,肯定人的尊严和人的价值,认为人不是手段而是目的.强烈的平民革命思想,石破天惊的社会契约论,“非攻”而赞“诛”的共和革命思想,平民的结社自由实践思想,从而为后人指出了更为具体的民主道路。

在这里,我们似乎看到了先人的智慧与中华思想的光明。如果后人继续秉承这种人性追求,宣扬人的尊严与价值,反对社会等级对于人的异化,在复古之风的旗帜下继续探询华夏“大道”,那么华夏儿女是幸运的。

      (5)

实际上,这种思想的光芒曾经在百家争鸣的高潮中似乎灵光一显。《管子》一书就提出“任法而不任智”(《任法》)的要求,同时又提出“法出乎权,权出乎道”(《心术》),从而为法治的合理性作了论证。若《管子》中讲的“道”继承了老孔墨三圣的大道观,那么必将进一步发展孔子的天下为公墨子的社会契约理论,也必将深刻影响中华政治实践。

但是,《管子》一书阉割了老孔墨三圣的大道观。《管子》将“道”荒唐地解释为“气”,又认为圣人能将气“藏于心中”。后来这个学说进一步发展成为所谓的“刑名之术”,成为暴王驱策天下的工具。《管子》一书,体现了先秦法家先驱管仲学派的思想,为后来法家所继承,给中国历史造成了很怀的影响。法家的大害,就是从独断论走向残忍专制,借今而非古为暴政而正名,完全抛弃了对人的关怀,降低了人的价值与尊严,是对中华三圣老孔墨一脉相承的"大道"思维的严重破坏.背离了"天下为公"社会契约的的"大道",法家与兵家完全沦为了暴王的凶器。

      (6)

孟子看到知识分子为强权立言的堕落,人之尊严惨遭践踏,于是宣扬天赋人“性本善”,以“仁政”之说批评法家的残忍专横,要求知识分子与执政者“养浩然之气”以立志做一个“仁者”。提出要做到“富贵不能淫,贫贱不能移,威武不能屈”。而独断论的存在、残忍专制的倾向,让庄子看到了此时“百家争鸣”中潜伏的危险,他提出相对主义和怀疑论,指责诸子百家“彼亦一是非,此亦一是非”。又要求执政者“不谴是非,以与世俗处。”(《天下》)。主张执政者不插手诸子百家的争论,让诸子百家在争论中达到自然的平衡。

然而,孟子与庄子这些光芒四射的人文精神被淹没在战争之中。之后的所谓“百家争鸣”,实际上沦丧了争鸣的精神。一方面,许多知识分子逐渐丧失了应有的情操,自觉或不自觉地屈服于强权的奴役,替暴政辩护为虎作猖;另一方面,以齐国建“稷下学宫”为代表,执政者用利禄收买腐蚀利用知识分子,致使相当一部分知识分子沦为了专制的工具。

关于“稷下学宫”,南方在野实在是不想说出这个真相。很多人包括我自己,过去一直认为齐都西城门外稷下学宫是一个自由讲学、立著、论辩的百家争鸣之中心,独立精神之异域。但是我们错了,一个由专制统治者建立的学术机构,能有多少学术的自由?稷下先生共百千人,何有一个敢宣扬老子之“道”孔子之“天下为公”,何有一个敢宣扬墨子之学?一个个莫不是狗苟蝇营之徒。从学宫领袖(“祭酒”)荀况开始,就为暴力霸业而辩护,他提出“人之性恶”,目的是为统治者践踏人的尊严而服务,他露骨地说:“由士以上必以礼乐节之,众数姓必以法数制之。”(《荀子—富国》)。荀况作为学宫领袖如此,其他稷下先生宋鐦、尹文、慎到、彭蒙、田骈、环渊、邹衍、驺爽、淳于髡、貌说、田巴、鲁仲连、接子等七十六人见于史册者无一不是专制犬牙。荀况之徒到最后培养出一个韩非子,公开宣扬暴政,践踏人的价值与尊严,将独断论发展为君主专制,建议君主将“法”“术”“势”相结合,用奸诈阴险的手段来奴役天下群臣与百姓。

      (7)

而真正的学术思想,在民间得以发展。墨家子弟以"舍我其谁"的态度面对现实,自觉远离“稷下学宫”,用实际行动学习墨子,亲身参与各种反战行动与建设事业。捍卫墨子的“兼爱”与天下为公社会契约之理。战国时代,墨子门人弟子扎根草民,遍布天下。

墨子死后,墨家分为三派,相里氏之墨、相夫氏之墨、邓陵氏之墨,都以《墨经》为研读与发展思想的主要依据。基本上朝两个方向发展:一者重视自然科学、逻辑思维、认识论,认为人的认识能力是获得知识的工具,主张透过感官与思维作用进行科学探索,又认为逻辑的真伪要透过客观的自然世界或人类社会现况的检证。在这一方向上,后期墨家不仅在政治、伦理、经济、教育、认识论、逻辑有重大发展,而且开创了中国最早的关于数学、光学、力学方面的知识。

另外一者则继续力行墨子平民结社的实践与“兴天下之利,除天下之害”的任侠理念,在现实政治权力无法取得或予以保护的情况下,他们奉行墨子平等兼爱的社会理想,从而走向为国为民的侠义之路。南方阅读网友文章,知墨子之后,有个墨家弟子叫孟胜,是墨家巨子(墨家领袖)。他有个好朋友叫阳城君的,是楚国人。阳城君让孟胜帮他守城。有一次楚国内乱阳城君出逃,楚国收回了阳城。孟胜即因此自杀以殉朋友之义,同时赴死的有墨家子弟83人。后期墨家希望通过有组织有信仰的的形式,培养能人义士,游说天下诸侯,逐步实现自己的政治主张。

墨家之道,切中专制暴政之病根,为一己之私家天下者之大敌。西汉初年,平民结社行侠仗义成为一种风气,以武犯禁,藐视权贵,杀人于千里之外。为专制暴政之心病,汉武帝先后三次重拳出击镇压墨家。汉兴以后,历代专制统治者贬斥墨家之道,《墨子》一书两千多年几无人传述。

虽累遭专制暴政的屠戮与灭杀,墨家精神已深入中华民间而不绝。到大唐,任侠精神又重出江湖,墨家子弟仗剑行侠,匡扶正义,今人梁羽生作《大唐游侠传》,影射唐风。此后,每民族危难、大恶当道之时,侠义精神便横出江湖。对于侠义的期盼与热情,俨然成为中国黎民唯一愿意真正寄托的希望。金庸先生在《射雕英雄传》中阐述,“侠之大者,为国为民”,再次激活国人深藏骨髓的墨家基因。当庸俗“唯物主义”横行中华大地,中华三圣在隐约中守卫着我们的灵魂,墨子之风在召唤着华夏儿女。

何人再读墨子之学?何人再传先贤"大道"?

- 作者: cyberfan 2006年03月6日, 星期一 14:24  回复(0) |  引用(0) 加入博采