注册 | 登录
收藏 | 帮助
热门文章
编辑推荐
相关文章  
模块复用——c++类、dll和com
获取ACCESS2000数据库中的所有表
C++语言常见问题解答(1)
C++语言常见问题解答(3)
C++语言常见问题解答(4)
在c++程序中重启自己的一种方法
您现在的位置: 顶尖设计 >> IT学院 >> 编程开发 >> C >> 文章正文
C++语言常见问题解答(2)
作者:中译者:叶秉哲  来源:永远的UNIX  点击:  更新:2006-12-19
简介:
== Part 2/4  ============================ 

============================= 
■□ 第9节:自由记忆体管理 
============================= 
 
Q33:"delete p" 会删去 "p" 指标,还是它指到的资料,"*p" ? 
 
该指标指到的资料。 
 
"delete" 真正的意思是:「删去指标所指到的东西」(delete the thing pointed 
to by)。同样的英文误用也发生在 C 语言的「『释放』指标所指向的记忆体」上 
("free(p)" 真正的意思是:"free_the_stuff_pointed_to_by(p)" )。 
 
======================================== 
 
Q34:我能 "free()" 掉由 "new" 配置到的、"delete" 掉由 "malloc()" 配置到的 
     记忆体吗? 
 
不行。 
 
在同一个程式里,使用 malloc/free 及 new/delete 是完全合法、合理、安全的; 
但 free 掉由 new 配置到的,或 delete 掉由 malloc 配置到的指标则是不合法、 
不合理、该被痛骂一顿的。 
 
======================================== 
 
Q35:为什麽该用 "new" 而不是老字号的 malloc() ? 
 
建构子/解构子、型别安全性、可被覆盖(overridability)。 
 
建构子/解构子:和 "malloc(sizeof(Fred))" 不同,"new Fred()" 还会去呼叫 
Fred 的建构子。同理,"delete p" 会去呼叫 "*p" 的解构子。 
 
型别安全性:malloc() 会传回一个不具型别安全的 "void*",而 "new Fred()" 则 
会传回正确型态的指标(一个 "Fred*")。 
 
可被覆盖:"new" 是个可被物件类别覆盖的运算子,而 "malloc" 不是以「各个类别 
」作为覆盖的基准。 
 
======================================== 
 
Q36:为什麽 C++ 不替 "new" 及 "delete" 搭配个 "realloc()" ? 
 
避免你产生意外。 
 
当 realloc() 要拷贝配置区时,它做的是「逐位元 bitwise」的拷贝,这会弄坏大 
部份的 C++ 物件。不过 C++ 的物件应该要能自我拷贝才对:用它们自己的拷贝建构 
子或设定运算子。 
 
======================================== 
 
Q37:我该怎样配置/释放阵列? 
 
用 new[] 和 delete[] : 
 
        Fred* p = new Fred[100]; 
        //... 
        delete [] p; 
 
每当你在 "new" 运算式中用了 "[...]",你就必须在 "delete" 陈述中使用 "[]"。 
                                         ^^^^ 
这语法是必要的,因为「指向单一元素的指标」与「指向一个阵列的指标」在语法上 
并无法区分开来。 
 
======================================== 
 
Q38:万一我忘了将 "[]" 用在 "delete" 由 "new Fred[n]" 配置到的阵列,会发生 
     什麽事? 
 
灾难。 
 
这是程式者的--而不是编译器的--责任,去确保 new[] 与 delete[] 的正确配 
对。若你弄错了,编译器不会产生任何编译期或执行期的错误讯息。堆积(heap)被 
破坏是最可能的结局,或是更糟的,你的程式会当掉。 
 
======================================== 
 
Q39:成员函数做 "delete this" 的动作是合法的(并且是好的)吗? 
 
只要你小心的话就没事。 
 
