回 帖 发 新 帖 刷新版面

主题:C++的一些FAQ

Bjarne Stroustrup对一些常见问题的答复,不一定因为是Bjarne Stroustrup说的就怎么怎么样,不过毕竟他是个很有水平的

原文的地址为:http://www.research.att.com/~bs/bs_faq.html
[建议E文好的看E文的,我最开始没找到翻译的,就是自己对着金山词霸慢慢看的,不过看了些就放弃了...大家不要学我]

中文的从网上发现的,翻译为左轻侯,这里只是一部分的问题

问题                                        楼层
我如何写个非常简单的程序?                  1
为什么一个空类的大小不为 0?                2
为什么析构函数默认不是 virtual 的?         3
为什么不能有虚拟构造函数?                   4
我能够在构造函数中调用一个虚拟函数吗?      5 
为什么重载在继承类中不工作?                6
怎样将一个整型值转换为一个字符串?          7
我应该将“const”放在类型之前还是之后?     8
“int* p”正确还是“int *p”正确?          9
为什么 delete 不会将操作数置 0?            10
我能够写“void main()”吗?                 11
我如何定义一个类内部(in-class)的常量?    12
我为什么必须使用一个造型来转换*void?       13
有没有“指定位置删除”(placement delete)?  14
为什么编译要花这么长的时间?                15
我必须在类声明处赋予数据吗?                16
我能防止别人继承我自己的类吗?              17
为什么不能为模板参数定义约束(constraints)?18
什么是函数对象(function object)?         19
我应该如何对付内存泄漏?                    20
我为什么在捕获一个异常之后就不能继续?      21
怎样从输入中读取一个字符串?                22
为什么 C++不提供“finally”的构造?         23
为什么我不能重载点符号,::,sizeof,等等?  24 
使用宏有什么问题?                          25



如果大家感觉好,希望顶一顶,多让些学习C++的朋友们都学习学习. [em2]

加油.!~~~~~~~~~~~~~~~~~ 同志们!

回复列表 (共61个回复)

21 楼

我为什么在捕获一个异常之后就不能继续? 
  
换句话说,C++为什么不提供一种简单的方式,让程序能够回到异常抛出点之后,并继续执
行? 
  
主要的原因是,如果从异常处理之后继续,那么无法预知掷出点之后的代码如何对待异常处
理,是否仅仅继续执行,就象什么也没有发生一样。异常处理者无法知道,在继续之前,有
关的上下文环境(context)是否是“正确”的。要让这样的代码正确执行,抛出异常的
编写者与捕获异常的编写者必须对彼此的代码与上下文环境都非常熟悉才行。这样会产生非
常复杂的依赖性,因此无论在什么情况下,都会导致一系列严重的维护问题。 
  
当我设计C++的异常处理机制时,我曾经认真地考虑过允许这种继续的可能性,而且在标准
化的过程中,这个问题被非常详细地讨论过。请参见《C++语言的设计和演变》中的异常处
理章节。

22 楼

怎样从输入中读取一个字符串? 
  
你可以用这种方式读取一个单独的以空格结束的词: 
  
    #include<iostream> 
    #include<string> 
    using namespace std; 
  
    int main() 
    { 
        cout << "Please enter a word:\n"; 
  
        string s; 
        cin>>s; 
     
        cout << "You entered " << s << '\n'; 
    } 
  
注意,这里没有显式的内存管理,也没有可能导致溢出的固定大小的缓冲区。 
  
如果你确实想得到一行而不是一个单独的词,可以这样做: 
  
  
    #include<iostream> 
    #include<string> 
    using namespace std; 
  
    int main() 
    { 
        cout << "Please enter a line:\n"; 
  
        string s; 
        getline(cin,s); 
     
        cout << "You entered " << s << '\n'; 
    } 
  

23 楼

为什么 C++不提供“finally”的构造? 
  
因为 C++提供了另外一种方法,它几乎总是更好的:“资源获得即初始化”(resource 
acquisiton is initialization)技术。基本的思路是,通过一个局部对象来表现资
源,于是局部对象的析构函数将会释放资源。这样,程序员就不会忘记释放资源了。举例来
说: 
  
    class File_handle { 
        FILE* p; 
    public: 
        File_handle(const char* n, const char* a) 
            { p = fopen(n,a); if (p==0) throw Open_error(errno); } 
        File_handle(FILE* pp) 
            { p = pp; if (p==0) throw Open_error(errno); } 
  
        ~File_handle() { fclose(p); } 
  
        operator FILE*() { return p; } 
  
        // ... 
    }; 
  
    void f(const char* fn) 
    { 
        File_handle f(fn,"rw"); //打开fn进行读写 
        // 通过f 使用文件 
    } 
  
在一个系统中,需要为每一个资源都使用一个“资源句柄”类。无论如何,我们不需要为每
一个资源获得都写出“finally”语句。在实时系统中,资源获得要远远多于资源的种类,
因此和使用“finally”构造相比,“资源获得即初始化”技术会产生少得多的代码。

24 楼

为什么我不能重载点符号,::,sizeof,等等? 
  
大多数的运算符能够被程序员重载。例外的是: 
  
    . (点符号)  ::  ?:  sizeof 
  
