| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 12982 人关注过本帖, 3 人收藏
标题:[分享]C++的一些FAQ
只看楼主 加入收藏
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏(3)
 问题点数:0 回复次数:69 
[分享]C++的一些FAQ
Bjarne Stroustrup对一些常见问题的答复,不一定因为是Bjarne Stroustrup说的就怎么怎么样,不过毕竟他是个很有水平的

原文的地址为:http://www.research.att.com/~bs/bs_faq.html

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

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

特别是在一个学期的开始,我常常收到许多关于编写一个非常简单的程序的询问。这个问题
有一个很具代表性的解决方法,那就是(在你的程序中)读入几个数字,对它们做一些处理,
再把结果输出。下面是一个这样做的例子:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main()
{
vector<double> v;

double d;
while(cin>>d) v.push_back(d); // 读入元素
if (!cin.eof()) { // 检查输入是否出错
cerr << "format error\n";
return 1; // 返回一个错误
}

cout << "read " << v.size() << " elements\n";

reverse(v.begin(),v.end());
cout << "elements in reverse order:\n";
for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

return 0; // 成功返回
}

对这段程序的观察:

这是一段标准的 ISO C++程序,使用了标准库(standard library)。标准库工具在命
名空间std中声明,封装在没有.h 后缀的头文件中。

如果你要在 Windows 下编译它,你需要将它编译成一个“控制台程序”(console
application)。记得将源文件加上.cpp 后缀,否则编译器可能会以为它是一段 C 代码
而不是 C++。

是的,main()函数返回一个 int值。

