c++经典问答

C++  的优点是什麽?
「封装性  encapsulation」:藉由隐藏内部的资料结构,让我们可以改变系统的某部份,而不必更动其他部份。我们为软体元件(称之为  class,类别)提供一个安全的介面,用户只碰得到这个介面而已;而相对起来比较容易变动的介面「实作」部份,就被封装起来(就像被包在胶囊里),以避免用户过於依赖他一时的实作决定。在比较简单的  C  里头,可由模组内的静态(static)资料来办到,以避免其他模组存取
到它。   

 

C++ 对  ANSI-C  回溯相容吗?  
几乎是。C++  尽可能地和  C  相容,但不能更相容了。事实上,主要的不同在於  C++  要求函数原型:"f()"  宣告的是无参数的函数(在  C  里,"f()"  和  "f(...)"  是一样的)。还有些细微的差别,像在  C++  里  sizeof('x')  等同於  sizeof(char),但在  C  里
面却是等同於  sizeof(int)。  而且,C++  直接就把结构的标签(tag)当成是型别的名字,但  C  就需要加个  "struct"  字("typedef  struct  Fred  Fred"  这种技巧仍然能用,但在  C++  中是累赘的)。 

 

什麽是物件(object)?
一块赋有某种语意的储存空间。在宣告  "int  i;"  之後,我们称「i  是个  int  型态的物件」。在  C++/OOP  里,「物件」通常意指「类别的案例(an  instance  of  a  class)」,因此类别定义了数个物件(案例)的行为。

 

如果设定某值给参考会怎麽样?
会更动到被参考者(referrent,该「参考」所参考到的物件)。记住:「参考」就是「被参考者」,因此动了参考就会改动到被参考者(「参考」是「被参考者」的左值  "Lvalue"〔出现在设定陈述的左边〕)。更进一步,我们也允许参考被传回。这样子函数呼叫就可放在设定陈述的左边,这对运算子多载的场合很有用。  

 

怎样才能将参考改设成别的物件?   
没有办法。和指标不同,一旦参考被系结到某个物件,它就不能再被改设到其他物件去。「参考」本身不是一个物件(它自己没有位址;「取参考的位址」只会得到被参考者的位址;切记:「参考」就是「被参考者」)。 将「参考」与「被参考者」分离开来是不可能的。   

 

何时该用参考,何时又该用指标?
可以时,用参考;必要时,就用指标。当你不需要“重设”它时(见前一个问题),参考会比指标好。这通常意味著:在物件类别的公共介面中参考最有用。参考大多用於物件的表层,而指标则多用於里层。但有一个例外:当函数参数或传回值需要一个「临界」(sentinel)的参考值时,最好是用指标来做,以  NULL  指标做为一个特别值(「参考」应该是个实质物件的「别名」,而不是个解参用的〔dereferenced〕NULL  指标)。   

 

建构子(constructor)是做什麽的?
建构子乃用来从零开始建立物件。建构子就像个「初始化函数」;它把一堆散乱的位元组成一个活生生的物件。最低限度它会初始化内部用到的栏位,也可能会配置所须的资源(记忆体、档案、semaphore、socket  等等)。"ctor"  是建构子  constructor  最常见的缩写。   

怎样才能让建构子呼叫另一个同处一室的建构子?  
没有办法。原因是:如果你呼叫另一个建构子,编译器会初始化一个暂时的区域性物件;但并没有初始化“这个”你想要的物件。你可以用预设参数(default  parameter),将两个建构子合并起来,或是在私有的  "init()"  成员函数中共享它们的程式码。   

 

解构子(destructor)是做什麽的?
解构子乃物件之葬礼。解构子是用来释放该物件所配置到的资源,譬如:Lock  类别可能会锁住一个semaphore,解构子则用来释放它。最常见的例子是:当建构子用了  "new"  以後,解构子用  "delete"。解构子是个「去死吧」的运作行为(method),通常缩写为  "dtor"。   

 

怎样做一个  "**"「次方」运算子?
无解。运算子的名称、优先序、结合律以及元数(arity)都被语言所定死了。C++  里没有"**"  运算子,所以你无法替类别订做一个它。还怀疑的话,考虑看看  "x  **  y"  和  "x  *  (*y)",这两者是完全一样的(换句话说,编译器会假设  "y"  是个指标)。此外,运算子多载只是函数呼叫的语法糖衣而已,虽然甜甜的,但本质上并未增加什麽东西。我建议你多载  "pow(base,exponent)"这个函数(它的倍精确度版本在中)。附带一提:operator^  可以用,但它的优先序及结合律不符「次方」所需。   

 

「夥伴」违反了封装性吗?   
若善用之,反而会「强化」封装性。我们经常得将一个类别切成两半,当这两半各有不同的案例个数及生命期时。在此情形之下,它们通常需要直接存取对方的内部(这两半“本来”是在同一个类别里面,所以你并未“增加”存取资料结构的运作行为个数;你只是在“搬动”这些运作行为 
所在之处而已)。最安全的实作方式,就是让这两半互为彼此的「夥伴」。   

 

「夥伴关系无继承及递移性」是什麽意思?
夥伴关系的特权性无法被继承下来:夥伴的衍生类别不必然还是夥伴(我把你当朋友,但这不代表我也一定会信任你的孩子)。如果  "Base"  类别宣告了  "f()"  为它的夥伴,"f()"  并不会自动对由  "Base"  衍生出来的  "Derived"  类别所多出来的部份拥有特殊的存取权力。夥伴关系的特权无递移性:夥伴类别的夥伴不必然还是原类别的夥伴(朋友的朋友不一定也是朋友)。譬如,如果  "Fred"  类别宣告了"Wilma"  类别为它的夥伴,而且"Wilma"  类别宣告了  "f()"  为它的夥伴, 则  "f()"  不见得对  "Fred"  有特殊的存取权力。   

 

