精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-05-01
最后修改:2009-05-01
从编程的未来这篇文章http://utensil.iteye.com/blog/367415 想到C++和Java语言的爽与不爽: 事实上Java的面向对象模型要比C++要好,C++是一个追求效率而不惜牺牲其他空间和优美方面的语言。
写道
C++是设计模式最好的描述语言,我看到许多书用Java这种先天不足的语言来描述设计模式,就觉得别扭
写道
Java使OO的一切都变得不再美妙,使一切变得麻烦,这就是我们为所谓的简化付出的代价。
写道
静态语言不能丢失指针,不能丢失引用
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-05-05
谢谢你的来访。原文固然带有个人的强烈偏好因素,然而你的观点我实在难以苟同。
首先想复述一些我原文中的论述,因为这些是我的主要观点: 引用 C++庞大而精深,任何一个怕难的人都可以栖居于这个语言的简单部分,而让写库的人去充分使用这个语言的其他层面。... 程序员的不自律和不善用库没有任何借口。... 程序员不应该对const &厌烦。... 托管使你完全失去了对一切的控制力,你不得不把东西丢得满地都是,等待机器人来捡你的餐巾。而且Java写的程序就使用的时候感觉,也常有内存释放的问题,也会崩溃。 文中你用了分散在多处的、非常多的篇幅来反感const &和C++中的引用,这是你之前某篇博客的观点的重复和展开: 引用 九、 C++的返回函数内的对象一直没有很好的解决方法。局部对象直接返回造成无意义的拷贝,返回地址则因失去作用范围而无效,使用new在堆中申请需要让别人去delete,而这使用者可能一无所知。使用智能指针,让人阅读代码痛苦不堪, 十一、其实大量的程序员不会使用C++的const,由此造成传递参数的时候,如果没有参数没有const修饰,而无法传递常量的困苦。以及其他赋值方面没有一个约束机制,例如char *s = "abcd";很自然的一个语法却潜藏阴险的问题。 Java的引用就是安全的指针,在面向对象语言中,指针的++,--操作不仅危险而且显得很别扭,C++引用其实是一种在提供一种防止大对象拷贝的方法,而Java完全不会有大对象拷贝的情况,而引用的副作用可怕的很。在引用传递的时候,形参的突然改成别的对象的别名, 实参的引用仍然很无辜的以为还在是原来那个对象的别名,虽然可以加上const T &t来限制这个东西。C++给程序员的责任太多,我每次写C++代码的时候都要小心翼翼,不是在考虑设计而是在考虑细微的语法所带来的别人如何使用问题。 三、C++引用这个东西的名字几乎起错了,实际应该叫别名,而不是到处都在说引用就是别名这个东东。引用这个词与大多数语言语义都不同。 四、 C++显式对象存在(Java中的对象都不是显式的,是由引用持有的,你看到的都是引用)只会导致误用,由此导致对象的无味拷贝。从而引出像拷贝构造函数,以及引用传递的复杂概念来支持和避免带来的问题。这当然是C++不得不做的,因为class的概念根深与struct,指针的作用指出也必须是这些显式存在才会发挥作用。 正是因为你从一开始错误地将C++中的引用理解为别名,所以才使得你在使用中在概念上排斥const &的做法,认为这是语言强加的语法细节。然而,这恰恰不是纯粹形式的、繁文缛节的语法,而是因为C++所保有的细微的控制的粒度而对语法产生的需要。简单地说,你不需要复杂的语言去描述简单的世界。遗憾的是,软件的世界是复杂的,底层是复杂的,对底层敬而远之的人依然要面对底层的复杂,只是遭遇的时候,他们更加不知其所以然,脸上的表情更茫然罢了。C++的许多语法的复杂,都可作此解。 而Java中对这些问题的隐藏,在不自律的程序员手中,一样问题重重,我使用Java写的软件,崩溃得比C++写的程序还要多(都不是我写的,而是广为使用的软件),一看,常常是空引用没有处理好。为什么?因为Java程序员,往往是C++的逃兵,是自律的逃兵,懒惰而推脱责任。JVM的底层,都是一流的程序员用C在认真严谨、小心翼翼地写着程序。而Java Guys,往往依赖着IDE,生成并修改着具有臭味的代码。 引用 一、C++没有interface的概念,...当然纯抽象类就是interface,但至少是概念上的一种缺失。... C++是一种多范式的编程语言。他自身提供了很多基础设施来让不同的“概念”来施展。如果有了纯抽象类,有了可以当接口使的基础设施,你仍然要强辩说语言缺了这个“概念”,那我倒要问了,Java中连个不纯抽象类(比如模板方法模式所需要的)的提供都没有,是不是连基础设施都缺了? 引用 五、C++拷贝构造函数,对指针成员深拷贝无能为力...这样是缺少单根继承所造成的后果。而这一切Java都是内置的。 另一方面C++缺少单根继承(Java的所以类的根都是Object),这其实也是面向对象概念的一个缺失,一切都是对象。这样一些类就没有了一个统一的约束。 六、九、十、 C++是一种类库百花齐放的语言。Java当中的类库世界是单极的,语言自身提供了过多的东西,使得太多的功能需要依赖语言自身,一旦这些基本功能有Bug,无从修复也很难绕开。 各类库往往有自己的方法来解决一些问题,而这些问题,由于是软的,所以可以自行解决,而Java中庞大的统一着Java世界的标准类库,使用其时,就依赖着其底层的实现代码,而这些代码,常常不是Java写的。逃避底层再一次显现出其依赖底层的悖论。这是对开源精神的一种违背。 你所津津乐道的单根继承,在wxWidgets库中实现着,在许多类库中也实现着。这个应该交由库来实现的事情,真的应该要让语言来做?只能有语言这一种方式来做?语言做的方式,真的适合所有的场合? C++中任何的库(包括STL)都退化为一种实现的建议,你得以采选,得以拥有调优的自由。Java中,这一切你想自己做都是不可以的。语言已经自以为是的做好了,并得意洋洋地说:菜鸟们,用就是了!单一的选择,懒惰的抉择。帝制。从clone到异常体系。 这也正是各大厂商选择Java作为它们中间件的语言的原因之一,因为这样单一的语言,最适合针对质量参差不齐的程序员而言的软件工程,代劳大量重复工作的IDE的出炉也有了理由。钱有得赚,因为语言本身不争气。 你所论述的深层拷贝问题、多维数组、string、STL等问题,只让我感觉到你对C++的某些基础设施还不够熟悉。自己不会用和语言不能做是两回事。具体此回复暂不展开。 引用 七、C++缺少反射机制,这点对使他真正的成为“静态语言”,许多更优雅的框架设计从此离他远去。 对Ruby和其他动态语言了解的越多,我就越感觉到,Java是一种尴尬。你觉得Java API式的反射机制与Ruby更为native的反射机制相比如何?Java真正需要动态的时候,只好靠Groovy、Scala出场。Spring将反射机制运用的出神入化,仍然使我们面对繁琐的XML配置。 在反射对C++的程序真正有用的时候,类库却也可以有自己的实现,wxWidgets自身的RTTI体系就是一例。反射的多种用途在C++中其实可以退化为多种表达形式,而且更贴合其使用情境。反射的滥用,却往往违背OO,违背了解耦,增添了依赖性。 引用 二、 C++的继承体系不够完善。...子类一个不同方法签名相同方法名的方法会隐藏掉继承下来父类的方法... 此现象闻所未闻,到时写个程序验证一下。 引用 我不知道有哪些不美妙的地方。至少我感觉Java的OO机制要比C++美妙的多,C++在向下兼容c的面向过程和一切以效率为目标,以及在stl试图得到使用函数式语言的美妙感觉而背弃面向对象的方法,让它变得不伦不类。 从main的病态放置到仅有单继承(也无任何类似Ruby的Mix-in,只有一个可怜巴巴的起一点规约作用和功能描述作用的接口),从析构函数的消失到界面与实现的强制混合,我没有看到Java中任何OO之美,只见到病态的束腰。Java对OO的理解完全是一种偏差,一种强行的简化。 “stl试图得到使用函数式语言的美妙感觉而背弃面向对象的方法”,OO的本质其实还是强化内聚和解耦,是分清责任与协作,STL恰恰是这种思想的杰作之一。我不明白你所说的背离是什么意思。 C++并不是OO的始祖,也不是OO最好的表达,C++也远不是一门完美的语言。然而,从语言层面(非平台层面),Java没有资格说C++什么,它并没有取C++精华,而是C++的一个拙劣的简化和改造。 语言之争往往无益,因为各自熟悉顺手。回复中语气冒犯之处,还请海涵;偏颇之处,也欢迎指出。 |
|
返回顶楼 | |
发表时间:2009-05-05
utensil 写道 程序员的不自律和不善用库没有任何借口。... 如果一个语言只有教父级别的人才能应用自如的话,那么这个语言是失败的,不能怪程序员功力不够,不自律,应该考虑这个语言本身的陷阱和琐碎的规则了。 utensil 写道 正是因为你从一开始错误地将C++中的引用理解为别名 既然我c++的引用不是别名,请教一下应该做何解释。 utensil 写道 单根继承,在wxWidgets库中实现着 库的支持只能说是一种对语言修补,语言级别的支持往往比第三方库支持来的更自然。 utensil 写道 C++中任何的库(包括STL)都退化为一种实现的建议,你得以采选,得以拥有调优的自由,Java中,这一切你想自己做都是不可以的 Java事实提供了更多可以选择和定制的东西,Java语言的接口的概念本身就是一种建议和契约,STL中的Set(Map)只有一种SortedSet (SortedMap)而且从名字根本无法晓得这个Set里的元素排好序的,这种潜规则需要仔细阅读文档才能知晓。而Java Set接口之下会有SortedSet,TreeSet,HashSet可供选自,当然可以自己实现Set接口来定制自己需要的Set. utensil 写道 Java中连个不纯抽象类(比如模板方法模式所需要的)的提供都没有,是不是连基础设施都缺了? 我不知道你对Java语言了解多少?Java没有不纯抽象类?abstract修饰的类随处可见,并且abstract class和interface在表现面向对象不同的哲学has a和contract。 utensil 写道 因为C++所保有的细微的控制的粒度而对语法产生的需要 这点我也很赞同,这种需要是通常一个概念很容易被误用,然后又出来一个东东来束缚一下。 utensil 写道 对Ruby和其他动态语言了解的越多,我就越感觉到,Java是一种尴尬。 如果那动态语言的强项和静态语言的弱项相比这种尴尬是任何语言都不可以避免的。 utensil 写道 你所论述的深层拷贝问题、多维数组、string、STL等问题,只让我感觉到你对C++的某些基础设施还不够熟悉 某些基础设施希望你能指出,如果是第三方的库的支持能做的话,与标准库的支持相比,我感觉着仍然是一个缺陷。 utensil 写道 从析构函数的消失到界面与实现的强制混合,我没有看到Java中任何OO之美,只见到病态的束腰。 有垃圾回收析构函数固然是一种多余,并且关键的资源用户可定义close或者dispose方法来释放资源。界面与实现的强制混合?我不知道你在说哪方面,是C++头文件和.cpp的分离么?事实上interface本身就提供了这个功能,并且可以有多个实现。 utensil 写道 Java中,这一切你想自己做都是不可以的。语言已经自以为是的做好了,并得意洋洋地说:菜鸟们,用就是了!单一的选择,懒惰的抉择。帝制。从clone到异常体系。 如果有了一个很好的基础设施的话,都被认为是一种懒惰的抉择,帝制的话,那最好用汇编吧!单一的选择?为什么单一,你可以自己做想要的实现,并且直接可以融入体系之中。 |
|
返回顶楼 | |
发表时间:2009-05-05
发现昨晚的回复写着写着走调了。
引用 ...如果一个语言只有教父级别的人才能应用自如的话,那么这个语言是失败的 C++真的需要教父级别的人才能良好运用吗?世上依然有许许多多愿意自律、愿意谨慎、愿意沉思的人。 引用 因为C++所保有的细微的控制的粒度而对语法产生的需要 这点我也很赞同,这种需要是通常一个概念很容易被误用,然后又出来一个东东来束缚一下。 你赞同的不是我的观点。这样可能被误用的形式是为了控制更底层的东西所设置的。二流程序员自己不断误用,却不断怪在语言上。真是令人难以气平。我没有感觉到C++给我的束缚,一切如此顺理成章,而Java却给我带来了很多混乱。而且Java是一种很罗嗦的语言,一吨一吨的关键字,远不如C++的几个符号简单明了。 引用 库的支持只能说是一种对语言修补,语言级别的支持往往比第三方库支持来的更自然 Java事实提供了更多可以选择和定制的东西 昨晚我的论述有些走调了。因为我其实是想论述有些基础设施由语言来提供这一点,是有利有弊的,而不如你可能认为的,利远大于弊。如果C++中在语言层面提供了这些东西,就会妨碍了自身的灵活,限制了自身所能运用于的领域,C++也不会取得今天这种基石的地位——你可能论述说,基石是C而非C++。然而C++提供了从底层到抽象的各种编程范式来供项目的不同部分栖居。 引用 set...有序... 你可能不了解的是:Specification没有规定其STL中的set是否有序,这是由实现自行决定的,只规定了set的时间复杂度。使用C++的set的时候,也并不需要知道其实现细节,而且其使用也不应依赖于实现细节,而是如使用黑盒子一般使用其接口。 引用 这种潜规则需要仔细阅读文档才能知晓。 什么时候起,我们不应该鼓励RTFM和RTFS的精神,而是变成了要鼓励Read the Fantastic Name的精神了?中国的许多程序员往往一知半解地抄用着别人的代码拼凑起来,遇到问题连google都不舍得,更不肯阅读文档。这种浮躁,也适宜用来指责一门语言? 引用 Java没有不纯抽象类? 不好意思,一时犯了Java中的常识错误。但你那个概念缺失的说法,实在是一种教科书为了解释interface的存在而培养起来的你们java guy的莫名其妙的优越感。令人难以苟同。 引用 既然我c++的引用不是别名,请教一下应该做何解释。 某些基础设施希望你能指出,如果是第三方的库的支持能做的话,与标准库的支持相比,我感觉着仍然是一个缺陷。 如果无意潜心C++,又何必解释这些概念和运用的细节。只是若博主要拿这些自己尚未掌握的知识来论述C++的不足,却有些贻笑大方。如果一个学C++的人只了解了语法、STL,再加上MFC,那根本还没开始涉足C++的美妙世界呢。 因为博主也未具体说明你是怎么感觉到不智能、不能转换什么的,我的解释也没有针对性啊~若要论具体的话,代码说话。可以你用Java实现某某事,我再用C++实现等等。但你我都没这么好精神在语言之争上浪费光阴吧? C++中,引用的最大用途是传引用或返引用,也就是参数或返回值中的&。这远比传入指针安全。你见过谁把C++中的引用当别名使的?一个{}之内,谁喜欢给一个变量起两个名字?只有教材,那误导人的教材,才会把应用理解为别名。 引用 引用 二、 C++的继承体系不够完善。...子类一个不同方法签名相同方法名的方法会隐藏掉继承下来父类的方法... 此现象闻所未闻,到时写个程序验证一下。 我写了个简单的测试程序,没有发现此问题。请博主再验证一下。 引用 有垃圾回收析构函数固然是一种多余,并且关键的资源用户可定义close或者dispose方法来释放资源。 我知道。而且你回避了我说了几次的“Java中对这些问题的隐藏,在不自律的程序员手中,一样问题重重”。 我举了一些未必恰当的例子来说明在我眼中Java对OO的扭曲。我觉得这是个人审美问题,也是因为我们浸润于不同的语言所致。我真的无意引发语言之争。但我觉得,Java的泛滥,助长浮躁的心态,而且喂饱了很多开培训班的人。 至于单一与多元之争,也无益。世界既需要多元也需要统一。我所指出的只是Java更为单极,而这单极,构造于Java代码所不能触及的底层。 |
|
返回顶楼 | |
发表时间:2009-05-05
utensil 写道 此现象闻所未闻,到时写个程序验证一下 这个现象是由于C++粗糙的对继承的支持导致的,utensil同学没有听说过? 举个例子吧: #include<iostream> using namespace std; class A{ public: void fun(int i){ cout << "fun(int i)" << endl; } }; class B: public A{ public: void fun(){ cout << "fun()" << endl; } }; int main(){ B b; b.fun(1);//报错,找不到匹配的函数,fun()隐藏掉了继承而来的fun(int i) return 0; } utensil 写道 二流程序员自己不断误用,却不断怪在语言上 如果一个语言语法本身就有太多的陷阱,误用者我想不光是二流程序员吧,事实上windows和firefox早期版本一直存在很严重的内存泄露和其他有语言误用造成的一些bug,不能说微软和Mozilla的程序员都是二流水平吧。 utensil 写道 使用C++的set的时候,也并不需要知道其实现细节,而且其使用也不应依赖于实现细节,而是如使用黑盒子一般使用其接口。 C++恰好混淆了这个问题,接口和实现没有分离。这样我使用普通的set的时候,你却暗地里不声不响的给我排好了序,我对此一无所知,结果又调用了一个快排重新排了一下,可知道快排对排好序的东西排序是一个最坏的复杂性。而Java则简单明了,我有Set接口,我有 SortedSet,TreeSet,HashSet等等这些实现,你想使用一个普通的Set,那好一个Set接口就好了,你想使用排好序的Set,那好SortSet吧。 utensil 写道 我们不应该鼓励RTFM和RTFS的精神,而是变成了要鼓励Read the Fantastic Name的精神了? 为什么叫 Fantastic Name呢,一个好的代码,看到名字就知道干什么的了,一个还算可以的代码看文档就知道干什么的了,一个差的代码把代码全看一边就知道干什么了,一个最最最差的代码是把所有的东西都看完了,还是不知所云。 utensil 写道 但你那个概念缺失的说法,实在是一种教科书为了解释interface的存在而培养起来的你们java guy的莫名其妙的优越感。 在面向对象中is-a和contract的关系就像区分is-a和has-a的关系一样重要,如果你认为那只是教科书了解释interface的存在的理由的话,建议你因该了解更多的关于interface和Abstract class的关系。 本来没想和你争,但你说Java在面向对象的描述上非常的Ugly,我无法忍受,因为C++跟本没有资格在面向对象方面和Java说三到四。其实Java和C++面向的领域不同,Java主要面向高层的应用和建模,而C++则面向底层还想面向对象的一些应用。 |
|
返回顶楼 | |
发表时间:2009-05-06
引用 ...报错,找不到匹配的函数,fun()隐藏掉了继承而来的... 又一个你对C++不熟悉的例子。你在原先指责这一点的时候,怪在多态和虚函数的实现身上,然而你的代码完全没有启用多态(没有用virtual)。再一次说明了,对语言没下功夫,没有真正熟悉,就开始用自己不熟悉的知识评判语言。 这个现象是C++中明确定义了的隐藏行为,是C++用编译器防止一些人为错误的设计哲学的产物之一。参见http://blog.csdn.net/zgbsoap/archive/2005/12/30/566120.aspx 中对重载和隐藏的不同的论述。 我之前的测试例子启用了多态,所以就编译无误。 引用 ....早期版本... 我还用说什么吗?早期的时候,从来就没有一流过的微软写出了臭名昭著的假C++框架MFC,至今仍有贻害。而Gecko的代码,我也阅读过,可惜啊,这样的代码质量严重限制了其在Mozilla自己的产品之外的运用。君不见大家(如Adobe Air)都宁可去包装WebKit,而不愿用Gecko吗? 引用 ...我使用普通的set的时候,你却暗地里不声不响的给我排好了序,我对此一无所知,结果又调用了一个快排重新排了一下... set为了提供所保证的时间复杂度,通常用的是红黑树的实现,从其对<的依赖可见一般。 我不知道你用什么神奇的代码对set进行了快排!set的接口既未提供快排,如果用迭代器和STL算法,set的readonly的迭代器也是不允许这样做的。如果你是对迭代完了之后复制出来 数据进行的快排,那再一次你没有认真阅读set的迭代器的输出顺序相关的文档。 你恰恰没有仅仅就接口而使用,也没有按set的抽象概念去使用它。 引用 建议你因该了解更多的关于interface和Abstract class的关系。 面向对象中的这些概念确实是重要的。然而Java用interface做的实现,却是拙劣的。我仅仅是认为这一点不能给Java任何优越性。C++中对同样的东西的更好的实现,是C++0x中的Concept。而且contract在编码中最重要的用途之一是在类似于trait和duck-typing之中的,而范型鸡肋的Java根本体会不到这个特性的真正用途。 引用 本来没想和你争,但你说Java在面向对象的描述上非常的Ugly,我无法忍受,因为C++跟本没有资格在面向对象方面和Java说三到四。其实Java和C++面向的领域不同,Java主要面向高层的应用和建模,而C++则面向底层还想面向对象的一些应用。 我本来也无意于语言之争,但你说C++在面向对象的描述上非常的Ugly,我无法忍受,因为Java跟本没有资格在面向对象方面和C++说三到四。我已经尽量回退到说这是我们审美的不同这样把问题相对主义化的地方了,你却还要步步紧逼,说“Java主要面向高层的应用和建模”而我们只是半吊子,“还想面向对象”。事实上Java才是真正的半吊子,C++是建立在强类型的扎实基础上的泛类型语言,提供了最为完整的语言机制来表达面向对象的思想,而Java中胡乱的取舍和自我限制,限制了对OO的表达。 最后我想再说一次,Java的历史功勋是无疑的,但这应该归功于Java平台而非Java语言自身的设计,Java语言的自身设计是非常拙劣的。 |
|
返回顶楼 | |
发表时间:2009-05-06
utensil 写道 又一个你对C++不熟悉的例子....我之前的测试例子启用了多态,所以就编译无误 我没有用到多态我为什么要启用virtual呢?我只是说明在继承的时候的诡异隐藏行为来说明C++在为追求效率,对继承进行一种粗糙的实现。 使用virtual编译无误? 你真的对C++很熟么,至少这点我不敢恭维。 utensil 写道 再一次说明了,对语言没下功夫,没有真正熟悉,就开始用自己不熟悉的知识评判语言。 这一点你是五十步笑百步吧,从你对Java有没有抽象类都不知道,可见你对Java的熟悉程度了。 |
|
返回顶楼 | |
发表时间:2009-05-06
引用 我没有用到多态我为什么要启用virtual呢? 但你在博客中却把这怪在了多态和虚函数身上。我主要是认为你并不了解出现这个现象的原因,胡乱地怪在虚函数身上,怪在C++的追求效率上。 有意而为之的隐藏行为是C++认真设计的作用域控制之一,并不是你所谓的粗糙。请看下面的程序。 引用 使用virtual编译无误? 之前的测试代码用了以前写的代码的一部分,的确编译无误,但我今早在没重新看自己代码的情况下草草归结的原因错了,你的程序编译不过不是virtual的问题。 放一个重新写过的最小化的通过编译的程序吧: #include <iostream> using namespace std; class A { public: void f(){ cout << "A's " << endl; } void f(double, double) { cout << "A's" << endl;} }; class B : public A { public: using A::f; void f(int){ cout << "B's" << endl; } }; int main() { A * a = new B; a->f(); a->f(1.0, 5.7); B b; b.f(); b.f(1.0, 5.7); } 引用 ...这一点你是五十步笑百步吧,从你对Java有没有抽象类都不知道,可见你对Java的熟悉程度了... 我承认现在我对Java已经不熟悉了。浸入一门语言并发现其拙劣之后,我就尽可能地忘记它给我带来的很多混乱的概念。实际写程序的时候需要Java的时候也很少。所以我举了一个错误的例子来批判你鼓吹interface的优越性的口吻。但你在回复中也不小心把abstract class称为has-a的代表。人,还是很容易想岔说错的。 我不能代表C++程序员,你也不能代表Java程序员。我们之间的争论,其实贻笑大方。Java源于C++,裁剪改造并不得体,却总是有高人一等的姿态。这是我从感情上非常抗拒的。我也不喜欢Java所培养程序员漠视底层的浮躁。看看C#,至少在语言中保留了通向底层的因素,也允许程序员书写不托管的代码。这些是我始终停不下争执的心因,偶尔语气冒犯像是人身攻击之处,其实更多的是指代浮躁、学艺不精却时常叫嚣的Java guy,it's not personal,还望海涵。 很高兴和你交流,其实这场论争也改变了我对Java的一些认识,也使我顺便复习了C++中一些知识。对C++某些知识不够熟悉是我学艺不精,不怪C++。我会继续在工作中熟悉C++的特性,并迎接改善了C++许多旧有弊端的C++0x。 |
|
返回顶楼 | |
浏览 4787 次