读到一个标准的向量(vector)中,可以避免在随意确定大小的缓冲中溢出的错误。读到一
个数组(array)中,而不产生“简单错误”(silly error),这已经超出了一个新手的能
力——如果你做到了,那你已经不是一个新手了。如果你对此表示怀疑,我建议你阅读我的
文章“将标准 C++作为一种新的语言来学习”("Learning Standard C++ as a New
Language"),你可以在本人著作列表(my publications list)中下载到它。

!cin.eof()是对流的格式的检查。事实上,它检查循环是否终结于发现一个
end-of-file(如果不是这样,那么意味着输入没有按照给定的格式)。
见你的C++教科书中的“流状态”(stream state)部分。

vector知道它自己的大小,因此我不需要计算元素的数量。

这段程序没有包含显式的内存管理。Vector维护一个内存中的栈,以存放它的元素。当一
个 vector 需要更多的内存时,它会分配一些;当它不再生存时,它会释放内存。于是,
使用者不需要再关心 vector中元素的内存分配和释放问题。

程序在遇到输入一个“end-of-file”时结束。如果你在 UNIX 平台下运行它,
“end-of-file”等于键盘上的 Ctrl+D。如果你在 Windows平台下,那么由于一个BUG
它无法辨别“end-of-file”字符,你可能倾向于使用下面这个稍稍复杂些的版本,它使
用一个词“end”来表示输入已经结束。

#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;

int main()
{
vector<double> v;

double d;
while(cin>>d) v.push_back(d); // 读入一个元素
if (!cin.eof()) { // 检查输入是否失败
cin.clear(); // 清除错误状态
string s;
cin >> s; // 查找结束字符
if (s != "end") {
cerr << "format error\n";
return 1; // 返回错误
}
}

cout << "read " << v.size() << " elements\n";

reverse(v.begin(),v.end());
cout << "elements in reverse order:\n";
for (int i = 0; i<v.size(); ++i) cout << v[i] << '\n';

return 0; // 成功返回
}

[此贴子已经被作者于2006-7-10 8:57:26编辑过]


2006-07-06 17:01
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
为什么一个空类的大小不为 0?

要清楚,两个不同的对象的地址也是不同的。基于同样的理由,new总是返回指向不同对象
的指针。
看看:

class Empty { };

void f()
{
Empty a, b;
if (&a == &b) cout << "impossible: report error to compiler supplier";

Empty* p1 = new Empty;
Empty* p2 = new Empty;
if (p1 == p2) cout << "impossible: report error to compiler supplier";
}

有一条有趣的规则:一个空的基类并不一定有分隔字节。

struct X : Empty {
int a;
// ...
};

void f(X* p)
{
void* p1 = p;
void* p2 = &p->a;
if (p1 == p2) cout << "nice: good optimizer";
}

这种优化是允许的,可以被广泛使用。它允许程序员使用空类以表现一些简单的概念。现在
有些编译器提供这种“空基类优化”(empty base class optimization)。

[此贴子已经被作者于2006-7-10 8:58:22编辑过]


2006-07-06 17:02
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
为什么析构函数默认不是 virtual 的?

因为很多类并不是被设计作为基类的。只有类在行为上是它的派生类的接口时(这些派生类
往往在堆中分配,通过指针或引用来访问),虚拟函数才有意义。

那么什么时候才应该将析构函数定义为虚拟呢?当类至少拥有一个虚拟函数时。拥有虚拟函
数意味着一个类是派生类的接口,在这种情况下,一个派生类的对象可能通过一个基类指针
来销毁。例如:

class Base {
// ...
virtual ~Base();
};

class Derived : public Base {
// ...
~Derived();
};

void f()
{
Base* p = new Derived;
delete p; // 虚拟析构函数保证~Derived函数被调用
}

如果基类的析构函数不是虚拟的,那么派生类的析构函数将不会被调用——这可能产生糟糕
的结果,例如派生类的资源不会被释放。

2006-07-06 17:09
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
为什么不能有虚拟构造函数?

虚拟调用是一种能够在给定信息不完全(given partial information)的情况下工作
的机制。特别地,虚拟允许我们调用某个函数,对于这个函数,仅仅知道它的接口,而不知
道具体的对象类型。但是要建立一个对象,你必须拥有完全的信息。特别地,你需要知道要
建立的对象的具体类型。因此,对构造函数的调用不可能是虚拟的。

当要求建立一个对象时,一种间接的技术常常被当作“虚拟构造函数”来使用。有关例子,
请参见《C++程序设计语言》第三版 15.6.2.节。

下面这个例子展示一种机制:如何使用一个抽象类来建立一个适当类型的对象。

struct F { // 对象建立函数的接口
virtual A* make_an_A() const = 0;
virtual B* make_a_B() const = 0;
};

void user(const F& fac)
{
A* p = fac.make_an_A(); // 将A 作为合适的类型
B* q = fac.make_a_B(); // 将B 作为合适的类型
// ...
}

struct FX : F {
A* make_an_A() const { return new AX(); } // AX是A 的派生
B* make_a_B() const { return new BX(); } // AX是 B的派生
};

struct FY : F {
A* make_an_A() const { return new AY(); } // AY是A 的派生
B* make_a_B() const { return new BY(); } // BY是B的派生

};

int main()
{
user(FX()); // 此用户建立 AX 与BX
user(FY()); // 此用户建立 AY 与BY
// ...
}

这是所谓的“工厂模式”(the factory pattern)的一个变形。关键在于,user 函数
与 AX 或AY这样的类的信息被完全分离开来了。

2006-07-06 17:10
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
我能够在构造函数中调用一个虚拟函数吗?

可以,但是要小心。它可能不象你期望的那样工作。在构造函数中,虚拟调用机制不起作用,
因为继承类的重载还没有发生。对象先从基类被创建,“基类先于继承类(base before
derived)”。

看看这个:

#include<string>
#include<iostream>
using namespace std;

class B {
public:
B(const string& ss) { cout << "B constructor\n"; f(ss); }
virtual void f(const string&) { cout << "B::f\n";}
};

class D : public B {
public:
D(const string & ss) :B(ss) { cout << "D constructor\n";}
void f(const string& ss) { cout << "D::f\n"; s = ss; }
private:
string s;
};

int main()
{
D d("Hello");
}

程序编译以后会输出:

B constructor
B::f
D constructor

注意不是 D::f。设想一下,如果出于不同的规则,B::B()可以调用 D::f()的话,会产
生什么样的后果:因为构造函数 D::D()还没有运行,D::f()将会试图将一个还没有初始
化的字符串 s赋予它的参数。结果很可能是导致立即崩溃。

析构函数在“继承类先于基类”的机制下运行,因此虚拟机制的行为和构造函数一样:只有
本地定义(local definitions)被使用——不会调用虚拟函数,以免触及对象中的(现
在已经被销毁的)继承类的部分。


有人暗示,这只是一条实现时的人为制造的规则。不是这样的。事实上,要实现这种不安全
的方法倒是非常容易的:在构造函数中直接调用虚拟函数,就象调用其它函数一样。但是,
这样就意味着,任何虚拟函数都无法编写了,因为它们需要依靠基类的固定的创建
(invariants established by base classes)。这将会导致一片混乱。

2006-07-06 17:10
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
为什么重载在继承类中不工作?

这个问题(非常常见)往往出现于这样的例子中:

#include<iostream>
using namespace std;

class B {
public:
int f(int i) { cout << "f(int): "; return i+1; }
// ...
};

class D : public B {
public:
double f(double d) { cout << "f(double): "; return d+1.3; }
// ...
};

int main()
{
D* pd = new D;

cout << pd->f(2) << '\n';
cout << pd->f(2.3) << '\n';
}

它输出的结果是:

f(double): 3.3
f(double): 3.6

而不是象有些人猜想的那样:

f(int): 3
f(double): 3.6

换句话说,在 B 和 D 之间并没有发生重载的解析。编译器在 D 的区域内寻找,找到了一个
函数double f(double),并执行了它。它永远不会涉及(被封装的)B的区域。在C++
中,没有跨越区域的重载——对于这条规则,继承类也不例外。更多的细节,参见《C++语
言的设计和演变》和《C++程序设计语言》。

但是,如果我需要在基类和继承类之间建立一组重载的 f()函数呢?很简单,使用 using
声明:

class D : public B {
public:
using B::f; // make every f from B available
double f(double d) { cout << "f(double): "; return d+1.3; }
// ...
};

进行这个修改之后,输出结果将是:

f(int): 3
f(double): 3.6

这样,在 B 的 f()和 D 的 f()之间,重载确实实现了,并且选择了一个最合适的 f()进行
调用。

2006-07-06 17:11
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
怎样将一个整型值转换为一个字符串?

最简单的方法是使用一个字符串流(stringstream):

#include<iostream>
#include<string>
#include<sstream>
using namespace std;

string itos(int i) // 将int 转换成string
{
stringstream s;
s << i;
return s.str();
}

int main()
{
int i = 127;
string ss = itos(i);
const char* p = ss.c_str();

cout << ss << " " << p << "\n";
}

自然地,这种技术能够将任何使用<<输出的类型转换为字符串。对于字符串流的更多说明,
参见《C++程序设计语言》21.5.3 节。

2006-07-06 17:11
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
我应该将“const”放在类型之前还是之后?

我把它放在前面,但那仅仅是个人爱好问题。“const T”和“T const”总是都被允许
的,而且是等效的。例如:

const int a = 1; // ok
int const b = 2; // also ok

我猜想第一种版本可能会让少数(更加固守语法规范)的程序员感到迷惑。

为什么?当我发明“const”(最初的名称叫做“readonly”,并且有一个对应的
“writeonly”)的时候,我就允许它出现在类型之前或之后,因为这样做不会带来任何
不明确。标准之前的 C和C++规定了很少的(如果有的话)特定的顺序规范。

我不记得当时有过任何有关顺序问题的深入思考或讨论。那时,早期的一些使用者——特别
是我——仅仅喜欢这种样子:

const int c = 10;

看起来比这种更好:

int const c = 10;

也许我也受了这种影响:在我最早的一些使用“readonly”的例子中

readonly int c = 10;

比这个更具有可读性:

int readonly c = 10;

我创造的那些最早的使用“const”的(C 或 C++)代码,看来已经在全球范围内取代了
“readonly”。

我记得这个语法的选择在几个人——例如 Dennis Ritchie——当中讨论过,但我不记得
当时我倾向于哪种语言了。

注意在固定指针(const pointer)中,“const”永远出现在“*”之后。例如:

int *const p1 = q; // 指向 int变量的固定指针
int const* p2 = q; //指向int 常量的指针
const int* p3 = q; //指向int 常量的指针


2006-07-06 17:12
woodhead
Rank: 3Rank: 3
等 级:新手上路
威 望:9
帖 子:1124
专家分:0
注 册:2005-7-18
收藏
得分:0 
“int* p”正确还是“int *p”正确?

二者都是正确的,因为二者在 C 和 C++中都是有效的,而且意义完全一样。就语言的定义
与相关的编译器来说,我们还可以说“int*p”或者“int * p”。

在“int* p”和“int *p”之间的选择与正确或错误无关,而只关乎风格与侧重点。C
侧重表达式;对声明往往比可能带来的问题考虑得更多。另一方面,C++则非常重视类型。

一个“典型的 C 程序员”写成“int *p”,并且解释说“*p 表示一个什么样的 int”以
强调语法,而且可能指出 C(与 C++)的语法来证明这种风格的正确性。是的,在语法上*
被绑定到名字p 上。

一个“典型的 C++程序员”写成“int* p”,并且解释说“p是一个指向 int 的指针类型”
以强调类型。是的,p 是一个指向 int 的指针类型。我明确地倾向于这种侧重方向,而且
认为对于学好更多的高级C++这是很重要的。

严重的混乱(仅仅)发生在当人们试图在一条声明中声明几个指针的时候:

int* p, p1; // 也许是错的:p1不是一个 int*

把*放到名字这一边,看来也不能有效地减少这种错误:

int *p, p1; // 也许是错的?

为每一个名字写一条声明最大程度地解决了问题——特别是当我们初始化变量的时候。人们
几乎不会这样写:

int* p = &i;
int p1 = p; // 错误:int用一个int*初始化了

如果他们真的这么干了,编译器也会指出。

每当事情可以有两种方法完成,有人就会迷惑。每当事情仅仅是一个风格的问题,争论就会
没完没了。为每一个指针写一条声明,而且永远都要初始化变量,这样,混乱之源就消失了。

2006-07-06 17:12
快速回复:[分享]C++的一些FAQ
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.016523 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved