java 析构函数_C++与Java的区别(一)

一,前言

网上经常看到编程语言之争,大伙儿皈依到不同门派,各自怀抱信仰,时不时还发生点儿“冲突”。

这其中,C++和Java的优劣,十多年前就常吵的火热。然而时代在进步,技术在发展,满街早已是“云大智”了,现在还讨论C++ vs Java这个话题,似乎早已经过时了。

07e97e1e196c95ec29b97f44e3909227.png

家鸽资质鲁钝学的慢,热门的知识没掌握,只能拿这个老话题炒炒冷饭。

参加工作的前两年,主要写Java;近些年由于工作需要,主要写C/C++了。也算是敝帚自珍吧,常觉得之前学会儿点儿东西不容易,舍不得丢掉。总会自觉不自觉的拿现在用的相互对比,也就粗略的总结了些东西,又怕记性不好以后忘了,就写个文字形式的记下来。

这个系列不敢说两种语言孰优孰劣,只妄图达成一个具体的目标:认真看过这些文字的人,以前写主写C/C++的,也能够写Java了;以前主做Java的,也可以尝试做C/C++了。这当然仅限于语言本身这个层面,框架与应用领域等范畴不在此列。与此同时,要是家鸽能够略微传达出一些观念,那就更好了。

二,形而上

形而上谓之道。人在道中而不知道,如同鱼在水中却不知水一样。

e6bd9b421cbfb447de4e6ba1f593140f.png

本节以下为家鸽自己看法的总结,纯属一家之言,有不同意见欢迎拍砖。

语言层面的“道”,是它的设计理念与发展思路。先看一下C++。它就是一个实用主义的大杂烩,有点儿吸星大法的感觉。

5c96910c862e169b9522afdaced419b9.png

1979,Bjarne等人试图去分析UNIX的内核的时候,诞生了这门语言。它初期的目标,首先是在兼容C的前提下,引入“面向对象”思想。1984年,“C with class”才改名为“C++”。当时面向对象思想刚出来没多久,炒的火热,也确实能解决很多问题。“兼容C”这个大前提不光是兼容,显然是当时出于复用与实用性的考虑。导致它近乎完全吸纳了C的风格,可以面向过程,又能面向对象(这个对象显然不纯,不如称之为基于对象),于是语言特性和写法不可避免的增加了不少,相对而言显得有些复杂。面向对象是C++编程思想的一个重要的部分,但不是它最核心的思想,从来也不是。STL也于1979年创立,于1993年成为C++标准的一部分。泛型思维是STL的基础。当前的STL就是个以高度复用为目标的,以泛型思维为基础的,高层次的,系统化的,类别分明的C++库。早期的C++并不支持template,而是用宏来构建复杂的结构。但很快就引入了template,又有了新的风格与写法。如果去分析不同版本的STL源码,会发现其中没多少面向对象,都是在泛型思维指导下,使用template实现的具有高复用性低时间复杂度的精巧代码。早期的Java没有泛型,后来也有了(虽然二者机制与地位并不等同)。可以看到语言之间常常会相互学习各自的语言特性。性能的考量以及直接而灵活的资源操控与系统调用,是这门语言设计的又一目标。它本来就为分析与实现操作系统而诞生的语言,操作系统内核里不会讲什么面向对象,主要是各种资源操控与管理的策略及算法。在从始至终的发展过程中,所有新特性的引入,都不会违背这个性能与资源操控这个前提。由于这个原因,这门语言灵活而强大。有这么一个说法,C++的灵活与强大不在于它能实现多高层的抽象,而恰恰在于它可以不抽象,以及可以自如的控制自己抽象的程度,要是觉得C/C++语言本身这一层抽象也碍事,甚至可以在里面嵌入汇编。在C++的世界里,既可以面向实际问题的结构,建立抽象模型,又可以看到解决问题时基于的计算机的结构,实际项目研发过程中一个重要的设计,就是建立二者之间的映射关系,这个过程需要不菲的成本。在更高层面抽象的语言诞生之后(比如Java),依项目类型和需求而定,这种映射关系的充分建立也许就不是必要的了。也是由于这些原因,使得C++不容易掌握,更难以精通,不易维护,开发效率低下,容易引入严重的内存泄漏和宕机等问题。于是实际工作中也就有了各种版本的开发规范。11版本及之后的C++吸取了现代语言的新思想,譬如有人说lamda和std::function std::bind让它能够进行函数式编程,能支持高阶函数和链式调用。也许在正统函数式编程语言的支持者看来,这个“函数式编程”未必纯正。但对新思想的吸纳,给C++带来很多新的语言特性。活用种种新特性与新风格写的代码,看起来似乎成了一种新的编程语言。C++委员会一方面想让这门语言长盛不衰,给开发者提供更加便捷,安全的语言特性;另一方面必须保持它在已有领域的优势,能够相对灵活自由的操作底层资源;当然必须保持对历史代码的兼容,以前的特性不能丢。这中间免不了权衡取舍综合折中,就成了我们现在看到的C++。这种权衡与引入,形成了当前一个重要的趋势:在提高(至少保持)对资源灵活控制能力的前提下,提高抽象程度,提升编程的方便与程序安全性。但在语言快速发展的过程中,对旧特性老式风格的兼容,legacy代码的维护,大部分一知半解的程序员,以及各种编程范式与新老风格在实际研发中的无原则的混用,都如同重重迷雾一般,掩盖了这种趋势,C++项目看起来反而愈加复杂,甚至有些让人望而生畏。总体上,C++是面向过程、基于对象、泛型思维和新思想并重,还在不断发展的一门编程语言。Java的设计者看到了(当时)C++的各种问题,总结实际项目的研发需求,形成了一套高度抽象与精炼的语言特性体系集合,从研发的源头就规避掉这些问题(与此同时也限制了自身在底层的能力)。这个精炼集合写出来的代码未必精炼了,有不少人评价它的语言风格有些啰嗦,但相对而言这种问题就无伤大雅了。

