![码农修行:编写优雅代码的32条法则](https://wfqqreader-1252317822.image.myqcloud.com/cover/469/37323469/b_37323469.jpg)
法则04:语法潜台词
每种编程语言都有对代码进行约束的一些语法,当然它们还传递着一些附加的信息。
● 函数入参
其中最为常用的应该是C/C++中的const关键字。而在Java中与之对应的是final关键字。
当需要表达一个函数的参数是入参时,可以加上const或final关键字。比如下面的C/C++代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_01.jpg?sign=1739529698-GgylwcoqqpsvDnoJXSag2H4rj2hugvpq-0-25dd3acdc90f73af76cf6b60ed5daa93)
或如下Java代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_02.jpg?sign=1739529698-YREjzJ2zsczbzovNC8VcTruzw6hMistK-0-74c8dadfe3376580126e1eaa8b6775dd)
这种表达方式比下面这样通过注释来说明更有约束力。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_03.jpg?sign=1739529698-p3ATYEuB2vlS60ua5XzN1KzrlFcARVag-0-2d8fc399d7a09251922fa43eb92f3c03)
因为新任的软件设计师稍不注意就修改了cmd的内容,但编译器并不会报错。此外对于Java语言,当函数参数为对象时,都是传引用的方式,效率很高。对于C++语言,当入参是一个对象时,需要再加一个传引用的修饰符“&”。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_04.jpg?sign=1739529698-AspRm8SzU9t1WaljjcdOl5lr0xZcOKCT-0-33e196bec0b5c4072a8e128b6fab51e8)
这样可以免去一次参数传递时的赋值拷贝,提高了效率。特别是入参为数组时,一定要加“&”修饰符(参考“法则14:关注性能热点”)。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/31_05.jpg?sign=1739529698-EQZEYHK66QbETd1pBrYqkzkHLNV6EvAC-0-3d4e7cb03b95ec41558a69c275d092a7)
● 构造函数
对于一个类,其构造函数大部分情况都声明为public,这表明该类的对象可以随时进行实例化,比如下面代码中可以直接定义一个Person的对象。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_01.jpg?sign=1739529698-JaOShJMZ6gllXMToIcqLE7BtkTknEcdm-0-56c7edc7e8096a1c9c4fab664e068722)
如果一个类的构造函数声明为protected,表明该类必须要经过派生后才能够使用。在“法则 13:规避短板”一节中介绍的可重用的基类ProcRuner,其构造函数就声明为protected。代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_02.jpg?sign=1739529698-O3USbhvmwjedYoUR8Iz59qlXjjzOpAXB-0-9b146aef6cbf2ada2ec94f48ad131c40)
因为该类必须经过派生后才能复用,派生类必须重写两个虚函数。代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_03.jpg?sign=1739529698-JI0AzOQNxXW88uXY0tapJ6nbLnKoIU93-0-e42288a4663970e35e627d55b15d8cbb)
如果一个类的构造函数被声明为private,那说明它既不能被派生,也不能被直接实例化,例如“法则 31:灵活注入对象”中的ModuleMgr类。它很可能为单例(singleton)模式,同时会提供一个配套的静态成员函数GetInst以供获取该类的全局唯一实例。代码如下。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/32_04.jpg?sign=1739529698-kRPqdGgImh2jz8QSpadl5oTQb0JuzenF-0-3710a14915b2301f9fa424b99286f65f)
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_01.jpg?sign=1739529698-xuHkuW3A87TA3aL6B4YlOE4RIMi1OY1u-0-53ec4abd8fccedf873de0f94072a437f)
● 公有继承
此外对于继承体系,也有其内在的表达。公有继承传递的信息是“is-a”的关系。比如派生类Eagle公有继承自基类Bird,C++语言中使用public关键字表示公有继承。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_02.jpg?sign=1739529698-QKNHJEnGuEcb48dTa96zUXY0Q7g8NN94-0-9e674db2182e93e8e3cb9c9ec8fc70cd)
对于Java语言,使用extends表示公有继承。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_03.jpg?sign=1739529698-LMbQxZHxeCio5Xh23u8JKmhKgaCe0bL8-0-e39a55df8ab7d1729e0dd571974988c5)
这是面向对象设计中非常重要的一个概念。比如上面代码描述的信息是:鹰“是一种”鸟类。当然,读到这里希望读者不要浅尝辄止,有两个要点需要特别注意。
第一:对“是”字的理解要正确,“是”字不能理解为“等于”。比如下面这个派生关系。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_04.jpg?sign=1739529698-FlBetyArJOv5ubDG34w8uYojv29aERWT-0-704fb2fc760c1e43c0562d0950b63f9a)
马和牛都派生自动物类,曾经有人热烈地讨论过CompareAnimal函数,如下所示。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_05.jpg?sign=1739529698-eiix6IjiVMrXU0FfPcsGst3N3pPn8WGf-0-8139447cbb62ae2e53db8467dd03f8a8)
当传入一个Horse和一个Bull的对象时,该CompareAnimal又该如何实现?
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/33_06.jpg?sign=1739529698-FaTwpdbVPIuGJ80Nd4TlogFBei8U9fkr-0-e1ef0366ff179c8d85121b091cf91a9a)
比体重还是比身高呢?因为马和牛没有可比性,因此这样的比较没有意义。但是他们简单地认为马“等于”动物,牛“等于”动物,既然都相等,就应该能拿来进行比较。
正确的理解应该为:“是”指的是一种被包含关系,是“属于”的意思,即前者“属于”后者的一类。比如我们所描述的学生是人、小轿车是车,都是指前者属于后者。即:学生属于人、小轿车属于车。
第二:公有继承的派生类和基类必须满足里氏替换原则。即所有使用基类对象的地方,都可以使用派生类对象进行替换。比如下面Android框架中,自定义的MyActivity派生自Activity。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/34_01.jpg?sign=1739529698-FesulpPQ9RRt2yoA0JFE4ANNX6ZGQhmZ-0-4a2c01f12805be800c29b3e896a8eb06)
系统启动时重写的onCreate方法会被回调,就是因为满足了这一原则。框架代码中操作的是Activity类,但传入的实际上是MyActivity的对象。以下代码描述了主要调用关系,剔除了无关代码。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/34_02.jpg?sign=1739529698-0rE1Tv4ByxJlB0WQ6XcKrsK9gSKqw8Dp-0-c4b043dfac0481c51a00a64598ff130f)
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/35_01.jpg?sign=1739529698-hAsUzzsrfpvLO2kri2mJKOrUzUQNWkOs-0-d4628bd4e57ed6b405c0df8028bcbd4e)
当然,如果不满足里氏替换原则,则不能使用公有继承。比如下面Penguin(企鹅)类的派生关系。
![](https://epubservercos.yuewen.com/CBBFE3/19773740908833406/epubprivate/OEBPS/Images/35_02.jpg?sign=1739529698-08rJr70I1kw4x1MU8DRFROdmEOxq5syF-0-a8a1d3323fcfe2516d148ba4626c75d6)
因为企鹅不会飞,因此不能给Penguin类定义fly函数。由于作用于Bird类上的fly函数不能作用于Penguin类,不满足里氏替换原则,因此不能使用公有继承描述二者的关系。
需要特别注意的是,一些在现实中看似合理的派生关系,用代码描述不一定合理。在“法则 25:正确理解面向对象设计”一节中将对这个话题展开进行讨论。