注册 登录
编程论坛 C++教室

推荐:C++语言常见问题解答

yuyunliuhen 发布于 2006-12-23 22:28, 6333 次点击

这些是我在网上看到一些资料,感觉很有用,并把他与大家一起分享。很长,不必所有的都看,选自己比较感觉有兴趣的就好。希望对大家有所帮助。
下面是C++语言常见问答:有兴趣的可以看看,也可以做为查询的依据!
下面的东西都包括在这里:
只有本站会员才能查看附件,请 登录



   

[此贴子已经被作者于2006-12-27 21:49:56编辑过]

120 回复
#102
yuyunliuhen2006-12-23 22:56
为什麽当我没执行 BC45 IDE 的话,BC++ 做出来的 Windows 应用程式就不能 
       用? 
 
用 BC++ 写 Windows 应用程式,如果当 BC45 IDE 正在执行时,你的程式很正常; 
待会儿当 BC45 IDE 关掉了,而你的程式却在建立视窗时产生了个 exception 的话 
,就把底下这行程式加到你的应用程式类别 ("YourApp::InitMainWindow()") 里头 
的 InitMainWindow() 内: 
 
         EnableBWCC(TRUE); 
 
【译注】这是因为你用 BC++ 写的应用程式,可能会自动用到 bwcc*.dll,刚好 
         BC++ 的 IDE 也会用到它,所以两者并存的话,BWCC 已先被 IDE 载入了。 
         若是 IDE 未执行,则 BWCC 未被载入,你就得用上面那一行程式来通知 
         OWL 去载入它。 
#103
yuyunliuhen2006-12-25 22:16
怎样从 C++ 中呼叫 C 的函数 "f(int,char,float)"? 
 
告诉 C++ 编译器说:它是个 C 的函数: 
        extern "C" void f(int,char,float); 
 
确定你有 include 进来完整的函数原型 (function prototype)。一堆 C 的函数可 
以用大括号框起来,如下: 
 
        extern "C" { 
          void* malloc(size_t); 
          char* strcpy(char* dest, const char* src); 
          int   printf(const char* fmt, ...); 
        } 
#104
yuyunliuhen2006-12-26 21:39
应该替类别宣告个成员函数,还是夥伴函数? 
 
可能的话,用成员函数;必要时,就用夥伴。 
 
有时在语法上来看,夥伴比较好(譬如:在 "Fred" 类别中,夥伴函数可把 "Fred" 
弄成是第二个参数,但在成员函数中则一定得放在第一个)。另一个好例子是:二元 
中序式算数运算子(譬如:"aComplex + aComplex" 可能应该定义成夥伴而非成员函 
数,因为你想让 "aFloat + aComplex" 这种写法也能成立;回想一下:成员函数无 
法提升它左侧的参数,因为那样会把引发该成员函数的物件所属之类别给改变掉)。 
 
在其他情况下,请选成员函数而不要用夥伴函数。 
 
#105
programer2006-12-26 22:13
到这里还没完...
县收下了。谢lz
#106
yuyunliuhen2006-12-27 21:54

我能 "free()" 掉由 "new" 配置到的、"delete" 掉由 "malloc()" 配置到的 
      记忆体吗? 
 
不行。 
 
在同一个程式里,使用 malloc/free 及 new/delete 是完全合法、合理、安全的; 
但 free 掉由 new 配置到的,或 delete 掉由 malloc 配置到的指标则是不合法、 
不合理、该被痛骂一顿的。 

[此贴子已经被作者于2006-12-29 22:42:21编辑过]

#107
shizhusz1102006-12-28 17:21

顶!!!谢谢楼主.

#108
yuyunliuhen2006-12-29 22:44
什麽是参考(reference)? 
 
一个物件的“别名”(alias,另一个名称)。 
 
参考通常用於传址呼叫(pass-by-reference): 
 
        void swap(int& i, int& j) 
        { 
          int tmp = i; 
          i = j; 
          j = tmp; 
        } 
 
        main() 
        { 
          int x, y; 
          //... 
          swap(x,y); 
        } 
 
在这里 "i" 和 "j" 分别是是 main 函数中 "x" 与 "y" 的别名,换句话说,"i" 就 
是 "x"--不是个指向 "x" 的指标,也不是 "x" 该值的复制品,而它的的确确就是 
"x" 本身。你对 "i" 做的任何动作,都会反映到 "x" 上;反之亦然。 
 
从最底层来看,参考最常用指标来实作,它的效果有点像 C 里头的「传指标呼叫」 
(pass-by-pointer),但 "&" 取址运算子由呼叫者换到被呼叫者之处了,你也要删 
去所有的 "*" 运算子。 
#109
yuyunliuhen2006-12-29 22:44
如果设定某值给参考会怎麽样? 
 