b9e8e1f9118e740f6339ddd8dd7d5758.png

Java的语言特性相当精炼,容易掌握,以至于很大一部分Java程序员相对的不怎么讨论语言特性本身,能更多的面向实际问题,将主要精力放在各种框架,API,设计模式中。你当然可以说,C++项目那些问题是由于编程人员水平不够,而不是语言本身的问题。但在Java这一套新的语言体系中,更多的人经过短期学习,更容易高效的写出问题很少的代码,何乐而不为呢?

高度抽象,是Java的设计原则。Java本身就是抽象而完备的;并且基于它可以进行更高层面的抽象。这使得代码量少,维护性好,研发工作高效,安全性容易得到保证。纯粹的面向对象是Java语言核心的思想与原则,在Java的王国里“一切都是对象”,连基本类型都有对应的包装类。它的复用也是更多基于面向对象来实现的。更完备的抽象,完全的面向对象,意味着更完全的封装,程序员不再需要知道细节就可以完成任务,也可以把无关他人的细节更好的隐藏起来。当然在一些情况下,掌控底层成为一种必要,它就无能为力了。在Java的设计思想中,开发效率的重要性高于程序运行效率本身,这在计算资源不是主要约束的实现需求中,显然有很大优势。

质量与安全性也是重要的考虑,除了规避掉容易引入问题的语言特性,Java风格中的异常处理机制非常完善。很多时候同一段逻辑,Java的写法也相对单一,不够自如灵活,但安全性与质量更高。在自由与安全中,Java选择了安全。

Java的语言特性相对精炼,但它也有C++不具备的语言特性。这些语言特性或者更加面向对象,比如接口。或者使它更能适用于Web,并发(C++也引入了)等领域当中。本节的描述有些抽象,不易理解,至少不容易有切身体会。为了说明问题,后文中家鸽会用自己写的一些代码示例,C++代码用黑背景,Java用浅绿。这些示例仅为突出演示某些语言特性的“玩具例子”,尽可能的简短易懂,但并不适用于实际工程。

三、 形而下

形而上谓之道,形而下谓之器。舍器而近道者,几稀!4a8448824d975a509aaf32d0b1cc018f.png

器,可以简单的理解为工具。编程语言本来就是工具,工具的区别在于使用方法。上图中,C++,C,Pascal都类似瑞士军刀,是用来做细活的工具。C语言的刀上有个USB,说明是可以做硬件操作的。C++刀是什么都有,说明C++是一种特性(过分)繁多的语言。Python是把电锯,面对大型的物体的修整,威力很大,人挡杀人,佛招杀佛,比C++/C/Java什么的得心应手得多得多,但是相对并不适合一些精细的调优工作。Java/C#是一把单刃工具刀,相对来说,其语法和使用相并不复杂。

不太关注技术细节的朋友,这一节后面的文字可以略过,直接看下一节。

这一节从语言具体的用法出发,来分析一下C++与Java二者的区别。可以粗分为三类:

  • Java不支持的C++特性

  • C++不支持的Java特性

  • C++和Java都有但是却不相同的特性

限于篇幅,本文只阐述前两种情况,第三种情况后续系列中再行阐述。

Java不支持的C++的特性

99231e28cf8817234fb7cf547724192c.gif

  • Java不支持指针;

  • Java不支持预处理,也不再支持预处理指令(宏等等都没有了);

  • Java不支持typedef;

  • Java不支持goto语句;

  • Java不支持结构体与联合体;

  • Java不支持操作符重载(比如Java中的<>不再重载I/O操作);

  • Java不支持全局变量或全局函数;

  • Java不支持默认参数。

  • Java不支持析构函数。(Java增加了finalize()函数);

  • Java不支持delete操作符;

  • Java中,参数传递的方式只能是传值(“传值”这个说法有争议,且看后文详细解释。C++中可以传值、传指针或传引用);

  • Java不支持多重继承,即不允许一个子类继承多个父类。

  • ............

下面挑几条典型的详细分析一下。

  • 指针

    最显然的区别是,C++支持指针,而Java不支持。

    下面这个例子,将平面一个点,移动到与它原点对称的位置。

9bbba1247b6f0c31eb5a085d57970b12.png

6dc6e43b0766fd4f47a32a999502b126.png

642b8d667387c8b3955bdab0faf48752.png

47c0e0a4733b23457d52e8e4b597646a.png

下面的这个例子,将一个数组的内容拷贝给另一个数组。

b1c690bce5ca30adb6780ae780665bd1.png

e42c012689330e35983a209be93678cf.png

eb34ac16836263c83525b6372942e08b.png

11cf3f660dc9db0857061d5f0a6075a7.png

上面例子的写法都有些笨拙。比如可以用STL的copy()函数也许会清爽一些。这里只为比较"指针"这一点。

可以看到,C++有了指针,灵活的多也自由的多,在细节操作方面有更多掌控力;但代码也复杂了。比如*p++这种写法,不熟悉的话,总会纠结优先级结合方式之类。指针操作不当也有相当的风险,加之可读性下降,一般说来开发效率是有所降低的。

Java在设计之初,就把安全性与开发效率作为考虑的重要因素。在语法上去除指针,从根本上就避免了一些可能的质量问题。而且语法相对简单,容易上手,不用太过小心翼翼规避问题,开发效率相对也会高些。

  • 对象的生命周期   

f66bf73847ef1da525e0d44e3f3f0e68.png

标号为(1)的C++构造函数调用中,我们可以把u本身当作一个User对象。实际是在栈空间中构造的对象。这种构造方法Java中并不支持(基本类型除外)。标号为(2)的C++构造函数调用中,使用一个指针p指向堆空间中构造的对象。类似Java中引用q的作用。

ebda33ebb38e28546a423620fc9c4255.png

a2daaebc482ecdad9847c14b84c9efa3.png

也许在C++程序员看来,这没什么区别,只是悄悄的把指针藏起来了。换成了“引用”这一语法糖,底层还是用指针实现的。事实上,Java的引用与C++的引用有重要的不同(具体下一段中分析),“引用的底层也是指针”这一说法,也仅仅是对内部实现的一种假设模型,对理解某些问题更加方便,但把它理解为“别名”更为恰当。至于析构函数与finalize()函数,它们在机制上有着本质的区别。下面这个例子中,假设资源有限,该类型对象只能创建有限个,需要计数控制。

8e89b3ccf45d6b5fdcb2420b7954156a.png

c3d4e2650efce8c9625f70671ab7780f.png

175cad648d7a8689b394af554d71e3c5.png

12cc2512aeb6bd736ec35510a78a011a.png

14b5dae1d4b1169bacef099a378b17f4.png

2ca61444ea24e6111935722c76cb8ea4.png

C++栈空间的对象会及时析构并释放空间(堆空间的对象则可在主动delete时释放),Java的finalize()只能在系统进行自动垃圾回收时调用。程序运行时报错的原因是每次f()返回时垃圾回收并不出现。因此,没有调用finalize()方法,count的值也没有减少。在5次调用方法后,count到达其最大值。C++有析构函数,可以自主决定堆中对象的析构时机。Java没有这个机制。析构的时机不能自主掌控,代码优化空间相对有限。因而内存占用率等指标无法与精心优化过的C++代码相比较,性能也会多少受影响。视具体软硬件运行情况,垃圾回收时可能会出现若干毫秒的延迟,不能适用于一些资源有限或高度实时的应用场景。但Java写法简单,容易掌握。而且不容易出错。C++中忘记析构导致内存泄露,重复析构引发崩溃等问题,Java中极少遇到。让C++程序员饱受折磨的内存问题,在JAVA的GC机制下不再是什么问题了。质量与安全性有了保证。
  • 参数传递

这是有一定代表性的一个区别,看似平常,真相却藏的比较深。家鸽比较喜欢的说法是:C++中可以传值、传指针或传引用;Java参数传递的方式只能是“传值”。这个说法是有争议的,而且这么说更不容易理解。比如《Java编程思想》里就不赞成。争议的原因在于对“引用”“传值”这些概念本身,大家的认识就是不一样的(虽然用了一样的词,但表达了不同的意思或侧重了不同的方面)。Bruce当然是理解这个问题的,但家鸽认为写书与教学的人,更倾向于选择更多读者容易理解和接受的说法,但心里明白这样并没有真正说清楚,于是又加了这一大串注解(瞧瞧页脚这一大坨注解吧,不管你主写Java还是C++,保准让你一遍看不明白)。

6eff8a6081b334b9b570070028dddf6d.png

更容易让人理解与接受的说法是,Java传参,对于基本类型是传值,对于“对象”,则是传引用。然鹅,这么简单理解的C++程序员来写Java,迟早会写出类似下面这种Bug。

66cd8ef64bac74b3eff56a5b59d703fc.png

69888dd709450d589a9c1eaa5a41ae79.png

536e97ba12fb3a47672a07e166ef5233.png

8b2b1026bcc61a5df241cdd99321eafa.png

0115066f678b85f547a51ba735973bc8.png

见鬼,Java的对象传参,不是传的引用么?为啥没交换过来啊?难道是值传递?在Java中,如果参数是通过传递引用的方式传递的,那么程序中的swap函数应该导致两个对象的内容交换,但是实际上却没有发生这种情况。应该用下面的话严谨的说明这个过程,Java的对象传参方式只有一种,即使用传值的方式传递了一个引用。而C++的引入传参方式,是传入了对象的一个别名。Java的引用更像是传了个指针,而指针本身是值传递的。理解了这句话,在你眼里,各种资料上的不同说法的出入,或者争议啥的就不再是问题了。详细说来,C++程序员眼中,Java的“传值调用模式”,调用函数向被调用函数传递对象引用的一份拷贝,而不是对象本身的一份拷贝。在C++中根据传引用方式传递参数(在Java中没有这种方式),意味着被调用函数中的引用形参作为调用函数所提供的实参对象的别名。那“正确”的写法呢?逼的我没办法了,似乎只能啰里啰嗦写这么一滩:

9905ada75b53e807096e2fd1e827b172.png

679efbb94f185bb92183725a7990d354.png

不过Java程序员,貌似不会用这么蹩脚的写法,他们可能更加面向对象一些。比如使用包装类来传参,把要交换的对象,封装在另一个对象中进行操作。在自己也不知情的情况下,完美的躲开了传值啊传引用啊这些坑。

dcddc0dd72022a4477ac7ff90f630e78.png

另一方面,对于基本类型,C++与Java都是传值。传参类型中,二者只有在这一点上是相同的。31f5cbb26fbdab8a53fc767dd91a1cf1.png0cade8acd695d7c657f55107e50a2116.png也可以拿C++的值传递与Java的对象传递方式做个对比。也许从表面语法形式上来看,二者更为类似。但实际差别很大。

71d7a77ebff83f68bce83191ec155bb1.png

e6ddfd97387af7d4008c697a1fff8e3f.png

在Java程序中是通过“传值语法”传递了一个对象的引用,而在C++程序中是通过“传值语法”传递了一个对象的拷贝,内部调用了拷贝构造函数。因此C++规范中更多强调如何使用const引用来提高性能。(顺带提一下,例子中C++的string,用法上更像是基本类型,而Java的String,则是货真价实的类对象,基于对象与面向对象的区别可见一斑。)

家鸽喜欢的所谓“Java一切皆传值”的说法,对于基本数据类型,指的便是变量值的拷贝,而对于对象(或String、Integer等包装基本类型),指的是对象地址的拷贝,可以理解为所传递的值是对象的地址。

C++的参数传递有传值,传引用,传指针的用法,还可以传多重指针,搭配上const和参数的析构问题,相对复杂很多;C++程序员始终面临着什么情况下什么样的用法比较好这个问题,大神可以写出简明易用的代码,令人叹为观止,而大部分程序员工作多年也搞不清楚,导致项目代码混乱不堪,惨不忍睹。这些在Java程序员眼中都不是问题,没有引用指针等方式反而简单,用JavaBean之类的包装类传参轻松愉快不出问题,中规中矩,虽然有时候包装类太多也会显得不够简洁。

  • 其他

前面说过,C++的很多特性,Java都不支持。比如默认参数。

af9bbc505aa0353144b0ed0571a50f6c.png

8e8bc94ead887365cbeb6c60bc5d9caf.png

有了默认参数,可以灵活而简洁的实现功能,提高代码复用性,用的好会上瘾。但也容易引入很多问题。首先,默认参数搭配函数重载与重写,不易阅读和维护。其次,多处位置声明时,不同位置的默认值可以不同,调用方还可以通过使用自己的声明来“篡改”默认参数,造成混乱,大型项目中这种问题很难排查(你碰上几次就深有体会了)。再次,在未来需要修改默认值时,所有调用的地方都需要一一找出并修改,如果默认值已经在接口中发布,再想要修改几乎是不可能的,这在团队协作研发中是很麻烦的问题(不信可以试一试)。因而有些开发规范(如google规范)中,明确禁止使用默认参数。Java干脆去除这个语法特性了。多重继承、操作符重载、预处理与typedef都不再支持,也是基于类似的原因或考虑。Java更加符合面向对象的思想,goto语句,结构体与联合体等等不再需要了。全局变量与全局函数也没有了,所谓“一切都是对象”,都在类或对象中实现。

C++不支持的Java特性

193bce455e92efe117b5b486c646537d.png

Java有“包”这个概念,而C++没有。C++使用"名字空间"这一机制来完成部分“包”的功能。但相较而言,“包”起了组织程序结构的作用,也提高了程序的组件化程度及复用性。“名字空间”更多则只是防止重名的含义,家鸽个人认为不如“包”用起来方便。

c72d506670a5822955a5dc2d74aaa683.png

49a067b010eb3fbb04783002a0588cb6.png

上面这个程序展示了在程序中使用Java所提供的程序包。这个程序首先定义和初始化了一个整形数组,然后调用java.uitl程序包的Array类所定义的sort方法对这个数组进行排序。

7bab6ef3807c7aa8cc56fffd9908731b.png

上面这个程序中定义了一个名为Module的名字空间,为了在main函数中使用名字空间中所定义的方法,使用using指令。

Java有现成的“包”,而C++程序的模块组织结构,更多需要自己设计与实现。不同项目的结构不一定相同。这在带来自由度的同时,也变的不及Java方便。
  • 接口

接口这一词汇有多重含义,这里指的是狭义上的接口,即公开暴露函数调用约定。Java在语言层面支持接口interface,C++则不支持,家鸽认为这是C++语言设计的一个缺憾。在开发大型项目时,高度抽象的接口设计是不可或缺的。使用C++开发的项目中,多使用抽象类来模拟(或自己实现)接口的机制,借用虚函数表(或由自己实现)接口-实现对应机制,比如微软COM规范中接口的概念与实现。虽然可以根据项目需求来设计与实现自己版本的“接口”,但不同系统的实现不尽相同会带来混乱;实现与使用接口的细节繁多,并不是很方便。

d76e4df74145f069bca93fb62392a28c.png

52c6d0eb037e37af0d40aa519cb49921.png

与接口相关的一个概念是抽象类。C++语言本身不提供接口,实际中使用抽象类来模拟接口(或当接口用)。抽象类这一概念,Java与C++是基本对等的,但具体细节不同,形成了不同的风格。下面这个例子,两种语言对应的类图是一样的。

c8e84fa6fa717f819a474f23b9a81489.png

acb5776feafa86ab050506095f41f205.png

497caa3116755ba9fc27afddd9db5296.png

7b12ab61118e24bb93629ab3ae058de7.png

2d0d6373befa033c9fd9f5f3b557d566.png

f2a56e8e2e19045cdb8d8fbe7f4701be.png

       C++中的抽象类,是通过纯虚函数实现的,读起来相对不那么明确。而且有不为人知的用法,比如与大部分人的理解不同,虽然抽象类不可实例化,但纯虚函数其实是可以有对应的实现的,并且还有方法调用纯虚函数的实现(不要惊讶,家鸽这里没写错),而且这种写法也是有适用场合的。限于篇幅,这里不做深入描述。后续会专写一篇探究虚表的文章。这里只是举例,看一下C++语法的隐晦与诡秘。

6100f82f7727650ddc0cbcf7e0849969.png

331b481c44564a9a017fd03ae71e202c.png

2de1c4bc30dccbb1aa355c926f49d45a.png

bd029bad1da214ffecc6ac1fdf5959de.png

Java中,抽象类和抽象方法必须用abstract关键字修饰,语法标志清晰明确。规定很明白也很容易理解:
  • 一旦类中包含了abstract方法,那类该类必须声明为abstract类;

  • 抽象类不一定有抽象方法,有抽象方法的类一定是抽象类或者是接口;

  • 抽象类不能实例化,只能被继承;

  • 抽象类的子类,要么是抽象类,要么重写抽象类中的所有抽象方法。

     几句话说明白,是不是很清楚,而且也藏不了什么特殊用法。二者的不同风格在这个例子中也有所体现。

另一个相关概念是继承。C++支持多重继承,因而有了菱形继承的问题和虚继承等等概念。这个用法很灵活,也有适用场合。比如需要适配的场景,可以用适配器模式解决问题。它分为两种,类适配器和对象适配器,类适配器用多重继承的方式比较合适。a841d3a9b09803b1733b4404d986a1b8.png但多重继承引来的问题会很多,一般的规范里都不推荐用。这里也不写示例代码了。Java干脆不支持多重继承,但可以实现多个接口,可以通过实现多个接口模拟多重继承,达到类似的效果。这一节举例并在一些具体用法上进行比较,但总体上来说,二者的相似之处还是多于不同之处的;理解并熟悉那些不同之处,可以让你根据实际工作的需要,在二者之间自如切换,而不会在不知不觉中写出问题代码。限于篇幅,二者其他一些重要区别会在本系列后续展开分析。

四、多与少

少即是多

 前几日的国庆阅兵,我们看到了陆军,海军,空军,火箭军,也看到了雷军。中国互联网史的元老人物雷军,在改革开放民营企业家的彩车方阵中,站在花车的C位出席了庆典。

3a3a7ab614b6bb72b108ebe02b6c15a3.png

“专注,极致,口碑,快”是雷军总结的产品经验。雷军认为的“专注"主要是一条,“少即是多”。

      雷军曾看到,传统的软件研发项目过于复杂,干着干着就干偏了方向,所以一定要把事情简化到两、三个月就能做完,而且,这两、三个月做完可能就解决了某一些用户的最核心的需求,然后这件事情就可以干了。

  专注,是很重要的一件事情,是每天需要不断地提醒自己的事情。“少就是多”,通常情况下,时间少,资源少,人也很不够,只能尽可能少做事情,找到关键点,以少胜多。用手机举例,在诺基亚和MOTO的时代,一个手机厂商一年要出60款,甚至100款手机,手机的型号都是很复杂的,没有人记得住哪个产品的名字。

  但从苹果开始,产品型号简单的要死,每个产品型号都特别的简单,但是大家都记住了他的产品型号。“集中所有的精力做一款产品”的成功概率,肯定比“分散精力做100款产品”要高,所以,要集中所有的资源,认认真真做好一两款产品,这就是“专注”。

于是雷军认为"砍掉90%,只做10%,认认真真把那10%做好就足够好"。通常的产品经理,是这个也想做,那个也想做,最后看起来花里胡哨,实际什么都没做好。优秀的产品经理,不是把需求往多了搞,而是不断的砍“需求”,专注于最核心的事情。

e300ea809e358ecfdd0ee7bd3b99dba2.png

早期的建筑大师路德维希·密斯·凡德罗就提出:“Less is more。”这也是他坚持的哲学,他的设计作品中各个细部精简到不可精简的绝对境界,不少作品结构几乎完全暴露,但是它们高贵、雅致,已使结构本身升华为建筑艺术。

老外说的的"architecture"来源于建筑领域。很多优秀的产品,都是简洁的代表。一款好的软件,必然是在满足需求的大前提下,拥有高适应能力和低复杂度的产品。如何控制复杂度就是软件工程的一个核心问题,不论业务架构、功能架构还是技术架构。所谓”复杂“不是“深”,而是“多”。通常的软件常常带着太过繁多的功能项目,没有分类整理,有着太过冗杂的交互。设计人员似乎为了彰显功能的强大,恨不能让屏幕上满是按钮,找个东西很难,仔细分析才发现未必有什么特别厉害的功能。技术实现中需要一次性考虑太多的对象,对象和对象之前的关系也繁多混杂,两两间都可能存在着关联关系,如同一个满是跳线的电路板,维护修改越来越困难,质量也越来越难以保证,这样显然做出不好软件。反观Google的大神们,做的页面只有一个logo和一个搜索框,却可以在十几毫秒内中从全世界包罗万象的海量web信息中获取到相应主题的内容,并完成相关度计算排序等工作;其中的大数据存储、处理与检索技术高难艰深,架构设计却精巧清晰。是“少即是多”的典型代表。

通过上面“形而下”一节的分析,C++和Java的区别,语言层面上归结起来,C++是“多”,而Java是“少”。

C++的多,是有历史原因的。从他诞生那一刻起,它必须兼容C,又要面向对象(或者说基于对象)。随后的发展要求他支持泛型编程。为了保有市场占有率,确保已有领域的优势,它必须足够贴近底层;为了保持活力,又必须吸收现代语言的新特性;与之同时还要保证对历史代码的兼容......搞到后来,语言特性越来越多,理论上什么都可以干;而且一段逻辑,可以用多种写法或不同风格来实现。

什么都可以干,注定了它的强大灵活,作为技术人员,掌握了这种语言,如同手握利器,知识类比迁移起来也容易。但什么都可以干常常意味着什么都容易干不好。就项目而言,针对自己的需求与所属的领域,很多时候常常有更好的选择。

语言特性就越来越多,初学者的门槛也越来越高,很少有人敢说自己“精通”C++。这严重影响了它的使用与推广。

5f8fadd6ced55bacebd88156504ba2e6.png