我所谓的「小心」是: 
 
  1) 你得 100% 确定 "this" 是由 "new" 配置来的(而非 "new[]",亦非自订的 
     "new" 版本,一定要是最原始的 "new")。 
 
  2) 你得 100% 确定该成员函数是此物件最後一个会呼叫到的。 
 
  3) 做完自杀的动作 ("delete this;") 後,你不能再去碰 "this" 的物件了,包 
     括资料及运作行为在内。 
 
  4) 做完自杀的动作 ("delete this;") 後,你不能再去碰 "this" 指标了。 
     换句话说,你不能查看它、将它与其他指标或是 NULL 相比较、印出其值、 
     对它转型、对它做任何事情。 
 
很自然的,这项警告也适用於:当 "this" 是个指向基底类别的指标,而解构子不是 
virtual 的场合。 
 
======================================== 
 
Q40:我该怎麽用 new 来配置多维阵列? 
 
有很多方法,端视你对阵列大小的伸缩性之要求而定。极端一点的情形,如果你在编 
译期就知道所有阵列的维度,你可以静态地配置(就像 C 一样): 
 
        class Fred { /*...*/ }; 
 
        void manipulateArray() 
        { 
          Fred matrix[10][20]; 
 
          //使用 matrix[i][j]... 
 
          //不须特地去释放该阵列 
        } 
 
另一个极端情况,如果你希望该矩阵的每个小块都能不一样大,你可以在自由记忆体 
里配置之: 
 
        void manipulateArray(unsigned nrows, unsigned ncols[]) 
        //'nrows' 是该阵列之列数。 
        //所以合法的列数为 (0, nrows-1) 开区间。 
        //'ncols[r]' 则是 'r' 列的行数 ('r' 值域为 [0..nrows-1])。 
        { 
          Fred** matrix = new Fred*[nrows]; 
          for (unsigned r = 0; r < nrows; ++r) 
            matrix[r] = new Fred[ ncols[r] ]; 
 
          //使用 matrix[i][j]... 
 
          //释放就是配置的反动作: 
          for (r = nrows; r > 0; --r) 
            delete [] matrix[r-1]; 
          delete [] matrix; 
        } 
 
======================================== 
 
Q41:C++ 能不能做到在执行时期才指定阵列的长度? 
 
可以。STL 有一个 vector template 提供这种行为。请参考“程式库”一节的 STL 
项目。 
 
不行。内建的阵列型态必须在编译期就指定它的长度了。 
 
可以,内建的阵列可以在执行期才指定第一个索引的□围。譬如说,和上一则 FAQ 
相较,如果你只需要第一个维度大小能够变动,你可以 new 一个阵列的阵列(而不 
是阵列指标的阵列 "an array of pointers to arrays"): 
 
        const unsigned ncols = 100; 
        //'ncols' 不是执行期才决定的变数 (用来代表阵列的行数) 
 
        class Fred { ... }; 
 
        void manipulateArray(unsigned nrows) 
        //'nrows' 是执行期才决定的变数 (用来代表阵列的列数) 
        { 
          Fred (*matrix)[ncols] = new Fred[nrows][ncols]; 
 
          //用 matrix[i][j] 来处理 
 
          //deletion 是物件配置的逆运算: 
          delete [] matrix; 
        } 
 
如果你不光是需要在执行期改变阵列的第一个维度的话,就不能这样做了。 
 
======================================== 
 
Q42:怎样确保某类别的物件都是用 "new" 建立的,而非区域或整体/静态变数? 
 
确定该类别的建构子都是 "private:" 的,并定义个 "friend" 或 "static" 函数, 
来传回一个指向由 "new" 建造出来的物件(把建构子设成 "protected:",如果你想 
要有衍生类别的话)。 
 
        class Fred {    //只允许 Fred 动态地配置出来 
        public: 
          static Fred* create()                 { return new Fred();     } 
          static Fred* create(int i)            { return new Fred(i);    } 
          static Fred* create(const Fred& fred) { return new Fred(fred); } 
        private: 
          Fred(); 
          Fred(int i); 
          Fred(const Fred& fred); 
          virtual ~Fred(); 
        }; 
 
        main() 
        { 
          Fred* p = Fred::create(5); 
          ... 
          delete p; 
        } 
 
 
=============================== 
■□ 第10节:除错与错误处理 
=============================== 
 
Q43:怎样处理建构子的错误? 
 
丢出一个例外(throw an exception)。 
 
建构子没有传回值,所以不可能采用它传回的错误码。因此,侦测建构子错误最好的 
方法,就是丢出一个例外。 
 
在 C++ 编译器尚未提供例外处理之前,我们可先把物件置於「半熟」的状态(譬如 
:设个内部的状态位元),用个查询子("inspector")来检查该位元,就可让用户 
查看该物件是否还活著。也可以用另一个成员函数来检查该位元,若该物件没存活 
下来,就做个「没动作」(或是更狠的像是 "abort()" )的程式。但这实在很丑陋。 
 
======================================== 
 
Q44:如果建构子会丢出例外的话,该怎麽处理它的资源? 
 
物件里面的每个资料成员,都该自己收拾残局。 
 
如果建构子丢出一个例外的话,该物件的解构子就“不会”执行。如果你的物件得回 
复些曾做过的事情(像是配置记忆体、开启档案、锁定 semaphore),该物件内的资 
料成员就“必须”记住这个「必须恢复的东西」。 
 
举例来说:不要单单的把配置到的记忆体放入 "Fred*" 资料成员,而要放入一个「 
聪明的指标」(smart pointer) 资料成员中;当该“聪明指标”死掉的话,它的解构 
子就会删去 Fred 物件。 
 
【译注】「聪明的指标」(smart pointer) 在 Q4 中有提到一点。 
 
 
============================= 
■□ 第11节:Const 正确性 
============================= 
 
Q45:什麽是 "const correctness"? 
 
好问题。 
 
「常数正确性」乃使用 "const" 关键字,以确保常数物件不会被更动到。譬如:若 
"f()" 函数接收一个 "String",且 "f()" 想确保 "String" 不会被改变,你可以: 
 
 * 传值呼叫 (pass by value):    void  f(      String  s   )  { /*...*/ } 
 * 透过常数参考 (reference):    void  f(const String& s   )  { /*...*/ } 
 * 透过常数指标 (pointer)  :    void  f(const String* sptr)  { /*...*/ } 
 * 但不能用非常数参考      :    void  f(      String& s   )  { /*...*/ } 
 * 也不能用非常数指标      :    void  f(      String* sptr)  { /*...*/ } 
 
在接收 "const String&" 参数的函数里面,想更动到 "s" 的话,会产生个编译期的 
错误;没有牺牲任何执行期的空间及速度。 
 
宣告 "const" 参数也是另一种型别安全方法,就像一个常数字串,它会“丧失”各 
种可能会变更其内容的行为动作。如果你发现型别安全性质让你的系统正确地运作 
(这是真的;特别是大型的系统),你会发现「常数正确性」亦如是。 
 
======================================== 
 
Q46:我该早一点还是晚一点让东西有常数正确性? 
 
越越越早越好。 
 
延後补以常数正确性,会导致雪球效应:每次你在「这儿」用了 "const",你就得在 
「那儿」加上四个以上的 "const"。 
 
======================================== 
 
Q47:什麽是「const 成员函数」? 
 
一个只检测(而不更动)其物件的成员函数。 
 
        class Fred { 
        public: 
          void f() const; 
        };      // ^^^^^--- 暗示说 "fred.f()" 不会改变到 "fred" 
 
此乃意指:「抽象层次」的(用户可见的)物件状态不被改变(而不是许诺:该物件 
的「每一个位元内容」都不会被动到)。C++ 编译器不会对你许诺「每一个位元」这 
种事情,因为不是常数的别名(alias)就可能会修改物件的状态(把 "const" 指标 
黏上某个物件,并不能担保该物件不被改变;它只能担保该物件不会「被该指标的动 
作」所改变)。 
 