"delete  p"  会删去  "p"  指标,还是它指到的资料,"*p" ?
该指标指到的资料。"delete"  真正的意思是:「删去指标所指到的东西」(delete  the  thing  pointedto  by)。同样的英文误用也发生在  C  语言的「『释放』指标所指向的记忆体」上("free(p)"  真正的意思是:"free_the_stuff_pointed_to_by(p)"  )。   

 

为什麽该用  "new"  而不是老字号的  malloc() ?  
建构子/解构子、型别安全性、可被覆盖(overridability)。建构子/解构子:和  "malloc(sizeof(Fred))"  不同,"new  Fred()"  还会去呼叫Fred  的建构子。同理,"delete  p"  会去呼叫  "*p"  的解构子。型别安全性:malloc()  会传回一个不具型别安全的  "void*",而  "new  Fred()"  则会传回正确型态的指标(一个  "Fred*")。可被覆盖:"new"  是个可被物件类别覆盖的运算子,而  "malloc"  不是以「各个类别」作为覆盖的基准。   
  
我该怎样配置/释放阵列?  
用  new[]  和  delete[]  :
Fred* p = new Fred[100]; 
//...  
delete [] p;
每当你在  "new"  运算式中用了  "[...]", 你就必须在  "delete"  陈述中使用  "[]"。这语法是必要的,因为「指向单一元素的指标」与「指向一个阵列的指标」在语法上并无法区分开来。   

怎样确保某类别的物件都是用  "new"  建立的,而非区域或整体/静态变数?   
确定该类别的建构子都是  "private:"  的, 并定义个  "friend"  或  "static"  函数,来传回一个指向由  "new"  建造出来的物件(把建构子设成  "protected:",如果你想要有衍生类别的话)。   

 

怎样处理建构子的错误?   
丢出一个例外(throw  an  exception)。

 

我该早一点还是晚一点让东西有常数正确性?
越越越早越好。   

 

什麽是「const  成员函数」?
一个只检测(而不更动)其物件的成员函数。   

 

何时该用继承?  
做为一个「特异化」(specialization)  的机制。人类以两种角度来抽象化事物:「部份」(part-of)  和「种类」(kind-of)。福特汽车“是一种”(is-a-kind-of-a)  车子,福特汽车“有”(has-a)  引擎、轮胎……等等零件。「部份」的层次随著  ADT  的流行,已成为软体系统的一份子了;而「继承」则添入了“另一个”重要的软体分解角度。   

 

我该遮蔽住由基底类别继承来的公共成员函数吗?  
绝对绝对绝对绝对不要这样做!想去遮蔽(删去、撤消)掉继承下来的公共成员函数,是个很常见的错误。这通常是脑袋塞满了浆糊的人才会做的傻事。   

 

当我改变了内部的东西,怎样避免子类别被破坏?
物件类别有两个不同的介面,提供给不同种类的用户:   
  *  "public:"  介面用以服务不相关的类别。   
  *  "protected:"  介面用以服务衍生的类别。
除非你预期所有的子类别都会由你们的工作小组建出来,否则你应该将基底类别的资料位元内容放在  "private:"  处,用  "protected:"  行内存取函数来存取那些资料。这样的话,即使基底类别的私有资料改变了,衍生类别的程式也不会报废,除非你改变了基底类别的protected 处的存取函数。   

 

若基底类别的建构子呼叫一个虚拟函数,为什麽衍生类别覆盖掉的那个虚拟函数却不会被呼叫到?   
在基底类别  Base  的建构子执行过程中,该物件还不是属於衍生  Derived  的,所以如果  "Base::Base()"  呼叫了虚拟函数  "virt()",则  "Base::virt()"  会被呼叫,即使真的有  "Derived::virt()"。类似的道理,当  Base  的解构子执行时,该物件不再是个  Derived  了,所以当Base::~Base()  呼叫  "virt()",则  "Base::virt()"  会被执行,而非覆盖後的版本"Derived::virt()"。

 

分离介面与实作是做什麽用的?
介面是企业体最有价值的资源。设计介面会比只把一堆独立的类别拼凑起来来得耗时,尤其是:介面需要花费更高阶人力的时间。既然介面是如此重要,它就应该保护起来,以避免被资料结构等等实作细节之变更所影响。因此你应该将介面与实作分离开来。   

 

何时该把解构子弄成  virtual?  
当你可能经由基底的指标去  "delete"  掉衍生的类别时。

 

为什麽我的执行档会这麽大?
很多人对这麽大的执行档感到惊讶,特别是当原始码只有一点点而已。例如一个简单的  "hello  world"  程式居然会产生大家都想不到的大小(40+K  bytes)。一个原因是:有些  C++  执行期程式库被连结进去了。有多少被连结进去,就要看看你用到多少,以及编译器把程式库切割成多少块而定。例如,iostream  很大,包含一大堆类别及虚拟函数,即使你只用到一点点,因为各元件之间的交互参考依存关系,可能会把整个  iostream  程式码都塞进来了。(【译注】如果  linker  做得好的话,应该能把完全用不到的元件  object  code  砍掉,不随之塞入你的执行档中。)不要用静态的,改用动态连结的程式库版本,就可以使你的程式变小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值