会更动到被参考者(referrent,该「参考」所参考到的物件)。 
  记住:「参考」就是「被参考者」,因此动了参考就会改动到被参考者(「参考」是 
「被参考者」的左值 "Lvalue"〔出现在设定陈述的左边〕)。 
 
更进一步,我们也允许参考被传回。这样子函数呼叫就可放在设定陈述的左边,这对 
运算子多载的场合很有用。 
#110
yuyunliuhen2006-12-29 22:46
行内函数是做什麽的? 
 
行内函数(inline function)是个程式码会塞入呼叫者所在之处的函数。就像巨集 
一样,行内函数免除了函数呼叫的额外负担,以增进效率,并且(尤其是!)还能让 
编译器对它施以最佳化(程序融合 "procedural integration")。不过和巨集不同 
的是:它只会对所有引数求一次的值(在语意上,该“函数呼叫”和正常函数一样, 
只是比较快速罢了),以避免某些不易察觉的巨集错误。此外,它还会检测引数的型 
态,做必要的型别转换(巨集对你有害;除非绝对必要,否则别再用它了)。 
 
注意:过度使用行内函数会让程式码肥胖,於分页(paging)环境下反而有负面的性 
能影响。 
宣告法:在函数定义处使用 "inline" 关键字: 
 
        inline void f(int i, char c) { /*...*/ } 
 
或者是在类别内将定义包括进去: 
 
        class Fred { 
        public: 
          void f(int i, char c) { /*...*/ } 
        }; 
 
或是在类别外头,以 "inline" 来定义该成员函数: 
 
        class Fred { 
        public: 
          void f(int i, char c); 
        }; 
 
        inline void Fred::f(int i, char c) { /*...*/ } 
#111
yuyunliuhen2006-12-29 22:46
建构子(constructor)是做什麽的? 
 
建构子乃用来从零开始建立物件。 
 
建构子就像个「初始化函数」;它把一堆散乱的位元组成一个活生生的物件。最低限 
度它会初始化内部用到的栏位,也可能会配置所须的资源(记忆体、档案、semaphore 
、socket 等等)。 
 
"ctor" 是建构子 constructor 最常见的缩写。
#112
yuyunliuhen2006-12-29 22:46
怎样才能让建构子呼叫另一个同处一室的建构子? 
 
没有办法。 
 
原因是:如果你呼叫另一个建构子,编译器会初始化一个暂时的区域性物件;但并没 
有初始化“这个”你想要的物件。你可以用预设参数(default parameter),将两 
个建构子合并起来,或是在私有的 "init()" 成员函数中共享它们的程式码。 
 
#113
yuyunliuhen2006-12-29 22:47
解构子(destructor)是做什麽的? 
 
解构子乃物件之葬礼。
解构子是用来释放该物件所配置到的资源,譬如:Lock 类别可能会锁住一个 
semaphore,解构子则用来释放它。最常见的例子是:当建构子用了 "new" 以後,解 
构子用 "delete"。 
 
解构子是个「去死吧」的运作行为(method),通常缩写为 "dtor"。 
 
#114
yuyunliuhen2006-12-29 22:47
运算子多载(operator overloading)是做什麽的? 
 
它可让使用类别的人以直觉来操作之。 
 
运算子多载让 C/C++ 的运算子,能对自订的型态(物件类别)赋予自订的意义。它 
们形同是函数呼叫的语法糖衣 (syntactic sugar): 
 
        class Fred { 
        public: 
          //... 
        }; 
 
        #if 0 
          Fred add(Fred, Fred);         //没有运算子多载 
          Fred mul(Fred, Fred); 
        #else 
          Fred operator+(Fred, Fred);   //有运算子多载 
          Fred operator*(Fred, Fred); 
        #endif 
 
        Fred f(Fred a, Fred b, Fred c) 
        { 
          #if 0 
            return add(add(mul(a,b), mul(b,c)), mul(c,a));  //没有... 
          #else 
            return a*b + b*c + c*a;                         //有... 
          #endif 
        } 
#115
yuyunliuhen2006-12-29 22:48
哪些运算子可以/不能被多载? 
 
大部份都可以被多载。 
不能的 C 运算子有 "." 和 "?:"(和以技术上来说,可算是运算子的 "sizeof")。 
C++ 增加了些自己的运算子,其中除了 "::" 和 ".*". 之外都可以被多载。 
 