【译注】请逐字细读上面这句话。 
 
"const" 成员函数常被称作「查询子」(inspector),不是 "const" 的成员函数则 
称为「更动子」(mutator)。 
 
======================================== 
 
Q48:若我想在 "const" 成员函数内更新一个「看不见的」资料成员,该怎麽做? 
 
使用 "mutable" 或是 "const_cast"。 
【译注】这是很新的 ANSI C++ RTTI (RunTime Type Information) 规定,Borland 
        C++ 4.0 就率先提供了 const_cast 运算子。 
 
少数的查询子需要对资料成员做些无害的改变(譬如:"Set" 物件可能想快取它上一 
回所查到的东西,以加速下一次的查询)。此改变「无害」是指:此改变不会由物件 
的外部介面察觉出来(否则,该运作行为就该叫做更动子,而非查询子了)。 
 
这类情况下,会被更动的资料成员就该被标示成 "mutable"(把 "mutable" 关键字 
放在该资料成员宣告处前面;也就是和你放 "const" 一样的地方),这会告诉编译 
器:此资料成员允许 const 成员函数改变之。若你不能用 "mutable" 的话,可以用 
"const_cast" 把 "this" 的「常数性」给转型掉。譬如,在 "Set::lookup() const" 
里,你可以说: 
 
        Set* self = const_cast(this); 
 
这行执行之後,"self" 的位元内容就和 "this" 一样(譬如:"self==this"),但 
是 "self" 是一个 "Set*" 而非 "const Set*" 了,所以你就可以用 "self" 去修改 
"this" 指标所指向的物件。 
 
======================================== 
 
Q49:"const_cast" 会不会丧失最佳化的可能? 
 
理论上,是;实际上,否。 
 
就算编译器没真正做好 "const_cast",欲避免 "const" 成员函数被呼叫时,会造成 
暂存器快取区被清空的唯一方法,乃确保没有任何「非常数」的指标指向该物件。这 
种情况很难得会发生(当物件在 const 成员函数被启用的□围内被建立出来;当所 
有非 const 的成员函数在物件建立间启用,和 const 成员函数的启用被静态系结住 
;当所有的启用也都是 "inline";当建构子本身就是 "inline";和当建构子所呼叫 
的任何成员函数都是 inline 时)。 
 
【译注】这一段话很难翻得好(好啦好啦!我功力不足... :-< ),所以附上原文: 
Even if a compiler outlawed "const_cast", the only way to avoid flushing 
the register cache across a "const" member function call would be to 
ensure that there are no non-const pointers that alias the object.  This 
can only happen in rare cases (when the object is constructed in the scope 
of the const member fn invocation, and when all the non-const member 
function invocations between the object's construction and the const 
member fn invocation are statically bound, and when every one of these 
invocations is also "inline"d, and when the constructor itself is "inline"d, 
and when any member fns the constructor calls are inline). 
 
 
===================== 
■□ 第12节:继承 
===================== 
 
Q50:「继承」对 C++ 来说很重要吗? 
 
是的。 
 
「继承」是抽象化资料型态(abstract data type, ADT)与 OOP 的一大分野。 
 
================================

[1] [2] [3] 下一页






  • 上一篇文章:
  • 下一篇文章:
  • 分享此文:该页面添加到 Mister Wong 添加到雅虎Yahoo!收藏 Add to:Del.icio.us Post to Furl Digg this 添加到Google书签 reddit spurl blogmarks 365Key 评论  收藏  分享  打印
     我来说两句
    姓名:       验证码:   
    主页: 
    评分: 1分 2分 3分 4分 5分
    本频道近期热评文章:
      关于我们 | 联系我们 | 站点地图 | 广告投放 | 友情链接 | 在线留言 | 版权申明
    版权所有 © 2004-2007 顶尖设计(bobd.cn)
    未经授权禁止转载,摘编,复制本站内容或建立镜像. 沪ICP备07504942号 
    网络110
    报警服务