Java就是那个“少”。他的设计者吸取了C++语言在项目中遇到的问题,砍掉了容易引入出问题的语言特性,变的“少”起来;在语言层面,一些事情的做法也变的相对单一化了。让开发、维护与质量保证变得相对简单,研发效率大幅提升。虽然这样做也会损失一些性能,使之不再适合做一些领域的事情。但在它擅长的领域(网络,Web, 企业级应用等),它成为经久不衰的语言,并为后来者(如C#)所仿效。如果把语言当成一种产品,从受欢迎程度和使用率上来说,Java显然比C++要成功。不少C++程序员并没有多喜欢C++,而是项目类型和需求决定,必须得使用它。

通常3年的Java程序员,多在讨论各种框架,API,设计模式乃至架构思想,可以引入使用成熟的包搭建大数据存储与处理环境,进行分布式与高并发编程。而通常3年的C++程序员,对开发规范还没有充分理解,经常碰到不明白的语法特性,在各种奇怪的问题中被考验着细致与耐心。这就是“少即是多”。

4e4c7750883420ba34cfa5f431b6a663.png0fa65cabd62f610806ed04e74615c9d1.png对个人发展而言,艺不压身,在能够透彻而全面理解的前提下,“多”会有“多”的优势,但不能拿这个标准来要求所有人。对团队而言,新同学需要从“少”开始学起。从这个角度说,“C++规范”起的作用就是:在充分考虑项目需求与具体情况的前提下,通过限制容易出问题的特性和写法,让风格与特性由多变少,从而达到代码复杂度控制与质量保证的效果。在学习C++11及更新的标准时也可以看到,当代C++会不断的从其他语言中汲取思想和新特性。我们如果有个参照对比和宏观视角,就会更容易学习与理解。而对于全新的项目,最好是完全摒弃老旧的写法与特性,新同学更应该从新标准学起,使用一个少而美的子集。To Be Continued......

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/472772.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

天池 在线编程 圣杯咒语

文章目录1. 题目2. 解题1. 题目 一段英文中同时出现大小写的字母中最大的那一个&#xff0c;现在给你一段英文&#xff0c;你能把这个字母找出来嘛&#xff1f; ps:一定存在答案 英文长度不会超过100000 样例 1&#xff1a; 输入&#xff1a;"aAbb" 输出&#xf…

crawler_java_数据平台结构

大数据生态架构 转载于:https://www.cnblogs.com/cphmvp/p/4105674.html

天池 在线编程 卡牌游戏(01背包)

文章目录1. 题目2. 解题1. 题目 你跟你的朋友在玩一个卡牌游戏&#xff0c;总共有 n 张牌。 每张牌的成本为 cost[i] 并且可以对对手造成 damage[i] 的伤害。 你总共有 totalMoney 元并且需要造成至少 totalDamage 的伤害才能获胜。 每张牌只能使用一次&#xff0c;判断你是否…

bean validation校验方法参数_Spring Boot 之使用 validation 验证参数

前言文本已收录至我的GitHub仓库&#xff0c;欢迎Star&#xff1a;https://github.com/bin392328206/six-finger种一棵树最好的时间是十年前&#xff0c;其次是现在我知道很多人不玩qq了,但是怀旧一下,欢迎加入六脉神剑Java菜鸟学习群&#xff0c;群聊号码&#xff1a;54968483…

LeetCode 891. 子序列宽度之和(数学)

文章目录1. 题目2. 解题1. 题目 给定一个整数数组 A &#xff0c;考虑 A 的所有非空子序列。 对于任意序列 S &#xff0c;设 S 的宽度是 S 的最大元素和最小元素的差。 返回 A 的所有子序列的宽度之和。 由于答案可能非常大&#xff0c;请返回答案模 10^97。 示例&#x…

hive建表语句_Hive数据如何同步到MaxCompute之实践讲解

摘要&#xff1a;本次分享主要介绍 Hive数据如何迁移到MaxCompute。MMA(MaxCompute Migration Assist)是一款MaxCompute数据迁移工具&#xff0c;本文将为大家介绍MMA工具的功能、技术架构和实现原理&#xff0c;再通过实际操作MMA&#xff0c;演示将Hive数据迁移到MaxCompute。…

Java 包及访问权限

文章目录1. package 包2. import3. JDK常见的包4. 访问权限5. jar 压缩1. package 包 作用&#xff1a;防止不同的人写的类的名称相同冲突了&#xff0c; package testPackage1.java; class testPg {public String talk(){return "talk()";} } class testAnother{p…

java phaser用法_第3章Phaser的使用-(java并发编程核心方法与框架)

3.1 Phaser的使用3.2 类Phaser的arriveAndAwaitAdvance()方法测试13.3 类Phaser的arriveAndAwaitAdvance()方法测试23.4 类Phaser的arriveAndDeregister()方法测试3.5 类Phaser的getPhase()和onAdvance()方法测试3.6 类Phaser的getRegisteredParties()方法和register()测试3.7 …

html选项卡_适用于Mac的最佳HTML文本编辑器,编码开发必备

一个好的文本编辑器对于想要建立网站或进行编码的任何人都是必不可少的。这对于习惯于面面俱到的环境的Mac开发人员而言更加明显。对于他们来说存在一系列不同的优质软件之选。今天小编将介绍几款Mac上好用的HTML文本编辑器&#xff0c;对于每一款文本编辑器&#xff0c;小编将…

java 判断请求为 ajax请求_Java过滤器处理Ajax请求,Java拦截器处理Ajax请求,java 判断请求是不是ajax请求...

Java过滤器处理Ajax请求&#xff0c;Java拦截器处理Ajax请求&#xff0c;拦截器Ajax请求java 判断请求是不是ajax请求&#xff0c;Java判断是否为ajax请求>>>>>>>>>>>>>>>>>>>>>>>>>>>>&…

java set hashcode_Java学习笔记_180724_HashSet_hashCode()

HashSet泛型E必须重写hashCode方法&#xff0c;否则会导致add结果与预期不符如class NewClass{String s;public NewClass(String s){this.ss;}overridepublic int hashCode(){//此处返回字符串s的hashCode()return s.hashCode();}}NewClass n new NewClass("1");New…

LeetCode 1773. 统计匹配检索规则的物品数量

文章目录1. 题目2. 解题1. 题目 给你一个数组 items &#xff0c;其中 items[i] [typei, colori, namei] &#xff0c;描述第 i 件物品的类型、颜色以及名称。 另给你一条由两个字符串 ruleKey 和 ruleValue 表示的检索规则。 如果第 i 件物品能满足下述条件之一&#xff0…

LeetCode 1774. 最接近目标价格的甜点成本(DFS / 01背包)

文章目录1. 题目2. 解题1. 题目 你打算做甜点&#xff0c;现在需要购买配料。目前共有 n 种冰激凌基料和 m 种配料可供选购。而制作甜点需要遵循以下几条规则&#xff1a; 必须选择 一种 冰激凌基料。可以添加 一种或多种 配料&#xff0c;也可以不添加任何配料。每种类型的配…

线程间通信的几种方法_并发编程中的线程间通信

线程通信的目标是使线程间能够互相发送信号。另一方面&#xff0c;线程通信使线程能够等待其他线程的信号。线程通信常用的方式有:wait/notify 等待Volatile 内存共享CountDownLatch 并发工具使用 ReentrantLock 结合 Condition基本LockSupport实现线程间的阻塞和唤醒方式一&am…

【IOS】Target membership

Target membership是指XCode中&#xff0c;一个文件属于哪一个工程&#xff0c;在XCode左侧的工程面板中选中一个文件&#xff0c;在XCode右侧的属性面板中会显示其Target Membership&#xff0c;如下图。 当前的文件AppDelegate.m属于书谱这个Target。 Target Membership的一些…

LeetCode 1775. 通过最少操作次数使数组的和相等(贪心+双指针)

文章目录1. 题目2. 解题1. 题目 给你两个长度可能不等的整数数组 nums1 和 nums2 。 两个数组中的所有值都在 1 到 6 之间&#xff08;包含 1 和 6&#xff09;。 每次操作中&#xff0c;你可以选择 任意 数组中的任意一个整数&#xff0c;将它变成 1 到 6 之间 任意 的值&am…

LeetCode 1776. 车队 II(单调栈)

文章目录1. 题目2. 解题1. 题目 在一条单车道上有 n 辆车&#xff0c;它们朝着同样的方向行驶。 给你一个长度为 n 的数组 cars &#xff0c;其中 cars[i] [positioni, speedi] &#xff0c;它表示&#xff1a; positioni 是第 i 辆车和道路起点之间的距离&#xff08;单位&…

wpf计算字符大小占像素_LCD作为终端显示字符串的过程

LCD作为终端显示字符串的过程1.本文目的2.资源评估3.显示原理4.嵌入式上汉字处理5.结果验证与展示6.总结1.本文目的做嵌入式图形开发&#xff0c;我们往往都会利用到各种GUI进行交互设计&#xff0c;但是对于GUI的字符串处理与中文字库显示&#xff0c;也许并不会特别关注&…

商城简单类图

转载于:https://www.cnblogs.com/stit/p/4125095.html

交换机的基本配置实验报告_无线网络设计配置即实验报告

工程师ACK接到一个小型图书馆的网络组建项目&#xff0c;根据目前流行的网络组建以及项目地点的使用需要便捷性&#xff0c;采用AC控制的瘦AP模式。不需要AC来管理网络的AP是胖AP需要单独配置&#xff0c;有多少个AP就要配置多少次。组建模式&#xff1a;一楼划分20个VLAN,二楼…