并没有什么根本的原因要禁止重载?:。仅仅是因为,我没有发现有哪种特殊的情况需要重
载一个三元运算符。注意一个重载了 表达式1?表达式2:表达式 3 的函数,不能够保证
表达式 2:表达式3 中只有一个会被执行。 
  
Sizeof 不能够被重载是因为内建的操作(built-in operations),诸如对一个指向
数组的指针进行增量操作,必须依靠它。考虑一下: 
  
    X a[10]; 
    X* p = &a[3]; 
    X* q = &a[3]; 
    p++;    // p指向a[4] 
        // 那么p 的整型值必须比 q的整型值大出一个 sizeof(X) 
  
所以,sizeof(X)不能由程序员来赋予一个不同的新意义,以免违反基本的语法。 
  
在 N::m 中,无论 N 还是 m 都不是值的表达式;N 和 m 是编译器知道的名字,::执行一个
(编译期的)范围解析,而不是表达式求值。你可以想象一下,允许重载 x::y的话,x 可
能是一个对象而不是一个名字空间(namespace)或者一个类,这样就会导致——与原来
的表现相反——产生新的语法(允许 表达式 1::表达式 2)。很明显,这种复杂性不会带
来任何好处。 
  
理论上来说,.(点运算符)可以通过使用和->一样的技术来进行重载。但是,这样做会导
致一个问题,那就是无法确定操作的是重载了.的对象呢,还是通过.引用的一个对象。例
如: 
  
  
    class Y { 
    public: 
        void f(); 
        // ... 
    }; 
  
    class X {   // 假设你能重载. 
        Y* p; 
        Y& operator.() { return *p; } 
        void f(); 
        // ... 
    }; 
  
    void g(X& x) 
    { 
        x.f();  // X::f还是Y::f还是错误? 
    } 
  
这个问题能够用几种不同的方法解决。在标准化的时候,哪种方法最好还没有定论。更多的
细节,请参见《C++语言的设计和演变》。 

25 楼

使用宏有什么问题? 
  
宏不遵循C++中关于范围和类型的规则。这经常导致一些微妙的或不那么微妙的问题。因此,
C++提供更适合其他的 C++(译注:原文为 the rest of C++,当指 C++除了兼容 C 以
外的部分)的替代品,例如内联函数、模板与名字空间。 
  
考虑一下: 
  
    #include "someheader.h" 
  
    struct S { 
        int alpha; 
        int beta; 
    }; 
  
如果某人(不明智地)地写了一个叫“alpha”或“beta”的宏,那么它将不会被编译,
或者被错误地编译,产生不可预知的结果。例如,“someheader.h”可能包含: 
  
    #define alpha 'a' 
    #define beta b[2] 
  
将宏(而且仅仅是宏)全部大写的习惯,会有所帮助,但是对于宏并没有语言层次上的保护
机制。例如,虽然成员的名字包含在结构体的内部,但这无济于事:在编译器能够正确地辨
别这一点之前,宏已经将程序作为一个字符流进行了处理。顺便说一句,这是 C 和 C++程
序开发环境和工具能够被简化的一个主要原因:人与编译器看到的是不同的东西。 
  
不幸的是,你不能假设别的程序员总是能够避免这种你认为“相当白痴”的事情。例如,最
近有人报告我,他们遇到了一个包含 goto 的宏。我也见过这种情况,而且听到过一些——
在很脆弱的时候——看起来确实有理的意见。例如: 
  
    #define prefix get_ready(); int ret__ 
    #define Return(i) ret__=i; do_something(); goto exit 
    #define suffix exit: cleanup(); return ret__ 
  
    void f() 
    { 
        prefix; 
        // ... 
        Return(10); 
        // ... 
        Return(x++); 
        //... 
        suffix; 
    } 
  
作为一个维护的程序员,就会产生这种印象;将宏“隐藏”到一个头文件中——这并不罕见
——使得这种“魔法”更难以被辨别。 
  
一个常见的微妙问题是,一个函数风格的宏并不遵守函数参数传递的规则。例如: 
  
    #define square(x) (x*x) 
  
    void f(double d, int i) 
    { 
        square(d);  // 好 
        square(i++);    // 糟糕:这表示 (i++*i++) 
        square(d+1);    //糟糕:这表示(d+1*d+1); 也就是 (d+d+1) 
        // ... 
    } 
  
“d+1”的问题,可以通过在“调用”时或宏定义时添加一对圆括号来解决: 
  
    #define square(x) ((x)*(x)) /*这样更好 */ 
  
但是, i++被执行了两次(可能并不是有意要这么做)的问题仍然存在。 
  
是的,我确实知道有些特殊的宏并不会导致 C/C++预处理宏这样的问题。但是,我无心去
发展 C++中的宏。作为替代,我推荐使用 C++语言中合适的工具,例如内联函数,模板,
构造函数(用来初始化),析构函数(用来清除),异常(用来退出上下文环境),等等。 

26 楼

辛苦了

27 楼

这些问题还不是初学者普遍的问题。

28 楼


这个简单程序,我不能解决 正常结束输入 这个问题[em10]

29 楼

听BS说话就想睡觉...

30 楼

格子的帖子都是那么的精华啊!
顶一下!

我来回复

您尚未登录,请登录后再回复。点此登录或注册