底下是个足标(subscript)运算子的例子(它会传回一个参考)。最前面是“不用 ”多载的: 
 
        class Array { 
        public: 
          #if 0 
            int& elem(unsigned i) { if (i>99) error(); return data[i]; } 
          #else 
            int& operator[] (unsigned i) { if (i>99) error(); return data[i]; } 
          #endif 
        private: 
          int data[100]; 
        }; 
 
        main() 
        { 
          Array a; 
 
          #if 0 
            a.elem(10) = 42; 
            a.elem(12) += a.elem(13); 
          #else 
            a[10] = 42; 
            a[12] += a[13]; 
          #endif 
        } 
#116
yuyunliuhen2006-12-29 22:48
怎样做一个 "**"「次方」运算子? 
 
无解。 
 
运算子的名称、优先序、结合律以及元数(arity)都被语言所定死了。C++ 里没有 
"**" 运算子,所以你无法替类别订做一个它。 
 
还怀疑的话,考虑看看 "x ** y" 和 "x * (*y)",这两者是完全一样的(换句话说 
,编译器会假设 "y" 是个指标)。此外,运算子多载只是函数呼叫的语法糖衣而已 
,虽然甜甜的,但本质上并未增加什麽东西。我建议你多载 "pow(base,exponent)" 
这个函数(它的倍精确度版本在  中)。 
 
附带一提:operator^ 可以用,但它的优先序及结合律不符「次方」所需。
#117
yuyunliuhen2006-12-29 22:50
夥伴(friend)是什麽? 
 
让别的类别或函数能存取到你的类别内部的东西。 
夥伴可以是函数或其他类别。类别会对它的夥伴开放存取权限。正常情况下,程式员 
会下意识、技术性地控制该类别的夥伴与运作行为(否则当你想更动类别时,还得先 
有其他部份的拥有者之同意才行)。 
「夥伴」违反了封装性吗? 
 
若善用之,反而会「强化」封装性。 
 
我们经常得将一个类别切成两半,当这两半各有不同的案例个数及生命期时。在此情 
形之下,它们通常需要直接存取对方的内部(这两半“本来”是在同一个类别里面, 
所以你并未“增加”存取资料结构的运作行为个数;你只是在“搬动”这些运作行为 
所在之处而已)。最安全的实作方式,就是让这两半互为彼此的「夥伴」。 
 
若你如上述般的使用夥伴,你依然是将私有的东西保持在私有的状态。遇到上述的情 
况,如果还呆呆的想避免使用夥伴关系,许多人不是采用公共资料(糟透了!),就 
是弄个公共的 get/set 存取运作行为来存取彼此的资料,事实上这些都破坏了封装 
性。只有在类别的外面该私有资料「仍有其意义」(以使用者的角度来看)时,开放 
出私有资料的存取运作行为才称得上是恰当的做法。多数情况下,「存取运作行为」 
就和「公共资料」一样糟糕:它们对私有资料成员只隐其“名”而已,却未隐藏其“ 
存在”。 
 
同样的,如果将「夥伴函数」做为另一种类别公共存取函数的语法,那就和违反封装 
性的成员函数一样破坏了封装。换句话说,物件类别的夥伴及成员都是「封装的界线 
」,如同「类别定义」本身一样。 
夥伴函数的优缺点? 
 
它提供了某种介面设计上的自由。 
 
成员函数和夥伴函数都有同等的存取特权(100% 的权利),主要的差别在於:夥伴 
函数用起来像是 "f(x)",而成员函数则是 "x.f()"。因此,夥伴函数可让物件类别 
设计者挑选他看得最顺眼的语法,以降低维护成本。 
 
夥伴函数主要的缺点在於:当你想做动态系结(dynamic binding)时,它需要额外 
的程式码。想做出「虚拟夥伴」的效果,该夥伴函数应该呼叫个隐藏的(通常是放在 
"protected:" 里)虚拟成员函数;像这个样子:"void f(Base& b) { b.do_f(); }" 
。衍生类别会覆盖(override)掉那个隐藏的成员函数("void Derived::do_f()") 
,而不是该夥伴函数。 
「夥伴关系无继承及递移性」是什麽意思? 
 
夥伴关系的特权性无法被继承下来:夥伴的衍生类别不必然还是夥伴(我把你当朋友,但这不代表我也一定会信任你的孩子)。如果 "Base" 类别宣告了 "f()" 为它的 
夥伴,"f()" 并不会自动对由 "Base" 衍生出来的 "Derived" 类别所多出来的部份 
拥有特殊的存取权力。 
 
夥伴关系的特权无递移性:夥伴类别的夥伴不必然还是原类别的夥伴(朋友的朋友不 
一定也是朋友)。譬如,如果 "Fred" 类别宣告了 "Wilma" 类别为它的夥伴,而且 
"Wilma" 类别宣告了 "f()" 为它的夥伴,则 "f()" 不见得对 "Fred" 有特殊的存取 
权力。 
应该替类别宣告个成员函数,还是夥伴函数? 
 
可能的话,用成员函数;必要时,就用夥伴。 
 
有时在语法上来看,夥伴比较好(譬如:在 "Fred" 类别中,夥伴函数可把 "Fred" 
弄成是第二个参数,但在成员函数中则一定得放在第一个)。另一个好例子是:二元 
中序式算数运算子(譬如:"aComplex + aComplex" 可能应该定义成夥伴而非成员函 
数,因为你想让 "aFloat + aComplex" 这种写法也能成立;回想一下:成员函数无 
法提升它左侧的参数,因为那样会把引发该成员函数的物件所属之类别给改变掉)。 
 
在其他情况下,请选成员函数而不要用夥伴函数。 
#118
yuyunliuhen2006-12-29 22:50
该怎样替 "class Fred" 提供输出功能? 
 
用夥伴函数 operator<<: 
 
        class Fred { 
        public: 
          friend ostream& operator<< (ostream& o, const Fred& fred) 
            { return o << fred.i; } 
          //... 
        private: 
          int i;    //只为了说明起见而设的 
        }; 
 
我们用夥伴而不用成员函数,因为 "Fred" 是第二个参数而非第一个。输入的功能亦 
类似,只是要改写成: 
 
        istream& operator>> (istream& i, Fred& fred); 
                                      // ^^^^^------- 不是 "const Fred& fred"! 
 
#119
yuyunliuhen2006-12-29 22:51
为什麽我该用  而不是以前的 ? 
 
增加型别安全、减少错误、增进效率、有延展性、提供衍生能力。 
 
Printf 还好,而 scanf 除了容易写错之外也还算可以,然而和 C++ 的 I/O 系统相 
比,它们都有其限制。C++ 的 I/O(用 "<<" 及 ">>" ),和 C( "printf()" 和 
"scanf()" )相比: 
 
 * 型别安全--要做 I/O 的物件,编译器会静态地事先得知其型别,而不是动态地 
   由 "%" 一栏查知。 
 
 * 不易出错--冗馀的资讯会增加错误的机会。C++ 的 I/O 就不需要多馀的 "%"。 
 
 * 更快速--printf 是个小型语言的「解译器」,该语言主要是由 "%" 这种东西 
   构成的;在执行期它用这些栏位来选择正确的格式化方式。C++ 的 I/O 系统则是 
   静态的依各引数真正的型别来挑选副程式,以增进执行效率。 
 
 * 延展性--C++ I/O 机制可在不改动原有程式码的情况下,就加进使用者新设计 
   的型态(能想像如果大家同时把互不相容的 "%" 栏位塞入 printf 和 scanf,会 
   是怎样的混乱场面?!)。 
 
 * 可衍生(subclassable)--ostream 和 istream(C++ 的 FILE* 代替品)都是 
   真正的类别,因此可以被衍生下去。这意味著:你可以让其他自定的东西有著和 
   stream 雷同的外表与行为,但实际上做的却是你想做的特定事情。你自动就重用 
   了数以万计别人(你甚至不认识它们)写好的 I/O 程式码,而他们也不需要知道 
   你所做的「延伸 stream」类别。 
#120
yuyunliuhen2006-12-29 22:51
为什麽我处理输入时,会超过档案的结尾? 
 
因为 eof(档案结尾)的状态,是到「将要超过档案结尾的动作」才会被设定。也就 
是说,读档案的最後一个位元组并不会设定 eof 的状态。 
 
【译注】这也是 C 常见的错误。 
 
如果你的程式像这样: 
 
        int i = 0; 
        while (! cin.eof())  { 
          cin >> x; 
          ++i; 
          // work with x 
        } 
 
你的 i 变数就会多了一。 
你真正该做的是这样: 
 
        int i; 
        while (cin >> x)  { ++i; 
          // work with x 
        } 
#121
yuyunliuhen2007-01-05 22:48
什麽是「虚拟成员函数」? 
 
虚拟函数可让衍生的类别「取代」原基底类别所提供的运作。只要某物件是衍生出来 
的,就算我们是透过基底物件的指标,而不是以衍生物件的指标来存取该物件,编译 
器仍会确保「取代後」的成员函数被呼叫。这可让基底类别的演算法被衍生者所替换 
,即使我们不知道衍生类别长什麽样子。 
 
注意:衍生的类别亦可“部份”取代(覆盖,override)掉基底的运作行为(如有必 
要,衍生类别的运作行为亦可呼叫它的基底类别版本)
123